fix(release): reject duplicate candidate checklist options

This commit is contained in:
Vincent Koc
2026-06-23 14:30:23 +02:00
parent 34d2d54d6c
commit c638f2beda
2 changed files with 61 additions and 18 deletions
+26 -18
View File
@@ -87,64 +87,72 @@ export function parseArgs(argv) {
windowsNodeInstallerDigests: "",
outputDir: "",
};
const seen = new Set();
const setOnce = (flag, key, value) => {
if (seen.has(flag)) {
throw new Error(`${flag} was provided more than once`);
}
seen.add(flag);
options[key] = value;
};
parseArgv: for (let index = 0; index < args.length; index += 1) {
const arg = args[index];
switch (arg) {
case "--":
break parseArgv;
case "--tag":
options.tag = requireValue(args, ++index, arg);
setOnce(arg, "tag", requireValue(args, ++index, arg));
break;
case "--workflow-ref":
options.workflowRef = requireValue(args, ++index, arg);
setOnce(arg, "workflowRef", requireValue(args, ++index, arg));
break;
case "--repo":
options.repo = requireValue(args, ++index, arg);
setOnce(arg, "repo", requireValue(args, ++index, arg));
break;
case "--full-release-run":
options.fullReleaseRunId = requireValue(args, ++index, arg);
setOnce(arg, "fullReleaseRunId", requireValue(args, ++index, arg));
break;
case "--npm-preflight-run":
options.npmPreflightRunId = requireValue(args, ++index, arg);
setOnce(arg, "npmPreflightRunId", requireValue(args, ++index, arg));
break;
case "--windows-node-tag":
options.windowsNodeTag = requireValue(args, ++index, arg);
setOnce(arg, "windowsNodeTag", requireValue(args, ++index, arg));
break;
case "--skip-dispatch":
options.skipDispatch = true;
setOnce(arg, "skipDispatch", true);
break;
case "--skip-local-generated-check":
options.skipLocalGeneratedCheck = true;
setOnce(arg, "skipLocalGeneratedCheck", true);
break;
case "--skip-parallels":
options.skipParallels = true;
setOnce(arg, "skipParallels", true);
break;
case "--skip-telegram":
options.skipTelegram = true;
setOnce(arg, "skipTelegram", true);
break;
case "--telegram-provider-mode":
options.telegramProviderMode = requireValue(args, ++index, arg);
setOnce(arg, "telegramProviderMode", requireValue(args, ++index, arg));
break;
case "--provider":
options.provider = requireValue(args, ++index, arg);
setOnce(arg, "provider", requireValue(args, ++index, arg));
break;
case "--mode":
options.mode = requireValue(args, ++index, arg);
setOnce(arg, "mode", requireValue(args, ++index, arg));
break;
case "--release-profile":
options.releaseProfile = requireValue(args, ++index, arg);
setOnce(arg, "releaseProfile", requireValue(args, ++index, arg));
break;
case "--npm-dist-tag":
options.npmDistTag = requireValue(args, ++index, arg);
setOnce(arg, "npmDistTag", requireValue(args, ++index, arg));
break;
case "--plugin-publish-scope":
options.pluginPublishScope = requireValue(args, ++index, arg);
setOnce(arg, "pluginPublishScope", requireValue(args, ++index, arg));
break;
case "--plugins":
options.plugins = requireValue(args, ++index, arg);
setOnce(arg, "plugins", requireValue(args, ++index, arg));
break;
case "--output-dir":
options.outputDir = requireValue(args, ++index, arg);
setOnce(arg, "outputDir", requireValue(args, ++index, arg));
break;
case "-h":
case "--help":
@@ -79,6 +79,41 @@ describe("release candidate checklist", () => {
);
});
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",