@npmcli/config 1.2.8 → 2.2.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/lib/index.js CHANGED
@@ -47,7 +47,6 @@ const envReplace = require('./env-replace.js')
47
47
  const parseField = require('./parse-field.js')
48
48
  const typeDescription = require('./type-description.js')
49
49
  const setEnvs = require('./set-envs.js')
50
- const getUserAgent = require('./get-user-agent.js')
51
50
 
52
51
  // types that can be saved back to
53
52
  const confFileTypes = new Set([
@@ -69,6 +68,9 @@ const _get = Symbol('get')
69
68
  const _find = Symbol('find')
70
69
  const _loadObject = Symbol('loadObject')
71
70
  const _loadFile = Symbol('loadFile')
71
+ const _checkDeprecated = Symbol('checkDeprecated')
72
+ const _flatten = Symbol('flatten')
73
+ const _flatOptions = Symbol('flatOptions')
72
74
 
73
75
  class Config {
74
76
  static get typeDefs () {
@@ -76,9 +78,9 @@ class Config {
76
78
  }
77
79
 
78
80
  constructor ({
79
- types,
81
+ definitions,
80
82
  shorthands,
81
- defaults,
83
+ flatten,
82
84
  npmPath,
83
85
 
84
86
  // options just to override in tests, mostly
@@ -89,10 +91,27 @@ class Config {
89
91
  execPath = process.execPath,
90
92
  cwd = process.cwd(),
91
93
  }) {
92
- this.npmPath = npmPath
94
+
95
+ // turn the definitions into nopt's weirdo syntax
96
+ this.definitions = definitions
97
+ const types = {}
98
+ const defaults = {}
99
+ this.deprecated = {}
100
+ for (const [key, def] of Object.entries(definitions)) {
101
+ defaults[key] = def.default
102
+ types[key] = def.type
103
+ if (def.deprecated)
104
+ this.deprecated[key] = def.deprecated.trim().replace(/\n +/, '\n')
105
+ }
106
+
107
+ // populated the first time we flatten the object
108
+ this[_flatOptions] = null
109
+ this[_flatten] = flatten
93
110
  this.types = types
94
111
  this.shorthands = shorthands
95
112
  this.defaults = defaults
113
+
114
+ this.npmPath = npmPath
96
115
  this.log = log
97
116
  this.argv = argv
98
117
  this.env = env
@@ -178,15 +197,31 @@ class Config {
178
197
  throw new Error('call config.load() before setting values')
179
198
  if (!confTypes.has(where))
180
199
  throw new Error('invalid config location param: ' + where)
181
- if (key === '_auth') {
182
- const { email } = this.getCredentialsByURI(this.get('registry'))
183
- if (!email)
184
- throw new Error('Cannot set _auth without first setting email')
185
- }
186
- this.data.get(where).data[key] = val
200
+ this[_checkDeprecated](key)
201
+ const { data } = this.data.get(where)
202
+ data[key] = val
187
203
 
188
204
  // this is now dirty, the next call to this.valid will have to check it
189
205
  this.data.get(where)[_valid] = null
206
+
207
+ // the flat options are invalidated, regenerate next time they're needed
208
+ this[_flatOptions] = null
209
+ }
210
+
211
+ get flat () {
212
+ if (this[_flatOptions])
213
+ return this[_flatOptions]
214
+
215
+ // create the object for flat options passed to deps
216
+ process.emit('time', 'config:load:flatten')
217
+ this[_flatOptions] = {}
218
+ // walk from least priority to highest
219
+ for (const { data } of this.data.values()) {
220
+ this[_flatten](data, this[_flatOptions])
221
+ }
222
+ process.emit('timeEnd', 'config:load:flatten')
223
+
224
+ return this[_flatOptions]
190
225
  }
191
226
 
192
227
  delete (key, where = 'cli') {
@@ -233,11 +268,6 @@ class Config {
233
268
  await this.loadGlobalConfig()
234
269
  process.emit('timeEnd', 'config:load:global')
235
270
 
236
- // now the extras
237
- process.emit('time', 'config:load:cafile')
238
- await this.loadCAFile()
239
- process.emit('timeEnd', 'config:load:cafile')
240
-
241
271
  // warn if anything is not valid
242
272
  process.emit('time', 'config:load:validate')
243
273
  this.validate()
@@ -247,13 +277,17 @@ class Config {
247
277
  // symbols, as that module also does a bunch of get operations
248
278
  this[_loaded] = true
249
279
 
280
+ process.emit('time', 'config:load:credentials')
281
+ const reg = this.get('registry')
282
+ const creds = this.getCredentialsByURI(reg)
283
+ // ignore this error because a failed set will strip out anything that
284
+ // might be a security hazard, which was the intention.
285
+ try { this.setCredentialsByURI(reg, creds) } catch (_) {}
286
+ process.emit('timeEnd', 'config:load:credentials')
287
+
250
288
  // set proper globalPrefix now that everything is loaded
251
289
  this.globalPrefix = this.get('prefix')
252
290
 
253
- process.emit('time', 'config:load:setUserAgent')
254
- this.setUserAgent()
255
- process.emit('timeEnd', 'config:load:setUserAgent')
256
-
257
291
  process.emit('time', 'config:load:setEnvs')
258
292
  this.setEnvs()
259
293
  process.emit('timeEnd', 'config:load:setEnvs')
@@ -376,13 +410,13 @@ class Config {
376
410
  this.data.get(where)[_valid] = false
377
411
 
378
412
  if (Array.isArray(type)) {
379
- if (type.indexOf(typeDefs.url.type) !== -1)
413
+ if (type.includes(typeDefs.url.type))
380
414
  type = typeDefs.url.type
381
415
  else {
382
416
  /* istanbul ignore if - no actual configs matching this, but
383
417
  * path types SHOULD be handled this way, like URLs, for the
384
418
  * same reason */
385
- if (type.indexOf(typeDefs.path.type) !== -1)
419
+ if (type.includes(typeDefs.path.type))
386
420
  type = typeDefs.path.type
387
421
  }
388
422
  }
@@ -428,11 +462,21 @@ class Config {
428
462
  for (const [key, value] of Object.entries(obj)) {
429
463
  const k = envReplace(key, this.env)
430
464
  const v = this.parseField(value, k)
465
+ if (where !== 'default')
466
+ this[_checkDeprecated](k, where, obj, [key, value])
431
467
  conf.data[k] = v
432
468
  }
433
469
  }
434
470
  }
435
471
 
472
+ [_checkDeprecated] (key, where, obj, kv) {
473
+ // XXX a future npm version will make this a warning.
474
+ // An even more future npm version will make this an error.
475
+ if (this.deprecated[key]) {
476
+ this.log.verbose('config', key, this.deprecated[key])
477
+ }
478
+ }
479
+
436
480
  // Parse a field, coercing it to the best type available.
437
481
  parseField (f, key, listElement = false) {
438
482
  return parseField(f, key, this, listElement)
@@ -547,14 +591,17 @@ class Config {
547
591
  const nerfed = nerfDart(uri)
548
592
  const def = nerfDart(this.get('registry'))
549
593
  if (def === nerfed) {
594
+ // do not delete email, that shouldn't be nerfed any more.
595
+ // just delete the nerfed copy, if one exists.
550
596
  this.delete(`-authtoken`, 'user')
551
597
  this.delete(`_authToken`, 'user')
598
+ this.delete(`_authtoken`, 'user')
552
599
  this.delete(`_auth`, 'user')
553
600
  this.delete(`_password`, 'user')
554
601
  this.delete(`username`, 'user')
555
- this.delete(`email`, 'user')
556
602
  }
557
603
  this.delete(`${nerfed}:-authtoken`, 'user')
604
+ this.delete(`${nerfed}:_authtoken`, 'user')
558
605
  this.delete(`${nerfed}:_authToken`, 'user')
559
606
  this.delete(`${nerfed}:_auth`, 'user')
560
607
  this.delete(`${nerfed}:_password`, 'user')
@@ -562,7 +609,7 @@ class Config {
562
609
  this.delete(`${nerfed}:email`, 'user')
563
610
  }
564
611
 
565
- setCredentialsByURI (uri, { token, username, password, email, alwaysAuth }) {
612
+ setCredentialsByURI (uri, { token, username, password, email }) {
566
613
  const nerfed = nerfDart(uri)
567
614
  const def = nerfDart(this.get('registry'))
568
615
 
@@ -570,41 +617,45 @@ class Config {
570
617
  // remove old style auth info not limited to a single registry
571
618
  this.delete('_password', 'user')
572
619
  this.delete('username', 'user')
573
- this.delete('email', 'user')
574
620
  this.delete('_auth', 'user')
575
621
  this.delete('_authtoken', 'user')
622
+ this.delete('-authtoken', 'user')
576
623
  this.delete('_authToken', 'user')
577
624
  }
578
625
 
579
- this.delete(`${nerfed}:-authtoken`)
626
+ // email used to be nerfed always. if we're using the default
627
+ // registry, de-nerf it.
628
+ if (nerfed === def) {
629
+ email = email ||
630
+ this.get('email', 'user') ||
631
+ this.get(`${nerfed}:email`, 'user')
632
+ if (email)
633
+ this.set('email', email, 'user')
634
+ }
635
+
636
+ // field that hasn't been used as documented for a LONG time,
637
+ // and as of npm 7.10.0, isn't used at all. We just always
638
+ // send auth if we have it, only to the URIs under the nerf dart.
639
+ this.delete(`${nerfed}:always-auth`, 'user')
640
+
641
+ this.delete(`${nerfed}:-authtoken`, 'user')
642
+ this.delete(`${nerfed}:_authtoken`, 'user')
643
+ this.delete(`${nerfed}:email`, 'user')
580
644
  if (token) {
581
645
  this.set(`${nerfed}:_authToken`, token, 'user')
582
646
  this.delete(`${nerfed}:_password`, 'user')
583
647
  this.delete(`${nerfed}:username`, 'user')
584
- this.delete(`${nerfed}:email`, 'user')
585
- this.delete(`${nerfed}:always-auth`, 'user')
586
- } else if (username || password || email) {
587
- if (username || password) {
588
- if (!username)
589
- throw new Error('must include username')
590
- if (!password)
591
- throw new Error('must include password')
592
- }
593
- if (!email)
594
- throw new Error('must include email')
648
+ } else if (username || password) {
649
+ if (!username)
650
+ throw new Error('must include username')
651
+ if (!password)
652
+ throw new Error('must include password')
595
653
  this.delete(`${nerfed}:_authToken`, 'user')
596
- if (username || password) {
597
- this.set(`${nerfed}:username`, username, 'user')
598
- // note: not encrypted, no idea why we bothered to do this, but oh well
599
- // protects against shoulder-hacks if password is memorable, I guess?
600
- const encoded = Buffer.from(password, 'utf8').toString('base64')
601
- this.set(`${nerfed}:_password`, encoded, 'user')
602
- }
603
- this.set(`${nerfed}:email`, email, 'user')
604
- if (alwaysAuth !== undefined)
605
- this.set(`${nerfed}:always-auth`, alwaysAuth, 'user')
606
- else
607
- this.delete(`${nerfed}:always-auth`, 'user')
654
+ this.set(`${nerfed}:username`, username, 'user')
655
+ // note: not encrypted, no idea why we bothered to do this, but oh well
656
+ // protects against shoulder-hacks if password is memorable, I guess?
657
+ const encoded = Buffer.from(password, 'utf8').toString('base64')
658
+ this.set(`${nerfed}:_password`, encoded, 'user')
608
659
  } else {
609
660
  throw new Error('No credentials to set.')
610
661
  }
@@ -615,18 +666,12 @@ class Config {
615
666
  const nerfed = nerfDart(uri)
616
667
  const creds = {}
617
668
 
618
- // you can set always-auth for a single registry, or as a default
619
- const alwaysAuthReg = this.get(`${nerfed}:always-auth`)
620
- if (alwaysAuthReg !== undefined)
621
- creds.alwaysAuth = !!alwaysAuthReg
622
- else
623
- creds.alwaysAuth = this.get('always-auth')
624
-
625
669
  const email = this.get(`${nerfed}:email`) || this.get('email')
626
670
  if (email)
627
671
  creds.email = email
628
672
 
629
673
  const tokenReg = this.get(`${nerfed}:_authToken`) ||
674
+ this.get(`${nerfed}:_authtoken`) ||
630
675
  this.get(`${nerfed}:-authtoken`) ||
631
676
  nerfed === nerfDart(this.get('registry')) && this.get('_authToken')
632
677
 
@@ -645,6 +690,16 @@ class Config {
645
690
  return creds
646
691
  }
647
692
 
693
+ const authReg = this.get(`${nerfed}:_auth`)
694
+ if (authReg) {
695
+ const authDecode = Buffer.from(authReg, 'base64').toString('utf8')
696
+ const authSplit = authDecode.split(':')
697
+ creds.username = authSplit.shift()
698
+ creds.password = authSplit.join(':')
699
+ creds.auth = authReg
700
+ return creds
701
+ }
702
+
648
703
  // at this point, we can only use the values if the URI is the
649
704
  // default registry.
650
705
  const defaultNerf = nerfDart(this.get('registry'))
@@ -675,48 +730,6 @@ class Config {
675
730
  return creds
676
731
  }
677
732
 
678
- async loadCAFile () {
679
- const where = this[_find]('cafile')
680
-
681
- /* istanbul ignore if - it'll always be set in the defaults */
682
- if (!where)
683
- return
684
-
685
- const cafile = this[_get]('cafile', where)
686
- const ca = this[_get]('ca', where)
687
-
688
- // if you have a ca, or cafile is set to null, then nothing to do here.
689
- if (ca || !cafile)
690
- return
691
-
692
- const raw = await readFile(cafile, 'utf8').catch(er => {
693
- if (er.code !== 'ENOENT')
694
- throw er
695
- })
696
- if (!raw)
697
- return
698
-
699
- const delim = '-----END CERTIFICATE-----'
700
- const output = raw.replace(/\r\n/g, '\n').split(delim)
701
- .filter(section => section.trim())
702
- .map(section => section.trimLeft() + delim)
703
-
704
- // make it non-enumerable so we don't save it back by accident
705
- const { data } = this.data.get(where)
706
- Object.defineProperty(data, 'ca', {
707
- value: output,
708
- enumerable: false,
709
- configurable: true,
710
- writable: true,
711
- })
712
- }
713
-
714
- // the user-agent configuration is a template that gets populated
715
- // with some variables, that takes place here
716
- setUserAgent () {
717
- this.set('user-agent', getUserAgent(this))
718
- }
719
-
720
733
  // set up the environment object we have with npm_config_* environs
721
734
  // for all configs that are different from their default values, and
722
735
  // set EDITOR and HOME.
package/lib/set-envs.js CHANGED
@@ -50,17 +50,13 @@ const setEnvs = (config) => {
50
50
  platform,
51
51
  env,
52
52
  defaults,
53
+ definitions,
53
54
  list: [cliConf, envConf],
54
55
  } = config
55
56
 
56
- const { DESTDIR } = env
57
- if (platform !== 'win32' && DESTDIR && globalPrefix.indexOf(DESTDIR) === 0)
58
- env.PREFIX = globalPrefix.substr(DESTDIR.length)
59
- else
60
- env.PREFIX = globalPrefix
61
-
62
- env.INIT_CWD = env.INIT_CWD || process.cwd()
57
+ env.INIT_CWD = process.cwd()
63
58
 
59
+ // if the key is deprecated, skip it always.
64
60
  // if the key is the default value,
65
61
  // if the environ is NOT the default value,
66
62
  // set the environ
@@ -71,6 +67,10 @@ const setEnvs = (config) => {
71
67
  const cliSet = new Set(Object.keys(cliConf))
72
68
  const envSet = new Set(Object.keys(envConf))
73
69
  for (const key in cliConf) {
70
+ const { deprecated, envExport = true } = definitions[key] || {}
71
+ if (deprecated || envExport === false)
72
+ continue
73
+
74
74
  if (sameConfigValue(defaults[key], cliConf[key])) {
75
75
  // config is the default, if the env thought different, then we
76
76
  // have to set it BACK to the default in the environment.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@npmcli/config",
3
- "version": "1.2.8",
3
+ "version": "2.2.0",
4
4
  "files": [
5
5
  "lib"
6
6
  ],
@@ -24,7 +24,7 @@
24
24
  "coverage-map": "map.js"
25
25
  },
26
26
  "devDependencies": {
27
- "tap": "^14.10.8"
27
+ "tap": "^15.0.4"
28
28
  },
29
29
  "dependencies": {
30
30
  "ini": "^2.0.0",
@@ -1,13 +0,0 @@
1
- // Accepts a config object, returns a user-agent string
2
- const getUserAgent = (config) => {
3
- const ciName = config.get('ci-name')
4
- return (config.get('user-agent') || '')
5
- .replace(/\{node-version\}/gi, config.get('node-version'))
6
- .replace(/\{npm-version\}/gi, config.get('npm-version'))
7
- .replace(/\{platform\}/gi, process.platform)
8
- .replace(/\{arch\}/gi, process.arch)
9
- .replace(/\{ci\}/gi, ciName ? `ci/${ciName}` : '')
10
- .trim()
11
- }
12
-
13
- module.exports = getUserAgent