From 9c3e9654d9cbbf9a17f4b112b736eba2e2c81d90 Mon Sep 17 00:00:00 2001 From: Michael Garvin Date: Fri, 6 Feb 2026 09:28:41 -0800 Subject: [PATCH] feat: add min-release-age This is a new config that is a way to populate the "before" config using a relative date integer. Credit: @kaezone - https://github.com/npm/cli/pull/8802 @PR3C14D0 - https://github.com/npm/cli/pull/8825 --- .../test/lib/commands/config.js.test.cjs | 2 + tap-snapshots/test/lib/docs.js.test.cjs | 37 +++++++++++++++---- .../config/lib/definitions/definitions.js | 23 ++++++++++++ .../test/type-description.js.test.cjs | 4 ++ workspaces/config/test/index.js | 16 ++++++++ 5 files changed, 74 insertions(+), 8 deletions(-) diff --git a/tap-snapshots/test/lib/commands/config.js.test.cjs b/tap-snapshots/test/lib/commands/config.js.test.cjs index d96ebf3412e04..5cb8fe9d0a094 100644 --- a/tap-snapshots/test/lib/commands/config.js.test.cjs +++ b/tap-snapshots/test/lib/commands/config.js.test.cjs @@ -103,6 +103,7 @@ exports[`test/lib/commands/config.js TAP config list --json > output matches sna "name": null, "maxsockets": 15, "message": "%s", + "min-release-age": null, "node-gyp": "{CWD}/node_modules/node-gyp/bin/node-gyp.js", "node-options": null, "noproxy": [ @@ -280,6 +281,7 @@ logs-max = 10 ; long = false ; overridden by cli maxsockets = 15 message = "%s" +min-release-age = null name = null node-gyp = "{CWD}/node_modules/node-gyp/bin/node-gyp.js" node-options = null diff --git a/tap-snapshots/test/lib/docs.js.test.cjs b/tap-snapshots/test/lib/docs.js.test.cjs index b9b2e32355e94..78e6f170d2cb1 100644 --- a/tap-snapshots/test/lib/docs.js.test.cjs +++ b/tap-snapshots/test/lib/docs.js.test.cjs @@ -290,7 +290,7 @@ If the requested version is a \`dist-tag\` and the given tag does not pass the will be used. For example, \`foo@latest\` might install \`foo@1.2\` even though \`latest\` is \`2.0\`. - +This config cannot be used with: \`min-release-age\` #### \`bin-links\` @@ -1121,6 +1121,21 @@ Any "%s" in the message will be replaced with the version number. +#### \`min-release-age\` + +* Default: null +* Type: null or Number + +If set, npm will build the npm tree such that only versions that were +available more than the given number of days ago will be installed. If there +are no versions available for the current set of dependencies, the command +will error. + +This flag is a complement to \`before\`, which accepts an exact date instead +of a relative number of days. + +This config cannot be used with: \`before\` + #### \`name\` * Default: null @@ -2318,6 +2333,7 @@ Array [ "name", "maxsockets", "message", + "min-release-age", "node-gyp", "node-options", "noproxy", @@ -2474,6 +2490,7 @@ Array [ "name", "maxsockets", "message", + "min-release-age", "node-gyp", "noproxy", "offline", @@ -3475,8 +3492,8 @@ Options: [--include [--include ...]] [--strict-peer-deps] [--prefer-dedupe] [--no-package-lock] [--package-lock-only] [--foreground-scripts] [--ignore-scripts] [--allow-git ] -[--no-audit] [--before ] [--no-bin-links] [--no-fund] [--dry-run] -[--cpu ] [--os ] [--libc ] +[--no-audit] [--before |--min-release-age ] [--no-bin-links] +[--no-fund] [--dry-run] [--cpu ] [--os ] [--libc ] [-w|--workspace [-w|--workspace ...]] [--workspaces] [--include-workspace-root] [--install-links] @@ -3507,6 +3524,7 @@ aliases: add, i, in, ins, inst, insta, instal, isnt, isnta, isntal, isntall #### \`allow-git\` #### \`audit\` #### \`before\` +#### \`min-release-age\` #### \`bin-links\` #### \`fund\` #### \`dry-run\` @@ -3578,8 +3596,8 @@ Options: [--include [--include ...]] [--strict-peer-deps] [--prefer-dedupe] [--no-package-lock] [--package-lock-only] [--foreground-scripts] [--ignore-scripts] [--allow-git ] -[--no-audit] [--before ] [--no-bin-links] [--no-fund] [--dry-run] -[--cpu ] [--os ] [--libc ] +[--no-audit] [--before |--min-release-age ] [--no-bin-links] +[--no-fund] [--dry-run] [--cpu ] [--os ] [--libc ] [-w|--workspace [-w|--workspace ...]] [--workspaces] [--include-workspace-root] [--install-links] @@ -3610,6 +3628,7 @@ alias: it #### \`allow-git\` #### \`audit\` #### \`before\` +#### \`min-release-age\` #### \`bin-links\` #### \`fund\` #### \`dry-run\` @@ -3858,7 +3877,7 @@ npm outdated [ ...] Options: [-a|--all] [--json] [-l|--long] [-p|--parseable] [-g|--global] [-w|--workspace [-w|--workspace ...]] -[--before ] +[--before |--min-release-age ] Run "npm help outdated" for more info @@ -3873,6 +3892,7 @@ npm outdated [ ...] #### \`global\` #### \`workspace\` #### \`before\` +#### \`min-release-age\` ` exports[`test/lib/docs.js TAP usage owner > must match snapshot 1`] = ` @@ -4634,8 +4654,8 @@ Options: [--omit [--omit ...]] [--include [--include ...]] [--strict-peer-deps] [--no-package-lock] [--foreground-scripts] -[--ignore-scripts] [--no-audit] [--before ] [--no-bin-links] [--no-fund] -[--dry-run] +[--ignore-scripts] [--no-audit] [--before |--min-release-age ] +[--no-bin-links] [--no-fund] [--dry-run] [-w|--workspace [-w|--workspace ...]] [--workspaces] [--include-workspace-root] [--install-links] @@ -4662,6 +4682,7 @@ aliases: up, upgrade, udpate #### \`ignore-scripts\` #### \`audit\` #### \`before\` +#### \`min-release-age\` #### \`bin-links\` #### \`fund\` #### \`dry-run\` diff --git a/workspaces/config/lib/definitions/definitions.js b/workspaces/config/lib/definitions/definitions.js index 78818eb1489b5..4c234699e2287 100644 --- a/workspaces/config/lib/definitions/definitions.js +++ b/workspaces/config/lib/definitions/definitions.js @@ -246,6 +246,7 @@ const definitions = { default: null, hint: '', type: [null, Date], + exclusive: ['min-release-age'], description: ` If passed to \`npm install\`, will rebuild the npm tree such that only versions that were available **on or before** the given date are @@ -1347,6 +1348,28 @@ const definitions = { `, flatten, }), + 'min-release-age': new Definition('min-release-age', { + default: null, + hint: '', + type: [null, Number], + exclusive: ['before'], + description: ` + If set, npm will build the npm tree such that only versions that were + available more than the given number of days ago will be installed. If + there are no versions available for the current set of dependencies, the + command will error. + + This flag is a complement to \`before\`, which accepts an exact date + instead of a relative number of days. + `, + flatten: (key, obj, flatOptions) => { + if (obj['min-release-age'] !== null) { + flatOptions.before = new Date(Date.now() - (86400000 * obj['min-release-age'])) + obj.before = flatOptions.before + delete obj['min-release-age'] + } + }, + }), 'node-gyp': new Definition('node-gyp', { default: (() => { try { diff --git a/workspaces/config/tap-snapshots/test/type-description.js.test.cjs b/workspaces/config/tap-snapshots/test/type-description.js.test.cjs index 4eb0a5942f262..79a9820f71746 100644 --- a/workspaces/config/tap-snapshots/test/type-description.js.test.cjs +++ b/workspaces/config/tap-snapshots/test/type-description.js.test.cjs @@ -340,6 +340,10 @@ Object { "message": Array [ Function String(), ], + "min-release-age": Array [ + null, + "numeric value", + ], "name": Array [ null, Function String(), diff --git a/workspaces/config/test/index.js b/workspaces/config/test/index.js index f60070d419bfd..b340bdc7f4919 100644 --- a/workspaces/config/test/index.js +++ b/workspaces/config/test/index.js @@ -1587,3 +1587,19 @@ t.test('abbreviation expansion warnings', async t => { ['warn', 'Expanding --bef to --before. This will stop working in the next major version of npm'], ], 'Warns about expanded abbreviations') }) + +t.test('before and min-release-age', async t => { + const path = t.testdir() + const config = new Config({ + npmPath: `${path}/npm`, + env: {}, + argv: [process.execPath, __filename, '--min-release-age', '30'], + cwd: path, + definitions, + shorthands, + flatten, + }) + await config.load() + // Simple gut check to make sure we didn't do + instead of - + t.ok(config.flat.before < Date.now(), 'before date is in the past not the future') +})