1/* eslint-disable standard/no-callback-literal */ 2module.exports = config 3 4var log = require('npmlog') 5var npm = require('./npm.js') 6var npmconf = require('./config/core.js') 7var fs = require('graceful-fs') 8var writeFileAtomic = require('write-file-atomic') 9var types = npmconf.defs.types 10var ini = require('ini') 11var editor = require('editor') 12var os = require('os') 13var path = require('path') 14var mkdirp = require('gentle-fs').mkdir 15var umask = require('./utils/umask') 16var usage = require('./utils/usage') 17var output = require('./utils/output') 18var noProgressTillDone = require('./utils/no-progress-while-running').tillDone 19 20config.usage = usage( 21 'config', 22 'npm config set <key> <value>' + 23 '\nnpm config get [<key>]' + 24 '\nnpm config delete <key>' + 25 '\nnpm config list [--json]' + 26 '\nnpm config edit' + 27 '\nnpm set <key> <value>' + 28 '\nnpm get [<key>]' 29) 30config.completion = function (opts, cb) { 31 var argv = opts.conf.argv.remain 32 if (argv[1] !== 'config') argv.unshift('config') 33 if (argv.length === 2) { 34 var cmds = ['get', 'set', 'delete', 'ls', 'rm', 'edit'] 35 if (opts.partialWord !== 'l') cmds.push('list') 36 return cb(null, cmds) 37 } 38 39 var action = argv[2] 40 switch (action) { 41 case 'set': 42 // todo: complete with valid values, if possible. 43 if (argv.length > 3) return cb(null, []) 44 // fallthrough 45 /* eslint no-fallthrough:0 */ 46 case 'get': 47 case 'delete': 48 case 'rm': 49 return cb(null, Object.keys(types)) 50 case 'edit': 51 case 'list': 52 case 'ls': 53 return cb(null, []) 54 default: 55 return cb(null, []) 56 } 57} 58 59// npm config set key value 60// npm config get key 61// npm config list 62function config (args, cb) { 63 var action = args.shift() 64 switch (action) { 65 case 'set': 66 return set(args[0], args[1], cb) 67 case 'get': 68 return get(args[0], cb) 69 case 'delete': 70 case 'rm': 71 case 'del': 72 return del(args[0], cb) 73 case 'list': 74 case 'ls': 75 return npm.config.get('json') ? listJson(cb) : list(cb) 76 case 'edit': 77 return edit(cb) 78 default: 79 return unknown(action, cb) 80 } 81} 82 83function edit (cb) { 84 var e = npm.config.get('editor') 85 var which = npm.config.get('global') ? 'global' : 'user' 86 var f = npm.config.get(which + 'config') 87 if (!e) return cb(new Error('No EDITOR config or environ set.')) 88 npm.config.save(which, function (er) { 89 if (er) return cb(er) 90 fs.readFile(f, 'utf8', function (er, data) { 91 if (er) data = '' 92 data = [ 93 ';;;;', 94 '; npm ' + (npm.config.get('global') 95 ? 'globalconfig' : 'userconfig') + ' file', 96 '; this is a simple ini-formatted file', 97 '; lines that start with semi-colons are comments.', 98 '; read `npm help config` for help on the various options', 99 ';;;;', 100 '', 101 data 102 ].concat([ 103 ';;;;', 104 '; all options with default values', 105 ';;;;' 106 ]).concat(Object.keys(npmconf.defaults).reduce(function (arr, key) { 107 var obj = {} 108 obj[key] = npmconf.defaults[key] 109 if (key === 'logstream') return arr 110 return arr.concat( 111 ini.stringify(obj) 112 .replace(/\n$/m, '') 113 .replace(/^/g, '; ') 114 .replace(/\n/g, '\n; ') 115 .split('\n')) 116 }, [])) 117 .concat(['']) 118 .join(os.EOL) 119 mkdirp(path.dirname(f), function (er) { 120 if (er) return cb(er) 121 writeFileAtomic( 122 f, 123 data, 124 function (er) { 125 if (er) return cb(er) 126 editor(f, { editor: e }, noProgressTillDone(cb)) 127 } 128 ) 129 }) 130 }) 131 }) 132} 133 134function del (key, cb) { 135 if (!key) return cb(new Error('no key provided')) 136 var where = npm.config.get('global') ? 'global' : 'user' 137 npm.config.del(key, where) 138 npm.config.save(where, cb) 139} 140 141function set (key, val, cb) { 142 if (key === undefined) { 143 return unknown('', cb) 144 } 145 if (val === undefined) { 146 if (key.indexOf('=') !== -1) { 147 var k = key.split('=') 148 key = k.shift() 149 val = k.join('=') 150 } else { 151 val = '' 152 } 153 } 154 key = key.trim() 155 val = val.trim() 156 log.info('config', 'set %j %j', key, val) 157 var where = npm.config.get('global') ? 'global' : 'user' 158 if (key.match(/umask/)) val = umask.fromString(val) 159 npm.config.set(key, val, where) 160 npm.config.save(where, cb) 161} 162 163function get (key, cb) { 164 if (!key) return list(cb) 165 if (!publicVar(key)) { 166 return cb(new Error('---sekretz---')) 167 } 168 var val = npm.config.get(key) 169 if (key.match(/umask/)) val = umask.toString(val) 170 output(val) 171 cb() 172} 173 174function sort (a, b) { 175 return a > b ? 1 : -1 176} 177 178function publicVar (k) { 179 return !(k.charAt(0) === '_' || k.indexOf(':_') !== -1) 180} 181 182function getKeys (data) { 183 return Object.keys(data).filter(publicVar).sort(sort) 184} 185 186function listJson (cb) { 187 const publicConf = npm.config.keys.reduce((publicConf, k) => { 188 var value = npm.config.get(k) 189 190 if (publicVar(k) && 191 // argv is not really config, it's command config 192 k !== 'argv' && 193 // logstream is a Stream, and would otherwise produce circular refs 194 k !== 'logstream') publicConf[k] = value 195 196 return publicConf 197 }, {}) 198 199 output(JSON.stringify(publicConf, null, 2)) 200 return cb() 201} 202 203function listFromSource (title, conf, long) { 204 var confKeys = getKeys(conf) 205 var msg = '' 206 207 if (confKeys.length) { 208 msg += '; ' + title + '\n' 209 confKeys.forEach(function (k) { 210 var val = JSON.stringify(conf[k]) 211 if (conf[k] !== npm.config.get(k)) { 212 if (!long) return 213 msg += '; ' + k + ' = ' + val + ' (overridden)\n' 214 } else msg += k + ' = ' + val + '\n' 215 }) 216 msg += '\n' 217 } 218 219 return msg 220} 221 222function list (cb) { 223 var msg = '' 224 var long = npm.config.get('long') 225 226 var cli = npm.config.sources.cli.data 227 var cliKeys = getKeys(cli) 228 if (cliKeys.length) { 229 msg += '; cli configs\n' 230 cliKeys.forEach(function (k) { 231 if (cli[k] && typeof cli[k] === 'object') return 232 if (k === 'argv') return 233 msg += k + ' = ' + JSON.stringify(cli[k]) + '\n' 234 }) 235 msg += '\n' 236 } 237 238 // env configs 239 msg += listFromSource('environment configs', npm.config.sources.env.data, long) 240 241 // project config file 242 var project = npm.config.sources.project 243 msg += listFromSource('project config ' + project.path, project.data, long) 244 245 // user config file 246 msg += listFromSource('userconfig ' + npm.config.get('userconfig'), npm.config.sources.user.data, long) 247 248 // global config file 249 msg += listFromSource('globalconfig ' + npm.config.get('globalconfig'), npm.config.sources.global.data, long) 250 251 // builtin config file 252 var builtin = npm.config.sources.builtin || {} 253 if (builtin && builtin.data) { 254 msg += listFromSource('builtin config ' + builtin.path, builtin.data, long) 255 } 256 257 // only show defaults if --long 258 if (!long) { 259 msg += '; node bin location = ' + process.execPath + '\n' + 260 '; cwd = ' + process.cwd() + '\n' + 261 '; HOME = ' + process.env.HOME + '\n' + 262 '; "npm config ls -l" to show all defaults.\n' 263 264 output(msg) 265 return cb() 266 } 267 268 var defaults = npmconf.defaults 269 var defKeys = getKeys(defaults) 270 msg += '; default values\n' 271 defKeys.forEach(function (k) { 272 if (defaults[k] && typeof defaults[k] === 'object') return 273 var val = JSON.stringify(defaults[k]) 274 if (defaults[k] !== npm.config.get(k)) { 275 msg += '; ' + k + ' = ' + val + ' (overridden)\n' 276 } else msg += k + ' = ' + val + '\n' 277 }) 278 msg += '\n' 279 280 output(msg) 281 return cb() 282} 283 284function unknown (action, cb) { 285 cb('Usage:\n' + config.usage) 286} 287