123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429 |
- var CC = require('config-chain').ConfigChain
- var inherits = require('inherits')
- var configDefs = require('./defaults.js')
- var types = configDefs.types
- var once = require('once')
- var fs = require('fs')
- var path = require('path')
- var nopt = require('nopt')
- var ini = require('ini')
- var Umask = configDefs.Umask
- var mkdirp = require('gentle-fs').mkdir
- var umask = require('../utils/umask')
- var isWindows = require('../utils/is-windows.js')
- exports.load = load
- exports.Conf = Conf
- exports.loaded = false
- exports.rootConf = null
- exports.usingBuiltin = false
- exports.defs = configDefs
- Object.defineProperty(exports, 'defaults', { get: function () {
- return configDefs.defaults
- },
- enumerable: true })
- Object.defineProperty(exports, 'types', { get: function () {
- return configDefs.types
- },
- enumerable: true })
- exports.validate = validate
- var myUid = process.getuid && process.getuid()
- var myGid = process.getgid && process.getgid()
- var loading = false
- var loadCbs = []
- function load () {
- var cli, builtin, cb
- for (var i = 0; i < arguments.length; i++) {
- switch (typeof arguments[i]) {
- case 'string': builtin = arguments[i]; break
- case 'object': cli = arguments[i]; break
- case 'function': cb = arguments[i]; break
- }
- }
- if (!cb) cb = function () {}
- if (exports.loaded) {
- var ret = exports.loaded
- if (cli) {
- ret = new Conf(ret)
- ret.unshift(cli)
- }
- return process.nextTick(cb.bind(null, null, ret))
- }
- // either a fresh object, or a clone of the passed in obj
- if (!cli) {
- cli = {}
- } else {
- cli = Object.keys(cli).reduce(function (c, k) {
- c[k] = cli[k]
- return c
- }, {})
- }
- loadCbs.push(cb)
- if (loading) return
- loading = true
- cb = once(function (er, conf) {
- if (!er) {
- exports.loaded = conf
- loading = false
- }
- loadCbs.forEach(function (fn) {
- fn(er, conf)
- })
- loadCbs.length = 0
- })
- // check for a builtin if provided.
- exports.usingBuiltin = !!builtin
- var rc = exports.rootConf = new Conf()
- if (builtin) {
- rc.addFile(builtin, 'builtin')
- } else {
- rc.add({}, 'builtin')
- }
- rc.on('load', function () {
- load_(builtin, rc, cli, cb)
- })
- rc.on('error', cb)
- }
- function load_ (builtin, rc, cli, cb) {
- var defaults = configDefs.defaults
- var conf = new Conf(rc)
- conf.usingBuiltin = !!builtin
- conf.add(cli, 'cli')
- conf.addEnv()
- conf.loadPrefix(function (er) {
- if (er) return cb(er)
- // If you're doing `npm --userconfig=~/foo.npmrc` then you'd expect
- // that ~/.npmrc won't override the stuff in ~/foo.npmrc (or, indeed
- // be used at all).
- //
- // However, if the cwd is ~, then ~/.npmrc is the home for the project
- // config, and will override the userconfig.
- //
- // If you're not setting the userconfig explicitly, then it will be loaded
- // twice, which is harmless but excessive. If you *are* setting the
- // userconfig explicitly then it will override your explicit intent, and
- // that IS harmful and unexpected.
- //
- // Solution: Do not load project config file that is the same as either
- // the default or resolved userconfig value. npm will log a "verbose"
- // message about this when it happens, but it is a rare enough edge case
- // that we don't have to be super concerned about it.
- var projectConf = path.resolve(conf.localPrefix, '.npmrc')
- var defaultUserConfig = rc.get('userconfig')
- var resolvedUserConfig = conf.get('userconfig')
- if (!conf.get('global') &&
- projectConf !== defaultUserConfig &&
- projectConf !== resolvedUserConfig) {
- conf.addFile(projectConf, 'project')
- conf.once('load', afterPrefix)
- } else {
- conf.add({}, 'project')
- afterPrefix()
- }
- })
- function afterPrefix () {
- conf.addFile(conf.get('userconfig'), 'user')
- conf.once('error', cb)
- conf.once('load', afterUser)
- }
- function afterUser () {
- // globalconfig and globalignorefile defaults
- // need to respond to the 'prefix' setting up to this point.
- // Eg, `npm config get globalconfig --prefix ~/local` should
- // return `~/local/etc/npmrc`
- // annoying humans and their expectations!
- if (conf.get('prefix')) {
- var etc = path.resolve(conf.get('prefix'), 'etc')
- defaults.globalconfig = path.resolve(etc, 'npmrc')
- defaults.globalignorefile = path.resolve(etc, 'npmignore')
- }
- conf.addFile(conf.get('globalconfig'), 'global')
- // move the builtin into the conf stack now.
- conf.root = defaults
- conf.add(rc.shift(), 'builtin')
- conf.once('load', function () {
- conf.loadExtras(afterExtras)
- })
- }
- function afterExtras (er) {
- if (er) return cb(er)
- // warn about invalid bits.
- validate(conf)
- var cafile = conf.get('cafile')
- if (cafile) {
- return conf.loadCAFile(cafile, finalize)
- }
- finalize()
- }
- function finalize (er) {
- if (er) {
- return cb(er)
- }
- exports.loaded = conf
- cb(er, conf)
- }
- }
- // Basically the same as CC, but:
- // 1. Always ini
- // 2. Parses environment variable names in field values
- // 3. Field values that start with ~/ are replaced with process.env.HOME
- // 4. Can inherit from another Conf object, using it as the base.
- inherits(Conf, CC)
- function Conf (base) {
- if (!(this instanceof Conf)) return new Conf(base)
- CC.call(this)
- if (base) {
- if (base instanceof Conf) {
- this.root = base.list[0] || base.root
- } else {
- this.root = base
- }
- } else {
- this.root = configDefs.defaults
- }
- }
- Conf.prototype.loadPrefix = require('./load-prefix.js')
- Conf.prototype.loadCAFile = require('./load-cafile.js')
- Conf.prototype.setUser = require('./set-user.js')
- Conf.prototype.getCredentialsByURI = require('./get-credentials-by-uri.js')
- Conf.prototype.setCredentialsByURI = require('./set-credentials-by-uri.js')
- Conf.prototype.clearCredentialsByURI = require('./clear-credentials-by-uri.js')
- Conf.prototype.loadExtras = function (cb) {
- this.setUser(function (er) {
- if (er) return cb(er)
- // Without prefix, nothing will ever work
- mkdirp(this.prefix, cb)
- }.bind(this))
- }
- Conf.prototype.save = function (where, cb) {
- var target = this.sources[where]
- if (!target || !(target.path || target.source) || !target.data) {
- var er
- if (where !== 'builtin') er = new Error('bad save target: ' + where)
- if (cb) {
- process.nextTick(cb.bind(null, er))
- return this
- }
- return this.emit('error', er)
- }
- if (target.source) {
- var pref = target.prefix || ''
- Object.keys(target.data).forEach(function (k) {
- target.source[pref + k] = target.data[k]
- })
- if (cb) process.nextTick(cb)
- return this
- }
- var data = ini.stringify(target.data)
- var then = function then (er) {
- if (er) return done(er)
- fs.chmod(target.path, mode, done)
- }
- var done = function done (er) {
- if (er) {
- if (cb) return cb(er)
- else return this.emit('error', er)
- }
- this._saving--
- if (this._saving === 0) {
- if (cb) cb()
- this.emit('save')
- }
- }
- then = then.bind(this)
- done = done.bind(this)
- this._saving++
- var mode = where === 'user' ? '0600' : '0666'
- if (!data.trim()) {
- fs.unlink(target.path, function () {
- // ignore the possible error (e.g. the file doesn't exist)
- done(null)
- })
- } else {
- // we don't have to use inferOwner here, because gentle-fs will
- // mkdir with the correctly inferred ownership. Just preserve it.
- const dir = path.dirname(target.path)
- mkdirp(dir, function (er) {
- if (er) return then(er)
- fs.stat(dir, (er, st) => {
- if (er) return then(er)
- fs.writeFile(target.path, data, 'utf8', function (er) {
- if (er) return then(er)
- if (myUid === 0 && (myUid !== st.uid || myGid !== st.gid)) {
- fs.chown(target.path, st.uid, st.gid, then)
- } else {
- then()
- }
- })
- })
- })
- }
- return this
- }
- Conf.prototype.addFile = function (file, name) {
- name = name || file
- var marker = { __source__: name }
- this.sources[name] = { path: file, type: 'ini' }
- this.push(marker)
- this._await()
- fs.readFile(file, 'utf8', function (er, data) {
- // just ignore missing files.
- if (er) return this.add({}, marker)
- this.addString(data, file, 'ini', marker)
- }.bind(this))
- return this
- }
- // always ini files.
- Conf.prototype.parse = function (content, file) {
- return CC.prototype.parse.call(this, content, file, 'ini')
- }
- Conf.prototype.add = function (data, marker) {
- try {
- Object.keys(data).forEach(function (k) {
- const newKey = envReplace(k)
- const newField = parseField(data[k], newKey)
- delete data[k]
- data[newKey] = newField
- })
- } catch (e) {
- this.emit('error', e)
- return this
- }
- return CC.prototype.add.call(this, data, marker)
- }
- Conf.prototype.addEnv = function (env) {
- env = env || process.env
- var conf = {}
- Object.keys(env)
- .filter(function (k) { return k.match(/^npm_config_/i) })
- .forEach(function (k) {
- if (!env[k]) return
- // leave first char untouched, even if
- // it is a '_' - convert all other to '-'
- var p = k.toLowerCase()
- .replace(/^npm_config_/, '')
- .replace(/(?!^)_/g, '-')
- conf[p] = env[k]
- })
- return CC.prototype.addEnv.call(this, '', conf, 'env')
- }
- function parseField (f, k) {
- if (typeof f !== 'string' && !(f instanceof String)) return f
- // type can be an array or single thing.
- var typeList = [].concat(types[k])
- var isPath = typeList.indexOf(path) !== -1
- var isBool = typeList.indexOf(Boolean) !== -1
- var isString = typeList.indexOf(String) !== -1
- var isUmask = typeList.indexOf(Umask) !== -1
- var isNumber = typeList.indexOf(Number) !== -1
- f = ('' + f).trim()
- if (f.match(/^".*"$/)) {
- try {
- f = JSON.parse(f)
- } catch (e) {
- throw new Error('Failed parsing JSON config key ' + k + ': ' + f)
- }
- }
- if (isBool && !isString && f === '') return true
- switch (f) {
- case 'true': return true
- case 'false': return false
- case 'null': return null
- case 'undefined': return undefined
- }
- f = envReplace(f)
- if (isPath) {
- var homePattern = isWindows ? /^~(\/|\\)/ : /^~\//
- if (f.match(homePattern) && process.env.HOME) {
- f = path.resolve(process.env.HOME, f.substr(2))
- }
- f = path.resolve(f)
- }
- if (isUmask) f = umask.fromString(f)
- if (isNumber && !isNaN(f)) f = +f
- return f
- }
- function envReplace (f) {
- if (typeof f !== 'string' || !f) return f
- // replace any ${ENV} values with the appropriate environ.
- var envExpr = /(\\*)\$\{([^}]+)\}/g
- return f.replace(envExpr, function (orig, esc, name) {
- esc = esc.length && esc.length % 2
- if (esc) return orig
- if (undefined === process.env[name]) {
- throw new Error('Failed to replace env in config: ' + orig)
- }
- return process.env[name]
- })
- }
- function validate (cl) {
- // warn about invalid configs at every level.
- cl.list.forEach(function (conf) {
- nopt.clean(conf, configDefs.types)
- })
- nopt.clean(cl.root, configDefs.types)
- }
|