@npmcli/config 6.1.5 → 6.1.7

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.
Files changed (2) hide show
  1. package/lib/index.js +84 -78
  2. package/package.json +5 -5
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // TODO: set the scope config from package.json or explicit cli config
2
- const walkUp = require('walk-up-path')
2
+ const { walkUp } = require('walk-up-path')
3
3
  const ini = require('ini')
4
4
  const nopt = require('nopt')
5
5
  const mapWorkspaces = require('@npmcli/map-workspaces')
@@ -72,16 +72,12 @@ const confTypes = new Set([
72
72
  'cli',
73
73
  ])
74
74
 
75
- const _loaded = Symbol('loaded')
76
- const _get = Symbol('get')
77
- const _find = Symbol('find')
78
- const _loadObject = Symbol('loadObject')
79
- const _loadFile = Symbol('loadFile')
80
- const _checkDeprecated = Symbol('checkDeprecated')
81
- const _flatten = Symbol('flatten')
82
- const _flatOptions = Symbol('flatOptions')
83
-
84
75
  class Config {
76
+ #loaded = false
77
+ #flatten
78
+ // populated the first time we flatten the object
79
+ #flatOptions = null
80
+
85
81
  static get typeDefs () {
86
82
  return typeDefs
87
83
  }
@@ -113,9 +109,7 @@ class Config {
113
109
  }
114
110
  }
115
111
 
116
- // populated the first time we flatten the object
117
- this[_flatOptions] = null
118
- this[_flatten] = flatten
112
+ this.#flatten = flatten
119
113
  this.types = types
120
114
  this.shorthands = shorthands
121
115
  this.defaults = defaults
@@ -159,15 +153,15 @@ class Config {
159
153
  }
160
154
  Object.freeze(this.list)
161
155
 
162
- this[_loaded] = false
156
+ this.#loaded = false
163
157
  }
164
158
 
165
159
  get loaded () {
166
- return this[_loaded]
160
+ return this.#loaded
167
161
  }
168
162
 
169
163
  get prefix () {
170
- return this[_get]('global') ? this.globalPrefix : this.localPrefix
164
+ return this.#get('global') ? this.globalPrefix : this.localPrefix
171
165
  }
172
166
 
173
167
  // return the location where key is found.
@@ -175,10 +169,7 @@ class Config {
175
169
  if (!this.loaded) {
176
170
  throw new Error('call config.load() before reading values')
177
171
  }
178
- return this[_find](key)
179
- }
180
172
 
