mirror of
https://github.com/pnpm/pnpm.git
synced 2026-04-27 18:46:18 -04:00
feat: add minimumReleaseAgeStrict setting (#11234)
- Adds a new `minimumReleaseAgeStrict` setting (default: `false`) - When `false` (default), pnpm falls back to versions that don't meet the `minimumReleaseAge` constraint if no mature versions satisfy the range being resolved - Set to `true` to preserve the previous strict behavior (error when no mature version matches)
This commit is contained in:
8
.changeset/minimum-release-age-strict.md
Normal file
8
.changeset/minimum-release-age-strict.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
"@pnpm/config.reader": minor
|
||||
"@pnpm/store.connection-manager": minor
|
||||
"@pnpm/deps.inspection.outdated": minor
|
||||
"pnpm": minor
|
||||
---
|
||||
|
||||
Added a new setting `minimumReleaseAgeStrict` that is `false` by default. When disabled (the default), pnpm falls back to versions that don't meet the `minimumReleaseAge` constraint if no mature versions satisfy the range being resolved. Set to `true` to fail installation instead.
|
||||
@@ -249,6 +249,7 @@ export interface Config extends OptionsFromRootManifest {
|
||||
preserveAbsolutePaths?: boolean
|
||||
minimumReleaseAge?: number
|
||||
minimumReleaseAgeExclude?: string[]
|
||||
minimumReleaseAgeStrict?: boolean
|
||||
fetchWarnTimeoutMs?: number
|
||||
fetchMinSpeedKiBps?: number
|
||||
trustPolicy?: TrustPolicy
|
||||
|
||||
@@ -36,6 +36,7 @@ export const pnpmConfigFileKeys = [
|
||||
'dlx-cache-max-age',
|
||||
'minimum-release-age',
|
||||
'minimum-release-age-exclude',
|
||||
'minimum-release-age-strict',
|
||||
'network-concurrency',
|
||||
'noproxy',
|
||||
'npm-path',
|
||||
|
||||
@@ -71,6 +71,7 @@ export const pnpmTypes = {
|
||||
'dlx-cache-max-age': Number,
|
||||
'minimum-release-age': Number,
|
||||
'minimum-release-age-exclude': [String, Array],
|
||||
'minimum-release-age-strict': Boolean,
|
||||
'modules-dir': String,
|
||||
'network-concurrency': Number,
|
||||
'node-linker': ['pnp', 'isolated', 'hoisted'],
|
||||
|
||||
@@ -12,6 +12,7 @@ interface GetManifestOpts {
|
||||
configByUri: object
|
||||
minimumReleaseAge?: number
|
||||
minimumReleaseAgeExclude?: string[]
|
||||
minimumReleaseAgeStrict?: boolean
|
||||
}
|
||||
|
||||
export type ManifestGetterOptions = Omit<ClientOptions, 'configByUri' | 'minimumReleaseAgeExclude' | 'storeIndex'>
|
||||
@@ -29,7 +30,7 @@ export function createManifestGetter (
|
||||
...opts,
|
||||
configByUri: opts.configByUri,
|
||||
filterMetadata: false, // We need all the data from metadata for "outdated --long" to work.
|
||||
strictPublishedByCheck: Boolean(opts.minimumReleaseAge),
|
||||
strictPublishedByCheck: Boolean(opts.minimumReleaseAge) && opts.minimumReleaseAgeStrict === true,
|
||||
})
|
||||
|
||||
const publishedBy = opts.minimumReleaseAge
|
||||
|
||||
@@ -106,6 +106,7 @@ export async function handler (
|
||||
configByUri: opts.configByUri,
|
||||
fullMetadata,
|
||||
filterMetadata: fullMetadata,
|
||||
strictPublishedByCheck: Boolean(opts.minimumReleaseAge) && opts.minimumReleaseAgeStrict === true,
|
||||
retry: {
|
||||
factor: opts.fetchRetryFactor,
|
||||
maxTimeout: opts.fetchRetryMaxtimeout,
|
||||
|
||||
@@ -396,6 +396,7 @@ test('dlx should fail when the requested package does not meet the minimum age r
|
||||
...DEFAULT_OPTS,
|
||||
dir: path.resolve('project'),
|
||||
minimumReleaseAge: 60 * 24 * 10000,
|
||||
minimumReleaseAgeStrict: true,
|
||||
registries: {
|
||||
// We must use the public registry instead of verdaccio here
|
||||
// because verdaccio has the "times" field in the abbreviated metadata too.
|
||||
|
||||
@@ -367,7 +367,7 @@ test('add: fail trying to install @pnpm/exe', async () => {
|
||||
expect(err.code).toBe('ERR_PNPM_GLOBAL_PNPM_INSTALL')
|
||||
})
|
||||
|
||||
test('minimumReleaseAge makes install fail if there is no version that was published before the cutoff', async () => {
|
||||
test('minimumReleaseAge with minimumReleaseAgeStrict enabled makes install fail if there is no version that was published before the cutoff', async () => {
|
||||
prepareEmpty()
|
||||
|
||||
const isOdd011ReleaseDate = new Date(2016, 11, 7 - 2) // 0.1.1 was released at 2016-12-07T07:18:01.205Z
|
||||
@@ -378,6 +378,7 @@ test('minimumReleaseAge makes install fail if there is no version that was publi
|
||||
...DEFAULT_OPTIONS,
|
||||
dir: path.resolve('project'),
|
||||
minimumReleaseAge,
|
||||
minimumReleaseAgeStrict: true,
|
||||
linkWorkspacePackages: false,
|
||||
}, ['is-odd@0.1.1'])).rejects.toThrow(/Version 0\.1\.1 \(released .+\) of is-odd does not meet the minimumReleaseAge constraint/)
|
||||
})
|
||||
|
||||
@@ -7,6 +7,9 @@ const isOdd011ReleaseDate = new Date(2016, 11, 7 - 2) // 0.1.1 was released at 2
|
||||
const diff = Date.now() - isOdd011ReleaseDate.getTime()
|
||||
const minimumReleaseAge = diff / (60 * 1000) // converting to minutes
|
||||
|
||||
// A very high value that makes ALL versions immature (cutoff date would be before any version was published)
|
||||
const allImmatureMinimumReleaseAge = Date.now() / (60 * 1000)
|
||||
|
||||
test('minimumReleaseAge prevents installation of versions that do not meet the required publish date cutoff', async () => {
|
||||
prepareEmpty()
|
||||
|
||||
@@ -60,6 +63,30 @@ test('minimumReleaseAge applies to versions not in minimumReleaseAgeExclude', as
|
||||
expect(manifest.dependencies!['is-odd']).toBe('~0.1.0')
|
||||
})
|
||||
|
||||
test('minimumReleaseAge falls back to immature version when no mature version satisfies the range (non-strict mode)', async () => {
|
||||
prepareEmpty()
|
||||
|
||||
// With non-strict mode (default), falls back to installing an immature version.
|
||||
// The fallback picks the lowest matching version (0.1.0), which differs from
|
||||
// normal resolution without minimumReleaseAge that would pick the highest (0.1.2).
|
||||
const opts = testDefaults({ minimumReleaseAge: allImmatureMinimumReleaseAge })
|
||||
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['is-odd@0.1'], opts)
|
||||
|
||||
expect(manifest.dependencies!['is-odd']).toBe('~0.1.0')
|
||||
})
|
||||
|
||||
test('minimumReleaseAge throws when no mature version satisfies the range and strict mode is enabled', async () => {
|
||||
prepareEmpty()
|
||||
|
||||
await expect(async () => {
|
||||
const opts = testDefaults(
|
||||
{ minimumReleaseAge: allImmatureMinimumReleaseAge },
|
||||
{ strictPublishedByCheck: true }
|
||||
)
|
||||
await addDependenciesToPackage({}, ['is-odd@0.1'], opts)
|
||||
}).rejects.toThrow(/does not meet the minimumReleaseAge constraint/)
|
||||
})
|
||||
|
||||
test('throws error when semver range is used in minimumReleaseAgeExclude', async () => {
|
||||
prepareEmpty()
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ export type CreateNewStoreControllerOptions = CreateResolverOptions & Pick<Confi
|
||||
| 'localAddress'
|
||||
| 'maxSockets'
|
||||
| 'minimumReleaseAge'
|
||||
| 'minimumReleaseAgeStrict'
|
||||
| 'networkConcurrency'
|
||||
| 'noProxy'
|
||||
| 'offline'
|
||||
@@ -109,7 +110,7 @@ export async function createNewStoreController (
|
||||
includeOnlyPackageFiles: !opts.deployAllFiles,
|
||||
saveWorkspaceProtocol: opts.saveWorkspaceProtocol,
|
||||
preserveAbsolutePaths: opts.preserveAbsolutePaths,
|
||||
strictPublishedByCheck: Boolean(opts.minimumReleaseAge),
|
||||
strictPublishedByCheck: Boolean(opts.minimumReleaseAge) && opts.minimumReleaseAgeStrict === true,
|
||||
storeIndex,
|
||||
})
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user