mirror of
https://github.com/pnpm/pnpm.git
synced 2026-06-04 22:44:56 -04:00
fix(engine.pm.commands): honor minimumReleaseAgeExclude in self-update (#11664)
* fix(engine.pm.commands): honor minimumReleaseAgeExclude in self-update * refactor(config.version-policy): centralize publishedBy policy derivation Extract the publishedBy / publishedByExclude derivation duplicated across selfUpdate, dlx, outdated, and deps-resolver into a new `getPublishedByPolicy()` helper, and the version-policy error rewrap into `createPackageVersionPolicyOrThrow()`. Also adds the global self-update test branch (no wantedPackageManager) requested in PR review, and harmonizes the dlx/outdated error code for invalid minimumReleaseAgeExclude patterns with install/self-update. * style(config.version-policy): rename 'callsite' to 'call site' to satisfy cspell --------- Co-authored-by: Zoltan Kochan <z@kochan.io>
This commit is contained in:
14
.changeset/fix-11655-self-update-minimum-release-age.md
Normal file
14
.changeset/fix-11655-self-update-minimum-release-age.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
"@pnpm/config.version-policy": minor
|
||||
"@pnpm/deps.inspection.outdated": patch
|
||||
"@pnpm/engine.pm.commands": patch
|
||||
"@pnpm/exec.commands": patch
|
||||
"@pnpm/installing.deps-resolver": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
Make `pnpm self-update` respect `minimumReleaseAge` (and `minimumReleaseAgeExclude`) when resolving which pnpm version to install.
|
||||
|
||||
When the `latest` dist-tag points to a version newer than the configured age threshold, `self-update` now selects the newest mature version instead unless excluded by `minimumReleaseAgeExclude`.
|
||||
|
||||
Also makes `dlx` and `outdated` surface invalid `minimumReleaseAgeExclude` patterns under the same `ERR_PNPM_INVALID_MINIMUM_RELEASE_AGE_EXCLUDE` error code already used by `install`, instead of leaking the internal `ERR_PNPM_INVALID_VERSION_UNION` / `ERR_PNPM_NAME_PATTERN_IN_VERSION_UNION` codes.
|
||||
@@ -12,6 +12,50 @@ export function createPackageVersionPolicy (patterns: string[]): PackageVersionP
|
||||
return evaluateVersionPolicy.bind(null, rules)
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link createPackageVersionPolicy}, but rewraps parser errors with an
|
||||
* `INVALID_<KEY>` PnpmError so the message points at the user-facing config key
|
||||
* (e.g. `minimumReleaseAgeExclude`) instead of the internal parser code.
|
||||
*/
|
||||
export function createPackageVersionPolicyOrThrow (patterns: string[], key: string): PackageVersionPolicy {
|
||||
try {
|
||||
return createPackageVersionPolicy(patterns)
|
||||
} catch (err) {
|
||||
if (!err || typeof err !== 'object' || !('message' in err)) throw err
|
||||
throw new PnpmError(
|
||||
`INVALID_${key.replace(/([A-Z])/g, '_$1').toUpperCase()}`,
|
||||
`Invalid value in ${key}: ${err.message as string}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export interface PublishedByPolicyOptions {
|
||||
minimumReleaseAge?: number
|
||||
minimumReleaseAgeExclude?: string[]
|
||||
}
|
||||
|
||||
export interface PublishedByPolicy {
|
||||
publishedBy?: Date
|
||||
publishedByExclude?: PackageVersionPolicy
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives the resolver's `publishedBy` cutoff date and `publishedByExclude`
|
||||
* policy from the user's `minimumReleaseAge` / `minimumReleaseAgeExclude`
|
||||
* config. Centralized so every call site computes the cutoff at the same
|
||||
* instant and surfaces invalid exclude patterns under the same error code.
|
||||
*/
|
||||
export function getPublishedByPolicy (opts: PublishedByPolicyOptions): PublishedByPolicy {
|
||||
return {
|
||||
publishedBy: opts.minimumReleaseAge
|
||||
? new Date(Date.now() - opts.minimumReleaseAge * 60 * 1000)
|
||||
: undefined,
|
||||
publishedByExclude: opts.minimumReleaseAgeExclude
|
||||
? createPackageVersionPolicyOrThrow(opts.minimumReleaseAgeExclude, 'minimumReleaseAgeExclude')
|
||||
: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
export function expandPackageVersionSpecs (specs: string[]): Set<string> {
|
||||
const expandedSpecs = new Set<string>()
|
||||
for (const spec of specs) {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { expect, test } from '@jest/globals'
|
||||
import { createPackageVersionPolicy } from '@pnpm/config.version-policy'
|
||||
import {
|
||||
createPackageVersionPolicy,
|
||||
createPackageVersionPolicyOrThrow,
|
||||
getPublishedByPolicy,
|
||||
} from '@pnpm/config.version-policy'
|
||||
|
||||
test('createPackageVersionPolicy()', () => {
|
||||
{
|
||||
@@ -49,3 +53,59 @@ test('createPackageVersionPolicy()', () => {
|
||||
expect(match('pkg')).toStrictEqual(['1.0.0', '1.0.1', '1.0.2'])
|
||||
}
|
||||
})
|
||||
|
||||
test('createPackageVersionPolicyOrThrow() rewraps parser errors with INVALID_<KEY>', () => {
|
||||
expect(() => createPackageVersionPolicyOrThrow(['lodash@^4.17.0'], 'minimumReleaseAgeExclude')).toThrow(
|
||||
expect.objectContaining({
|
||||
code: 'ERR_PNPM_INVALID_MINIMUM_RELEASE_AGE_EXCLUDE',
|
||||
message: expect.stringContaining('Invalid value in minimumReleaseAgeExclude:'),
|
||||
})
|
||||
)
|
||||
expect(() => createPackageVersionPolicyOrThrow(['is-*@1.0.0'], 'trustPolicyExclude')).toThrow(
|
||||
expect.objectContaining({
|
||||
code: 'ERR_PNPM_INVALID_TRUST_POLICY_EXCLUDE',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test('createPackageVersionPolicyOrThrow() returns a working policy for valid input', () => {
|
||||
const policy = createPackageVersionPolicyOrThrow(['axios@1.12.2'], 'minimumReleaseAgeExclude')
|
||||
expect(policy('axios')).toStrictEqual(['1.12.2'])
|
||||
expect(policy('lodash')).toBe(false)
|
||||
})
|
||||
|
||||
test('getPublishedByPolicy() returns undefined fields when no config is set', () => {
|
||||
expect(getPublishedByPolicy({})).toEqual({
|
||||
publishedBy: undefined,
|
||||
publishedByExclude: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
test('getPublishedByPolicy() derives publishedBy from minimumReleaseAge (minutes)', () => {
|
||||
const before = Date.now()
|
||||
const { publishedBy, publishedByExclude } = getPublishedByPolicy({ minimumReleaseAge: 24 * 60 })
|
||||
const after = Date.now()
|
||||
expect(publishedByExclude).toBeUndefined()
|
||||
expect(publishedBy).toBeInstanceOf(Date)
|
||||
// 24h ago, ± the wall-clock drift between sampling `before` and `after`.
|
||||
const expectedMin = before - 24 * 60 * 60 * 1000
|
||||
const expectedMax = after - 24 * 60 * 60 * 1000
|
||||
expect(publishedBy!.getTime()).toBeGreaterThanOrEqual(expectedMin)
|
||||
expect(publishedBy!.getTime()).toBeLessThanOrEqual(expectedMax)
|
||||
})
|
||||
|
||||
test('getPublishedByPolicy() builds publishedByExclude policy when minimumReleaseAgeExclude is set', () => {
|
||||
const { publishedByExclude } = getPublishedByPolicy({
|
||||
minimumReleaseAge: 24 * 60,
|
||||
minimumReleaseAgeExclude: ['pnpm@9.1.0'],
|
||||
})
|
||||
expect(publishedByExclude!('pnpm')).toStrictEqual(['9.1.0'])
|
||||
expect(publishedByExclude!('axios')).toBe(false)
|
||||
})
|
||||
|
||||
test('getPublishedByPolicy() rewraps invalid exclude patterns as ERR_PNPM_INVALID_MINIMUM_RELEASE_AGE_EXCLUDE', () => {
|
||||
expect(() => getPublishedByPolicy({
|
||||
minimumReleaseAge: 24 * 60,
|
||||
minimumReleaseAgeExclude: ['pnpm@^9.0.0'],
|
||||
})).toThrow(expect.objectContaining({ code: 'ERR_PNPM_INVALID_MINIMUM_RELEASE_AGE_EXCLUDE' }))
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createPackageVersionPolicy } from '@pnpm/config.version-policy'
|
||||
import { getPublishedByPolicy } from '@pnpm/config.version-policy'
|
||||
import {
|
||||
type ClientOptions,
|
||||
createResolver,
|
||||
@@ -23,9 +23,7 @@ export type ManifestGetterOptions = Omit<ClientOptions, 'configByUri' | 'minimum
|
||||
export function createManifestGetter (
|
||||
opts: ManifestGetterOptions
|
||||
): (packageName: string, bareSpecifier: string) => Promise<DependencyManifest | null> {
|
||||
const publishedByExclude = opts.minimumReleaseAgeExclude
|
||||
? createPackageVersionPolicy(opts.minimumReleaseAgeExclude)
|
||||
: undefined
|
||||
const { publishedBy, publishedByExclude } = getPublishedByPolicy(opts)
|
||||
|
||||
const { resolve } = createResolver({
|
||||
...opts,
|
||||
@@ -35,10 +33,6 @@ export function createManifestGetter (
|
||||
ignoreMissingTimeField: opts.minimumReleaseAgeIgnoreMissingTime,
|
||||
})
|
||||
|
||||
const publishedBy = opts.minimumReleaseAge
|
||||
? new Date(Date.now() - opts.minimumReleaseAge * 60 * 1000)
|
||||
: undefined
|
||||
|
||||
return getManifest.bind(null, {
|
||||
...opts,
|
||||
resolve,
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"@pnpm/cli.meta": "workspace:*",
|
||||
"@pnpm/cli.utils": "workspace:*",
|
||||
"@pnpm/config.reader": "workspace:*",
|
||||
"@pnpm/config.version-policy": "workspace:*",
|
||||
"@pnpm/deps.graph-hasher": "workspace:*",
|
||||
"@pnpm/error": "workspace:*",
|
||||
"@pnpm/global.commands": "workspace:*",
|
||||
|
||||
@@ -5,6 +5,7 @@ import { linkBins } from '@pnpm/bins.linker'
|
||||
import { isExecutedByCorepack, packageManager } from '@pnpm/cli.meta'
|
||||
import { docsUrl } from '@pnpm/cli.utils'
|
||||
import { type Config, type ConfigContext, parsePackageManager, types as allTypes } from '@pnpm/config.reader'
|
||||
import { getPublishedByPolicy } from '@pnpm/config.version-policy'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import { createResolver } from '@pnpm/installing.client'
|
||||
import { resolvePackageManagerIntegrities } from '@pnpm/installing.env-installer'
|
||||
@@ -60,6 +61,10 @@ export function help (): string {
|
||||
export type SelfUpdateCommandOptions = CreateStoreControllerOptions & Pick<Config,
|
||||
| 'globalPkgDir'
|
||||
| 'lockfileDir'
|
||||
| 'minimumReleaseAge'
|
||||
| 'minimumReleaseAgeExclude'
|
||||
| 'minimumReleaseAgeIgnoreMissingTime'
|
||||
| 'minimumReleaseAgeStrict'
|
||||
| 'modulesDir'
|
||||
| 'pnpmHomeDir'
|
||||
> & Pick<ConfigContext,
|
||||
@@ -75,8 +80,14 @@ export async function handler (
|
||||
throw new PnpmError('CANT_SELF_UPDATE_IN_COREPACK', 'You should update pnpm with corepack')
|
||||
}
|
||||
globalInfo('Checking for updates...')
|
||||
const { resolve } = createResolver({ ...opts, configByUri: opts.configByUri })
|
||||
const { resolve } = createResolver({
|
||||
...opts,
|
||||
configByUri: opts.configByUri,
|
||||
strictPublishedByCheck: Boolean(opts.minimumReleaseAge) && opts.minimumReleaseAgeStrict === true,
|
||||
ignoreMissingTimeField: opts.minimumReleaseAgeIgnoreMissingTime,
|
||||
})
|
||||
const pkgName = 'pnpm'
|
||||
const { publishedBy, publishedByExclude } = getPublishedByPolicy(opts)
|
||||
// `pnpm self-update` (no args) defaults to the `latest` dist-tag, but we
|
||||
// refuse to downgrade in that case — `latest` on the registry can lag the
|
||||
// installed version when a new major has shipped without being tagged.
|
||||
@@ -88,6 +99,8 @@ export async function handler (
|
||||
lockfileDir: opts.lockfileDir ?? opts.dir,
|
||||
preferredVersions: {},
|
||||
projectDir: opts.dir,
|
||||
publishedBy,
|
||||
publishedByExclude,
|
||||
})
|
||||
if (!resolution?.manifest) {
|
||||
throw new PnpmError('CANNOT_RESOLVE_PNPM', `Cannot find "${bareSpecifier}" version of pnpm`)
|
||||
@@ -297,3 +310,4 @@ async function readProjectPinnedPnpmVersion (rootProjectManifestDir: string, spe
|
||||
}
|
||||
return lockfilePinned ?? specMin
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,12 @@ function prepareOptions (dir: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function createMetadata (latest: string, registry: string, otherVersions: string[] = []) {
|
||||
function createMetadata (
|
||||
latest: string,
|
||||
registry: string,
|
||||
otherVersions: string[] = [],
|
||||
time: Record<string, string> = {}
|
||||
) {
|
||||
const versions = [...otherVersions, latest]
|
||||
return {
|
||||
name: 'pnpm',
|
||||
@@ -87,6 +92,7 @@ function createMetadata (latest: string, registry: string, otherVersions: string
|
||||
},
|
||||
},
|
||||
])),
|
||||
time,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,6 +264,171 @@ test('self-update does nothing when pnpm is up to date', async () => {
|
||||
expect(output).toBe('The currently active pnpm v9.0.0 is already "latest" and doesn\'t need an update')
|
||||
})
|
||||
|
||||
test('self-update respects minimumReleaseAge for implicit latest resolution', async () => {
|
||||
const opts = prepare({
|
||||
packageManager: 'pnpm@8.0.0',
|
||||
})
|
||||
const pkgJsonPath = path.join(opts.dir, 'package.json')
|
||||
const now = Date.now()
|
||||
const metadata = createMetadata('9.1.0', opts.registries.default, ['9.0.0'], {
|
||||
'9.0.0': new Date(now - 48 * 60 * 60 * 1000).toISOString(),
|
||||
'9.1.0': new Date(now - 8 * 60 * 60 * 1000).toISOString(),
|
||||
})
|
||||
getMockAgent().get(opts.registries.default.replace(/\/$/, ''))
|
||||
.intercept({ path: '/pnpm', method: 'GET' })
|
||||
.reply(200, metadata)
|
||||
|
||||
const output = await selfUpdate.handler({
|
||||
...opts,
|
||||
minimumReleaseAge: 24 * 60,
|
||||
wantedPackageManager: {
|
||||
name: 'pnpm',
|
||||
version: '8.0.0',
|
||||
},
|
||||
}, [])
|
||||
|
||||
expect(output).toBe('The current project has been updated to use pnpm v9.0.0')
|
||||
expect(JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8')).packageManager).toBe('pnpm@9.0.0')
|
||||
})
|
||||
|
||||
test('global self-update respects minimumReleaseAge: skips immature latest, no-op when older mature matches active', async () => {
|
||||
// Reproduces #11655: a globally-installed pnpm (no project pin / no
|
||||
// wantedPackageManager) must not jump to a "latest" version younger than
|
||||
// minimumReleaseAge. Active pnpm is mocked as 9.0.0 at the top of this
|
||||
// file. The registry's `latest` (9.1.0) is 8h old — immature — so the
|
||||
// resolver should fall back to 9.0.0, which equals the active version,
|
||||
// producing a no-op rather than reinstalling.
|
||||
const opts = prepare()
|
||||
const now = Date.now()
|
||||
const metadata = createMetadata('9.1.0', opts.registries.default, ['9.0.0'], {
|
||||
'9.0.0': new Date(now - 48 * 60 * 60 * 1000).toISOString(),
|
||||
'9.1.0': new Date(now - 8 * 60 * 60 * 1000).toISOString(),
|
||||
})
|
||||
getMockAgent().get(opts.registries.default.replace(/\/$/, ''))
|
||||
.intercept({ path: '/pnpm', method: 'GET' })
|
||||
.reply(200, metadata)
|
||||
|
||||
const output = await selfUpdate.handler({
|
||||
...opts,
|
||||
minimumReleaseAge: 24 * 60,
|
||||
}, [])
|
||||
|
||||
expect(output).toBe('The currently active pnpm v9.0.0 is already "latest" and doesn\'t need an update')
|
||||
// No global install dir should have been created.
|
||||
const globalDir = path.join(opts.pnpmHomeDir, 'global', 'v11')
|
||||
expect(fs.existsSync(globalDir)).toBe(false)
|
||||
})
|
||||
|
||||
test('self-update respects minimumReleaseAgeExclude for implicit latest resolution', async () => {
|
||||
const opts = prepare({
|
||||
packageManager: 'pnpm@8.0.0',
|
||||
})
|
||||
const pkgJsonPath = path.join(opts.dir, 'package.json')
|
||||
const now = Date.now()
|
||||
const metadata = createMetadata('9.1.0', opts.registries.default, ['9.0.0'], {
|
||||
'9.0.0': new Date(now - 48 * 60 * 60 * 1000).toISOString(),
|
||||
'9.1.0': new Date(now - 8 * 60 * 60 * 1000).toISOString(),
|
||||
})
|
||||
getMockAgent().get(opts.registries.default.replace(/\/$/, ''))
|
||||
.intercept({ path: '/pnpm', method: 'GET' })
|
||||
.reply(200, metadata)
|
||||
|
||||
const output = await selfUpdate.handler({
|
||||
...opts,
|
||||
minimumReleaseAge: 24 * 60,
|
||||
minimumReleaseAgeExclude: ['pnpm'],
|
||||
wantedPackageManager: {
|
||||
name: 'pnpm',
|
||||
version: '8.0.0',
|
||||
},
|
||||
}, [])
|
||||
|
||||
expect(output).toBe('The current project has been updated to use pnpm v9.1.0')
|
||||
expect(JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8')).packageManager).toBe('pnpm@9.1.0')
|
||||
})
|
||||
|
||||
test('self-update respects minimumReleaseAgeExclude exact version for implicit latest resolution', async () => {
|
||||
const opts = prepare({
|
||||
packageManager: 'pnpm@8.0.0',
|
||||
})
|
||||
const pkgJsonPath = path.join(opts.dir, 'package.json')
|
||||
const now = Date.now()
|
||||
const metadata = createMetadata('9.1.0', opts.registries.default, ['9.0.0'], {
|
||||
'9.0.0': new Date(now - 48 * 60 * 60 * 1000).toISOString(),
|
||||
'9.1.0': new Date(now - 8 * 60 * 60 * 1000).toISOString(),
|
||||
})
|
||||
getMockAgent().get(opts.registries.default.replace(/\/$/, ''))
|
||||
.intercept({ path: '/pnpm', method: 'GET' })
|
||||
.reply(200, metadata)
|
||||
|
||||
const output = await selfUpdate.handler({
|
||||
...opts,
|
||||
minimumReleaseAge: 24 * 60,
|
||||
minimumReleaseAgeExclude: ['pnpm@9.1.0'],
|
||||
wantedPackageManager: {
|
||||
name: 'pnpm',
|
||||
version: '8.0.0',
|
||||
},
|
||||
}, [])
|
||||
|
||||
expect(output).toBe('The current project has been updated to use pnpm v9.1.0')
|
||||
expect(JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8')).packageManager).toBe('pnpm@9.1.0')
|
||||
})
|
||||
|
||||
test('self-update does not bypass minimumReleaseAge when minimumReleaseAgeExclude exact version does not match latest', async () => {
|
||||
const opts = prepare({
|
||||
packageManager: 'pnpm@8.0.0',
|
||||
})
|
||||
const pkgJsonPath = path.join(opts.dir, 'package.json')
|
||||
const now = Date.now()
|
||||
const metadata = createMetadata('9.1.0', opts.registries.default, ['9.0.0'], {
|
||||
'9.0.0': new Date(now - 48 * 60 * 60 * 1000).toISOString(),
|
||||
'9.1.0': new Date(now - 8 * 60 * 60 * 1000).toISOString(),
|
||||
})
|
||||
getMockAgent().get(opts.registries.default.replace(/\/$/, ''))
|
||||
.intercept({ path: '/pnpm', method: 'GET' })
|
||||
.reply(200, metadata)
|
||||
|
||||
const output = await selfUpdate.handler({
|
||||
...opts,
|
||||
minimumReleaseAge: 24 * 60,
|
||||
minimumReleaseAgeExclude: ['pnpm@9.0.0'],
|
||||
wantedPackageManager: {
|
||||
name: 'pnpm',
|
||||
version: '8.0.0',
|
||||
},
|
||||
}, [])
|
||||
|
||||
expect(output).toBe('The current project has been updated to use pnpm v9.0.0')
|
||||
expect(JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8')).packageManager).toBe('pnpm@9.0.0')
|
||||
})
|
||||
|
||||
test('self-update throws on invalid minimumReleaseAgeExclude pattern', async () => {
|
||||
const opts = prepare({
|
||||
packageManager: 'pnpm@8.0.0',
|
||||
})
|
||||
const now = Date.now()
|
||||
const metadata = createMetadata('9.1.0', opts.registries.default, ['9.0.0'], {
|
||||
'9.0.0': new Date(now - 48 * 60 * 60 * 1000).toISOString(),
|
||||
'9.1.0': new Date(now - 8 * 60 * 60 * 1000).toISOString(),
|
||||
})
|
||||
getMockAgent().get(opts.registries.default.replace(/\/$/, ''))
|
||||
.intercept({ path: '/pnpm', method: 'GET' })
|
||||
.reply(200, metadata)
|
||||
|
||||
await expect(selfUpdate.handler({
|
||||
...opts,
|
||||
minimumReleaseAge: 24 * 60,
|
||||
minimumReleaseAgeExclude: ['pnpm@^9.0.0'],
|
||||
wantedPackageManager: {
|
||||
name: 'pnpm',
|
||||
version: '8.0.0',
|
||||
},
|
||||
}, [])).rejects.toMatchObject({
|
||||
code: 'ERR_PNPM_INVALID_MINIMUM_RELEASE_AGE_EXCLUDE',
|
||||
})
|
||||
})
|
||||
|
||||
test('self-update refuses to downgrade when latest is older than current', async () => {
|
||||
const opts = prepare()
|
||||
getMockAgent().get(opts.registries.default.replace(/\/$/, ''))
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
{
|
||||
"path": "../../../config/reader"
|
||||
},
|
||||
{
|
||||
"path": "../../../config/version-policy"
|
||||
},
|
||||
{
|
||||
"path": "../../../core/constants"
|
||||
},
|
||||
|
||||
@@ -12,7 +12,7 @@ import type { CommandHandlerMap } from '@pnpm/cli.command'
|
||||
import { OUTPUT_OPTIONS } from '@pnpm/cli.common-cli-options-help'
|
||||
import { docsUrl, readProjectManifestOnly } from '@pnpm/cli.utils'
|
||||
import { type Config, types } from '@pnpm/config.reader'
|
||||
import { createPackageVersionPolicy } from '@pnpm/config.version-policy'
|
||||
import { getPublishedByPolicy } from '@pnpm/config.version-policy'
|
||||
import { createHexHash } from '@pnpm/crypto.hash'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import { createResolver } from '@pnpm/installing.client'
|
||||
@@ -129,10 +129,7 @@ export async function handler (
|
||||
timeout: opts.fetchTimeout,
|
||||
})
|
||||
const resolvedPkgAliases: string[] = []
|
||||
const publishedBy = opts.minimumReleaseAge ? new Date(Date.now() - opts.minimumReleaseAge * 60 * 1000) : undefined
|
||||
const publishedByExclude = opts.minimumReleaseAgeExclude
|
||||
? createPackageVersionPolicy(opts.minimumReleaseAgeExclude)
|
||||
: undefined
|
||||
const { publishedBy, publishedByExclude } = getPublishedByPolicy(opts)
|
||||
const resolvedPkgs = await Promise.all(pkgs.map(async (pkg) => {
|
||||
const { alias, bareSpecifier } = parseWantedDependency(pkg) || {}
|
||||
if (alias == null) return pkg
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { resolveFromCatalog } from '@pnpm/catalogs.resolver'
|
||||
import type { Catalogs } from '@pnpm/catalogs.types'
|
||||
import { createPackageVersionPolicy } from '@pnpm/config.version-policy'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import { createPackageVersionPolicyOrThrow, getPublishedByPolicy } from '@pnpm/config.version-policy'
|
||||
import type { LockfileObject } from '@pnpm/lockfile.types'
|
||||
import { globalWarn } from '@pnpm/logger'
|
||||
import type { PatchGroupRecord } from '@pnpm/patching.config'
|
||||
@@ -11,7 +10,6 @@ import type { StoreController } from '@pnpm/store.controller-types'
|
||||
import type {
|
||||
AllowBuild,
|
||||
AllowedDeprecatedVersions,
|
||||
PackageVersionPolicy,
|
||||
PinnedVersion,
|
||||
PkgResolutionId,
|
||||
ProjectId,
|
||||
@@ -169,6 +167,7 @@ export async function resolveDependencyTree<T> (
|
||||
): Promise<ResolveDependencyTreeResult> {
|
||||
const wantedToBeSkippedPackageIds = new Set<PkgResolutionId>()
|
||||
const autoInstallPeers = opts.autoInstallPeers === true
|
||||
const { publishedBy, publishedByExclude } = getPublishedByPolicy(opts)
|
||||
const ctx: ResolutionContext = {
|
||||
allowBuild: opts.allowBuild,
|
||||
autoInstallPeers,
|
||||
@@ -215,23 +214,14 @@ export async function resolveDependencyTree<T> (
|
||||
missingPeersOfChildrenByPkgId: {},
|
||||
hoistPeers: autoInstallPeers || opts.dedupePeerDependents,
|
||||
allPeerDepNames: new Set(),
|
||||
maximumPublishedBy: opts.minimumReleaseAge ? new Date(Date.now() - opts.minimumReleaseAge * 60 * 1000) : undefined,
|
||||
publishedByExclude: opts.minimumReleaseAgeExclude ? createPackageVersionPolicyByExclude(opts.minimumReleaseAgeExclude, 'minimumReleaseAgeExclude') : undefined,
|
||||
maximumPublishedBy: publishedBy,
|
||||
publishedByExclude,
|
||||
trustPolicy: opts.trustPolicy,
|
||||
trustPolicyExclude: opts.trustPolicyExclude ? createPackageVersionPolicyByExclude(opts.trustPolicyExclude, 'trustPolicyExclude') : undefined,
|
||||
trustPolicyExclude: opts.trustPolicyExclude ? createPackageVersionPolicyOrThrow(opts.trustPolicyExclude, 'trustPolicyExclude') : undefined,
|
||||
trustPolicyIgnoreAfter: opts.trustPolicyIgnoreAfter,
|
||||
blockExoticSubdeps: opts.blockExoticSubdeps,
|
||||
}
|
||||
|
||||
function createPackageVersionPolicyByExclude (patterns: string[], key: string): PackageVersionPolicy {
|
||||
try {
|
||||
return createPackageVersionPolicy(patterns)
|
||||
} catch (err) {
|
||||
if (!err || typeof err !== 'object' || !('message' in err)) throw err
|
||||
throw new PnpmError(`INVALID_${key.replace(/([A-Z])/g, '_$1').toUpperCase()}`, `Invalid value in ${key}: ${err.message as string}`)
|
||||
}
|
||||
}
|
||||
|
||||
const resolveArgs: ImporterToResolve[] = importers.map((importer) => {
|
||||
const projectSnapshot = opts.wantedLockfile.importers[importer.id]
|
||||
// This may be optimized.
|
||||
|
||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -3895,6 +3895,9 @@ importers:
|
||||
'@pnpm/config.reader':
|
||||
specifier: workspace:*
|
||||
version: link:../../../config/reader
|
||||
'@pnpm/config.version-policy':
|
||||
specifier: workspace:*
|
||||
version: link:../../../config/version-policy
|
||||
'@pnpm/deps.graph-hasher':
|
||||
specifier: workspace:*
|
||||
version: link:../../../deps/graph-hasher
|
||||
|
||||
Reference in New Issue
Block a user