123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- 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 {
- /* istanbul ignore next - see test/lib/load-all-commands.js */
- static get description () {
- return 'Get help on npm'
- }
- /* istanbul ignore next - see test/lib/load-all-commands.js */
- static get name () {
- return 'help'
- }
- /* istanbul ignore next - see test/lib/load-all-commands.js */
- static get usage () {
- return ['<term> [<terms..>]']
- }
- /* istanbul ignore next - see test/lib/load-all-commands.js */
- static get params () {
- return ['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 }))
- }
- exec (args, cb) {
- this.help(args).then(() => cb()).catch(cb)
- }
- async help (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(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 new Promise((resolve, reject) => {
- this.npm.commands['help-search'](args, (err) => {
- // This would only error if args was empty, which it never is
- /* istanbul ignore next */
- if (err)
- return reject(err)
- resolve()
- })
- })
- }
- 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')
- 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
|