diff --git a/packages/cli/src/commands/upgrade.project.test.ts b/packages/cli/src/commands/upgrade.project.test.ts index cbc360ebc09..b29e107e0b7 100644 --- a/packages/cli/src/commands/upgrade.project.test.ts +++ b/packages/cli/src/commands/upgrade.project.test.ts @@ -98,4 +98,40 @@ describe("upgradeProjectPins", () => { expect(r.changed).toBe(true); expect(readFileSync(join(d, "package.json"), "utf-8")).toBe(before); }); + + it("upgrades Hyperframes pins without changing unrelated package pins", async () => { + const unrelated = "npx @acme/hyperframes@1.2.3 preview"; + const d = project({ render: "npx hyperframes@0.7.48 render", unrelated }); + const wrapperPath = join(d, "render.sh"); + writeFileSync( + wrapperPath, + "npx custom-hyperframes@1.2.3 preview\nnpx hyperframes@0.7.48 render\n", + ); + + const result = await upgradeProjectPins(d, { json: true, check: false }); + + expect(result.from).toEqual(["0.7.48"]); + const pkg = JSON.parse(readFileSync(join(d, "package.json"), "utf-8")); + expect(pkg.scripts).toEqual({ render: "npx hyperframes@0.7.55 render", unrelated }); + expect(readFileSync(wrapperPath, "utf-8")).toBe( + "npx custom-hyperframes@1.2.3 preview\nnpx hyperframes@0.7.55 render\n", + ); + }); + + it("leaves an unrelated-only project's files intact in check and apply modes", async () => { + const d = project({ preview: "npx @acme/hyperframes@1.2.3 preview" }); + const wrapperPath = join(d, "preview.sh"); + writeFileSync(wrapperPath, "npx custom-hyperframes@1.2.3 preview\n"); + const packageBefore = readFileSync(join(d, "package.json"), "utf-8"); + const wrapperBefore = readFileSync(wrapperPath, "utf-8"); + + for (const check of [true, false]) { + const result = await upgradeProjectPins(d, { json: true, check }); + + expect(result.changed).toBe(false); + expect(result.from).toEqual([]); + expect(readFileSync(join(d, "package.json"), "utf-8")).toBe(packageBefore); + expect(readFileSync(wrapperPath, "utf-8")).toBe(wrapperBefore); + } + }); }); diff --git a/packages/cli/src/utils/projectPin.test.ts b/packages/cli/src/utils/projectPin.test.ts index 18a60e72508..5cfa8bc7b7a 100644 --- a/packages/cli/src/utils/projectPin.test.ts +++ b/packages/cli/src/utils/projectPin.test.ts @@ -55,4 +55,31 @@ describe("rewriteProjectPinnedScripts", () => { }), ).toEqual(["0.7.48", "0.7.50"]); }); + + it.each(["@acme/hyperframes", "custom-hyperframes", "custom.hyperframes"])( + "preserves the version of the unrelated package %s", + (name) => { + const unrelated = `npx --yes ${name}@1.2.3 preview`; + const scripts = { unrelated, render: "npx hyperframes@0.7.48 render" }; + const result = rewriteProjectPinnedScripts(scripts, "0.7.55"); + + expect(result.scripts.unrelated).toBe(unrelated); + expect(result.scripts.render).toBe("npx hyperframes@0.7.55 render"); + expect(result.fromVersions).toEqual(["0.7.48"]); + expect(readPinnedHyperframesVersions(scripts)).toEqual(["0.7.48"]); + }, + ); + + it("recognizes a package option alongside a standalone pin", () => { + const scripts = { + render: "npx --package=hyperframes@0.7.48 hyperframes render", + preview: "bunx hyperframes@0.7.50 preview", + }; + + expect(readPinnedHyperframesVersions(scripts)).toEqual(["0.7.48", "0.7.50"]); + expect(rewriteProjectPinnedScripts(scripts, "0.7.55").scripts).toEqual({ + render: "npx --package=hyperframes@0.7.55 hyperframes render", + preview: "bunx hyperframes@0.7.55 preview", + }); + }); }); diff --git a/packages/cli/src/utils/projectPin.ts b/packages/cli/src/utils/projectPin.ts index fcaeb6a7a23..8a780deb350 100644 --- a/packages/cli/src/utils/projectPin.ts +++ b/packages/cli/src/utils/projectPin.ts @@ -1,10 +1,10 @@ import { isSafeVersion } from "./safeVersion.js"; -// Matches `hyperframes@` as a whole token inside a script string. The -// version class mirrors isSafeVersion's semver shape; capturing group 1 is the -// old version. `(?=\s|$)` keeps it from matching a longer package name. +// Package-name characters before the name belong to another package, including +// scoped names: a word boundary also matches after their slash, hyphen or dot. +// The version shape follows isSafeVersion; group 1 captures the current pin. export const HYPERFRAMES_PIN_RE = - /\bhyperframes@([0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)(?=\s|$)/g; + /(?