UNPKG

nx

Version:

The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.

207 lines (206 loc) • 9.01 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.classifySpec = classifySpec; exports.splitPackageDescriptor = splitPackageDescriptor; exports.isVersionMature = isVersionMature; exports.newestInRange = newestInRange; exports.degradeTagToCompliant = degradeTagToCompliant; exports.blockedVersionsFrom = blockedVersionsFrom; exports.pickMinReleaseAgeCompliantVersion = pickMinReleaseAgeCompliantVersion; const semver_1 = require("semver"); const bun_1 = require("./behavior/bun"); const npm_1 = require("./behavior/npm"); const pnpm_1 = require("./behavior/pnpm"); const yarn_1 = require("./behavior/yarn"); /** * Classifies a version spec the way the package managers do: an exact semver is * a pin, a valid range is a range, anything else (latest, hot, canary, ...) is * a dist-tag. Catalog refs are dereferenced before this layer. */ function classifySpec(spec) { if ((0, semver_1.valid)(spec)) { return 'exact'; } if ((0, semver_1.validRange)(spec)) { return 'range'; } return 'tag'; } /** * Splits an npm package descriptor into its name and the raw version part after * the separating `@`, honoring scoped names where the leading `@` is part of the * name. `versionPart` is null when there is no separator (bare name) and an * empty string for a trailing `@`; callers interpret the version part. */ function splitPackageDescriptor(entry) { const scoped = entry.startsWith('@'); const at = entry.indexOf('@', scoped ? 1 : 0); if (at === -1) { return { name: entry, versionPart: null }; } return { name: entry.slice(0, at), versionPart: entry.slice(at + 1) }; } /** * Shared maturity test: a version passes when its publish time is at or before * the cutoff (inclusive), or when it is explicitly excluded. */ function isVersionMature(name, version, metadata, policy) { if (policy.isExcluded(name, version)) { return true; } const time = metadata.time?.[version]; if (!time) { // Missing individual time: callers that diverge (pnpm blocks, yarn // quarantines) handle it themselves; the default is to pass like npm/bun. return true; } return Date.parse(time) <= policy.cutoffMs; } /** * Newest version satisfying the range, ignoring the cooldown gate. Used only to * compute PickOutcome.unconstrained (messaging); never gates a pick. Falls back * to the raw range when nothing matches. */ function newestInRange(metadata, range) { return (metadata.versions .filter((v) => (0, semver_1.satisfies)(v, range, { includePrerelease: false })) .sort(semver_1.rcompare)[0] ?? range); } /** * The prerelease channel of a version: the first dotted identifier of its * prerelease tag (e.g. `rc` for `23.0.0-rc.0`, `pr` for `23.0.0-pr.123`), or * null for a stable release. Channels keep parallel prerelease lines apart so a * cooldown degrade never crosses from `rc` into an internal `pr` build. */ function prereleaseChannel(version) { const pre = (0, semver_1.prerelease)(version); return pre && pre.length > 0 ? String(pre[0]) : null; } // The stable `major.minor.patch` of a version, ignoring any prerelease tag // (e.g. `23.0.0` for both `23.0.0-rc.0` and `23.0.0`). function releaseLine(version) { const parsed = (0, semver_1.parse)(version); return parsed ? `${parsed.major}.${parsed.minor}.${parsed.patch}` : version; } // Publish time in epoch ms; a version with no registry time sorts last. function publishedAtMs(metadata, version) { const time = metadata.time?.[version]; const parsed = time ? Date.parse(time) : NaN; return Number.isNaN(parsed) ? -Infinity : parsed; } // Prerelease channels with a conventional maturity order, least mature first. // A blocked target may descend to a lower rung of its own release line // (rc.0 -> beta.x); channels off the ladder (pr, canary, ...) have no implied // ordering and stay walled off. `next` is deliberately omitted: it is pre-rc // in some ecosystems (Angular) but a rolling dev snapshot in others - exactly // the kind of build a degrade must never land on. const ORDERED_CHANNELS = ['alpha', 'beta', 'rc']; // Whether a prerelease channel may serve as a degrade candidate for a target // on `targetChannel`: the target's own channel always; a strictly lower rung // when both sit on the ladder. A stable target (null channel) admits none. function channelAdmits(targetChannel, channel) { if (channel === targetChannel) { return true; } if (targetChannel === null) { return false; } const rank = ORDERED_CHANNELS.indexOf(channel); const targetRank = ORDERED_CHANNELS.indexOf(targetChannel); return rank !== -1 && targetRank !== -1 && rank < targetRank; } /** * Degrades a too-new dist-tag target to a cooldown-compliant version "of the * same kind", shared by every package manager. * * The candidate pool is every version at or below the resolved target that is * stable, in the target's prerelease channel, or on a lower rung of the * channel ladder (alpha < beta < rc). It is ordered so that prereleases of * the target's exact release line come first - own channel, then lower rungs - * then everything else; within each group the most recently published version * comes first (semver breaks ties and orders versions with no publish time). * The first compliant version in that order wins. * * So a stable target degrades to the newest compliant stable, and a prerelease * target keeps a compliant prerelease of the release it points at when one * exists (an rc may fall to a same-line beta), otherwise drops to the newest * compliant version below it - never crossing into a channel with no place on * the ladder (e.g. an internal `pr` build) and never climbing up it. Returns * null when nothing in the pool is compliant; callers turn that into their * package manager's violation. * * `isCompliant` is the caller's per-PM maturity test (package managers differ * on missing publish times and excludes). */ function degradeTagToCompliant(target, metadata, isCompliant) { // semver.compare throws on a non-semver string; a tag pointing at one has no // channel or ordering to reason about, so report no candidate and let the // caller raise its violation. if (!(0, semver_1.valid)(target)) { return null; } const targetChannel = prereleaseChannel(target); const targetLine = releaseLine(target); const pool = metadata.versions.filter((version) => { if (!(0, semver_1.valid)(version) || (0, semver_1.compare)(version, target) > 0) { return false; // unparseable or newer than the target } const channel = prereleaseChannel(version); // Stables always stay; a prerelease stays only in the target's channel or // on a lower ladder rung (every prerelease is out for a stable target). return channel === null || channelAdmits(targetChannel, channel); }); // Prereleases of the target's exact release line rank ahead of the rest - // own channel before lower rungs - then everything else; within each tier, // newest published first. const tier = (version) => { if (releaseLine(version) !== targetLine) { return 2; } const channel = prereleaseChannel(version); if (channel === targetChannel) { return 0; } return channel === null ? 2 : 1; }; pool.sort((a, b) => { const tierDelta = tier(a) - tier(b); if (tierDelta !== 0) { return tierDelta; } const publishedA = publishedAtMs(metadata, a); const publishedB = publishedAtMs(metadata, b); if (publishedA !== publishedB) { return publishedB - publishedA; } return (0, semver_1.compare)(b, a); }); return pool.find((version) => isCompliant(version)) ?? null; } /** * Maps a list of held-back versions to the blocked-candidate shape carried by * MinReleaseAgeViolationError, keeping only versions the registry has a publish * time for. */ function blockedVersionsFrom(metadata, versions) { return versions .filter((v) => !!metadata.time?.[v]) .map((v) => ({ version: v, publishedAt: metadata.time[v] })); } /** * Resolves a spec to a version that complies with the effective cooldown * policy, dispatching to the per-PM pick rules. Throws * MinReleaseAgeViolationError when the PM at this version would fail. */ function pickMinReleaseAgeCompliantVersion(spec, metadata, policy) { switch (policy.behavior.packageManager) { case 'npm': return (0, npm_1.pickNpmVersion)(spec, metadata, policy); case 'pnpm': return (0, pnpm_1.pickPnpmVersion)(spec, metadata, policy); case 'yarn': return (0, yarn_1.pickYarnVersion)(spec, metadata, policy); case 'bun': return (0, bun_1.pickBunVersion)(spec, metadata, policy); } }