Skip to content

Commit a9b8bf2

Browse files
committed
Support multiple set/get/deletes in npm config
While digging into #2300, I realized it would be a lot easier if we could do this: npm config set [email protected] _auth=xxxx and avoid the whole issue of what gets set first. Also, why not let `npm config get foo bar baz` return just the keys specified? Also updates the docs, including the statement that `npm config set foo` with no value sets it to `true`, when as far as I can tell, that has never been the case. PR-URL: #2362 Credit: @isaacs Close: #2362 Reviewed-by: @nlf
1 parent a92d310 commit a9b8bf2

File tree

6 files changed

+163
-52
lines changed

6 files changed

+163
-52
lines changed

docs/content/commands/npm-config.md

+25-14
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ description: Manage the npm configuration files
77
### Synopsis
88

99
```bash
10-
npm config set <key> <value> [-g|--global]
11-
npm config get <key>
12-
npm config delete <key>
13-
npm config list [-l] [--json]
10+
npm config set <key>=<value> [<key>=<value> ...]
11+
npm config get [<key> [<key> ...]]
12+
npm config delete <key> [<key> ...]
13+
npm config list [--json]
1414
npm config edit
15-
npm get <key>
16-
npm set <key> <value> [-g|--global]
15+
npm set <key>=<value> [<key>=<value> ...]
16+
npm get [<key> [<key> ...]]
1717

18-
aliases: c
18+
alias: c
1919
```
2020
2121
### Description
@@ -39,20 +39,31 @@ Config supports the following sub-commands:
3939
#### set
4040
4141
```bash
42-
npm config set key value
42+
npm config set key=value [key=value...]
43+
npm set key=value [key=value...]
4344
```
4445
45-
Sets the config key to the value.
46+
Sets each of the config keys to the value provided.
4647
47-
If value is omitted, then it sets it to "true".
48+
If value is omitted, then it sets it to an empty string.
49+
50+
Note: for backwards compatibility, `npm config set key value` is supported
51+
as an alias for `npm config set key=value`.
4852
4953
#### get
5054
5155
```bash
52-
npm config get key
56+
npm config get [key ...]
57+
npm get [key ...]
5358
```
5459
55-
Echo the config value to stdout.
60+
Echo the config value(s) to stdout.
61+
62+
If multiple keys are provided, then the values will be prefixed with the
63+
key names.
64+
65+
If no keys are provided, then this command behaves the same as `npm config
66+
list`.
5667
5768
#### list
5869
@@ -66,10 +77,10 @@ to show the settings in json format.
6677
#### delete
6778
6879
```bash
69-
npm config delete key
80+
npm config delete key [key ...]
7081
```
7182
72-
Deletes the key from all configuration files.
83+
Deletes the specified keys from all configuration files.
7384
7485
#### edit
7586

lib/config.js

+46-34
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ const ini = require('ini')
1515

1616
const usage = usageUtil(
1717
'config',
18-
'npm config set <key> <value>' +
19-
'\nnpm config get [<key>]' +
20-
'\nnpm config delete <key>' +
18+
'npm config set <key>=<value> [<key>=<value> ...]' +
19+
'\nnpm config get [<key> [<key> ...]]' +
20+
'\nnpm config delete <key> [<key> ...]' +
2121
'\nnpm config list [--json]' +
2222
'\nnpm config edit' +
23-
'\nnpm set <key> <value>' +
24-
'\nnpm get [<key>]'
23+
'\nnpm set <key>=<value> [<key>=<value> ...]' +
24+
'\nnpm get [<key> [<key> ...]]'
2525
)
2626

