From 65665bd46b8350a8b752efd039690109f58f8603 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= Date: Tue, 11 Jul 2017 22:49:36 -0700 Subject: [PATCH] feat(node): add --node-arg support to pass flags to node for script binaries (#77) * feat(node): add ability to inspect binaries when detected to be node scripts Fixes: #68 * test: add tests for -n and --node-arg parsing * feat(node-arg): allow space-separated arg strings --- README.md | 9 +++++++++ index.js | 28 +++++++++++++++++++++++++--- locales/en.json | 3 ++- parse-args.js | 11 ++++++++++- test/parse-args.js | 45 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 91 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index cf4f947..436bdd0 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,8 @@ If a full specifier is included, or if `--package` is used, npx will always use * `-q, --quiet` - Suppressed any output from npx itself (progress bars, error messages, install reports). Subcommand output itself will not be silenced. +* `-n, --node-arg` - Extra node argument to supply to node when binary is a node script. You can supply this option multiple times to add more arguments. + * `-v, --version` - Show the current npx version. ## EXAMPLES @@ -89,6 +91,13 @@ $ npx -p lolcatjs -p cowsay -c \ || || ``` +### Run node binary with --inspect + +``` +$ npx --node-arg=--inspect cowsay +Debugger listening on ws://127.0.0.1:9229/.... +``` + ## SHELL AUTO FALLBACK You can configure `npx` to run as your default fallback command when you type something in the command line with an `@` but the command is not found. This includes installing packages that were not found in the local prefix either. diff --git a/index.js b/index.js index a8a4058..88a199e 100644 --- a/index.js +++ b/index.js @@ -233,8 +233,8 @@ function installPackages (specs, prefix, opts) { module.exports._execCommand = execCommand function execCommand (_existing, argv) { return findNodeScript(_existing, argv).then(existing => { - const Module = require('module') - if (existing && Module.runMain && !argv.shell && existing !== process.argv[1]) { + if (existing && !argv.nodeArg && !argv.shell && existing !== process.argv[1]) { + const Module = require('module') // let it take over the process. This means we can skip node startup! if (!argv.noYargs) { // blow away built-up yargs crud @@ -245,8 +245,30 @@ function execCommand (_existing, argv) { existing // node script path. `runMain()` will set this as the new main ].concat(argv.cmdOpts) // options for the cmd itself Module.runMain() // ✨MAGIC✨. Sorry-not-sorry + } else if (!existing && argv.nodeArg && argv.nodeArg.length) { + throw new Error(Y()`ERROR: --node-arg/-n can only be used on packages with node scripts.`) } else { - return child.runCommand(existing, argv).catch(err => { + let cmd = existing + let opts = argv + if (existing && argv.nodeArg && argv.nodeArg.length) { + // If we know we're running a run script and we got a --node-arg, + // we need to fudge things a bit to get them working right. + let nargs = argv.nodeArg + if (typeof nargs === 'string') { + nargs = [nargs] + } + // It's valid for a single arg to be a string of multiple + // space-separated node args. + // Example: `$ npx -n '--inspect --harmony --debug' ...` + nargs = nargs.reduce((acc, arg) => { + return acc.concat(arg.split(/\s+/)) + }, []) + cmd = process.argv[0] + opts = Object.assign({}, argv, { + cmdOpts: nargs.concat([existing], argv.cmdOpts || []) + }) + } + return child.runCommand(cmd, opts).catch(err => { if (err.isOperational && err.exitCode) { // At this point, we want to treat errors from the child as if // we were just running the command. That means no extra msg logging diff --git a/locales/en.json b/locales/en.json index 4b10e1e..358e343 100644 --- a/locales/en.json +++ b/locales/en.json @@ -24,5 +24,6 @@ "shell": "shell", "package": "package", "npx: installed %s in %ss": "npx: installed %s in %ss", - "Suppress output from npx itself. Subcommands will not be affected.": "Suppress output from npx itself. Subcommands will not be affected." + "Suppress output from npx itself. Subcommands will not be affected.": "Suppress output from npx itself. Subcommands will not be affected.", + "Extra node argument when calling a node binary.": "Extra node argument when calling a node binary." } \ No newline at end of file diff --git a/parse-args.js b/parse-args.js index c277d3b..240fdd4 100644 --- a/parse-args.js +++ b/parse-args.js @@ -25,12 +25,16 @@ function parseArgs (argv, defaultNpm) { if (opt === '--') { hasDashDash = true break + } else if (opt === '--node-arg' || opt === '-n') { + argv[i] = `${opt}=${argv[i + 1]}` + argv.splice(i + 1, 1) } else if (opt[0] === '-') { if ( // --no-install needs to be special-cased because we're abusing // yargs a bit in order to get the --help text right. opt !== '--no-install' && - !bools.has(opt.replace(/^--?(no-)?/i, '')) + !bools.has(opt.replace(/^--?(no-)?/i, '')) && + opt.indexOf('=') === -1 ) { i++ } @@ -214,6 +218,11 @@ function yargsParser (argv, defaultNpm) { type: 'string', default: defaultNpm || 'npm' }) + .option('node-arg', { + alias: 'n', + type: 'string', + describe: Y()`Extra node argument when calling a node binary.` + }) .version(() => require('./package.json').version) .alias('version', 'v') .help() diff --git a/test/parse-args.js b/test/parse-args.js index 9d8ce31..77364af 100644 --- a/test/parse-args.js +++ b/test/parse-args.js @@ -159,3 +159,48 @@ test('treats directory-type commands specially', t => { t.equal(parsed.cmdHadVersion, false) t.done() }) + +test('-n and --node-arg special parsing rules', t => { + const command = 'command' + t.like( + mockParse('-n=--foo', command), + {command, nodeArg: '--foo'} + ) + t.like( + mockParse('-n', '--foo', command), + {command, nodeArg: '--foo'} + ) + t.like( + mockParse('--node-arg=--foo', command), + {command, nodeArg: '--foo'} + ) + t.like( + mockParse('--node-arg', '--foo', command), + {command, nodeArg: '--foo'} + ) + t.like( + mockParse('-n', '--foo', '-n', '--bar', '-n', 'baz', command), + {command, nodeArg: ['--foo', '--bar', 'baz']} + ) + t.like( + mockParse('--node-arg', '--foo', '--node-arg', '--bar', '--node-arg', 'baz', command), + {command, nodeArg: ['--foo', '--bar', 'baz']} + ) + t.like( + mockParse('-n', '--foo', '--node-arg', '--bar', '-n=baz', command), + {command, nodeArg: ['--foo', '--bar', 'baz']} + ) + t.like( + mockParse('-n', '-n', command), + {command, nodeArg: '-n'} + ) + t.like( + mockParse('--node-arg', '--node-arg', command), + {command, nodeArg: '--node-arg'} + ) + t.like( + mockParse(command, '--node-arg', 'blah'), + {command, nodeArg: undefined, cmdOpts: ['--node-arg', 'blah']} + ) + t.done() +})