• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict'
2
3let npa
4const path = require('path')
5
6module.exports = parseArgs
7function parseArgs (argv, defaultNpm) {
8  argv = argv || process.argv
9  if (argv.length > 2 && argv[2][0] !== '-') {
10    // fast-path around arg parsing! Don't even need to load yargs here.
11    return fastPathArgs(argv, defaultNpm)
12  }
13
14  npa = require('npm-package-arg')
15
16  const parser = yargsParser(argv, defaultNpm)
17
18  const opts = parser.getOptions()
19  const bools = new Set(opts.boolean)
20
21  let cmdIndex
22  let hasDashDash
23  for (let i = 2; i < argv.length; i++) {
24    const opt = argv[i]
25    if (opt === '--') {
26      hasDashDash = true
27      break
28    } else if (opt === '--node-arg' || opt === '-n') {
29      argv[i] = `${opt}=${argv[i + 1]}`
30      argv.splice(i + 1, 1)
31    } else if (opt[0] === '-') {
32      if (
33        // --no-install needs to be special-cased because we're abusing
34        // yargs a bit in order to get the --help text right.
35        opt !== '--no-install' &&
36        !bools.has(opt.replace(/^--?(no-)?/i, '')) &&
37        opt.indexOf('=') === -1
38      ) {
39        i++
40      }
41    } else {
42      cmdIndex = i
43      break
44    }
45  }
46  if (cmdIndex) {
47    const parsed = parser.parse(argv.slice(0, cmdIndex))
48    const parsedCmd = npa(argv[cmdIndex])
49    parsed.command = parsed.package && parsedCmd.type !== 'directory'
50      ? argv[cmdIndex]
51      : guessCmdName(parsedCmd)
52    parsed.isLocal = parsedCmd.type === 'directory'
53    parsed.cmdOpts = argv.slice(cmdIndex + 1)
54    if (typeof parsed.package === 'string') {
55      parsed.package = [parsed.package]
56    }
57    parsed.packageRequested = !!parsed.package
58    parsed.cmdHadVersion = parsed.package || parsedCmd.type === 'directory'
59      ? false
60      : parsedCmd.name !== parsedCmd.raw
61    const pkg = parsed.package || [argv[cmdIndex]]
62    parsed.p = parsed.package = pkg.map(p => npa(p).toString())
63    return parsed
64  } else {
65    const parsed = parser.parse(argv)
66    if (typeof parsed.package === 'string') {
67      parsed.package = [parsed.package]
68    }
69    // -c *requires* -p, because the -c string should not be touched by npx
70    if (parsed.call && parsed.package) {
71      parsed.packageRequested = !!parsed.package
72      parsed.cmdHadVersion = false
73      const pkg = parsed.package
74      parsed.p = parsed.package = pkg.map(p => npa(p).toString())
75    } else if (parsed.call && !parsed.package) {
76      parsed.packageRequested = false
77      parsed.cmdHadVersion = false
78      parsed.p = parsed.package = []
79    } else if (hasDashDash) {
80      const splitCmd = parsed._.slice(2)
81      const parsedCmd = npa(splitCmd[0])
82      parsed.command = parsed.package
83        ? splitCmd[0]
84        : guessCmdName(parsedCmd)
85      parsed.cmdOpts = splitCmd.slice(1)
86      parsed.packageRequested = !!parsed.package
87      parsed.cmdHadVersion = parsed.package
88        ? false
89        : parsedCmd.name !== parsedCmd.raw
90      const pkg = parsed.package || [splitCmd[0]]
91      parsed.p = parsed.package = pkg.map(p => npa(p).toString())
92    }
93    return parsed
94  }
95}
96
97function fastPathArgs (argv, defaultNpm) {
98  let parsedCmd
99  let pkg
100  if (argv[2].match(/^[a-z0-9_-]+$/i)) {
101    parsedCmd = { registry: true, name: argv[2], raw: argv[2] }
102    pkg = [`${argv[2]}@latest`]
103  } else {
104    npa = require('npm-package-arg')
105    parsedCmd = npa(argv[2])
106    if (parsedCmd.type === 'directory') {
107      pkg = []
108    } else {
109      pkg = [parsedCmd.toString()]
110    }
111  }
112  return {
113    command: guessCmdName(parsedCmd),
114    cmdOpts: argv.slice(3),
115    packageRequested: false,
116    isLocal: parsedCmd.type === 'directory',
117    cmdHadVersion: (
118      parsedCmd.name !== parsedCmd.raw &&
119      parsedCmd.type !== 'directory'
120    ),
121    package: pkg,
122    p: pkg,
123    shell: false,
124    noYargs: true,
125    npm: defaultNpm || 'npm'
126  }
127}
128
129parseArgs.showHelp = () => require('yargs').showHelp()
130
131module.exports._guessCmdName = guessCmdName
132function guessCmdName (spec) {
133  if (typeof spec === 'string') {
134    if (!npa) { npa = require('npm-package-arg') }
135    spec = npa(spec)
136  }
137  if (spec.scope) {
138    return spec.name.slice(spec.scope.length + 1)
139  } else if (spec.registry) {
140    return spec.name
141  } else if (spec.hosted && spec.hosted.project) {
142    return spec.hosted.project
143  } else if (spec.type === 'git') {
144    const match = spec.fetchSpec.match(/([a-z0-9-]+)(?:\.git)?$/i)
145    return match[1]
146  } else if (spec.type === 'directory') {
147    return spec.raw
148  } else if (spec.type === 'file' || spec.type === 'remote') {
149    let ext = path.extname(spec.fetchSpec)
150    if (ext === '.gz') {
151      ext = path.extname(path.basename(spec.fetchSpec, ext)) + ext
152    }
153    return path.basename(spec.fetchSpec, ext).replace(/-\d+\.\d+\.\d+(?:-[a-z0-9.\-+]+)?$/i, '')
154  }
155
156  console.error(Y()`Unable to guess a binary name from ${spec.raw}. Please use --package.`)
157  return null
158}
159
160function yargsParser (argv, defaultNpm) {
161  const usage = `
162  npx [${Y()`options`}] <${Y()`command`}>[@${Y()`version`}] [${Y()`command-arg`}]...
163
164  npx [${Y()`options`}] [-p|--package <${Y()`package`}>]... <${Y()`command`}> [${Y()`command-arg`}]...
165
166  npx [${Y()`options`}] -c '<${Y()`command-string`}>'
167
168  npx --shell-auto-fallback [${Y()`shell`}]
169  `
170
171  return require('yargs')
172    .usage(Y()`Execute binaries from npm packages.\n${usage}`)
173    .option('package', {
174      alias: 'p',
175      type: 'string',
176      describe: Y()`Package to be installed.`
177    })
178    .option('cache', {
179      type: 'string',
180      describe: Y()`Location of the npm cache.`
181    })
182    .option('always-spawn', {
183      describe: Y()`Always spawn a child process to execute the command.`,
184      type: 'boolean'
185    })
186    .option('no-install', {
187      type: 'boolean',
188      describe: Y()`Skip installation if a package is missing.`
189    })
190    .option('userconfig', {
191      type: 'string',
192      describe: Y()`Path to user npmrc.`
193    })
194    .option('call', {
195      alias: 'c',
196      type: 'string',
197      describe: Y()`Execute string as if inside \`npm run-script\`.`
198    })
199    .option('shell', {
200      alias: 's',
201      type: 'string',
202      describe: Y()`Shell to execute the command with, if any.`,
203      default: false
204    })
205    .option('shell-auto-fallback', {
206      choices: ['', 'bash', 'fish', 'zsh'],
207      describe: Y()`Generate shell code to use npx as the "command not found" fallback.`,
208      requireArg: false,
209      type: 'string'
210    })
211    .option('ignore-existing', {
212      describe: Y()`Ignores existing binaries in $PATH, or in the local project. This forces npx to do a temporary install and use the latest version.`,
213      type: 'boolean'
214    })
215    .option('quiet', {
216      alias: 'q',
217      describe: Y()`Suppress output from npx itself. Subcommands will not be affected.`,
218      type: 'boolean'
219    })
220    .option('npm', {
221      describe: Y()`npm binary to use for internal operations.`,
222      type: 'string',
223      default: defaultNpm || 'npm'
224    })
225    .option('node-arg', {
226      alias: 'n',
227      type: 'string',
228      describe: Y()`Extra node argument when calling a node binary.`
229    })
230    .version()
231    .alias('version', 'v')
232    .help()
233    .alias('help', 'h')
234    .epilogue(Y()`For the full documentation, see the manual page for npx(1).`)
235}
236
237var _y
238function Y () {
239  if (!_y) { _y = require('./y.js') }
240  return _y
241}
242