// don't expand so that we only assemble the set of defaults when needed const configDefs = require('../utils/config/index.js') const mkdirp = require('mkdirp-infer-owner') const { dirname, resolve } = require('path') const { promisify } = require('util') const fs = require('fs') const readFile = promisify(fs.readFile) const writeFile = promisify(fs.writeFile) const { spawn } = require('child_process') const { EOL } = require('os') const ini = require('ini') const localeCompare = require('@isaacs/string-locale-compare')('en') const rpj = require('read-package-json-fast') const log = require('../utils/log-shim.js') // take an array of `[key, value, k2=v2, k3, v3, ...]` and turn into // { key: value, k2: v2, k3: v3 } const keyValues = args => { const kv = {} for (let i = 0; i < args.length; i++) { const arg = args[i].split('=') const key = arg.shift() const val = arg.length ? arg.join('=') : i < args.length - 1 ? args[++i] : '' kv[key.trim()] = val.trim() } return kv } const publicVar = k => { // _password if (k.startsWith('_')) { return false } // //localhost:8080/:_password if (k.startsWith('//') && k.includes(':_')) { return false } return true } const BaseCommand = require('../base-command.js') class Config extends BaseCommand { static description = 'Manage the npm configuration files' static name = 'config' static usage = [ 'set <key>=<value> [<key>=<value> ...]', 'get [<key> [<key> ...]]', 'delete <key> [<key> ...]', 'list [--json]', 'edit', ] static params = [ 'json', 'global', 'editor', 'location', 'long', ] static ignoreImplicitWorkspace = false async completion (opts) { const argv = opts.conf.argv.remain if (argv[1] !== 'config') { argv.unshift('config') } if (argv.length === 2) { const cmds = ['get', 'set', 'delete', 'ls', 'rm', 'edit'] if (opts.partialWord !== 'l') { cmds.push('list') } return cmds } const action = argv[2] switch (action) { case 'set': // todo: complete with valid values, if possible. if (argv.length > 3) { return [] } // fallthrough /* eslint no-fallthrough:0 */ case 'get': case 'delete': case 'rm': return Object.keys(configDefs.definitions) case 'edit': case 'list': case 'ls': default: return [] } } async execWorkspaces (args, filters) { log.warn('config', 'This command does not support workspaces.') return this.exec(args) } async exec ([action, ...args]) { log.disableProgress() try { switch (action) { case 'set': await this.set(args) break case 'get': await this.get(args) break case 'delete': case 'rm': case 'del': await this.del(args) break case 'list': case 'ls': await (this.npm.flatOptions.json ? this.listJson() : this.list()) break case 'edit': await this.edit() break default: throw this.usageError() } } finally { log.enableProgress() } } async set (args) { if (!args.length) { throw this.usageError() } const where = this.npm.flatOptions.location for (const [key, val] of Object.entries(keyValues(args))) { log.info('config', 'set %j %j', key, val) this.npm.config.set(key, val || '', where) if (!this.npm.config.validate(where)) { log.warn('config', 'omitting invalid config values') } } await this.npm.config.save(where) } async get (keys) { if (!keys.length) { return this.list() } const out = [] for (const key of keys) { if (!publicVar(key)) { throw new Error(`The ${key} option is protected, and cannot be retrieved in this way`) } const pref = keys.length > 1 ? `${key}=` : '' out.push(pref + this.npm.config.get(key)) } this.npm.output(out.join('\n')) } async del (keys) { if (!keys.length) { throw this.usageError() } const where = this.npm.flatOptions.location for (const key of keys) { this.npm.config.delete(key, where) } await this.npm.config.save(where) } async edit () { const e = this.npm.flatOptions.editor const where = this.npm.flatOptions.location const file = this.npm.config.data.get(where).source // save first, just to make sure it's synced up // this also removes all the comments from the last time we edited it. await this.npm.config.save(where) const data = ( await readFile(file, 'utf8').catch(() => '') ).replace(/\r\n/g, '\n') const entries = Object.entries(configDefs.defaults) const defData = entries.reduce((str, [key, val]) => { const obj = { [key]: val } const i = ini.stringify(obj) .replace(/\r\n/g, '\n') // normalizes output from ini.stringify .replace(/\n$/m, '') .replace(/^/g, '; ') .replace(/\n/g, '\n; ') .split('\n') return str + '\n' + i }, '') const tmpData = `;;;; ; npm ${where}config file: ${file} ; this is a simple ini-formatted file ; lines that start with semi-colons are comments ; run \`npm help 7 config\` for documentation of the various options ; ; Configs like \`@scope:registry\` map a scope to a given registry url. ; ; Configs like \`//<hostname>/:_authToken\` are auth that is restricted ; to the registry host specified. ${data.split('\n').sort(localeCompare).join('\n').trim()} ;;;; ; all available options shown below with default values ;;;; ${defData} `.split('\n').join(EOL) await mkdirp(dirname(file)) await writeFile(file, tmpData, 'utf8') await new Promise((resolve, reject) => { const [bin, ...args] = e.split(/\s+/) const editor = spawn(bin, [...args, file], { stdio: 'inherit' }) editor.on('exit', (code) => { if (code) { return reject(new Error(`editor process exited with code: ${code}`)) } return resolve() }) }) } async list () { const msg = [] // long does not have a flattener const long = this.npm.config.get('long') for (const [where, { data, source }] of this.npm.config.data.entries()) { if (where === 'default' && !long) { continue } const keys = Object.keys(data).sort(localeCompare) if (!keys.length) { continue } msg.push(`; "${where}" config from ${source}`, '') for (const k of keys) { const v = publicVar(k) ? JSON.stringify(data[k]) : '(protected)' const src = this.npm.config.find(k) const overridden = src !== where msg.push((overridden ? '; ' : '') + `${k} = ${v} ${overridden ? `; overridden by ${src}` : ''}`) } msg.push('') } if (!long) { msg.push( `; node bin location = ${process.execPath}`, `; node version = ${process.version}`, `; npm local prefix = ${this.npm.localPrefix}`, `; npm version = ${this.npm.version}`, `; cwd = ${process.cwd()}`, `; HOME = ${process.env.HOME}`, '; Run `npm config ls -l` to show all defaults.' ) msg.push('') } if (!this.npm.global) { const pkgPath = resolve(this.npm.prefix, 'package.json') const pkg = await rpj(pkgPath).catch(() => ({})) if (pkg.publishConfig) { msg.push(`; "publishConfig" from ${pkgPath}`) msg.push('; This set of config values will be used at publish-time.', '') const pkgKeys = Object.keys(pkg.publishConfig).sort(localeCompare) for (const k of pkgKeys) { const v = publicVar(k) ? JSON.stringify(pkg.publishConfig[k]) : '(protected)' msg.push(`${k} = ${v}`) } msg.push('') } } this.npm.output(msg.join('\n').trim()) } async listJson () { const publicConf = {} for (const key in this.npm.config.list[0]) { if (!publicVar(key)) { continue } publicConf[key] = this.npm.config.get(key) } this.npm.output(JSON.stringify(publicConf, null, 2)) } } module.exports = Config
Name | Type | Size | Permission | Actions |
---|---|---|---|---|
access.js | File | 5.45 KB | 0644 |
|
adduser.js | File | 2.2 KB | 0644 |
|
audit.js | File | 11.95 KB | 0644 |
|
bin.js | File | 729 B | 0644 |
|
birthday.js | File | 508 B | 0644 |
|
bugs.js | File | 815 B | 0644 |
|
cache.js | File | 7.08 KB | 0644 |
|
ci.js | File | 3.63 KB | 0644 |
|
completion.js | File | 8.91 KB | 0644 |
|
config.js | File | 8.11 KB | 0644 |
|
dedupe.js | File | 1.37 KB | 0644 |
|
deprecate.js | File | 2.06 KB | 0644 |
|
diff.js | File | 8.1 KB | 0644 |
|
dist-tag.js | File | 5.47 KB | 0644 |
|
docs.js | File | 447 B | 0644 |
|
doctor.js | File | 9.22 KB | 0644 |
|
edit.js | File | 2 KB | 0644 |
|
exec.js | File | 2.44 KB | 0644 |
|
explain.js | File | 3.55 KB | 0644 |
|
explore.js | File | 2.33 KB | 0644 |
|
find-dupes.js | File | 602 B | 0644 |
|
fund.js | File | 6.37 KB | 0644 |
|
get.js | File | 524 B | 0644 |
|
help-search.js | File | 5.62 KB | 0644 |
|
help.js | File | 4.53 KB | 0644 |
|
hook.js | File | 3.93 KB | 0644 |
|
init.js | File | 6.81 KB | 0644 |
|
install-ci-test.js | File | 377 B | 0644 |
|
install-test.js | File | 374 B | 0644 |
|
install.js | File | 5.11 KB | 0644 |
|
link.js | File | 5.02 KB | 0644 |
|
ll.js | File | 234 B | 0644 |
|
logout.js | File | 1.34 KB | 0644 |
|
ls.js | File | 16.94 KB | 0644 |
|
org.js | File | 4.2 KB | 0644 |
|
outdated.js | File | 8.84 KB | 0644 |
|
owner.js | File | 5.88 KB | 0644 |
|
pack.js | File | 2.36 KB | 0644 |
|
ping.js | File | 874 B | 0644 |
|
pkg.js | File | 3.47 KB | 0644 |
|
prefix.js | File | 343 B | 0644 |
|
profile.js | File | 11.25 KB | 0644 |
|
prune.js | File | 779 B | 0644 |
|
publish.js | File | 6.33 KB | 0644 |
|
query.js | File | 2.81 KB | 0644 |
|
rebuild.js | File | 2.16 KB | 0644 |
|
repo.js | File | 1.24 KB | 0644 |
|
restart.js | File | 351 B | 0644 |
|
root.js | File | 298 B | 0644 |
|
run-script.js | File | 6.9 KB | 0644 |
|
search.js | File | 2.72 KB | 0644 |
|
set-script.js | File | 2.63 KB | 0644 |
|
set.js | File | 572 B | 0644 |
|
shrinkwrap.js | File | 2.64 KB | 0644 |
|
star.js | File | 1.87 KB | 0644 |
|
stars.js | File | 1.03 KB | 0644 |
|
start.js | File | 341 B | 0644 |
|
stop.js | File | 336 B | 0644 |
|
team.js | File | 4.44 KB | 0644 |
|
test.js | File | 336 B | 0644 |
|
token.js | File | 6.79 KB | 0644 |
|
uninstall.js | File | 1.52 KB | 0644 |
|
unpublish.js | File | 4.51 KB | 0644 |
|
unstar.js | File | 182 B | 0644 |
|
update.js | File | 1.7 KB | 0644 |
|
version.js | File | 3.6 KB | 0644 |
|
view.js | File | 14.38 KB | 0644 |
|
whoami.js | File | 514 B | 0644 |
|