From 601d6427e601739c01967e7d80c277aaa27149ca Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 27 Mar 2025 12:43:45 -0400 Subject: [PATCH] fix: coerce dependency versions in semver parsing --- src/blocks/blockPackageJson.test.ts | 32 ++++++++++++++++++++++++++++- src/blocks/blockPackageJson.ts | 11 +++++++--- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/blocks/blockPackageJson.test.ts b/src/blocks/blockPackageJson.test.ts index 58bcf73f5..4bf71356e 100644 --- a/src/blocks/blockPackageJson.test.ts +++ b/src/blocks/blockPackageJson.test.ts @@ -260,7 +260,7 @@ describe("blockPackageJson", () => { `); }); - it("preserves an existing dependency when the addon has an older pinned version", () => { + it("preserves an existing dependency when the addon has an invalid version", () => { const dependency = "test-dependency"; const creation = testBlock(blockPackageJson, { addons: { @@ -318,6 +318,36 @@ describe("blockPackageJson", () => { `); }); + it("uses the addon's version when the existing equivalent is invalid semver", () => { + const dependency = "test-dependency"; + const creation = testBlock(blockPackageJson, { + addons: { + properties: { + dependencies: { + [dependency]: "1.0.0", + }, + }, + }, + options: { + ...optionsBase, + packageData: { + dependencies: { + [dependency]: "next", + }, + }, + }, + }); + + expect( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unsafe-member-access + JSON.parse(creation.files!["package.json"] as string).dependencies, + ).toMatchInlineSnapshot(` + { + "test-dependency": "1.0.0", + } + `); + }); + it("preserves an existing dependency when the addon has an older version minimum", () => { const dependency = "test-dependency"; const creation = testBlock(blockPackageJson, { diff --git a/src/blocks/blockPackageJson.ts b/src/blocks/blockPackageJson.ts index 0b5bad806..e94b3932c 100644 --- a/src/blocks/blockPackageJson.ts +++ b/src/blocks/blockPackageJson.ts @@ -119,15 +119,20 @@ function collectBinFiles(bin: Record | string | undefined) { } function removeRangePrefix(version: string) { - return version.replaceAll(/[\^~><=]/gu, "").split(" ")[0]; + const raw = version.replaceAll(/[\^~><=]/gu, "").split(" ")[0]; + + return semver.coerce(raw) ?? raw; } function useLargerVersion(existing: string | undefined, replacement: string) { - if (!existing) { + if (!existing || existing === replacement) { return replacement; } - return semver.gt(removeRangePrefix(existing), removeRangePrefix(replacement)) + const existingCoerced = semver.coerce(removeRangePrefix(existing)); + + return existingCoerced && + semver.gt(existingCoerced, removeRangePrefix(replacement)) ? existing : replacement; }