From 2800b40a0abfb1271a7d60c0221bca17769cc1bd Mon Sep 17 00:00:00 2001 From: Dipesh Babu Date: Fri, 31 Jul 2026 04:20:25 -0400 Subject: [PATCH 1/2] Fix SemVer prerelease comparison --- sdk/typescript/src/version.ts | 34 +++++++++++++--- sdk/typescript/tests-ts/update-notice.test.ts | 40 +++++++++++++++++++ 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/sdk/typescript/src/version.ts b/sdk/typescript/src/version.ts index 25619ca1..fd33ee13 100644 --- a/sdk/typescript/src/version.ts +++ b/sdk/typescript/src/version.ts @@ -154,12 +154,36 @@ function isNewerVersion(latest: string, current: string): boolean { if (candidate[4] === installed[4]) return false; if (candidate[4] === undefined) return true; if (installed[4] === undefined) return false; - return ( - new Intl.Collator("en", { numeric: true }).compare( - candidate[4], - installed[4], - ) > 0 + return isNewerPrerelease(candidate[4], installed[4]); +} + +function isNewerPrerelease(candidate: string, installed: string): boolean { + const candidateIdentifiers = candidate.split("."); + const installedIdentifiers = installed.split("."); + const length = Math.max( + candidateIdentifiers.length, + installedIdentifiers.length, ); + + for (let index = 0; index < length; index += 1) { + const candidateIdentifier = candidateIdentifiers[index]; + const installedIdentifier = installedIdentifiers[index]; + if (candidateIdentifier === installedIdentifier) continue; + if (candidateIdentifier === undefined) return false; + if (installedIdentifier === undefined) return true; + + const candidateIsNumeric = /^\d+$/u.test(candidateIdentifier); + const installedIsNumeric = /^\d+$/u.test(installedIdentifier); + if (candidateIsNumeric && installedIsNumeric) { + return BigInt(candidateIdentifier) > BigInt(installedIdentifier); + } + if (candidateIsNumeric !== installedIsNumeric) { + return !candidateIsNumeric; + } + return candidateIdentifier > installedIdentifier; + } + + return false; } function packageVersions(url: URL): { diff --git a/sdk/typescript/tests-ts/update-notice.test.ts b/sdk/typescript/tests-ts/update-notice.test.ts index bc2f8424..66babe18 100644 --- a/sdk/typescript/tests-ts/update-notice.test.ts +++ b/sdk/typescript/tests-ts/update-notice.test.ts @@ -125,6 +125,46 @@ describe("CLI update notice", () => { ).toBeUndefined(); }); + test("uses SemVer precedence for prerelease identifiers", async () => { + const precedence = [ + "1.0.0-alpha", + "1.0.0-alpha.1", + "1.0.0-alpha.beta", + "1.0.0-beta", + "1.0.0-beta.2", + "1.0.0-beta.11", + "1.0.0-rc.1", + "1.0.0", + ]; + + for (let index = 1; index < precedence.length; index += 1) { + const lower = precedence[index - 1]!; + const higher = precedence[index]!; + await expect( + checkForUpdate({ + environment: {}, + currentVersion: lower, + fetch: registryResponse(higher), + }), + ).resolves.toBeDefined(); + await expect( + checkForUpdate({ + environment: {}, + currentVersion: higher, + fetch: registryResponse(lower), + }), + ).resolves.toBeUndefined(); + } + + await expect( + checkForUpdate({ + environment: {}, + currentVersion: "1.0.0-alpha.1", + fetch: registryResponse("1.0.0-alpha-1"), + }), + ).resolves.toBeDefined(); + }); + test("suppresses registry checks in CI or when disabled", async () => { let requests = 0; const fetchLatest = async () => { From ca7c9f44a9abad92c87e2027dae00ab62577af9c Mon Sep 17 00:00:00 2001 From: Dipesh Babu Date: Sat, 1 Aug 2026 10:23:03 -0400 Subject: [PATCH 2/2] Harden numeric prerelease comparison --- sdk/typescript/src/version.ts | 5 ++++- sdk/typescript/tests-ts/update-notice.test.ts | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/sdk/typescript/src/version.ts b/sdk/typescript/src/version.ts index fd33ee13..9f626105 100644 --- a/sdk/typescript/src/version.ts +++ b/sdk/typescript/src/version.ts @@ -175,7 +175,10 @@ function isNewerPrerelease(candidate: string, installed: string): boolean { const candidateIsNumeric = /^\d+$/u.test(candidateIdentifier); const installedIsNumeric = /^\d+$/u.test(installedIdentifier); if (candidateIsNumeric && installedIsNumeric) { - return BigInt(candidateIdentifier) > BigInt(installedIdentifier); + if (candidateIdentifier.length !== installedIdentifier.length) { + return candidateIdentifier.length > installedIdentifier.length; + } + return candidateIdentifier > installedIdentifier; } if (candidateIsNumeric !== installedIsNumeric) { return !candidateIsNumeric; diff --git a/sdk/typescript/tests-ts/update-notice.test.ts b/sdk/typescript/tests-ts/update-notice.test.ts index 66babe18..71ae2a58 100644 --- a/sdk/typescript/tests-ts/update-notice.test.ts +++ b/sdk/typescript/tests-ts/update-notice.test.ts @@ -163,6 +163,15 @@ describe("CLI update notice", () => { fetch: registryResponse("1.0.0-alpha-1"), }), ).resolves.toBeDefined(); + + const longNumericIdentifier = "9".repeat(4_096); + await expect( + checkForUpdate({ + environment: {}, + currentVersion: `1.0.0-alpha.${longNumericIdentifier}`, + fetch: registryResponse(`1.0.0-alpha.1${longNumericIdentifier}`), + }), + ).resolves.toBeDefined(); }); test("suppresses registry checks in CI or when disabled", async () => {