Skip to content
Draft
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
13 changes: 13 additions & 0 deletions workspaces/arborist/lib/arborist/reify.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const Shrinkwrap = require('../shrinkwrap.js')
const { defaultLockfileVersion } = Shrinkwrap
const { saveTypeMap, hasSubKey } = require('../add-rm-pkg-deps.js')
const { IsolatedNode, IsolatedLink } = require('../isolated-classes.js')
const { isReleaseAgeExcluded } = require('../release-age-exclude.js')

// Part of steps (steps need refactoring before we can do anything about these)
const _retireShallowNodes = Symbol.for('retireShallowNodes')
Expand Down Expand Up @@ -737,8 +738,20 @@ module.exports = cls => class Reifier extends cls {
})
}
})
// When node.resolved is missing (e.g. a lockfile written with
// omit-lockfile-registry-resolved) `res` above is a bare `name@version`
// spec, so pacote has to hit the registry and re-run version-picking
// (including the `min-release-age`/`before` cutoff) to find the tarball.
// That re-resolution must honor `min-release-age-exclude` the same way
// build-ideal-tree's #releaseAgeBefore does, or a package that was
// correctly exempted while building the ideal tree can still get
// rejected here with ETARGET.
const before = isReleaseAgeExcluded(node.name, this.options.minReleaseAgeExclude)
? null
: this.options.before
await pacote.extract(res, node.path, {
...this.options,
before,
resolved: node.resolved,
integrity: node.integrity,
// A node counts as "root" for allow-* enforcement if it satisfies at least one valid dependency edge declared by the project root or a workspace.
Expand Down
50 changes: 50 additions & 0 deletions workspaces/arborist/test/arborist/reify.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,56 @@ t.test('weirdly broken lockfile without resolved value', async t => {
await t.resolveMatchSnapshot(printReified(fixture(t, 'dep-missing-resolved')))
})

t.test('min-release-age-exclude applies when reify re-resolves a node with no resolved value', async t => {
// minimist@1.2.5 was published 2020-03-12T22:16:19.463Z (see
// ../fixtures/registry-mocks/content/minimist.json). A lockfile entry
// missing `resolved` (e.g. written with omit-lockfile-registry-resolved)
// forces reify's #extractOrLink to re-resolve `minimist@1.2.5` against the
// registry instead of extracting directly from a known tarball URL. That
// re-resolution must still honor min-release-age-exclude the same way
// build-ideal-tree's manifest fetches do.
const before = new Date('2020-01-01T00:00:00.000Z')

const mkPath = t => t.testdir({
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
dependencies: { minimist: '1.2.5' },
}),
'package-lock.json': JSON.stringify({
name: 'root',
version: '1.0.0',
lockfileVersion: 2,
requires: true,
packages: {
'': {
name: 'root',
version: '1.0.0',
dependencies: { minimist: '1.2.5' },
},
'node_modules/minimist': {
version: '1.2.5',
},
},
}, null, 2),
})

await t.test('without exclude, before still applies and fails', async t => {
createRegistry(t, true)
await t.rejects(
reify(mkPath(t), { before }),
{ code: 'ETARGET' },
'the already-locked version is rejected by the release-age filter'
)
})

await t.test('with exclude, the already-locked version installs', async t => {
createRegistry(t, true)
const tree = await reify(mkPath(t), { before, minReleaseAgeExclude: ['minimist'] })
t.equal(tree.children.get('minimist').version, '1.2.5', 'excluded package still installs')
})
})

t.test('testing-peer-deps package', async t => {
createRegistry(t, true)
await t.resolveMatchSnapshot(printReified(fixture(t, 'testing-peer-deps')))
Expand Down