Skip to content

Commit 5974397

Browse files
committed
fix(did-you-mean): succeed if cwd is not a package
The did-you-mean code was trying to parse a local package.json to suggest scripts and bins, which was causing an exception if you ran npm outside of a directory with a valid package.json. This fixes that. PR-URL: #3747 Credit: @wraithgar Close: #3747 Reviewed-by: @nlf
1 parent ac8e4ad commit 5974397

File tree

2 files changed

+61
-39
lines changed

2 files changed

+61
-39
lines changed

lib/utils/did-you-mean.js

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,26 @@ const readJson = require('read-package-json-fast')
33
const { cmdList } = require('./cmd-list.js')
44

55
const didYouMean = async (npm, path, scmd) => {
6-
const bestCmd = cmdList
6+
let best = cmdList
77
.filter(cmd => distance(scmd, cmd) < scmd.length * 0.4 && scmd !== cmd)
88
.map(str => ` npm ${str} # ${npm.commands[str].description}`)
99

10-
const pkg = await readJson(`${path}/package.json`)
11-
const { scripts } = pkg
1210
// We would already be suggesting this in `npm x` so omit them here
1311
const runScripts = ['stop', 'start', 'test', 'restart']
14-
const bestRun = Object.keys(scripts || {})
15-
.filter(cmd => distance(scmd, cmd) < scmd.length * 0.4 &&
16-
!runScripts.includes(cmd))
17-
.map(str => ` npm run ${str} # run the "${str}" package script`)
18-
19-
const { bin } = pkg
20-
const bestBin = Object.keys(bin || {})
21-
.filter(cmd => distance(scmd, cmd) < scmd.length * 0.4)
22-
.map(str => ` npm exec ${str} # run the "${str}" command from either this or a remote npm package`)
23-
24-
const best = [...bestCmd, ...bestRun, ...bestBin]
12+
try {
13+
const { bin, scripts } = await readJson(`${path}/package.json`)
14+
best = best.concat(
15+
Object.keys(scripts || {})
16+
.filter(cmd => distance(scmd, cmd) < scmd.length * 0.4 &&
17+
!runScripts.includes(cmd))
18+
.map(str => ` npm run ${str} # run the "${str}" package script`),
19+
Object.keys(bin || {})
20+
.filter(cmd => distance(scmd, cmd) < scmd.length * 0.4)
21+
.map(str => ` npm exec ${str} # run the "${str}" command from either this or a remote npm package`)
22+
)
23+
} catch (_) {
24+
// gracefully ignore not being in a folder w/ a package.json
25+
}
2526

2627
if (best.length === 0)
2728
return ''

test/lib/utils/did-you-mean.js

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,55 @@ const dym = require('../../../lib/utils/did-you-mean.js')
55
t.test('did-you-mean', t => {
66
npm.load(err => {
77
t.notOk(err)
8-
t.test('nistall', async t => {
9-
const result = await dym(npm, npm.localPrefix, 'nistall')
10-
t.match(result, 'npm install')
11-
})
12-
t.test('sttest', async t => {
13-
const result = await dym(npm, npm.localPrefix, 'sttest')
14-
t.match(result, 'npm test')
15-
t.match(result, 'npm run posttest')
8+
t.test('with package.json', t => {
9+
const testdir = t.testdir({
10+
'package.json': JSON.stringify({
11+
bin: {
12+
npx: 'exists',
13+
},
14+
scripts: {
15+
install: 'exists',
16+
posttest: 'exists',
17+
},
18+
}),
19+
})
20+
t.test('nistall', async t => {
21+
const result = await dym(npm, testdir, 'nistall')
22+
t.match(result, 'npm install')
23+
})
24+
t.test('sttest', async t => {
25+
const result = await dym(npm, testdir, 'sttest')
26+
t.match(result, 'npm test')
27+
t.match(result, 'npm run posttest')
28+
})
29+
t.test('npz', async t => {
30+
const result = await dym(npm, testdir, 'npxx')
31+
t.match(result, 'npm exec npx')
32+
})
33+
t.test('qwuijbo', async t => {
34+
const result = await dym(npm, testdir, 'qwuijbo')
35+
t.match(result, '')
36+
})
37+
t.end()
1638
})
17-
t.test('npz', async t => {
18-
const result = await dym(npm, npm.localPrefix, 'npxx')
19-
t.match(result, 'npm exec npx')
39+
t.test('with no package.json', t => {
40+
const testdir = t.testdir({})
41+
t.test('nistall', async t => {
42+
const result = await dym(npm, testdir, 'nistall')
43+
t.match(result, 'npm install')
44+
})
45+
t.end()
2046
})
21-
t.test('qwuijbo', async t => {
22-
const result = await dym(npm, npm.localPrefix, 'qwuijbo')
23-
t.match(result, '')
47+
t.test('missing bin and script properties', async t => {
48+
const testdir = t.testdir({
49+
'package.json': JSON.stringify({
50+
name: 'missing-bin',
51+
}),
52+
})
53+
54+
const result = await dym(npm, testdir, 'nistall')
55+
t.match(result, 'npm install')
2456
})
2557
t.end()
2658
})
2759
})
28-
29-
t.test('missing bin and script properties', async t => {
30-
const path = t.testdir({
31-
'package.json': JSON.stringify({
32-
name: 'missing-bin',
33-
}),
34-
})
35-
36-
const result = await dym(npm, path, 'nistall')
37-
t.match(result, 'npm install')
38-
})

0 commit comments

Comments
 (0)