diff --git a/scripts/resolve-upgrade-survivor-baselines.mjs b/scripts/resolve-upgrade-survivor-baselines.mjs index 5636e694ed8..9325959fa32 100644 --- a/scripts/resolve-upgrade-survivor-baselines.mjs +++ b/scripts/resolve-upgrade-survivor-baselines.mjs @@ -33,6 +33,18 @@ function dedupeSpecs(specs) { return [...new Set(specs.map(normalizeUpgradeSurvivorBaselineSpec).filter(Boolean))]; } +function parsePositiveInteger(value, label) { + const text = String(value ?? "").trim(); + if (!/^[1-9]\d*$/u.test(text)) { + throw new Error(`${label} must be a positive integer`); + } + const parsed = Number(text); + if (!Number.isSafeInteger(parsed)) { + throw new Error(`${label} must be a positive integer`); + } + return parsed; +} + function readPublishedVersions(file) { if (!file) { return undefined; @@ -111,10 +123,7 @@ export function resolveReleaseHistory(args) { if (!releasesJson) { throw new Error("--releases-json is required when requested baselines include release-history"); } - const historyCount = Number.parseInt(args.get("history-count") ?? "6", 10); - if (!Number.isInteger(historyCount) || historyCount < 1) { - throw new Error("--history-count must be a positive integer"); - } + const historyCount = parsePositiveInteger(args.get("history-count") ?? "6", "--history-count"); const includeVersion = args.get("include-version") ?? "2026.4.23"; const preDate = args.get("pre-date") ?? "2026-03-15T00:00:00Z"; const publishedVersions = readPublishedVersions(args.get("npm-versions-json")); @@ -181,7 +190,10 @@ export function resolveBaselines(args) { if (token === "release-history") { resolved.push(...resolveReleaseHistory(args)); } else if (token.startsWith("last-stable-")) { - const count = Number.parseInt(token.slice("last-stable-".length), 10); + const count = parsePositiveInteger( + token.slice("last-stable-".length), + "last-stable baseline count", + ); resolved.push(...resolveLastStable(args, count)); } else if (token.startsWith("all-since-")) { const minimumVersion = token.slice("all-since-".length); diff --git a/test/scripts/upgrade-survivor-baselines.test.ts b/test/scripts/upgrade-survivor-baselines.test.ts index 801787ccdc2..09bc5b1e4c1 100644 --- a/test/scripts/upgrade-survivor-baselines.test.ts +++ b/test/scripts/upgrade-survivor-baselines.test.ts @@ -159,6 +159,33 @@ describe("scripts/resolve-upgrade-survivor-baselines", () => { }); }); + it("rejects loose release-history count values", () => { + withReleaseFixture([], (file) => { + expect(() => + resolveBaselines( + new Map([ + ["requested", "release-history"], + ["releases-json", file], + ["history-count", "1e3"], + ]), + ), + ).toThrow("--history-count must be a positive integer"); + }); + }); + + it("rejects loose last-stable count tokens", () => { + withReleaseFixture([], (file) => { + expect(() => + resolveBaselines( + new Map([ + ["requested", "last-stable-1e3"], + ["releases-json", file], + ]), + ), + ).toThrow("last-stable baseline count must be a positive integer"); + }); + }); + it("maps release-history anchors to npm-published package versions when GitHub tags have republish suffixes", () => { const releases = ( [