@npmcli/config 4.2.2 → 5.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 +106 -92
- package/package.json +9 -8
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
|
@@ -51,6 +51,10 @@ const parseField = require('./parse-field.js')
|
|
|
51
51
|
const typeDescription = require('./type-description.js')
|
|
52
52
|
const setEnvs = require('./set-envs.js')
|
|
53
53
|
|
|
54
|
+
const {
|
|
55
|
+
ErrInvalidAuth,
|
|
56
|
+
} = require('./errors.js')
|
|
57
|
+
|
|
54
58
|
// types that can be saved back to
|
|
55
59
|
const confFileTypes = new Set([
|
|
56
60
|
'global',
|
|
@@ -280,26 +284,10 @@ class Config {
|
|
|
280
284
|
await this.loadGlobalConfig()
|
|
281
285
|
process.emit('timeEnd', 'config:load:global')
|
|
282
286
|
|
|
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
287
|
// set this before calling setEnvs, so that we don't have to share
|
|
289
288
|
// symbols, as that module also does a bunch of get operations
|
|
290
289
|
this[_loaded] = true
|
|
291
290
|
|
|
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
291
|
// set proper globalPrefix now that everything is loaded
|
|
304
292
|
this.globalPrefix = this.get('prefix')
|
|
305
293
|
|
|
@@ -399,7 +387,9 @@ class Config {
|
|
|
399
387
|
validate (where) {
|
|
400
388
|
if (!where) {
|
|
401
389
|
let valid = true
|
|
402
|
-
|
|
390
|
+
const authProblems = []
|
|
391
|
+
|
|
392
|
+
for (const entryWhere of this.data.keys()) {
|
|
403
393
|
// no need to validate our defaults, we know they're fine
|
|
404
394
|
// cli was already validated when parsed the first time
|
|
405
395
|
if (entryWhere === 'default' || entryWhere === 'builtin' || entryWhere === 'cli') {
|
|
@@ -407,7 +397,48 @@ class Config {
|
|
|
407
397
|
}
|
|
408
398
|
const ret = this.validate(entryWhere)
|
|
409
399
|
valid = valid && ret
|
|
400
|
+
|
|
401
|
+
if (['global', 'user', 'project'].includes(entryWhere)) {
|
|
402
|
+
// after validating everything else, we look for old auth configs we no longer support
|
|
403
|
+
// if these keys are found, we build up a list of them and the appropriate action and
|
|
404
|
+
// attach it as context on the thrown error
|
|
405
|
+
|
|
406
|
+
// first, keys that should be removed
|
|
407
|
+
for (const key of ['_authtoken', '-authtoken']) {
|
|
408
|
+
if (this.get(key, entryWhere)) {
|
|
409
|
+
authProblems.push({ action: 'delete', key, where: entryWhere })
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// NOTE we pull registry without restricting to the current 'where' because we want to
|
|
414
|
+
// suggest scoping things to the registry they would be applied to, which is the default
|
|
415
|
+
// regardless of where it was defined
|
|
416
|
+
const nerfedReg = nerfDart(this.get('registry'))
|
|
417
|
+
// keys that should be nerfed but currently are not
|
|
418
|
+
for (const key of ['_auth', '_authToken', 'username', '_password']) {
|
|
419
|
+
if (this.get(key, entryWhere)) {
|
|
420
|
+
// username and _password must both exist in the same file to be recognized correctly
|
|
421
|
+
if (key === 'username' && !this.get('_password', entryWhere)) {
|
|
422
|
+
authProblems.push({ action: 'delete', key, where: entryWhere })
|
|
423
|
+
} else if (key === '_password' && !this.get('username', entryWhere)) {
|
|
424
|
+
authProblems.push({ action: 'delete', key, where: entryWhere })
|
|
425
|
+
} else {
|
|
426
|
+
authProblems.push({
|
|
427
|
+
action: 'rename',
|
|
428
|
+
from: key,
|
|
429
|
+
to: `${nerfedReg}:${key}`,
|
|
430
|
+
where: entryWhere,
|
|
431
|
+
})
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
410
436
|
}
|
|
437
|
+
|
|
438
|
+
if (authProblems.length) {
|
|
439
|
+
throw new ErrInvalidAuth(authProblems)
|
|
440
|
+
}
|
|
441
|
+
|
|
411
442
|
return valid
|
|
412
443
|
} else {
|
|
413
444
|
const obj = this.data.get(where)
|
|
@@ -423,6 +454,40 @@ class Config {
|
|
|
423
454
|
}
|
|
424
455
|
}
|
|
425
456
|
|
|
457
|
+
// fixes problems identified by validate(), accepts the 'problems' property from a thrown
|
|
458
|
+
// ErrInvalidAuth to avoid having to check everything again
|
|
459
|
+
repair (problems) {
|
|
460
|
+
if (!problems) {
|
|
461
|
+
try {
|
|
462
|
+
this.validate()
|
|
463
|
+
} catch (err) {
|
|
464
|
+
// coverage skipped here because we don't need to test re-throwing an error
|
|
465
|
+
// istanbul ignore next
|
|
466
|
+
if (err.code !== 'ERR_INVALID_AUTH') {
|
|
467
|
+
throw err
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
problems = err.problems
|
|
471
|
+
} finally {
|
|
472
|
+
if (!problems) {
|
|
473
|
+
problems = []
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
for (const problem of problems) {
|
|
479
|
+
// coverage disabled for else branch because it doesn't do anything and shouldn't
|
|
480
|
+
// istanbul ignore else
|
|
481
|
+
if (problem.action === 'delete') {
|
|
482
|
+
this.delete(problem.key, problem.where)
|
|
483
|
+
} else if (problem.action === 'rename') {
|
|
484
|
+
const old = this.get(problem.from, problem.where)
|
|
485
|
+
this.set(problem.to, old, problem.where)
|
|
486
|
+
this.delete(problem.from, problem.where)
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
426
491
|
// Returns true if the value is coming directly from the source defined
|
|
427
492
|
// in default definitions, if the current value for the key config is
|
|
428
493
|
// coming from any other different source, returns false
|
|
@@ -644,21 +709,19 @@ class Config {
|
|
|
644
709
|
if (!confFileTypes.has(where)) {
|
|
645
710
|
throw new Error('invalid config location param: ' + where)
|
|
646
711
|
}
|
|
712
|
+
|
|
647
713
|
const conf = this.data.get(where)
|
|
648
714
|
conf[_raw] = { ...conf.data }
|
|
649
715
|
conf[_loadError] = null
|
|
650
716
|
|
|
651
|
-
// upgrade auth configs to more secure variants before saving
|
|
652
717
|
if (where === 'user') {
|
|
653
|
-
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
// eslint-disable-next-line no-empty
|
|
661
|
-
} catch (_) {}
|
|
718
|
+
// if email is nerfed, then we want to de-nerf it
|
|
719
|
+
const nerfed = nerfDart(this.get('registry'))
|
|
720
|
+
const email = this.get(`${nerfed}:email`, 'user')
|
|
721
|
+
if (email) {
|
|
722
|
+
this.delete(`${nerfed}:email`, 'user')
|
|
723
|
+
this.set('email', email, 'user')
|
|
724
|
+
}
|
|
662
725
|
}
|
|
663
726
|
|
|
664
727
|
const iniData = ini.stringify(conf.data).trim() + '\n'
|
|
@@ -686,14 +749,17 @@ class Config {
|
|
|
686
749
|
const nerfed = nerfDart(uri)
|
|
687
750
|
const def = nerfDart(this.get('registry'))
|
|
688
751
|
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
752
|
this.delete(`-authtoken`, 'user')
|
|
692
753
|
this.delete(`_authToken`, 'user')
|
|
693
754
|
this.delete(`_authtoken`, 'user')
|
|
694
755
|
this.delete(`_auth`, 'user')
|
|
695
756
|
this.delete(`_password`, 'user')
|
|
696
757
|
this.delete(`username`, 'user')
|
|
758
|
+
// de-nerf email if it's nerfed to the default registry
|
|
759
|
+
const email = this.get(`${nerfed}:email`, 'user')
|
|
760
|
+
if (email) {
|
|
761
|
+
this.set('email', email, 'user')
|
|
762
|
+
}
|
|
697
763
|
}
|
|
698
764
|
this.delete(`${nerfed}:_authToken`, 'user')
|
|
699
765
|
this.delete(`${nerfed}:_auth`, 'user')
|
|
@@ -706,28 +772,9 @@ class Config {
|
|
|
706
772
|
|
|
707
773
|
setCredentialsByURI (uri, { token, username, password, email, certfile, keyfile }) {
|
|
708
774
|
const nerfed = nerfDart(uri)
|
|
709
|
-
const def = nerfDart(this.get('registry'))
|
|
710
775
|
|
|
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
|
-
}
|
|
776
|
+
// email is either provided, a top level key, or nothing
|
|
777
|
+
email = email || this.get('email', 'user')
|
|
731
778
|
|
|
732
779
|
// field that hasn't been used as documented for a LONG time,
|
|
733
780
|
// and as of npm 7.10.0, isn't used at all. We just always
|
|
@@ -765,15 +812,17 @@ class Config {
|
|
|
765
812
|
// this has to be a bit more complicated to support legacy data of all forms
|
|
766
813
|
getCredentialsByURI (uri) {
|
|
767
814
|
const nerfed = nerfDart(uri)
|
|
815
|
+
const def = nerfDart(this.get('registry'))
|
|
768
816
|
const creds = {}
|
|
769
817
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
].join(' ')
|
|
774
|
-
|
|
818
|
+
// email is handled differently, it used to always be nerfed and now it never should be
|
|
819
|
+
// if it's set nerfed to the default registry, then we copy it to the unnerfed key
|
|
820
|
+
// TODO: evaluate removing 'email' from the credentials object returned here
|
|
775
821
|
const email = this.get(`${nerfed}:email`) || this.get('email')
|
|
776
822
|
if (email) {
|
|
823
|
+
if (nerfed === def) {
|
|
824
|
+
this.set('email', email, 'user')
|
|
825
|
+
}
|
|
777
826
|
creds.email = email
|
|
778
827
|
}
|
|
779
828
|
|
|
@@ -785,13 +834,8 @@ class Config {
|
|
|
785
834
|
// cert/key may be used in conjunction with other credentials, thus no `return`
|
|
786
835
|
}
|
|
787
836
|
|
|
788
|
-
const
|
|
789
|
-
const tokenReg = this.get(`${nerfed}:_authToken`) || defaultToken
|
|
790
|
-
|
|
837
|
+
const tokenReg = this.get(`${nerfed}:_authToken`)
|
|
791
838
|
if (tokenReg) {
|
|
792
|
-
if (tokenReg === defaultToken) {
|
|
793
|
-
log.warn('config', deprecatedAuthWarning)
|
|
794
|
-
}
|
|
795
839
|
creds.token = tokenReg
|
|
796
840
|
return creds
|
|
797
841
|
}
|
|
@@ -816,37 +860,7 @@ class Config {
|
|
|
816
860
|
return creds
|
|
817
861
|
}
|
|
818
862
|
|
|
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
|
|
863
|
+
// at this point, nothing else is usable so just return what we do have
|
|
850
864
|
return creds
|
|
851
865
|
}
|
|
852
866
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@npmcli/config",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.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,11 +24,15 @@
|
|
|
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.0",
|
|
35
36
|
"tap": "^16.0.1"
|
|
36
37
|
},
|
|
37
38
|
"dependencies": {
|
|
@@ -45,10 +46,10 @@
|
|
|
45
46
|
"walk-up-path": "^1.0.0"
|
|
46
47
|
},
|
|
47
48
|
"engines": {
|
|
48
|
-
"node": "^
|
|
49
|
+
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
|
49
50
|
},
|
|
50
51
|
"templateOSS": {
|
|
51
52
|
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
|
|
52
|
-
"version": "
|
|
53
|
+
"version": "4.5.0"
|
|
53
54
|
}
|
|
54
55
|
}
|