Skip to content
This repository was archived by the owner on Jul 6, 2019. It is now read-only.

Commit f2fa6b3

Browse files
committed
feat(package): multiple --package options are now accepted
Fixes: #13
1 parent 9a9504b commit f2fa6b3

File tree

4 files changed

+49
-40
lines changed

4 files changed

+49
-40
lines changed

README.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ An optional `@version` may be appended to specify the package version required,
2222

2323
If a version specifier is included, or if `--package` is used, npx will ignore the version of the package in the current path, if it exists. This can also be forced with the `--ignore-existing` flag.
2424

25-
* `-p, --package <package>` - define the package to be installed. This defaults to the value of `<command>`. This is only needed for packages with multiple binaries if you want to call one of the other executables, or where the binary name does not match the package name. If this option is provided `<command>` will be executed as-is, without interpreting `@version` if it's there.
25+
* `-p, --package <package>` - define the package to be installed. This defaults to the value of `<command>`. This is only needed for packages with multiple binaries if you want to call one of the other executables, or where the binary name does not match the package name. If this option is provided `<command>` will be executed as-is, without interpreting `@version` if it's there. Multiple `--package` options may be provided, and all the packages specified will be installed.
2626

2727
* `--no-install` - If passed to `npx`, it will only try to run `<command>` if it already exists in the current path or in `$prefix/node_modules/.bin`. It won't try to install missing commands.
2828

@@ -67,13 +67,11 @@ $ npx git+ssh://my.hosted.git:cowsay.git#semver:^1
6767
...etc...
6868
```
6969

70-
### Execute a full shell command using one npx call
70+
### Execute a full shell command using one npx call w/ multiple packages
7171

7272
```
73-
$ npx -p cowsay -c 'echo "foo" | cowsay'
73+
$ npx -p lolcatjs -p cowsay -c 'echo "foo" | cowsay | lolcatjs'
7474
...
75-
+ cowsay@1.1.9
76-
added 1 package in 0.421s
7775
_____
7876
< foo >
7977
-----

index.js

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const getPrefix = require('./get-prefix.js')
99
const parseArgs = require('./parse-args.js')
1010
const path = require('path')
1111
const pkg = require('./package.json')
12+
let rimraf
1213
const updateNotifier = require('update-notifier')
1314
const which = BB.promisify(require('which'))
1415

@@ -57,7 +58,7 @@ function localBinPath (cwd) {
5758
}
5859

