Skip to content

Commit 4c7f6ba

Browse files
manzoorwanijkowlstronaut
authored andcommitted
fix(arborist): prune removed-workspace entries from package-lock.json
1 parent 70af7b3 commit 4c7f6ba

4 files changed

Lines changed: 63 additions & 52 deletions

File tree

workspaces/arborist/lib/shrinkwrap.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,10 @@ class Shrinkwrap {
905905
continue
906906
}
907907
const loc = relpath(this.path, node.path)
908+
// Drop lockfile entries for extraneous nodes outside node_modules. These are stale workspace entries: the workspace was removed from package.json or its directory was deleted, so it should not be tracked in package-lock.json.
909+
if (node.extraneous && !/(^|\/)node_modules\//.test(loc) && loc !== 'node_modules') {
910+
continue
911+
}
908912
this.data.packages[loc] = Shrinkwrap.metaFromNode(
909913
node,
910914
this.path,

workspaces/arborist/tap-snapshots/test/arborist/reify.js.test.cjs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3183,10 +3183,6 @@ exports[`test/arborist/reify.js TAP filtered reification in workspaces > hidden
31833183
"apps/x": {
31843184
"version": "1.2.3"
31853185
},
3186-
"foo/x": {
3187-
"version": "1.2.3",
3188-
"extraneous": true
3189-
},
31903186
"node_modules/c": {
31913187
"resolved": "packages/c",
31923188
"link": true
@@ -32370,9 +32366,6 @@ Object {
3237032366
"e",
3237132367
],
3237232368
},
32373-
"e": Object {
32374-
"extraneous": true,
32375-
},
3237632369
"node_modules/a": Object {
3237732370
"extraneous": true,
3237832371
"inBundle": true,

workspaces/arborist/tap-snapshots/test/shrinkwrap.js.test.cjs

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2734,14 +2734,6 @@ Object {
27342734
"name": "example",
27352735
"version": "1.0.0",
27362736
},
2737-
"../bar": Object {
2738-
"extraneous": true,
2739-
"version": "1.0.0",
2740-
},
2741-
"../linked-node-modules/foo": Object {
2742-
"extraneous": true,
2743-
"version": "1.0.0",
2744-
},
27452737
"node_modules/bar": Object {
27462738
"link": true,
27472739
"resolved": "../bar",
@@ -9755,16 +9747,6 @@ Object {
97559747
"": Object {
97569748
"name": "workspace3",
97579749
},
9758-
"app": Object {
9759-
"dependencies": Object {
9760-
"a": "",
9761-
"b": "",
9762-
"c": "",
9763-
"i": "",
9764-
},
9765-
"extraneous": true,
9766-
"version": "1.2.3",
9767-
},
97689750
"app/node_modules/i": Object {
97699751
"extraneous": true,
97709752
"version": "1.2.3",
@@ -9785,41 +9767,14 @@ Object {
97859767
"link": true,
97869768
"resolved": "packages/c",
97879769
},
9788-
"packages/a": Object {
9789-
"dependencies": Object {
9790-
"b": "",
9791-
"c": "",
9792-
"x": "",
9793-
},
9794-
"extraneous": true,
9795-
"version": "1.2.3",
9796-
},
97979770
"packages/a/node_modules/x": Object {
97989771
"extraneous": true,
97999772
"version": "1.2.3",
98009773
},
9801-
"packages/b": Object {
9802-
"dependencies": Object {
9803-
"a": "",
9804-
"c": "",
9805-
"y": "",
9806-
},
9807-
"extraneous": true,
9808-
"version": "1.2.3",
9809-
},
98109774
"packages/b/node_modules/y": Object {
98119775
"extraneous": true,
98129776
"version": "1.2.3",
98139777
},
9814-
"packages/c": Object {
9815-
"dependencies": Object {
9816-
"a": "",
9817-
"b": "",
9818-
"z": "",
9819-
},
9820-
"extraneous": true,
9821-
"version": "1.2.3",
9822-
},
98239778
"packages/c/node_modules/z": Object {
98249779
"extraneous": true,
98259780
"version": "1.2.3",

workspaces/arborist/test/arborist/reify.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1960,6 +1960,65 @@ t.test('filtered reification in workspaces', async t => {
19601960
'hidden lockfile - foo/x linked, c, old x, removed a')
19611961
})
19621962

1963+
// Regression for https://github.com/npm/cli/issues/5463: a workspace whose directory has been deleted should not leave behind an extraneous entry (or a lingering reference in the root's workspaces array) in package-lock.json after `npm install`.
1964+
t.test('removed workspace is pruned from package-lock.json', async t => {
1965+
const setup = () => {
1966+
const path = t.testdir({
1967+
'package.json': JSON.stringify({
1968+
name: 'remove-ws',
1969+
version: '1.0.0',
1970+
workspaces: ['packages/a', 'packages/b'],
1971+
}),
1972+
packages: {
1973+
a: {
1974+
'package.json': JSON.stringify({ name: 'a', version: '1.0.0' }),
1975+
},
1976+
b: {
1977+
'package.json': JSON.stringify({ name: 'b', version: '1.0.0' }),
1978+
},
1979+
},
1980+
})
1981+
return path
1982+
}
1983+
1984+
// The lockfile's root.workspaces array mirrors package.json verbatim and is intentionally not mutated here, so we only assert that orphan package/link entries are dropped.
1985+
const assertClean = (t, path, label) => {
1986+
const lock = JSON.parse(fs.readFileSync(`${path}/package-lock.json`, 'utf8'))
1987+
t.notOk(lock.packages['packages/b'],
1988+
`${label}: packages/b entry removed from lockfile`)
1989+
t.notOk(lock.packages['node_modules/b'],
1990+
`${label}: node_modules/b link removed from lockfile`)
1991+
t.notOk(lock.dependencies && lock.dependencies.b,
1992+
`${label}: dependencies.b removed from legacy lockfile`)
1993+
}
1994+
1995+
for (const strategy of ['hoisted', 'linked']) {
1996+
t.test(`${strategy} strategy, package.json kept stale`, async t => {
1997+
const path = setup()
1998+
createRegistry(t, false)
1999+
await reify(path, { installStrategy: strategy })
2000+
// Remove only the directory, leave package.json's workspaces array alone.
2001+
fs.rmSync(`${path}/packages/b`, { recursive: true, force: true })
2002+
await reify(path, { installStrategy: strategy })
2003+
assertClean(t, path, `${strategy}/keep-pj`)
2004+
})
2005+
2006+
t.test(`${strategy} strategy, package.json updated`, async t => {
2007+
const path = setup(strategy)
2008+
createRegistry(t, false)
2009+
await reify(path, { installStrategy: strategy })
2010+
fs.rmSync(`${path}/packages/b`, { recursive: true, force: true })
2011+
fs.writeFileSync(`${path}/package.json`, JSON.stringify({
2012+
name: 'remove-ws',
2013+
version: '1.0.0',
2014+
workspaces: ['packages/a'],
2015+
}))
2016+
await reify(path, { installStrategy: strategy })
2017+
assertClean(t, path, `${strategy}/clean-pj`)
2018+
})
2019+
}
2020+
})
2021+
19632022
t.test('project with bundled deps and a link dep on itself', async t => {
19642023
const pkg = {
19652024
name: '@isaacs/testing-bundle-self-link',

0 commit comments

Comments
 (0)