@npmcli/config 4.2.2 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/lib/errors.js +22 -0
- package/lib/index.js +115 -115
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -89,6 +89,7 @@ process.on('log', (level, ...args) => {
|
|
|
89
89
|
// returns a promise that fails if config loading fails, and
|
|
90
90
|
// resolves when the config object is ready for action
|
|
91
91
|
conf.load().then(() => {
|
|
92
|
+
conf.validate()
|
|
92
93
|
console.log('loaded ok! some-key = ' + conf.get('some-key'))
|
|
93
94
|
}).catch(er => {
|
|
94
95
|
console.error('error loading configs!', er)
|
|
@@ -210,6 +211,10 @@ Delete the configuration key from the specified level in the config stack.
|
|
|
210
211
|
Verify that all known configuration options are set to valid values, and
|
|
211
212
|
log a warning if they are invalid.
|
|
212
213
|
|
|
214
|
+
Invalid auth options will cause this method to throw an error with a `code`
|
|
215
|
+
property of `ERR_INVALID_AUTH`, and a `problems` property listing the specific
|
|
216
|
+
concerns with the current configuration.
|
|
217
|
+
|
|
213
218
|
If `where` is not set, then all config objects are validated.
|
|
214
219
|
|
|
215
220
|
Returns `true` if all configs are valid.
|
|
@@ -218,6 +223,14 @@ Note that it's usually enough (and more efficient) to just check
|
|
|
218
223
|
`config.valid`, since each data object is marked for re-evaluation on every
|
|
219
224
|
`config.set()` operation.
|
|
220
225
|
|
|
226
|
+
### `config.repair(problems)`
|
|
227
|
+
|
|
228
|
+
Accept an optional array of problems (as thrown by `config.validate()`) and
|
|
229
|
+
perform the necessary steps to resolve them. If no problems are provided,
|
|
230
|
+
this method will call `config.validate()` internally to retrieve them.
|
|
231
|
+
|
|
232
|
+
Note that you must `await config.save('user')` in order to persist the changes.
|
|
233
|
+
|
|
221
234
|
### `config.isDefault(key)`
|
|
222
235
|
|
|
223
236
|
Returns `true` if the value is coming directly from the
|
package/lib/errors.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
class ErrInvalidAuth extends Error {
|
|
4
|
+
constructor (problems) {
|
|
5
|
+
let message = 'Invalid auth configuration found: '
|
|
6
|
+
message += problems.map((problem) => {
|
|
7
|
+
if (problem.action === 'delete') {
|
|
8
|
+
return `\`${problem.key}\` is not allowed in ${problem.where} config`
|
|
9
|
+
} else if (problem.action === 'rename') {
|
|
10
|
+
return `\`${problem.from}\` must be renamed to \`${problem.to}\` in ${problem.where} config`
|
|
11
|
+
}
|
|
12
|
+
}).join(', ')
|
|
13
|
+
message += '\nPlease run `npm config fix` to repair your configuration.`'
|
|
14
|
+
super(message)
|
|
15
|
+
this.code = 'ERR_INVALID_AUTH'
|
|
16
|
+
this.problems = problems
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = {
|
|
21
|
+
ErrInvalidAuth,
|
|
22
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -2,26 +2,20 @@
|
|
|
2
2
|
const walkUp = require('walk-up-path')
|
|
3
3
|
const ini = require('ini')
|
|
4
4
|
const nopt = require('nopt')
|
|
5
|
-
const mkdirp = require('mkdirp-infer-owner')
|
|
6
5
|
const mapWorkspaces = require('@npmcli/map-workspaces')
|
|
7
6
|
const rpj = require('read-package-json-fast')
|
|
8
7
|
const log = require('proc-log')
|
|
9
8
|
|
|
10
|
-
/* istanbul ignore next */
|
|
11
|
-
const myUid = process.getuid && process.getuid()
|
|
12
|
-
/* istanbul ignore next */
|
|
13
|
-
const myGid = process.getgid && process.getgid()
|
|
14
|
-
|
|
15
9
|
const { resolve, dirname, join } = require('path')
|
|
16
10
|
const { homedir } = require('os')
|
|
17
|
-
const {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
11
|
+
const {
|
|
12
|
+
readFile,
|
|
13
|
+
writeFile,
|
|
14
|
+
chmod,
|
|
15
|
+
unlink,
|
|
16
|
+
stat,
|
|
17
|
+
mkdir,
|
|
18
|
+
} = require('fs/promises')
|
|
25
19
|
|
|
26
20
|
const hasOwnProperty = (obj, key) =>
|
|
27
21
|
Object.prototype.hasOwnProperty.call(obj, key)
|
|
@@ -51,6 +45,10 @@ const parseField = require('./parse-field.js')
|
|
|
51
45
|
const typeDescription = require('./type-description.js')
|
|
52
46
|
const setEnvs = require('./set-envs.js')
|
|
53
47
|
|
|
48
|
+
const {
|
|
49
|
+
ErrInvalidAuth,
|
|
50
|
+
} = require('./errors.js')
|
|
51
|
+
|
|
54
52
|
// types that can be saved back to
|
|
55
53
|
const confFileTypes = new Set([
|
|
56
54
|
'global',
|
|
@@ -280,26 +278,10 @@ class Config {
|
|
|
280
278
|
await this.loadGlobalConfig()
|
|
281
279
|
process.emit('timeEnd', 'config:load:global')
|
|
282
280
|
|
|
283
|
-
// warn if anything is not valid
|
|
284
|
-
process.emit('time', 'config:load:validate')
|
|
285
|
-
this.validate()
|
|
286
|
-
process.emit('timeEnd', 'config:load:validate')
|
|
287
|
-
|
|
288
281
|
// set this before calling setEnvs, so that we don't have to share
|
|
289
282
|
// symbols, as that module also does a bunch of get operations
|
|
290
283
|
this[_loaded] = true
|
|
291
284
|
|
|
292
|
-
process.emit('time', 'config:load:credentials')
|
|
293
|
-
const reg = this.get('registry')
|
|
294
|
-
const creds = this.getCredentialsByURI(reg)
|
|
295
|
-
// ignore this error because a failed set will strip out anything that
|
|
296
|
-
// might be a security hazard, which was the intention.
|
|
297
|
-
try {
|
|
298
|
-
this.setCredentialsByURI(reg, creds)
|
|
299
|
-
// eslint-disable-next-line no-empty
|
|
300
|
-
} catch (_) {}
|
|
301
|
-
process.emit('timeEnd', 'config:load:credentials')
|
|
302
|
-
|
|
303
285
|
// set proper globalPrefix now that everything is loaded
|
|
304
286
|
this.globalPrefix = this.get('prefix')
|
|
305
287
|
|
|
@@ -399,7 +381,9 @@ class Config {
|
|
|
399
381
|
validate (where) {
|
|
400
382
|
if (!where) {
|
|
401
383
|
let valid = true
|
|
402
|
-
|
|
384
|
+
const authProblems = []
|
|
385
|
+
|
|
386
|
+
for (const entryWhere of this.data.keys()) {
|
|
403
387
|
// no need to validate our defaults, we know they're fine
|
|
404
388
|
// cli was already validated when parsed the first time
|
|
405
389
|
if (entryWhere === 'default' || entryWhere === 'builtin' || entryWhere === 'cli') {
|
|
@@ -407,7 +391,48 @@ class Config {
|
|
|
407
391
|
}
|
|
408
392
|
const ret = this.validate(entryWhere)
|
|
409
393
|
valid = valid && ret
|
|
394
|
+
|
|
395
|
+
if (['global', 'user', 'project'].includes(entryWhere)) {
|
|
396
|
+
// after validating everything else, we look for old auth configs we no longer support
|
|
397
|
+
// if these keys are found, we build up a list of them and the appropriate action and
|
|
398
|
+
// attach it as context on the thrown error
|
|
399
|
+
|
|
400
|
+
// first, keys that should be removed
|
|
401
|
+
for (const key of ['_authtoken', '-authtoken']) {
|
|
402
|
+
if (this.get(key, entryWhere)) {
|
|
403
|
+
authProblems.push({ action: 'delete', key, where: entryWhere })
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// NOTE we pull registry without restricting to the current 'where' because we want to
|
|
408
|
+
// suggest scoping things to the registry they would be applied to, which is the default
|
|
409
|
+
// regardless of where it was defined
|
|
410
|
+
const nerfedReg = nerfDart(this.get('registry'))
|
|
411
|
+
// keys that should be nerfed but currently are not
|
|
412
|
+
for (const key of ['_auth', '_authToken', 'username', '_password']) {
|
|
413
|
+
if (this.get(key, entryWhere)) {
|
|
414
|
+
// username and _password must both exist in the same file to be recognized correctly
|
|
415
|
+
if (key === 'username' && !this.get('_password', entryWhere)) {
|
|
416
|
+
authProblems.push({ action: 'delete', key, where: entryWhere })
|
|
417
|
+
} else if (key === '_password' && !this.get('username', entryWhere)) {
|
|
418
|
+
authProblems.push({ action: 'delete', key, where: entryWhere })
|
|
419
|
+
} else {
|
|
420
|
+
authProblems.push({
|
|
421
|
+
action: 'rename',
|
|
422
|
+
from: key,
|
|
423
|
+
to: `${nerfedReg}:${key}`,
|
|
424
|
+
where: entryWhere,
|
|
425
|
+
})
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (authProblems.length) {
|
|
433
|
+
throw new ErrInvalidAuth(authProblems)
|
|
410
434
|
}
|
|
435
|
+
|
|
411
436
|
return valid
|
|
412
437
|
} else {
|
|
413
438
|
const obj = this.data.get(where)
|
|
@@ -423,6 +448,40 @@ class Config {
|
|
|
423
448
|
}
|
|
424
449
|
}
|
|
425
450
|
|
|
451
|
+
// fixes problems identified by validate(), accepts the 'problems' property from a thrown
|
|
452
|
+
// ErrInvalidAuth to avoid having to check everything again
|
|
453
|
+
repair (problems) {
|
|
454
|
+
if (!problems) {
|
|
455
|
+
try {
|
|
456
|
+
this.validate()
|
|
457
|
+
} catch (err) {
|
|
458
|
+
// coverage skipped here because we don't need to test re-throwing an error
|
|
459
|
+
// istanbul ignore next
|
|
460
|
+
if (err.code !== 'ERR_INVALID_AUTH') {
|
|
461
|
+
throw err
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
problems = err.problems
|
|
465
|
+
} finally {
|
|
466
|
+
if (!problems) {
|
|
467
|
+
problems = []
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
for (const problem of problems) {
|
|
473
|
+
// coverage disabled for else branch because it doesn't do anything and shouldn't
|
|
474
|
+
// istanbul ignore else
|
|
475
|
+
if (problem.action === 'delete') {
|
|
476
|
+
this.delete(problem.key, problem.where)
|
|
477
|
+
} else if (problem.action === 'rename') {
|
|
478
|
+
const old = this.get(problem.from, problem.where)
|
|
479
|
+
this.set(problem.to, old, problem.where)
|
|
480
|
+
this.delete(problem.from, problem.where)
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
426
485
|
// Returns true if the value is coming directly from the source defined
|
|
427
486
|
// in default definitions, if the current value for the key config is
|
|
428
487
|
// coming from any other different source, returns false
|
|
@@ -644,21 +703,19 @@ class Config {
|
|
|
644
703
|
if (!confFileTypes.has(where)) {
|
|
645
704
|
throw new Error('invalid config location param: ' + where)
|
|
646
705
|
}
|
|
706
|
+
|
|
647
707
|
const conf = this.data.get(where)
|
|
648
708
|
conf[_raw] = { ...conf.data }
|
|
649
709
|
conf[_loadError] = null
|
|
650
710
|
|
|
651
|
-
// upgrade auth configs to more secure variants before saving
|
|
652
711
|
if (where === 'user') {
|
|
653
|
-
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
// eslint-disable-next-line no-empty
|
|
661
|
-
} catch (_) {}
|
|
712
|
+
// if email is nerfed, then we want to de-nerf it
|
|
713
|
+
const nerfed = nerfDart(this.get('registry'))
|
|
714
|
+
const email = this.get(`${nerfed}:email`, 'user')
|
|
715
|
+
if (email) {
|
|
716
|
+
this.delete(`${nerfed}:email`, 'user')
|
|
717
|
+
this.set('email', email, 'user')
|
|
718
|
+
}
|
|
662
719
|
}
|
|
663
720
|
|
|
664
721
|
const iniData = ini.stringify(conf.data).trim() + '\n'
|
|
@@ -668,16 +725,8 @@ class Config {
|
|
|
668
725
|
return
|
|
669
726
|
}
|
|
670
727
|
const dir = dirname(conf.source)
|
|
671
|
-
await
|
|
728
|
+
await mkdir(dir, { recursive: true })
|
|
672
729
|
await writeFile(conf.source, iniData, 'utf8')
|
|
673
|
-
// don't leave a root-owned config file lying around
|
|
674
|
-
/* istanbul ignore if - this is best-effort and a pita to test */
|
|
675
|
-
if (myUid === 0) {
|
|
676
|
-
const st = await stat(dir).catch(() => null)
|
|
677
|
-
if (st && (st.uid !== myUid || st.gid !== myGid)) {
|
|
678
|
-
await chown(conf.source, st.uid, st.gid).catch(() => {})
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
730
|
const mode = where === 'user' ? 0o600 : 0o666
|
|
682
731
|
await chmod(conf.source, mode)
|
|
683
732
|
}
|
|
@@ -686,14 +735,17 @@ class Config {
|
|
|
686
735
|
const nerfed = nerfDart(uri)
|
|
687
736
|
const def = nerfDart(this.get('registry'))
|
|
688
737
|
if (def === nerfed) {
|
|
689
|
-
// do not delete email, that shouldn't be nerfed any more.
|
|
690
|
-
// just delete the nerfed copy, if one exists.
|
|
691
738
|
this.delete(`-authtoken`, 'user')
|
|
692
739
|
this.delete(`_authToken`, 'user')
|
|
693
740
|
this.delete(`_authtoken`, 'user')
|
|
694
741
|
this.delete(`_auth`, 'user')
|
|
695
742
|
this.delete(`_password`, 'user')
|
|
696
743
|
this.delete(`username`, 'user')
|
|
744
|
+
// de-nerf email if it's nerfed to the default registry
|
|
745
|
+
const email = this.get(`${nerfed}:email`, 'user')
|
|
746
|
+
if (email) {
|
|
747
|
+
this.set('email', email, 'user')
|
|
748
|
+
}
|
|
697
749
|
}
|
|
698
750
|
this.delete(`${nerfed}:_authToken`, 'user')
|
|
699
751
|
this.delete(`${nerfed}:_auth`, 'user')
|
|
@@ -706,28 +758,9 @@ class Config {
|
|
|
706
758
|
|
|
707
759
|
setCredentialsByURI (uri, { token, username, password, email, certfile, keyfile }) {
|
|
708
760
|
const nerfed = nerfDart(uri)
|
|
709
|
-
const def = nerfDart(this.get('registry'))
|
|
710
761
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
this.delete('_password', 'user')
|
|
714
|
-
this.delete('username', 'user')
|
|
715
|
-
this.delete('_auth', 'user')
|
|
716
|
-
this.delete('_authtoken', 'user')
|
|
717
|
-
this.delete('-authtoken', 'user')
|
|
718
|
-
this.delete('_authToken', 'user')
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
// email used to be nerfed always. if we're using the default
|
|
722
|
-
// registry, de-nerf it.
|
|
723
|
-
if (nerfed === def) {
|
|
724
|
-
email = email ||
|
|
725
|
-
this.get('email', 'user') ||
|
|
726
|
-
this.get(`${nerfed}:email`, 'user')
|
|
727
|
-
if (email) {
|
|
728
|
-
this.set('email', email, 'user')
|
|
729
|
-
}
|
|
730
|
-
}
|
|
762
|
+
// email is either provided, a top level key, or nothing
|
|
763
|
+
email = email || this.get('email', 'user')
|
|
731
764
|
|
|
732
765
|
// field that hasn't been used as documented for a LONG time,
|
|
733
766
|
// and as of npm 7.10.0, isn't used at all. We just always
|
|
@@ -765,15 +798,17 @@ class Config {
|
|
|
765
798
|
// this has to be a bit more complicated to support legacy data of all forms
|
|
766
799
|
getCredentialsByURI (uri) {
|
|
767
800
|
const nerfed = nerfDart(uri)
|
|
801
|
+
const def = nerfDart(this.get('registry'))
|
|
768
802
|
const creds = {}
|
|
769
803
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
].join(' ')
|
|
774
|
-
|
|
804
|
+
// email is handled differently, it used to always be nerfed and now it never should be
|
|
805
|
+
// if it's set nerfed to the default registry, then we copy it to the unnerfed key
|
|
806
|
+
// TODO: evaluate removing 'email' from the credentials object returned here
|
|
775
807
|
const email = this.get(`${nerfed}:email`) || this.get('email')
|
|
776
808
|
if (email) {
|
|
809
|
+
if (nerfed === def) {
|
|
810
|
+
this.set('email', email, 'user')
|
|
811
|
+
}
|
|
777
812
|
creds.email = email
|
|
778
813
|
}
|
|
779
814
|
|
|
@@ -785,13 +820,8 @@ class Config {
|
|
|
785
820
|
// cert/key may be used in conjunction with other credentials, thus no `return`
|
|
786
821
|
}
|
|
787
822
|
|
|
788
|
-
const
|
|
789
|
-
const tokenReg = this.get(`${nerfed}:_authToken`) || defaultToken
|
|
790
|
-
|
|
823
|
+
const tokenReg = this.get(`${nerfed}:_authToken`)
|
|
791
824
|
if (tokenReg) {
|
|
792
|
-
if (tokenReg === defaultToken) {
|
|
793
|
-
log.warn('config', deprecatedAuthWarning)
|
|
794
|
-
}
|
|
795
825
|
creds.token = tokenReg
|
|
796
826
|
return creds
|
|
797
827
|
}
|
|
@@ -816,37 +846,7 @@ class Config {
|
|
|
816
846
|
return creds
|
|
817
847
|
}
|
|
818
848
|
|
|
819
|
-
// at this point,
|
|
820
|
-
// default registry.
|
|
821
|
-
const defaultNerf = nerfDart(this.get('registry'))
|
|
822
|
-
if (nerfed !== defaultNerf) {
|
|
823
|
-
return creds
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
const userDef = this.get('username')
|
|
827
|
-
const passDef = this.get('_password')
|
|
828
|
-
if (userDef && passDef) {
|
|
829
|
-
log.warn('config', deprecatedAuthWarning)
|
|
830
|
-
creds.username = userDef
|
|
831
|
-
creds.password = Buffer.from(passDef, 'base64').toString('utf8')
|
|
832
|
-
const auth = `${creds.username}:${creds.password}`
|
|
833
|
-
creds.auth = Buffer.from(auth, 'utf8').toString('base64')
|
|
834
|
-
return creds
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
// Handle the old-style _auth=<base64> style for the default
|
|
838
|
-
// registry, if set.
|
|
839
|
-
const auth = this.get('_auth')
|
|
840
|
-
if (!auth) {
|
|
841
|
-
return creds
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
log.warn('config', deprecatedAuthWarning)
|
|
845
|
-
const authDecode = Buffer.from(auth, 'base64').toString('utf8')
|
|
846
|
-
const authSplit = authDecode.split(':')
|
|
847
|
-
creds.username = authSplit.shift()
|
|
848
|
-
creds.password = authSplit.join(':')
|
|
849
|
-
creds.auth = auth
|
|
849
|
+
// at this point, nothing else is usable so just return what we do have
|
|
850
850
|
return creds
|
|
851
851
|
}
|
|
852
852
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@npmcli/config",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"files": [
|
|
5
5
|
"bin/",
|
|
6
6
|
"lib/"
|
|
@@ -16,9 +16,6 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"test": "tap",
|
|
18
18
|
"snap": "tap",
|
|
19
|
-
"preversion": "npm test",
|
|
20
|
-
"postversion": "npm publish",
|
|
21
|
-
"prepublishOnly": "git push origin --follow-tags",
|
|
22
19
|
"lint": "eslint \"**/*.js\"",
|
|
23
20
|
"postlint": "template-oss-check",
|
|
24
21
|
"lintfix": "npm run lint -- --fix",
|
|
@@ -27,28 +24,31 @@
|
|
|
27
24
|
},
|
|
28
25
|
"tap": {
|
|
29
26
|
"check-coverage": true,
|
|
30
|
-
"coverage-map": "map.js"
|
|
27
|
+
"coverage-map": "map.js",
|
|
28
|
+
"nyc-arg": [
|
|
29
|
+
"--exclude",
|
|
30
|
+
"tap-snapshots/**"
|
|
31
|
+
]
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
34
|
"@npmcli/eslint-config": "^3.0.1",
|
|
34
|
-
"@npmcli/template-oss": "
|
|
35
|
+
"@npmcli/template-oss": "4.5.1",
|
|
35
36
|
"tap": "^16.0.1"
|
|
36
37
|
},
|
|
37
38
|
"dependencies": {
|
|
38
39
|
"@npmcli/map-workspaces": "^2.0.2",
|
|
39
40
|
"ini": "^3.0.0",
|
|
40
|
-
"mkdirp-infer-owner": "^2.0.0",
|
|
41
41
|
"nopt": "^6.0.0",
|
|
42
42
|
"proc-log": "^2.0.0",
|
|
43
|
-
"read-package-json-fast": "^
|
|
43
|
+
"read-package-json-fast": "^3.0.0",
|
|
44
44
|
"semver": "^7.3.5",
|
|
45
45
|
"walk-up-path": "^1.0.0"
|
|
46
46
|
},
|
|
47
47
|
"engines": {
|
|
48
|
-
"node": "^
|
|
48
|
+
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
|
49
49
|
},
|
|
50
50
|
"templateOSS": {
|
|
51
51
|
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
|
|
52
|
-
"version": "
|
|
52
|
+
"version": "4.5.1"
|
|
53
53
|
}
|
|
54
54
|
}
|