diff options
Diffstat (limited to 'deps/npm/node_modules/@npmcli/config/lib/index.js')
-rw-r--r-- | deps/npm/node_modules/@npmcli/config/lib/index.js | 206 |
1 files changed, 148 insertions, 58 deletions
diff --git a/deps/npm/node_modules/@npmcli/config/lib/index.js b/deps/npm/node_modules/@npmcli/config/lib/index.js index 293fad2ec56..dc839a5389c 100644 --- a/deps/npm/node_modules/@npmcli/config/lib/index.js +++ b/deps/npm/node_modules/@npmcli/config/lib/index.js @@ -3,6 +3,8 @@ const walkUp = require('walk-up-path') const ini = require('ini') const nopt = require('nopt') const mkdirp = require('mkdirp-infer-owner') +const mapWorkspaces = require('@npmcli/map-workspaces') +const rpj = require('read-package-json-fast') /* istanbul ignore next */ const myUid = process.getuid && process.getuid() @@ -91,7 +93,6 @@ class Config { execPath = process.execPath, cwd = process.cwd(), }) { - // turn the definitions into nopt's weirdo syntax this.definitions = definitions const types = {} @@ -100,8 +101,9 @@ class Config { for (const [key, def] of Object.entries(definitions)) { defaults[key] = def.default types[key] = def.type - if (def.deprecated) + if (def.deprecated) { this.deprecated[key] = def.deprecated.trim().replace(/\n +/, '\n') + } } // populated the first time we flatten the object @@ -162,41 +164,48 @@ class Config { // return the location where key is found. find (key) { - if (!this.loaded) + if (!this.loaded) { throw new Error('call config.load() before reading values') + } return this[_find](key) } + [_find] (key) { // have to look in reverse order const entries = [...this.data.entries()] for (let i = entries.length - 1; i > -1; i--) { const [where, { data }] = entries[i] - if (hasOwnProperty(data, key)) + if (hasOwnProperty(data, key)) { return where + } } 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 // while in the process of loading. [_get] (key, where = null) { if (where !== null && !confTypes.has(where)) { throw new Error('invalid config location param: ' + where) } - const { data, source } = this.data.get(where || 'cli') + const { data } = this.data.get(where || 'cli') return where === null || hasOwnProperty(data, key) ? data[key] : undefined } 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) const { data } = this.data.get(where) data[key] = val @@ -209,8 +218,9 @@ class Config { } get flat () { - if (this[_flatOptions]) + if (this[_flatOptions]) { return this[_flatOptions] + } // create the object for flat options passed to deps process.emit('time', 'config:load:flatten') @@ -225,16 +235,19 @@ class Config { } 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] } async load () { - if (this.loaded) + if (this.loaded) { throw new Error('attempting to load npm config multiple times') + } process.emit('time', 'config:load') // first load the defaults, which sets the global prefix @@ -282,7 +295,9 @@ class Config { const creds = this.getCredentialsByURI(reg) // ignore this error because a failed set will strip out anything that // 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') // set proper globalPrefix now that everything is loaded @@ -319,14 +334,16 @@ class Config { } loadHome () { - if (this.env.HOME) + if (this.env.HOME) { return this.home = this.env.HOME + } this.home = homedir() } loadGlobalPrefix () { - if (this.globalPrefix) + if (this.globalPrefix) { throw new Error('cannot load default global prefix more than once') + } if (this.env.PREFIX) { this.globalPrefix = this.env.PREFIX @@ -338,17 +355,18 @@ class Config { this.globalPrefix = dirname(dirname(this.execPath)) // destdir only is respected on Unix - if (this.env.DESTDIR) + if (this.env.DESTDIR) { this.globalPrefix = join(this.env.DESTDIR, this.globalPrefix) + } } } 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) .replace(/(?!^)_/g, '-') // don't replace _ at the start of the key .toLowerCase() @@ -368,9 +386,10 @@ class Config { } 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 + } } return true } @@ -378,11 +397,12 @@ class Config { validate (where) { if (!where) { 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) valid = valid && ret } @@ -424,14 +444,15 @@ class Config { this.data.get(where)[_valid] = false 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 + } } } @@ -469,15 +490,17 @@ class Config { this.sources.set(source, where) if (er) { conf.loadError = er - if (er.code !== 'ENOENT') + if (er.code !== 'ENOENT') { this.log.verbose('config', `error loading ${where} config`, er) + } } else { conf.raw = obj for (const [key, value] of Object.entries(obj)) { const k = envReplace(key, this.env) const v = this.parseField(value, k) - if (where !== 'default') + if (where !== 'default') { this[_checkDeprecated](k, where, obj, [key, value]) + } conf.data[k] = v } } @@ -528,9 +551,9 @@ class Config { // up loading the "project" config where the "userconfig" will be, // which causes some calamaties. So, we only load project config if // 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)' this.sources.set(this.data.get('project').source, 'project') } @@ -543,23 +566,65 @@ class Config { return } + 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 + } } loadUserConfig () { @@ -571,10 +636,12 @@ class Config { } 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) conf[_raw] = { ...conf.data } conf[_loadError] = null @@ -586,7 +653,9 @@ class Config { // we ignore this error because the failed set already removed // anything that might be a security hazard, and it won't be // saved back to the .npmrc file, so we're good. - try { this.setCredentialsByURI(reg, creds) } catch (_) {} + try { + this.setCredentialsByURI(reg, creds) + } catch (_) {} } const iniData = ini.stringify(conf.data).trim() + '\n' @@ -602,8 +671,9 @@ class Config { /* istanbul ignore if - this is best-effort and a pita to test */ if (myUid === 0) { 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(() => {}) + } } const mode = where === 'user' ? 0o600 : 0o666 await chmod(conf.source, mode) @@ -651,8 +721,9 @@ class Config { email = email || this.get('email', 'user') || this.get(`${nerfed}:email`, 'user') - if (email) + if (email) { this.set('email', email, 'user') + } } // field that hasn't been used as documented for a LONG time, @@ -668,10 +739,12 @@ class Config { this.delete(`${nerfed}:_password`, 'user') this.delete(`${nerfed}:username`, 'user') } 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') this.set(`${nerfed}:username`, username, 'user') // note: not encrypted, no idea why we bothered to do this, but oh well @@ -689,8 +762,9 @@ class Config { const creds = {} const email = this.get(`${nerfed}:email`) || this.get('email') - if (email) + if (email) { creds.email = email + } const tokenReg = this.get(`${nerfed}:_authToken`) || this.get(`${nerfed}:_authtoken`) || @@ -725,8 +799,9 @@ class Config { // at this point, we can only use the values if the URI is the // default registry. const defaultNerf = nerfDart(this.get('registry')) - if (nerfed !== defaultNerf) + if (nerfed !== defaultNerf) { return creds + } const userDef = this.get('username') const passDef = this.get('_password') @@ -741,8 +816,9 @@ class Config { // Handle the old-style _auth=<base64> style for the default // registry, if set. const auth = this.get('_auth') - if (!auth) + if (!auth) { return creds + } const authDecode = Buffer.from(auth, 'base64').toString('utf8') const authSplit = authDecode.split(':') @@ -755,7 +831,9 @@ class Config { // set up the environment object we have with npm_config_* environs // for all configs that are different from their default values, and // set EDITOR and HOME. - setEnvs () { setEnvs(this) } + setEnvs () { + setEnvs(this) + } } const _data = Symbol('data') @@ -781,25 +859,37 @@ class ConfigData { } 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 |