mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 20:05:46 -06:00
fix(release): route core to extended-stable
Adapt the closed publish-plan and tarball identity checks from upstream c7810fc697 for the v6.11 publisher.
This commit is contained in:
@@ -40,25 +40,43 @@ if [[ -n "${publish_target}" && -f "${publish_target}" ]]; then
|
||||
fi
|
||||
|
||||
package_version="$(node -p "require('./package.json').version")"
|
||||
mapfile -t publish_plan < <(
|
||||
if [[ -n "${publish_target}" ]]; then
|
||||
if [[ ! -f "${publish_target}" ]]; then
|
||||
echo "error: npm publish tarball not found: ${publish_target}" >&2
|
||||
exit 2
|
||||
fi
|
||||
if ! tarball_package_json="$(tar -xOf "${publish_target}" package/package.json)"; then
|
||||
echo "error: npm publish tarball is missing a readable package/package.json: ${publish_target}" >&2
|
||||
exit 2
|
||||
fi
|
||||
if ! tarball_package_version="$(printf '%s' "${tarball_package_json}" | node -e '
|
||||
let input = "";
|
||||
process.stdin.on("data", (chunk) => { input += chunk; });
|
||||
process.stdin.on("end", () => {
|
||||
const pkg = JSON.parse(input);
|
||||
if (!pkg || typeof pkg !== "object" || Array.isArray(pkg) || typeof pkg.version !== "string" || pkg.version.trim() === "") {
|
||||
throw new Error("package/package.json must contain a nonempty string version");
|
||||
}
|
||||
process.stdout.write(pkg.version.trim());
|
||||
});
|
||||
')"; then
|
||||
echo "error: npm publish tarball package/package.json is malformed or has no valid version: ${publish_target}" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [[ "${tarball_package_version}" != "${package_version}" ]]; then
|
||||
echo "error: npm publish tarball version mismatch: expected ${package_version}, got ${tarball_package_version}" >&2
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
|
||||
publish_plan="$(
|
||||
PACKAGE_VERSION="${package_version}" REQUESTED_PUBLISH_TAG="${OPENCLAW_NPM_PUBLISH_TAG:-}" \
|
||||
node --import tsx --input-type=module <<'EOF'
|
||||
import { resolveNpmPublishPlan } from "./scripts/openclaw-npm-release-check.ts";
|
||||
BYPASS_EXTENDED_STABLE_GUARD="${BYPASS_EXTENDED_STABLE_GUARD:-}" \
|
||||
node scripts/openclaw-npm-extended-stable-release.mjs publish-plan
|
||||
)"
|
||||
|
||||
const requestedPublishTag =
|
||||
process.env.REQUESTED_PUBLISH_TAG === "latest"
|
||||
? "latest"
|
||||
: process.env.REQUESTED_PUBLISH_TAG === "alpha"
|
||||
? "alpha"
|
||||
: "beta";
|
||||
const plan = resolveNpmPublishPlan(process.env.PACKAGE_VERSION ?? "", undefined, requestedPublishTag);
|
||||
console.log(plan.channel);
|
||||
console.log(plan.publishTag);
|
||||
EOF
|
||||
)
|
||||
|
||||
release_channel="${publish_plan[0]}"
|
||||
publish_tag="${publish_plan[1]}"
|
||||
release_channel="${publish_plan%%$'\n'*}"
|
||||
publish_tag="${publish_plan#*$'\n'}"
|
||||
publish_cmd=(npm publish)
|
||||
if [[ -n "${publish_target}" ]]; then
|
||||
publish_cmd+=("${publish_target}")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// OpenClaw NPM Publish tests cover publish wrapper argument safety.
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { execFileSync, spawnSync } from "node:child_process";
|
||||
import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
@@ -14,13 +14,49 @@ function makeTempDir(prefix: string): string {
|
||||
return dir;
|
||||
}
|
||||
|
||||
function runPublishWrapper(args: string[]) {
|
||||
function runPublishWrapper(
|
||||
args: string[],
|
||||
env: NodeJS.ProcessEnv = {},
|
||||
cwd: string = process.cwd(),
|
||||
) {
|
||||
return spawnSync("bash", [scriptPath, ...args], {
|
||||
cwd: process.cwd(),
|
||||
cwd,
|
||||
encoding: "utf8",
|
||||
env: { ...process.env, ...env },
|
||||
});
|
||||
}
|
||||
|
||||
function makeReleaseCheckout(root: string, version: string): string {
|
||||
const checkout = path.join(root, "checkout");
|
||||
const scriptsDir = path.join(checkout, "scripts");
|
||||
mkdirSync(scriptsDir, { recursive: true });
|
||||
writeFileSync(path.join(checkout, "package.json"), JSON.stringify({ version }), "utf8");
|
||||
copyFileSync(scriptPath, path.join(checkout, scriptPath));
|
||||
copyFileSync(
|
||||
"scripts/openclaw-npm-extended-stable-release.mjs",
|
||||
path.join(checkout, "scripts/openclaw-npm-extended-stable-release.mjs"),
|
||||
);
|
||||
mkdirSync(path.join(scriptsDir, "lib"));
|
||||
copyFileSync(
|
||||
"scripts/lib/npm-publish-plan.mjs",
|
||||
path.join(checkout, "scripts/lib/npm-publish-plan.mjs"),
|
||||
);
|
||||
return checkout;
|
||||
}
|
||||
|
||||
function makePackageTarball(root: string, packageJson?: string): string {
|
||||
const packageDir = path.join(root, "package");
|
||||
const tarball = path.join(root, "openclaw.tgz");
|
||||
mkdirSync(packageDir);
|
||||
if (packageJson === undefined) {
|
||||
writeFileSync(path.join(packageDir, "README.md"), "missing package metadata", "utf8");
|
||||
} else {
|
||||
writeFileSync(path.join(packageDir, "package.json"), packageJson, "utf8");
|
||||
}
|
||||
execFileSync("tar", ["-czf", tarball, "-C", root, "package"]);
|
||||
return tarball;
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const dir of tempDirs.splice(0)) {
|
||||
rmSync(dir, { force: true, recursive: true });
|
||||
@@ -67,4 +103,114 @@ describe("openclaw npm publish wrapper", () => {
|
||||
expect(result.stdout).toBe("");
|
||||
expect(result.stderr.trim()).toBe("error: unexpected npm publish argument: extra");
|
||||
});
|
||||
|
||||
it.each(["beta", "latest"])("publishes the prepared tarball to the %s dist-tag", (distTag) => {
|
||||
const tempRoot = makeTempDir("openclaw-npm-publish-");
|
||||
const binDir = path.join(tempRoot, "bin");
|
||||
const packageVersion = distTag === "beta" ? "2026.5.32-beta.1" : "2026.5.32";
|
||||
const checkout = makeReleaseCheckout(tempRoot, packageVersion);
|
||||
const tarball = makePackageTarball(tempRoot, JSON.stringify({ version: packageVersion }));
|
||||
const npmLog = path.join(tempRoot, "npm.log");
|
||||
mkdirSync(binDir);
|
||||
writeFileSync(path.join(binDir, "npm"), `#!/bin/sh\nprintf '%s\\n' "$*" > "${npmLog}"\n`, {
|
||||
mode: 0o755,
|
||||
});
|
||||
|
||||
const result = runPublishWrapper(
|
||||
["--publish", tarball],
|
||||
{
|
||||
OPENCLAW_NPM_PUBLISH_TAG: distTag,
|
||||
PATH: `${binDir}:${process.env.PATH}`,
|
||||
},
|
||||
checkout,
|
||||
);
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(readFileSync(npmLog, "utf8")).toContain(
|
||||
`publish ${tarball} --access public --tag ${distTag} --provenance`,
|
||||
);
|
||||
expect(result.stdout).toContain(`Resolved publish tag: ${distTag}`);
|
||||
});
|
||||
|
||||
it("rejects a tarball whose package version differs from the checkout", () => {
|
||||
const tempRoot = makeTempDir("openclaw-npm-publish-");
|
||||
const packageVersion = JSON.parse(readFileSync("package.json", "utf8")).version as string;
|
||||
const tarballVersion = `${packageVersion}-mismatch`;
|
||||
const tarball = makePackageTarball(tempRoot, JSON.stringify({ version: tarballVersion }));
|
||||
const result = runPublishWrapper(["--publish", tarball], {
|
||||
OPENCLAW_NPM_PUBLISH_TAG: "extended-stable",
|
||||
});
|
||||
|
||||
expect(result.status).toBe(2);
|
||||
expect(result.stderr).toContain(
|
||||
`npm publish tarball version mismatch: expected ${packageVersion}, got ${tarballVersion}`,
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["missing package.json", undefined, "missing a readable package/package.json"],
|
||||
["malformed package.json", "{not-json", "package/package.json is malformed"],
|
||||
["missing version", JSON.stringify({ name: "openclaw" }), "has no valid version"],
|
||||
])("rejects a tarball with %s", (_label, packageJson, expectedError) => {
|
||||
const tempRoot = makeTempDir("openclaw-npm-publish-");
|
||||
const tarball = makePackageTarball(tempRoot, packageJson);
|
||||
const result = runPublishWrapper(["--publish", tarball], {
|
||||
OPENCLAW_NPM_PUBLISH_TAG: "extended-stable",
|
||||
});
|
||||
|
||||
expect(result.status).toBe(2);
|
||||
expect(result.stderr).toContain(expectedError);
|
||||
});
|
||||
|
||||
it("rejects a pre-.33 final version on extended-stable", () => {
|
||||
const tempRoot = makeTempDir("openclaw-npm-publish-");
|
||||
const checkout = makeReleaseCheckout(tempRoot, "2026.5.32");
|
||||
const result = runPublishWrapper(
|
||||
["--publish"],
|
||||
{ OPENCLAW_NPM_PUBLISH_TAG: "extended-stable" },
|
||||
checkout,
|
||||
);
|
||||
|
||||
expect(result.status).not.toBe(0);
|
||||
expect(result.stderr).toContain(
|
||||
"Extended-stable npm publication requires release patch 33 or above",
|
||||
);
|
||||
});
|
||||
|
||||
it("routes the prepared 2026.6.33 tarball only to extended-stable", () => {
|
||||
const tempRoot = makeTempDir("openclaw-npm-publish-");
|
||||
const binDir = path.join(tempRoot, "bin");
|
||||
const checkout = makeReleaseCheckout(tempRoot, "2026.6.33");
|
||||
const tarball = makePackageTarball(tempRoot, JSON.stringify({ version: "2026.6.33" }));
|
||||
const npmLog = path.join(tempRoot, "npm.log");
|
||||
mkdirSync(binDir);
|
||||
writeFileSync(path.join(binDir, "npm"), `#!/bin/sh\nprintf '%s\\n' "$*" > "${npmLog}"\n`, {
|
||||
mode: 0o755,
|
||||
});
|
||||
|
||||
const result = runPublishWrapper(
|
||||
["--publish", tarball],
|
||||
{
|
||||
OPENCLAW_NPM_PUBLISH_TAG: "extended-stable",
|
||||
PATH: `${binDir}:${process.env.PATH}`,
|
||||
},
|
||||
checkout,
|
||||
);
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(readFileSync(npmLog, "utf8")).toContain(
|
||||
`publish ${tarball} --access public --tag extended-stable --provenance`,
|
||||
);
|
||||
expect(result.stdout).toContain("Resolved publish tag: extended-stable");
|
||||
expect(result.stdout).not.toContain("Resolved publish tag: beta");
|
||||
});
|
||||
|
||||
it("rejects unknown requested dist-tags instead of falling back to beta", () => {
|
||||
const result = runPublishWrapper(["--publish"], {
|
||||
OPENCLAW_NPM_PUBLISH_TAG: "nightly",
|
||||
});
|
||||
|
||||
expect(result.status).not.toBe(0);
|
||||
expect(result.stderr).toContain('Unsupported npm dist-tag "nightly"');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user