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