@npmcli/config 2.3.0 → 3.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 +23 -0
- package/lib/env-replace.js +3 -2
- package/lib/index.js +171 -59
- package/lib/parse-field.js +14 -8
- package/lib/set-envs.js +18 -11
- package/lib/type-defs.js +4 -2
- package/lib/type-description.js +6 -3
- package/lib/umask.js +8 -5
- package/package.json +16 -4
package/README.md
CHANGED
|
@@ -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
|
package/lib/env-replace.js
CHANGED
|
@@ -3,11 +3,12 @@
|
|
|
3
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] :
|
|
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,8 @@ 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')
|
|
6
8
|
|
|
7
9
|
/* istanbul ignore next */
|
|
8
10
|
const myUid = process.getuid && process.getuid()
|
|
@@ -91,7 +93,6 @@ class Config {
|
|
|
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
|
|
@@ -162,41 +164,48 @@ class Config {
|
|
|
162
164
|
|
|
163
165
|
// return the location where key is found.
|
|
164
166
|
find (key) {
|
|
165
|
-
if (!this.loaded)
|
|
167
|
+
if (!this.loaded) {
|
|
166
168
|
throw new Error('call config.load() before reading values')
|
|
169
|
+
}
|
|
167
170
|
return this[_find](key)
|
|
168
171
|
}
|
|
172
|
+
|
|
169
173
|
[_find] (key) {
|
|
170
174
|
// have to look in reverse order
|
|
171
175
|
const entries = [...this.data.entries()]
|
|
172
176
|
for (let i = entries.length - 1; i > -1; i--) {
|
|
173
177
|
const [where, { data }] = entries[i]
|
|
174
|
-
if (hasOwnProperty(data, key))
|
|
178
|
+
if (hasOwnProperty(data, key)) {
|
|
175
179
|
return where
|
|
180
|
+
}
|
|
176
181
|
}
|
|
177
182
|
return null
|
|
178
183
|
}
|
|
179
184
|
|
|
180
185
|
get (key, where) {
|
|
181
|
-
if (!this.loaded)
|
|
186
|
+
if (!this.loaded) {
|
|
182
187
|
throw new Error('call config.load() before reading values')
|
|
188
|
+
}
|
|
183
189
|
return this[_get](key, where)
|
|
184
190
|
}
|
|
191
|
+
|
|
185
192
|
// we need to get values sometimes, so use this internal one to do so
|
|
186
193
|
// while in the process of loading.
|
|
187
194
|
[_get] (key, where = null) {
|
|
188
195
|
if (where !== null && !confTypes.has(where)) {
|
|
189
196
|
throw new Error('invalid config location param: ' + where)
|
|
190
197
|
}
|
|
191
|
-
const { data
|
|
198
|
+
const { data } = this.data.get(where || 'cli')
|
|
192
199
|
return where === null || hasOwnProperty(data, key) ? data[key] : undefined
|
|
193
200
|
}
|
|
194
201
|
|
|
195
202
|
set (key, val, where = 'cli') {
|
|
196
|
-
if (!this.loaded)
|
|
203
|
+
if (!this.loaded) {
|
|
197
204
|
throw new Error('call config.load() before setting values')
|
|
198
|
-
|
|
205
|
+
}
|
|
206
|
+
if (!confTypes.has(where)) {
|
|
199
207
|
throw new Error('invalid config location param: ' + where)
|
|
208
|
+
}
|
|
200
209
|
this[_checkDeprecated](key)
|
|
201
210
|
const { data } = this.data.get(where)
|
|
202
211
|
data[key] = val
|
|
@@ -209,8 +218,9 @@ class Config {
|
|
|
209
218
|
}
|
|
210
219
|
|
|
211
220
|
get flat () {
|
|
212
|
-
if (this[_flatOptions])
|
|
221
|
+
if (this[_flatOptions]) {
|
|
213
222
|
return this[_flatOptions]
|
|
223
|
+
}
|
|
214
224
|
|
|
215
225
|
// create the object for flat options passed to deps
|
|
216
226
|
process.emit('time', 'config:load:flatten')
|
|
@@ -225,16 +235,19 @@ class Config {
|
|
|
225
235
|
}
|
|
226
236
|
|
|
227
237
|
delete (key, where = 'cli') {
|
|
228
|
-
if (!this.loaded)
|
|
238
|
+
if (!this.loaded) {
|
|
229
239
|
throw new Error('call config.load() before deleting values')
|
|
230
|
-
|
|
240
|
+
}
|
|
241
|
+
if (!confTypes.has(where)) {
|
|
231
242
|
throw new Error('invalid config location param: ' + where)
|
|
243
|
+
}
|
|
232
244
|
delete this.data.get(where).data[key]
|
|
233
245
|
}
|
|
234
246
|
|
|
235
247
|
async load () {
|
|
236
|
-
if (this.loaded)
|
|
248
|
+
if (this.loaded) {
|
|
237
249
|
throw new Error('attempting to load npm config multiple times')
|
|
250
|
+
}
|
|
238
251
|
|
|
239
252
|
process.emit('time', 'config:load')
|
|
240
253
|
// first load the defaults, which sets the global prefix
|
|
@@ -282,7 +295,9 @@ class Config {
|
|
|
282
295
|
const creds = this.getCredentialsByURI(reg)
|
|
283
296
|
// ignore this error because a failed set will strip out anything that
|
|
284
297
|
// might be a security hazard, which was the intention.
|
|
285
|
-
try {
|
|
298
|
+
try {
|
|
299
|
+
this.setCredentialsByURI(reg, creds)
|
|
300
|
+
} catch (_) {}
|
|
286
301
|
process.emit('timeEnd', 'config:load:credentials')
|
|
287
302
|
|
|
288
303
|
// set proper globalPrefix now that everything is loaded
|
|
@@ -319,14 +334,16 @@ class Config {
|
|
|
319
334
|
}
|
|
320
335
|
|
|
321
336
|
loadHome () {
|
|
322
|
-
if (this.env.HOME)
|
|
337
|
+
if (this.env.HOME) {
|
|
323
338
|
return this.home = this.env.HOME
|
|
339
|
+
}
|
|
324
340
|
this.home = homedir()
|
|
325
341
|
}
|
|
326
342
|
|
|
327
343
|
loadGlobalPrefix () {
|
|
328
|
-
if (this.globalPrefix)
|
|
344
|
+
if (this.globalPrefix) {
|
|
329
345
|
throw new Error('cannot load default global prefix more than once')
|
|
346
|
+
}
|
|
330
347
|
|
|
331
348
|
if (this.env.PREFIX) {
|
|
332
349
|
this.globalPrefix = this.env.PREFIX
|
|
@@ -338,17 +355,18 @@ class Config {
|
|
|
338
355
|
this.globalPrefix = dirname(dirname(this.execPath))
|
|
339
356
|
|
|
340
357
|
// destdir only is respected on Unix
|
|
341
|
-
if (this.env.DESTDIR)
|
|
358
|
+
if (this.env.DESTDIR) {
|
|
342
359
|
this.globalPrefix = join(this.env.DESTDIR, this.globalPrefix)
|
|
360
|
+
}
|
|
343
361
|
}
|
|
344
362
|
}
|
|
345
363
|
|
|
346
364
|
loadEnv () {
|
|
347
|
-
const prefix = 'npm_config_'
|
|
348
365
|
const conf = Object.create(null)
|
|
349
366
|
for (const [envKey, envVal] of Object.entries(this.env)) {
|
|
350
|
-
if (!/^npm_config_/i.test(envKey) || envVal === '')
|
|
367
|
+
if (!/^npm_config_/i.test(envKey) || envVal === '') {
|
|
351
368
|
continue
|
|
369
|
+
}
|
|
352
370
|
const key = envKey.substr('npm_config_'.length)
|
|
353
371
|
.replace(/(?!^)_/g, '-') // don't replace _ at the start of the key
|
|
354
372
|
.toLowerCase()
|
|
@@ -368,9 +386,10 @@ class Config {
|
|
|
368
386
|
}
|
|
369
387
|
|
|
370
388
|
get valid () {
|
|
371
|
-
for (const [where, {valid}] of this.data.entries()) {
|
|
372
|
-
if (valid === false || valid === null && !this.validate(where))
|
|
389
|
+
for (const [where, { valid }] of this.data.entries()) {
|
|
390
|
+
if (valid === false || valid === null && !this.validate(where)) {
|
|
373
391
|
return false
|
|
392
|
+
}
|
|
374
393
|
}
|
|
375
394
|
return true
|
|
376
395
|
}
|
|
@@ -378,11 +397,12 @@ class Config {
|
|
|
378
397
|
validate (where) {
|
|
379
398
|
if (!where) {
|
|
380
399
|
let valid = true
|
|
381
|
-
for (const [where
|
|
400
|
+
for (const [where] of this.data.entries()) {
|
|
382
401
|
// no need to validate our defaults, we know they're fine
|
|
383
402
|
// cli was already validated when parsed the first time
|
|
384
|
-
if (where === 'default' || where === 'builtin' || where === 'cli')
|
|
403
|
+
if (where === 'default' || where === 'builtin' || where === 'cli') {
|
|
385
404
|
continue
|
|
405
|
+
}
|
|
386
406
|
const ret = this.validate(where)
|
|
387
407
|
valid = valid && ret
|
|
388
408
|
}
|
|
@@ -401,6 +421,20 @@ class Config {
|
|
|
401
421
|
}
|
|
402
422
|
}
|
|
403
423
|
|
|
424
|
+
// Returns true if the value is coming directly from the source defined
|
|
425
|
+
// in default definitions, if the current value for the key config is
|
|
426
|
+
// coming from any other different source, returns false
|
|
427
|
+
isDefault (key) {
|
|
428
|
+
const [defaultType, ...types] = [...confTypes]
|
|
429
|
+
const defaultData = this.data.get(defaultType).data
|
|
430
|
+
|
|
431
|
+
return hasOwnProperty(defaultData, key)
|
|
432
|
+
&& types.every(type => {
|
|
433
|
+
const typeData = this.data.get(type).data
|
|
434
|
+
return !hasOwnProperty(typeData, key)
|
|
435
|
+
})
|
|
436
|
+
}
|
|
437
|
+
|
|
404
438
|
invalidHandler (k, val, type, source, where) {
|
|
405
439
|
this.log.warn(
|
|
406
440
|
'invalid config',
|
|
@@ -410,14 +444,15 @@ class Config {
|
|
|
410
444
|
this.data.get(where)[_valid] = false
|
|
411
445
|
|
|
412
446
|
if (Array.isArray(type)) {
|
|
413
|
-
if (type.includes(typeDefs.url.type))
|
|
447
|
+
if (type.includes(typeDefs.url.type)) {
|
|
414
448
|
type = typeDefs.url.type
|
|
415
|
-
else {
|
|
449
|
+
} else {
|
|
416
450
|
/* istanbul ignore if - no actual configs matching this, but
|
|
417
451
|
* path types SHOULD be handled this way, like URLs, for the
|
|
418
452
|
* same reason */
|
|
419
|
-
if (type.includes(typeDefs.path.type))
|
|
453
|
+
if (type.includes(typeDefs.path.type)) {
|
|
420
454
|
type = typeDefs.path.type
|
|
455
|
+
}
|
|
421
456
|
}
|
|
422
457
|
}
|
|
423
458
|
|
|
@@ -455,15 +490,17 @@ class Config {
|
|
|
455
490
|
this.sources.set(source, where)
|
|
456
491
|
if (er) {
|
|
457
492
|
conf.loadError = er
|
|
458
|
-
if (er.code !== 'ENOENT')
|
|
493
|
+
if (er.code !== 'ENOENT') {
|
|
459
494
|
this.log.verbose('config', `error loading ${where} config`, er)
|
|
495
|
+
}
|
|
460
496
|
} else {
|
|
461
497
|
conf.raw = obj
|
|
462
498
|
for (const [key, value] of Object.entries(obj)) {
|
|
463
499
|
const k = envReplace(key, this.env)
|
|
464
500
|
const v = this.parseField(value, k)
|
|
465
|
-
if (where !== 'default')
|
|
501
|
+
if (where !== 'default') {
|
|
466
502
|
this[_checkDeprecated](k, where, obj, [key, value])
|
|
503
|
+
}
|
|
467
504
|
conf.data[k] = v
|
|
468
505
|
}
|
|
469
506
|
}
|
|
@@ -498,17 +535,25 @@ class Config {
|
|
|
498
535
|
|
|
499
536
|
async loadProjectConfig () {
|
|
500
537
|
// the localPrefix can be set by the CLI config, but otherwise is
|
|
501
|
-
// found by walking up the folder tree
|
|
538
|
+
// found by walking up the folder tree. either way, we load it before
|
|
539
|
+
// we return to make sure localPrefix is set
|
|
502
540
|
await this.loadLocalPrefix()
|
|
541
|
+
|
|
542
|
+
if (this[_get]('global') === true || this[_get]('location') === 'global') {
|
|
543
|
+
this.data.get('project').source = '(global mode enabled, ignored)'
|
|
544
|
+
this.sources.set(this.data.get('project').source, 'project')
|
|
545
|
+
return
|
|
546
|
+
}
|
|
547
|
+
|
|
503
548
|
const projectFile = resolve(this.localPrefix, '.npmrc')
|
|
504
549
|
// if we're in the ~ directory, and there happens to be a node_modules
|
|
505
550
|
// folder (which is not TOO uncommon, it turns out), then we can end
|
|
506
551
|
// up loading the "project" config where the "userconfig" will be,
|
|
507
552
|
// which causes some calamaties. So, we only load project config if
|
|
508
553
|
// it doesn't match what the userconfig will be.
|
|
509
|
-
if (projectFile !== this[_get]('userconfig'))
|
|
554
|
+
if (projectFile !== this[_get]('userconfig')) {
|
|
510
555
|
return this[_loadFile](projectFile, 'project')
|
|
511
|
-
else {
|
|
556
|
+
} else {
|
|
512
557
|
this.data.get('project').source = '(same as "user" config, ignored)'
|
|
513
558
|
this.sources.set(this.data.get('project').source, 'project')
|
|
514
559
|
}
|
|
@@ -521,23 +566,65 @@ class Config {
|
|
|
521
566
|
return
|
|
522
567
|
}
|
|
523
568
|
|
|
569
|
+
const cliWorkspaces = this[_get]('workspaces', 'cli')
|
|
570
|
+
|
|
524
571
|
for (const p of walkUp(this.cwd)) {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
if (hasAny) {
|
|
572
|
+
const hasNodeModules = await stat(resolve(p, 'node_modules'))
|
|
573
|
+
.then((st) => st.isDirectory())
|
|
574
|
+
.catch(() => false)
|
|
575
|
+
|
|
576
|
+
const hasPackageJson = await stat(resolve(p, 'package.json'))
|
|
577
|
+
.then((st) => st.isFile())
|
|
578
|
+
.catch(() => false)
|
|
579
|
+
|
|
580
|
+
if (!this.localPrefix && (hasNodeModules || hasPackageJson)) {
|
|
535
581
|
this.localPrefix = p
|
|
536
|
-
|
|
582
|
+
|
|
583
|
+
// if workspaces are disabled, return now
|
|
584
|
+
if (cliWorkspaces === false) {
|
|
585
|
+
return
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// otherwise, continue the loop
|
|
589
|
+
continue
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if (this.localPrefix && hasPackageJson) {
|
|
593
|
+
// if we already set localPrefix but this dir has a package.json
|
|
594
|
+
// then we need to see if `p` is a workspace root by reading its package.json
|
|
595
|
+
// however, if reading it fails then we should just move on
|
|
596
|
+
const pkg = await rpj(resolve(p, 'package.json')).catch(() => false)
|
|
597
|
+
if (!pkg) {
|
|
598
|
+
continue
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const workspaces = await mapWorkspaces({ cwd: p, pkg })
|
|
602
|
+
for (const w of workspaces.values()) {
|
|
603
|
+
if (w === this.localPrefix) {
|
|
604
|
+
// see if there's a .npmrc file in the workspace, if so log a warning
|
|
605
|
+
const hasNpmrc = await stat(resolve(this.localPrefix, '.npmrc'))
|
|
606
|
+
.then((st) => st.isFile())
|
|
607
|
+
.catch(() => false)
|
|
608
|
+
|
|
609
|
+
if (hasNpmrc) {
|
|
610
|
+
this.log.warn(`ignoring workspace config at ${this.localPrefix}/.npmrc`)
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// set the workspace in the default layer, which allows it to be overridden easily
|
|
614
|
+
const { data } = this.data.get('default')
|
|
615
|
+
data.workspace = [this.localPrefix]
|
|
616
|
+
this.localPrefix = p
|
|
617
|
+
this.log.info(`found workspace root at ${this.localPrefix}`)
|
|
618
|
+
// we found a root, so we return now
|
|
619
|
+
return
|
|
620
|
+
}
|
|
621
|
+
}
|
|
537
622
|
}
|
|
538
623
|
}
|
|
539
624
|
|
|
540
|
-
this.localPrefix
|
|
625
|
+
if (!this.localPrefix) {
|
|
626
|
+
this.localPrefix = this.cwd
|
|
627
|
+
}
|
|
541
628
|
}
|
|
542
629
|
|
|
543
630
|
loadUserConfig () {
|
|
@@ -549,10 +636,12 @@ class Config {
|
|
|
549
636
|
}
|
|
550
637
|
|
|
551
638
|
async save (where) {
|
|
552
|
-
if (!this.loaded)
|
|
639
|
+
if (!this.loaded) {
|
|
553
640
|
throw new Error('call config.load() before saving')
|
|
554
|
-
|
|
641
|
+
}
|
|
642
|
+
if (!confFileTypes.has(where)) {
|
|
555
643
|
throw new Error('invalid config location param: ' + where)
|
|
644
|
+
}
|
|
556
645
|
const conf = this.data.get(where)
|
|
557
646
|
conf[_raw] = { ...conf.data }
|
|
558
647
|
conf[_loadError] = null
|
|
@@ -564,7 +653,9 @@ class Config {
|
|
|
564
653
|
// we ignore this error because the failed set already removed
|
|
565
654
|
// anything that might be a security hazard, and it won't be
|
|
566
655
|
// saved back to the .npmrc file, so we're good.
|
|
567
|
-
try {
|
|
656
|
+
try {
|
|
657
|
+
this.setCredentialsByURI(reg, creds)
|
|
658
|
+
} catch (_) {}
|
|
568
659
|
}
|
|
569
660
|
|
|
570
661
|
const iniData = ini.stringify(conf.data).trim() + '\n'
|
|
@@ -580,8 +671,9 @@ class Config {
|
|
|
580
671
|
/* istanbul ignore if - this is best-effort and a pita to test */
|
|
581
672
|
if (myUid === 0) {
|
|
582
673
|
const st = await stat(dir).catch(() => null)
|
|
583
|
-
if (st && (st.uid !== myUid || st.gid !== myGid))
|
|
674
|
+
if (st && (st.uid !== myUid || st.gid !== myGid)) {
|
|
584
675
|
await chown(conf.source, st.uid, st.gid).catch(() => {})
|
|
676
|
+
}
|
|
585
677
|
}
|
|
586
678
|
const mode = where === 'user' ? 0o600 : 0o666
|
|
587
679
|
await chmod(conf.source, mode)
|
|
@@ -629,8 +721,9 @@ class Config {
|
|
|
629
721
|
email = email ||
|
|
630
722
|
this.get('email', 'user') ||
|
|
631
723
|
this.get(`${nerfed}:email`, 'user')
|
|
632
|
-
if (email)
|
|
724
|
+
if (email) {
|
|
633
725
|
this.set('email', email, 'user')
|
|
726
|
+
}
|
|
634
727
|
}
|
|
635
728
|
|
|
636
729
|
// field that hasn't been used as documented for a LONG time,
|
|
@@ -646,10 +739,12 @@ class Config {
|
|
|
646
739
|
this.delete(`${nerfed}:_password`, 'user')
|
|
647
740
|
this.delete(`${nerfed}:username`, 'user')
|
|
648
741
|
} else if (username || password) {
|
|
649
|
-
if (!username)
|
|
742
|
+
if (!username) {
|
|
650
743
|
throw new Error('must include username')
|
|
651
|
-
|
|
744
|
+
}
|
|
745
|
+
if (!password) {
|
|
652
746
|
throw new Error('must include password')
|
|
747
|
+
}
|
|
653
748
|
this.delete(`${nerfed}:_authToken`, 'user')
|
|
654
749
|
this.set(`${nerfed}:username`, username, 'user')
|
|
655
750
|
// note: not encrypted, no idea why we bothered to do this, but oh well
|
|
@@ -667,8 +762,9 @@ class Config {
|
|
|
667
762
|
const creds = {}
|
|
668
763
|
|
|
669
764
|
const email = this.get(`${nerfed}:email`) || this.get('email')
|
|
670
|
-
if (email)
|
|
765
|
+
if (email) {
|
|
671
766
|
creds.email = email
|
|
767
|
+
}
|
|
672
768
|
|
|
673
769
|
const tokenReg = this.get(`${nerfed}:_authToken`) ||
|
|
674
770
|
this.get(`${nerfed}:_authtoken`) ||
|
|
@@ -703,8 +799,9 @@ class Config {
|
|
|
703
799
|
// at this point, we can only use the values if the URI is the
|
|
704
800
|
// default registry.
|
|
705
801
|
const defaultNerf = nerfDart(this.get('registry'))
|
|
706
|
-
if (nerfed !== defaultNerf)
|
|
802
|
+
if (nerfed !== defaultNerf) {
|
|
707
803
|
return creds
|
|
804
|
+
}
|
|
708
805
|
|
|
709
806
|
const userDef = this.get('username')
|
|
710
807
|
const passDef = this.get('_password')
|
|
@@ -719,8 +816,9 @@ class Config {
|
|
|
719
816
|
// Handle the old-style _auth=<base64> style for the default
|
|
720
817
|
// registry, if set.
|
|
721
818
|
const auth = this.get('_auth')
|
|
722
|
-
if (!auth)
|
|
819
|
+
if (!auth) {
|
|
723
820
|
return creds
|
|
821
|
+
}
|
|
724
822
|
|
|
725
823
|
const authDecode = Buffer.from(auth, 'base64').toString('utf8')
|
|
726
824
|
const authSplit = authDecode.split(':')
|
|
@@ -733,7 +831,9 @@ class Config {
|
|
|
733
831
|
// set up the environment object we have with npm_config_* environs
|
|
734
832
|
// for all configs that are different from their default values, and
|
|
735
833
|
// set EDITOR and HOME.
|
|
736
|
-
setEnvs () {
|
|
834
|
+
setEnvs () {
|
|
835
|
+
setEnvs(this)
|
|
836
|
+
}
|
|
737
837
|
}
|
|
738
838
|
|
|
739
839
|
const _data = Symbol('data')
|
|
@@ -759,25 +859,37 @@ class ConfigData {
|
|
|
759
859
|
}
|
|
760
860
|
|
|
761
861
|
set source (s) {
|
|
762
|
-
if (this[_source])
|
|
862
|
+
if (this[_source]) {
|
|
763
863
|
throw new Error('cannot set ConfigData source more than once')
|
|
864
|
+
}
|
|
764
865
|
this[_source] = s
|
|
765
866
|
}
|
|
766
|
-
|
|
867
|
+
|
|
868
|
+
get source () {
|
|
869
|
+
return this[_source]
|
|
870
|
+
}
|
|
767
871
|
|
|
768
872
|
set loadError (e) {
|
|
769
|
-
if (this[_loadError] || this[_raw])
|
|
873
|
+
if (this[_loadError] || this[_raw]) {
|
|
770
874
|
throw new Error('cannot set ConfigData loadError after load')
|
|
875
|
+
}
|
|
771
876
|
this[_loadError] = e
|
|
772
877
|
}
|
|
773
|
-
|
|
878
|
+
|
|
879
|
+
get loadError () {
|
|
880
|
+
return this[_loadError]
|
|
881
|
+
}
|
|
774
882
|
|
|
775
883
|
set raw (r) {
|
|
776
|
-
if (this[_raw] || this[_loadError])
|
|
884
|
+
if (this[_raw] || this[_loadError]) {
|
|
777
885
|
throw new Error('cannot set ConfigData raw after load')
|
|
886
|
+
}
|
|
778
887
|
this[_raw] = r
|
|
779
888
|
}
|
|
780
|
-
|
|
889
|
+
|
|
890
|
+
get raw () {
|
|
891
|
+
return this[_raw]
|
|
892
|
+
}
|
|
781
893
|
}
|
|
782
894
|
|
|
783
895
|
module.exports = Config
|
package/lib/parse-field.js
CHANGED
|
@@ -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,
|
|
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
|
|
package/lib/type-description.js
CHANGED
|
@@ -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": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"files": [
|
|
5
|
+
"bin",
|
|
5
6
|
"lib"
|
|
6
7
|
],
|
|
7
8
|
"main": "lib/index.js",
|
|
@@ -10,30 +11,41 @@
|
|
|
10
11
|
"type": "git",
|
|
11
12
|
"url": "git+https://github.com/npm/config"
|
|
12
13
|
},
|
|
13
|
-
"author": "
|
|
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": {
|
|
33
|
+
"@npmcli/template-oss": "^2.5.1",
|
|
27
34
|
"tap": "^15.0.4"
|
|
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",
|
|
41
|
+
"read-package-json-fast": "^2.0.3",
|
|
33
42
|
"semver": "^7.3.4",
|
|
34
43
|
"walk-up-path": "^1.0.0"
|
|
35
44
|
},
|
|
36
45
|
"engines": {
|
|
37
|
-
"node": ">=
|
|
46
|
+
"node": "^12.13.0 || ^14.15.0 || >=16"
|
|
47
|
+
},
|
|
48
|
+
"templateOSS": {
|
|
49
|
+
"version": "2.6.0"
|
|
38
50
|
}
|
|
39
51
|
}
|