Skip to content

Commit

Permalink
Support multiple set/get/deletes in npm config
Browse files Browse the repository at this point in the history
While digging into #2300, I realized it would be a lot easier if we
could do this:

    npm config set email=me@example.com _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
  • Loading branch information
isaacs committed Dec 18, 2020
1 parent a92d310 commit a9b8bf2
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 52 deletions.
39 changes: 25 additions & 14 deletions docs/content/commands/npm-config.md
Expand Up @@ -7,15 +7,15 @@ description: Manage the npm configuration files
### Synopsis

```bash
npm config set <key> <value> [-g|--global]
npm config get <key>
npm config delete <key>
npm config list [-l] [--json]
npm config set <key>=<value> [<key>=<value> ...]
npm config get [<key> [<key> ...]]
npm config delete <key> [<key> ...]
npm config list [--json]
npm config edit
npm get <key>
npm set <key> <value> [-g|--global]
npm set <key>=<value> [<key>=<value> ...]
npm get [<key> [<key> ...]]

aliases: c
alias: c
```
### Description
Expand All @@ -39,20 +39,31 @@ Config supports the following sub-commands:
#### set
```bash
npm config set key value
npm config set key=value [key=value...]
npm set key=value [key=value...]
```
Sets the config key to the value.
Sets each of the config keys to the value provided.
If value is omitted, then it sets it to "true".
If value is omitted, then it sets it to an empty string.
Note: for backwards compatibility, `npm config set key value` is supported
as an alias for `npm config set key=value`.
#### get
```bash
npm config get key
npm config get [key ...]
npm get [key ...]
```
Echo the config value to stdout.
Echo the config value(s) to stdout.
If multiple keys are provided, then the values will be prefixed with the
key names.
If no keys are provided, then this command behaves the same as `npm config
list`.
#### list
Expand All @@ -66,10 +77,10 @@ to show the settings in json format.
#### delete
```bash
npm config delete key
npm config delete key [key ...]
```
Deletes the key from all configuration files.
Deletes the specified keys from all configuration files.
#### edit
Expand Down
80 changes: 46 additions & 34 deletions lib/config.js
Expand Up @@ -15,13 +15,13 @@ const ini = require('ini')

const usage = usageUtil(
'config',
'npm config set <key> <value>' +
'\nnpm config get [<key>]' +
'\nnpm config delete <key>' +
'npm config set <key>=<value> [<key>=<value> ...]' +
'\nnpm config get [<key> [<key> ...]]' +
'\nnpm config delete <key> [<key> ...]' +
'\nnpm config list [--json]' +
'\nnpm config edit' +
'\nnpm set <key> <value>' +
'\nnpm get [<key>]'
'\nnpm set <key>=<value> [<key>=<value> ...]' +
'\nnpm get [<key> [<key> ...]]'
)

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

