@npmcli/config 2.3.2 → 4.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 CHANGED
@@ -78,9 +78,12 @@ const conf = new Config({
78
78
  platform: process.platform,
79
79
  // optional, defaults to process.cwd()
80
80
  cwd: process.cwd(),
81
- // optional, defaults to emitting 'log' events on process object
82
- // only silly, verbose, warn, and error are logged by this module
83
- log: require('npmlog')
81
+ })
82
+
83
+ // emits log events on the process object
84
+ // see `proc-log` for more info
85
+ process.on('log', (level, ...args) => {
86
+ console.log(level, ...args)
84
87
  })
85
88
 
86
89
  // returns a promise that fails if config loading fails, and
@@ -124,8 +127,6 @@ Options:
124
127
  Windows.
125
128
  - `execPath` Optional, defaults to `process.execPath`. Used to infer the
126
129
  `globalPrefix`.
127
- - `log` Optional, the object used to log debug messages, warnings, and
128
- errors. Defaults to emitting on the `process` object.
129
130
  - `env` Optional, defaults to `process.env`. Source of the environment
130
131
  variables for configuration.
131
132
  - `argv` Optional, defaults to `process.argv`. Source of the CLI options
@@ -161,7 +162,6 @@ Fields:
161
162
  - `argv` The `argv` param
162
163
  - `execPath` The `execPath` param
163
164
  - `platform` The `platform` param
164
- - `log` The `log` param
165
165
  - `defaults` The `defaults` param
166
166
  - `shorthands` The `shorthands` param
167
167
  - `types` The `types` param
@@ -218,6 +218,29 @@ Note that it's usually enough (and more efficient) to just check
218
218
  `config.valid`, since each data object is marked for re-evaluation on every
219
219
  `config.set()` operation.
220
220
 
221
+ ### `config.isDefault(key)`
222
+
223
+ Returns `true` if the value is coming directly from the
224
+ default definitions, if the current value for the key config is
225
+ coming from any other source, returns `false`.
226
+
227
+ This method can be used for avoiding or tweaking default values, e.g:
228
+
229
+ > Given a global default definition of foo='foo' it's possible to read that
230
+ > value such as:
231
+ >
232
+ > ```js
233
+ > const save = config.get('foo')
234
+ > ```
235
+ >
236
+ > Now in a different place of your app it's possible to avoid using the `foo`
237
+ > default value, by checking to see if the current config value is currently
238
+ > one that was defined by the default definitions:
239
+ >
240
+ > ```js
241
+ > const save = config.isDefault('foo') ? 'bar' : config.get('foo')
242
+ > ```
243
+
221
244
  ### `config.save(where)`
222
245
 
223
246
  Save the config file specified by the `where` param. Must be one of
@@ -1,13 +1,14 @@
1
1
  // replace any ${ENV} values with the appropriate environ.
2
2
 
3
- const envExpr = /(\\*)\$\{([^}]+)\}/g
3
+ const envExpr = /(?<!\\)(\\*)\$\{([^${}]+)\}/g
4
4
 
5
5
  module.exports = (f, env) => f.replace(envExpr, (orig, esc, name) => {
6
- const val = env[name] !== undefined ? env[name] : `\$\{${name}\}`
6
+ const val = env[name] !== undefined ? env[name] : `$\{${name}}`
7
7
 
8
8
  // consume the escape chars that are relevant.
9
- if (esc.length % 2)
9
+ if (esc.length % 2) {
10
10
  return orig.substr((esc.length + 1) / 2)
11
+ }
11
12
 
12
13
  return (esc.substr(esc.length / 2)) + val
13
14
  })
package/lib/index.js CHANGED
@@ -3,6 +3,9 @@ const walkUp = require('walk-up-path')
3
3
  const ini = require('ini')
4
4
  const nopt = require('nopt')
5
5
  const mkdirp = require('mkdirp-infer-owner')
6
+ const mapWorkspaces = require('@npmcli/map-workspaces')
7
+ const rpj = require('read-package-json-fast')
8
+ const log = require('proc-log')
6
9
 
7
10
  /* istanbul ignore next */
8
11
  const myUid = process.getuid && process.getuid()