2727
const cmd = (args, cb) => config(args).then(() => cb()).catch(cb)
@@ -63,20 +63,20 @@ const completion = (opts, cb) => {
6363
const UsageError = () =>
6464
Object.assign(new Error(usage), { code: 'EUSAGE' })
6565

66-
const config = async ([action, key, val]) => {
66+
const config = async ([action, ...args]) => {
6767
npm.log.disableProgress()
6868
try {
6969
switch (action) {
7070
case 'set':
71-
await set(key, val)
71+
await set(args)
7272
break
7373
case 'get':
74-
await get(key)
74+
await get(args)
7575
break
7676
case 'delete':
7777
case 'rm':
7878
case 'del':
79-
await del(key)
79+
await del(args)
8080
break
8181
case 'list':
8282
case 'ls':
@@ -93,46 +93,58 @@ const config = async ([action, key, val]) => {
9393
}
9494
}
9595

96-
const set = async (key, val) => {
97-
if (key === undefined)
98-
throw UsageError()
99-
100-
if (val === undefined) {
101-
if (key.indexOf('=') !== -1) {
102-
const k = key.split('=')
103-
key = k.shift()
104-
val = k.join('=')
105-
} else
106-
val = ''
96+
// take an array of `[key, value, k2=v2, k3, v3, ...]` and turn into
97+
// { key: value, k2: v2, k3: v3 }
98+
const keyValues = args => {
99+
const kv = {}
100+
for (let i = 0; i < args.length; i++) {
101+
const arg = args[i].split('=')
102+
const key = arg.shift()
103+
const val = arg.length ? arg.join('=')
104+
: i < args.length - 1 ? args[++i]
105+
: ''
106+
kv[key.trim()] = val.trim()
107107
}
108+
return kv
109+
}
110+
111+
const set = async (args) => {
112+
if (!args.length)
113+
throw UsageError()
108114

109-
key = key.trim()
110-
val = val.trim()
111-
npm.log.info('config', 'set %j %j', key, val)
112115
const where = npm.flatOptions.global ? 'global' : 'user'
113-
npm.config.set(key, val, where)
114-
if (!npm.config.validate(where))
115-
npm.log.warn('config', 'omitting invalid config values')
116+
for (const [key, val] of Object.entries(keyValues(args))) {
117+
npm.log.info('config', 'set %j %j', key, val)
118+
npm.config.set(key, val || '', where)
119+
if (!npm.config.validate(where))
120+
npm.log.warn('config', 'omitting invalid config values')
121+
}
116122

117123
await npm.config.save(where)
118124
}
119125

120-
const get = async key => {
121-
if (!key)
126+
const get = async keys => {
127+
if (!keys.length)
122128
return list()
123129

124-
if (!publicVar(key))
125-
throw `The ${key} option is protected, and cannot be retrieved in this way`
130+
const out = []
131+
for (const key of keys) {
132+
if (!publicVar(key))
133+
throw `The ${key} option is protected, and cannot be retrieved in this way`
126134

127-
output(npm.config.get(key))
135+
const pref = keys.length > 1 ? `${key}=` : ''
136+
out.push(pref + npm.config.get(key))
137+
}
138+
output(out.join('\n'))
128139
}
129140

130-
const del = async key => {
131-
if (!key)
141+
const del = async keys => {
142+
if (!keys.length)
132143
throw UsageError()
133144

134145
const where = npm.flatOptions.global ? 'global' : 'user'
135-
npm.config.delete(key, where)
146+
for (const key of keys)
147+
npm.config.delete(key, where)
136148
await npm.config.save(where)
137149
}
138150

lib/get.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const usageUtil = require('./utils/usage.js')
33

44
const usage = usageUtil(
55
'get',
6-
'npm get <key> <value> (See `npm config`)'
6+
'npm get [<key> ...] (See `npm config`)'
77
)
88

99
const completion = npm.commands.config.completion

lib/set.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
module.exports = set
33

4-
set.usage = 'npm set <key> <value> (See `npm config`)'
4+
set.usage = 'npm set <key>=<value> [<key>=<value> ...] (See `npm config`)'
55

66
var npm = require('./npm.js')
77

test/lib/config.js

+89-1
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,33 @@ t.test('config delete key', t => {
212212
})
213213
})
214214

215+
t.test('config delete multiple key', t => {
216+
t.plan(6)
217+
218+
const expect = [
219+
'foo',
220+
'bar',
221+
]
222+
223+
npm.config.delete = (key, where) => {
224+
t.equal(key, expect.shift(), 'should delete expected keyword')
225+
t.equal(where, 'user', 'should delete key from user config by default')
226+
}
227+
228+
npm.config.save = where => {
229+
t.equal(where, 'user', 'should save user config post-delete')
230+
}
231+
232+
config(['delete', 'foo', 'bar'], (err) => {
233+
t.ifError(err, 'npm config delete keys')
234+
})
235+
236+
t.teardown(() => {
237+
delete npm.config.delete
238+
delete npm.config.save
239+
})
240+
})
241+
215242
t.test('config delete key --global', t => {
216243
t.plan(4)
217244

@@ -293,12 +320,43 @@ t.test('config set key=val', t => {
293320
})
294321
})
295322

323+
t.test('config set multiple keys', t => {
324+
t.plan(11)
325+
326+
const expect = [
327+
['foo', 'bar'],
328+
['bar', 'baz'],
329+
['asdf', ''],
330+
]
331+
const args = ['foo', 'bar', 'bar=baz', 'asdf']
332+
333+
npm.config.set = (key, val, where) => {
334+
const [expectKey, expectVal] = expect.shift()
335+
t.equal(key, expectKey, 'should set expected key to user config')
336+
t.equal(val, expectVal, 'should set expected value to user config')
337+
t.equal(where, 'user', 'should set key/val in user config by default')
338+
}
339+
340+
npm.config.save = where => {
341+
t.equal(where, 'user', 'should save user config')
342+
}
343+
344+
config(['set', ...args], (err) => {
345+
t.ifError(err, 'npm config set key')
346+
})
347+
348+
t.teardown(() => {
349+
delete npm.config.set
350+
delete npm.config.save
351+
})
352+
})
353+
296354
t.test('config set key to empty value', t => {
297355
t.plan(5)
298356

299357
npm.config.set = (key, val, where) => {
300358
t.equal(key, 'foo', 'should set expected key to user config')
301-
t.equal(val, '', 'should set empty value to user config')
359+
t.equal(val, '', 'should set "" to user config')
302360
t.equal(where, 'user', 'should set key/val in user config by default')
303361
}
304362

@@ -403,6 +461,36 @@ t.test('config get key', t => {
403461
})
404462
})
405463

464+
t.test('config get multiple keys', t => {
465+
t.plan(4)
466+
467+
const expect = [
468+
'foo',
469+
'bar',
470+
]
471+
472+
const npmConfigGet = npm.config.get
473+
npm.config.get = (key) => {
474+
t.equal(key, expect.shift(), 'should use expected key')
475+
return 'asdf'
476+
}
477+
478+
npm.config.save = where => {
479+
throw new Error('should not save')
480+
}
481+
482+
config(['get', 'foo', 'bar'], (err) => {
483+
t.ifError(err, 'npm config get multiple keys')
484+
t.equal(result, 'foo=asdf\nbar=asdf')
485+
})
486+
487+
t.teardown(() => {
488+
result = ''
489+
npm.config.get = npmConfigGet
490+
delete npm.config.save
491+
})
492+
})
493+
406494
t.test('config get private key', t => {
407495
config(['get', '//private-reg.npmjs.org/:_authThoken'], (err) => {
408496
t.match(

test/lib/npm.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ t.test('npm.load', t => {
342342
/Completed in [0-9]+ms/,
343343
],
344344
])
345-
t.same(consoleLogs, [['@foo']])
345+
t.same(consoleLogs, [['scope=@foo\n\u2010not-a-dash=undefined']])
346346
})
347347

348348
// need this here or node 10 will improperly end the promise ahead of time

0 commit comments

Comments
 (0)