Skip to content

Commit 31af1aa

Browse files
committed
fix: refactor help to use @npmcli/promise-spawn
This also refactors the test to use `mockNpm`
1 parent 669ef94 commit 31af1aa

File tree

2 files changed

+183
-336
lines changed

2 files changed

+183
-336
lines changed

lib/commands/help.js

Lines changed: 33 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const { spawn } = require('child_process')
1+
const spawn = require('@npmcli/promise-spawn')
22
const path = require('path')
33
const openUrl = require('../utils/open-url.js')
44
const { promisify } = require('util')
@@ -14,19 +14,26 @@ const BaseCommand = require('../base-command.js')
1414
const manNumberRegex = /\.(\d+)(\.[^/\\]*)?$/
1515
// Searches for the "npm-" prefix in page names, to prefer those.
1616
const manNpmPrefixRegex = /\/npm-/
17+
// hardcoded names for mansections
18+
// XXX: these are used in the docs workspace and should be exported
19+
// from npm so section names can changed more easily
20+
const manSectionNames = {
21+
1: 'commands',
22+
5: 'configuring-npm',
23+
7: 'using-npm',
24+
}
1725

1826
class Help extends BaseCommand {
1927
static description = 'Get help on npm'
2028
static name = 'help'
2129
static usage = ['<term> [<terms..>]']
2230
static params = ['viewer']
23-
static ignoreImplicitWorkspace = true
2431

2532
async completion (opts) {
2633
if (opts.conf.argv.remain.length > 2) {
2734
return []
2835
}
29-
const g = path.resolve(__dirname, '../../man/man[0-9]/*.[0-9]')
36+
const g = path.resolve(this.npm.npmRoot, 'man/man[0-9]/*.[0-9]')
3037
const files = await glob(globify(g))
3138

3239
return Object.keys(files.reduce(function (acc, file) {
@@ -40,10 +47,7 @@ class Help extends BaseCommand {
4047
async exec (args) {
4148
// By default we search all of our man subdirectories, but if the user has
4249
// asked for a specific one we limit the search to just there
43-
let manSearch = 'man*'
44-
if (/^\d+$/.test(args[0])) {
45-
manSearch = `man${args.shift()}`
46-
}
50+
const manSearch = /^\d+$/.test(args[0]) ? `man${args.shift()}` : 'man*'
4751

4852
if (!args.length) {
4953
return this.npm.output(await this.npm.usage)
@@ -54,20 +58,18 @@ class Help extends BaseCommand {
5458
return this.helpSearch(args)
5559
}
5660

57-
let section = this.npm.deref(args[0]) || args[0]
58-
59-
// support `npm help package.json`
60-
section = section.replace('.json', '-json')
61+
// `npm help package.json`
62+
const arg = (this.npm.deref(args[0]) || args[0]).replace('.json', '-json')
6163

62-
const manroot = path.resolve(__dirname, '..', '..', 'man')
6364
// find either section.n or npm-section.n
64-
const f = `${manroot}/${manSearch}/?(npm-)${section}.[0-9]*`
65-
let mans = await glob(globify(f))
66-
mans = mans.sort((a, b) => {
65+
const f = globify(path.resolve(this.npm.npmRoot, `man/${manSearch}/?(npm-)${arg}.[0-9]*`))
66+
67+
const [man] = await glob(f).then(r => r.sort((a, b) => {
6768
// Prefer the page with an npm prefix, if there's only one.
6869
const aHasPrefix = manNpmPrefixRegex.test(a)
6970
const bHasPrefix = manNpmPrefixRegex.test(b)
7071
if (aHasPrefix !== bHasPrefix) {
72+
/* istanbul ignore next */
7173
return aHasPrefix ? -1 : 1
7274
}
7375

@@ -76,6 +78,7 @@ class Help extends BaseCommand {
7678
const aManNumberMatch = a.match(manNumberRegex)
7779
const bManNumberMatch = b.match(manNumberRegex)
7880
if (aManNumberMatch) {
81+
/* istanbul ignore next */
7982
if (!bManNumberMatch) {
8083
return -1
8184
}
@@ -88,77 +91,41 @@ class Help extends BaseCommand {
8891
}
8992

9093
return localeCompare(a, b)
91-
})
92-
const man = mans[0]
94+
}))
9395

94-
if (man) {
95-
await this.viewMan(man)
96-
} else {
97-
return this.helpSearch(args)
98-
}
96+
return man ? this.viewMan(man) : this.helpSearch(args)
9997
}
10098

10199
helpSearch (args) {
102100
return this.npm.exec('help-search', args)
103101
}
104102

105103
async viewMan (man) {
106-
const env = {}
107-
Object.keys(process.env).forEach(function (i) {
108-
env[i] = process.env[i]
109-
})
110104
const viewer = this.npm.config.get('viewer')
111105

112-
const opts = {
113-
env,
114-
stdio: 'inherit',
106+
if (viewer === 'browser') {
107+
return openUrl(this.npm, this.htmlMan(man), 'help available at the following URL', true)
115108
}
116109

117-
let bin = 'man'
118-
const args = []
119-
switch (viewer) {
120-
case 'woman':
121-
bin = 'emacsclient'
122-
args.push('-e', `(woman-find-file '${man}')`)
123-
break
124-
125-
case 'browser':
126-
await openUrl(this.npm, this.htmlMan(man), 'help available at the following URL', true)
127-
return
128-
129-
default:
130-
args.push(man)
131-
break
110+
let args = ['man', [man]]
111+
if (viewer === 'woman') {
112+
args = ['emacsclient', ['-e', `(woman-find-file '${man}')`]]
132113
}
133114

134-
const proc = spawn(bin, args, opts)
135-
return new Promise((resolve, reject) => {
136-
proc.on('exit', (code) => {
137-
if (code) {
138-
return reject(new Error(`help process exited with code: ${code}`))
139-
}
140-
141-
return resolve()
142-
})
115+
return spawn(...args, { stdio: 'inherit' }).catch(err => {
116+
if (err.code) {
117+
throw new Error(`help process exited with code: ${err.code}`)
118+
} else {
119+
throw err
120+
}
143121
})
144122
}
145123

146124
// Returns the path to the html version of the man page
147125
htmlMan (man) {
148-
let sect = man.match(manNumberRegex)[1]
126+
const sect = manSectionNames[man.match(manNumberRegex)[1]]
149127
const f = path.basename(man).replace(manNumberRegex, '')
150-
switch (sect) {
151-
case '1':
152-
sect = 'commands'
153-
break
154-
case '5':
155-
sect = 'configuring-npm'
156-
break
157-
case '7':
158-
sect = 'using-npm'
159-
break
160-
}
161-
return 'file:///' + path.resolve(__dirname, '..', '..', 'docs', 'output', sect, f + '.html')
128+
return 'file:///' + path.resolve(this.npm.npmRoot, `docs/output/${sect}/${f}.html`)
162129
}
163130
}
164131
module.exports = Help

0 commit comments

Comments
 (0)