@@ -86,12 +89,10 @@ class Config {
86
89
  // options just to override in tests, mostly
87
90
  env = process.env,
88
91
  argv = process.argv,
89
- log = require('./proc-log.js'),
90
92
  platform = process.platform,
91
93
  execPath = process.execPath,
92
94
  cwd = process.cwd(),
93
95
  }) {
94
-
95
96
  // turn the definitions into nopt's weirdo syntax
96
97
  this.definitions = definitions
97
98
  const types = {}
@@ -100,8 +101,9 @@ class Config {
100
101
  for (const [key, def] of Object.entries(definitions)) {
101
102
  defaults[key] = def.default
102
103
  types[key] = def.type
103
- if (def.deprecated)
104
+ if (def.deprecated) {
104
105
  this.deprecated[key] = def.deprecated.trim().replace(/\n +/, '\n')
106
+ }
105
107
  }
106
108
 
107
109
  // populated the first time we flatten the object
@@ -112,7 +114,6 @@ class Config {
112
114
  this.defaults = defaults
113
115
 
114
116
  this.npmPath = npmPath
115
- this.log = log
116
117
  this.argv = argv
117
118
  this.env = env
118
119
  this.execPath = execPath
@@ -162,41 +163,48 @@ class Config {
162
163
 
163
164
  // return the location where key is found.
164
165
  find (key) {
165
- if (!this.loaded)
166
+ if (!this.loaded) {
166
167
  throw new Error('call config.load() before reading values')
168
+ }
167
169
  return this[_find](key)
168
170
  }
171
+
169
172
  [_find] (key) {
170
173
  // have to look in reverse order
171
174
  const entries = [...this.data.entries()]
172
175
  for (let i = entries.length - 1; i > -1; i--) {
173
176
  const [where, { data }] = entries[i]
174
- if (hasOwnProperty(data, key))
177
+ if (hasOwnProperty(data, key)) {
175
178
  return where
179
+ }
176
180
  }
177
181
  return null
178
182
  }
179
183
 
180
184
  get (key, where) {
181
- if (!this.loaded)
185
+ if (!this.loaded) {
182
186
  throw new Error('call config.load() before reading values')
187
+ }
183
188
  return this[_get](key, where)
184
189
  }
190
+
185
191
  // we need to get values sometimes, so use this internal one to do so
186
192
  // while in the process of loading.
187
193
  [_get] (key, where = null) {
188
194
  if (where !== null && !confTypes.has(where)) {
189
195
  throw new Error('invalid config location param: ' + where)
190
196
  }
191
- const { data, source } = this.data.get(where || 'cli')
197
+ const { data } = this.data.get(where || 'cli')
192
198
  return where === null || hasOwnProperty(data, key) ? data[key] : undefined
193
199
  }
194
200
 
195
201
  set (key, val, where = 'cli') {
196
- if (!this.loaded)
202
+ if (!this.loaded) {
197
203
  throw new Error('call config.load() before setting values')
198
- if (!confTypes.has(where))
204
+ }
205
+ if (!confTypes.has(where)) {
199
206
  throw new Error('invalid config location param: ' + where)
207
+ }
200
208
  this[_checkDeprecated](key)
201
209
  const { data } = this.data.get(where)
202
210
  data[key] = val
@@ -209,8 +217,9 @@ class Config {
209
217
  }
210
218
 
211
219
  get flat () {
212
- if (this[_flatOptions])
220
+ if (this[_flatOptions]) {
213
221
  return this[_flatOptions]
222
+ }
214
223
 
215
224
  // create the object for flat options passed to deps
216
225
  process.emit('time', 'config:load:flatten')
@@ -225,16 +234,19 @@ class Config {
225
234
  }
226
235
 
227
236
  delete (key, where = 'cli') {
228
- if (!this.loaded)
237
+ if (!this.loaded) {
229
238
  throw new Error('call config.load() before deleting values')
230
- if (!confTypes.has(where))
239
+ }
240
+ if (!confTypes.has(where)) {
231
241
  throw new Error('invalid config location param: ' + where)
242
+ }
232
243
  delete this.data.get(where).data[key]
233
244
  }
234
245
 
235
246
  async load () {
236
- if (this.loaded)
247
+ if (this.loaded) {
237
248
  throw new Error('attempting to load npm config multiple times')
249
+ }
238
250
 
239
251
  process.emit('time', 'config:load')
240
252
  // first load the defaults, which sets the global prefix
@@ -282,7 +294,9 @@ class Config {
282
294
  const creds = this.getCredentialsByURI(reg)
283
295
  // ignore this error because a failed set will strip out anything that
284
296
  // might be a security hazard, which was the intention.
285
- try { this.setCredentialsByURI(reg, creds) } catch (_) {}
297
+ try {
298
+ this.setCredentialsByURI(reg, creds)
299
+ } catch (_) {}
286
300
  process.emit('timeEnd', 'config:load:credentials')
287
301
 
288
302
  // set proper globalPrefix now that everything is loaded
@@ -319,14 +333,16 @@ class Config {
319
333
  }
320
334
 
321
335
  loadHome () {
322
- if (this.env.HOME)
336
+ if (this.env.HOME) {
323
337
  return this.home = this.env.HOME
338
+ }
324
339
  this.home = homedir()
325
340
  }
326
341
 
327
342
  loadGlobalPrefix () {
328
- if (this.globalPrefix)
343
+ if (this.globalPrefix) {
329
344
  throw new Error('cannot load default global prefix more than once')
345
+ }
330
346
 
331
347
  if (this.env.PREFIX) {
332
348
  this.globalPrefix = this.env.PREFIX
@@ -338,17 +354,18 @@ class Config {
338
354
  this.globalPrefix = dirname(dirname(this.execPath))
339
355
 
340
356
  // destdir only is respected on Unix
341
- if (this.env.DESTDIR)
357
+ if (this.env.DESTDIR) {
342
358
  this.globalPrefix = join(this.env.DESTDIR, this.globalPrefix)
359
+ }
343
360
  }
344
361
  }
345
362
 
346
363
  loadEnv () {
347
- const prefix = 'npm_config_'
348
364
  const conf = Object.create(null)
349
365
  for (const [envKey, envVal] of Object.entries(this.env)) {
350
- if (!/^npm_config_/i.test(envKey) || envVal === '')
366
+ if (!/^npm_config_/i.test(envKey) || envVal === '') {
351
367
  continue
368
+ }
352
369
  const key = envKey.substr('npm_config_'.length)
353
370
  .replace(/(?!^)_/g, '-') // don't replace _ at the start of the key
354
371
  .toLowerCase()
@@ -368,9 +385,10 @@ class Config {
368
385
  }
369
386
 
370
387
  get valid () {
371
- for (const [where, {valid}] of this.data.entries()) {
372
- if (valid === false || valid === null && !this.validate(where))
388
+ for (const [where, { valid }] of this.data.entries()) {
389
+ if (valid === false || valid === null && !this.validate(where)) {
373
390
  return false
391
+ }
374
392
  }
375
393
  return true
376
394
  }
@@ -378,11 +396,12 @@ class Config {
378
396
  validate (where) {
379
397
  if (!where) {
380
398
  let valid = true
381
- for (const [where, obj] of this.data.entries()) {
399
+ for (const [where] of this.data.entries()) {
382
400
  // no need to validate our defaults, we know they're fine
383
401
  // cli was already validated when parsed the first time
384
- if (where === 'default' || where === 'builtin' || where === 'cli')
402
+ if (where === 'default' || where === 'builtin' || where === 'cli') {
385
403
  continue
404
+ }
386
405
  const ret = this.validate(where)
387
406
  valid = valid && ret
388
407
  }
@@ -401,8 +420,22 @@ class Config {
401
420
  }
402
421
  }
403
422
 
423
+ // Returns true if the value is coming directly from the source defined
424
+ // in default definitions, if the current value for the key config is
425
+ // coming from any other different source, returns false
426
+ isDefault (key) {
427
+ const [defaultType, ...types] = [...confTypes]
428
+ const defaultData = this.data.get(defaultType).data
429
+
430
+ return hasOwnProperty(defaultData, key)
431
+ && types.every(type => {
432
+ const typeData = this.data.get(type).data
433
+ return !hasOwnProperty(typeData, key)
434
+ })
435
+ }
436
+
404
437
  invalidHandler (k, val, type, source, where) {
405
- this.log.warn(
438
+ log.warn(
406
439
  'invalid config',
407
440
  k + '=' + JSON.stringify(val),
408
441
  `set in ${source}`
@@ -410,14 +443,15 @@ class Config {
410
443
  this.data.get(where)[_valid] = false
411
444
 
412
445
  if (Array.isArray(type)) {
413
- if (type.includes(typeDefs.url.type))
446
+ if (type.includes(typeDefs.url.type)) {
414
447
  type = typeDefs.url.type
415
- else {
448
+ } else {
416
449
  /* istanbul ignore if - no actual configs matching this, but
417
450
  * path types SHOULD be handled this way, like URLs, for the
418
451
  * same reason */
419
- if (type.includes(typeDefs.path.type))
452
+ if (type.includes(typeDefs.path.type)) {
420
453
  type = typeDefs.path.type
454
+ }
421
455
  }
422
456
  }
423
457
 
@@ -434,7 +468,7 @@ class Config {
434
468
  : mustBe.filter(m => m !== Array)
435
469
  .map(n => typeof n === 'string' ? n : JSON.stringify(n))
436
470
  .join(', ')
437
- this.log.warn('invalid config', msg, desc)
471
+ log.warn('invalid config', msg, desc)
438
472
  }
439
473
 
440
474
  [_loadObject] (obj, where, source, er = null) {
@@ -455,15 +489,17 @@ class Config {
455
489
  this.sources.set(source, where)
456
490
  if (er) {
457
491
  conf.loadError = er
458
- if (er.code !== 'ENOENT')
459
- this.log.verbose('config', `error loading ${where} config`, er)
492
+ if (er.code !== 'ENOENT') {
493
+ log.verbose('config', `error loading ${where} config`, er)
494
+ }
460
495
  } else {
461
496
  conf.raw = obj
462
497
  for (const [key, value] of Object.entries(obj)) {
463
498
  const k = envReplace(key, this.env)
464
499
  const v = this.parseField(value, k)
465
- if (where !== 'default')
500
+ if (where !== 'default') {
466
501
  this[_checkDeprecated](k, where, obj, [key, value])
502
+ }
467
503
  conf.data[k] = v
468
504
  }
469
505
  }
@@ -473,7 +509,7 @@ class Config {
473
509
  // XXX a future npm version will make this a warning.
474
510
  // An even more future npm version will make this an error.
475
511
  if (this.deprecated[key]) {
476
- this.log.verbose('config', key, this.deprecated[key])
512
+ log.verbose('config', key, this.deprecated[key])
477
513
  }
478
514
  }
479
515
 
@@ -514,9 +550,9 @@ class Config {
514
550
  // up loading the "project" config where the "userconfig" will be,
515
551
  // which causes some calamaties. So, we only load project config if
516
552
  // it doesn't match what the userconfig will be.
517
- if (projectFile !== this[_get]('userconfig'))
553
+ if (projectFile !== this[_get]('userconfig')) {
518
554
  return this[_loadFile](projectFile, 'project')
519
- else {
555
+ } else {
520
556
  this.data.get('project').source = '(same as "user" config, ignored)'
521
557
  this.sources.set(this.data.get('project').source, 'project')
522
558
  }
@@ -529,23 +565,65 @@ class Config {
529
565
  return
530
566
  }
531
567
 
568
+ const cliWorkspaces = this[_get]('workspaces', 'cli')
569
+
532
570
  for (const p of walkUp(this.cwd)) {
533
- // walk up until we have a nm dir or a pj file
534
- const hasAny = (await Promise.all([
535
- stat(resolve(p, 'node_modules'))
536
- .then(st => st.isDirectory())
537
- .catch(() => false),
538
- stat(resolve(p, 'package.json'))
539
- .then(st => st.isFile())
540
- .catch(() => false),
541
- ])).some(is => is)
542
- if (hasAny) {
571
+ const hasNodeModules = await stat(resolve(p, 'node_modules'))
572
+ .then((st) => st.isDirectory())
573
+ .catch(() => false)
574
+
575
+ const hasPackageJson = await stat(resolve(p, 'package.json'))
576
+ .then((st) => st.isFile())
577
+ .catch(() => false)
578
+
579
+ if (!this.localPrefix && (hasNodeModules || hasPackageJson)) {
543
580
  this.localPrefix = p
544
- return
581
+
582
+ // if workspaces are disabled, return now
583
+ if (cliWorkspaces === false) {
584
+ return
585
+ }
586
+
587
+ // otherwise, continue the loop
588
+ continue
589
+ }
590
+
591
+ if (this.localPrefix && hasPackageJson) {
592
+ // if we already set localPrefix but this dir has a package.json
593
+ // then we need to see if `p` is a workspace root by reading its package.json
594
+ // however, if reading it fails then we should just move on
595
+ const pkg = await rpj(resolve(p, 'package.json')).catch(() => false)
596
+ if (!pkg) {
597
+ continue
598
+ }
599
+
600
+ const workspaces = await mapWorkspaces({ cwd: p, pkg })
601
+ for (const w of workspaces.values()) {
602
+ if (w === this.localPrefix) {
603
+ // see if there's a .npmrc file in the workspace, if so log a warning
604
+ const hasNpmrc = await stat(resolve(this.localPrefix, '.npmrc'))
605
+ .then((st) => st.isFile())
606
+ .catch(() => false)
607
+
608
+ if (hasNpmrc) {
609
+ log.warn(`ignoring workspace config at ${this.localPrefix}/.npmrc`)
610
+ }
611
+
612
+ // set the workspace in the default layer, which allows it to be overridden easily
613
+ const { data } = this.data.get('default')
614
+ data.workspace = [this.localPrefix]
615
+ this.localPrefix = p
616
+ log.info(`found workspace root at ${this.localPrefix}`)
617
+ // we found a root, so we return now
618
+ return
619
+ }
620
+ }
545
621
  }
546
622
  }
547
623
 
548
- this.localPrefix = this.cwd
624
+ if (!this.localPrefix) {
625
+ this.localPrefix = this.cwd
626
+ }
549
627
  }
550
628
 
551
629
  loadUserConfig () {
@@ -557,10 +635,12 @@ class Config {
557
635
  }
558
636
 
559
637
  async save (where) {
560
- if (!this.loaded)
638
+ if (!this.loaded) {
561
639
  throw new Error('call config.load() before saving')
562
- if (!confFileTypes.has(where))
640
+ }
641
+ if (!confFileTypes.has(where)) {
563
642
  throw new Error('invalid config location param: ' + where)
643
+ }
564
644
  const conf = this.data.get(where)
565
645
  conf[_raw] = { ...conf.data }
566
646
  conf[_loadError] = null
@@ -572,7 +652,9 @@ class Config {
572
652
  // we ignore this error because the failed set already removed
573
653
  // anything that might be a security hazard, and it won't be
574
654
  // saved back to the .npmrc file, so we're good.
575
- try { this.setCredentialsByURI(reg, creds) } catch (_) {}
655
+ try {
656
+ this.setCredentialsByURI(reg, creds)
657
+ } catch (_) {}
576
658
  }
577
659
 
578
660
  const iniData = ini.stringify(conf.data).trim() + '\n'
@@ -588,8 +670,9 @@ class Config {
588
670
  /* istanbul ignore if - this is best-effort and a pita to test */
589
671
  if (myUid === 0) {
590
672
  const st = await stat(dir).catch(() => null)
591
- if (st && (st.uid !== myUid || st.gid !== myGid))
673
+ if (st && (st.uid !== myUid || st.gid !== myGid)) {
592
674
  await chown(conf.source, st.uid, st.gid).catch(() => {})
675
+ }
593
676
  }
594
677
  const mode = where === 'user' ? 0o600 : 0o666
595
678
  await chmod(conf.source, mode)
@@ -637,8 +720,9 @@ class Config {
637
720
  email = email ||
638
721
  this.get('email', 'user') ||
639
722
  this.get(`${nerfed}:email`, 'user')
640
- if (email)
723
+ if (email) {
641
724
  this.set('email', email, 'user')
725
+ }
642
726
  }
643
727
 
644
728
  // field that hasn't been used as documented for a LONG time,
@@ -654,10 +738,12 @@ class Config {
654
738
  this.delete(`${nerfed}:_password`, 'user')
655
739
  this.delete(`${nerfed}:username`, 'user')
656
740
  } else if (username || password) {
657
- if (!username)
741
+ if (!username) {
658
742
  throw new Error('must include username')
659
- if (!password)
743
+ }
744
+ if (!password) {
660
745
  throw new Error('must include password')
746
+ }
661
747
  this.delete(`${nerfed}:_authToken`, 'user')
662
748
  this.set(`${nerfed}:username`, username, 'user')
663
749
  // note: not encrypted, no idea why we bothered to do this, but oh well
@@ -675,8 +761,9 @@ class Config {
675
761
  const creds = {}
676
762
 
677
763
  const email = this.get(`${nerfed}:email`) || this.get('email')
678
- if (email)
764
+ if (email) {
679
765
  creds.email = email
766
+ }
680
767
 
681
768
  const tokenReg = this.get(`${nerfed}:_authToken`) ||
682
769
  this.get(`${nerfed}:_authtoken`) ||
@@ -711,8 +798,9 @@ class Config {
711
798
  // at this point, we can only use the values if the URI is the
712
799
  // default registry.
713
800
  const defaultNerf = nerfDart(this.get('registry'))
714
- if (nerfed !== defaultNerf)
801
+ if (nerfed !== defaultNerf) {
715
802
  return creds
803
+ }
716
804
 
717
805
  const userDef = this.get('username')
718
806
  const passDef = this.get('_password')
@@ -727,8 +815,9 @@ class Config {
727
815
  // Handle the old-style _auth=<base64> style for the default
728
816
  // registry, if set.
729
817
  const auth = this.get('_auth')
730
- if (!auth)
818
+ if (!auth) {
731
819
  return creds
820
+ }
732
821
 
733
822
  const authDecode = Buffer.from(auth, 'base64').toString('utf8')
734
823
  const authSplit = authDecode.split(':')
@@ -741,7 +830,9 @@ class Config {
741
830
  // set up the environment object we have with npm_config_* environs
742
831
  // for all configs that are different from their default values, and
743
832
  // set EDITOR and HOME.
744
- setEnvs () { setEnvs(this) }
833
+ setEnvs () {
834
+ setEnvs(this)
835
+ }
745
836
  }
746
837
 
747
838
  const _data = Symbol('data')
@@ -767,25 +858,37 @@ class ConfigData {
767
858
  }
768
859
 
769
860
  set source (s) {
770
- if (this[_source])
861
+ if (this[_source]) {
771
862
  throw new Error('cannot set ConfigData source more than once')
863
+ }
772
864
  this[_source] = s
773
865
  }
774
- get source () { return this[_source] }
866
+
867
+ get source () {
868
+ return this[_source]
869
+ }
775
870
 
776
871
  set loadError (e) {
777
- if (this[_loadError] || this[_raw])
872
+ if (this[_loadError] || this[_raw]) {
778
873
  throw new Error('cannot set ConfigData loadError after load')
874
+ }
779
875
  this[_loadError] = e
780
876
  }
781
- get loadError () { return this[_loadError] }
877
+
878
+ get loadError () {
879
+ return this[_loadError]
880
+ }
782
881
 
783
882
  set raw (r) {
784
- if (this[_raw] || this[_loadError])
883
+ if (this[_raw] || this[_loadError]) {
785
884
  throw new Error('cannot set ConfigData raw after load')
885
+ }
786
886
  this[_raw] = r
787
887
  }
788
- get raw () { return this[_raw] }
888
+
889
+ get raw () {
890
+ return this[_raw]
891
+ }
789
892
  }
790
893
 
791
894
  module.exports = Config
@@ -6,10 +6,11 @@ const { resolve } = require('path')
6
6
  const { parse: umaskParse } = require('./umask.js')
7
7
 
8
8
  const parseField = (f, key, opts, listElement = false) => {
9
- if (typeof f !== 'string' && !Array.isArray(f))
9
+ if (typeof f !== 'string' && !Array.isArray(f)) {
10
10
  return f
11
+ }
11
12
 
12
- const { platform, types, log, home, env } = opts
13
+ const { platform, types, home, env } = opts
13
14
 
14
15
  // type can be array or a single thing. coerce to array.
15
16
  const typeList = new Set([].concat(types[key]))
@@ -20,8 +21,9 @@ const parseField = (f, key, opts, listElement = false) => {
20
21
  const isNumber = typeList.has(typeDefs.Number.type)
21
22
  const isList = !listElement && typeList.has(Array)
22
23
 
23
- if (Array.isArray(f))
24
+ if (Array.isArray(f)) {
24
25
  return !isList ? f : f.map(field => parseField(field, key, opts, true))
26
+ }
25
27
 
26
28
  // now we know it's a string
27
29
  f = f.trim()
@@ -29,12 +31,14 @@ const parseField = (f, key, opts, listElement = false) => {
29
31
  // list types get put in the environment separated by double-\n
30
32
  // usually a single \n would suffice, but ca/cert configs can contain
31
33
  // line breaks and multiple entries.
32
- if (isList)
34
+ if (isList) {
33
35
  return parseField(f.split('\n\n'), key, opts)
36
+ }
34
37
 
35
38
  // --foo is like --foo=true for boolean types
36
- if (isBool && !isString && f === '')
39
+ if (isBool && !isString && f === '') {
37
40
  return true
41
+ }
38
42
 
39
43
  // string types can be the string 'true', 'false', etc.
40
44
  // otherwise, parse these values out
@@ -51,10 +55,11 @@ const parseField = (f, key, opts, listElement = false) => {
51
55
 
52
56
  if (isPath) {
53
57
  const homePattern = platform === 'win32' ? /^~(\/|\\)/ : /^~\//
54
- if (homePattern.test(f) && home)
58
+ if (homePattern.test(f) && home) {
55
59
  f = resolve(home, f.substr(2))
56
- else
60
+ } else {
57
61
  f = resolve(f)
62
+ }
58
63
  }
59
64
 
60
65
  if (isUmask) {
@@ -66,8 +71,9 @@ const parseField = (f, key, opts, listElement = false) => {
66
71
  }
67
72
  }
68
73
 
69
- if (isNumber && !isNaN(f))
74
+ if (isNumber && !isNaN(f)) {
70
75
  f = +f
76
+ }
71
77
 
72
78
  return f
73
79
  }
package/lib/set-envs.js CHANGED
@@ -22,15 +22,17 @@ const sameConfigValue = (def, val) =>
22
22
  : sameArrayValue(def, val)
23
23
 
24
24
  const sameArrayValue = (def, val) => {
25
- if (def.length !== val.length)
25
+ if (def.length !== val.length) {
26
26
  return false
27
+ }
27
28
 
28
29
  for (let i = 0; i < def.length; i++) {
29
30
  /* istanbul ignore next - there are no array configs where the default
30
31
  * is not an empty array, so this loop is a no-op, but it's the correct
31
32
  * thing to do if we ever DO add a config like that. */
32
- if (def[i] !== val[i])
33
+ if (def[i] !== val[i]) {
33
34
  return false
35
+ }
34
36
  }
35
37
  return true
36
38
  }
@@ -38,16 +40,15 @@ const sameArrayValue = (def, val) => {
38
40
  const setEnv = (env, rawKey, rawVal) => {
39
41
  const val = envVal(rawVal)
40
42
  const key = envKey(rawKey, val)
41
- if (key && val !== null)
43
+ if (key && val !== null) {
42
44
  env[key] = val
45
+ }
43
46
  }
44
47
 
45
48
  const setEnvs = (config) => {
46
49
  // This ensures that all npm config values that are not the defaults are
47
50
  // shared appropriately with child processes, without false positives.
48
51
  const {
49
- globalPrefix,
50
- platform,
51
52
  env,
52
53
  defaults,
53
54
  definitions,
@@ -68,19 +69,22 @@ const setEnvs = (config) => {
68
69
  const envSet = new Set(Object.keys(envConf))
69
70
  for (const key in cliConf) {
70
71
  const { deprecated, envExport = true } = definitions[key] || {}
71
- if (deprecated || envExport === false)
72
+ if (deprecated || envExport === false) {
72
73
  continue
74
+ }
73
75
 
74
76
  if (sameConfigValue(defaults[key], cliConf[key])) {
75
77
  // config is the default, if the env thought different, then we
76
78
  // have to set it BACK to the default in the environment.
77
- if (!sameConfigValue(envConf[key], cliConf[key]))
79
+ if (!sameConfigValue(envConf[key], cliConf[key])) {
78
80
  setEnv(env, key, cliConf[key])
81
+ }
79
82
  } else {
80
83
  // config is not the default. if the env wasn't the one to set
81
84
  // it that way, then we have to put it in the env
82
- if (!(envSet.has(key) && !cliSet.has(key)))
85
+ if (!(envSet.has(key) && !cliSet.has(key))) {
83
86
  setEnv(env, key, cliConf[key])
87
+ }
84
88
  }
85
89
  }
86
90
 
@@ -88,16 +92,19 @@ const setEnvs = (config) => {
88
92
  env.HOME = config.home
89
93
  env.npm_config_global_prefix = config.globalPrefix
90
94
  env.npm_config_local_prefix = config.localPrefix
91
- if (cliConf.editor)
95
+ if (cliConf.editor) {
92
96
  env.EDITOR = cliConf.editor
97
+ }
93
98
 
94
99
  // note: this doesn't afect the *current* node process, of course, since
95
100
  // it's already started, but it does affect the options passed to scripts.
96
- if (cliConf['node-options'])
101
+ if (cliConf['node-options']) {
97
102
  env.NODE_OPTIONS = cliConf['node-options']
103
+ }
98
104
 
99
- if (require.main && require.main.filename)
105
+ if (require.main && require.main.filename) {
100
106
  env.npm_execpath = require.main.filename
107
+ }
101
108
  env.NODE = env.npm_node_execpath = config.execPath
102
109
  }
103
110
 
package/lib/type-defs.js CHANGED
@@ -5,15 +5,17 @@ const { Umask, validate: validateUmask } = require('./umask.js')
5
5
  const semver = require('semver')
6
6
  const validateSemver = (data, k, val) => {
7
7
  const valid = semver.valid(val)
8
- if (!valid)
8
+ if (!valid) {
9
9
  return false
10
+ }
10
11
  data[k] = valid
11
12
  }
12
13
 
13
14
  const noptValidatePath = nopt.typeDefs.path.validate
14
15
  const validatePath = (data, k, val) => {
15
- if (typeof val !== 'string')
16
+ if (typeof val !== 'string') {
16
17
  return false
18
+ }
17
19
  return noptValidatePath(data, k, val)
18
20
  }
19
21
 
@@ -2,15 +2,18 @@
2
2
  // returns a string for one thing, or an array of descriptions
3
3
  const typeDefs = require('./type-defs.js')
4
4
  const typeDescription = t => {
5
- if (!t || typeof t !== 'function' && typeof t !== 'object')
5
+ if (!t || typeof t !== 'function' && typeof t !== 'object') {
6
6
  return t
7
+ }
7
8
 
8
- if (Array.isArray(t))
9
+ if (Array.isArray(t)) {
9
10
  return t.map(t => typeDescription(t))
11
+ }
10
12
 
11
13
  for (const { type, description } of Object.values(typeDefs)) {
12
- if (type === t)
14
+ if (type === t) {
13
15
  return description || type
16
+ }
14
17
  }
15
18
 
16
19
  return t
package/lib/umask.js CHANGED
@@ -1,18 +1,21 @@
1
1
  class Umask {}
2
2
  const parse = val => {
3
3
  if (typeof val === 'string') {
4
- if (/^0o?[0-7]+$/.test(val))
4
+ if (/^0o?[0-7]+$/.test(val)) {
5
5
  return parseInt(val.replace(/^0o?/, ''), 8)
6
- else if (/^[1-9][0-9]*$/.test(val))
6
+ } else if (/^[1-9][0-9]*$/.test(val)) {
7
7
  return parseInt(val, 10)
8
- else
8
+ } else {
9
9
  throw new Error(`invalid umask value: ${val}`)
10
+ }
10
11
  }
11
- if (typeof val !== 'number')
12
+ if (typeof val !== 'number') {
12
13
  throw new Error(`invalid umask value: ${val}`)
14
+ }
13
15
  val = Math.floor(val)
14
- if (val < 0 || val > 511)
16
+ if (val < 0 || val > 511) {
15
17
  throw new Error(`invalid umask value: ${val}`)
18
+ }
16
19
  return val
17
20
  }
18
21
 
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@npmcli/config",
3
- "version": "2.3.2",
3
+ "version": "4.0.0",
4
4
  "files": [
5
+ "bin",
5
6
  "lib"
6
7
  ],
7
8
  "main": "lib/index.js",
@@ -10,30 +11,42 @@
10
11
  "type": "git",
11
12
  "url": "git+https://github.com/npm/config"
12
13
  },
13
- "author": "Isaac Z. Schlueter <i@izs.me> (https://izs.me)",
14
+ "author": "GitHub Inc.",
14
15
  "license": "ISC",
15
16
  "scripts": {
16
17
  "test": "tap",
17
18
  "snap": "tap",
18
19
  "preversion": "npm test",
19
20
  "postversion": "npm publish",
20
- "prepublishOnly": "git push origin --follow-tags"
21
+ "prepublishOnly": "git push origin --follow-tags",
22
+ "lint": "eslint '**/*.js'",
23
+ "postlint": "npm-template-check",
24
+ "lintfix": "npm run lint -- --fix",
25
+ "posttest": "npm run lint",
26
+ "template-copy": "npm-template-copy --force"
21
27
  },
22
28
  "tap": {
23
29
  "check-coverage": true,
24
30
  "coverage-map": "map.js"
25
31
  },
26
32
  "devDependencies": {
27
- "tap": "^15.0.4"
33
+ "@npmcli/template-oss": "^2.7.1",
34
+ "tap": "^15.1.6"
28
35
  },
29
36
  "dependencies": {
37
+ "@npmcli/map-workspaces": "^2.0.0",
30
38
  "ini": "^2.0.0",
31
39
  "mkdirp-infer-owner": "^2.0.0",
32
40
  "nopt": "^5.0.0",
33
- "semver": "^7.3.4",
41
+ "proc-log": "^2.0.0",
42
+ "read-package-json-fast": "^2.0.3",
43
+ "semver": "^7.3.5",
34
44
  "walk-up-path": "^1.0.0"
35
45
  },
36
46
  "engines": {
37
- "node": ">=10"
47
+ "node": "^12.13.0 || ^14.15.0 || >=16"
48
+ },
49
+ "templateOSS": {
50
+ "version": "2.7.1"
38
51
  }
39
52
  }
package/lib/proc-log.js DELETED
@@ -1,4 +0,0 @@
1
- const log = (level) => (...args) => process.emit('log', level, ...args)
2
- for (const level of ['silly', 'verbose', 'warn', 'error']) {
3
- exports[level] = log(level)
4
- }