Skip to content

Commit 23ce3af

Browse files
isaacswraithgar
authored andcommitted
feat(ls): report *why* something is invalid
This is a papercut that has been driving me crazy when debugging ERESOLVE issues. It's not particularly useful to just say something is "invalid", without showing which module's dependency is not being met. With this commit, it prints all the packages that depend on that dependency and do not have their required version met. This does print multiple times for deduped deps that are invalid, but if we skip the printing of the `invalid` label for deduped deps, we end up losing information that is only detected later in the tree walk. PR-URL: #3460 Credit: @isaacs Close: #3460 Reviewed-by: @nlf
1 parent 6254b6f commit 23ce3af

File tree

3 files changed

+109
-20
lines changed

3 files changed

+109
-20
lines changed

lib/ls.js

+14-5
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,9 @@ const getHumanOutputItem = (node, { args, color, global, long }) => {
308308
const targetLocation = node.root
309309
? relative(node.root.realpath, node.realpath)
310310
: node.targetLocation
311+
const invalid = node[_invalid]
312+
? `invalid: ${node[_invalid]}`
313+
: ''
311314
const label =
312315
(
313316
node[_missing]
@@ -321,8 +324,8 @@ const getHumanOutputItem = (node, { args, color, global, long }) => {
321324
: ''
322325
) +
323326
(
324-
node[_invalid]
325-
? ' ' + (color ? chalk.red.bgBlack('invalid') : 'invalid')
327+
invalid
328+
? ' ' + (color ? chalk.red.bgBlack(invalid) : invalid)
326329
: ''
327330
) +
328331
(
@@ -373,7 +376,7 @@ const getJsonOutputItem = (node, { global, long }) => {
373376
item.extraneous = true
374377

375378
if (node[_invalid])
376-
item.invalid = true
379+
item.invalid = node[_invalid]
377380

378381
if (node[_missing] && !isOptional(node)) {
379382
item.required = node[_required]
@@ -432,9 +435,15 @@ const mapEdgesToNodes = ({ seenPaths }) => (edge) => {
432435
if (node.path)
433436
seenPaths.add(node.path)
434437

435-
node[_required] = edge.spec
438+
node[_required] = edge.spec || '*'
436439
node[_type] = edge.type
437-
node[_invalid] = edge.invalid
440+
441+
if (edge.invalid) {
442+
const spec = JSON.stringify(node[_required])
443+
const from = edge.from.location || 'the root project'
444+
node[_invalid] = (node[_invalid] ? node[_invalid] + ', ' : '') +
445+
(`${spec} from ${from}`)
446+
}
438447

439448
return node
440449
}

tap-snapshots/test/lib/ls.js.test.cjs

+21-10
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,16 @@ exports[`test/lib/ls.js TAP ignore missing optional deps human output > ls resul
3232
3333
+-- unmet optional dependency optional-missing@1
3434
35-
+-- [email protected] invalid
35+
+-- [email protected] invalid: "1" from the root project
3636
+-- unmet dependency peer-missing@1
3737
3838
+-- unmet optional dependency peer-optional-missing@1
3939
40-
+-- [email protected] invalid
41-
+-- [email protected] invalid
40+
+-- [email protected] invalid: "1" from the root project
41+
+-- [email protected] invalid: "1" from the root project
4242
+-- unmet dependency prod-missing@1
4343
44-
\`-- [email protected] invalid
44+
\`-- [email protected] invalid: "1" from the root project
4545
4646
`
4747

@@ -356,7 +356,7 @@ [email protected] {CWD}/tap-testdir-ls-ls-broken-resolved-fie
356356
exports[`test/lib/ls.js TAP ls colored output > should output tree containing color info 1`] = `
357357
[[email protected] {CWD}/tap-testdir-ls-ls-colored-output
358358
+-- [email protected] extraneous
359-
[0m+-- [email protected] [31m[40minvalid[49m[39m[0m
359+
[0m+-- [email protected] [31m[40minvalid: "^2.0.0" from the root project[49m[39m[0m
360360
| \`-- [email protected]
361361
\`-- UNMET DEPENDENCY ipsum@^1.0.0
362362

@@ -454,8 +454,8 @@ exports[`test/lib/ls.js TAP ls global > should print tree and not mark top-level
454454
exports[`test/lib/ls.js TAP ls invalid deduped dep > should output tree signaling mismatching peer dep in problems 1`] = `
455455
[[email protected] {CWD}/tap-testdir-ls-ls-invalid-deduped-dep
456456
+-- [email protected]
457-
[0m| \`-- [email protected] [90mdeduped[39m [31m[40minvalid[49m[39m[0m
458-
[0m\`-- [email protected] [31m[40minvalid[49m[39m[0m
457+
[0m| \`-- [email protected] [90mdeduped[39m [31m[40minvalid: "^2.0.0" from the root project, "^2.0.0" from node_modules/a[49m[39m[0m
458+
[0m\`-- [email protected] [31m[40minvalid: "^2.0.0" from the root project, "^2.0.0" from node_modules/a[49m[39m[0m
459459

460460
`
461461

@@ -466,7 +466,7 @@ [email protected] {CWD}/tap-testdir-ls-ls-invalid-peer-dep
466466
467467
468468
469-
+-- [email protected] invalid
469+
+-- [email protected] invalid: "^2.0.0" from the root project
470470
471471
472472
@@ -567,7 +567,7 @@ exports[`test/lib/ls.js TAP ls missing package.json > should output tree missing
567567
exports[`test/lib/ls.js TAP ls missing/invalid/extraneous > should output tree containing missing, invalid, extraneous labels 1`] = `
568568
[email protected] {CWD}/tap-testdir-ls-ls-missing-invalid-extraneous
569569
+-- [email protected] extraneous
570-
+-- [email protected] invalid
570+
+-- [email protected] invalid: "^2.0.0" from the root project
571571
572572
\`-- UNMET DEPENDENCY ipsum@^1.0.0
573573
@@ -602,7 +602,7 @@ exports[`test/lib/ls.js TAP ls unmet optional dep > should output tree with empt
602602
| \`-- [email protected]
603603
| \`-- [email protected]
604604
+-- UNMET OPTIONAL DEPENDENCY missing-optional-dep@^1.0.0
605-
[0m+-- [email protected] [31m[40minvalid[49m[39m[0m
605+
[0m+-- [email protected] [31m[40minvalid: "^2.0.0" from the root project[49m[39m[0m
606606
+-- [email protected]
607607
\`-- [email protected]
608608
 \`-- [email protected]
@@ -691,3 +691,14 @@ [email protected] {CWD}/tap-testdir-ls-ls-with-no-args-dedupe-entries-and-not
691691
\`-- @npmcli/[email protected]
692692
693693
`
694+
695+
exports[`test/lib/ls.js TAP show multiple invalid reasons > ls result 1`] = `
696+
[email protected] {cwd}/tap-testdir-ls-show-multiple-invalid-reasons
697+
+-- [email protected] invalid: "^2.0.0" from the root project
698+
| \`-- [email protected] deduped invalid: "^1.2.3" from the root project, "^2.0.0" from node_modules/cat
699+
+-- [email protected] extraneous
700+
| \`-- [email protected] deduped invalid: "^1.2.3" from the root project, "^2.0.0" from node_modules/cat, "2.x" from node_modules/chai
701+
\`-- [email protected] invalid: "^1.2.3" from the root project, "^2.0.0" from node_modules/cat, "2.x" from node_modules/chai
702+
\`-- [email protected] deduped invalid: "^2.0.0" from the root project
703+
704+
`

test/lib/ls.js

+74-5
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,23 @@ const ls = new LS(npm)
123123
const redactCwd = res =>
124124
res && res.replace(/\\+/g, '/').replace(new RegExp(__dirname.replace(/\\+/g, '/'), 'gi'), '{CWD}')
125125

126-
const jsonParse = res => JSON.parse(redactCwd(res))
126+
const redactCwdObj = obj => {
127+
if (Array.isArray(obj))
128+
return obj.map(o => redactCwdObj(o))
129+
else if (typeof obj === 'string')
130+
return redactCwd(obj)
131+
else if (!obj)
132+
return obj
133+
else if (typeof obj === 'object') {
134+
return Object.keys(obj).reduce((o, k) => {
135+
o[k] = redactCwdObj(obj[k])
136+
return o
137+
}, {})
138+
} else
139+
return obj
140+
}
141+
142+
const jsonParse = res => redactCwdObj(JSON.parse(res))
127143

128144
const cleanUpResult = () => result = ''
129145

@@ -3060,7 +3076,7 @@ t.test('ls --json', (t) => {
30603076
dependencies: {
30613077
foo: {
30623078
version: '1.0.0',
3063-
invalid: true,
3079+
invalid: '"^2.0.0" from the root project',
30643080
problems: [
30653081
'invalid: [email protected] {CWD}/tap-testdir-ls-ls---json-missing-invalid-extraneous/node_modules/foo',
30663082
],
@@ -3756,7 +3772,7 @@ t.test('ls --json', (t) => {
37563772
dependencies: {
37573773
'peer-dep': {
37583774
version: '1.0.0',
3759-
invalid: true,
3775+
invalid: '"^2.0.0" from the root project',
37603776
problems: [
37613777
'invalid: [email protected] {CWD}/tap-testdir-ls-ls---json-unmet-peer-dep/node_modules/peer-dep',
37623778
],
@@ -3817,7 +3833,7 @@ t.test('ls --json', (t) => {
38173833
dependencies: {
38183834
'optional-dep': {
38193835
version: '1.0.0',
3820-
invalid: true,
3836+
invalid: '"^2.0.0" from the root project',
38213837
problems: [
38223838
'invalid: [email protected] {CWD}/tap-testdir-ls-ls---json-unmet-optional-dep/node_modules/optional-dep',
38233839
],
@@ -4175,6 +4191,59 @@ t.test('ls --json', (t) => {
41754191
t.end()
41764192
})
41774193

4194+
t.test('show multiple invalid reasons', (t) => {
4195+
config.json = false
4196+
config.all = true
4197+
config.depth = Infinity
4198+
npm.prefix = t.testdir({
4199+
'package.json': JSON.stringify({
4200+
name: 'test-npm-ls',
4201+
version: '1.0.0',
4202+
dependencies: {
4203+
cat: '^2.0.0',
4204+
dog: '^1.2.3',
4205+
},
4206+
}),
4207+
node_modules: {
4208+
cat: {
4209+
'package.json': JSON.stringify({
4210+
name: 'cat',
4211+
version: '1.0.0',
4212+
dependencies: {
4213+
dog: '^2.0.0',
4214+
},
4215+
}),
4216+
},
4217+
dog: {
4218+
'package.json': JSON.stringify({
4219+
name: 'dog',
4220+
version: '1.0.0',
4221+
dependencies: {
4222+
cat: '',
4223+
},
4224+
}),
4225+
},
4226+
chai: {
4227+
'package.json': JSON.stringify({
4228+
name: 'chai',
4229+
version: '1.0.0',
4230+
dependencies: {
4231+
dog: '2.x',
4232+
},
4233+
}),
4234+
},
4235+
},
4236+
})
4237+
4238+
const cleanupPaths = str =>
4239+
redactCwd(str).toLowerCase().replace(/\\/g, '/')
4240+
ls.exec([], (err) => {
4241+
t.match(err, { code: 'ELSPROBLEMS' }, 'should list dep problems')
4242+
t.matchSnapshot(cleanupPaths(result), 'ls result')
4243+
t.end()
4244+
})
4245+
})
4246+
41784247
t.test('ls --package-lock-only', (t) => {
41794248
config['package-lock-only'] = true
41804249
t.test('ls --package-lock-only --json', (t) => {
@@ -4751,7 +4820,7 @@ t.test('ls --package-lock-only', (t) => {
47514820
dependencies: {
47524821
foo: {
47534822
version: '1.0.0',
4754-
invalid: true,
4823+
invalid: '"^2.0.0" from the root project',
47554824
problems: [
47564825
'invalid: [email protected] {CWD}/tap-testdir-ls-ls---package-lock-only-ls---package-lock-only---json-missing-invalid-extraneous/node_modules/foo',
47574826
],

0 commit comments

Comments
 (0)