diff --git a/package.json b/package.json index dae7bddb..fde35894 100644 --- a/package.json +++ b/package.json @@ -139,6 +139,7 @@ "miniflare": "4.20260722.0", "oxc-parser": "0.147.0", "publint": "catalog:", + "semver": "^7.8.5", "tsdown": "catalog:", "tsx": "catalog:", "typedoc": "0.28.20", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 31ac6df9..7d7020f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -102,6 +102,9 @@ importers: publint: specifier: 'catalog:' version: 0.3.24 + semver: + specifier: ^7.8.5 + version: 7.8.5 tsdown: specifier: 'catalog:' version: 0.22.14(@arethetypeswrong/core@0.18.5)(publint@0.3.24)(tsx@4.23.12)(typescript@6.0.3) diff --git a/scripts/lib/packed-package-test.mjs b/scripts/lib/packed-package-test.mjs index 1346bf4f..c5a72b13 100644 --- a/scripts/lib/packed-package-test.mjs +++ b/scripts/lib/packed-package-test.mjs @@ -1,3 +1,4 @@ +import { satisfies, valid } from 'semver' import { sandboxCompatibilityVersions, sandboxPeerRange } from './dependency-contract.mjs' const unsupportedDependencyProtocol = /^(?:catalog|file|link|patch|portal|workspace):/ @@ -58,22 +59,9 @@ export function expectedPeerRange(version) { return Number(match[1]) >= 1 ? `^${version}` : currentMinorPeerRange(version) } -/** A caret range admits a version when the major matches and the floor is at or below it. */ -export function caretAdmits(range, version) { - const floor = /^\^(\d+)\.(\d+)\.(\d+)$/.exec(range) - const found = /^(\d+)\.(\d+)\.(\d+)/.exec(version) - if (floor === null || found === null) return false - const [floorMajor, floorMinor, floorPatch] = floor.slice(1).map(Number) - const [major, minor, patch] = found.slice(1).map(Number) - if (floorMajor < 1 || major !== floorMajor) return false - return minor * 1_000_000 + patch >= floorMinor * 1_000_000 + floorPatch -} - -const exactVersion = /^\d+\.\d+\.\d+(?:[-+].*)?$/ - /** True when a specifier names one version and admits no other. */ export function isExactVersionSpec(spec) { - return typeof spec === 'string' && exactVersion.test(spec.trim()) + return typeof spec === 'string' && valid(spec.trim()) !== null } /** @@ -89,21 +77,9 @@ export function cohortRange(spec) { return isExactVersionSpec(spec) ? expectedPeerRange(spec.trim()) : spec } -/** A `>=floor =(\d+)\.(\d+)\.(\d+)\s+<(\d+)\.(\d+)\.(\d+)$/.exec(range) - const found = /^(\d+)\.(\d+)\.(\d+)/.exec(version) - if (window === null || found === null) return false - const parts = window.slice(1).map(Number) - const order = ([major, minor, patch]) => - major * 1_000_000_000_000 + minor * 1_000_000 + patch - const target = order(found.slice(1).map(Number)) - return target >= order(parts.slice(0, 3)) && target < order(parts.slice(3)) -} - -/** A range admits a version through either supported cohort shape. */ +/** Use npm's version rules while keeping the cohort's no-exact-pin policy. */ export function rangeAdmits(range, version) { - return caretAdmits(range, version) || windowAdmits(range, version) + return typeof range === 'string' && !isExactVersionSpec(range) && satisfies(version, range) } /** diff --git a/scripts/lib/packed-package-test.test.mjs b/scripts/lib/packed-package-test.test.mjs index 3dabd984..bcd17d1b 100644 --- a/scripts/lib/packed-package-test.test.mjs +++ b/scripts/lib/packed-package-test.test.mjs @@ -72,6 +72,24 @@ describe('rangeAdmits', () => { it('refuses an exact specifier, which states no range', () => { expect(rangeAdmits('0.145.21', '0.145.21')).toBe(false) }) + + it('does not admit prereleases through stable ranges', () => { + expect(rangeAdmits('>=0.36.4 <0.39.0', '0.38.2-develop.1')).toBe(false) + expect(rangeAdmits('^1.0.0', '1.4.2-rc.1')).toBe(false) + }) + + it('admits only the named snapshot beside the stable range', () => { + const range = '>=0.36.4 <0.39.0 || 0.39.0-develop.1' + expect(rangeAdmits(range, '0.38.2')).toBe(true) + expect(rangeAdmits(range, '0.39.0-develop.1')).toBe(true) + expect(rangeAdmits(range, '0.39.0-develop.2')).toBe(false) + expect(rangeAdmits(range, '0.39.0')).toBe(false) + }) + + it('compares multi-digit version components without collisions', () => { + expect(rangeAdmits('>=0.1.1000000 <0.2.0', '0.2.0')).toBe(false) + expect(rangeAdmits('>=0.1.1000000 <0.2.0', '0.1.1000000')).toBe(true) + }) }) describe('assertFirstPartyRangeSpecs', () => {