Skip to content

Commit 3d4012a

Browse files
committedOct 30, 2019
add pacote CLI
This is surprisingly useful, especially while doing work on npm stuff!
1 parent 99a3f21 commit 3d4012a

File tree

5 files changed

+510
-1
lines changed

5 files changed

+510
-1
lines changed
 

‎README.md

+33-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# pacote
22

3-
JavaScript package downloader
3+
JavaScript Package Handler
44

55
## USAGE
66

@@ -25,6 +25,38 @@ Anything that you can do to with kind of package, you can do to any kind of
2525
package. Data that isn't relevant (like a packument for a tarball) will be
2626
simulated.
2727

28+
## CLI
29+
30+
This module exports a command line interface that can do most of what is
31+
described below. Run `pacote -h` to learn more.
32+
33+
```
34+
Pacote - The JavaScript Package Handler, v10.0.0
35+
36+
Usage:
37+
38+
pacote resolve <spec>
39+
Fesolve a specifier and output the fully resolved target
40+
41+
pacote manifest <spec>
42+
Fetch a manifest and print to stdout
43+
44+
pacote packument <spec>
45+
Fetch a full packument and print to stdout
46+
47+
pacote tarball <spec> <filename>
48+
Fetch a package tarball and save to <filename>
49+
If <filename> is missing or '-', the tarball will be streamed to stdout.
50+
51+
pacote extract <spec> <folder>
52+
Extract a package to the destination folder.
53+
54+
Configuration values all match the names of configs passed to npm, or options
55+
passed to Pacote.
56+
57+
For example '--cache=/path/to/folder' will use that folder as the cache.
58+
```
59+
2860
## API
2961

3062
The `spec` refers to any kind of package specifier that npm can install.

‎lib/bin.js

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#!/usr/bin/env node
2+
3+
const run = conf => {
4+
const pacote = require('../')
5+
switch (conf._[0]) {
6+
case 'resolve':
7+
case 'manifest':
8+
case 'packument':
9+
return pacote[conf._[0]](conf._[1], conf)
10+
11+
case 'tarball':
12+
if (!conf._[2] || conf._[2] === '-') {
13+
return pacote.tarball.stream(conf._[1], stream => {
14+
stream.pipe(conf.testStdout ||
15+
/* istanbul ignore next */ process.stdout)
16+
// make sure it resolves something falsey
17+
return stream.promise().then(() => {})
18+
}, conf)
19+
} else
20+
return pacote.tarball.file(conf._[1], conf._[2], conf)
21+
22+
case 'extract':
23+
return pacote.extract(conf._[1], conf._[2], conf)
24+
25+
default: /* istanbul ignore next */ {
26+
throw new Error(`bad command: ${conf._[0]}`)
27+
}
28+
}
29+
}
30+
31+
const version = require('../package.json').version
32+
const usage = () =>
33+
`Pacote - The JavaScript Package Handler, v${version}
34+
35+
Usage:
36+
37+
pacote resolve <spec>
38+
Fesolve a specifier and output the fully resolved target
39+
40+
pacote manifest <spec>
41+
Fetch a manifest and print to stdout
42+
43+
pacote packument <spec>
44+
Fetch a full packument and print to stdout
45+
46+
pacote tarball <spec> <filename>
47+
Fetch a package tarball and save to <filename>
48+
If <filename> is missing or '-', the tarball will be streamed to stdout.
49+
50+
pacote extract <spec> <folder>
51+
Extract a package to the destination folder.
52+
53+
Configuration values all match the names of configs passed to npm, or options
54+
passed to Pacote.
55+
56+
For example '--cache=/path/to/folder' will use that folder as the cache.
57+
`
58+
59+
const main = args => {
60+
const conf = parse(args)
61+
if (conf.help || conf.h)
62+
return console.log(usage())
63+
64+
process.on('log', console.error)
65+
66+
try {
67+
return run(conf)
68+
.then(result => result && console.log(
69+
conf.json ? JSON.stringify(result, 0, 2) : result
70+
))
71+
.catch(er => {
72+
console.error(er)
73+
process.exit(1)
74+
})
75+
} catch (er) {
76+
console.error(er.message)
77+
console.error(usage())
78+
}
79+
}
80+
81+
const parseArg = arg => {
82+
const split = arg.slice(2).split('=')
83+
const k = split.shift()
84+
const v = split.join('=')
85+
const no = /^no-/.test(k) && !v
86+
const key = (no ? k.substr(3) : k)
87+
.replace(/-([a-z])/g, (_, c) => c.toUpperCase())
88+
const value = v ? v.replace(/^~/, process.env.HOME) : !no
89+
return { key, value }
90+
}
91+
92+
const parse = args => {
93+
const conf = {
94+
_: [],
95+
json: !process.stdout.isTTY,
96+
cache: process.env.HOME + '/.npm/_cacache',
97+
}
98+
let dashdash = false
99+
args.forEach(arg => {
100+
if (dashdash)
101+
conf._.push(arg)
102+
else if (arg === '--')
103+
dashdash = true
104+
else if (arg === '-h')
105+
conf.help = true
106+
else if (/^--/.test(arg)) {
107+
const {key, value} = parseArg(arg)
108+
conf[key] = value
109+
} else {
110+
conf._.push(arg)
111+
}
112+
})
113+
return conf
114+
}
115+
116+
if (module === require.main)
117+
main(process.argv.slice(2))
118+
else
119+
module.exports = {
120+
main,
121+
run,
122+
usage,
123+
parseArg,
124+
parse,
125+
}