181
- [_find] (key) {
182
173
  // have to look in reverse order
183
174
  const entries = [...this.data.entries()]
184
175
  for (let i = entries.length - 1; i > -1; i--) {
@@ -194,12 +185,12 @@ class Config {
194
185
  if (!this.loaded) {
195
186
  throw new Error('call config.load() before reading values')
196
187
  }
197
- return this[_get](key, where)
188
+ return this.#get(key, where)
198
189
  }
199
190
 
200
191
  // we need to get values sometimes, so use this internal one to do so
201
192
  // while in the process of loading.
202
- [_get] (key, where = null) {
193
+ #get (key, where = null) {
203
194
  if (where !== null && !confTypes.has(where)) {
204
195
  throw new Error('invalid config location param: ' + where)
205
196
  }
@@ -214,32 +205,35 @@ class Config {
214
205
  if (!confTypes.has(where)) {
215
206
  throw new Error('invalid config location param: ' + where)
216
207
  }
217
- this[_checkDeprecated](key)
218
- const { data } = this.data.get(where)
208
+ this.#checkDeprecated(key)
209
+ const { data, raw } = this.data.get(where)
219
210
  data[key] = val
211
+ if (['global', 'user', 'project'].includes(where)) {
212
+ raw[key] = val
213
+ }
220
214
 
221
215
  // this is now dirty, the next call to this.valid will have to check it
222
216
  this.data.get(where)[_valid] = null
223
217
 
224
218
  // the flat options are invalidated, regenerate next time they're needed
225
- this[_flatOptions] = null
219
+ this.#flatOptions = null
226
220
  }
227
221
 
228
222
  get flat () {
229
- if (this[_flatOptions]) {
230
- return this[_flatOptions]
223
+ if (this.#flatOptions) {
224
+ return this.#flatOptions
231
225
  }
232
226
 
233
227
  // create the object for flat options passed to deps
234
228
  process.emit('time', 'config:load:flatten')
235
- this[_flatOptions] = {}
229
+ this.#flatOptions = {}
236
230
  // walk from least priority to highest
237
231
  for (const { data } of this.data.values()) {
238
- this[_flatten](data, this[_flatOptions])
232
+ this.#flatten(data, this.#flatOptions)
239
233
  }
240
234
  process.emit('timeEnd', 'config:load:flatten')
241
235
 
242
- return this[_flatOptions]
236
+ return this.#flatOptions
243
237
  }
244
238
 
245
239
  delete (key, where = 'cli') {
@@ -249,7 +243,11 @@ class Config {
249
243
  if (!confTypes.has(where)) {
250
244
  throw new Error('invalid config location param: ' + where)
251
245
  }
252
- delete this.data.get(where).data[key]
246
+ const { data, raw } = this.data.get(where)
247
+ delete data[key]
248
+ if (['global', 'user', 'project'].includes(where)) {
249
+ delete raw[key]
250
+ }
253
251
  }
254
252
 
255
253
  async load () {
@@ -290,8 +288,8 @@ class Config {
290
288
  process.emit('timeEnd', 'config:load:global')
291
289
 
292
290
  // set this before calling setEnvs, so that we don't have to share
293
- // symbols, as that module also does a bunch of get operations
294
- this[_loaded] = true
291
+ // private attributes, as that module also does a bunch of get operations
292
+ this.#loaded = true
295
293
 
296
294
  // set proper globalPrefix now that everything is loaded
297
295
  this.globalPrefix = this.get('prefix')
@@ -307,7 +305,7 @@ class Config {
307
305
  this.loadGlobalPrefix()
308
306
  this.loadHome()
309
307
 
310
- this[_loadObject]({
308
+ this.#loadObject({
311
309
  ...this.defaults,
312
310
  prefix: this.globalPrefix,
313
311
  }, 'default', 'default values')
@@ -316,13 +314,13 @@ class Config {
316
314
 
317
315
  // the metrics-registry defaults to the current resolved value of
318
316
  // the registry, unless overridden somewhere else.
319
- settableGetter(data, 'metrics-registry', () => this[_get]('registry'))
317
+ settableGetter(data, 'metrics-registry', () => this.#get('registry'))
320
318
 
321
319
  // if the prefix is set on cli, env, or userconfig, then we need to
322
320
  // default the globalconfig file to that location, instead of the default
323
321
  // global prefix. It's weird that `npm get globalconfig --prefix=/foo`
324
322
  // returns `/foo/etc/npmrc`, but better to not change it at this point.
325
- settableGetter(data, 'globalconfig', () => resolve(this[_get]('prefix'), 'etc/npmrc'))
323
+ settableGetter(data, 'globalconfig', () => resolve(this.#get('prefix'), 'etc/npmrc'))
326
324
  }
327
325
 
328
326
  loadHome () {
@@ -363,7 +361,7 @@ class Config {
363
361
  }
364
362
  conf[key] = envVal
365
363
  }
366
- this[_loadObject](conf, 'env', 'environment')
364
+ this.#loadObject(conf, 'env', 'environment')
367
365
  }
368
366
 
369
367
  loadCLI () {
@@ -373,7 +371,7 @@ class Config {
373
371
  nopt.invalidHandler = null
374
372
  this.parsedArgv = conf.argv
375
373
  delete conf.argv
376
- this[_loadObject](conf, 'cli', 'command line options')
374
+ this.#loadObject(conf, 'cli', 'command line options')
377
375
  }
378
376
 
379
377
  get valid () {
@@ -526,22 +524,30 @@ class Config {
526
524
  }
527
525
 
528
526
  const typeDesc = typeDescription(type)
529
- const oneOrMore = typeDesc.indexOf(Array) !== -1
530
527
  const mustBe = typeDesc
531
528
  .filter(m => m !== undefined && m !== Array)
532
- const oneOf = mustBe.length === 1 && oneOrMore ? ' one or more'
533
- : mustBe.length > 1 && oneOrMore ? ' one or more of:'
534
- : mustBe.length > 1 ? ' one of:'
535
- : ''
536
- const msg = 'Must be' + oneOf
529
+ const msg = 'Must be' + this.#getOneOfKeywords(mustBe, typeDesc)
537
530
  const desc = mustBe.length === 1 ? mustBe[0]
538
- : mustBe.filter(m => m !== Array)
539
- .map(n => typeof n === 'string' ? n : JSON.stringify(n))
540
- .join(', ')
531
+ : [...new Set(mustBe.map(n => typeof n === 'string' ? n : JSON.stringify(n)))].join(', ')
541
532
  log.warn('invalid config', msg, desc)
542
533
  }
543
534
 
544
- [_loadObject] (obj, where, source, er = null) {
535
+ #getOneOfKeywords (mustBe, typeDesc) {
536
+ let keyword
537
+ if (mustBe.length === 1 && typeDesc.includes(Array)) {
538
+ keyword = ' one or more'
539
+ } else if (mustBe.length > 1 && typeDesc.includes(Array)) {
540
+ keyword = ' one or more of:'
541
+ } else if (mustBe.length > 1) {
542
+ keyword = ' one of:'
543
+ } else {
544
+ keyword = ''
545
+ }
546
+ return keyword
547
+ }
548
+
549
+ #loadObject (obj, where, source, er = null) {
550
+ // obj is the raw data read from the file
545
551
  const conf = this.data.get(where)
546
552
  if (conf.source) {
547
553
  const m = `double-loading "${where}" configs from ${source}, ` +
@@ -568,14 +574,14 @@ class Config {
568
574
  const k = envReplace(key, this.env)
569
575
  const v = this.parseField(value, k)
570
576
  if (where !== 'default') {
571
- this[_checkDeprecated](k, where, obj, [key, value])
577
+ this.#checkDeprecated(k, where, obj, [key, value])
572
578
  }
573
579
  conf.data[k] = v
574
580
  }
575
581
  }
576
582
  }
577
583
 
578
- [_checkDeprecated] (key, where, obj, kv) {
584
+ #checkDeprecated (key, where, obj, kv) {
579
585
  // XXX(npm9+) make this throw an error
580
586
  if (this.deprecated[key]) {
581
587
  log.warn('config', key, this.deprecated[key])
@@ -587,18 +593,18 @@ class Config {
587
593
  return parseField(f, key, this, listElement)
588
594
  }
589
595
 
590
- async [_loadFile] (file, type) {
596
+ async #loadFile (file, type) {
591
597
  process.emit('time', 'config:load:file:' + file)
592
598
  // only catch the error from readFile, not from the loadObject call
593
599
  await readFile(file, 'utf8').then(
594
- data => this[_loadObject](ini.parse(data), type, file),
595
- er => this[_loadObject](null, type, file, er)
600
+ data => this.#loadObject(ini.parse(data), type, file),
601
+ er => this.#loadObject(null, type, file, er)
596
602
  )
597
603
  process.emit('timeEnd', 'config:load:file:' + file)
598
604
  }
599
605
 
600
606
  loadBuiltinConfig () {
601
- return this[_loadFile](resolve(this.npmPath, 'npmrc'), 'builtin')
607
+ return this.#loadFile(resolve(this.npmPath, 'npmrc'), 'builtin')
602
608
  }
603
609
 
604
610
  async loadProjectConfig () {
@@ -613,7 +619,7 @@ class Config {
613
619
  this.localPackage = await fileExists(this.localPrefix, 'package.json')
614
620
  }
615
621
 
616
- if (this[_get]('global') === true || this[_get]('location') === 'global') {
622
+ if (this.#get('global') === true || this.#get('location') === 'global') {
617
623
  this.data.get('project').source = '(global mode enabled, ignored)'
618
624
  this.sources.set(this.data.get('project').source, 'project')
619
625
  return
@@ -625,8 +631,8 @@ class Config {
625
631
  // up loading the "project" config where the "userconfig" will be,
626
632
  // which causes some calamaties. So, we only load project config if
627
633
  // it doesn't match what the userconfig will be.
628
- if (projectFile !== this[_get]('userconfig')) {
629
- return this[_loadFile](projectFile, 'project')
634
+ if (projectFile !== this.#get('userconfig')) {
635
+ return this.#loadFile(projectFile, 'project')
630
636
  } else {
631
637
  this.data.get('project').source = '(same as "user" config, ignored)'
632
638
  this.sources.set(this.data.get('project').source, 'project')
@@ -634,14 +640,14 @@ class Config {
634
640
  }
635
641
 
636
642
  async loadLocalPrefix () {
637
- const cliPrefix = this[_get]('prefix', 'cli')
643
+ const cliPrefix = this.#get('prefix', 'cli')
638
644
  if (cliPrefix) {
639
645
  this.localPrefix = cliPrefix
640
646
  return
641
647
  }
642
648
 
643
- const cliWorkspaces = this[_get]('workspaces', 'cli')
644
- const isGlobal = this[_get]('global') || this[_get]('location') === 'global'
649
+ const cliWorkspaces = this.#get('workspaces', 'cli')
650
+ const isGlobal = this.#get('global') || this.#get('location') === 'global'
645
651
 
646
652
  for (const p of walkUp(this.cwd)) {
647
653
  // HACK: this is an option set in tests to stop the local prefix from being set
@@ -701,11 +707,11 @@ class Config {
701
707
  }
702
708
 
703
709
  loadUserConfig () {
704
- return this[_loadFile](this[_get]('userconfig'), 'user')
710
+ return this.#loadFile(this.#get('userconfig'), 'user')
705
711
  }
706
712
 
707
713
  loadGlobalConfig () {
708
- return this[_loadFile](this[_get]('globalconfig'), 'global')
714
+ return this.#loadFile(this.#get('globalconfig'), 'global')
709
715
  }
710
716
 
711
717
  async save (where) {
@@ -717,7 +723,6 @@ class Config {
717
723
  }
718
724
 
719
725
  const conf = this.data.get(where)
720
- conf[_raw] = { ...conf.data }
721
726
  conf[_loadError] = null
722
727
 
723
728
  if (where === 'user') {
@@ -730,7 +735,9 @@ class Config {
730
735
  }
731
736
  }
732
737
 
733
- const iniData = ini.stringify(conf.data).trim() + '\n'
738
+ // We need the actual raw data before we called parseField so that we are
739
+ // saving the same content back to the file
740
+ const iniData = ini.stringify(conf.raw).trim() + '\n'
734
741
  if (!iniData.trim()) {
735
742
  // ignore the unlink error (eg, if file doesn't exist)
736
743
  await unlink(conf.source).catch(er => {})
@@ -870,22 +877,21 @@ class Config {
870
877
  }
871
878
  }
872
879
 
873
- const _data = Symbol('data')
874
- const _raw = Symbol('raw')
875
880
  const _loadError = Symbol('loadError')
876
- const _source = Symbol('source')
877
881
  const _valid = Symbol('valid')
882
+
878
883
  class ConfigData {
884
+ #data
885
+ #source = null
886
+ #raw = null
879
887
  constructor (parent) {
880
- this[_data] = Object.create(parent && parent.data)
881
- this[_source] = null
882
- this[_loadError] = null
883
- this[_raw] = null
888
+ this.#data = Object.create(parent && parent.data)
889
+ this.#raw = {}
884
890
  this[_valid] = true
885
891
  }
886
892
 
887
893
  get data () {
888
- return this[_data]
894
+ return this.#data
889
895
  }
890
896
 
891
897
  get valid () {
@@ -893,18 +899,18 @@ class ConfigData {
893
899
  }
894
900
 
895
901
  set source (s) {
896
- if (this[_source]) {
902
+ if (this.#source) {
897
903
  throw new Error('cannot set ConfigData source more than once')
898
904
  }
899
- this[_source] = s
905
+ this.#source = s
900
906
  }
901
907
 
902
908
  get source () {
903
- return this[_source]
909
+ return this.#source
904
910
  }
905
911
 
906
912
  set loadError (e) {
907
- if (this[_loadError] || this[_raw]) {
913
+ if (this[_loadError] || (Object.keys(this.#raw).length)) {
908
914
  throw new Error('cannot set ConfigData loadError after load')
909
915
  }
910
916
  this[_loadError] = e
@@ -915,14 +921,14 @@ class ConfigData {
915
921
  }
916
922
 
917
923
  set raw (r) {
918
- if (this[_raw] || this[_loadError]) {
924
+ if (Object.keys(this.#raw).length || this[_loadError]) {
919
925
  throw new Error('cannot set ConfigData raw after load')
920
926
  }
921
- this[_raw] = r
927
+ this.#raw = r
922
928
  }
923
929
 
924
930
  get raw () {
925
- return this[_raw]
931
+ return this.#raw
926
932
  }
927
933
  }
928
934
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@npmcli/config",
3
- "version": "6.1.5",
3
+ "version": "6.1.7",
4
4
  "files": [
5
5
  "bin/",
6
6
  "lib/"
@@ -33,23 +33,23 @@
33
33
  },
34
34
  "devDependencies": {
35
35
  "@npmcli/eslint-config": "^4.0.0",
36
- "@npmcli/template-oss": "4.12.1",
36
+ "@npmcli/template-oss": "4.14.1",
37
37
  "tap": "^16.3.4"
38
38
  },
39
39
  "dependencies": {
40
40
  "@npmcli/map-workspaces": "^3.0.2",
41
- "ini": "^3.0.0",
41
+ "ini": "^4.1.0",
42
42
  "nopt": "^7.0.0",
43
43
  "proc-log": "^3.0.0",
44
44
  "read-package-json-fast": "^3.0.2",
45
45
  "semver": "^7.3.5",
46
- "walk-up-path": "^1.0.0"
46
+ "walk-up-path": "^3.0.1"
47
47
  },
48
48
  "engines": {
49
49
  "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
50
50
  },
51
51
  "templateOSS": {
52
52
  "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
53
- "version": "4.12.1"
53
+ "version": "4.14.1"
54
54
  }
55
55
  }