5960
module.exports._getCmdPath = getCmdPath
60-
function getCmdPath (command, spec, npmOpts) {
61+
function getCmdPath (command, specs, npmOpts) {
6162
return getExistingPath(command, npmOpts).then(cmdPath => {
6263
if (cmdPath) {
6364
return cmdPath
@@ -66,11 +67,15 @@ function getCmdPath (command, spec, npmOpts) {
6667
npmOpts.cache ? BB.resolve(npmOpts.cache) : getNpmCache(npmOpts)
6768
).then(cache => {
6869
const prefix = path.join(cache, '_npx')
69-
return installPackage(spec, prefix, npmOpts).then(() => {
70-
process.env.PATH = `${
71-
path.join(prefix, 'bin')
72-
}${PATH_SEP}${process.env.PATH}`
73-
return which(command)
70+
if (!rimraf) { rimraf = BB.promisify(require('rimraf')) }
71+
// TODO: this is a bit heavy-handed but it's the safest one right now
72+
return rimraf(prefix).then(() => {
73+
return installPackages(specs, prefix, npmOpts).then(() => {
74+
process.env.PATH = `${
75+
path.join(prefix, 'bin')
76+
}${PATH_SEP}${process.env.PATH}`
77+
return which(command)
78+
})
7479
})
7580
})
7681
}
@@ -102,25 +107,25 @@ function getNpmCache (opts) {
102107
}
103108

104109
module.exports._buildArgs = buildArgs
105-
function buildArgs (spec, prefix, opts) {
106-
const args = ['install', spec]
110+
function buildArgs (specs, prefix, opts) {
111+
const args = ['install'].concat(specs)
107112
args.push('--global', '--prefix', prefix)
108113
if (opts.cache) args.push('--cache', opts.cache)
109114
if (opts.userconfig) args.push('--userconfig', opts.userconfig)
110-
args.push('--loglevel', 'error')
115+
args.push('--loglevel', 'error', '--json')
111116

112117
return args
113118
}
114119

115-
module.exports._installPackage = installPackage
116-
function installPackage (spec, prefix, npmOpts) {
117-
const args = buildArgs(spec, prefix, npmOpts)
120+
module.exports._installPackages = installPackages
121+
function installPackages (specs, prefix, npmOpts) {
122+
const args = buildArgs(specs, prefix, npmOpts)
118123
return which('npm').then(npmPath => {
119124
return child.spawn(npmPath, args, {
120-
stdio: [0, 2, 2] // pipe npm's output to stderr
125+
stdio: [0, 'ignore', 2] // pipe npm's output to stderr
121126
}).catch(err => {
122127
if (err.exitCode) {
123-
err.message = `Install for ${spec} failed with code ${err.exitCode}`
128+
err.message = `Install for ${specs} failed with code ${err.exitCode}`
124129
}
125130
throw err
126131
})

parse-args.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,21 @@ function parseArgs (argv) {
8181
? argv[cmdIndex]
8282
: guessCmdName(parsedCmd)
8383
parsed.cmdOpts = argv.slice(cmdIndex + 1)
84+
if (typeof parsed.package === 'string') {
85+
parsed.package = [parsed.package]
86+
}
8487
parsed.packageRequested = !!parsed.package
8588
parsed.cmdHadVersion = parsed.package
8689
? false
8790
: parsedCmd.name !== parsedCmd.raw
88-
const pkg = parsed.package || argv[cmdIndex]
89-
parsed.p = parsed.package = npa(pkg).toString()
91+
const pkg = parsed.package || [argv[cmdIndex]]
92+
parsed.p = parsed.package = pkg.map(p => npa(p).toString())
9093
return parsed
9194
} else {
9295
const parsed = parser.parse(argv)
96+
if (typeof parsed.package === 'string') {
97+
parsed.package = [parsed.package]
98+
}
9399
if (parsed.call) {
94100
const splitCmd = parsed.call.trim().split(/\s+/)
95101
const parsedCmd = npa(splitCmd[0])
@@ -101,8 +107,8 @@ function parseArgs (argv) {
101107
parsed.cmdHadVersion = parsed.package
102108
? false
103109
: parsedCmd.name !== parsedCmd.raw
104-
const pkg = parsed.package || splitCmd[0]
105-
parsed.p = parsed.package = npa(pkg).toString()
110+
const pkg = parsed.package || [splitCmd[0]]
111+
parsed.p = parsed.package = pkg.map(p => npa(p).toString())
106112
} else if (hasDashDash) {
107113
const splitCmd = parsed._.slice(2)
108114
const parsedCmd = npa(splitCmd[0])
@@ -114,8 +120,8 @@ function parseArgs (argv) {
114120
parsed.cmdHadVersion = parsed.package
115121
? false
116122
: parsedCmd.name !== parsedCmd.raw
117-
const pkg = parsed.package || splitCmd[0]
118-
parsed.p = parsed.package = npa(pkg).toString()
123+
const pkg = parsed.package || [splitCmd[0]]
124+
parsed.p = parsed.package = pkg.map(p => npa(p).toString())
119125
}
120126
return parsed
121127
}

test/parse-args.js

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const parseArgs = require('../parse-args.js')
77
test('parses basic command', t => {
88
const parsed = parseArgs(['/node', '/npx', 'foo'])
99
t.equal(parsed.command, 'foo')
10-
t.equal(parsed.package, 'foo@latest')
10+
t.deepEqual(parsed.package, ['foo@latest'])
1111
t.equal(parsed.packageRequested, false)
1212
t.equal(parsed.cmdHadVersion, false)
1313
t.deepEqual(parsed.cmdOpts, [])
@@ -17,7 +17,7 @@ test('parses basic command', t => {
1717
test('parses command with version', t => {
1818
const parsed = parseArgs(['/node', '/npx', 'foo@1.2.3'])
1919
t.equal(parsed.command, 'foo')
20-
t.equal(parsed.package, 'foo@1.2.3')
20+
t.deepEqual(parsed.package, ['foo@1.2.3'])
2121
t.equal(parsed.packageRequested, false)
2222
t.equal(parsed.cmdHadVersion, true)
2323
t.done()
@@ -26,7 +26,7 @@ test('parses command with version', t => {
2626
test('parses command opts', t => {
2727
const parsed = parseArgs(['/node', '/npx', 'foo', 'a', 'b'])
2828
t.equal(parsed.command, 'foo')
29-
t.equal(parsed.package, 'foo@latest')
29+
t.deepEqual(parsed.package, ['foo@latest'])
3030
t.equal(parsed.packageRequested, false)
3131
t.equal(parsed.cmdHadVersion, false)
3232
t.deepEqual(parsed.cmdOpts, ['a', 'b'])
@@ -36,7 +36,7 @@ test('parses command opts', t => {
3636
test('parses scoped package command opts', t => {
3737
const parsed = parseArgs(['/node', '/npx', '@user/foo', 'a', 'b'])
3838
t.equal(parsed.command, 'foo')
39-
t.equal(parsed.package, '@user/foo@latest')
39+
t.deepEqual(parsed.package, ['@user/foo@latest'])
4040
t.equal(parsed.packageRequested, false)
4141
t.equal(parsed.cmdHadVersion, false)
4242
t.deepEqual(parsed.cmdOpts, ['a', 'b'])
@@ -46,7 +46,7 @@ test('parses scoped package command opts', t => {
4646
test('ignores options after command', t => {
4747
const parsed = parseArgs(['/node', '/npx', 'foo', '-p', 'bar', 'a', 'b'])
4848
t.equal(parsed.command, 'foo')
49-
t.equal(parsed.package, 'foo@latest')
49+
t.deepEqual(parsed.package, ['foo@latest'])
5050
t.equal(parsed.packageRequested, false)
5151
t.equal(parsed.cmdHadVersion, false)
5252
t.deepEqual(parsed.cmdOpts, ['-p', 'bar', 'a', 'b'])
@@ -56,7 +56,7 @@ test('ignores options after command', t => {
5656
test('assumes unknown args before cmd have values and ignores them', t => {
5757
const parsed = parseArgs(['/node', '/npx', '-p', 'bar', '--blahh', 'arg', '--ignore-existing', 'foo', 'a', 'b'])
5858
t.equal(parsed.command, 'foo')
59-
t.equal(parsed.package, 'bar@latest')
59+
t.deepEqual(parsed.package, ['bar@latest'])
6060
t.equal(parsed.packageRequested, true)
6161
t.equal(parsed.cmdHadVersion, false)
6262
t.deepEqual(parsed.cmdOpts, ['a', 'b'])
@@ -66,17 +66,17 @@ test('assumes unknown args before cmd have values and ignores them', t => {
6666
test('parses package option', t => {
6767
const parsed = parseArgs(['/node', '/npx', '-p', 'bar', 'foo', 'a', 'b'])
6868
t.equal(parsed.command, 'foo')
69-
t.equal(parsed.package, 'bar@latest')
69+
t.deepEqual(parsed.package, ['bar@latest'])
7070
t.equal(parsed.packageRequested, true)
7171
t.equal(parsed.cmdHadVersion, false)
7272
t.deepEqual(parsed.cmdOpts, ['a', 'b'])
7373
t.done()
7474
})
7575

76-
test('parses package option', t => {
77-
const parsed = parseArgs(['/node', '/npx', '-p', 'bar', 'foo', 'a', 'b'])
76+
test('parses multiple package options', t => {
77+
const parsed = parseArgs(['/node', '/npx', '-p', 'baz@1.2.3', '-p', 'bar', 'foo', 'a', 'b'])
7878
t.equal(parsed.command, 'foo')
79-
t.equal(parsed.package, 'bar@latest')
79+
t.deepEqual(parsed.package, ['baz@1.2.3', 'bar@latest'])
8080
t.equal(parsed.packageRequested, true)
8181
t.equal(parsed.cmdHadVersion, false)
8282
t.deepEqual(parsed.cmdOpts, ['a', 'b'])
@@ -86,7 +86,7 @@ test('parses package option', t => {
8686
test('parses -c', t => {
8787
const parsed = parseArgs(['/node', '/npx', '-c', 'foo a b'])
8888
t.equal(parsed.command, 'foo')
89-
t.equal(parsed.package, 'foo@latest')
89+
t.deepEqual(parsed.package, ['foo@latest'])
9090
t.equal(parsed.packageRequested, false)
9191
t.equal(parsed.cmdHadVersion, false)
9292
t.deepEqual(parsed.cmdOpts, ['a', 'b'])
@@ -96,7 +96,7 @@ test('parses -c', t => {
9696
test('uses -p even with -c', t => {
9797
const parsed = parseArgs(['/node', '/npx', '-c', 'foo a b', '-p', 'bar'])
9898
t.equal(parsed.command, 'foo')
99-
t.equal(parsed.package, 'bar@latest')
99+
t.deepEqual(parsed.package, ['bar@latest'])
100100
t.equal(parsed.packageRequested, true)
101101
t.equal(parsed.cmdHadVersion, false)
102102
t.deepEqual(parsed.cmdOpts, ['a', 'b'])
@@ -106,7 +106,7 @@ test('uses -p even with -c', t => {
106106
test('-p prevents command parsing', t => {
107107
const parsed = parseArgs(['/node', '/npx', '-p', 'pkg', 'foo@1.2.3', 'a', 'b'])
108108
t.equal(parsed.command, 'foo@1.2.3')
109-
t.equal(parsed.package, 'pkg@latest')
109+
t.deepEqual(parsed.package, ['pkg@latest'])
110110
t.equal(parsed.packageRequested, true)
111111
t.equal(parsed.cmdHadVersion, false)
112112
t.deepEqual(parsed.cmdOpts, ['a', 'b'])
@@ -116,7 +116,7 @@ test('-p prevents command parsing', t => {
116116
test('-- stops option parsing but still does command', t => {
117117
const parsed = parseArgs(['/node', '/npx', '--', '-foo', 'a', 'b'])
118118
t.equal(parsed.command, '-foo')
119-
t.equal(parsed.package, '-foo@latest')
119+
t.deepEqual(parsed.package, ['-foo@latest'])
120120
t.equal(parsed.packageRequested, false)
121121
t.equal(parsed.cmdHadVersion, false)
122122
t.deepEqual(parsed.cmdOpts, ['a', 'b'])
@@ -126,7 +126,7 @@ test('-- stops option parsing but still does command', t => {
126126
test('-- still respects -p', t => {
127127
const parsed = parseArgs(['/node', '/npx', '-p', 'bar', '--', '-foo', 'a', 'b'])
128128
t.equal(parsed.command, '-foo')
129-
t.equal(parsed.package, 'bar@latest')
129+
t.deepEqual(parsed.package, ['bar@latest'])
130130
t.equal(parsed.packageRequested, true)
131131
t.equal(parsed.cmdHadVersion, false)
132132
t.deepEqual(parsed.cmdOpts, ['a', 'b'])

0 commit comments

Comments
 (0)