‎package.json

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"version": "10.0.0",
44
"description": "JavaScript package downloader",
55
"author": "Isaac Z. Schlueter <i@izs.me> (https://izs.me)",
6+
"bin": "lib/bin.js",
67
"license": "ISC",
78
"main": "lib/index.js",
89
"scripts": {

‎tap-snapshots/test-bin.js-TAP.test.js

+239
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
/* IMPORTANT
2+
* This snapshot file is auto-generated, but designed for humans.
3+
* It should be checked into source control and tracked carefully.
4+
* Re-generate by setting TAP_SNAPSHOT=1 and running tests.
5+
* Make sure to inspect the output below. Do not ignore changes!
6+
*/
7+
'use strict'
8+
exports[`test/bin.js TAP main --help > must match snapshot 1`] = `
9+
Object {
10+
"errorlog": Array [],
11+
"exitlog": Array [],
12+
"loglog": Array [
13+
Array [
14+
"Pacote - The JavaScript Package Handler, v{VERSION}\\n\\nUsage:\\n\\n pacote resolve <spec>\\n Fesolve a specifier and output the fully resolved target\\n\\n pacote manifest <spec>\\n Fetch a manifest and print to stdout\\n\\n pacote packument <spec>\\n Fetch a full packument and print to stdout\\n\\n pacote tarball <spec> <filename>\\n Fetch a package tarball and save to <filename>\\n If <filename> is missing or '-', the tarball will be streamed to stdout.\\n\\n pacote extract <spec> <folder>\\n Extract a package to the destination folder.\\n\\nConfiguration values all match the names of configs passed to npm, or options\\npassed to Pacote.\\n\\nFor example '--cache=/path/to/folder' will use that folder as the cache.\\n",
15+
],
16+
],
17+
}
18+
`
19+
20+
exports[`test/bin.js TAP main blerg glorb glork > must match snapshot 1`] = `
21+
Object {
22+
"errorlog": Array [
23+
Array [
24+
"bad command: blerg",
25+
],
26+
Array [
27+
"Pacote - The JavaScript Package Handler, v{VERSION}\\n\\nUsage:\\n\\n pacote resolve <spec>\\n Fesolve a specifier and output the fully resolved target\\n\\n pacote manifest <spec>\\n Fetch a manifest and print to stdout\\n\\n pacote packument <spec>\\n Fetch a full packument and print to stdout\\n\\n pacote tarball <spec> <filename>\\n Fetch a package tarball and save to <filename>\\n If <filename> is missing or '-', the tarball will be streamed to stdout.\\n\\n pacote extract <spec> <folder>\\n Extract a package to the destination folder.\\n\\nConfiguration values all match the names of configs passed to npm, or options\\npassed to Pacote.\\n\\nFor example '--cache=/path/to/folder' will use that folder as the cache.\\n",
28+
],
29+
],
30+
"exitlog": Array [],
31+
"loglog": Array [],
32+
}
33+
`
34+
35+
exports[`test/bin.js TAP main extract npm@latest-6 folder --json > must match snapshot 1`] = `
36+
Object {
37+
"errorlog": Array [],
38+
"exitlog": Array [],
39+
"loglog": Array [
40+
Array [
41+
"{\\n \\"method\\": \\"extract\\",\\n \\"spec\\": \\"npm@latest-6\\",\\n \\"dest\\": \\"folder\\",\\n \\"conf\\": {\\n \\"_\\": [\\n \\"extract\\",\\n \\"npm@latest-6\\",\\n \\"folder\\"\\n ],\\n \\"json\\": true,\\n \\"cache\\": \\"/Users/isaacs/.npm/_cacache\\"\\n }\\n}",
42+
],
43+
],
44+
}
45+
`
46+
47+
exports[`test/bin.js TAP main extract npm@latest-6 folder --no-json > must match snapshot 1`] = `
48+
Object {
49+
"errorlog": Array [],
50+
"exitlog": Array [],
51+
"loglog": Array [
52+
Array [
53+
Object {
54+
"conf": Object {
55+
"_": Array [
56+
"extract",
57+
"npm@latest-6",
58+
"folder",
59+
],
60+
"cache": "/Users/isaacs/.npm/_cacache",
61+
"json": false,
62+
},
63+
"dest": "folder",
64+
"method": "extract",
65+
"spec": "npm@latest-6",
66+
},
67+
],
68+
],
69+
}
70+
`
71+
72+
exports[`test/bin.js TAP main manifest bar@foo > must match snapshot 1`] = `
73+
Object {
74+
"errorlog": Array [],
75+
"exitlog": Array [],
76+
"loglog": Array [
77+
Array [
78+
"{\\n \\"method\\": \\"manifest\\",\\n \\"spec\\": \\"bar@foo\\",\\n \\"conf\\": {\\n \\"_\\": [\\n \\"manifest\\",\\n \\"bar@foo\\"\\n ],\\n \\"json\\": true,\\n \\"cache\\": \\"/Users/isaacs/.npm/_cacache\\"\\n }\\n}",
79+
],
80+
],
81+
}
82+
`
83+
84+
exports[`test/bin.js TAP main packument paku@mint > must match snapshot 1`] = `
85+
Object {
86+
"errorlog": Array [],
87+
"exitlog": Array [],
88+
"loglog": Array [
89+
Array [
90+
"{\\n \\"method\\": \\"packument\\",\\n \\"spec\\": \\"paku@mint\\",\\n \\"conf\\": {\\n \\"_\\": [\\n \\"packument\\",\\n \\"paku@mint\\"\\n ],\\n \\"json\\": true,\\n \\"cache\\": \\"/Users/isaacs/.npm/_cacache\\"\\n }\\n}",
91+
],
92+
],
93+
}
94+
`
95+
96+
exports[`test/bin.js TAP main resolve fail > must match snapshot 1`] = `
97+
Object {
98+
"errorlog": Array [
99+
Array [
100+
Error: fail,
101+
],
102+
],
103+
"exitlog": Array [
104+
1,
105+
],
106+
"loglog": Array [],
107+
}
108+
`
109+
110+
exports[`test/bin.js TAP main resolve foo@bar > must match snapshot 1`] = `
111+
Object {
112+
"errorlog": Array [],
113+
"exitlog": Array [],
114+
"loglog": Array [
115+
Array [
116+
"{\\n \\"method\\": \\"resolve\\",\\n \\"spec\\": \\"foo@bar\\",\\n \\"conf\\": {\\n \\"_\\": [\\n \\"resolve\\",\\n \\"foo@bar\\"\\n ],\\n \\"json\\": true,\\n \\"cache\\": \\"/Users/isaacs/.npm/_cacache\\"\\n }\\n}",
117+
],
118+
],
119+
}
120+
`
121+
122+
exports[`test/bin.js TAP main tarball tar@ball file.tgz > must match snapshot 1`] = `
123+
Object {
124+
"errorlog": Array [],
125+
"exitlog": Array [],
126+
"loglog": Array [
127+
Array [
128+
"{\\n \\"method\\": \\"tarball\\",\\n \\"spec\\": \\"tar@ball\\",\\n \\"file\\": \\"file.tgz\\",\\n \\"conf\\": {\\n \\"_\\": [\\n \\"tarball\\",\\n \\"tar@ball\\",\\n \\"file.tgz\\"\\n ],\\n \\"json\\": true,\\n \\"cache\\": \\"/Users/isaacs/.npm/_cacache\\"\\n }\\n}",
129+
],
130+
],
131+
}
132+
`
133+
134+
exports[`test/bin.js TAP run > expect resolving Promise 1`] = `
135+
Object {
136+
"conf": Object {
137+
"_": Array [
138+
"resolve",
139+
"spec",
140+
],
141+
"some": "configs",
142+
},
143+
"method": "resolve",
144+
"spec": "spec",
145+
}
146+
`
147+
148+
exports[`test/bin.js TAP run > expect resolving Promise 2`] = `
149+
Object {
150+
"conf": Object {
151+
"_": Array [
152+
"manifest",
153+
"spec",
154+
],
155+
"some": "configs",
156+
},
157+
"method": "manifest",
158+
"spec": "spec",
159+
}
160+
`
161+
162+
exports[`test/bin.js TAP run > expect resolving Promise 3`] = `
163+
Object {
164+
"conf": Object {
165+
"_": Array [
166+
"packument",
167+
"spec",
168+
],
169+
"some": "configs",
170+
},
171+
"method": "packument",
172+
"spec": "spec",
173+
}
174+
`
175+
176+
exports[`test/bin.js TAP run > expect resolving Promise 4`] = `
177+
Object {
178+
"conf": Object {
179+
"_": Array [
180+
"tarball",
181+
"spec",
182+
"file",
183+
],
184+
"some": "configs",
185+
},
186+
"file": "file",
187+
"method": "tarball",
188+
"spec": "spec",
189+
}
190+
`
191+
192+
exports[`test/bin.js TAP run > expect resolving Promise 5`] = `
193+
Object {
194+
"conf": Object {
195+
"_": Array [
196+
"extract",
197+
"spec",
198+
"dest",
199+
],
200+
"some": "configs",
201+
},
202+
"dest": "dest",
203+
"method": "extract",
204+
"spec": "spec",
205+
}
206+
`
207+
208+
exports[`test/bin.js TAP run > expect resolving Promise 6`] = `
209+
undefined
210+
`
211+
212+
exports[`test/bin.js TAP running bin runs main file > helpful output 1`] = `
213+
Pacote - The JavaScript Package Handler, v{VERSION}
214+
215+
Usage:
216+
217+
pacote resolve <spec>
218+
Fesolve a specifier and output the fully resolved target
219+
220+
pacote manifest <spec>
221+
Fetch a manifest and print to stdout
222+
223+
pacote packument <spec>
224+
Fetch a full packument and print to stdout
225+
226+
pacote tarball <spec> <filename>
227+
Fetch a package tarball and save to <filename>
228+
If <filename> is missing or '-', the tarball will be streamed to stdout.
229+
230+
pacote extract <spec> <folder>
231+
Extract a package to the destination folder.
232+
233+
Configuration values all match the names of configs passed to npm, or options
234+
passed to Pacote.
235+
236+
For example '--cache=/path/to/folder' will use that folder as the cache.
237+
238+
239+
`

‎test/bin.js

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
const bin = require.resolve('../lib/bin.js')
2+
const {main, run, usage, parseArg, parse} = require('../lib/bin.js')
3+
const {spawn} = require('child_process')
4+
const t = require('tap')
5+
const version = require('../package.json').version
6+
t.cleanSnapshot = str => str.split(version).join('{VERSION}')
7+
8+
const pacote = require('../')
9+
const called = []
10+
pacote.resolve = (spec, conf) =>
11+
spec === 'fail' ? Promise.reject(new Error('fail'))
12+
: Promise.resolve({method: 'resolve', spec, conf})
13+
pacote.manifest = (spec, conf) => Promise.resolve({method: 'manifest', spec, conf})
14+
pacote.packument = (spec, conf) => Promise.resolve({method: 'packument', spec, conf})
15+
pacote.tarball.file = (spec, file, conf) => Promise.resolve({method: 'tarball', spec, file, conf})
16+
const Minipass = require('minipass')
17+
pacote.tarball.stream = (spec, handler, conf) => handler(new Minipass().end('tarball data'))
18+
pacote.extract = (spec, dest, conf) => Promise.resolve({method: 'extract', spec, dest, conf})
19+
20+
t.test('running bin runs main file', t => {
21+
const proc = spawn(process.execPath, [bin, '-h'])
22+
const out = []
23+
proc.stdout.on('data', c => out.push(c))
24+
proc.on('close', (code, signal) => {
25+
t.equal(code, 0)
26+
t.equal(signal, null)
27+
t.matchSnapshot(Buffer.concat(out).toString('utf8'), 'helpful output')
28+
t.end()
29+
})
30+
})
31+
32+
t.test('parseArg', t => {
33+
t.same(parseArg('--foo-bar=baz=boo'), { key: 'fooBar', value: 'baz=boo' })
34+
t.same(parseArg('--foo'), { key: 'foo', value: true })
35+
t.same(parseArg('--path=~'), { key: 'path', value: process.env.HOME })
36+
t.same(parseArg('--no-foo'), { key: 'foo', value: false })
37+
t.end()
38+
})
39+
40+
t.test('parse', t => {
41+
t.same(parse(['a', 'b', '--foo', '--no-json']), {
42+
_: ['a', 'b'],
43+
foo: true,
44+
json: false,
45+
cache: process.env.HOME + '/.npm/_cacache',
46+
})
47+
t.same(parse(['a', 'b', '--', '--json']), {
48+
_: ['a', 'b', '--json'],
49+
json: !process.stdout.isTTY,
50+
cache: process.env.HOME + '/.npm/_cacache',
51+
})
52+
t.match(parse(['-h']), { help: true })
53+
t.end()
54+
})
55+
56+
t.test('run', t => {
57+
const conf = {some: 'configs'}
58+
t.resolveMatchSnapshot(run({...conf, _: ['resolve', 'spec']}))
59+
t.resolveMatchSnapshot(run({...conf, _: ['manifest', 'spec']}))
60+
t.resolveMatchSnapshot(run({...conf, _: ['packument', 'spec']}))
61+
t.resolveMatchSnapshot(run({...conf, _: ['tarball', 'spec', 'file']}))
62+
t.resolveMatchSnapshot(run({...conf, _: ['extract', 'spec', 'dest']}))
63+
t.throws(() => run({...conf, _: ['x']}), { message: 'bad command: x' })
64+
65+
const testStdout = new Minipass({encoding: 'utf8'})
66+
const testOutput = []
67+
return t.resolveMatchSnapshot(run({
68+
...conf,
69+
_: ['tarball'],
70+
testStdout,
71+
})).then(() => t.equal(testStdout.read(), 'tarball data'))
72+
})
73+
74+
t.test('main', t => {
75+
const {log, error} = console
76+
const {exit} = process
77+
t.teardown(() => {
78+
console.log = log
79+
console.error = error
80+
process.exit = exit
81+
})
82+
83+
const errorlog = []
84+
console.error = (...args) => errorlog.push(args)
85+
const loglog = []
86+
console.log = (...args) => loglog.push(args)
87+
88+
const exitlog = []
89+
process.exit = code => exitlog.push(code)
90+
91+
t.beforeEach(cb => {
92+
errorlog.length = 0
93+
loglog.length = 0
94+
exitlog.length = 0
95+
cb()
96+
})
97+
98+
const test = (...args) =>
99+
t.test(args.join(' '), t => Promise.resolve(main(['--json', ...args]))
100+
.then(() => t.matchSnapshot({errorlog, loglog, exitlog})))
101+
102+
test('--help')
103+
test('resolve', 'foo@bar')
104+
test('manifest', 'bar@foo')
105+
test('packument', 'paku@mint')
106+
test('tarball', 'tar@ball', 'file.tgz')
107+
test('extract', 'npm@latest-6', 'folder', '--no-json')
108+
test('extract', 'npm@latest-6', 'folder', '--json')
109+
test('blerg', 'glorb', 'glork')
110+
test('resolve', 'fail')
111+
t.end()
112+
})

0 commit comments

Comments
 (0)
Please sign in to comment.