mirror of
https://github.com/pnpm/pnpm.git
synced 2026-07-22 05:32:44 -04:00
- Upgrade `@pnpm/semver-diff` and `@pnpm/colorize-semver-diff` to v2, which expose the helpers as named exports. - Update the call sites in `@pnpm/deps.inspection.commands` and `@pnpm/installing.commands` from `semverDiff.default(...)` / `colorizeSemverDiff.default(...)` to plain `semverDiff(...)` / `colorizeSemverDiff(...)`. - Refactor `buildPkgChoice` in `getUpdateChoices.ts` to build the row as a `string[]`. Previously the row was an object whose values relied on `nextVersion` being inferred as `any` (a side effect of the broken `.default` access poisoning the type) — that masked `outdatedPkg.current` and `outdatedPkg.workspace` being `string | undefined`. With the v2 named imports the types tighten up, and `Object.values(lineParts)` would no longer assign cleanly to `string[]`. The previous v1 packages exported their helpers as `module.exports.default = fn`, so `.default(...)` only worked through the legacy CJS interop — and it broke under Node.js ESM (which is what the Jest runner uses with `--experimental-vm-modules`). Most of the `deps/inspection/commands` outdated tests had been silently failing on `main` with `TypeError: semverDiff.default is not a function`; this change brings them back.
165 lines
5.1 KiB
TypeScript
165 lines
5.1 KiB
TypeScript
import { PnpmError } from '@pnpm/error'
|
|
import { parseJsrSpecifier } from '@pnpm/resolving.jsr-specifier-parser'
|
|
import { parseNpmTarballUrl } from 'parse-npm-tarball-url'
|
|
import semver from 'semver'
|
|
import getVersionSelectorType from 'version-selector-type'
|
|
|
|
export interface RegistryPackageSpec {
|
|
type: 'tag' | 'version' | 'range'
|
|
name: string
|
|
fetchSpec: string
|
|
normalizedBareSpecifier?: string
|
|
}
|
|
|
|
export function parseBareSpecifier (
|
|
bareSpecifier: string,
|
|
alias: string | undefined,
|
|
defaultTag: string,
|
|
registry: string
|
|
): RegistryPackageSpec | null {
|
|
let name = alias
|
|
if (bareSpecifier.startsWith('npm:')) {
|
|
bareSpecifier = bareSpecifier.slice(4)
|
|
// `npm:<version_selector>` — fall back to the outer dependency alias as
|
|
// the package name, mirroring the named-registry shape (e.g. `gh:^1.0.0`).
|
|
// Restricted to semver ranges/versions so unscoped package names like
|
|
// `npm:is-positive` keep their npm package-aliasing meaning.
|
|
if (alias && semver.validRange(bareSpecifier) != null) {
|
|
name = alias
|
|
} else {
|
|
const index = bareSpecifier.lastIndexOf('@')
|
|
if (index < 1) {
|
|
name = bareSpecifier
|
|
bareSpecifier = defaultTag
|
|
} else {
|
|
name = bareSpecifier.slice(0, index)
|
|
bareSpecifier = bareSpecifier.slice(index + 1)
|
|
}
|
|
}
|
|
}
|
|
if (name) {
|
|
const selector = getVersionSelectorType(bareSpecifier)
|
|
if (selector != null) {
|
|
return {
|
|
fetchSpec: selector.normalized,
|
|
name,
|
|
type: selector.type,
|
|
}
|
|
}
|
|
}
|
|
if (bareSpecifier.startsWith(registry)) {
|
|
const pkg = parseNpmTarballUrl(bareSpecifier)
|
|
if (pkg != null) {
|
|
return {
|
|
fetchSpec: pkg.version,
|
|
name: pkg.name,
|
|
normalizedBareSpecifier: bareSpecifier,
|
|
type: 'version',
|
|
}
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
export interface JsrRegistryPackageSpec extends RegistryPackageSpec {
|
|
jsrPkgName: string
|
|
}
|
|
|
|
export function parseJsrSpecifierToRegistryPackageSpec (
|
|
rawSpecifier: string,
|
|
alias: string | undefined,
|
|
defaultTag: string
|
|
): JsrRegistryPackageSpec | null {
|
|
const spec = parseJsrSpecifier(rawSpecifier, alias)
|
|
if (!spec?.npmPkgName) return null
|
|
|
|
const selector = getVersionSelectorType(spec.versionSelector ?? defaultTag)
|
|
if (selector == null) return null
|
|
|
|
return {
|
|
fetchSpec: selector.normalized,
|
|
name: spec.npmPkgName,
|
|
type: selector.type,
|
|
jsrPkgName: spec.jsrPkgName,
|
|
}
|
|
}
|
|
|
|
export const BUILTIN_NAMED_REGISTRIES: Readonly<Record<string, string>> = Object.freeze({
|
|
gh: 'https://npm.pkg.github.com/',
|
|
})
|
|
|
|
export interface NamedRegistryPackageSpec extends RegistryPackageSpec {
|
|
registryName: string
|
|
}
|
|
|
|
// Parses a named-registry specifier of the shape `<alias>:<body>` into a
|
|
// RegistryPackageSpec. Returns `null` when the specifier does not use one of
|
|
// the configured aliases, so the caller can fall through to other resolvers.
|
|
// Supported shapes:
|
|
// - `<alias>:[@<owner>/]<name>[@<version_selector>]`
|
|
// - `<alias>:<version_selector>` paired with a package alias
|
|
export function parseNamedRegistrySpecifierToRegistryPackageSpec (
|
|
rawSpecifier: string,
|
|
knownRegistryNames: ReadonlySet<string>,
|
|
packageAlias: string | undefined,
|
|
defaultTag: string
|
|
): NamedRegistryPackageSpec | null {
|
|
const colon = rawSpecifier.indexOf(':')
|
|
if (colon <= 0) return null
|
|
const registryName = rawSpecifier.substring(0, colon)
|
|
if (!knownRegistryNames.has(registryName)) return null
|
|
|
|
const body = rawSpecifier.substring(colon + 1)
|
|
let pkgName: string
|
|
let versionSelector: string | undefined
|
|
|
|
if (semver.validRange(body) != null) {
|
|
// `<alias>:<version_selector>` — fall back to the dependency alias as
|
|
// the package name. Unresolvable without one.
|
|
if (!packageAlias) return null
|
|
pkgName = packageAlias
|
|
versionSelector = body
|
|
} else if (body[0] === '@') {
|
|
// `<alias>:@<owner>/<name>[@<version_selector>]` — scoped package.
|
|
const index = body.lastIndexOf('@')
|
|
if (index === 0) {
|
|
pkgName = body
|
|
} else {
|
|
pkgName = body.substring(0, index)
|
|
versionSelector = body.substring(index + '@'.length)
|
|
}
|
|
if (pkgName.indexOf('/') === -1 || pkgName.endsWith('/')) {
|
|
throw new PnpmError(
|
|
'INVALID_NAMED_REGISTRY_PACKAGE_NAME',
|
|
`The package name '${pkgName}' in named registry '${registryName}:' is invalid`
|
|
)
|
|
}
|
|
} else if (packageAlias?.startsWith('@')) {
|
|
// `<alias>:<tag>` paired with a scoped alias — body is a version
|
|
// selector (tag/dist-tag). Mirrors GitHub Packages, where the package
|
|
// is always scoped and a bare body is a tag.
|
|
pkgName = packageAlias
|
|
versionSelector = body
|
|
} else {
|
|
// `<alias>:<name>[@<version_selector>]` — unscoped package in body.
|
|
const index = body.lastIndexOf('@')
|
|
if (index < 1) {
|
|
pkgName = body
|
|
} else {
|
|
pkgName = body.substring(0, index)
|
|
versionSelector = body.substring(index + '@'.length)
|
|
}
|
|
if (!pkgName) return null
|
|
}
|
|
|
|
const selector = getVersionSelectorType(versionSelector ?? defaultTag)
|
|
if (selector == null) return null
|
|
|
|
return {
|
|
fetchSpec: selector.normalized,
|
|
name: pkgName,
|
|
type: selector.type,
|
|
registryName,
|
|
}
|
|
}
|