@npmcli/config 2.3.1 → 3.0.1
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 +24 -1
- package/lib/env-replace.js +3 -2
- package/lib/index.js +168 -62
- 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 +19 -6
- package/lib/proc-log.js +0 -4
package/README.md
CHANGED
|
@@ -80,7 +80,7 @@ const conf = new Config({
|
|
|
80
80
|
cwd: process.cwd(),
|
|
81
81
|
// optional, defaults to emitting 'log' events on process object
|
|
82
82
|
// only silly, verbose, warn, and error are logged by this module
|
|
83
|
-
log: require('
|
|
83
|
+
log: require('proc-log')
|
|
84
84
|
})
|
|
85
85
|
|
|
86
86
|
// returns a promise that fails if config loading fails, and
|
|
@@ -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()
|
|
@@ -86,12 +88,11 @@ class Config {
|
|
|
86
88
|
// options just to override in tests, mostly
|
|
87
89
|
env = process.env,
|
|
88
90
|
argv = process.argv,
|
|
89
|
-
log = require('
|
|
91
|
+
log = require('proc-log'),
|
|
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
|
|
@@ -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
|
}
|
|
@@ -497,24 +534,26 @@ class Config {
|
|
|
497
534
|
}
|
|
498
535
|
|
|
499
536
|
async loadProjectConfig () {
|
|
537
|
+
// the localPrefix can be set by the CLI config, but otherwise is
|
|
538
|
+
// found by walking up the folder tree. either way, we load it before
|
|
539
|
+
// we return to make sure localPrefix is set
|
|
540
|
+
await this.loadLocalPrefix()
|
|
541
|
+
|
|
500
542
|
if (this[_get]('global') === true || this[_get]('location') === 'global') {
|
|
501
543
|
this.data.get('project').source = '(global mode enabled, ignored)'
|
|
502
544
|
this.sources.set(this.data.get('project').source, 'project')
|
|
503
545
|
return
|
|
504
546
|
}
|
|
505
547
|
|
|
506
|
-
// the localPrefix can be set by the CLI config, but otherwise is
|
|
507
|
-
// found by walking up the folder tree
|
|
508
|
-
await this.loadLocalPrefix()
|
|
509
548
|
const projectFile = resolve(this.localPrefix, '.npmrc')
|
|
510
549
|
// if we're in the ~ directory, and there happens to be a node_modules
|
|
511
550
|
// folder (which is not TOO uncommon, it turns out), then we can end
|
|
512
551
|
// up loading the "project" config where the "userconfig" will be,
|
|
513
552
|
// which causes some calamaties. So, we only load project config if
|
|
514
553
|
// it doesn't match what the userconfig will be.
|
|
515
|
-
if (projectFile !== this[_get]('userconfig'))
|
|
554
|
+
if (projectFile !== this[_get]('userconfig')) {
|
|
516
555
|
return this[_loadFile](projectFile, 'project')
|
|
517
|
-
else {
|
|
556
|
+
} else {
|
|
518
557
|
this.data.get('project').source = '(same as "user" config, ignored)'
|
|
519
558
|
this.sources.set(this.data.get('project').source, 'project')
|
|
520
559
|
}
|
|
@@ -527,23 +566,65 @@ class Config {
|
|
|
527
566
|
return
|
|
528
567
|
}
|
|
529
568
|
|
|
569
|
+
const cliWorkspaces = this[_get]('workspaces', 'cli')
|
|
570
|
+
|
|
530
571
|
for (const p of walkUp(this.cwd)) {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
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)) {
|
|
541
581
|
this.localPrefix = p
|
|
542
|
-
|
|
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
|
+
}
|
|
543
622
|
}
|
|
544
623
|
}
|
|
545
624
|
|
|
546
|
-
this.localPrefix
|
|
625
|
+
if (!this.localPrefix) {
|
|
626
|
+
this.localPrefix = this.cwd
|
|
627
|
+
}
|
|
547
628
|
}
|
|
548
629
|
|
|
549
630
|
loadUserConfig () {
|
|
@@ -555,10 +636,12 @@ class Config {
|
|
|
555
636
|
}
|
|
556
637
|
|
|
557
638
|
async save (where) {
|
|
558
|
-
if (!this.loaded)
|
|
639
|
+
if (!this.loaded) {
|
|
559
640
|
throw new Error('call config.load() before saving')
|
|
560
|
-
|
|
641
|
+
}
|
|
642
|
+
if (!confFileTypes.has(where)) {
|
|
561
643
|
throw new Error('invalid config location param: ' + where)
|
|
644
|
+
}
|
|
562
645
|
const conf = this.data.get(where)
|
|
563
646
|
conf[_raw] = { ...conf.data }
|
|
564
647
|
conf[_loadError] = null
|
|
@@ -570,7 +653,9 @@ class Config {
|
|
|
570
653
|
// we ignore this error because the failed set already removed
|
|
571
654
|
// anything that might be a security hazard, and it won't be
|
|
572
655
|
// saved back to the .npmrc file, so we're good.
|
|
573
|
-
try {
|
|
656
|
+
try {
|
|
657
|
+
this.setCredentialsByURI(reg, creds)
|
|
658
|
+
} catch (_) {}
|
|
574
659
|
}
|
|
575
660
|
|
|
576
661
|
const iniData = ini.stringify(conf.data).trim() + '\n'
|
|
@@ -586,8 +671,9 @@ class Config {
|
|
|
586
671
|
/* istanbul ignore if - this is best-effort and a pita to test */
|
|
587
672
|
if (myUid === 0) {
|
|
588
673
|
const st = await stat(dir).catch(() => null)
|
|
589
|
-
if (st && (st.uid !== myUid || st.gid !== myGid))
|
|
674
|
+
if (st && (st.uid !== myUid || st.gid !== myGid)) {
|
|
590
675
|
await chown(conf.source, st.uid, st.gid).catch(() => {})
|
|
676
|
+
}
|
|
591
677
|
}
|
|
592
678
|
const mode = where === 'user' ? 0o600 : 0o666
|
|
593
679
|
await chmod(conf.source, mode)
|
|
@@ -635,8 +721,9 @@ class Config {
|
|
|
635
721
|
email = email ||
|
|
636
722
|
this.get('email', 'user') ||
|
|
637
723
|
this.get(`${nerfed}:email`, 'user')
|
|
638
|
-
if (email)
|
|
724
|
+
if (email) {
|
|
639
725
|
this.set('email', email, 'user')
|
|
726
|
+
}
|
|
640
727
|
}
|
|
641
728
|
|
|
642
729
|
// field that hasn't been used as documented for a LONG time,
|
|
@@ -652,10 +739,12 @@ class Config {
|
|
|
652
739
|
this.delete(`${nerfed}:_password`, 'user')
|
|
653
740
|
this.delete(`${nerfed}:username`, 'user')
|
|
654
741
|
} else if (username || password) {
|
|
655
|
-
if (!username)
|
|
742
|
+
if (!username) {
|
|
656
743
|
throw new Error('must include username')
|
|
657
|
-
|
|
744
|
+
}
|
|
745
|
+
if (!password) {
|
|
658
746
|
throw new Error('must include password')
|
|
747
|
+
}
|
|
659
748
|
this.delete(`${nerfed}:_authToken`, 'user')
|
|
660
749
|
this.set(`${nerfed}:username`, username, 'user')
|
|
661
750
|
// note: not encrypted, no idea why we bothered to do this, but oh well
|
|
@@ -673,8 +762,9 @@ class Config {
|
|
|
673
762
|
const creds = {}
|
|
674
763
|
|
|
675
764
|
const email = this.get(`${nerfed}:email`) || this.get('email')
|
|
676
|
-
if (email)
|
|
765
|
+
if (email) {
|
|
677
766
|
creds.email = email
|
|
767
|
+
}
|
|
678
768
|
|
|
679
769
|
const tokenReg = this.get(`${nerfed}:_authToken`) ||
|
|
680
770
|
this.get(`${nerfed}:_authtoken`) ||
|
|
@@ -709,8 +799,9 @@ class Config {
|
|
|
709
799
|
// at this point, we can only use the values if the URI is the
|
|
710
800
|
// default registry.
|
|
711
801
|
const defaultNerf = nerfDart(this.get('registry'))
|
|
712
|
-
if (nerfed !== defaultNerf)
|
|
802
|
+
if (nerfed !== defaultNerf) {
|
|
713
803
|
return creds
|
|
804
|
+
}
|
|
714
805
|
|
|
715
806
|
const userDef = this.get('username')
|
|
716
807
|
const passDef = this.get('_password')
|
|
@@ -725,8 +816,9 @@ class Config {
|
|
|
725
816
|
// Handle the old-style _auth=<base64> style for the default
|
|
726
817
|
// registry, if set.
|
|
727
818
|
const auth = this.get('_auth')
|
|
728
|
-
if (!auth)
|
|
819
|
+
if (!auth) {
|
|
729
820
|
return creds
|
|
821
|
+
}
|
|
730
822
|
|
|
731
823
|
const authDecode = Buffer.from(auth, 'base64').toString('utf8')
|
|
732
824
|
const authSplit = authDecode.split(':')
|
|
@@ -739,7 +831,9 @@ class Config {
|
|
|
739
831
|
// set up the environment object we have with npm_config_* environs
|
|
740
832
|
// for all configs that are different from their default values, and
|
|
741
833
|
// set EDITOR and HOME.
|
|
742
|
-
setEnvs () {
|
|
834
|
+
setEnvs () {
|
|
835
|
+
setEnvs(this)
|
|
836
|
+
}
|
|
743
837
|
}
|
|
744
838
|
|
|
745
839
|
const _data = Symbol('data')
|
|
@@ -765,25 +859,37 @@ class ConfigData {
|
|
|
765
859
|
}
|
|
766
860
|
|
|
767
861
|
set source (s) {
|
|
768
|
-
if (this[_source])
|
|
862
|
+
if (this[_source]) {
|
|
769
863
|
throw new Error('cannot set ConfigData source more than once')
|
|
864
|
+
}
|
|
770
865
|
this[_source] = s
|
|
771
866
|
}
|
|
772
|
-
|
|
867
|
+
|
|
868
|
+
get source () {
|
|
869
|
+
return this[_source]
|
|
870
|
+
}
|
|
773
871
|
|
|
774
872
|
set loadError (e) {
|
|
775
|
-
if (this[_loadError] || this[_raw])
|
|
873
|
+
if (this[_loadError] || this[_raw]) {
|
|
776
874
|
throw new Error('cannot set ConfigData loadError after load')
|
|
875
|
+
}
|
|
777
876
|
this[_loadError] = e
|
|
778
877
|
}
|
|
779
|
-
|
|
878
|
+
|
|
879
|
+
get loadError () {
|
|
880
|
+
return this[_loadError]
|
|
881
|
+
}
|
|
780
882
|
|
|
781
883
|
set raw (r) {
|
|
782
|
-
if (this[_raw] || this[_loadError])
|
|
884
|
+
if (this[_raw] || this[_loadError]) {
|
|
783
885
|
throw new Error('cannot set ConfigData raw after load')
|
|
886
|
+
}
|
|
784
887
|
this[_raw] = r
|
|
785
888
|
}
|
|
786
|
-
|
|
889
|
+
|
|
890
|
+
get raw () {
|
|
891
|
+
return this[_raw]
|
|
892
|
+
}
|
|
787
893
|
}
|
|
788
894
|
|
|
789
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.1",
|
|
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": "
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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": ">=
|
|
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