mirror of
https://github.com/gianfrancopiana/openclaw-autoresearch.git
synced 2026-08-14 00:48:06 +00:00
chore: add release prepare command
This commit is contained in:
@@ -132,7 +132,7 @@ npm run smoke:openclaw-host -- /absolute/path/to/openclaw
|
||||
npm run smoke:registry-openclaw-host -- <published-version> /absolute/path/to/openclaw
|
||||
```
|
||||
|
||||
Release instructions, including GitHub Actions trusted publishing with npm provenance, live in [`RELEASING.md`](RELEASING.md).
|
||||
Release instructions, including `npm run release:prepare -- <version> --host /absolute/path/to/openclaw` and GitHub Actions trusted publishing with npm provenance, live in [`RELEASING.md`](RELEASING.md).
|
||||
|
||||
The local test shim supports typechecking and tests without a full OpenClaw host checkout. Runtime behavior depends on a real OpenClaw host, so run the host smoke against a current checkout before release.
|
||||
|
||||
|
||||
+16
-19
@@ -11,32 +11,29 @@
|
||||
|
||||
## Release
|
||||
|
||||
1. Update the package version in `package.json`, then sync generated metadata:
|
||||
1. Prepare the version bump from a clean branch:
|
||||
|
||||
```bash
|
||||
npm run sync:release-metadata
|
||||
npm run release:prepare -- <version> --host /absolute/path/to/openclaw
|
||||
```
|
||||
|
||||
If you change the minimum supported OpenClaw version, keep
|
||||
`openclaw.install`, `openclaw.compat`, and `openclaw.build` aligned too.
|
||||
This updates `package.json`, `openclaw.plugin.json`, and `package-lock.json`,
|
||||
syncs generated metadata, checks the matching `v<version>` tag, runs
|
||||
`release:verify`, and smoke-tests against the supplied OpenClaw checkout.
|
||||
|
||||
2. Run the release checks:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run release:verify
|
||||
```
|
||||
|
||||
CI runs the same release verification, and `prepublishOnly` runs it again
|
||||
before any npm publish.
|
||||
|
||||
3. Smoke-test against a current local OpenClaw checkout:
|
||||
If you do not have a host checkout available, omit `--host` and run the host
|
||||
smoke before publishing:
|
||||
|
||||
```bash
|
||||
npm run smoke:openclaw-host -- /absolute/path/to/openclaw
|
||||
```
|
||||
|
||||
4. Create and publish the matching GitHub release/tag:
|
||||
If you change the minimum supported OpenClaw version, keep
|
||||
`openclaw.install`, `openclaw.compat`, and `openclaw.build` aligned too.
|
||||
|
||||
2. Open and merge a PR with the version and metadata changes.
|
||||
|
||||
3. Create and publish the matching GitHub release/tag from `main`:
|
||||
|
||||
```bash
|
||||
npm run release:check-tag -- v<version>
|
||||
@@ -46,7 +43,7 @@
|
||||
Publishing the GitHub release triggers `.github/workflows/npm-publish.yml`,
|
||||
which publishes the package to npm with provenance through GitHub OIDC.
|
||||
|
||||
5. Watch the publish workflow and verify npm:
|
||||
4. Watch the publish workflow and verify npm:
|
||||
|
||||
```bash
|
||||
gh run list --workflow npm-publish.yml --limit 1
|
||||
@@ -54,13 +51,13 @@
|
||||
npm view @gianfrancopiana/openclaw-autoresearch@<version> version
|
||||
```
|
||||
|
||||
6. Verify the published registry tarball against the same host:
|
||||
5. Verify the published registry tarball against the same host:
|
||||
|
||||
```bash
|
||||
npm run smoke:registry-openclaw-host -- <published-version> /absolute/path/to/openclaw
|
||||
```
|
||||
|
||||
7. Verify install:
|
||||
6. Verify install:
|
||||
|
||||
```bash
|
||||
openclaw plugins install @gianfrancopiana/openclaw-autoresearch
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"validate": "npm run check:release-metadata && npm run typecheck && npm run test",
|
||||
"release:verify": "npm run validate && npm pack --dry-run",
|
||||
"release:check-tag": "node ./scripts/check-release-tag.mjs",
|
||||
"release:prepare": "node ./scripts/prepare-release.mjs",
|
||||
"prepublishOnly": "npm run release:verify",
|
||||
"smoke:openclaw-host": "node ./scripts/smoke-openclaw-host.mjs",
|
||||
"smoke:registry-openclaw-host": "node ./scripts/smoke-openclaw-registry.mjs"
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env node
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { readFileSync, writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const defaultRepoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const versionPattern = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
||||
const usage = [
|
||||
"Usage: npm run release:prepare -- <version> [--host /path/to/openclaw] [--skip-verify]",
|
||||
"",
|
||||
"Bumps package/plugin metadata, refreshes the lockfile, runs release checks, and prints next steps.",
|
||||
].join("\n");
|
||||
|
||||
function fail(message) {
|
||||
process.stderr.write(`${message}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function readJson(file) {
|
||||
return JSON.parse(readFileSync(file, "utf8"));
|
||||
}
|
||||
|
||||
function writeJson(file, value) {
|
||||
writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
const options = {
|
||||
version: undefined,
|
||||
host: undefined,
|
||||
repoRoot: defaultRepoRoot,
|
||||
skipVerify: false,
|
||||
};
|
||||
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const arg = argv[index];
|
||||
if (arg === "--host") {
|
||||
options.host = argv[index + 1];
|
||||
index += 1;
|
||||
} else if (arg === "--repo") {
|
||||
options.repoRoot = path.resolve(argv[index + 1] ?? "");
|
||||
index += 1;
|
||||
} else if (arg === "--skip-verify") {
|
||||
options.skipVerify = true;
|
||||
} else if (!options.version) {
|
||||
options.version = arg;
|
||||
} else {
|
||||
fail(`${usage}\n\nUnexpected argument: ${arg}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!options.version || !versionPattern.test(options.version)) {
|
||||
fail(usage);
|
||||
}
|
||||
|
||||
if (options.host === undefined && argv.includes("--host")) {
|
||||
fail(`${usage}\n\n--host requires a path`);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
function run(command, args, cwd) {
|
||||
const result = spawnSync(command, args, {
|
||||
cwd,
|
||||
encoding: "utf8",
|
||||
env: process.env,
|
||||
maxBuffer: 16 * 1024 * 1024,
|
||||
});
|
||||
|
||||
if (result.stdout) {
|
||||
process.stdout.write(result.stdout);
|
||||
}
|
||||
if (result.stderr) {
|
||||
process.stderr.write(result.stderr);
|
||||
}
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
if (result.status !== 0) {
|
||||
fail(`${command} ${args.join(" ")} failed with exit ${result.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
function prepareRelease(options) {
|
||||
const repoRoot = path.resolve(options.repoRoot);
|
||||
const packagePath = path.join(repoRoot, "package.json");
|
||||
const manifestPath = path.join(repoRoot, "openclaw.plugin.json");
|
||||
const packageJson = readJson(packagePath);
|
||||
const manifest = readJson(manifestPath);
|
||||
|
||||
packageJson.version = options.version;
|
||||
manifest.version = options.version;
|
||||
writeJson(packagePath, packageJson);
|
||||
writeJson(manifestPath, manifest);
|
||||
|
||||
run("npm", ["run", "sync:release-metadata"], repoRoot);
|
||||
run("npm", ["install", "--package-lock-only"], repoRoot);
|
||||
run("npm", ["run", "release:check-tag", "--", `v${options.version}`], repoRoot);
|
||||
|
||||
if (!options.skipVerify) {
|
||||
run("npm", ["run", "release:verify"], repoRoot);
|
||||
}
|
||||
|
||||
if (options.host) {
|
||||
run("npm", ["run", "smoke:openclaw-host", "--", options.host], repoRoot);
|
||||
}
|
||||
|
||||
process.stdout.write(`\nRelease prep complete for v${options.version}\n`);
|
||||
process.stdout.write("Next steps:\n");
|
||||
process.stdout.write("- Review the git diff\n");
|
||||
process.stdout.write("- Open and merge a PR with the version/metadata changes\n");
|
||||
process.stdout.write(`- After merge, create the GitHub release: gh release create v${options.version} --target main --title v${options.version} --notes-file release-notes.md\n`);
|
||||
}
|
||||
|
||||
try {
|
||||
prepareRelease(parseArgs(process.argv.slice(2)));
|
||||
} catch (error) {
|
||||
fail(error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const script = fileURLToPath(new URL("../scripts/prepare-release.mjs", import.meta.url));
|
||||
|
||||
function writeJson(file: string, value: unknown) {
|
||||
writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
|
||||
}
|
||||
|
||||
describe("release prepare command", () => {
|
||||
it("bumps metadata, refreshes the lockfile, and prints release next steps", () => {
|
||||
const repo = mkdtempSync(path.join(tmpdir(), "openclaw-autoresearch-release-prep-test-"));
|
||||
const log = path.join(repo, "commands.log");
|
||||
|
||||
writeJson(path.join(repo, "package.json"), {
|
||||
name: "@gianfrancopiana/openclaw-autoresearch",
|
||||
version: "1.0.8",
|
||||
type: "module",
|
||||
scripts: {
|
||||
"sync:release-metadata": "node ./sync.mjs",
|
||||
"release:check-tag": "node ./check-tag.mjs",
|
||||
"release:verify": "node ./verify.mjs",
|
||||
"smoke:openclaw-host": "node ./smoke.mjs",
|
||||
},
|
||||
});
|
||||
writeJson(path.join(repo, "openclaw.plugin.json"), {
|
||||
name: "openclaw-autoresearch",
|
||||
version: "1.0.8",
|
||||
});
|
||||
writeFileSync(
|
||||
path.join(repo, "sync.mjs"),
|
||||
`import { readFileSync, writeFileSync, appendFileSync } from 'node:fs';\nconst pkg = JSON.parse(readFileSync('package.json', 'utf8'));\nconst manifest = JSON.parse(readFileSync('openclaw.plugin.json', 'utf8'));\nmanifest.version = pkg.version;\nwriteFileSync('openclaw.plugin.json', JSON.stringify(manifest, null, 2) + '\\n');\nappendFileSync(${JSON.stringify(log)}, 'sync\\n');\n`,
|
||||
);
|
||||
writeFileSync(
|
||||
path.join(repo, "check-tag.mjs"),
|
||||
`import { appendFileSync } from 'node:fs';\nappendFileSync(${JSON.stringify(log)}, 'check ' + process.argv.at(-1) + '\\n');\n`,
|
||||
);
|
||||
writeFileSync(
|
||||
path.join(repo, "verify.mjs"),
|
||||
`import { appendFileSync } from 'node:fs';\nappendFileSync(${JSON.stringify(log)}, 'verify\\n');\n`,
|
||||
);
|
||||
writeFileSync(
|
||||
path.join(repo, "smoke.mjs"),
|
||||
`import { appendFileSync } from 'node:fs';\nappendFileSync(${JSON.stringify(log)}, 'smoke ' + process.argv.at(-1) + '\\n');\n`,
|
||||
);
|
||||
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
[script, "2.0.0", "--repo", repo, "--skip-verify", "--host", "/tmp/openclaw-host"],
|
||||
{ encoding: "utf8" },
|
||||
);
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(JSON.parse(readFileSync(path.join(repo, "package.json"), "utf8")).version).toBe("2.0.0");
|
||||
expect(JSON.parse(readFileSync(path.join(repo, "openclaw.plugin.json"), "utf8")).version).toBe("2.0.0");
|
||||
expect(readFileSync(log, "utf8")).toContain("sync\ncheck v2.0.0\nsmoke /tmp/openclaw-host\n");
|
||||
expect(result.stdout).toContain("Release prep complete for v2.0.0");
|
||||
expect(result.stdout).toContain("gh release create v2.0.0");
|
||||
});
|
||||
|
||||
it("rejects versions that are not plain semver", () => {
|
||||
const result = spawnSync(process.execPath, [script, "not-a-version"], { encoding: "utf8" });
|
||||
|
||||
expect(result.status).not.toBe(0);
|
||||
expect(result.stderr).toContain("Usage: npm run release:prepare -- <version>");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user