Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/commands/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class Install extends ArboristWorkspaceCmd {
'audit',
'before',
'min-release-age',
'min-release-age-exclude',
'bin-links',
'fund',
'dry-run',
Expand Down
1 change: 1 addition & 0 deletions lib/commands/outdated.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class Outdated extends ArboristWorkspaceCmd {
'global',
'workspace',
'before',
'min-release-age-exclude',
]

#tree
Expand Down
3 changes: 3 additions & 0 deletions lib/commands/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ class Query extends BaseCommand {
'include-workspace-root',
'package-lock-only',
'expect-results',
'before',
'min-release-age',
'min-release-age-exclude',
]

constructor (...args) {
Expand Down
18 changes: 17 additions & 1 deletion node_modules/npm-pick-manifest/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const npa = require('npm-package-arg')
const semver = require('semver')
const { minimatch } = require('minimatch')
const { checkEngine } = require('npm-install-checks')
const normalizeBin = require('npm-normalize-package-bin')

Expand All @@ -21,6 +22,16 @@ const avoidSemverOpt = { includePrerelease: true, loose: true }
const shouldAvoid = (ver, avoid) =>
avoid && semver.satisfies(ver, avoid, avoidSemverOpt)

const isExcludedFromTimeFilter = (name, exclude) => {
if (!exclude) {
return false
}
const patterns = Array.isArray(exclude) ? exclude : [exclude]
return patterns
.filter(pattern => typeof pattern === 'string' && pattern)
.some(pattern => minimatch(name, pattern))
}

const decorateAvoid = (result, avoid) =>
result && shouldAvoid(result.version, avoid)
? { ...result, _shouldAvoid: true }
Expand All @@ -35,6 +46,7 @@ const pickManifest = (packument, wanted, opts) => {
includeStaged = false,
avoid = null,
avoidStrict = false,
minReleaseAgeExclude = opts['min-release-age-exclude'],
} = opts

const { name, time: verTimes } = packument
Expand Down Expand Up @@ -84,7 +96,9 @@ const pickManifest = (packument, wanted, opts) => {
const restricted = (packument.policyRestrictions &&
packument.policyRestrictions.versions) || {}

const time = before && verTimes ? +(new Date(before)) : Infinity
const time = before && verTimes && !isExcludedFromTimeFilter(name, minReleaseAgeExclude)
? +(new Date(before))
: Infinity
const spec = npa.resolve(name, wanted || defaultTag)
const type = spec.type
const distTags = packument['dist-tags'] || {}
Expand Down Expand Up @@ -217,3 +231,5 @@ module.exports = (packument, wanted, opts = {}) => {
defaultTag,
})
}

module.exports.isExcludedFromTimeFilter = isExcludedFromTimeFilter
55 changes: 51 additions & 4 deletions tap-snapshots/test/lib/docs.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -1151,6 +1151,19 @@ This config cannot be used with: \`before\`

This value is not exported to the environment for child processes.

#### \`min-release-age-exclude\`

* Default:
* Type: String (can be set multiple times)

Exclude package names from \`min-release-age\` and \`before\` publish-time
filtering.

Values can be exact package names (\`left-pad\`) or glob patterns
(\`@myorg/*\`).

This value is not exported to the environment for child processes.

#### \`name\`

* Default: null
Expand Down Expand Up @@ -2350,6 +2363,7 @@ Array [
"maxsockets",
"message",
"min-release-age",
"min-release-age-exclude",
"node-gyp",
"node-options",
"noproxy",
Expand Down Expand Up @@ -2508,6 +2522,7 @@ Array [
"maxsockets",
"message",
"min-release-age",
"min-release-age-exclude",
"node-gyp",
"noproxy",
"offline",
Expand Down Expand Up @@ -2675,6 +2690,7 @@ Object {
"logColor": false,
"maxSockets": 15,
"message": "%s",
"minReleaseAgeExclude": Array [],
"name": null,
"nodeBin": "{NODE}",
"nodeGyp": "{CWD}/node_modules/node-gyp/bin/node-gyp.js",
Expand Down Expand Up @@ -3948,8 +3964,10 @@ Options:
[--include <prod|dev|optional|peer> [--include <prod|dev|optional|peer> ...]]
[--strict-peer-deps] [--prefer-dedupe] [--no-package-lock] [--package-lock-only]
[--foreground-scripts] [--ignore-scripts] [--allow-git <all|none|root>]
[--no-audit] [--before <date>|--min-release-age <days>] [--no-bin-links]
[--no-fund] [--dry-run] [--cpu <cpu>] [--os <os>] [--libc <libc>]
[--no-audit] [--before <date>|--min-release-age <days>]
[--min-release-age-exclude <package-name> [--min-release-age-exclude <package-name> ...]]
[--no-bin-links] [--no-fund] [--dry-run] [--cpu <cpu>] [--os <os>]
[--libc <libc>]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
[--workspaces] [--include-workspace-root] [--install-links]

Expand Down Expand Up @@ -4007,6 +4025,9 @@ Options:
--min-release-age
If set, npm will build the npm tree such that only versions that were

--min-release-age-exclude
Exclude package names from \`min-release-age\` and \`before\` publish-time

--bin-links
Tells npm to create symlinks (or \`.cmd\` shims on Windows) for package

Expand Down Expand Up @@ -4066,6 +4087,7 @@ aliases: add, i, in, ins, inst, insta, instal, isnt, isnta, isntal, isntall
#### \`audit\`
#### \`before\`
#### \`min-release-age\`
#### \`min-release-age-exclude\`
#### \`bin-links\`
#### \`fund\`
#### \`dry-run\`
Expand Down Expand Up @@ -4189,8 +4211,10 @@ Options:
[--include <prod|dev|optional|peer> [--include <prod|dev|optional|peer> ...]]
[--strict-peer-deps] [--prefer-dedupe] [--no-package-lock] [--package-lock-only]
[--foreground-scripts] [--ignore-scripts] [--allow-git <all|none|root>]
[--no-audit] [--before <date>|--min-release-age <days>] [--no-bin-links]
[--no-fund] [--dry-run] [--cpu <cpu>] [--os <os>] [--libc <libc>]
[--no-audit] [--before <date>|--min-release-age <days>]
[--min-release-age-exclude <package-name> [--min-release-age-exclude <package-name> ...]]
[--no-bin-links] [--no-fund] [--dry-run] [--cpu <cpu>] [--os <os>]
[--libc <libc>]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
[--workspaces] [--include-workspace-root] [--install-links]

Expand Down Expand Up @@ -4248,6 +4272,9 @@ Options:
--min-release-age
If set, npm will build the npm tree such that only versions that were

--min-release-age-exclude
Exclude package names from \`min-release-age\` and \`before\` publish-time

--bin-links
Tells npm to create symlinks (or \`.cmd\` shims on Windows) for package

Expand Down Expand Up @@ -4307,6 +4334,7 @@ alias: it
#### \`audit\`
#### \`before\`
#### \`min-release-age\`
#### \`min-release-age-exclude\`
#### \`bin-links\`
#### \`fund\`
#### \`dry-run\`
Expand Down Expand Up @@ -4739,6 +4767,7 @@ Options:
[-a|--all] [--json] [-l|--long] [-p|--parseable] [-g|--global]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
[--before <date>|--min-release-age <days>]
[--min-release-age-exclude <package-name> [--min-release-age-exclude <package-name> ...]]

-a|--all
When running \`npm outdated\` and \`npm ls\`, setting \`--all\` will show
Expand All @@ -4761,6 +4790,9 @@ Options:
--before
If passed to \`npm install\`, will rebuild the npm tree such that only

--min-release-age-exclude
Exclude package names from \`min-release-age\` and \`before\` publish-time


Run "npm help outdated" for more info

Expand All @@ -4776,6 +4808,7 @@ npm outdated [<package-spec> ...]
#### \`workspace\`
#### \`before\`
#### \`min-release-age\`
#### \`min-release-age-exclude\`
`

exports[`test/lib/docs.js TAP usage owner > must match snapshot 1`] = `
Expand Down Expand Up @@ -5132,6 +5165,8 @@ Options:
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
[--workspaces] [--include-workspace-root] [--package-lock-only]
[--expect-results|--expect-result-count <count>]
[--before <date>|--min-release-age <days>]
[--min-release-age-exclude <package-name> [--min-release-age-exclude <package-name> ...]]

-g|--global
Operates in "global" mode, so that packages are installed into the
Expand All @@ -5151,6 +5186,15 @@ Options:
--expect-results
Tells npm whether or not to expect results from the command.

--before
If passed to \`npm install\`, will rebuild the npm tree such that only

--min-release-age
If set, npm will build the npm tree such that only versions that were

--min-release-age-exclude
Exclude package names from \`min-release-age\` and \`before\` publish-time


Run "npm help query" for more info

Expand All @@ -5165,6 +5209,9 @@ npm query <selector>
#### \`package-lock-only\`
#### \`expect-results\`
#### \`expect-result-count\`
#### \`before\`
#### \`min-release-age\`
#### \`min-release-age-exclude\`
`

exports[`test/lib/docs.js TAP usage rebuild > must match snapshot 1`] = `
Expand Down
4 changes: 3 additions & 1 deletion workspaces/arborist/lib/query-selector-all.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const localeCompare = require('@isaacs/string-locale-compare')('en')
const { log } = require('proc-log')
const { minimatch } = require('minimatch')
const npa = require('npm-package-arg')
const { isExcludedFromTimeFilter } = require('npm-pick-manifest')
const pacote = require('pacote')
const semver = require('semver')
const npmFetch = require('npm-registry-fetch')
Expand Down Expand Up @@ -873,6 +874,7 @@ const combinators = {
// get a list of available versions of a package filtered to respect --before
// NOTE: this runs over each node and should not throw
const getPackageVersions = async (name, opts) => {
const minReleaseAgeExclude = opts.minReleaseAgeExclude
let packument
try {
packument = await pacote.packument(name, {
Expand All @@ -890,7 +892,7 @@ const getPackageVersions = async (name, opts) => {

// if the packument has a time property, and the user passed a before flag, then
// we filter this list down to only those versions that existed before the specified date
if (packument.time && opts.before) {
if (packument.time && opts.before && !isExcludedFromTimeFilter(name, minReleaseAgeExclude)) {
candidates = candidates.filter((version) => {
// this version isn't found in the times at all, drop it
if (!packument.time[version]) {
Expand Down
38 changes: 38 additions & 0 deletions workspaces/arborist/test/query-selector-all.js
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,9 @@ t.test('query-selector-all', async t => {
: options.before
title += ` before ${friendlyTime}`
}
if (options.minReleaseAgeExclude) {
title += ` exclude ${JSON.stringify(options.minReleaseAgeExclude)}`
}
t.test(title, async t => {
const res = await querySelectorAll(tree, selector, options)
t.same(
Expand Down Expand Up @@ -865,6 +868,20 @@ t.test('query-selector-all', async t => {
[':outdated(out-of-range)', [
'dash-separated-pkg@1.0.0', // 2.0.0 is available, out-of-range and published yesterday
], { before: yesterday }],
[':outdated(out-of-range)', [
'dash-separated-pkg@1.0.0', // 2.0.0 is available, out-of-range and published yesterday
'bar@1.4.0', // excluded from --before filter
], {
before: yesterday,
minReleaseAgeExclude: ['bar'],
}],
[':outdated(out-of-range)', [
'dash-separated-pkg@1.0.0', // 2.0.0 is available, out-of-range and published yesterday
'bar@1.4.0', // excluded from --before filter by glob
], {
before: yesterday,
minReleaseAgeExclude: ['ba*'],
Comment thread
caseyjhol marked this conversation as resolved.
}],
[':outdated(nonsense)', [], { before: yesterday }], // again, no results here ever

// vuln pseudo
Expand Down Expand Up @@ -1123,3 +1140,24 @@ t.test('linked strategy: :root > * excludes transitive deps and store nodes', as
'nopt@7.2.1',
], ':root * should return all descendants including transitive deps')
})

t.test('isExcludedFromTimeFilter with scoped package patterns', async t => {
const { isExcludedFromTimeFilter } = require('npm-pick-manifest')

t.equal(isExcludedFromTimeFilter('@myorg/foo', ['@myorg/*']), true,
'scoped glob @myorg/* matches @myorg/foo')
t.equal(isExcludedFromTimeFilter('@myorg/bar', ['@myorg/*']), true,
'scoped glob @myorg/* matches @myorg/bar')
t.equal(isExcludedFromTimeFilter('@other/foo', ['@myorg/*']), false,
'scoped glob @myorg/* does not match @other/foo')
t.equal(isExcludedFromTimeFilter('unscoped', ['@myorg/*']), false,
'scoped glob @myorg/* does not match unscoped package')
t.equal(isExcludedFromTimeFilter('@myorg/foo', ['@myorg/foo']), true,
'exact scoped name matches')
t.equal(isExcludedFromTimeFilter('@myorg/foo', ['@myorg/bar']), false,
'exact scoped name does not match different package')
t.equal(isExcludedFromTimeFilter('@myorg/foo', []), false,
'empty exclude list matches nothing')
t.equal(isExcludedFromTimeFilter('@myorg/foo', undefined), false,
'undefined exclude list matches nothing')
})
19 changes: 19 additions & 0 deletions workspaces/config/lib/definitions/definitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -1382,6 +1382,25 @@ const definitions = {
}
},
}),
'min-release-age-exclude': new Definition('min-release-age-exclude', {
default: [],
hint: '<package-name>',
type: [String, Array],
envExport: false,
description: `
Exclude package names from \`min-release-age\` and \`before\` publish-time
filtering.
Values can be exact package names (\`left-pad\`) or glob patterns
(\`@myorg/*\`).
`,
flatten: (key, obj, flatOptions) => {
const patterns = Array.isArray(obj[key])
? obj[key]
: obj[key] ? [obj[key]] : []
flatOptions.minReleaseAgeExclude = patterns.filter(Boolean)
},
}),
'node-gyp': new Definition('node-gyp', {
default: (() => {
try {
Expand Down
22 changes: 22 additions & 0 deletions workspaces/config/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1868,3 +1868,25 @@ t.test('before and min-release-age', async t => {
// 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')
})

t.test('min-release-age-exclude flattens to list', async t => {
const path = t.testdir()
const config = new Config({
npmPath: `${path}/npm`,
env: {},
argv: [
process.execPath,
__filename,
'--min-release-age-exclude',
'@myorg/*',
'--min-release-age-exclude',
'left-pad',
],
cwd: path,
definitions,
shorthands,
flatten,
})
await config.load()
t.same(config.flat.minReleaseAgeExclude, ['@myorg/*', 'left-pad'])
})