@npmcli/config
Advanced tools
Comparing version 2.4.0 to 3.0.0
@@ -6,9 +6,10 @@ // replace any ${ENV} values with the appropriate environ. | ||
module.exports = (f, env) => f.replace(envExpr, (orig, esc, name) => { | ||
const val = env[name] !== undefined ? env[name] : `\$\{${name}\}` | ||
const val = env[name] !== undefined ? env[name] : `$\{${name}}` | ||
// consume the escape chars that are relevant. | ||
if (esc.length % 2) | ||
if (esc.length % 2) { | ||
return orig.substr((esc.length + 1) / 2) | ||
} | ||
return (esc.substr(esc.length / 2)) + val | ||
}) |
206
lib/index.js
@@ -6,2 +6,4 @@ // TODO: set the scope config from package.json or explicit cli config | ||
const mkdirp = require('mkdirp-infer-owner') | ||
const mapWorkspaces = require('@npmcli/map-workspaces') | ||
const rpj = require('read-package-json-fast') | ||
@@ -95,3 +97,2 @@ /* istanbul ignore next */ | ||
}) { | ||
// turn the definitions into nopt's weirdo syntax | ||
@@ -105,4 +106,5 @@ this.definitions = definitions | ||
types[key] = def.type | ||
if (def.deprecated) | ||
if (def.deprecated) { | ||
this.deprecated[key] = def.deprecated.trim().replace(/\n +/, '\n') | ||
} | ||
} | ||
@@ -168,6 +170,8 @@ | ||
find (key) { | ||
if (!this.loaded) | ||
if (!this.loaded) { | ||
throw new Error('call config.load() before reading values') | ||
} | ||
return this[_find](key) | ||
} | ||
[_find] (key) { | ||
@@ -178,4 +182,5 @@ // have to look in reverse order | ||
const [where, { data }] = entries[i] | ||
if (hasOwnProperty(data, key)) | ||
if (hasOwnProperty(data, key)) { | ||
return where | ||
} | ||
} | ||
@@ -186,6 +191,8 @@ return null | ||
get (key, where) { | ||
if (!this.loaded) | ||
if (!this.loaded) { | ||
throw new Error('call config.load() before reading values') | ||
} | ||
return this[_get](key, where) | ||
} | ||
// we need to get values sometimes, so use this internal one to do so | ||
@@ -197,3 +204,3 @@ // while in the process of loading. | ||
} | ||
const { data, source } = this.data.get(where || 'cli') | ||
const { data } = this.data.get(where || 'cli') | ||
return where === null || hasOwnProperty(data, key) ? data[key] : undefined | ||
@@ -203,6 +210,8 @@ } | ||
set (key, val, where = 'cli') { | ||
if (!this.loaded) | ||
if (!this.loaded) { | ||
throw new Error('call config.load() before setting values') | ||
if (!confTypes.has(where)) | ||
} | ||
if (!confTypes.has(where)) { | ||
throw new Error('invalid config location param: ' + where) | ||
} | ||
this[_checkDeprecated](key) | ||
@@ -220,4 +229,5 @@ const { data } = this.data.get(where) | ||
get flat () { | ||
if (this[_flatOptions]) | ||
if (this[_flatOptions]) { | ||
return this[_flatOptions] | ||
} | ||
@@ -237,6 +247,8 @@ // create the object for flat options passed to deps | ||
delete (key, where = 'cli') { | ||
if (!this.loaded) | ||
if (!this.loaded) { | ||
throw new Error('call config.load() before deleting values') | ||
if (!confTypes.has(where)) | ||
} | ||
if (!confTypes.has(where)) { | ||
throw new Error('invalid config location param: ' + where) | ||
} | ||
delete this.data.get(where).data[key] | ||
@@ -246,4 +258,5 @@ } | ||
async load () { | ||
if (this.loaded) | ||
if (this.loaded) { | ||
throw new Error('attempting to load npm config multiple times') | ||
} | ||
@@ -296,3 +309,5 @@ process.emit('time', 'config:load') | ||
// might be a security hazard, which was the intention. | ||
try { this.setCredentialsByURI(reg, creds) } catch (_) {} | ||
try { | ||
this.setCredentialsByURI(reg, creds) | ||
} catch (_) {} | ||
process.emit('timeEnd', 'config:load:credentials') | ||
@@ -334,4 +349,5 @@ | ||
loadHome () { | ||
if (this.env.HOME) | ||
if (this.env.HOME) { | ||
return this.home = this.env.HOME | ||
} | ||
this.home = homedir() | ||
@@ -341,4 +357,5 @@ } | ||
loadGlobalPrefix () { | ||
if (this.globalPrefix) | ||
if (this.globalPrefix) { | ||
throw new Error('cannot load default global prefix more than once') | ||
} | ||
@@ -355,4 +372,5 @@ if (this.env.PREFIX) { | ||
// destdir only is respected on Unix | ||
if (this.env.DESTDIR) | ||
if (this.env.DESTDIR) { | ||
this.globalPrefix = join(this.env.DESTDIR, this.globalPrefix) | ||
} | ||
} | ||
@@ -362,7 +380,7 @@ } | ||
loadEnv () { | ||
const prefix = 'npm_config_' | ||
const conf = Object.create(null) | ||
for (const [envKey, envVal] of Object.entries(this.env)) { | ||
if (!/^npm_config_/i.test(envKey) || envVal === '') | ||
if (!/^npm_config_/i.test(envKey) || envVal === '') { | ||
continue | ||
} | ||
const key = envKey.substr('npm_config_'.length) | ||
@@ -387,5 +405,6 @@ .replace(/(?!^)_/g, '-') // don't replace _ at the start of the key | ||
get valid () { | ||
for (const [where, {valid}] of this.data.entries()) { | ||
if (valid === false || valid === null && !this.validate(where)) | ||
for (const [where, { valid }] of this.data.entries()) { | ||
if (valid === false || valid === null && !this.validate(where)) { | ||
return false | ||
} | ||
} | ||
@@ -398,7 +417,8 @@ return true | ||
let valid = true | ||
for (const [where, obj] of this.data.entries()) { | ||
for (const [where] of this.data.entries()) { | ||
// no need to validate our defaults, we know they're fine | ||
// cli was already validated when parsed the first time | ||
if (where === 'default' || where === 'builtin' || where === 'cli') | ||
if (where === 'default' || where === 'builtin' || where === 'cli') { | ||
continue | ||
} | ||
const ret = this.validate(where) | ||
@@ -445,10 +465,11 @@ valid = valid && ret | ||
if (Array.isArray(type)) { | ||
if (type.includes(typeDefs.url.type)) | ||
if (type.includes(typeDefs.url.type)) { | ||
type = typeDefs.url.type | ||
else { | ||
} else { | ||
/* istanbul ignore if - no actual configs matching this, but | ||
* path types SHOULD be handled this way, like URLs, for the | ||
* same reason */ | ||
if (type.includes(typeDefs.path.type)) | ||
if (type.includes(typeDefs.path.type)) { | ||
type = typeDefs.path.type | ||
} | ||
} | ||
@@ -491,4 +512,5 @@ } | ||
conf.loadError = er | ||
if (er.code !== 'ENOENT') | ||
if (er.code !== 'ENOENT') { | ||
this.log.verbose('config', `error loading ${where} config`, er) | ||
} | ||
} else { | ||
@@ -499,4 +521,5 @@ conf.raw = obj | ||
const v = this.parseField(value, k) | ||
if (where !== 'default') | ||
if (where !== 'default') { | ||
this[_checkDeprecated](k, where, obj, [key, value]) | ||
} | ||
conf.data[k] = v | ||
@@ -552,5 +575,5 @@ } | ||
// it doesn't match what the userconfig will be. | ||
if (projectFile !== this[_get]('userconfig')) | ||
if (projectFile !== this[_get]('userconfig')) { | ||
return this[_loadFile](projectFile, 'project') | ||
else { | ||
} else { | ||
this.data.get('project').source = '(same as "user" config, ignored)' | ||
@@ -568,19 +591,61 @@ this.sources.set(this.data.get('project').source, 'project') | ||
const cliWorkspaces = this[_get]('workspaces', 'cli') | ||
for (const p of walkUp(this.cwd)) { | ||
// walk up until we have a nm dir or a pj file | ||
const hasAny = (await Promise.all([ | ||
stat(resolve(p, 'node_modules')) | ||
.then(st => st.isDirectory()) | ||
.catch(() => false), | ||
stat(resolve(p, 'package.json')) | ||
.then(st => st.isFile()) | ||
.catch(() => false), | ||
])).some(is => is) | ||
if (hasAny) { | ||
const hasNodeModules = await stat(resolve(p, 'node_modules')) | ||
.then((st) => st.isDirectory()) | ||
.catch(() => false) | ||
const hasPackageJson = await stat(resolve(p, 'package.json')) | ||
.then((st) => st.isFile()) | ||
.catch(() => false) | ||
if (!this.localPrefix && (hasNodeModules || hasPackageJson)) { | ||
this.localPrefix = p | ||
return | ||
// if workspaces are disabled, return now | ||
if (cliWorkspaces === false) { | ||
return | ||
} | ||
// otherwise, continue the loop | ||
continue | ||
} | ||
if (this.localPrefix && hasPackageJson) { | ||
// if we already set localPrefix but this dir has a package.json | ||
// then we need to see if `p` is a workspace root by reading its package.json | ||
// however, if reading it fails then we should just move on | ||
const pkg = await rpj(resolve(p, 'package.json')).catch(() => false) | ||
if (!pkg) { | ||
continue | ||
} | ||
const workspaces = await mapWorkspaces({ cwd: p, pkg }) | ||
for (const w of workspaces.values()) { | ||
if (w === this.localPrefix) { | ||
// see if there's a .npmrc file in the workspace, if so log a warning | ||
const hasNpmrc = await stat(resolve(this.localPrefix, '.npmrc')) | ||
.then((st) => st.isFile()) | ||
.catch(() => false) | ||
if (hasNpmrc) { | ||
this.log.warn(`ignoring workspace config at ${this.localPrefix}/.npmrc`) | ||
} | ||
// set the workspace in the default layer, which allows it to be overridden easily | ||
const { data } = this.data.get('default') | ||
data.workspace = [this.localPrefix] | ||
this.localPrefix = p | ||
this.log.info(`found workspace root at ${this.localPrefix}`) | ||
// we found a root, so we return now | ||
return | ||
} | ||
} | ||
} | ||
} | ||
this.localPrefix = this.cwd | ||
if (!this.localPrefix) { | ||
this.localPrefix = this.cwd | ||
} | ||
} | ||
@@ -597,6 +662,8 @@ | ||
async save (where) { | ||
if (!this.loaded) | ||
if (!this.loaded) { | ||
throw new Error('call config.load() before saving') | ||
if (!confFileTypes.has(where)) | ||
} | ||
if (!confFileTypes.has(where)) { | ||
throw new Error('invalid config location param: ' + where) | ||
} | ||
const conf = this.data.get(where) | ||
@@ -613,3 +680,5 @@ conf[_raw] = { ...conf.data } | ||
// saved back to the .npmrc file, so we're good. | ||
try { this.setCredentialsByURI(reg, creds) } catch (_) {} | ||
try { | ||
this.setCredentialsByURI(reg, creds) | ||
} catch (_) {} | ||
} | ||
@@ -630,4 +699,5 @@ | ||
const st = await stat(dir).catch(() => null) | ||
if (st && (st.uid !== myUid || st.gid !== myGid)) | ||
if (st && (st.uid !== myUid || st.gid !== myGid)) { | ||
await chown(conf.source, st.uid, st.gid).catch(() => {}) | ||
} | ||
} | ||
@@ -680,4 +750,5 @@ const mode = where === 'user' ? 0o600 : 0o666 | ||
this.get(`${nerfed}:email`, 'user') | ||
if (email) | ||
if (email) { | ||
this.set('email', email, 'user') | ||
} | ||
} | ||
@@ -698,6 +769,8 @@ | ||
} else if (username || password) { | ||
if (!username) | ||
if (!username) { | ||
throw new Error('must include username') | ||
if (!password) | ||
} | ||
if (!password) { | ||
throw new Error('must include password') | ||
} | ||
this.delete(`${nerfed}:_authToken`, 'user') | ||
@@ -720,4 +793,5 @@ this.set(`${nerfed}:username`, username, 'user') | ||
const email = this.get(`${nerfed}:email`) || this.get('email') | ||
if (email) | ||
if (email) { | ||
creds.email = email | ||
} | ||
@@ -757,4 +831,5 @@ const tokenReg = this.get(`${nerfed}:_authToken`) || | ||
const defaultNerf = nerfDart(this.get('registry')) | ||
if (nerfed !== defaultNerf) | ||
if (nerfed !== defaultNerf) { | ||
return creds | ||
} | ||
@@ -774,4 +849,5 @@ const userDef = this.get('username') | ||
const auth = this.get('_auth') | ||
if (!auth) | ||
if (!auth) { | ||
return creds | ||
} | ||
@@ -789,3 +865,5 @@ const authDecode = Buffer.from(auth, 'base64').toString('utf8') | ||
// set EDITOR and HOME. | ||
setEnvs () { setEnvs(this) } | ||
setEnvs () { | ||
setEnvs(this) | ||
} | ||
} | ||
@@ -816,23 +894,35 @@ | ||
set source (s) { | ||
if (this[_source]) | ||
if (this[_source]) { | ||
throw new Error('cannot set ConfigData source more than once') | ||
} | ||
this[_source] = s | ||
} | ||
get source () { return this[_source] } | ||
get source () { | ||
return this[_source] | ||
} | ||
set loadError (e) { | ||
if (this[_loadError] || this[_raw]) | ||
if (this[_loadError] || this[_raw]) { | ||
throw new Error('cannot set ConfigData loadError after load') | ||
} | ||
this[_loadError] = e | ||
} | ||
get loadError () { return this[_loadError] } | ||
get loadError () { | ||
return this[_loadError] | ||
} | ||
set raw (r) { | ||
if (this[_raw] || this[_loadError]) | ||
if (this[_raw] || this[_loadError]) { | ||
throw new Error('cannot set ConfigData raw after load') | ||
} | ||
this[_raw] = r | ||
} | ||
get raw () { return this[_raw] } | ||
get raw () { | ||
return this[_raw] | ||
} | ||
} | ||
module.exports = Config |
@@ -9,6 +9,7 @@ // Parse a field, coercing it to the best type available. | ||
const parseField = (f, key, opts, listElement = false) => { | ||
if (typeof f !== 'string' && !Array.isArray(f)) | ||
if (typeof f !== 'string' && !Array.isArray(f)) { | ||
return f | ||
} | ||
const { platform, types, log, home, env } = opts | ||
const { platform, types, home, env } = opts | ||
@@ -24,4 +25,5 @@ // type can be array or a single thing. coerce to array. | ||
if (Array.isArray(f)) | ||
if (Array.isArray(f)) { | ||
return !isList ? f : f.map(field => parseField(field, key, opts, true)) | ||
} | ||
@@ -34,8 +36,10 @@ // now we know it's a string | ||
// line breaks and multiple entries. | ||
if (isList) | ||
if (isList) { | ||
return parseField(f.split('\n\n'), key, opts) | ||
} | ||
// --foo is like --foo=true for boolean types | ||
if (isBool && !isString && f === '') | ||
if (isBool && !isString && f === '') { | ||
return true | ||
} | ||
@@ -57,6 +61,7 @@ // string types can be the string 'true', 'false', etc. | ||
const homePattern = platform === 'win32' ? /^~(\/|\\)/ : /^~\// | ||
if (homePattern.test(f) && home) | ||
if (homePattern.test(f) && home) { | ||
f = resolve(home, f.substr(2)) | ||
else | ||
} else { | ||
f = resolve(f) | ||
} | ||
} | ||
@@ -73,4 +78,5 @@ | ||
if (isNumber && !isNaN(f)) | ||
if (isNumber && !isNaN(f)) { | ||
f = +f | ||
} | ||
@@ -77,0 +83,0 @@ return f |
@@ -25,4 +25,5 @@ // Set environment variables for any non-default configs, | ||
const sameArrayValue = (def, val) => { | ||
if (def.length !== val.length) | ||
if (def.length !== val.length) { | ||
return false | ||
} | ||
@@ -33,4 +34,5 @@ for (let i = 0; i < def.length; i++) { | ||
* thing to do if we ever DO add a config like that. */ | ||
if (def[i] !== val[i]) | ||
if (def[i] !== val[i]) { | ||
return false | ||
} | ||
} | ||
@@ -43,4 +45,5 @@ return true | ||
const key = envKey(rawKey, val) | ||
if (key && val !== null) | ||
if (key && val !== null) { | ||
env[key] = val | ||
} | ||
} | ||
@@ -52,4 +55,2 @@ | ||
const { | ||
globalPrefix, | ||
platform, | ||
env, | ||
@@ -75,4 +76,5 @@ defaults, | ||
const { deprecated, envExport = true } = definitions[key] || {} | ||
if (deprecated || envExport === false) | ||
if (deprecated || envExport === false) { | ||
continue | ||
} | ||
@@ -82,9 +84,11 @@ if (sameConfigValue(defaults[key], cliConf[key])) { | ||
// have to set it BACK to the default in the environment. | ||
if (!sameConfigValue(envConf[key], cliConf[key])) | ||
if (!sameConfigValue(envConf[key], cliConf[key])) { | ||
setEnv(env, key, cliConf[key]) | ||
} | ||
} else { | ||
// config is not the default. if the env wasn't the one to set | ||
// it that way, then we have to put it in the env | ||
if (!(envSet.has(key) && !cliSet.has(key))) | ||
if (!(envSet.has(key) && !cliSet.has(key))) { | ||
setEnv(env, key, cliConf[key]) | ||
} | ||
} | ||
@@ -97,12 +101,15 @@ } | ||
env.npm_config_local_prefix = config.localPrefix | ||
if (cliConf.editor) | ||
if (cliConf.editor) { | ||
env.EDITOR = cliConf.editor | ||
} | ||
// note: this doesn't afect the *current* node process, of course, since | ||
// it's already started, but it does affect the options passed to scripts. | ||
if (cliConf['node-options']) | ||
if (cliConf['node-options']) { | ||
env.NODE_OPTIONS = cliConf['node-options'] | ||
} | ||
if (require.main && require.main.filename) | ||
if (require.main && require.main.filename) { | ||
env.npm_execpath = require.main.filename | ||
} | ||
env.NODE = env.npm_node_execpath = config.execPath | ||
@@ -109,0 +116,0 @@ } |
@@ -8,4 +8,5 @@ const nopt = require('nopt') | ||
const valid = semver.valid(val) | ||
if (!valid) | ||
if (!valid) { | ||
return false | ||
} | ||
data[k] = valid | ||
@@ -16,4 +17,5 @@ } | ||
const validatePath = (data, k, val) => { | ||
if (typeof val !== 'string') | ||
if (typeof val !== 'string') { | ||
return false | ||
} | ||
return noptValidatePath(data, k, val) | ||
@@ -20,0 +22,0 @@ } |
@@ -5,11 +5,14 @@ // return the description of the valid values of a field | ||
const typeDescription = t => { | ||
if (!t || typeof t !== 'function' && typeof t !== 'object') | ||
if (!t || typeof t !== 'function' && typeof t !== 'object') { | ||
return t | ||
} | ||
if (Array.isArray(t)) | ||
if (Array.isArray(t)) { | ||
return t.map(t => typeDescription(t)) | ||
} | ||
for (const { type, description } of Object.values(typeDefs)) { | ||
if (type === t) | ||
if (type === t) { | ||
return description || type | ||
} | ||
} | ||
@@ -16,0 +19,0 @@ |
class Umask {} | ||
const parse = val => { | ||
if (typeof val === 'string') { | ||
if (/^0o?[0-7]+$/.test(val)) | ||
if (/^0o?[0-7]+$/.test(val)) { | ||
return parseInt(val.replace(/^0o?/, ''), 8) | ||
else if (/^[1-9][0-9]*$/.test(val)) | ||
} else if (/^[1-9][0-9]*$/.test(val)) { | ||
return parseInt(val, 10) | ||
else | ||
} else { | ||
throw new Error(`invalid umask value: ${val}`) | ||
} | ||
} | ||
if (typeof val !== 'number') | ||
if (typeof val !== 'number') { | ||
throw new Error(`invalid umask value: ${val}`) | ||
} | ||
val = Math.floor(val) | ||
if (val < 0 || val > 511) | ||
if (val < 0 || val > 511) { | ||
throw new Error(`invalid umask value: ${val}`) | ||
} | ||
return val | ||
@@ -17,0 +20,0 @@ } |
{ | ||
"name": "@npmcli/config", | ||
"version": "2.4.0", | ||
"version": "3.0.0", | ||
"files": [ | ||
"bin", | ||
"lib" | ||
@@ -13,3 +14,3 @@ ], | ||
}, | ||
"author": "Isaac Z. Schlueter <i@izs.me> (https://izs.me)", | ||
"author": "GitHub Inc.", | ||
"license": "ISC", | ||
@@ -21,3 +22,8 @@ "scripts": { | ||
"postversion": "npm publish", | ||
"prepublishOnly": "git push origin --follow-tags" | ||
"prepublishOnly": "git push origin --follow-tags", | ||
"lint": "eslint '**/*.js'", | ||
"postlint": "npm-template-check", | ||
"lintfix": "npm run lint -- --fix", | ||
"posttest": "npm run lint", | ||
"template-copy": "npm-template-copy --force" | ||
}, | ||
@@ -29,8 +35,11 @@ "tap": { | ||
"devDependencies": { | ||
"@npmcli/template-oss": "^2.5.1", | ||
"tap": "^15.0.4" | ||
}, | ||
"dependencies": { | ||
"@npmcli/map-workspaces": "^2.0.0", | ||
"ini": "^2.0.0", | ||
"mkdirp-infer-owner": "^2.0.0", | ||
"nopt": "^5.0.0", | ||
"read-package-json-fast": "^2.0.3", | ||
"semver": "^7.3.4", | ||
@@ -40,4 +49,7 @@ "walk-up-path": "^1.0.0" | ||
"engines": { | ||
"node": ">=10" | ||
"node": "^12.13.0 || ^14.15.0 || >=16" | ||
}, | ||
"templateOSS": { | ||
"version": "2.6.0" | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
48093
1071
7
2
+ Added@npmcli/map-workspaces@2.0.4(transitive)
+ Added@npmcli/name-from-folder@1.0.1(transitive)
+ Addedbalanced-match@1.0.2(transitive)
+ Addedbrace-expansion@2.0.1(transitive)
+ Addedfs.realpath@1.0.0(transitive)
+ Addedglob@8.1.0(transitive)
+ Addedinflight@1.0.6(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedjson-parse-even-better-errors@2.3.1(transitive)
+ Addedminimatch@5.1.6(transitive)
+ Addednpm-normalize-package-bin@1.0.1(transitive)
+ Addedonce@1.4.0(transitive)
+ Addedread-package-json-fast@2.0.3(transitive)
+ Addedwrappy@1.0.2(transitive)