mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-19 17:11:42 -06:00
fix(release): complete wrapper prerequisites
Partial backports from9d2d517296anda1201e99fc, adapted for the preflight-only rehearsal workflow.
This commit is contained in:
@@ -24,7 +24,7 @@ export const JUNE_2026_PATCH_FLOOR = 5;
|
||||
/**
|
||||
* @typedef {object} NpmPublishPlan
|
||||
* @property {"stable" | "alpha" | "beta"} channel
|
||||
* @property {"latest" | "alpha" | "beta"} publishTag
|
||||
* @property {"latest" | "alpha" | "beta" | "extended-stable"} publishTag
|
||||
* @property {("latest" | "alpha" | "beta")[]} mirrorDistTags
|
||||
*/
|
||||
|
||||
@@ -186,14 +186,38 @@ export function compareReleaseVersions(left, right) {
|
||||
/**
|
||||
* @param {string} version
|
||||
* @param {string | null} [currentBetaVersion]
|
||||
* @param {string | null} [publishTagOverride]
|
||||
* @returns {NpmPublishPlan}
|
||||
*/
|
||||
export function resolveNpmPublishPlan(version, currentBetaVersion) {
|
||||
export function resolveNpmPublishPlan(version, currentBetaVersion, publishTagOverride) {
|
||||
const parsedVersion = parseReleaseVersion(version);
|
||||
if (parsedVersion === null) {
|
||||
throw new Error(`Unsupported release version "${version}".`);
|
||||
}
|
||||
|
||||
const normalizedOverride = publishTagOverride?.trim();
|
||||
if (normalizedOverride && normalizedOverride !== "extended-stable") {
|
||||
throw new Error(
|
||||
`Unsupported npm publish tag override "${normalizedOverride}". Expected "extended-stable".`,
|
||||
);
|
||||
}
|
||||
if (normalizedOverride === "extended-stable") {
|
||||
if (
|
||||
parsedVersion.channel !== "stable" ||
|
||||
parsedVersion.correctionNumber !== undefined ||
|
||||
parsedVersion.patch < 33
|
||||
) {
|
||||
throw new Error(
|
||||
`Extended-stable npm publication requires a final YYYY.M.PATCH version with PATCH >= 33; found "${version}".`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
channel: "stable",
|
||||
publishTag: "extended-stable",
|
||||
mirrorDistTags: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (parsedVersion.channel === "beta") {
|
||||
return {
|
||||
channel: "beta",
|
||||
|
||||
@@ -2,11 +2,33 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
mode="${1:-}"
|
||||
publish_target="${2:-}"
|
||||
usage() {
|
||||
echo "usage: bash scripts/openclaw-npm-publish.sh --publish [package.tgz]"
|
||||
}
|
||||
|
||||
if [[ "${mode}" != "--publish" ]]; then
|
||||
echo "usage: bash scripts/openclaw-npm-publish.sh --publish [package.tgz]" >&2
|
||||
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "${1:-}" != "--publish" ]]; then
|
||||
usage >&2
|
||||
exit 2
|
||||
fi
|
||||
shift
|
||||
|
||||
publish_target=""
|
||||
if [[ "${1:-}" == "--" ]]; then
|
||||
shift
|
||||
fi
|
||||
if [[ "$#" -gt 0 ]]; then
|
||||
case "$1" in
|
||||
-*) echo "error: unexpected npm publish target option: $1" >&2; exit 2 ;;
|
||||
*) publish_target="$1"; shift ;;
|
||||
esac
|
||||
fi
|
||||
if [[ "$#" -gt 0 ]]; then
|
||||
echo "error: unexpected npm publish argument: $1" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ import {
|
||||
const plan = resolveNpmPublishPlan(
|
||||
process.env.PACKAGE_VERSION ?? "",
|
||||
process.env.CURRENT_BETA_VERSION,
|
||||
process.env.OPENCLAW_PLUGIN_NPM_PUBLISH_TAG,
|
||||
);
|
||||
const auth = resolveNpmDistTagMirrorAuth({
|
||||
nodeAuthToken: process.env.NODE_AUTH_TOKEN,
|
||||
|
||||
@@ -97,3 +97,28 @@ describe("shouldRequireNpmDistTagMirrorAuth", () => {
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("extended-stable npm publish override", () => {
|
||||
it("publishes final patch 33 and later to extended-stable without mirrors", () => {
|
||||
expect(resolveNpmPublishPlan("2026.7.33", undefined, "extended-stable")).toEqual({
|
||||
channel: "stable",
|
||||
publishTag: "extended-stable",
|
||||
mirrorDistTags: [],
|
||||
});
|
||||
expect(resolveNpmPublishPlan("2026.7.34", "2026.8.1-beta.1", "extended-stable")).toEqual({
|
||||
channel: "stable",
|
||||
publishTag: "extended-stable",
|
||||
mirrorDistTags: [],
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
["pre-.33 final", "2026.7.32", "extended-stable"],
|
||||
["correction", "2026.7.33-1", "extended-stable"],
|
||||
["alpha", "2026.7.33-alpha.1", "extended-stable"],
|
||||
["beta", "2026.7.33-beta.1", "extended-stable"],
|
||||
["open override", "2026.7.33", "latest"],
|
||||
])("rejects %s releases", (_label, version, override) => {
|
||||
expect(() => resolveNpmPublishPlan(version, undefined, override)).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1512,7 +1512,7 @@ describe("package artifact reuse", () => {
|
||||
expect(manifestStep.run).toContain('--arg performanceRunId "$PERFORMANCE_RUN_ID"');
|
||||
});
|
||||
|
||||
it("keeps release publish creation compatible with gh api and prerelease notes", () => {
|
||||
it("keeps release publish creation compatible while npm rehearsal stays preflight-only", () => {
|
||||
const workflow = readFileSync(RELEASE_PUBLISH_WORKFLOW, "utf8");
|
||||
const npmWorkflow = readFileSync(".github/workflows/openclaw-npm-release.yml", "utf8");
|
||||
const fullReleaseWorkflow = readFileSync(FULL_RELEASE_VALIDATION_WORKFLOW, "utf8");
|
||||
@@ -1532,8 +1532,11 @@ describe("package artifact reuse", () => {
|
||||
);
|
||||
expect(workflow).toContain("preflight-manifest.json");
|
||||
expect(npmWorkflow).toContain("preflight-manifest.json");
|
||||
expect(npmWorkflow).toContain("Verify full release validation run metadata");
|
||||
expect(npmWorkflow).toContain("Verify full release validation target");
|
||||
expect(npmWorkflow).not.toContain("validate_publish_request:");
|
||||
expect(npmWorkflow).not.toContain("publish_openclaw_npm:");
|
||||
expect(npmWorkflow).not.toContain("id-token: write");
|
||||
expect(npmWorkflow).toContain("This throwaway workflow requires preflight_only=true");
|
||||
expect(npmWorkflow).toContain("This throwaway workflow requires a full 40-character commit SHA");
|
||||
expect(npmWorkflow).not.toContain("Build and smoke test final Docker runtime image");
|
||||
expect(fullReleaseWorkflow).toContain("docker_runtime_assets_preflight");
|
||||
expect(fullReleaseWorkflow).not.toContain("Build and smoke test final Docker runtime image");
|
||||
@@ -1547,12 +1550,6 @@ describe("package artifact reuse", () => {
|
||||
expect(fullReleaseWorkflow).toContain(
|
||||
"needs.docker_runtime_assets_preflight.result == 'success'",
|
||||
);
|
||||
expect(npmWorkflow).toContain("full_release_validation_run_id");
|
||||
expect(npmWorkflow).toContain("release_publish_run_id");
|
||||
expect(npmWorkflow).toContain("Real publish requires full_release_validation_run_id");
|
||||
expect(npmWorkflow).toContain(
|
||||
"Workflow-dispatched real publish requires release_publish_run_id",
|
||||
);
|
||||
expect(npmWorkflow).toContain("tarballSha256");
|
||||
expect(workflow).toContain("Checkout release SHA");
|
||||
expect(workflow).toContain('git show "${TARGET_SHA}:CHANGELOG.md" > "${changelog_file}"');
|
||||
@@ -1886,7 +1883,7 @@ describe("package artifact reuse", () => {
|
||||
expect(clawHubReleasePlanScript).toContain("--skip-clawhub");
|
||||
expect(pluginNpmWorkflow).toContain("Validate release publish approval run");
|
||||
expect(clawHubWorkflow).toContain("Validate release publish approval run");
|
||||
expect(openclawNpmWorkflow).toContain("Validate release publish approval run");
|
||||
expect(openclawNpmWorkflow).not.toContain("Validate release publish approval run");
|
||||
expect(pluginNpmWorkflow).toContain("Check npm package version");
|
||||
expect(pluginNpmWorkflow).toContain("already_published=true");
|
||||
expect(pluginNpmWorkflow).toContain(
|
||||
@@ -1894,16 +1891,16 @@ describe("package artifact reuse", () => {
|
||||
);
|
||||
expect(pluginNpmWorkflow).toContain("Direct Plugin NPM Release dispatch");
|
||||
expect(clawHubWorkflow).toContain("Direct Plugin ClawHub Release dispatch");
|
||||
expect(openclawNpmWorkflow).toContain("Direct OpenClaw npm publish");
|
||||
expect(openclawNpmWorkflow).not.toContain("Direct OpenClaw npm publish");
|
||||
expect(pluginNpmWorkflow).toContain('GITHUB_ACTOR}" != "github-actions[bot]"');
|
||||
expect(clawHubWorkflow).toContain('GITHUB_ACTOR}" != "github-actions[bot]"');
|
||||
expect(openclawNpmWorkflow).toContain('GITHUB_ACTOR}" != "github-actions[bot]"');
|
||||
expect(openclawNpmWorkflow).not.toContain('GITHUB_ACTOR}" != "github-actions[bot]"');
|
||||
expect(pluginNpmWorkflow).toContain("Direct Plugin NPM Release recovery");
|
||||
expect(clawHubWorkflow).toContain("Direct Plugin ClawHub Release recovery");
|
||||
expect(openclawNpmWorkflow).toContain("Direct OpenClaw npm recovery");
|
||||
expect(openclawNpmWorkflow).not.toContain("Direct OpenClaw npm recovery");
|
||||
expect(pluginNpmWorkflow).toContain("validate-release-publish-approval.mjs");
|
||||
expect(clawHubWorkflow).toContain("validate-release-publish-approval.mjs");
|
||||
expect(openclawNpmWorkflow).toContain("validate-release-publish-approval.mjs");
|
||||
expect(openclawNpmWorkflow).not.toContain("validate-release-publish-approval.mjs");
|
||||
expect(approvalScript).toContain("must still be in_progress");
|
||||
expect(approvalScript).toContain("completed with success/failure");
|
||||
expect(pluginNpmWorkflow).toContain("environment: npm-release");
|
||||
@@ -1963,7 +1960,7 @@ describe("package artifact reuse", () => {
|
||||
expect(clawHubNewWorkflow).toContain("Verify bootstrap ClawHub package and trusted publisher");
|
||||
expect(clawHubNewWorkflow).toContain("/trusted-publisher");
|
||||
expect(clawHubNewWorkflow).toContain('trustedPublisher?.repository !== "openclaw/openclaw"');
|
||||
expect(openclawNpmWorkflow).toContain("environment: npm-release");
|
||||
expect(openclawNpmWorkflow).not.toContain("environment: npm-release");
|
||||
expect(releaseWorkflow).toContain("default: from-validation");
|
||||
expect(releaseWorkflow).toContain('--release-publish-branch "${CHILD_WORKFLOW_REF}"');
|
||||
expect(releaseWorkflow).toContain('--release-publish-run-id "${GITHUB_RUN_ID}"');
|
||||
|
||||
Reference in New Issue
Block a user