Skip to content

Commit bf7fb3b

Browse files
committed
chore: refactor command
Usage is driven by actual code, commands are tagged for grouping
1 parent 95afc18 commit bf7fb3b

File tree

5 files changed

+315
-246
lines changed

5 files changed

+315
-246
lines changed

docs/lib/content/commands/npm-doctor.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@ Also, in addition to this, there are also very many issue reports due to
3131
using old versions of npm. Since npm is constantly improving, running
3232
`npm@latest` is better than an old version.
3333

34-
`npm doctor` verifies the following items in your environment, and if there
35-
are any recommended changes, it will display them.
34+
`npm doctor` verifies the following items in your environment, and if
35+
there are any recommended changes, it will display them. By default npm
36+
runs all of these checks. You can limit what checks are ran by
37+
specifying them as extra arguments.
3638

3739
#### `npm ping`
3840

lib/commands/doctor.js

Lines changed: 119 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -34,30 +34,104 @@ const maskLabel = mask => {
3434
return label.join(', ')
3535
}
3636

37+
const subcommands = [
38+
{
39+
groups: ['ping', 'registry'],
40+
title: 'npm ping',
41+
cmd: 'checkPing',
42+
}, {
43+
groups: ['versions'],
44+
title: 'npm -v',
45+
cmd: 'getLatestNpmVersion',
46+
}, {
47+
groups: ['versions'],
48+
title: 'node -v',
49+
cmd: 'getLatestNodejsVersion',
50+
}, {
51+
groups: ['registry'],
52+
title: 'npm config get registry',
53+
cmd: 'checkNpmRegistry',
54+
}, {
55+
groups: ['environment'],
56+
title: 'git executable in PATH',
57+
cmd: 'getGitPath',
58+
}, {
59+
groups: ['environment'],
60+
title: 'global bin folder in PATH',
61+
cmd: 'getBinPath',
62+
}, {
63+
groups: ['permissions', 'cache'],
64+
title: 'Perms check on cached files',
65+
cmd: 'checkCachePermission',
66+
windows: false,
67+
}, {
68+
groups: ['permissions'],
69+
title: 'Perms check on local node_modules',
70+
cmd: 'checkLocalModulesPermission',
71+
windows: false,
72+
}, {
73+
groups: ['permissions'],
74+
title: 'Perms check on global node_modules',
75+
cmd: 'checkGlobalModulesPermission',
76+
windows: false,
77+
}, {
78+
groups: ['permissions'],
79+
title: 'Perms check on local bin folder',
80+
cmd: 'checkLocalBinPermission',
81+
windows: false,
82+
}, {
83+
groups: ['permissions'],
84+
title: 'Perms check on global bin folder',
85+
cmd: 'checkGlobalBinPermission',
86+
windows: false,
87+
}, {
88+
groups: ['cache'],
89+
title: 'Verify cache contents',
90+
cmd: 'verifyCachedFiles',
91+
windows: false,
92+
},
93+
// TODO:
94+
// group === 'dependencies'?
95+
// - ensure arborist.loadActual() runs without errors and no invalid edges
96+
// - ensure package-lock.json matches loadActual()
97+
// - verify loadActual without hidden lock file matches hidden lockfile
98+
// group === '???'
99+
// - verify all local packages have bins linked
100+
// What is the fix for these?
101+
]
37102
const BaseCommand = require('../base-command.js')
38103
class Doctor extends BaseCommand {
39104
static description = 'Check your npm environment'
40105
static name = 'doctor'
41106
static params = ['registry']
42107
static ignoreImplicitWorkspace = false
108+
static usage = [`[${subcommands.flatMap(s => s.groups)
109+
.filter((value, index, self) => self.indexOf(value) === index)
110+
.join('] [')}]`]
111+
112+
static subcommands = subcommands
113+
114+
// minimum width of check column, enough for the word `Check`
115+
#checkWidth = 5
43116

44117
async exec (args) {
45118
log.info('Running checkup')
46119
let allOk = true
47120

48121
const actions = this.actions(args)
49-
this.checkWidth = actions.reduce((length, item) => Math.max(item[0].length, length), 5)
122+
this.#checkWidth = actions.reduce((length, item) =>
123+
Math.max(item.title.length, length), this.#checkWidth)
50124

51125
if (!this.npm.silent) {
52126
this.output(['Check', 'Value', 'Recommendation/Notes'].map(h => this.npm.chalk.underline(h)))
53127
}
54128
// Do the actual work
55-
for (const [msg, fn, args] of actions) {
56-
const item = [msg]
129+
for (const { title, cmd } of actions) {
130+
const item = [title]
57131
try {
58-
item.push(true, await this[fn](...args))
59-
} catch (er) {
60-
item.push(false, er)
132+
item.push(true, await this[cmd]())
133+
} catch (err) {
134+
item.push(false, err)
61135
}
62136
if (!item[1]) {
63137
allOk = false
@@ -72,11 +146,13 @@ class Doctor extends BaseCommand {
72146
}
73147
}
74148

75-
if (!this.npm.silent) {
76-
// this.npm.output(table(outTable, tableOpts))
77-
}
78149
if (!allOk) {
79-
throw new Error('Some problems found. See above for recommendations.')
150+
if (this.npm.silent) {
151+
/* eslint-disable-next-line max-len */
152+
throw new Error('Some problems found. Check logs or disable silent mode for recommendations.')
153+
} else {
154+
throw new Error('Some problems found. See above for recommendations.')
155+
}
80156
}
81157
}
82158

@@ -144,15 +220,35 @@ class Doctor extends BaseCommand {
144220
}
145221
}
146222

147-
async checkBinPath (dir) {
148-
const tracker = log.newItem('checkBinPath', 1)
149-
tracker.info('checkBinPath', 'Finding npm global bin in your PATH')
223+
async getBinPath (dir) {
224+
const tracker = log.newItem('getBinPath', 1)
225+
tracker.info('getBinPath', 'Finding npm global bin in your PATH')
150226
if (!process.env.PATH.includes(this.npm.globalBin)) {
151227
throw new Error(`Add ${this.npm.globalBin} to your $PATH`)
152228
}
153229
return this.npm.globalBin
154230
}
155231

232+
async checkCachePermission () {
233+
return this.checkFilesPermission(this.npm.cache, true, R_OK)
234+
}
235+
236+
async checkLocalModulesPermission () {
237+
return this.checkFilesPermission(this.npm.localDir, true, R_OK | W_OK, true)
238+
}
239+
240+
async checkGlobalModulesPermission () {
241+
return this.checkFilesPermission(this.npm.globalDir, false, R_OK)
242+
}
243+
244+
async checkLocalBinPermission () {
245+
return this.checkFilesPermission(this.npm.localBin, false, R_OK | W_OK | X_OK, true)
246+
}
247+
248+
async checkGlobalBinPermission () {
249+
return this.checkFilesPermission(this.npm.globalBin, false, X_OK)
250+
}
251+
156252
async checkFilesPermission (root, shouldOwn, mask, missingOk) {
157253
let ok = true
158254

@@ -293,79 +389,22 @@ class Doctor extends BaseCommand {
293389
'right-mid': '',
294390
middle: ' ' },
295391
style: { 'padding-left': 0, 'padding-right': 0 },
296-
colWidths: [this.checkWidth, 6],
392+
colWidths: [this.#checkWidth, 6],
297393
})
298394
t.push(row)
299395
this.npm.output(t.toString())
300396
}
301397

302-
actions (subcmds) {
303-
const a = []
304-
if (!subcmds.length || subcmds.includes('ping')) {
305-
a.push(['npm ping', 'checkPing', []])
306-
}
307-
if (!subcmds.length || subcmds.includes('versions')) {
308-
a.push(['npm -v', 'getLatestNpmVersion', []])
309-
a.push(['node -v', 'getLatestNodejsVersion', []])
310-
}
311-
if (!subcmds.length || subcmds.includes('registry')) {
312-
a.push(['npm config get registry', 'checkNpmRegistry', []])
313-
}
314-
if (!subcmds.length || subcmds.includes('git')) {
315-
a.push(['which git', 'getGitPath', []])
316-
}
317-
if (!subcmds.length || subcmds.includes('permissions')) {
318-
if (subcmds.includes('permissions') && process.platform === 'win32') {
319-
log.warn('Ignoring permissions checks for windows')
320-
} else if (process.platform !== 'win32') {
321-
a.push([
322-
'Perms check on cached files',
323-
'checkFilesPermission',
324-
[this.npm.cache, true, R_OK],
325-
])
326-
a.push([
327-
'Perms check on local node_modules',
328-
'checkFilesPermission',
329-
[this.npm.localDir, true, R_OK | W_OK, true],
330-
])
331-
a.push([
332-
'Perms check on global node_modules',
333-
'checkFilesPermission',
334-
[this.npm.globalDir, false, R_OK],
335-
])
336-
a.push([
337-
'Perms check on local bin folder',
338-
'checkFilesPermission',
339-
[this.npm.localBin, false, R_OK | W_OK | X_OK, true],
340-
])
341-
a.push([
342-
'Perms check on global bin folder',
343-
'checkFilesPermission',
344-
[this.npm.globalBin, false, X_OK],
345-
])
398+
actions (params) {
399+
return this.constructor.subcommands.filter(subcmd => {
400+
if (process.platform === 'win32' && subcmd.windows === false) {
401+
return false
346402
}
347-
}
348-
if (!subcmds.length || subcmds.includes('cache')) {
349-
a.push([
350-
'Verify cache contents',
351-
'verifyCachedFiles',
352-
[this.npm.flatOptions.cache],
353-
])
354-
}
355-
356-
if (!subcmds.length || subcmds.includes('environment')) {
357-
a.push(['Global bin folder in PATH', 'checkBinPath', []])
358-
}
359-
360-
// TODO:
361-
// subcmd === 'dependencies'?
362-
// - ensure arborist.loadActual() runs without errors and no invalid edges
363-
// - ensure package-lock.json matches loadActual()
364-
// - verify loadActual without hidden lock file matches hidden lockfile
365-
// subcmd === '???'
366-
// - verify all local packages have bins linked
367-
// What is the fix for these?
368-
return a
403+
if (params.length) {
404+
return params.some(param => subcmd.groups.includes(param))
405+
}
406+
return true
407+
})
369408
}
370409
}
371410

0 commit comments

Comments
 (0)