// 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 }