You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

363 lines
9.7 KiB

// Create a chain of config objects, in this priority order:
//
// CLI - the --foo things in the command line.
// ENV - all the things starting with npm_config_ in the environment
// USER - $HOME/.npmrc
// GLOBAL - $PREFIX/etc/npmrc
//
// If the CLI or ENV specify a userconfig, then that file is used
// as the USER config.
//
// If the CLI or ENV specify a globalconfig, then that file is used
// as the GLOBAL config.
//
// export npm_config_userconfig=/some/other/file
// export npm_config_globalconfig=global
//
// For implementation reasons, "_" in env vars is turned into "-". So,
// export npm_config_node_version
exports.resolveConfigs = resolveConfigs
exports.save = save
exports.saveConfig = saveConfig
exports.del = del
exports.get = get
exports.set = set
exports.unParseField = unParseField
exports.defaultConfig = null
Object.defineProperty(exports, "keys",
{ get : function () { return configList.keys }})
var fs = require("graceful-fs")
, fstream = require("fstream")
, rimraf = require("rimraf")
, path = require("path")
, nopt = require("nopt")
, ini = require("ini")
, ProtoList = require("proto-list")
, log = require("./log.js")
, configDefs = require("./config-defs.js")
, myUid = process.env.SUDO_UID !== undefined
? process.env.SUDO_UID : (process.getuid && process.getuid())
, myGid = process.env.SUDO_GID !== undefined
? process.env.SUDO_GID : (process.getgid && process.getgid())
, eol = process.platform === "win32" ? "\r\n" : "\n"
, privateKey = null
, defaultConfig
, configList = new ProtoList()
, types = configDefs.types
, TRANS = exports.TRANS =
{ "default" : 5
, "builtin": 4
, "global" : 3
, "user" : 2
, "env" : 1
, "cli" : 0
}
exports.configList = configList
// just put this here for a moment, so that the logs
// in the config-loading phase don't cause it to blow up.
configList.push({loglevel:"warn"})
function resolveConfigs (cli, cb_) {
defaultConfig = defaultConfig || configDefs.defaults
exports.defaultConfig = defaultConfig
configList.pop()
configList.push(defaultConfig)
var cl = configList
, dc = cl.pop()
if (!cb_) cb_ = cli, cli = {}
function cb (er) {
//console.error("resolving configs")
exports.resolved = true
cb_(er)
}
cl.list.length = 0
Object.keys(cli).forEach(function (k) {
cli[k] = parseField(cli[k], k)
})
cl.push(cli)
cl.push(parseEnv(process.env))
parseFile(cl.get("userconfig") || dc.userconfig, function (er, conf) {
if (er) return cb(er)
cl.push(conf)
// 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`
if (cl.get("prefix")) {
dc.globalconfig = path.resolve(cl.get("prefix"), "etc", "npmrc")
dc.globalignorefile = path.resolve(cl.get("prefix"), "etc", "npmignore")
}
parseFile( cl.get("globalconfig") || dc.globalconfig
, function (er, conf) {
if (er) return cb(er)
if (conf.hasOwnProperty("prefix")) {
log.warn("Cannot set prefix in globalconfig file"
, cl.get("globalconfig"))
delete conf.prefix
}
cl.push(conf)
// the builtin config file, for distros to use.
parseFile(path.resolve(__dirname, "../../npmrc"), function (er, conf) {
if (er) conf = {}
cl.push(conf)
cl.push(dc)
setUser(cl, dc, thenValidate(cl, cb))
})
})
})
}
function thenValidate (cl, cb) { return function (er) {
if (er) return cb(er)
// warn about invalid configs at every level.
cl.list.forEach(function (conf, level) {
// clean(data, types, typeDefs)
nopt.clean(conf, configDefs.types)
})
cb()
}}
function setUser (cl, dc, cb) {
// If global, leave it as-is.
// If not global, then set the user to the owner of the prefix folder.
// Just set the default, so it can be overridden.
//console.error("setUser "+cl.get("global")+" "+ cb.toString())
if (cl.get("global")) return cb()
if (process.env.SUDO_UID) {
//console.error("uid="+process.env.SUDO_UID)
dc.user = +(process.env.SUDO_UID)
return cb()
}
//console.error("prefix="+cl.get("prefix"))
fs.stat(path.resolve(cl.get("prefix")), function (er, st) {
if (er) {
return log.er(cb, "prefix directory not found")(er)
}
dc.user = st.uid
return cb()
})
}
function parseEnv (env) {
var conf = {}
Object.keys(env)
.filter(function (k) { return k.match(/^npm_config_[^_]/i) })
.forEach(function (k) {
conf[k.replace(/^npm_config_/i, "")
.toLowerCase()
.replace(/_/g, "-")] = parseField(env[k], k)
})
return conf
}
function unParseField (f, k) {
// type can be an array or single thing.
var isPath = -1 !== [].concat(types[k]).indexOf(path)
if (isPath) {
if (typeof process.env.HOME !== 'undefined') {
if (process.env.HOME.substr(-1) === "/") {
process.env.HOME = process.env.HOME(0, process.env.HOME.length-1)
}
if (f.indexOf(process.env.HOME) === 0) {
f = "~"+f.substr(process.env.HOME.length)
}
}
}
return ini.safe(f)
}
function parseField (f, k, emptyIsFalse) {
if (typeof f !== "string" && !(f instanceof String)) return f
// type can be an array or single thing.
var isPath = -1 !== [].concat(types[k]).indexOf(path)
, isBool = -1 !== [].concat(types[k]).indexOf(Boolean)
, isString = -1 !== [].concat(types[k]).indexOf(String)
f = ini.unsafe((""+f).trim())
if (isBool && !isString && f === "") return f = true
switch (f) {
case "true": return true
case "false": return false
case "null": return null
case "undefined": return undefined
}
f = envReplace(f)
if (isPath) {
if (f.substr(0, 2) === "~/" && process.env.HOME) {
f = path.resolve(process.env.HOME, f.substr(2))
}
f = path.resolve(f)
}
return f
}
function parseFile (file, cb) {
if (!file) return cb(null, {})
log.verbose(file, "config file")
fs.readFile(file, function (er, data) {
// treat all errors as just an empty file
if (er) return cb(null, {})
var d = ini.parse(""+data)
, f = {}
Object.keys(d).forEach(function (k) {
f[k] = parseField(d[k], k)
})
cb(null, parseAuth(f))
})
}
function encryptAuth (config, cb) {
if (config.username && config._password) {
var b = new Buffer(config.username+":"+config._password)
config._auth = b.toString("base64")
}
delete config.username
delete config._password
return cb(null, config)
}
function parseAuth (config) {
//console.error("parsing config %j", config)
if (!config._auth) return config
var b = new Buffer(config._auth, "base64")
, unpw = b.toString().split(":")
, un = unpw.shift()
, pw = unpw.join(":")
config.username = un = (config.username || un)
config._password = pw = (config._password || pw)
b = new Buffer(un + ":" + pw)
config._auth = b.toString("base64")
return config
}
function save (which, cb) {
if (typeof which === "function") cb = which, which = null
if (!which) which = ["global", "user", "builtin"]
if (!Array.isArray(which)) which = [which]
var errState = null
, done = which.length
, failed = []
which.forEach(function (c) {
saveConfig(c, function (er) {
if (errState) return
if (er) return cb(errState = er)
if (-- done === 0) return cb()
})
})
}
function saveConfig (which, file, cb) {
if (typeof file === "function") cb = file, file = null
if (!file) {
switch (which) {
case "builtin":
file = path.resolve(__dirname, "../../npmrc")
break
case "global":
file = configList.get("globalconfig")
break
default:
file = configList.get("userconfig")
which = "user"
}
}
saveConfigfile
( file
, configList.list[TRANS[which]]
, which
, cb )
}
function saveConfigfile (file, config, which, cb) {
encryptAuth(config, function () { // ignore errors
var data = {}
Object.keys(config).forEach(function (k) {
data[k] = unParseField(config[k], k)
})
data = ini.stringify(data)
return (data.trim())
? writeConfigfile(file, data, which, cb)
: rimraf(file, cb)
})
}
function writeConfigfile (configfile, data, which, cb) {
data = data.split(/\r*\n/).join(eol)
var props = { type: "File", path: configfile }
if (which === "user") {
props.mode = 0600
if (typeof myUid === "number") {
props.uid = +myUid
props.gid = +myGid
}
} else {
props.mode = 0644
}
fstream.Writer(props)
.on("close", cb)
.on("error", cb)
.end(data)
}
function snapshot (which) {
var x = (!which) ? configList.snapshot
: configList.list[TRANS[which]] ? configList.list[TRANS[which]]
: undefined
if (!x) return
Object.keys(x).forEach(function (k) { if (k.match(/^_/)) delete x[k] })
return x
}
function get (key, which) {
return (!key) ? snapshot(which)
: (!which) ? configList.get(key) // resolved
: configList.list[TRANS[which]]
? envReplace(configList.list[TRANS[which]][key])
: undefined
}
function envReplace (f) {
if (typeof f !== "string" || !f) return f
// replace any ${ENV} values with the appropriate environ.
return f.replace(/\$\{([^}]+)\}/g, function (orig, name, i, s) {
return process.env[name] || orig
})
}
function del (key, which) {
if (!which) configList.list.forEach(function (l) {
delete l[key]
})
else if (configList.list[TRANS[which]]) {
delete configList.list[TRANS[which]]
}
}
function set (key, value, which) {
which = which || "cli"
if (configList.length === 1) {
return new Error("trying to set before loading")
}
return configList.list[TRANS[which]][key] = value
}