Skip to content

Commit 1d0395e

Browse files
manzoorwanijkowlstronaut
authored andcommitted
fix(arborist): prune removed-workspace entries from package-lock.json
(cherry picked from commit 4c7f6ba)
1 parent 0fc09e8 commit 1d0395e

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
@@ -929,6 +929,10 @@ class Shrinkwrap {
929929
continue
930930
}
931931
const loc = relpath(this.path, node.path)
932+
// 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.
933+
if (node.extraneous && !/(^|\/)node_modules\//.test(loc) && loc !== 'node_modules') {
934+
continue
935+
}
932936
this.data.packages[loc] = Shrinkwrap.metaFromNode(
933937
node,
934938
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
@@ -3589,10 +3589,6 @@ exports[`test/arborist/reify.js TAP filtered reification in workspaces > hidden
35893589
"apps/x": {
35903590
"version": "1.2.3"
35913591
},
3592-
"foo/x": {
3593-
"version": "1.2.3",
3594-
"extraneous": true
3595-
},
35963592
"node_modules/c": {
35973593
"resolved": "packages/c",
35983594
"link": true
@@ -33028,9 +33024,6 @@ Object {
3302833024
"e",
3302933025
],
3303033026
},
33031-
"e": Object {
33032-
"extraneous": true,
33033-
},
3303433027
"node_modules/a": Object {
3303533028
"extraneous": true,
3303633029
"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
@@ -2055,6 +2055,65 @@ t.test('filtered reification in workspaces', async t => {
20552055
'hidden lockfile - foo/x linked, c, old x, removed a')
20562056
})
20572057

2058+
// 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`.
2059+
t.test('removed workspace is pruned from package-lock.json', async t => {
2060+
const setup = () => {
2061+
const path = t.testdir({
2062+
'package.json': JSON.stringify({
2063+
name: 'remove-ws',
2064+
version: '1.0.0',
2065+
workspaces: ['packages/a', 'packages/b'],
2066+
}),
2067+
packages: {
2068+
a: {
2069+
'package.json': JSON.stringify({ name: 'a', version: '1.0.0' }),
2070+
},
2071+
b: {
2072+
'package.json': JSON.stringify({ name: 'b', version: '1.0.0' }),
2073+
},
2074+
},
2075+
})
2076+
return path
2077+
}
2078+
2079+
// 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.
2080+
const assertClean = (t, path, label) => {
2081+
const lock = JSON.parse(fs.readFileSync(`${path}/package-lock.json`, 'utf8'))
2082+
t.notOk(lock.packages['packages/b'],
2083+
`${label}: packages/b entry removed from lockfile`)
2084+
t.notOk(lock.packages['node_modules/b'],
2085+
`${label}: node_modules/b link removed from lockfile`)
2086+
t.notOk(lock.dependencies && lock.dependencies.b,
2087+
`${label}: dependencies.b removed from legacy lockfile`)
2088+
}
2089+
2090+
for (const strategy of ['hoisted', 'linked']) {
2091+
t.test(`${strategy} strategy, package.json kept stale`, async t => {
2092+
const path = setup()
2093+
createRegistry(t, false)
2094+
await reify(path, { installStrategy: strategy })
2095+
// Remove only the directory, leave package.json's workspaces array alone.
2096+
fs.rmSync(`${path}/packages/b`, { recursive: true, force: true })
2097+
await reify(path, { installStrategy: strategy })
2098+
assertClean(t, path, `${strategy}/keep-pj`)
2099+
})
2100+
2101+
t.test(`${strategy} strategy, package.json updated`, async t => {
2102+
const path = setup(strategy)
2103+
createRegistry(t, false)
2104+
await reify(path, { installStrategy: strategy })
2105+
fs.rmSync(`${path}/packages/b`, { recursive: true, force: true })
2106+
fs.writeFileSync(`${path}/package.json`, JSON.stringify({
2107+
name: 'remove-ws',
2108+
version: '1.0.0',
2109+
workspaces: ['packages/a'],
2110+
}))
2111+
await reify(path, { installStrategy: strategy })
2112+
assertClean(t, path, `${strategy}/clean-pj`)
2113+
})
2114+
}
2115+
})
2116+
20582117
t.test('project with bundled deps and a link dep on itself', async t => {
20592118
const pkg = {
20602119
name: '@isaacs/testing-bundle-self-link',

0 commit comments

Comments
 (0)