const config = async ([action, key, val]) => {
const config = async ([action, ...args]) => {
npm.log.disableProgress()
try {
switch (action) {
case 'set':
await set(key, val)
await set(args)
break
case 'get':
await get(key)
await get(args)
break
case 'delete':
case 'rm':
case 'del':
await del(key)
await del(args)
break
case 'list':
case 'ls':
Expand All @@ -93,46 +93,58 @@ const config = async ([action, key, val]) => {
}
}

const set = async (key, val) => {
if (key === undefined)
throw UsageError()

if (val === undefined) {
if (key.indexOf('=') !== -1) {
const k = key.split('=')
key = k.shift()
val = k.join('=')
} else
val = ''
// take an array of `[key, value, k2=v2, k3, v3, ...]` and turn into
// { key: value, k2: v2, k3: v3 }
const keyValues = args => {
const kv = {}
for (let i = 0; i < args.length; i++) {
const arg = args[i].split('=')
const key = arg.shift()
const val = arg.length ? arg.join('=')
: i < args.length - 1 ? args[++i]
: ''
kv[key.trim()] = val.trim()
}
return kv
}

const set = async (args) => {
if (!args.length)
throw UsageError()

key = key.trim()
val = val.trim()
npm.log.info('config', 'set %j %j', key, val)
const where = npm.flatOptions.global ? 'global' : 'user'
npm.config.set(key, val, where)
if (!npm.config.validate(where))
npm.log.warn('config', 'omitting invalid config values')
for (const [key, val] of Object.entries(keyValues(args))) {
npm.log.info('config', 'set %j %j', key, val)
npm.config.set(key, val || '', where)
if (!npm.config.validate(where))
npm.log.warn('config', 'omitting invalid config values')
}

await npm.config.save(where)
}

const get = async key => {
if (!key)
const get = async keys => {
if (!keys.length)
return list()

if (!publicVar(key))
throw `The ${key} option is protected, and cannot be retrieved in this way`
const out = []
for (const key of keys) {
if (!publicVar(key))
throw `The ${key} option is protected, and cannot be retrieved in this way`

output(npm.config.get(key))
const pref = keys.length > 1 ? `${key}=` : ''
out.push(pref + npm.config.get(key))
}
output(out.join('\n'))
}

const del = async key => {
if (!key)
const del = async keys => {
if (!keys.length)
throw UsageError()

const where = npm.flatOptions.global ? 'global' : 'user'
npm.config.delete(key, where)
for (const key of keys)
npm.config.delete(key, where)
await npm.config.save(where)
}

Expand Down
2 changes: 1 addition & 1 deletion lib/get.js
Expand Up @@ -3,7 +3,7 @@ const usageUtil = require('./utils/usage.js')

const usage = usageUtil(
'get',
'npm get <key> <value> (See `npm config`)'
'npm get [<key> ...] (See `npm config`)'
)

const completion = npm.commands.config.completion
Expand Down
2 changes: 1 addition & 1 deletion lib/set.js
@@ -1,7 +1,7 @@

module.exports = set

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

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

Expand Down
90 changes: 89 additions & 1 deletion test/lib/config.js
Expand Up @@ -212,6 +212,33 @@ t.test('config delete key', t => {
})
})

t.test('config delete multiple key', t => {
t.plan(6)

const expect = [
'foo',
'bar',
]

npm.config.delete = (key, where) => {
t.equal(key, expect.shift(), 'should delete expected keyword')
t.equal(where, 'user', 'should delete key from user config by default')
}

npm.config.save = where => {
t.equal(where, 'user', 'should save user config post-delete')
}

config(['delete', 'foo', 'bar'], (err) => {
t.ifError(err, 'npm config delete keys')
})

t.teardown(() => {
delete npm.config.delete
delete npm.config.save
})
})

t.test('config delete key --global', t => {
t.plan(4)

Expand Down Expand Up @@ -293,12 +320,43 @@ t.test('config set key=val', t => {
})
})

t.test('config set multiple keys', t => {
t.plan(11)

const expect = [
['foo', 'bar'],
['bar', 'baz'],
['asdf', ''],
]
const args = ['foo', 'bar', 'bar=baz', 'asdf']

npm.config.set = (key, val, where) => {
const [expectKey, expectVal] = expect.shift()
t.equal(key, expectKey, 'should set expected key to user config')
t.equal(val, expectVal, 'should set expected value to user config')
t.equal(where, 'user', 'should set key/val in user config by default')
}

npm.config.save = where => {
t.equal(where, 'user', 'should save user config')
}

config(['set', ...args], (err) => {
t.ifError(err, 'npm config set key')
})

t.teardown(() => {
delete npm.config.set
delete npm.config.save
})
})

t.test('config set key to empty value', t => {
t.plan(5)

npm.config.set = (key, val, where) => {
t.equal(key, 'foo', 'should set expected key to user config')
t.equal(val, '', 'should set empty value to user config')
t.equal(val, '', 'should set "" to user config')
t.equal(where, 'user', 'should set key/val in user config by default')
}

Expand Down Expand Up @@ -403,6 +461,36 @@ t.test('config get key', t => {
})
})

t.test('config get multiple keys', t => {
t.plan(4)

const expect = [
'foo',
'bar',
]

const npmConfigGet = npm.config.get
npm.config.get = (key) => {
t.equal(key, expect.shift(), 'should use expected key')
return 'asdf'
}

npm.config.save = where => {
throw new Error('should not save')
}

config(['get', 'foo', 'bar'], (err) => {
t.ifError(err, 'npm config get multiple keys')
t.equal(result, 'foo=asdf\nbar=asdf')
})

t.teardown(() => {
result = ''
npm.config.get = npmConfigGet
delete npm.config.save
})
})

t.test('config get private key', t => {
config(['get', '//private-reg.npmjs.org/:_authThoken'], (err) => {
t.match(
Expand Down
2 changes: 1 addition & 1 deletion test/lib/npm.js
Expand Up @@ -342,7 +342,7 @@ t.test('npm.load', t => {
/Completed in [0-9]+ms/,
],
])
t.same(consoleLogs, [['@foo']])
t.same(consoleLogs, [['scope=@foo\n\u2010not-a-dash=undefined']])
})

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

1 comment on commit a9b8bf2

@Queen300
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idk #2398

Please sign in to comment.