Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
526
test/scripts/release-candidate-checklist.test.ts
Normal file
526
test/scripts/release-candidate-checklist.test.ts
Normal file
@@ -0,0 +1,526 @@
|
||||
// Release Candidate Checklist tests cover release candidate checklist script behavior.
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
buildPublishCommand,
|
||||
candidateParallelsArgs,
|
||||
candidateParallelsShellCommand,
|
||||
githubApi,
|
||||
parseArgs,
|
||||
parseRunIdFromDispatchOutput,
|
||||
resolveArtifactName,
|
||||
requireRunIdFromDispatchOutput,
|
||||
validateFullManifest,
|
||||
validateWindowsSourceRelease,
|
||||
} from "../../scripts/release-candidate-checklist.mjs";
|
||||
|
||||
function jsonResponse(body: unknown, init: ResponseInit = {}): Response {
|
||||
return new Response(JSON.stringify(body), init);
|
||||
}
|
||||
|
||||
async function withGithubApiTimeoutEnv<T>(value: string, fn: () => Promise<T>): Promise<T> {
|
||||
const previous = process.env.OPENCLAW_RELEASE_CANDIDATE_GITHUB_API_TIMEOUT_MS;
|
||||
process.env.OPENCLAW_RELEASE_CANDIDATE_GITHUB_API_TIMEOUT_MS = value;
|
||||
try {
|
||||
return await fn();
|
||||
} finally {
|
||||
if (previous === undefined) {
|
||||
delete process.env.OPENCLAW_RELEASE_CANDIDATE_GITHUB_API_TIMEOUT_MS;
|
||||
} else {
|
||||
process.env.OPENCLAW_RELEASE_CANDIDATE_GITHUB_API_TIMEOUT_MS = previous;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe("release candidate checklist", () => {
|
||||
it("infers validation profiles from candidate tags", () => {
|
||||
expect(parseArgs(["--tag", "v2026.5.14-beta.3"]).releaseProfile).toBe("beta");
|
||||
expect(parseArgs(["--tag", "v2026.5.14", "--windows-node-tag", "v0.6.3"]).releaseProfile).toBe(
|
||||
"stable",
|
||||
);
|
||||
expect(
|
||||
parseArgs([
|
||||
"--tag",
|
||||
"v2026.5.14",
|
||||
"--windows-node-tag",
|
||||
"v0.6.3",
|
||||
"--release-profile",
|
||||
"full",
|
||||
]).releaseProfile,
|
||||
).toBe("full");
|
||||
});
|
||||
|
||||
it("runs Parallels against the exact prepared candidate tarball", () => {
|
||||
expect(candidateParallelsArgs(".artifacts/preflight/openclaw.tgz")).toEqual([
|
||||
"test:parallels:npm-update",
|
||||
"--",
|
||||
"--target-tarball",
|
||||
".artifacts/preflight/openclaw.tgz",
|
||||
"--json",
|
||||
]);
|
||||
expect(
|
||||
candidateParallelsShellCommand(
|
||||
".artifacts/preflight/openclaw candidate.tgz",
|
||||
"/opt/homebrew/bin/gtimeout",
|
||||
),
|
||||
).toContain(
|
||||
"set -a; source \"$HOME/.profile\" >/dev/null 2>&1 || true; set +a; exec '/opt/homebrew/bin/gtimeout' --foreground 150m pnpm",
|
||||
);
|
||||
expect(
|
||||
candidateParallelsShellCommand(
|
||||
".artifacts/preflight/openclaw candidate.tgz",
|
||||
"/opt/homebrew/bin/gtimeout",
|
||||
),
|
||||
).toContain("'--target-tarball' '.artifacts/preflight/openclaw candidate.tgz'");
|
||||
});
|
||||
|
||||
it("requires run ids when dispatch is disabled", () => {
|
||||
expect(() => parseArgs(["--tag", "v2026.5.14-beta.3", "--skip-dispatch"])).toThrow(
|
||||
"--skip-dispatch requires --full-release-run and --npm-preflight-run",
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects duplicate release candidate CLI options", () => {
|
||||
const requiredArgs = ["--tag", "v2026.5.14-beta.3"];
|
||||
const duplicateOption = (
|
||||
flag: string,
|
||||
firstValue: string,
|
||||
secondValue: string,
|
||||
prefix = requiredArgs,
|
||||
): [string, string[]] => [flag, [...prefix, flag, firstValue, flag, secondValue]];
|
||||
const duplicateFlag = (flag: string): [string, string[]] => [flag, [...requiredArgs, flag, flag]];
|
||||
const duplicateCases = [
|
||||
duplicateOption("--tag", "v2026.5.14-beta.3", "v2026.5.14-beta.4", []),
|
||||
duplicateOption("--workflow-ref", "release/a", "release/b"),
|
||||
duplicateOption("--repo", "openclaw/openclaw", "fork/openclaw"),
|
||||
duplicateOption("--full-release-run", "111", "222"),
|
||||
duplicateOption("--npm-preflight-run", "111", "222"),
|
||||
duplicateOption("--windows-node-tag", "v0.6.3", "v0.6.4"),
|
||||
duplicateFlag("--skip-dispatch"),
|
||||
duplicateFlag("--skip-local-generated-check"),
|
||||
duplicateFlag("--skip-parallels"),
|
||||
duplicateFlag("--skip-telegram"),
|
||||
duplicateOption("--telegram-provider-mode", "mock-openai", "live-frontier"),
|
||||
duplicateOption("--provider", "blacksmith-testbox", "crabbox"),
|
||||
duplicateOption("--mode", "fresh", "upgrade"),
|
||||
duplicateOption("--release-profile", "beta", "stable"),
|
||||
duplicateOption("--npm-dist-tag", "beta", "latest"),
|
||||
duplicateOption("--plugin-publish-scope", "all-publishable", "selected"),
|
||||
duplicateOption("--plugins", "telegram", "discord"),
|
||||
duplicateOption("--output-dir", ".artifacts/a", ".artifacts/b"),
|
||||
] satisfies Array<[string, string[]]>;
|
||||
|
||||
for (const [flag, args] of duplicateCases) {
|
||||
expect(() => parseArgs(args), flag).toThrow(`${flag} was provided more than once`);
|
||||
}
|
||||
});
|
||||
|
||||
it("requires stable validation evidence to include soak and blocking performance", () => {
|
||||
const stableManifest = {
|
||||
workflowName: "Full Release Validation",
|
||||
targetSha: "candidate-sha",
|
||||
releaseProfile: "stable",
|
||||
rerunGroup: "all",
|
||||
runReleaseSoak: "true",
|
||||
controls: { performanceBlocking: true },
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
validateFullManifest(stableManifest, {
|
||||
targetSha: "candidate-sha",
|
||||
releaseProfile: "stable",
|
||||
}),
|
||||
).not.toThrow();
|
||||
|
||||
expect(() =>
|
||||
validateFullManifest(
|
||||
{
|
||||
...stableManifest,
|
||||
runReleaseSoak: "false",
|
||||
},
|
||||
{
|
||||
targetSha: "candidate-sha",
|
||||
releaseProfile: "stable",
|
||||
},
|
||||
),
|
||||
).toThrow("runReleaseSoak=true");
|
||||
expect(() =>
|
||||
validateFullManifest(
|
||||
{
|
||||
...stableManifest,
|
||||
controls: { performanceBlocking: false },
|
||||
},
|
||||
{
|
||||
targetSha: "candidate-sha",
|
||||
releaseProfile: "stable",
|
||||
},
|
||||
),
|
||||
).toThrow("blocking product performance");
|
||||
});
|
||||
|
||||
it("stops parsing options after the argument terminator", () => {
|
||||
const options = parseArgs([
|
||||
"--tag",
|
||||
"v2026.5.14-beta.3",
|
||||
"--full-release-run",
|
||||
"111",
|
||||
"--npm-preflight-run",
|
||||
"222",
|
||||
"--skip-dispatch",
|
||||
"--",
|
||||
"--plugin-publish-scope",
|
||||
"selected",
|
||||
]);
|
||||
|
||||
expect(options.pluginPublishScope).toBe("all-publishable");
|
||||
});
|
||||
|
||||
it("accepts package-manager argument separators before script options", () => {
|
||||
const options = parseArgs([
|
||||
"--",
|
||||
"--tag",
|
||||
"v2026.5.14-beta.3",
|
||||
"--full-release-run",
|
||||
"111",
|
||||
"--npm-preflight-run",
|
||||
"222",
|
||||
"--skip-dispatch",
|
||||
"--skip-parallels",
|
||||
]);
|
||||
|
||||
expect(options.tag).toBe("v2026.5.14-beta.3");
|
||||
expect(options.skipParallels).toBe(true);
|
||||
});
|
||||
|
||||
it("builds the gated release publish command from green evidence inputs", () => {
|
||||
const options = {
|
||||
...parseArgs([
|
||||
"--tag",
|
||||
"v2026.5.14-beta.3",
|
||||
"--workflow-ref",
|
||||
"release/2026.5.14",
|
||||
"--full-release-run",
|
||||
"111",
|
||||
"--npm-preflight-run",
|
||||
"222",
|
||||
"--skip-dispatch",
|
||||
]),
|
||||
workflowRef: "release/2026.5.14",
|
||||
};
|
||||
|
||||
expect(buildPublishCommand(options)).toContain("'full_release_validation_run_id=111'");
|
||||
expect(buildPublishCommand(options)).toContain("'preflight_run_id=222'");
|
||||
expect(buildPublishCommand(options)).toContain("'tag=v2026.5.14-beta.3'");
|
||||
expect(buildPublishCommand(options)).toContain("'plugin_publish_scope=all-publishable'");
|
||||
expect(buildPublishCommand(options)).not.toContain("windows_node_tag=");
|
||||
});
|
||||
|
||||
it("requires and carries an exact Windows Node tag for stable release candidates", () => {
|
||||
expect(() => parseArgs(["--tag", "v2026.5.14"])).toThrow(
|
||||
"stable release candidates require --windows-node-tag",
|
||||
);
|
||||
expect(() => parseArgs(["--tag", "v2026.5.14", "--windows-node-tag", "latest"])).toThrow(
|
||||
"--windows-node-tag must be an explicit version tag, not latest",
|
||||
);
|
||||
|
||||
const options = {
|
||||
...parseArgs([
|
||||
"--tag",
|
||||
"v2026.5.14",
|
||||
"--windows-node-tag",
|
||||
"v0.6.3",
|
||||
"--workflow-ref",
|
||||
"release/2026.5.14",
|
||||
]),
|
||||
workflowRef: "release/2026.5.14",
|
||||
windowsNodeInstallerDigests: JSON.stringify({
|
||||
"OpenClawCompanion-Setup-x64.exe": `sha256:${"a".repeat(64)}`,
|
||||
"OpenClawCompanion-Setup-arm64.exe": `sha256:${"b".repeat(64)}`,
|
||||
}),
|
||||
};
|
||||
|
||||
expect(buildPublishCommand(options)).toContain("'windows_node_tag=v0.6.3'");
|
||||
expect(buildPublishCommand(options)).toContain(
|
||||
`'windows_node_installer_digests={"OpenClawCompanion-Setup-x64.exe":"sha256:${"a".repeat(64)}","OpenClawCompanion-Setup-arm64.exe":"sha256:${"b".repeat(64)}"}'`,
|
||||
);
|
||||
});
|
||||
|
||||
it("validates the stable Windows source release and immutable installer digests", async () => {
|
||||
const assets = [
|
||||
{
|
||||
name: "OpenClawCompanion-Setup-x64.exe",
|
||||
digest: `sha256:${"a".repeat(64)}`,
|
||||
},
|
||||
{
|
||||
name: "OpenClawCompanion-Setup-arm64.exe",
|
||||
digest: `sha256:${"b".repeat(64)}`,
|
||||
},
|
||||
];
|
||||
const fetchImpl = vi.fn(async () => {
|
||||
return jsonResponse({
|
||||
tag_name: "v0.6.3",
|
||||
draft: false,
|
||||
prerelease: false,
|
||||
html_url: "https://github.com/openclaw/openclaw-windows-node/releases/tag/v0.6.3",
|
||||
assets,
|
||||
});
|
||||
});
|
||||
|
||||
await expect(
|
||||
validateWindowsSourceRelease("v0.6.3", {
|
||||
fetchImpl,
|
||||
timeoutMs: 1234,
|
||||
token: "test-token",
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
tag: "v0.6.3",
|
||||
url: "https://github.com/openclaw/openclaw-windows-node/releases/tag/v0.6.3",
|
||||
assets,
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
[{ draft: true }, "must be published"],
|
||||
[{ prerelease: true }, "must not be a prerelease"],
|
||||
[{ tag_name: "v0.6.4" }, "Windows source release tag mismatch: expected v0.6.3, got v0.6.4"],
|
||||
[
|
||||
{ assets: [] },
|
||||
"must contain exactly one required asset OpenClawCompanion-Setup-x64.exe; found 0",
|
||||
],
|
||||
[
|
||||
{
|
||||
assets: [
|
||||
{
|
||||
name: "OpenClawCompanion-Setup-x64.exe",
|
||||
digest: `sha256:${"a".repeat(64)}`,
|
||||
},
|
||||
{
|
||||
name: "OpenClawCompanion-Setup-x64.exe",
|
||||
digest: `sha256:${"c".repeat(64)}`,
|
||||
},
|
||||
{
|
||||
name: "OpenClawCompanion-Setup-arm64.exe",
|
||||
digest: `sha256:${"b".repeat(64)}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
"must contain exactly one required asset OpenClawCompanion-Setup-x64.exe; found 2",
|
||||
],
|
||||
[
|
||||
{
|
||||
assets: [
|
||||
{ name: "OpenClawCompanion-Setup-x64.exe", digest: "" },
|
||||
{ name: "OpenClawCompanion-Setup-arm64.exe", digest: `sha256:${"b".repeat(64)}` },
|
||||
],
|
||||
},
|
||||
"asset OpenClawCompanion-Setup-x64.exe is missing its SHA-256 digest",
|
||||
],
|
||||
])("rejects an invalid stable Windows source release", async (override, message) => {
|
||||
const fetchImpl = vi.fn(async () => {
|
||||
return jsonResponse({
|
||||
tag_name: "v0.6.3",
|
||||
draft: false,
|
||||
prerelease: false,
|
||||
html_url: "https://github.com/openclaw/openclaw-windows-node/releases/tag/v0.6.3",
|
||||
assets: [
|
||||
{
|
||||
name: "OpenClawCompanion-Setup-x64.exe",
|
||||
digest: `sha256:${"a".repeat(64)}`,
|
||||
},
|
||||
{
|
||||
name: "OpenClawCompanion-Setup-arm64.exe",
|
||||
digest: `sha256:${"b".repeat(64)}`,
|
||||
},
|
||||
],
|
||||
...override,
|
||||
});
|
||||
});
|
||||
|
||||
await expect(
|
||||
validateWindowsSourceRelease("v0.6.3", {
|
||||
fetchImpl,
|
||||
timeoutMs: 1234,
|
||||
token: "test-token",
|
||||
}),
|
||||
).rejects.toThrow(message);
|
||||
});
|
||||
|
||||
it("carries the Telegram proof run into the publish command when available", () => {
|
||||
const options = {
|
||||
...parseArgs([
|
||||
"--tag",
|
||||
"v2026.5.14-beta.3",
|
||||
"--workflow-ref",
|
||||
"release/2026.5.14",
|
||||
"--full-release-run",
|
||||
"111",
|
||||
"--npm-preflight-run",
|
||||
"222",
|
||||
"--skip-dispatch",
|
||||
]),
|
||||
workflowRef: "release/2026.5.14",
|
||||
npmTelegramRunId: "333",
|
||||
};
|
||||
|
||||
expect(buildPublishCommand(options)).toContain("'npm_telegram_run_id=333'");
|
||||
});
|
||||
|
||||
it("requires explicit plugin names for selected plugin publish scope", () => {
|
||||
expect(() =>
|
||||
parseArgs(["--tag", "v2026.5.14-beta.3", "--plugin-publish-scope", "selected"]),
|
||||
).toThrow("--plugin-publish-scope selected requires --plugins");
|
||||
});
|
||||
|
||||
it("rejects selected plugin publish scope for release candidates", () => {
|
||||
expect(() =>
|
||||
parseArgs([
|
||||
"--tag",
|
||||
"v2026.5.14-beta.3",
|
||||
"--plugin-publish-scope",
|
||||
"selected",
|
||||
"--plugins",
|
||||
"@openclaw/diffs",
|
||||
]),
|
||||
).toThrow("release candidates publish OpenClaw with --plugin-publish-scope all-publishable");
|
||||
});
|
||||
|
||||
it("extracts a workflow run id from gh dispatch output", () => {
|
||||
expect(
|
||||
parseRunIdFromDispatchOutput(
|
||||
"https://github.com/openclaw/openclaw/actions/runs/25922042055\n",
|
||||
),
|
||||
).toBe("25922042055");
|
||||
});
|
||||
|
||||
it("fails closed when gh dispatch output does not include the run url", () => {
|
||||
expect(() =>
|
||||
requireRunIdFromDispatchOutput(
|
||||
"Created workflow_dispatch event for full-release-validation.yml",
|
||||
"full-release-validation.yml",
|
||||
),
|
||||
).toThrow("refusing to guess from recent workflow_dispatch runs");
|
||||
});
|
||||
|
||||
it("falls back to a single compatible artifact from the same run", () => {
|
||||
expect(
|
||||
resolveArtifactName(
|
||||
[{ name: "openclaw-npm-preflight-dba00", expired: false }],
|
||||
"openclaw-npm-preflight-v2026.5.16-beta.2",
|
||||
"openclaw-npm-preflight-",
|
||||
),
|
||||
).toBe("openclaw-npm-preflight-dba00");
|
||||
});
|
||||
|
||||
it("bounds GitHub API requests with a timeout signal", async () => {
|
||||
const fetchImpl = vi.fn(async (_url: string, init?: RequestInit) => {
|
||||
expect(init?.signal).toBeInstanceOf(AbortSignal);
|
||||
expect(init?.headers).toMatchObject({
|
||||
Accept: "application/vnd.github+json",
|
||||
Authorization: "Bearer test-token",
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
});
|
||||
return jsonResponse({ workflow_runs: [] });
|
||||
});
|
||||
|
||||
await expect(
|
||||
githubApi("repos/openclaw/openclaw/actions/runs", {
|
||||
fetchImpl,
|
||||
timeoutMs: 1234,
|
||||
token: "test-token",
|
||||
}),
|
||||
).resolves.toEqual({ workflow_runs: [] });
|
||||
expect(fetchImpl).toHaveBeenCalledWith(
|
||||
"https://api.github.com/repos/openclaw/openclaw/actions/runs",
|
||||
expect.objectContaining({
|
||||
signal: expect.any(AbortSignal),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("uses a positive integer GitHub API timeout env", async () => {
|
||||
const fetchImpl = vi.fn(async (_url: string, init?: RequestInit) => {
|
||||
expect(init?.signal).toBeInstanceOf(AbortSignal);
|
||||
return jsonResponse({ workflow_runs: [] });
|
||||
});
|
||||
|
||||
await withGithubApiTimeoutEnv("2500", async () => {
|
||||
await expect(
|
||||
githubApi("repos/openclaw/openclaw/actions/runs", {
|
||||
fetchImpl,
|
||||
token: "test-token",
|
||||
}),
|
||||
).resolves.toEqual({ workflow_runs: [] });
|
||||
});
|
||||
expect(fetchImpl).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it.each(["1e3", "10.5", "0", "soon"])(
|
||||
"rejects malformed GitHub API timeout env %s",
|
||||
async (raw) => {
|
||||
const fetchImpl = vi.fn();
|
||||
|
||||
await withGithubApiTimeoutEnv(raw, async () => {
|
||||
await expect(
|
||||
githubApi("repos/openclaw/openclaw/actions/runs", {
|
||||
fetchImpl,
|
||||
token: "test-token",
|
||||
}),
|
||||
).rejects.toThrow(
|
||||
"OPENCLAW_RELEASE_CANDIDATE_GITHUB_API_TIMEOUT_MS must be a positive integer",
|
||||
);
|
||||
});
|
||||
expect(fetchImpl).not.toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
|
||||
it("bounds GitHub API error bodies", async () => {
|
||||
const fetchImpl = vi.fn(async () => {
|
||||
return new Response("x".repeat(65), {
|
||||
headers: { "content-length": "65" },
|
||||
status: 500,
|
||||
});
|
||||
});
|
||||
|
||||
await expect(
|
||||
githubApi("repos/openclaw/openclaw/actions/runs", {
|
||||
fetchImpl,
|
||||
maxBodyBytes: 64,
|
||||
timeoutMs: 1234,
|
||||
token: "test-token",
|
||||
}),
|
||||
).rejects.toThrow(
|
||||
"GitHub API repos/openclaw/openclaw/actions/runs response body exceeded 64 bytes",
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps GitHub API timeouts active while reading response bodies", async () => {
|
||||
const fetchImpl = vi.fn(async () => {
|
||||
return new Response(new ReadableStream<Uint8Array>({ start() {} }), {
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
|
||||
await expect(
|
||||
githubApi("repos/openclaw/openclaw/actions/runs", {
|
||||
fetchImpl,
|
||||
timeoutMs: 25,
|
||||
token: "test-token",
|
||||
}),
|
||||
).rejects.toThrow("GitHub API repos/openclaw/openclaw/actions/runs timed out after 25ms");
|
||||
});
|
||||
|
||||
it("includes the GitHub API path when a request times out", async () => {
|
||||
const fetchImpl = vi.fn(async () => {
|
||||
throw new DOMException("request timed out", "TimeoutError");
|
||||
});
|
||||
|
||||
await expect(
|
||||
githubApi("repos/openclaw/openclaw/actions/runs/123/jobs", {
|
||||
fetchImpl,
|
||||
timeoutMs: 5,
|
||||
token: "test-token",
|
||||
}),
|
||||
).rejects.toThrow(
|
||||
"GitHub API repos/openclaw/openclaw/actions/runs/123/jobs timed out after 5ms",
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user