147 lines
3.8 KiB
JavaScript
147 lines
3.8 KiB
JavaScript
|
const { spawn } = require('child_process')
|
||
|
const path = require('path')
|
||
|
const openUrl = require('../utils/open-url.js')
|
||
|
const { promisify } = require('util')
|
||
|
const glob = promisify(require('glob'))
|
||
|
const localeCompare = require('@isaacs/string-locale-compare')('en')
|
||
|
|
||
|
const BaseCommand = require('../base-command.js')
|
||
|
|
||
|
// Strips out the number from foo.7 or foo.7. or foo.7.tgz
|
||
|
// We don't currently compress our man pages but if we ever did this would
|
||
|
// seemlessly continue supporting it
|
||
|
const manNumberRegex = /\.(\d+)(\.[^/\\]*)?$/
|
||
|
|
||
|
class Help extends BaseCommand {
|
||
|
static description = 'Get help on npm'
|
||
|
static name = 'help'
|
||
|
static usage = ['<term> [<terms..>]']
|
||
|
static params = ['viewer']
|
||
|
|
||
|
async completion (opts) {
|
||
|
if (opts.conf.argv.remain.length > 2) {
|
||
|
return []
|
||
|
}
|
||
|
const g = path.resolve(__dirname, '../../man/man[0-9]/*.[0-9]')
|
||
|
const files = await glob(g)
|
||
|
|
||
|
return Object.keys(files.reduce(function (acc, file) {
|
||
|
file = path.basename(file).replace(/\.[0-9]+$/, '')
|
||
|
file = file.replace(/^npm-/, '')
|
||
|
acc[file] = true
|
||
|
return acc
|
||
|
}, { help: true }))
|
||
|
}
|
||
|
|
||
|
async exec (args) {
|
||
|
// By default we search all of our man subdirectories, but if the user has
|
||
|
// asked for a specific one we limit the search to just there
|
||
|
let manSearch = 'man*'
|
||
|
if (/^\d+$/.test(args[0])) {
|
||
|
manSearch = `man${args.shift()}`
|
||
|
}
|
||
|
|
||
|
if (!args.length) {
|
||
|
return this.npm.output(await this.npm.usage)
|
||
|
}
|
||
|
|
||
|
// npm help foo bar baz: search topics
|
||
|
if (args.length > 1) {
|
||
|
return this.helpSearch(args)
|
||
|
}
|
||
|
|
||
|
let section = this.npm.deref(args[0]) || args[0]
|
||
|
|
||
|
// support `npm help package.json`
|
||
|
section = section.replace('.json', '-json')
|
||
|
|
||
|
const manroot = path.resolve(__dirname, '..', '..', 'man')
|
||
|
// find either section.n or npm-section.n
|
||
|
const f = `${manroot}/${manSearch}/?(npm-)${section}.[0-9]*`
|
||
|
let mans = await glob(f)
|
||
|
mans = mans.sort((a, b) => {
|
||
|
// Because of the glob we know the manNumberRegex will pass
|
||
|
const aManNumber = a.match(manNumberRegex)[1]
|
||
|
const bManNumber = b.match(manNumberRegex)[1]
|
||
|
|
||
|
// man number sort first so that 1 aka commands are preferred
|
||
|
if (aManNumber !== bManNumber) {
|
||
|
return aManNumber - bManNumber
|
||
|
}
|
||
|
|
||
|
return localeCompare(a, b)
|
||
|
})
|
||
|
const man = mans[0]
|
||
|
|
||
|
if (man) {
|
||
|
await this.viewMan(man)
|
||
|
} else {
|
||
|
return this.helpSearch(args)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
helpSearch (args) {
|
||
|
return this.npm.exec('help-search', args)
|
||
|
}
|
||
|
|
||
|
async viewMan (man) {
|
||
|
const env = {}
|
||
|
Object.keys(process.env).forEach(function (i) {
|
||
|
env[i] = process.env[i]
|
||
|
})
|
||
|
const viewer = this.npm.config.get('viewer')
|
||
|
|
||
|
const opts = {
|
||
|
env,
|
||
|
stdio: 'inherit',
|
||
|
}
|
||
|
|
||
|
let bin = 'man'
|
||
|
const args = []
|
||
|
switch (viewer) {
|
||
|
case 'woman':
|
||
|
bin = 'emacsclient'
|
||
|
args.push('-e', `(woman-find-file '${man}')`)
|
||
|
break
|
||
|
|
||
|
case 'browser':
|
||
|
await openUrl(this.npm, this.htmlMan(man), 'help available at the following URL', true)
|
||
|
return
|
||
|
|
||
|
default:
|
||
|
args.push(man)
|
||
|
break
|
||
|
}
|
||
|
|
||
|
const proc = spawn(bin, args, opts)
|
||
|
return new Promise((resolve, reject) => {
|
||
|
proc.on('exit', (code) => {
|
||
|
if (code) {
|
||
|
return reject(new Error(`help process exited with code: ${code}`))
|
||
|
}
|
||
|
|
||
|
return resolve()
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Returns the path to the html version of the man page
|
||
|
htmlMan (man) {
|
||
|
let sect = man.match(manNumberRegex)[1]
|
||
|
const f = path.basename(man).replace(manNumberRegex, '')
|
||
|
switch (sect) {
|
||
|
case '1':
|
||
|
sect = 'commands'
|
||
|
break
|
||
|
case '5':
|
||
|
sect = 'configuring-npm'
|
||
|
break
|
||
|
case '7':
|
||
|
sect = 'using-npm'
|
||
|
break
|
||
|
}
|
||
|
return 'file:///' + path.resolve(__dirname, '..', '..', 'docs', 'output', sect, f + '.html')
|
||
|
}
|
||
|
}
|
||
|
module.exports = Help
|