diff --git a/packages/coding-agent/test/update-cli.test.ts b/packages/coding-agent/test/update-cli.test.ts index 4bf380ce0..4d731d4cc 100644 --- a/packages/coding-agent/test/update-cli.test.ts +++ b/packages/coding-agent/test/update-cli.test.ts @@ -380,6 +380,30 @@ describe("update-cli bun cache pruning", () => { expect(await Bun.file(path.join(dir, "pkg", "1.0.0@@@1")).exists()).toBe(true); expect(await Bun.file(path.join(dir, "pkg@1.0.0@@@1", "package.json")).exists()).toBe(true); }); + + it("compares numeric version segments without precision loss", async () => { + const dir = await makeTempDir(); + const older = "1.0.99999999999999999999"; + const newer = "1.0.100000000000000000000"; + await Bun.write(path.join(dir, "pkg", `${older}@@@1`), ""); + await Bun.write(path.join(dir, "pkg", `${newer}@@@1`), ""); + await Bun.write( + path.join(dir, `pkg@${older}@@@1`, "package.json"), + JSON.stringify({ name: "pkg", version: older }), + ); + await Bun.write( + path.join(dir, `pkg@${newer}@@@1`, "package.json"), + JSON.stringify({ name: "pkg", version: newer }), + ); + + const result = await pruneBunInstallCache(dir, new Set(["pkg"])); + + expect(result).toEqual({ scannedPackages: 1, removedEntries: 2 }); + expect(await Bun.file(path.join(dir, "pkg", `${older}@@@1`)).exists()).toBe(false); + expect(await Bun.file(path.join(dir, `pkg@${older}@@@1`, "package.json")).exists()).toBe(false); + expect(await Bun.file(path.join(dir, "pkg", `${newer}@@@1`)).exists()).toBe(true); + expect(await Bun.file(path.join(dir, `pkg@${newer}@@@1`, "package.json")).exists()).toBe(true); + }); }); describe("update-cli release binary integrity", () => { diff --git a/scripts/release.test.ts b/scripts/release.test.ts index c7f640715..bc37ffc8b 100644 --- a/scripts/release.test.ts +++ b/scripts/release.test.ts @@ -14,6 +14,13 @@ describe("validateExplicitVersion", () => { expect(validateExplicitVersion("17.2.8-")).toBe(null); }); + test("rejects leading zeroes in numeric segments", () => { + expect(validateExplicitVersion("018.0.0")).toBe(null); + expect(validateExplicitVersion("v018.0.0")).toBe(null); + expect(validateExplicitVersion("18.00.0")).toBe(null); + expect(validateExplicitVersion("18.0.00")).toBe(null); + }); + test("rejects prerelease suffixes (not supported by this release path)", () => { // Prereleases would be published as npm `latest` because the downstream // publish runs `npm publish` with no `--tag`. diff --git a/scripts/release.ts b/scripts/release.ts index 156ac11b7..b29ef8abb 100755 --- a/scripts/release.ts +++ b/scripts/release.ts @@ -1,5 +1,5 @@ #!/usr/bin/env bun -import { compareVersions } from "@oh-my-pi/pi-utils/version"; +import { compareVersions } from "../packages/utils/src/version.ts"; /** * Release script for pi-mono * @@ -30,7 +30,7 @@ const cargoTomlGlob = new Glob("crates/*/Cargo.toml"); * prefix — Cargo rejects `version = "v17.2.8"`. */ export function validateExplicitVersion(version: string): string | null { - const match = /^v?(\d+\.\d+\.\d+)$/.exec(version); + const match = /^v?((?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*))$/.exec(version); return match ? match[1] : null; } diff --git a/scripts/setup-npm-trust.ts b/scripts/setup-npm-trust.ts index f4959c53c..336ae5e15 100755 --- a/scripts/setup-npm-trust.ts +++ b/scripts/setup-npm-trust.ts @@ -35,6 +35,7 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import { $ } from "bun"; +import { compareVersions } from "../packages/utils/src/version.ts"; import { LEAF_TARGETS } from "../packages/natives/scripts/gen-npm-packages.ts"; import { packages } from "./ci-release-publish.ts"; @@ -179,17 +180,6 @@ async function collectTargets(): Promise<{ names: string[]; repoFromManifest: st return { names, repoFromManifest }; } -/** Compare dotted version numbers; true when `version` >= `minimum`. */ -function meetsMinimum(version: string, minimum: string): boolean { - const a = version.split(".").map(Number); - const b = minimum.split(".").map(Number); - for (let i = 0; i < Math.max(a.length, b.length); i++) { - const diff = (a[i] ?? 0) - (b[i] ?? 0); - if (diff !== 0) return diff > 0; - } - return true; -} - /** Run npm with the terminal attached so the web 2FA flow stays interactive. */ function npmInteractive(args: readonly string[]): Promise { return Bun.spawn(["npm", ...args], { stdin: "inherit", stdout: "inherit", stderr: "inherit" }).exited; @@ -321,7 +311,7 @@ async function main(): Promise { console.error("Could not determine npm version. Is npm installed and on PATH?"); process.exit(1); } - if (!meetsMinimum(npmVersion, MIN_NPM)) { + if (compareVersions(npmVersion, MIN_NPM) < 0) { console.error(`npm ${MIN_NPM}+ is required for trusted publishing (found ${npmVersion}).`); console.error("Upgrade with: npm install -g npm@latest"); process.exit(1);