Installing dependencies.
This commit is contained in:
+251
@@ -0,0 +1,251 @@
|
||||
// class that describes a config key we know about
|
||||
// this keeps us from defining a config key and not
|
||||
// providing a default, description, etc.
|
||||
//
|
||||
// TODO: some kind of categorization system, so we can
|
||||
// say "these are for registry access", "these are for
|
||||
// version resolution" etc.
|
||||
|
||||
const required = ['type', 'description', 'default', 'key']
|
||||
|
||||
const allowed = [
|
||||
'default',
|
||||
'defaultDescription',
|
||||
'deprecated',
|
||||
'description',
|
||||
'flatten',
|
||||
'hint',
|
||||
'key',
|
||||
'short',
|
||||
'type',
|
||||
'typeDescription',
|
||||
'usage',
|
||||
'envExport',
|
||||
]
|
||||
|
||||
const {
|
||||
typeDefs: {
|
||||
semver: { type: semver },
|
||||
Umask: { type: Umask },
|
||||
url: { type: url },
|
||||
path: { type: path },
|
||||
},
|
||||
} = require('@npmcli/config')
|
||||
|
||||
class Definition {
|
||||
constructor (key, def) {
|
||||
this.key = key
|
||||
// if it's set falsey, don't export it, otherwise we do by default
|
||||
this.envExport = true
|
||||
Object.assign(this, def)
|
||||
this.validate()
|
||||
if (!this.defaultDescription) {
|
||||
this.defaultDescription = describeValue(this.default)
|
||||
}
|
||||
if (!this.typeDescription) {
|
||||
this.typeDescription = describeType(this.type)
|
||||
}
|
||||
// hint is only used for non-boolean values
|
||||
if (!this.hint) {
|
||||
if (this.type === Number) {
|
||||
this.hint = '<number>'
|
||||
} else {
|
||||
this.hint = `<${this.key}>`
|
||||
}
|
||||
}
|
||||
if (!this.usage) {
|
||||
this.usage = describeUsage(this)
|
||||
}
|
||||
}
|
||||
|
||||
validate () {
|
||||
for (const req of required) {
|
||||
if (!Object.prototype.hasOwnProperty.call(this, req)) {
|
||||
throw new Error(`config lacks ${req}: ${this.key}`)
|
||||
}
|
||||
}
|
||||
if (!this.key) {
|
||||
throw new Error(`config lacks key: ${this.key}`)
|
||||
}
|
||||
for (const field of Object.keys(this)) {
|
||||
if (!allowed.includes(field)) {
|
||||
throw new Error(`config defines unknown field ${field}: ${this.key}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// a textual description of this config, suitable for help output
|
||||
describe () {
|
||||
const description = unindent(this.description)
|
||||
const noEnvExport = this.envExport
|
||||
? ''
|
||||
: `
|
||||
This value is not exported to the environment for child processes.
|
||||
`
|
||||
const deprecated = !this.deprecated ? '' : `* DEPRECATED: ${unindent(this.deprecated)}\n`
|
||||
return wrapAll(`#### \`${this.key}\`
|
||||
|
||||
* Default: ${unindent(this.defaultDescription)}
|
||||
* Type: ${unindent(this.typeDescription)}
|
||||
${deprecated}
|
||||
${description}
|
||||
${noEnvExport}`)
|
||||
}
|
||||
}
|
||||
|
||||
const describeUsage = def => {
|
||||
let key = ''
|
||||
|
||||
// Single type
|
||||
if (!Array.isArray(def.type)) {
|
||||
if (def.short) {
|
||||
key = `-${def.short}|`
|
||||
}
|
||||
|
||||
if (def.type === Boolean && def.default !== false) {
|
||||
key = `${key}--no-${def.key}`
|
||||
} else {
|
||||
key = `${key}--${def.key}`
|
||||
}
|
||||
|
||||
if (def.type !== Boolean) {
|
||||
key = `${key} ${def.hint}`
|
||||
}
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
key = `--${def.key}`
|
||||
if (def.short) {
|
||||
key = `-${def.short}|--${def.key}`
|
||||
}
|
||||
|
||||
// Multiple types
|
||||
let types = def.type
|
||||
const multiple = types.includes(Array)
|
||||
const bool = types.includes(Boolean)
|
||||
|
||||
// null type means optional and doesn't currently affect usage output since
|
||||
// all non-optional params have defaults so we render everything as optional
|
||||
types = types.filter(t => t !== null && t !== Array && t !== Boolean)
|
||||
|
||||
if (!types.length) {
|
||||
return key
|
||||
}
|
||||
|
||||
let description
|
||||
if (!types.some(t => typeof t !== 'string')) {
|
||||
// Specific values, use specifics given
|
||||
description = `<${types.filter(d => d).join('|')}>`
|
||||
} else {
|
||||
// Generic values, use hint
|
||||
description = def.hint
|
||||
}
|
||||
|
||||
if (bool) {
|
||||
// Currently none of our multi-type configs with boolean values default to
|
||||
// false so all their hints should show `--no-`, if we ever add ones that
|
||||
// default to false we can branch the logic here
|
||||
key = `--no-${def.key}|${key}`
|
||||
}
|
||||
|
||||
const usage = `${key} ${description}`
|
||||
if (multiple) {
|
||||
return `${usage} [${usage} ...]`
|
||||
} else {
|
||||
return usage
|
||||
}
|
||||
}
|
||||
|
||||
const describeType = type => {
|
||||
if (Array.isArray(type)) {
|
||||
const descriptions = type.filter(t => t !== Array).map(t => describeType(t))
|
||||
|
||||
// [a] => "a"
|
||||
// [a, b] => "a or b"
|
||||
// [a, b, c] => "a, b, or c"
|
||||
// [a, Array] => "a (can be set multiple times)"
|
||||
// [a, Array, b] => "a or b (can be set multiple times)"
|
||||
const last = descriptions.length > 1 ? [descriptions.pop()] : []
|
||||
const oxford = descriptions.length > 1 ? ', or ' : ' or '
|
||||
const words = [descriptions.join(', ')].concat(last).join(oxford)
|
||||
const multiple = type.includes(Array) ? ' (can be set multiple times)' : ''
|
||||
return `${words}${multiple}`
|
||||
}
|
||||
|
||||
// Note: these are not quite the same as the description printed
|
||||
// when validation fails. In that case, we want to give the user
|
||||
// a bit more information to help them figure out what's wrong.
|
||||
switch (type) {
|
||||
case String:
|
||||
return 'String'
|
||||
case Number:
|
||||
return 'Number'
|
||||
case Umask:
|
||||
return 'Octal numeric string in range 0000..0777 (0..511)'
|
||||
case Boolean:
|
||||
return 'Boolean'
|
||||
case Date:
|
||||
return 'Date'
|
||||
case path:
|
||||
return 'Path'
|
||||
case semver:
|
||||
return 'SemVer string'
|
||||
case url:
|
||||
return 'URL'
|
||||
default:
|
||||
return describeValue(type)
|
||||
}
|
||||
}
|
||||
|
||||
// if it's a string, quote it. otherwise, just cast to string.
|
||||
const describeValue = val => (typeof val === 'string' ? JSON.stringify(val) : String(val))
|
||||
|
||||
const unindent = s => {
|
||||
// get the first \n followed by a bunch of spaces, and pluck off
|
||||
// that many spaces from the start of every line.
|
||||
const match = s.match(/\n +/)
|
||||
return !match ? s.trim() : s.split(match[0]).join('\n').trim()
|
||||
}
|
||||
|
||||
const wrap = s => {
|
||||
const cols = Math.min(Math.max(20, process.stdout.columns) || 80, 80) - 5
|
||||
return unindent(s)
|
||||
.split(/[ \n]+/)
|
||||
.reduce((left, right) => {
|
||||
const last = left.split('\n').pop()
|
||||
const join = last.length && last.length + right.length > cols ? '\n' : ' '
|
||||
return left + join + right
|
||||
})
|
||||
}
|
||||
|
||||
const wrapAll = s => {
|
||||
let inCodeBlock = false
|
||||
return s
|
||||
.split('\n\n')
|
||||
.map(block => {
|
||||
if (inCodeBlock || block.startsWith('```')) {
|
||||
inCodeBlock = !block.endsWith('```')
|
||||
return block
|
||||
}
|
||||
|
||||
if (block.charAt(0) === '*') {
|
||||
return (
|
||||
'* ' +
|
||||
block
|
||||
.slice(1)
|
||||
.trim()
|
||||
.split('\n* ')
|
||||
.map(li => {
|
||||
return wrap(li).replace(/\n/g, '\n ')
|
||||
})
|
||||
.join('\n* ')
|
||||
)
|
||||
} else {
|
||||
return wrap(block)
|
||||
}
|
||||
})
|
||||
.join('\n\n')
|
||||
}
|
||||
|
||||
module.exports = Definition
|
||||
+2369
File diff suppressed because it is too large
Load Diff
+20
@@ -0,0 +1,20 @@
|
||||
const definitions = require('./definitions.js')
|
||||
const localeCompare = require('@isaacs/string-locale-compare')('en')
|
||||
const describeAll = () => {
|
||||
// sort not-deprecated ones to the top
|
||||
/* istanbul ignore next - typically already sorted in the definitions file,
|
||||
* but this is here so that our help doc will stay consistent if we decide
|
||||
* to move them around. */
|
||||
const sort = ([keya, { deprecated: depa }], [keyb, { deprecated: depb }]) => {
|
||||
return depa && !depb ? 1
|
||||
: !depa && depb ? -1
|
||||
: localeCompare(keya, keyb)
|
||||
}
|
||||
return Object.entries(definitions).sort(sort)
|
||||
.map(([key, def]) => def.describe())
|
||||
.join(
|
||||
'\n\n<!-- automatically generated, do not edit manually -->\n' +
|
||||
'<!-- see lib/utils/config/definitions.js -->\n\n'
|
||||
)
|
||||
}
|
||||
module.exports = describeAll
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
// use the defined flattening function, and copy over any scoped
|
||||
// registries and registry-specific "nerfdart" configs verbatim
|
||||
//
|
||||
// TODO: make these getters so that we only have to make dirty
|
||||
// the thing that changed, and then flatten the fields that
|
||||
// could have changed when a config.set is called.
|
||||
//
|
||||
// TODO: move nerfdart auth stuff into a nested object that
|
||||
// is only passed along to paths that end up calling npm-registry-fetch.
|
||||
const definitions = require('./definitions.js')
|
||||
const flatten = (obj, flat = {}) => {
|
||||
for (const [key, val] of Object.entries(obj)) {
|
||||
const def = definitions[key]
|
||||
if (def && def.flatten) {
|
||||
def.flatten(key, obj, flat)
|
||||
} else if (/@.*:registry$/i.test(key) || /^\/\//.test(key)) {
|
||||
flat[key] = val
|
||||
}
|
||||
}
|
||||
|
||||
// XXX make this the bin/npm-cli.js file explicitly instead
|
||||
// otherwise using npm programmatically is a bit of a pain.
|
||||
flat.npmBin = require.main ? require.main.filename
|
||||
: /* istanbul ignore next - not configurable property */ undefined
|
||||
flat.nodeBin = process.env.NODE || process.execPath
|
||||
|
||||
// XXX should this be sha512? is it even relevant?
|
||||
flat.hashAlgorithm = 'sha1'
|
||||
|
||||
return flat
|
||||
}
|
||||
|
||||
module.exports = flatten
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
const flatten = require('./flatten.js')
|
||||
const definitions = require('./definitions.js')
|
||||
const describeAll = require('./describe-all.js')
|
||||
|
||||
// aliases where they get expanded into a completely different thing
|
||||
// these are NOT supported in the environment or npmrc files, only
|
||||
// expanded on the CLI.
|
||||
// TODO: when we switch off of nopt, use an arg parser that supports
|
||||
// more reasonable aliasing and short opts right in the definitions set.
|
||||
const shorthands = {
|
||||
'enjoy-by': ['--before'],
|
||||
d: ['--loglevel', 'info'],
|
||||
dd: ['--loglevel', 'verbose'],
|
||||
ddd: ['--loglevel', 'silly'],
|
||||
quiet: ['--loglevel', 'warn'],
|
||||
q: ['--loglevel', 'warn'],
|
||||
s: ['--loglevel', 'silent'],
|
||||
silent: ['--loglevel', 'silent'],
|
||||
verbose: ['--loglevel', 'verbose'],
|
||||
desc: ['--description'],
|
||||
help: ['--usage'],
|
||||
local: ['--no-global'],
|
||||
n: ['--no-yes'],
|
||||
no: ['--no-yes'],
|
||||
porcelain: ['--parseable'],
|
||||
readonly: ['--read-only'],
|
||||
reg: ['--registry'],
|
||||
iwr: ['--include-workspace-root'],
|
||||
}
|
||||
|
||||
for (const [key, { short }] of Object.entries(definitions)) {
|
||||
if (!short) {
|
||||
continue
|
||||
}
|
||||
// can be either an array or string
|
||||
for (const s of [].concat(short)) {
|
||||
shorthands[s] = [`--${key}`]
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
get defaults () {
|
||||
// NB: 'default' is a reserved word
|
||||
return Object.entries(definitions).map(([key, { default: def }]) => {
|
||||
return [key, def]
|
||||
}).reduce((defaults, [key, def]) => {
|
||||
defaults[key] = def
|
||||
return defaults
|
||||
}, {})
|
||||
},
|
||||
definitions,
|
||||
flatten,
|
||||
shorthands,
|
||||
describeAll,
|
||||
}
|
||||
Reference in New Issue
Block a user