diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index 681fc6ebc3fa1..5e91aebe1dfe9 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -187,11 +187,13 @@ graph LR; npmcli-config-->nopt; npmcli-config-->npmcli-eslint-config["@npmcli/eslint-config"]; npmcli-config-->npmcli-map-workspaces["@npmcli/map-workspaces"]; + npmcli-config-->npmcli-mock-globals["@npmcli/mock-globals"]; npmcli-config-->npmcli-template-oss["@npmcli/template-oss"]; npmcli-config-->proc-log; npmcli-config-->read-package-json-fast; npmcli-config-->semver; npmcli-docs-->ignore-walk; + npmcli-docs-->npmcli-config["@npmcli/config"]; npmcli-docs-->npmcli-eslint-config["@npmcli/eslint-config"]; npmcli-docs-->npmcli-template-oss["@npmcli/template-oss"]; npmcli-docs-->semver; @@ -644,10 +646,12 @@ graph LR; npmcli-arborist-->tcompare; npmcli-arborist-->treeverse; npmcli-arborist-->walk-up-path; + npmcli-config-->ci-info; npmcli-config-->ini; npmcli-config-->nopt; npmcli-config-->npmcli-eslint-config["@npmcli/eslint-config"]; npmcli-config-->npmcli-map-workspaces["@npmcli/map-workspaces"]; + npmcli-config-->npmcli-mock-globals["@npmcli/mock-globals"]; npmcli-config-->npmcli-template-oss["@npmcli/template-oss"]; npmcli-config-->proc-log; npmcli-config-->read-package-json-fast; @@ -659,6 +663,7 @@ graph LR; npmcli-docs-->ignore-walk; npmcli-docs-->isaacs-string-locale-compare["@isaacs/string-locale-compare"]; npmcli-docs-->jsdom; + npmcli-docs-->npmcli-config["@npmcli/config"]; npmcli-docs-->npmcli-eslint-config["@npmcli/eslint-config"]; npmcli-docs-->npmcli-template-oss["@npmcli/template-oss"]; npmcli-docs-->rehype-stringify; @@ -826,8 +831,8 @@ packages higher up the chain. - @npmcli/arborist - @npmcli/metavuln-calculator - pacote, libnpmhook, libnpmorg, libnpmsearch, libnpmteam, npm-profile - - npm-registry-fetch, @npmcli/package-json, libnpmversion - - @npmcli/git, make-fetch-happen, @npmcli/config, init-package-json - - @npmcli/installed-package-contents, @npmcli/map-workspaces, cacache, npm-pick-manifest, @npmcli/run-script, read-package-json, promzard - - @npmcli/docs, @npmcli/fs, npm-bundled, read-package-json-fast, unique-filename, npm-install-checks, npm-package-arg, npm-packlist, normalize-package-data, bin-links, nopt, npmlog, parse-conflict-json, @npmcli/mock-globals, read - - @npmcli/eslint-config, @npmcli/template-oss, ignore-walk, semver, npm-normalize-package-bin, @npmcli/name-from-folder, json-parse-even-better-errors, fs-minipass, ssri, unique-slug, @npmcli/promise-spawn, hosted-git-info, proc-log, validate-npm-package-name, @npmcli/node-gyp, minipass-fetch, @npmcli/query, cmd-shim, read-cmd-shim, write-file-atomic, abbrev, are-we-there-yet, gauge, minify-registry-metadata, ini, @npmcli/disparity-colors, mute-stream, npm-audit-report, npm-user-validate + - @npmcli/docs, npm-registry-fetch, @npmcli/package-json, libnpmversion + - @npmcli/config, @npmcli/git, make-fetch-happen, init-package-json + - @npmcli/map-workspaces, @npmcli/installed-package-contents, cacache, npm-pick-manifest, @npmcli/run-script, read-package-json, promzard + - read-package-json-fast, nopt, @npmcli/mock-globals, @npmcli/fs, npm-bundled, unique-filename, npm-install-checks, npm-package-arg, npm-packlist, normalize-package-data, bin-links, npmlog, parse-conflict-json, read + - @npmcli/name-from-folder, json-parse-even-better-errors, npm-normalize-package-bin, ini, abbrev, proc-log, semver, @npmcli/eslint-config, @npmcli/template-oss, ignore-walk, fs-minipass, ssri, unique-slug, @npmcli/promise-spawn, hosted-git-info, validate-npm-package-name, @npmcli/node-gyp, minipass-fetch, @npmcli/query, cmd-shim, read-cmd-shim, write-file-atomic, are-we-there-yet, gauge, minify-registry-metadata, @npmcli/disparity-colors, mute-stream, npm-audit-report, npm-user-validate diff --git a/bin/npx-cli.js b/bin/npx-cli.js index 75090aed41f1f..17d96fb26267c 100755 --- a/bin/npx-cli.js +++ b/bin/npx-cli.js @@ -24,7 +24,7 @@ const removed = new Set([ ...removedOpts, ]) -const { definitions, shorthands } = require('../lib/utils/config/index.js') +const { definitions, shorthands } = require('@npmcli/config/lib/definitions') const npmSwitches = Object.entries(definitions) .filter(([key, { type }]) => type === Boolean || (Array.isArray(type) && type.includes(Boolean))) diff --git a/docs/lib/index.js b/docs/lib/index.js index e999d6f6159fd..5d4ae7af3457b 100644 --- a/docs/lib/index.js +++ b/docs/lib/index.js @@ -3,7 +3,7 @@ const { join, basename, resolve } = require('path') const transformHTML = require('./transform-html.js') const { version } = require('../../lib/npm.js') const { aliases } = require('../../lib/utils/cmd-list') -const { shorthands, definitions } = require('../../lib/utils/config/index.js') +const { shorthands, definitions } = require('@npmcli/config/lib/definitions') const DOC_EXT = '.md' diff --git a/docs/package.json b/docs/package.json index 841071fb13c1b..78b62e540bfba 100644 --- a/docs/package.json +++ b/docs/package.json @@ -21,6 +21,7 @@ }, "devDependencies": { "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/config": "^6.1.7", "@npmcli/eslint-config": "^4.0.0", "@npmcli/template-oss": "4.14.1", "front-matter": "^4.0.2", diff --git a/lib/base-command.js b/lib/base-command.js index 598964ce524e3..e763820cb052b 100644 --- a/lib/base-command.js +++ b/lib/base-command.js @@ -2,7 +2,7 @@ const { relative } = require('path') -const definitions = require('./utils/config/definitions.js') +const { definitions } = require('@npmcli/config/lib/definitions') const getWorkspaces = require('./workspaces/get-workspaces.js') const { aliases: cmdAliases } = require('./utils/cmd-list') diff --git a/lib/commands/completion.js b/lib/commands/completion.js index 38205ad8fc49e..59113c50560bc 100644 --- a/lib/commands/completion.js +++ b/lib/commands/completion.js @@ -34,7 +34,7 @@ const nopt = require('nopt') const { resolve } = require('path') const Npm = require('../npm.js') -const { definitions, shorthands } = require('../utils/config/index.js') +const { definitions, shorthands } = require('@npmcli/config/lib/definitions') const { commands, aliases, deref } = require('../utils/cmd-list.js') const configNames = Object.keys(definitions) const shorthandNames = Object.keys(shorthands) diff --git a/lib/commands/config.js b/lib/commands/config.js index b49cdd648f9d0..8e8358fc50b7b 100644 --- a/lib/commands/config.js +++ b/lib/commands/config.js @@ -1,6 +1,3 @@ -// don't expand so that we only assemble the set of defaults when needed -const configDefs = require('../utils/config/index.js') - const { mkdir, readFile, writeFile } = require('fs/promises') const { dirname, resolve } = require('path') const { spawn } = require('child_process') @@ -8,6 +5,7 @@ const { EOL } = require('os') const ini = require('ini') const localeCompare = require('@isaacs/string-locale-compare')('en') const pkgJson = require('@npmcli/package-json') +const { defaults, definitions } = require('@npmcli/config/lib/definitions') const log = require('../utils/log-shim.js') // These are the configs that we can nerf-dart. Not all of them currently even @@ -102,7 +100,7 @@ class Config extends BaseCommand { case 'get': case 'delete': case 'rm': - return Object.keys(configDefs.definitions) + return Object.keys(definitions) case 'edit': case 'list': case 'ls': @@ -219,7 +217,7 @@ class Config extends BaseCommand { const data = ( await readFile(file, 'utf8').catch(() => '') ).replace(/\r\n/g, '\n') - const entries = Object.entries(configDefs.defaults) + const entries = Object.entries(defaults) const defData = entries.reduce((str, [key, val]) => { const obj = { [key]: val } const i = ini.stringify(obj) diff --git a/lib/commands/doctor.js b/lib/commands/doctor.js index 19262e537dbe0..fa491ae6177ff 100644 --- a/lib/commands/doctor.js +++ b/lib/commands/doctor.js @@ -9,9 +9,7 @@ const semver = require('semver') const { promisify } = require('util') const log = require('../utils/log-shim.js') const ping = require('../utils/ping.js') -const { - registry: { default: defaultRegistry }, -} = require('../utils/config/definitions.js') +const { defaults } = require('@npmcli/config/lib/definitions') const lstat = promisify(fs.lstat) const readdir = promisify(fs.readdir) const access = promisify(fs.access) @@ -364,10 +362,10 @@ class Doctor extends BaseCommand { } async checkNpmRegistry () { - if (this.npm.flatOptions.registry !== defaultRegistry) { - throw `Try \`npm config set registry=${defaultRegistry}\`` + if (this.npm.flatOptions.registry !== defaults.registry) { + throw `Try \`npm config set registry=${defaults.registry}\`` } else { - return `using default registry (${defaultRegistry})` + return `using default registry (${defaults.registry})` } } diff --git a/lib/commands/publish.js b/lib/commands/publish.js index 54707278f9691..8d2aa9e0e47f6 100644 --- a/lib/commands/publish.js +++ b/lib/commands/publish.js @@ -15,7 +15,7 @@ const { getContents, logTar } = require('../utils/tar.js') // keys that npm supports in .npmrc files and elsewhere. We *may* want to // revisit this at some point, and have a minimal set that's a SemVer-major // change that ought to get a RFC written on it. -const { flatten } = require('../utils/config/index.js') +const { flatten } = require('@npmcli/config/lib/definitions') const pkgJson = require('@npmcli/package-json') const BaseCommand = require('../base-command.js') diff --git a/lib/commands/unpublish.js b/lib/commands/unpublish.js index 66985297b9574..402f8f30efff8 100644 --- a/lib/commands/unpublish.js +++ b/lib/commands/unpublish.js @@ -4,7 +4,7 @@ const npa = require('npm-package-arg') const npmFetch = require('npm-registry-fetch') const pkgJson = require('@npmcli/package-json') -const { flatten } = require('../utils/config/index.js') +const { flatten } = require('@npmcli/config/lib/definitions') const getIdentity = require('../utils/get-identity.js') const log = require('../utils/log-shim') const otplease = require('../utils/otplease.js') diff --git a/lib/npm.js b/lib/npm.js index f08ef32c180d9..36118fe345019 100644 --- a/lib/npm.js +++ b/lib/npm.js @@ -6,7 +6,7 @@ const fs = require('fs/promises') // Patch the global fs module here at the app level require('graceful-fs').gracefulify(require('fs')) -const { definitions, flatten, shorthands } = require('./utils/config/index.js') +const { definitions, flatten, shorthands } = require('@npmcli/config/lib/definitions') const usage = require('./utils/npm-usage.js') const LogFile = require('./utils/log-file.js') const Timers = require('./utils/timers.js') diff --git a/package-lock.json b/package-lock.json index 0d306ca4f4393..ea9ce92c57c58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -182,6 +182,7 @@ "license": "ISC", "devDependencies": { "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/config": "^6.1.7", "@npmcli/eslint-config": "^4.0.0", "@npmcli/template-oss": "4.14.1", "front-matter": "^4.0.2", @@ -15668,6 +15669,7 @@ "license": "ISC", "dependencies": { "@npmcli/map-workspaces": "^3.0.2", + "ci-info": "^3.8.0", "ini": "^4.1.0", "nopt": "^7.0.0", "proc-log": "^3.0.0", @@ -15677,6 +15679,7 @@ }, "devDependencies": { "@npmcli/eslint-config": "^4.0.0", + "@npmcli/mock-globals": "^1.0.0", "@npmcli/template-oss": "4.14.1", "tap": "^16.3.4" }, diff --git a/scripts/fish-completion.js b/scripts/fish-completion.js index 6357c1032fe56..4686befd8ba7f 100644 --- a/scripts/fish-completion.js +++ b/scripts/fish-completion.js @@ -3,7 +3,7 @@ const fs = require('fs/promises') const { resolve } = require('path') const { commands, aliases } = require('../lib/utils/cmd-list.js') -const { definitions } = require('../lib/utils/config/index.js') +const { definitions } = require('@npmcli/config/lib/definitions') async function main () { const file = resolve(__dirname, '..', 'lib', 'utils', 'completion.fish') diff --git a/tap-snapshots/test/lib/commands/config.js.test.cjs b/tap-snapshots/test/lib/commands/config.js.test.cjs index d4fc5de7a1c4e..7c590b1cf3fb4 100644 --- a/tap-snapshots/test/lib/commands/config.js.test.cjs +++ b/tap-snapshots/test/lib/commands/config.js.test.cjs @@ -161,6 +161,7 @@ exports[`test/lib/commands/config.js TAP config list --json > output matches sna "workspaces": null, "workspaces-update": true, "yes": null, + "npm-version": "{NPM-VERSION}", "metrics-registry": "https://registry.npmjs.org/" } ` @@ -256,6 +257,7 @@ message = "%s" metrics-registry = "https://registry.npmjs.org/" node-options = null noproxy = [""] +npm-version = "{NPM-VERSION}" offline = false omit = [] omit-lockfile-registry-resolved = false @@ -341,6 +343,18 @@ userconfig = "{HOME}/.npmrc" ` exports[`test/lib/commands/config.js TAP config list > output matches snapshot 1`] = ` +; "global" config from {GLOBALPREFIX}/npmrc + +globalloaded = "yes" + +; "user" config from {HOME}/.npmrc + +userloaded = "yes" + +; "project" config from {LOCALPREFIX}/.npmrc + +projectloaded = "yes" + ; "cli" config from command line options cache = "{NPMDIR}/test/lib/commands/tap-testdir-config-config-list-sandbox/cache" @@ -383,6 +397,7 @@ global-prefix = "{LOCALPREFIX}" globalconfig = "{GLOBALPREFIX}/npmrc" init-module = "{HOME}/.npm-init.js" local-prefix = "{LOCALPREFIX}" +npm-version = "{NPM-VERSION}" ; prefix = "{LOCALPREFIX}" ; overridden by cli user-agent = "npm/{NPM-VERSION} node/{NODE-VERSION} {PLATFORM} {ARCH} workspaces/false" ; userconfig = "{HOME}/.npmrc" ; overridden by cli diff --git a/tap-snapshots/test/lib/docs.js.test.cjs b/tap-snapshots/test/lib/docs.js.test.cjs index 92708f23199c8..3986b74f72561 100644 --- a/tap-snapshots/test/lib/docs.js.test.cjs +++ b/tap-snapshots/test/lib/docs.js.test.cjs @@ -2390,7 +2390,7 @@ Object { "noProxy": "", "npmBin": "{CWD}/{TESTDIR}/docs.js", "npmCommand": "version", - "npmVersion": "1.1.1", + "npmVersion": "3.3.3", "npxCache": "{CWD}/cache/_npx", "offline": false, "omit": Array [], diff --git a/test/fixtures/mock-npm.js b/test/fixtures/mock-npm.js index b91ee8a3933a3..4646e79146e86 100644 --- a/test/fixtures/mock-npm.js +++ b/test/fixtures/mock-npm.js @@ -47,19 +47,28 @@ const setGlobalNodeModules = (globalDir) => { return globalDir } -const getMockNpm = async (t, { mocks, init, load, npm: npmOpts }) => { - const mock = { - ...mockLogs(mocks), - outputs: [], - outputErrors: [], - joinedOutput: () => mock.outputs.map(o => o.join(' ')).join('\n'), - } - - const Npm = tmock(t, '{LIB}/npm.js', { +const buildMocks = (t, mocks) => { + const allMocks = { '{LIB}/utils/update-notifier.js': async () => {}, ...mocks, - ...mock.logMocks, - }) + } + // The definitions must be mocked since they are a singleton that reads from + // process and environs to build defaults in order to break the requiure + // cache. We also need to mock them with any mocks that were passed in for the + // test in case those mocks are for things like ci-info which is used there. + const definitions = '@npmcli/config/lib/definitions' + allMocks[definitions] = tmock(t, definitions, allMocks) + + return allMocks +} + +const getMockNpm = async (t, { mocks, init, load, npm: npmOpts }) => { + const { logMocks, logs, display } = mockLogs(mocks) + const allMocks = buildMocks(t, { ...mocks, ...logMocks }) + const Npm = tmock(t, '{LIB}/npm.js', allMocks) + + const outputs = [] + const outputErrors = [] class MockNpm extends Npm { async exec (...args) { @@ -86,23 +95,29 @@ const getMockNpm = async (t, { mocks, init, load, npm: npmOpts }) => { } output (...args) { - mock.outputs.push(args) + outputs.push(args) } outputError (...args) { - mock.outputErrors.push(args) + outputErrors.push(args) } } - mock.Npm = MockNpm - if (init) { - mock.npm = new MockNpm(npmOpts) - if (load) { - await mock.npm.load() - } + const npm = init ? new MockNpm(npmOpts) : null + if (npm && load) { + await npm.load() } - return mock + return { + Npm: MockNpm, + npm, + outputs, + outputErrors, + joinedOutput: () => outputs.map(o => o.join(' ')).join('\n'), + logMocks, + logs, + display, + } } const mockNpms = new Map() @@ -127,6 +142,7 @@ const setupMockNpm = async (t, { globals = {}, npm: npmOpts = {}, argv: rawArgv = [], + ...r } = {}) => { // easy to accidentally forget to pass in tap if (!(t instanceof tap.Test)) { diff --git a/test/fixtures/sandbox.js b/test/fixtures/sandbox.js index 01a5e562fd9b2..2c4e5c2968a38 100644 --- a/test/fixtures/sandbox.js +++ b/test/fixtures/sandbox.js @@ -81,7 +81,7 @@ class Sandbox extends EventEmitter { get: this[_get].bind(this), set: this[_set].bind(this), }) - this[_proxy].env = {} + this[_proxy].env = { ...options.env } this[_proxy].argv = [] test.cleanSnapshot = this.cleanSnapshot.bind(this) @@ -262,7 +262,9 @@ class Sandbox extends EventEmitter { const mockedLogs = mockLogs(this[_mocks]) this[_logs] = mockedLogs.logs + const definitions = this[_test].mock('@npmcli/config/lib/definitions') const Npm = this[_test].mock('../../lib/npm.js', { + '@npmcli/config/lib/definitions': definitions, '../../lib/utils/update-notifier.js': async () => {}, ...this[_mocks], ...mockedLogs.logMocks, @@ -313,7 +315,9 @@ class Sandbox extends EventEmitter { const mockedLogs = mockLogs(this[_mocks]) this[_logs] = mockedLogs.logs + const definitions = this[_test].mock('@npmcli/config/lib/definitions') const Npm = this[_test].mock('../../lib/npm.js', { + '@npmcli/config/lib/definitions': definitions, '../../lib/utils/update-notifier.js': async () => {}, ...this[_mocks], ...mockedLogs.logMocks, diff --git a/test/lib/cli-entry.js b/test/lib/cli-entry.js index b436304c30047..22dca32f1a934 100644 --- a/test/lib/cli-entry.js +++ b/test/lib/cli-entry.js @@ -135,11 +135,6 @@ t.test('load error calls error handler', async t => { const err = new Error('test load error') const { cli, exitHandlerCalled } = await cliMock(t, { mocks: { - '{LIB}/utils/config/index.js': { - definitions: null, - flatten: null, - shorthands: null, - }, '@npmcli/config': class BadConfig { async load () { throw err diff --git a/test/lib/commands/config.js b/test/lib/commands/config.js index 7d5140bffc45b..b54096fd216f2 100644 --- a/test/lib/commands/config.js +++ b/test/lib/commands/config.js @@ -33,8 +33,6 @@ t.test('config ignores workspaces', async t => { }) t.test('config list', async t => { - const sandbox = new Sandbox(t) - const temp = t.testdir({ global: { npmrc: 'globalloaded=yes', @@ -50,7 +48,8 @@ t.test('config list', async t => { const project = join(temp, 'project') const home = join(temp, 'home') - await sandbox.run('config', ['list'], { global, project, home }) + const sandbox = new Sandbox(t, { global, project, home }) + await sandbox.run('config', ['list']) t.matchSnapshot(sandbox.output, 'output matches snapshot') }) @@ -137,8 +136,8 @@ t.test('config delete single key', async t => { '.npmrc': 'access=public\nall=true', }) - const sandbox = new Sandbox(t) - await sandbox.run('config', ['delete', 'access'], { home }) + const sandbox = new Sandbox(t, { home }) + await sandbox.run('config', ['delete', 'access']) t.equal(sandbox.config.get('access'), null, 'acces should be defaulted') @@ -152,8 +151,8 @@ t.test('config delete multiple keys', async t => { '.npmrc': 'access=public\nall=true\naudit=false', }) - const sandbox = new Sandbox(t) - await sandbox.run('config', ['delete', 'access', 'all'], { home }) + const sandbox = new Sandbox(t, { home }) + await sandbox.run('config', ['delete', 'access', 'all']) t.equal(sandbox.config.get('access'), null, 'access should be defaulted') t.equal(sandbox.config.get('all'), false, 'all should be defaulted') @@ -169,8 +168,8 @@ t.test('config delete key --location=global', async t => { npmrc: 'access=public\nall=true', }) - const sandbox = new Sandbox(t) - await sandbox.run('config', ['delete', 'access', '--location=global'], { global }) + const sandbox = new Sandbox(t, { global }) + await sandbox.run('config', ['delete', 'access', '--location=global']) t.equal(sandbox.config.get('access', 'global'), undefined, 'access should be defaulted') @@ -184,8 +183,8 @@ t.test('config delete key --global', async t => { npmrc: 'access=public\nall=true', }) - const sandbox = new Sandbox(t) - await sandbox.run('config', ['delete', 'access', '--global'], { global }) + const sandbox = new Sandbox(t, { global }) + await sandbox.run('config', ['delete', 'access', '--global']) t.equal(sandbox.config.get('access', 'global'), undefined, 'access should no longer be set') @@ -408,8 +407,7 @@ t.test('config edit', async t => { const EDITOR = 'vim' const editor = spawk.spawn(EDITOR).exit(0) - const sandbox = new Sandbox(t, { home }) - sandbox.process.env.EDITOR = EDITOR + const sandbox = new Sandbox(t, { home, env: { EDITOR } }) await sandbox.run('config', ['edit']) t.ok(editor.called, 'editor was spawned') diff --git a/test/lib/docs.js b/test/lib/docs.js index 5e7bfe45a93cc..30279d0e9184c 100644 --- a/test/lib/docs.js +++ b/test/lib/docs.js @@ -5,7 +5,7 @@ const localeCompare = require('@isaacs/string-locale-compare')('en') const docs = require('@npmcli/docs') const { load: loadMockNpm } = require('../fixtures/mock-npm.js') -const { definitions } = require('../../lib/utils/config/index.js') +const { definitions } = require('@npmcli/config/lib/definitions') const cmdList = require('../../lib/utils/cmd-list.js') const pkg = require('../../package.json') const { cleanCwd } = require('../fixtures/clean-snapshot.js') @@ -37,6 +37,12 @@ t.test('flat options', async t => { const { npm } = await loadMockNpm(t, { command: 'version', exec: true, + npm: ({ other }) => ({ + npmRoot: other, + }), + otherDirs: { + 'package.json': JSON.stringify({ version: '1.1.1' }), + }, globals: { 'process.env': { EDITOR: '{EDITOR}', @@ -47,8 +53,14 @@ t.test('flat options', async t => { 'process.arch': '{ARCH}', }, mocks: { - 'ci-info': { name: '{CI}' }, - '{ROOT}/package.json': { version: '1.1.1' }, + 'ci-info': { isCI: true, name: '{CI}' }, + // currently the config version is read from npmRoot and the Npm + // constructor version is read from the root package.json file. This + // snapshot is meant to explicitly show that they are read from different + // places. In the future we should probably only read it from npmRoot, + // which would require more changes to ensure nothing depends on it being + // a static prop on the constructor + '{ROOT}/package.json': { version: '3.3.3' }, }, }) diff --git a/lib/utils/config/definition.js b/workspaces/config/lib/definitions/definition.js similarity index 97% rename from lib/utils/config/definition.js rename to workspaces/config/lib/definitions/definition.js index 54e522c355b93..333a91928526e 100644 --- a/lib/utils/config/definition.js +++ b/workspaces/config/lib/definitions/definition.js @@ -25,13 +25,11 @@ const allowed = [ ] const { - typeDefs: { - semver: { type: semver }, - Umask: { type: Umask }, - url: { type: url }, - path: { type: path }, - }, -} = require('@npmcli/config') + semver: { type: semver }, + Umask: { type: Umask }, + url: { type: url }, + path: { type: path }, +} = require('../type-defs.js') class Definition { constructor (key, def) { diff --git a/lib/utils/config/definitions.js b/workspaces/config/lib/definitions/definitions.js similarity index 99% rename from lib/utils/config/definitions.js rename to workspaces/config/lib/definitions/definitions.js index f86c3ddff81b5..fe5cafa1922d9 100644 --- a/lib/utils/config/definitions.js +++ b/workspaces/config/lib/definitions/definitions.js @@ -3,12 +3,12 @@ module.exports = definitions const Definition = require('./definition.js') -const { version: npmVersion } = require('../../../package.json') const ciInfo = require('ci-info') const querystring = require('querystring') -const { isWindows } = require('../is-windows.js') const { join } = require('path') +const isWindows = process.platform === 'win32' + // used by cafile flattening to flatOptions.ca const fs = require('fs') const maybeReadFile = file => { @@ -87,19 +87,15 @@ const cacheRoot = (isWindows && process.env.LOCALAPPDATA) || '~' const cacheExtra = isWindows ? 'npm-cache' : '.npm' const cache = `${cacheRoot}/${cacheExtra}` -const Config = require('@npmcli/config') - // TODO: refactor these type definitions so that they are less // weird to pull out of the config module. // TODO: use better type definition/validation API, nopt's is so weird. const { - typeDefs: { - semver: { type: semver }, - Umask: { type: Umask }, - url: { type: url }, - path: { type: path }, - }, -} = Config + semver: { type: semver }, + Umask: { type: Umask }, + url: { type: url }, + path: { type: path }, +} = require('../type-defs.js') const define = (key, def) => { /* istanbul ignore if - this should never happen, prevents mistakes below */ @@ -2233,7 +2229,7 @@ define('user-agent', { } flatOptions.userAgent = value.replace(/\{node-version\}/gi, process.version) - .replace(/\{npm-version\}/gi, npmVersion) + .replace(/\{npm-version\}/gi, obj['npm-version']) .replace(/\{platform\}/gi, process.platform) .replace(/\{arch\}/gi, process.arch) .replace(/\{workspaces\}/gi, inWorkspaces) diff --git a/lib/utils/config/index.js b/workspaces/config/lib/definitions/index.js similarity index 86% rename from lib/utils/config/index.js rename to workspaces/config/lib/definitions/index.js index d393aec2297d2..748f306bd2ce3 100644 --- a/lib/utils/config/index.js +++ b/workspaces/config/lib/definitions/index.js @@ -31,6 +31,16 @@ const flatten = (obj, flat = {}) => { return flat } +const definitionProps = Object.entries(definitions) + .reduce((acc, [key, { short = [], default: d }]) => { + // can be either an array or string + for (const s of [].concat(short)) { + acc.shorthands[s] = [`--${key}`] + } + acc.defaults[key] = d + return acc + }, { shorthands: {}, defaults: {} }) + // 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. @@ -55,23 +65,11 @@ const shorthands = { readonly: ['--read-only'], reg: ['--registry'], iwr: ['--include-workspace-root'], - ...Object.entries(definitions).reduce((acc, [key, { short = [] }]) => { - // can be either an array or string - for (const s of [].concat(short)) { - acc[s] = [`--${key}`] - } - return acc - }, {}), + ...definitionProps.shorthands, } module.exports = { - get defaults () { - // NB: 'default' is a reserved word - return Object.entries(definitions).reduce((acc, [key, { default: d }]) => { - acc[key] = d - return acc - }, {}) - }, + defaults: definitionProps.defaults, definitions, flatten, shorthands, diff --git a/workspaces/config/lib/errors.js b/workspaces/config/lib/errors.js index fa3e20798542a..6161509108ff0 100644 --- a/workspaces/config/lib/errors.js +++ b/workspaces/config/lib/errors.js @@ -4,6 +4,7 @@ class ErrInvalidAuth extends Error { constructor (problems) { let message = 'Invalid auth configuration found: ' message += problems.map((problem) => { + // istanbul ignore else if (problem.action === 'delete') { return `\`${problem.key}\` is not allowed in ${problem.where} config` } else if (problem.action === 'rename') { diff --git a/workspaces/config/lib/index.js b/workspaces/config/lib/index.js index 84a009851d8a2..0e19d32e3f8b4 100644 --- a/workspaces/config/lib/index.js +++ b/workspaces/config/lib/index.js @@ -305,10 +305,20 @@ class Config { this.loadGlobalPrefix() this.loadHome() - this.#loadObject({ + const defaultsObject = { ...this.defaults, prefix: this.globalPrefix, - }, 'default', 'default values') + } + + try { + defaultsObject['npm-version'] = require(join(this.npmPath, 'package.json')).version + } catch { + // in some weird state where the passed in npmPath does not have a package.json + // this will never happen in npm, but is guarded here in case this is consumed + // in other ways + tests + } + + this.#loadObject(defaultsObject, 'default', 'default values') const { data } = this.data.get('default') @@ -446,7 +456,7 @@ class Config { nopt.invalidHandler = (k, val, type) => this.invalidHandler(k, val, type, obj.source, where) - nopt.clean(obj.data, this.types, this.typeDefs) + nopt.clean(obj.data, this.types, typeDefs) nopt.invalidHandler = null return obj[_valid] diff --git a/workspaces/config/lib/umask.js b/workspaces/config/lib/umask.js index 195fad2386702..4d9ebbdc96545 100644 --- a/workspaces/config/lib/umask.js +++ b/workspaces/config/lib/umask.js @@ -1,5 +1,9 @@ class Umask {} const parse = val => { + // this is run via nopt and parse field where everything is + // converted to a string first, ignoring coverage for now + // instead of figuring out what is happening under the hood in nopt + // istanbul ignore else if (typeof val === 'string') { if (/^0o?[0-7]+$/.test(val)) { return parseInt(val.replace(/^0o?/, ''), 8) @@ -8,15 +12,16 @@ const parse = val => { } else { throw new Error(`invalid umask value: ${val}`) } + } else { + if (typeof val !== 'number') { + throw new Error(`invalid umask value: ${val}`) + } + val = Math.floor(val) + if (val < 0 || val > 511) { + throw new Error(`invalid umask value: ${val}`) + } + return val } - if (typeof val !== 'number') { - throw new Error(`invalid umask value: ${val}`) - } - val = Math.floor(val) - if (val < 0 || val > 511) { - throw new Error(`invalid umask value: ${val}`) - } - return val } const validate = (data, k, val) => { diff --git a/workspaces/config/map.js b/workspaces/config/map.js deleted file mode 100644 index 0b263fbecedf1..0000000000000 --- a/workspaces/config/map.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = t => t.replace(/^test/, 'lib') diff --git a/workspaces/config/package.json b/workspaces/config/package.json index e0190fc3ac53f..0b7d07387615f 100644 --- a/workspaces/config/package.json +++ b/workspaces/config/package.json @@ -24,8 +24,6 @@ "template-oss-apply": "template-oss-apply --force" }, "tap": { - "check-coverage": true, - "coverage-map": "map.js", "nyc-arg": [ "--exclude", "tap-snapshots/**" @@ -33,11 +31,13 @@ }, "devDependencies": { "@npmcli/eslint-config": "^4.0.0", + "@npmcli/mock-globals": "^1.0.0", "@npmcli/template-oss": "4.14.1", "tap": "^16.3.4" }, "dependencies": { "@npmcli/map-workspaces": "^3.0.2", + "ci-info": "^3.8.0", "ini": "^4.1.0", "nopt": "^7.0.0", "proc-log": "^3.0.0", diff --git a/workspaces/config/scripts/example.js b/workspaces/config/scripts/example.js deleted file mode 100644 index bbb2992a5de8a..0000000000000 --- a/workspaces/config/scripts/example.js +++ /dev/null @@ -1,43 +0,0 @@ -const Config = require('../') - -const shorthands = require('../test/fixtures/shorthands.js') -const types = require('../test/fixtures/types.js') -const defaults = require('../test/fixtures/defaults.js') - -const npmPath = __dirname - -const timers = {} -process.on('time', k => { - if (timers[k]) { - throw new Error('duplicate timer: ' + k) - } - timers[k] = process.hrtime() -}) -process.on('timeEnd', k => { - if (!timers[k]) { - throw new Error('ending unstarted timer: ' + k) - } - const dur = process.hrtime(timers[k]) - delete timers[k] - console.error(`\x1B[2m${k}\x1B[22m`, Math.round(dur[0] * 1e6 + dur[1] / 1e3) / 1e3) - delete timers[k] -}) - -process.on('log', (level, ...message) => - console.log(`\x1B[31m${level}\x1B[39m`, ...message)) - -const priv = /(^|:)_([^=]+)=(.*)\n/g -const ini = require('ini') -const config = new Config({ shorthands, types, defaults, npmPath }) -config.load().then(async () => { - for (const [where, { data, source }] of config.data.entries()) { - console.log(`; ${where} from ${source}`) - if (where === 'default' && !config.get('long')) { - console.log('; not shown, run with -l to show all\n') - } else { - console.log(ini.stringify(data).replace(priv, '$1_$2=******\n')) - } - } - console.log('argv:', { raw: config.argv, parsed: config.parsedArgv }) - return undefined -}).catch(() => {}) diff --git a/tap-snapshots/test/lib/utils/config/definition.js.test.cjs b/workspaces/config/tap-snapshots/test/definitions/definition.js.test.cjs similarity index 85% rename from tap-snapshots/test/lib/utils/config/definition.js.test.cjs rename to workspaces/config/tap-snapshots/test/definitions/definition.js.test.cjs index bf4dc30a041f7..e405d599029ec 100644 --- a/tap-snapshots/test/lib/utils/config/definition.js.test.cjs +++ b/workspaces/config/tap-snapshots/test/definitions/definition.js.test.cjs @@ -5,7 +5,7 @@ * Make sure to inspect the output below. Do not ignore changes! */ 'use strict' -exports[`test/lib/utils/config/definition.js TAP basic definition > description of deprecated thing 1`] = ` +exports[`test/definitions/definition.js TAP basic definition > description of deprecated thing 1`] = ` #### \`deprecated\` * Default: A number bigger than 1 @@ -19,7 +19,18 @@ not even once. ` -exports[`test/lib/utils/config/definition.js TAP basic definition > human-readable description 1`] = ` +exports[`test/definitions/definition.js TAP basic definition > description of deprecated thing 2`] = ` +#### \`exclusive\` + +* Default: 1234 +* Type: Number + +a number + +This config can not be used with: \`x\` +` + +exports[`test/definitions/definition.js TAP basic definition > human-readable description 1`] = ` #### \`key\` * Default: "some default value" @@ -30,7 +41,7 @@ just a test thingie ` -exports[`test/lib/utils/config/definition.js TAP long description > cols=-1 1`] = ` +exports[`test/definitions/definition.js TAP long description > cols=-1 1`] = ` #### \`walden\` * Default: true @@ -100,7 +111,7 @@ with (multiple) { ` -exports[`test/lib/utils/config/definition.js TAP long description > cols=0 1`] = ` +exports[`test/definitions/definition.js TAP long description > cols=0 1`] = ` #### \`walden\` * Default: true @@ -170,7 +181,7 @@ with (multiple) { ` -exports[`test/lib/utils/config/definition.js TAP long description > cols=40 1`] = ` +exports[`test/definitions/definition.js TAP long description > cols=40 1`] = ` #### \`walden\` * Default: true @@ -210,7 +221,7 @@ with (multiple) { ` -exports[`test/lib/utils/config/definition.js TAP long description > cols=9000 1`] = ` +exports[`test/definitions/definition.js TAP long description > cols=9000 1`] = ` #### \`walden\` * Default: true @@ -241,7 +252,7 @@ with (multiple) { ` -exports[`test/lib/utils/config/definition.js TAP long description > cols=NaN 1`] = ` +exports[`test/definitions/definition.js TAP long description > cols=NaN 1`] = ` #### \`walden\` * Default: true diff --git a/workspaces/config/tap-snapshots/test/type-description.js.test.cjs b/workspaces/config/tap-snapshots/test/type-description.js.test.cjs index 367731d1d4702..5157d6708b0a4 100644 --- a/workspaces/config/tap-snapshots/test/type-description.js.test.cjs +++ b/workspaces/config/tap-snapshots/test/type-description.js.test.cjs @@ -7,13 +7,9 @@ 'use strict' exports[`test/type-description.js TAP > must match snapshot 1`] = ` Object { - "_exit": Array [ - "boolean value (true or false)", - ], - "_single": Array [ - Object { - "exotic": "not part of normal typedefs", - }, + "_auth": Array [ + null, + Function String(), ], "access": Array [ null, @@ -31,25 +27,21 @@ Object { "dev", "development", ], - "always-auth": Array [ - "boolean value (true or false)", - ], "audit": Array [ "boolean value (true or false)", ], "audit-level": Array [ + null, + "info", "low", "moderate", "high", "critical", "none", - null, ], "auth-type": Array [ "legacy", - "sso", - "saml", - "oauth", + "web", ], "before": Array [ null, @@ -71,15 +63,6 @@ Object { "cache": Array [ "valid filesystem path", ], - "cache-lock-retries": Array [ - "numeric value", - ], - "cache-lock-stale": Array [ - "numeric value", - ], - "cache-lock-wait": Array [ - "numeric value", - ], "cache-max": Array [ "numeric value", ], @@ -96,6 +79,10 @@ Object { null, Function String(), ], + "ci-name": Array [ + null, + Function String(), + ], "cidr": Array [ null, Function String(), @@ -109,6 +96,7 @@ Object { "boolean value (true or false)", ], "depth": Array [ + null, "numeric value", ], "description": Array [ @@ -117,6 +105,31 @@ Object { "dev": Array [ "boolean value (true or false)", ], + "diff": Array [ + Function String(), + Function Array(), + ], + "diff-dst-prefix": Array [ + Function String(), + ], + "diff-ignore-all-space": Array [ + "boolean value (true or false)", + ], + "diff-name-only": Array [ + "boolean value (true or false)", + ], + "diff-no-prefix": Array [ + "boolean value (true or false)", + ], + "diff-src-prefix": Array [ + Function String(), + ], + "diff-text": Array [ + "boolean value (true or false)", + ], + "diff-unified": Array [ + "numeric value", + ], "dry-run": Array [ "boolean value (true or false)", ], @@ -138,9 +151,15 @@ Object { "fetch-retry-mintimeout": Array [ "numeric value", ], + "fetch-timeout": Array [ + "numeric value", + ], "force": Array [ "boolean value (true or false)", ], + "foreground-scripts": Array [ + "boolean value (true or false)", + ], "format-package-lock": Array [ "boolean value (true or false)", ], @@ -172,9 +191,6 @@ Object { "if-present": Array [ "boolean value (true or false)", ], - "ignore-prepublish": Array [ - "boolean value (true or false)", - ], "ignore-scripts": Array [ "boolean value (true or false)", ], @@ -188,6 +204,9 @@ Object { "include-staged": Array [ "boolean value (true or false)", ], + "include-workspace-root": Array [ + "boolean value (true or false)", + ], "init-author-email": Array [ Function String(), ], @@ -207,6 +226,34 @@ Object { "init-version": Array [ "full valid SemVer string", ], + "init.author.email": Array [ + Function String(), + ], + "init.author.name": Array [ + Function String(), + ], + "init.author.url": Array [ + "", + "full url with \\"http://\\"", + ], + "init.license": Array [ + Function String(), + ], + "init.module": Array [ + "valid filesystem path", + ], + "init.version": Array [ + "full valid SemVer string", + ], + "install-links": Array [ + "boolean value (true or false)", + ], + "install-strategy": Array [ + "hoisted", + "nested", + "shallow", + "linked", + ], "json": Array [ "boolean value (true or false)", ], @@ -223,17 +270,34 @@ Object { "link": Array [ "boolean value (true or false)", ], + "location": Array [ + "global", + "user", + "project", + ], + "lockfile-version": Array [ + null, + 1, + 2, + 3, + "1", + "2", + "3", + ], "loglevel": Array [ "silent", "error", "warn", "notice", "http", - "timing", "info", "verbose", "silly", ], + "logs-dir": Array [ + null, + "valid filesystem path", + ], "logs-max": Array [ "numeric value", ], @@ -246,24 +310,11 @@ Object { "message": Array [ Function String(), ], - "metrics-registry": Array [ - null, - Function String(), - ], - "multiple-numbers": Array [ - Function Array(), - "numeric value", - ], "node-options": Array [ null, Function String(), ], - "node-version": Array [ - null, - "full valid SemVer string", - ], "noproxy": Array [ - null, Function String(), Function Array(), ], @@ -276,20 +327,25 @@ Object { "optional", "peer", ], + "omit-lockfile-registry-resolved": Array [ + "boolean value (true or false)", + ], "only": Array [ null, - "dev", - "development", "prod", "production", ], "optional": Array [ + null, "boolean value (true or false)", ], "otp": Array [ null, Function String(), ], + "pack-destination": Array [ + Function String(), + ], "package": Array [ Function String(), Function Array(), @@ -303,6 +359,9 @@ Object { "parseable": Array [ "boolean value (true or false)", ], + "prefer-dedupe": Array [ + "boolean value (true or false)", + ], "prefer-offline": Array [ "boolean value (true or false)", ], @@ -316,11 +375,18 @@ Object { Function String(), ], "production": Array [ + null, "boolean value (true or false)", ], "progress": Array [ "boolean value (true or false)", ], + "provenance": Array [ + "boolean value (true or false)", + ], + "provenance-file": Array [ + "valid filesystem path", + ], "proxy": Array [ null, false, @@ -333,11 +399,13 @@ Object { "boolean value (true or false)", ], "registry": Array [ - null, "full url with \\"http://\\"", ], - "rollback": Array [ - "boolean value (true or false)", + "replace-registry-host": Array [ + "npmjs", + "never", + "always", + Function String(), ], "save": Array [ "boolean value (true or false)", @@ -354,6 +422,9 @@ Object { "save-optional": Array [ "boolean value (true or false)", ], + "save-peer": Array [ + "boolean value (true or false)", + ], "save-prefix": Array [ Function String(), ], @@ -367,13 +438,7 @@ Object { null, Function String(), ], - "scripts-prepend-node-path": Array [ - "boolean value (true or false)", - "auto", - "warn-only", - ], "searchexclude": Array [ - null, Function String(), ], "searchlimit": Array [ @@ -385,9 +450,6 @@ Object { "searchstaleness": Array [ "numeric value", ], - "send-metrics": Array [ - "boolean value (true or false)", - ], "shell": Array [ Function String(), ], @@ -400,13 +462,8 @@ Object { "sign-git-tag": Array [ "boolean value (true or false)", ], - "sso-poll-frequency": Array [ - "numeric value", - ], - "sso-type": Array [ - null, - "oauth", - "saml", + "strict-peer-deps": Array [ + "boolean value (true or false)", ], "strict-ssl": Array [ "boolean value (true or false)", @@ -450,5 +507,24 @@ Object { "viewer": Array [ Function String(), ], + "which": Array [ + null, + "numeric value", + ], + "workspace": Array [ + Function String(), + Function Array(), + ], + "workspaces": Array [ + null, + "boolean value (true or false)", + ], + "workspaces-update": Array [ + "boolean value (true or false)", + ], + "yes": Array [ + null, + "boolean value (true or false)", + ], } ` diff --git a/test/lib/utils/config/definition.js b/workspaces/config/test/definitions/definition.js similarity index 93% rename from test/lib/utils/config/definition.js rename to workspaces/config/test/definitions/definition.js index a17a1a09a2240..ae5ed9ebf4334 100644 --- a/test/lib/utils/config/definition.js +++ b/workspaces/config/test/definitions/definition.js @@ -1,13 +1,11 @@ const t = require('tap') -const Definition = require('../../../../lib/utils/config/definition.js') +const Definition = require('../../lib/definitions/definition.js') const { - typeDefs: { - semver: { type: semver }, - Umask: { type: Umask }, - url: { type: url }, - path: { type: path }, - }, -} = require('@npmcli/config') + semver: { type: semver }, + Umask: { type: Umask }, + url: { type: url }, + path: { type: path }, +} = require('../../lib/type-defs.js') t.test('basic definition', async t => { const def = new Definition('key', { @@ -39,6 +37,14 @@ t.test('basic definition', async t => { }) t.matchSnapshot(deprecated.describe(), 'description of deprecated thing') + const exclusive = new Definition('exclusive', { + default: 1234, + type: Number, + description: 'a number', + exclusive: ['x'], + }) + t.matchSnapshot(exclusive.describe(), 'description of deprecated thing') + const nullOrUmask = new Definition('key', { default: null, type: [null, Umask], diff --git a/test/lib/utils/config/definitions.js b/workspaces/config/test/definitions/definitions.js similarity index 95% rename from test/lib/utils/config/definitions.js rename to workspaces/config/test/definitions/definitions.js index 8775824c6c131..2c608870b51f1 100644 --- a/test/lib/utils/config/definitions.js +++ b/workspaces/config/test/definitions/definitions.js @@ -1,15 +1,15 @@ const t = require('tap') const { resolve } = require('path') const mockGlobals = require('@npmcli/mock-globals') -const tmock = require('../../../fixtures/tmock') -const pkg = require('../../../../package.json') // have to fake the node version, or else it'll only pass on this one mockGlobals(t, { 'process.version': 'v14.8.0', 'process.env.NODE_ENV': undefined }) -const mockDefs = (mocks = {}) => tmock(t, '{LIB}/utils/config/definitions.js', mocks) +const mockDefs = (mocks = {}) => t.mock('../../lib/definitions/definitions.js', mocks) -const isWin = (isWindows) => ({ '{LIB}/utils/is-windows.js': { isWindows } }) +const isWin = (t, isWindows) => { + mockGlobals(t, { 'process.platform': isWindows ? 'win32' : 'not-windows' }) +} t.test('basic flattening function camelCases from css-case', t => { const flat = {} @@ -41,9 +41,9 @@ t.test('editor', t => { SYSTEMROOT: 'C:\\Windows', }, }) - const defsWin = mockDefs(isWin(true)) + const defsWin = mockDefs(isWin(t, true)) t.equal(defsWin.editor.default, 'C:\\Windows\\notepad.exe') - const defsNix = mockDefs(isWin(false)) + const defsNix = mockDefs(isWin(t, false)) t.equal(defsNix.editor.default, 'vi') t.end() }) @@ -53,20 +53,20 @@ t.test('editor', t => { t.test('shell', t => { t.test('windows, env.ComSpec then cmd.exe', t => { mockGlobals(t, { 'process.env.ComSpec': 'command.com' }) - const defsComSpec = mockDefs(isWin(true)) + const defsComSpec = mockDefs(isWin(t, true)) t.equal(defsComSpec.shell.default, 'command.com') mockGlobals(t, { 'process.env.ComSpec': undefined }) - const defsNoComSpec = mockDefs(isWin(true)) + const defsNoComSpec = mockDefs(isWin(t, true)) t.equal(defsNoComSpec.shell.default, 'cmd') t.end() }) t.test('nix, SHELL then sh', t => { mockGlobals(t, { 'process.env.SHELL': '/usr/local/bin/bash' }) - const defsShell = mockDefs(isWin(false)) + const defsShell = mockDefs(isWin(t, false)) t.equal(defsShell.shell.default, '/usr/local/bin/bash') mockGlobals(t, { 'process.env.SHELL': undefined }) - const defsNoShell = mockDefs(isWin(false)) + const defsNoShell = mockDefs(isWin(t, false)) t.equal(defsNoShell.shell.default, 'sh') t.end() }) @@ -134,14 +134,14 @@ t.test('unicode allowed?', t => { t.test('cache', t => { mockGlobals(t, { 'process.env.LOCALAPPDATA': 'app/data/local' }) - const defsWinLocalAppData = mockDefs(isWin(true)) + const defsWinLocalAppData = mockDefs(isWin(t, true)) t.equal(defsWinLocalAppData.cache.default, 'app/data/local/npm-cache') mockGlobals(t, { 'process.env.LOCALAPPDATA': undefined }) - const defsWinNoLocalAppData = mockDefs(isWin(true)) + const defsWinNoLocalAppData = mockDefs(isWin(t, true)) t.equal(defsWinNoLocalAppData.cache.default, '~/npm-cache') - const defsNix = mockDefs(isWin(false)) + const defsNix = mockDefs(isWin(t, false)) t.equal(defsNix.cache.default, '~/.npm') const flat = {} @@ -743,11 +743,13 @@ t.test('detect CI', t => { }) t.test('user-agent', t => { + const npmVersion = '1.2.3' const obj = { + 'npm-version': npmVersion, 'user-agent': mockDefs()['user-agent'].default, } const flat = {} - const expectNoCI = `npm/${pkg.version} node/${process.version} ` + + const expectNoCI = `npm/${npmVersion} node/${process.version} ` + `${process.platform} ${process.arch} workspaces/false` mockDefs()['user-agent'].flatten('user-agent', obj, flat) t.equal(flat.userAgent, expectNoCI) @@ -835,6 +837,19 @@ t.test('location', t => { // global = false leaves location unaltered t.strictSame(flat, { global: false, location: 'user' }) t.strictSame(obj, { global: false, location: 'user' }) + + obj.global = false + obj.location = 'global' + delete flat.global + delete flat.location + + mockDefs().global.flatten('global', obj, flat) + mockDefs().location.flatten('location', obj, flat) + // location = global sets global to true + t.strictSame(flat, { global: true, location: 'global' }) + // global here is still false because flattening doesn't modify the object + t.strictSame(obj, { global: false, location: 'global' }) + t.end() }) diff --git a/test/lib/utils/config/index.js b/workspaces/config/test/definitions/index.js similarity index 83% rename from test/lib/utils/config/index.js rename to workspaces/config/test/definitions/index.js index 010ec34888da4..26c2a7fdf84e2 100644 --- a/test/lib/utils/config/index.js +++ b/workspaces/config/test/definitions/index.js @@ -1,6 +1,6 @@ const t = require('tap') -const config = require('../../../../lib/utils/config/index.js') -const definitions = require('../../../../lib/utils/config/definitions.js') +const config = require('../../lib/definitions/index.js') +const definitions = require('../../lib/definitions/definitions.js') const mockGlobals = require('@npmcli/mock-globals') t.test('defaults', t => { @@ -10,10 +10,6 @@ t.test('defaults', t => { 'init-module': definitions['init-module'].default, }) - // is a getter, so changes are reflected - definitions.registry.default = 'https://example.com' - t.strictSame(config.defaults.registry, 'https://example.com') - t.end() }) diff --git a/workspaces/config/test/fixtures/defaults.js b/workspaces/config/test/fixtures/defaults.js deleted file mode 100644 index 322ceb018bf31..0000000000000 --- a/workspaces/config/test/fixtures/defaults.js +++ /dev/null @@ -1,143 +0,0 @@ -module.exports = { - methane: 'CH4', - access: null, - all: false, - 'allow-same-version': false, - 'always-auth': false, - also: null, - audit: true, - 'audit-level': null, - 'auth-type': 'legacy', - - before: null, - 'bin-links': true, - browser: null, - - ca: null, - cafile: null, - - cache: '~/.npm', - - 'cache-lock-stale': 60000, - 'cache-lock-retries': 10, - 'cache-lock-wait': 10000, - - 'cache-max': Infinity, - 'cache-min': 10, - - cert: null, - - cidr: null, - - color: true, - call: '', - depth: 0, - description: true, - dev: false, - 'dry-run': false, - editor: 'vim', - 'engine-strict': false, - force: false, - 'format-package-lock': true, - - fund: true, - - 'fetch-retries': 2, - 'fetch-retry-factor': 10, - 'fetch-retry-mintimeout': 10000, - 'fetch-retry-maxtimeout': 60000, - - git: 'git', - 'git-tag-version': true, - 'commit-hooks': true, - - global: false, - 'global-style': false, - heading: 'npm', - 'if-present': false, - include: [], - 'include-staged': false, - 'ignore-prepublish': false, - 'ignore-scripts': false, - 'init-module': '~/.npm-init.js', - 'init-author-name': '', - 'init-author-email': '', - 'init-author-url': '', - 'init-version': '1.0.0', - 'init-license': 'ISC', - json: false, - key: null, - 'legacy-bundling': false, - 'legacy-peer-deps': false, - link: false, - 'local-address': undefined, - loglevel: 'notice', - 'logs-max': 10, - long: false, - maxsockets: 50, - message: '%s', - 'metrics-registry': null, - 'node-options': null, - 'node-version': process.version, - offline: false, - omit: [], - only: null, - optional: true, - otp: null, - package: [], - 'package-lock': true, - 'package-lock-only': false, - parseable: false, - 'prefer-offline': false, - 'prefer-online': false, - preid: '', - production: true, - progress: true, - proxy: null, - 'https-proxy': null, - noproxy: null, - 'user-agent': 'npm/{npm-version} ' + - 'node/{node-version} ' + - '{platform} ' + - '{arch} ' + - '{ci}', - 'read-only': false, - 'rebuild-bundle': true, - registry: 'https://registry.npmjs.org/', - rollback: true, - save: true, - 'save-bundle': false, - 'save-dev': false, - 'save-exact': false, - 'save-optional': false, - 'save-prefix': '^', - 'save-prod': false, - scope: '', - 'script-shell': null, - 'scripts-prepend-node-path': 'warn-only', - searchopts: '', - searchexclude: null, - searchlimit: 20, - searchstaleness: 15 * 60, - 'send-metrics': false, - shell: '/bin/sh', - shrinkwrap: true, - 'sign-git-commit': false, - 'sign-git-tag': false, - 'sso-poll-frequency': 500, - 'sso-type': 'oauth', - 'strict-ssl': true, - tag: 'latest', - 'tag-version-prefix': 'v', - timing: false, - unicode: /UTF-?8$/i.test( - process.env.LC_ALL || process.env.LC_CTYPE || process.env.LANG - ), - 'update-notifier': true, - usage: false, - userconfig: '~/.npmrc', - umask: 0o22, - version: false, - versions: false, - viewer: 'man', -} diff --git a/workspaces/config/test/fixtures/definitions.js b/workspaces/config/test/fixtures/definitions.js deleted file mode 100644 index f17cb1031dde3..0000000000000 --- a/workspaces/config/test/fixtures/definitions.js +++ /dev/null @@ -1,2623 +0,0 @@ -const url = require('url') -const path = require('path') -const { join } = path -const querystring = require('querystring') -const semver = require('semver') -const Umask = require('../../lib/type-defs.js').Umask.type - -// dumped out of npm/cli/lib/utils/config/definitions.js - -// used by cafile flattening to flatOptions.ca -const fs = require('fs') -const maybeReadFile = file => { - if (file.includes('WEIRD-ERROR')) { - throw Object.assign(new Error('weird error'), { code: 'EWEIRD' }) - } - - try { - return fs.readFileSync(file, 'utf8') - } catch (er) { - if (er.code !== 'ENOENT') { - throw er - } - return null - } -} - -const definitions = module.exports = { - methane: { - envExport: false, - type: String, - typeDescription: 'Greenhouse Gas', - default: 'CH4', - description: ` - This is bad for the environment, for our children, do not put it there. - `, - }, - 'multiple-numbers': { - key: 'multiple-numbers', - default: [], - type: [ - Array, - Number, - ], - descriptions: 'one or more numbers', - }, - _auth: { - key: '_auth', - default: null, - type: [ - null, - String, - ], - description: ` - A basic-auth string to use when authenticating against the npm registry. - - Warning: This should generally not be set via a command-line option. It - is safer to use a registry-provided authentication bearer token stored in - the ~/.npmrc file by running \`npm login\`. - `, - defaultDescription: 'null', - typeDescription: 'null or String', - }, - access: { - key: 'access', - default: null, - defaultDescription: ` - 'restricted' for scoped packages, 'public' for unscoped packages - `, - type: [ - null, - 'restricted', - 'public', - ], - description: ` - When publishing scoped packages, the access level defaults to - \`restricted\`. If you want your scoped package to be publicly viewable - (and installable) set \`--access=public\`. The only valid values for - \`access\` are \`public\` and \`restricted\`. Unscoped packages _always_ - have an access level of \`public\`. - - Note: Using the \`--access\` flag on the \`npm publish\` command will only - set the package access level on the initial publish of the package. Any - subsequent \`npm publish\` commands using the \`--access\` flag will not - have an effect to the access level. To make changes to the access level - after the initial publish use \`npm access\`. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - typeDescription: 'null, "restricted", or "public"', - }, - all: { - key: 'all', - default: false, - type: Boolean, - short: 'a', - description: ` - When running \`npm outdated\` and \`npm ls\`, setting \`--all\` will show - all outdated or installed packages, rather than only those directly - depended upon by the current project. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - 'allow-same-version': { - key: 'allow-same-version', - default: false, - type: Boolean, - description: ` - Prevents throwing an error when \`npm version\` is used to set the new - version to the same value as the current version. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - also: { - key: 'also', - default: null, - type: [ - null, - 'dev', - 'development', - ], - description: ` - When set to \`dev\` or \`development\`, this is an alias for - \`--include=dev\`. - `, - deprecated: 'Please use --include=dev instead.', - flatten (key, obj, flatOptions) { - if (!/^dev(elopment)?$/.test(obj.also)) { - return - } - - // add to include, and call the omit flattener - obj.include = obj.include || [] - obj.include.push('dev') - definitions.omit.flatten('omit', obj, flatOptions) - }, - defaultDescription: 'null', - typeDescription: 'null, "dev", or "development"', - }, - audit: { - key: 'audit', - default: true, - type: Boolean, - description: ` - When "true" submit audit reports alongside the current npm command to the - default registry and all registries configured for scopes. See the - documentation for [\`npm audit\`](/commands/npm-audit) for details on what - is submitted. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'true', - typeDescription: 'Boolean', - }, - 'audit-level': { - key: 'audit-level', - default: null, - type: [ - 'low', - 'moderate', - 'high', - 'critical', - 'none', - null, - ], - description: ` - The minimum level of vulnerability for \`npm audit\` to exit with - a non-zero exit code. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'null', - typeDescription: '"low", "moderate", "high", "critical", "none", or null', - }, - 'auth-type': { - key: 'auth-type', - default: 'legacy', - type: [ - 'legacy', - 'sso', - 'saml', - 'oauth', - ], - deprecated: ` - This method of SSO/SAML/OAuth is deprecated and will be removed in - a future version of npm in favor of web-based login. - `, - description: ` - What authentication strategy to use with \`adduser\`/\`login\`. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: '"legacy"', - typeDescription: '"legacy", "sso", "saml", or "oauth"', - }, - before: { - key: 'before', - default: null, - type: [ - null, - Date, - ], - description: ` - If passed to \`npm install\`, will rebuild the npm tree such that only - versions that were available **on or before** the \`--before\` time get - installed. If there's no versions available for the current set of - direct dependencies, the command will error. - - If the requested version is a \`dist-tag\` and the given tag does not - pass the \`--before\` filter, the most recent version less than or equal - to that tag will be used. For example, \`foo@latest\` might install - \`foo@1.2\` even though \`latest\` is \`2.0\`. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'null', - typeDescription: 'null or Date', - }, - 'bin-links': { - key: 'bin-links', - default: true, - type: Boolean, - description: ` - Tells npm to create symlinks (or \`.cmd\` shims on Windows) for package - executables. - - Set to false to have it not do this. This can be used to work around the - fact that some file systems don't support symlinks, even on ostensibly - Unix systems. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'true', - typeDescription: 'Boolean', - }, - browser: { - key: 'browser', - default: null, - defaultDescription: ` - OS X: \`"open"\`, Windows: \`"start"\`, Others: \`"xdg-open"\` - `, - type: [ - null, - Boolean, - String, - ], - description: ` - The browser that is called by npm commands to open websites. - - Set to \`false\` to suppress browser behavior and instead print urls to - terminal. - - Set to \`true\` to use default system URL opener. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - typeDescription: 'null, Boolean, or String', - }, - ca: { - key: 'ca', - default: null, - type: [ - null, - String, - Array, - ], - description: ` - The Certificate Authority signing certificate that is trusted for SSL - connections to the registry. Values should be in PEM format (Windows - calls it "Base-64 encoded X.509 (.CER)") with newlines replaced by the - string "\\n". For example: - - \`\`\`ini - ca="-----BEGIN CERTIFICATE-----\\nXXXX\\nXXXX\\n-----END CERTIFICATE-----" - \`\`\` - - Set to \`null\` to only allow "known" registrars, or to a specific CA - cert to trust only that specific signing authority. - - Multiple CAs can be trusted by specifying an array of certificates: - - \`\`\`ini - ca[]="..." - ca[]="..." - \`\`\` - - See also the \`strict-ssl\` config. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'null', - typeDescription: 'null or String (can be set multiple times)', - }, - cache: { - key: 'cache', - default: '~/.npm', - defaultDescription: ` - Windows: \`%LocalAppData%\\npm-cache\`, Posix: \`~/.npm\` - `, - type: path, - description: ` - The location of npm's cache directory. See [\`npm - cache\`](/commands/npm-cache) - `, - flatten (key, obj, flatOptions) { - flatOptions.cache = join(obj.cache, '_cacache') - }, - typeDescription: 'Path', - }, - 'cache-max': { - key: 'cache-max', - default: null, - type: Number, - description: ` - \`--cache-max=0\` is an alias for \`--prefer-online\` - `, - deprecated: ` - This option has been deprecated in favor of \`--prefer-online\` - `, - flatten (key, obj, flatOptions) { - if (obj[key] <= 0) { - flatOptions.preferOnline = true - } - }, - defaultDescription: 'Infinity', - typeDescription: 'Number', - }, - 'cache-min': { - key: 'cache-min', - default: 0, - type: Number, - description: ` - \`--cache-min=9999 (or bigger)\` is an alias for \`--prefer-offline\`. - `, - deprecated: ` - This option has been deprecated in favor of \`--prefer-offline\`. - `, - flatten (key, obj, flatOptions) { - if (obj[key] >= 9999) { - flatOptions.preferOffline = true - } - }, - defaultDescription: '0', - typeDescription: 'Number', - }, - cafile: { - key: 'cafile', - default: null, - type: path, - description: ` - A path to a file containing one or multiple Certificate Authority signing - certificates. Similar to the \`ca\` setting, but allows for multiple - CA's, as well as for the CA information to be stored in a file on disk. - `, - flatten (key, obj, flatOptions) { - // always set to null in defaults - if (!obj.cafile) { - return - } - - const raw = maybeReadFile(obj.cafile) - if (!raw) { - return - } - - const delim = '-----END CERTIFICATE-----' - flatOptions.ca = raw.replace(/\r\n/g, '\n').split(delim) - .filter(section => section.trim()) - .map(section => section.trimLeft() + delim) - }, - defaultDescription: 'null', - typeDescription: 'Path', - }, - call: { - key: 'call', - default: '', - type: String, - short: 'c', - description: ` - Optional companion option for \`npm exec\`, \`npx\` that allows for - specifying a custom command to be run along with the installed packages. - - \`\`\`bash - npm exec --package yo --package generator-node --call "yo node" - \`\`\` - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: '""', - typeDescription: 'String', - }, - cert: { - key: 'cert', - default: null, - type: [ - null, - String, - ], - description: ` - A client certificate to pass when accessing the registry. Values should - be in PEM format (Windows calls it "Base-64 encoded X.509 (.CER)") with - newlines replaced by the string "\\n". For example: - - \`\`\`ini - cert="-----BEGIN CERTIFICATE-----\\nXXXX\\nXXXX\\n-----END CERTIFICATE-----" - \`\`\` - - It is _not_ the path to a certificate file (and there is no "certfile" - option). - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'null', - typeDescription: 'null or String', - }, - 'ci-name': { - key: 'ci-name', - default: null, - defaultDescription: ` - The name of the current CI system, or \`null\` when not on a known CI - platform. - `, - type: [ - null, - String, - ], - description: ` - The name of a continuous integration system. If not set explicitly, npm - will detect the current CI environment using the - [\`@npmcli/ci-detect\`](http://npm.im/@npmcli/ci-detect) module. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - typeDescription: 'null or String', - }, - cidr: { - key: 'cidr', - default: null, - type: [ - null, - String, - Array, - ], - description: ` - This is a list of CIDR address to be used when configuring limited access - tokens with the \`npm token create\` command. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'null', - typeDescription: 'null or String (can be set multiple times)', - }, - color: { - key: 'color', - default: true, - defaultDescription: ` - true unless the NO_COLOR environ is set to something other than '0' - `, - type: [ - 'always', - Boolean, - ], - description: ` - If false, never shows colors. If \`"always"\` then always shows colors. - If true, then only prints color codes for tty file descriptors. - `, - flatten (key, obj, flatOptions) { - flatOptions.color = !obj.color ? false - : obj.color === 'always' ? true - : process.stdout.isTTY - }, - typeDescription: '"always" or Boolean', - }, - 'commit-hooks': { - key: 'commit-hooks', - default: true, - type: Boolean, - description: ` - Run git commit hooks when using the \`npm version\` command. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'true', - typeDescription: 'Boolean', - }, - depth: { - key: 'depth', - default: null, - defaultDescription: '\n `Infinity` if `--all` is set, otherwise `1`\n ', - type: [ - null, - Number, - ], - description: ` - The depth to go when recursing packages for \`npm ls\`. - - If not set, \`npm ls\` will show only the immediate dependencies of the - root project. If \`--all\` is set, then npm will show all dependencies - by default. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - typeDescription: 'null or Number', - }, - description: { - key: 'description', - default: true, - type: Boolean, - description: ` - Show the description in \`npm search\` - `, - flatten (key, obj, flatOptions) { - flatOptions.search = flatOptions.search || { limit: 20 } - flatOptions.search[key] = obj[key] - }, - defaultDescription: 'true', - typeDescription: 'Boolean', - }, - diff: { - key: 'diff', - default: [], - type: [ - String, - Array, - ], - description: ` - Define arguments to compare in \`npm diff\`. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: '', - typeDescription: 'String (can be set multiple times)', - }, - 'diff-ignore-all-space': { - key: 'diff-ignore-all-space', - default: false, - type: Boolean, - description: ` - Ignore whitespace when comparing lines in \`npm diff\`. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - 'diff-name-only': { - key: 'diff-name-only', - default: false, - type: Boolean, - description: ` - Prints only filenames when using \`npm diff\`. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - 'diff-no-prefix': { - key: 'diff-no-prefix', - default: false, - type: Boolean, - description: ` - Do not show any source or destination prefix in \`npm diff\` output. - - Note: this causes \`npm diff\` to ignore the \`--diff-src-prefix\` and - \`--diff-dst-prefix\` configs. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - 'diff-dst-prefix': { - key: 'diff-dst-prefix', - default: 'b/', - type: String, - description: ` - Destination prefix to be used in \`npm diff\` output. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: '"b/"', - typeDescription: 'String', - }, - 'diff-src-prefix': { - key: 'diff-src-prefix', - default: 'a/', - type: String, - description: ` - Source prefix to be used in \`npm diff\` output. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: '"a/"', - typeDescription: 'String', - }, - 'diff-text': { - key: 'diff-text', - default: false, - type: Boolean, - description: ` - Treat all files as text in \`npm diff\`. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - 'diff-unified': { - key: 'diff-unified', - default: 3, - type: Number, - description: ` - The number of lines of context to print in \`npm diff\`. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: '3', - typeDescription: 'Number', - }, - 'dry-run': { - key: 'dry-run', - default: false, - type: Boolean, - description: ` - Indicates that you don't want npm to make any changes and that it should - only report what it would have done. This can be passed into any of the - commands that modify your local installation, eg, \`install\`, - \`update\`, \`dedupe\`, \`uninstall\`, as well as \`pack\` and - \`publish\`. - - Note: This is NOT honored by other network related commands, eg - \`dist-tags\`, \`owner\`, etc. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - editor: { - key: 'editor', - default: 'vim', - defaultDescription: ` - The EDITOR or VISUAL environment variables, or 'notepad.exe' on Windows, - or 'vim' on Unix systems - `, - type: String, - description: ` - The command to run for \`npm edit\` and \`npm config edit\`. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - typeDescription: 'String', - }, - 'engine-strict': { - key: 'engine-strict', - default: false, - type: Boolean, - description: ` - If set to true, then npm will stubbornly refuse to install (or even - consider installing) any package that claims to not be compatible with - the current Node.js version. - - This can be overridden by setting the \`--force\` flag. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - 'fetch-retries': { - key: 'fetch-retries', - default: 2, - type: Number, - description: ` - The "retries" config for the \`retry\` module to use when fetching - packages from the registry. - - npm will retry idempotent read requests to the registry in the case - of network failures or 5xx HTTP errors. - `, - flatten (key, obj, flatOptions) { - flatOptions.retry = flatOptions.retry || {} - flatOptions.retry.retries = obj[key] - }, - defaultDescription: '2', - typeDescription: 'Number', - }, - 'fetch-retry-factor': { - key: 'fetch-retry-factor', - default: 10, - type: Number, - description: ` - The "factor" config for the \`retry\` module to use when fetching - packages. - `, - flatten (key, obj, flatOptions) { - flatOptions.retry = flatOptions.retry || {} - flatOptions.retry.factor = obj[key] - }, - defaultDescription: '10', - typeDescription: 'Number', - }, - 'fetch-retry-maxtimeout': { - key: 'fetch-retry-maxtimeout', - default: 60000, - defaultDescription: '60000 (1 minute)', - type: Number, - description: ` - The "maxTimeout" config for the \`retry\` module to use when fetching - packages. - `, - flatten (key, obj, flatOptions) { - flatOptions.retry = flatOptions.retry || {} - flatOptions.retry.maxTimeout = obj[key] - }, - typeDescription: 'Number', - }, - 'fetch-retry-mintimeout': { - key: 'fetch-retry-mintimeout', - default: 10000, - defaultDescription: '10000 (10 seconds)', - type: Number, - description: ` - The "minTimeout" config for the \`retry\` module to use when fetching - packages. - `, - flatten (key, obj, flatOptions) { - flatOptions.retry = flatOptions.retry || {} - flatOptions.retry.minTimeout = obj[key] - }, - typeDescription: 'Number', - }, - 'fetch-timeout': { - key: 'fetch-timeout', - default: 300000, - defaultDescription: '300000 (5 minutes)', - type: Number, - description: ` - The maximum amount of time to wait for HTTP requests to complete. - `, - flatten (key, obj, flatOptions) { - flatOptions.timeout = obj[key] - }, - typeDescription: 'Number', - }, - force: { - key: 'force', - default: false, - type: Boolean, - short: 'f', - description: ` - Removes various protections against unfortunate side effects, common - mistakes, unnecessary performance degradation, and malicious input. - - * Allow clobbering non-npm files in global installs. - * Allow the \`npm version\` command to work on an unclean git repository. - * Allow deleting the cache folder with \`npm cache clean\`. - * Allow installing packages that have an \`engines\` declaration - requiring a different version of npm. - * Allow installing packages that have an \`engines\` declaration - requiring a different version of \`node\`, even if \`--engine-strict\` - is enabled. - * Allow \`npm audit fix\` to install modules outside your stated - dependency range (including SemVer-major changes). - * Allow unpublishing all versions of a published package. - * Allow conflicting peerDependencies to be installed in the root project. - * Implicitly set \`--yes\` during \`npm init\`. - * Allow clobbering existing values in \`npm pkg\` - - If you don't have a clear idea of what you want to do, it is strongly - recommended that you do not use this option! - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - 'foreground-scripts': { - key: 'foreground-scripts', - default: false, - type: Boolean, - description: ` - Run all build scripts (ie, \`preinstall\`, \`install\`, and - \`postinstall\`) scripts for installed packages in the foreground - process, sharing standard input, output, and error with the main npm - process. - - Note that this will generally make installs run slower, and be much - noisier, but can be useful for debugging. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - 'format-package-lock': { - key: 'format-package-lock', - default: true, - type: Boolean, - description: ` - Format \`package-lock.json\` or \`npm-shrinkwrap.json\` as a human - readable file. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'true', - typeDescription: 'Boolean', - }, - fund: { - key: 'fund', - default: true, - type: Boolean, - description: ` - When "true" displays the message at the end of each \`npm install\` - acknowledging the number of dependencies looking for funding. - See [\`npm fund\`](/commands/npm-fund) for details. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'true', - typeDescription: 'Boolean', - }, - git: { - key: 'git', - default: 'git', - type: String, - description: ` - The command to use for git commands. If git is installed on the - computer, but is not in the \`PATH\`, then set this to the full path to - the git binary. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: '"git"', - typeDescription: 'String', - }, - 'git-tag-version': { - key: 'git-tag-version', - default: true, - type: Boolean, - description: ` - Tag the commit when using the \`npm version\` command. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'true', - typeDescription: 'Boolean', - }, - global: { - key: 'global', - default: false, - type: Boolean, - short: 'g', - description: ` - Operates in "global" mode, so that packages are installed into the - \`prefix\` folder instead of the current working directory. See - [folders](/configuring-npm/folders) for more on the differences in - behavior. - - * packages are installed into the \`{prefix}/lib/node_modules\` folder, - instead of the current working directory. - * bin files are linked to \`{prefix}/bin\` - * man pages are linked to \`{prefix}/share/man\` - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - 'global-style': { - key: 'global-style', - default: false, - type: Boolean, - description: ` - Causes npm to install the package into your local \`node_modules\` folder - with the same layout it uses with the global \`node_modules\` folder. - Only your direct dependencies will show in \`node_modules\` and - everything they depend on will be flattened in their \`node_modules\` - folders. This obviously will eliminate some deduping. If used with - \`legacy-bundling\`, \`legacy-bundling\` will be preferred. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - globalconfig: { - key: 'globalconfig', - type: path, - default: '', - defaultDescription: ` - The global --prefix setting plus 'etc/npmrc'. For example, - '/usr/local/etc/npmrc' - `, - description: ` - The config file to read for global config options. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - typeDescription: 'Path', - }, - heading: { - key: 'heading', - default: 'npm', - type: String, - description: ` - The string that starts all the debugging log output. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: '"npm"', - typeDescription: 'String', - }, - 'https-proxy': { - key: 'https-proxy', - default: null, - type: [ - null, - url, - ], - description: ` - A proxy to use for outgoing https requests. If the \`HTTPS_PROXY\` or - \`https_proxy\` or \`HTTP_PROXY\` or \`http_proxy\` environment variables - are set, proxy settings will be honored by the underlying - \`make-fetch-happen\` library. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'null', - typeDescription: 'null or URL', - }, - 'if-present': { - key: 'if-present', - default: false, - type: Boolean, - description: ` - If true, npm will not exit with an error code when \`run-script\` is - invoked for a script that isn't defined in the \`scripts\` section of - \`package.json\`. This option can be used when it's desirable to - optionally run a script when it's present and fail if the script fails. - This is useful, for example, when running scripts that may only apply for - some builds in an otherwise generic CI setup. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - 'ignore-scripts': { - key: 'ignore-scripts', - default: false, - type: Boolean, - description: ` - If true, npm does not run scripts specified in package.json files. - - Note that commands explicitly intended to run a particular script, such - as \`npm start\`, \`npm stop\`, \`npm restart\`, \`npm test\`, and \`npm - run-script\` will still run their intended script if \`ignore-scripts\` is - set, but they will *not* run any pre- or post-scripts. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - include: { - key: 'include', - default: [], - type: [ - Array, - 'prod', - 'dev', - 'optional', - 'peer', - ], - description: ` - Option that allows for defining which types of dependencies to install. - - This is the inverse of \`--omit=\`. - - Dependency types specified in \`--include\` will not be omitted, - regardless of the order in which omit/include are specified on the - command-line. - `, - flatten (key, obj, flatOptions) { - // just call the omit flattener, it reads from obj.include - definitions.omit.flatten('omit', obj, flatOptions) - }, - defaultDescription: '', - typeDescription: '"prod", "dev", "optional", or "peer" (can be set multiple times)', - }, - 'include-staged': { - key: 'include-staged', - default: false, - type: Boolean, - description: ` - Allow installing "staged" published packages, as defined by [npm RFC PR - #92](https://github.com/npm/rfcs/pull/92). - - This is experimental, and not implemented by the npm public registry. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - 'init-author-email': { - key: 'init-author-email', - default: '', - type: String, - description: ` - The value \`npm init\` should use by default for the package author's - email. - `, - defaultDescription: '""', - typeDescription: 'String', - }, - 'init-author-name': { - key: 'init-author-name', - default: '', - type: String, - description: ` - The value \`npm init\` should use by default for the package author's name. - `, - defaultDescription: '""', - typeDescription: 'String', - }, - 'init-author-url': { - key: 'init-author-url', - default: '', - type: [ - '', - url, - ], - description: ` - The value \`npm init\` should use by default for the package author's homepage. - `, - defaultDescription: '""', - typeDescription: '"" or URL', - }, - 'init-license': { - key: 'init-license', - default: 'ISC', - type: String, - description: ` - The value \`npm init\` should use by default for the package license. - `, - defaultDescription: '"ISC"', - typeDescription: 'String', - }, - 'init-module': { - key: 'init-module', - default: '~/.npm-init.js', - type: path, - description: ` - A module that will be loaded by the \`npm init\` command. See the - documentation for the - [init-package-json](https://github.com/npm/init-package-json) module for - more information, or [npm init](/commands/npm-init). - `, - defaultDescription: '"~/.npm-init.js"', - typeDescription: 'Path', - }, - 'init-version': { - key: 'init-version', - default: '1.0.0', - type: semver, - description: ` - The value that \`npm init\` should use by default for the package - version number, if not already set in package.json. - `, - defaultDescription: '"1.0.0"', - typeDescription: 'SemVer string', - }, - 'init.author.email': { - key: 'init.author.email', - default: '', - type: String, - deprecated: ` - Use \`--init-author-email\` instead.`, - description: ` - Alias for \`--init-author-email\` - `, - defaultDescription: '""', - typeDescription: 'String', - }, - 'init.author.name': { - key: 'init.author.name', - default: '', - type: String, - deprecated: ` - Use \`--init-author-name\` instead. - `, - description: ` - Alias for \`--init-author-name\` - `, - defaultDescription: '""', - typeDescription: 'String', - }, - 'init.author.url': { - key: 'init.author.url', - default: '', - type: [ - '', - url, - ], - deprecated: ` - Use \`--init-author-url\` instead. - `, - description: ` - Alias for \`--init-author-url\` - `, - defaultDescription: '""', - typeDescription: '"" or URL', - }, - 'init.license': { - key: 'init.license', - default: 'ISC', - type: String, - deprecated: ` - Use \`--init-license\` instead. - `, - description: ` - Alias for \`--init-license\` - `, - defaultDescription: '"ISC"', - typeDescription: 'String', - }, - 'init.module': { - key: 'init.module', - default: '~/.npm-init.js', - type: path, - deprecated: ` - Use \`--init-module\` instead. - `, - description: ` - Alias for \`--init-module\` - `, - defaultDescription: '"~/.npm-init.js"', - typeDescription: 'Path', - }, - 'init.version': { - key: 'init.version', - default: '1.0.0', - type: semver, - deprecated: ` - Use \`--init-version\` instead. - `, - description: ` - Alias for \`--init-version\` - `, - defaultDescription: '"1.0.0"', - typeDescription: 'SemVer string', - }, - json: { - key: 'json', - default: false, - type: Boolean, - description: ` - Whether or not to output JSON data, rather than the normal output. - - * In \`npm pkg set\` it enables parsing set values with JSON.parse() - before saving them to your \`package.json\`. - - Not supported by all npm commands. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - key: { - key: 'key', - default: null, - type: [ - null, - String, - ], - description: ` - A client key to pass when accessing the registry. Values should be in - PEM format with newlines replaced by the string "\\n". For example: - - \`\`\`ini - key="-----BEGIN PRIVATE KEY-----\\nXXXX\\nXXXX\\n-----END PRIVATE KEY-----" - \`\`\` - - It is _not_ the path to a key file (and there is no "keyfile" option). - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'null', - typeDescription: 'null or String', - }, - 'legacy-bundling': { - key: 'legacy-bundling', - default: false, - type: Boolean, - description: ` - Causes npm to install the package such that versions of npm prior to 1.4, - such as the one included with node 0.8, can install the package. This - eliminates all automatic deduping. If used with \`global-style\` this - option will be preferred. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - 'legacy-peer-deps': { - key: 'legacy-peer-deps', - default: false, - type: Boolean, - description: ` - Causes npm to completely ignore \`peerDependencies\` when building a - package tree, as in npm versions 3 through 6. - - If a package cannot be installed because of overly strict - \`peerDependencies\` that collide, it provides a way to move forward - resolving the situation. - - This differs from \`--omit=peer\`, in that \`--omit=peer\` will avoid - unpacking \`peerDependencies\` on disk, but will still design a tree such - that \`peerDependencies\` _could_ be unpacked in a correct place. - - Use of \`legacy-peer-deps\` is not recommended, as it will not enforce - the \`peerDependencies\` contract that meta-dependencies may rely on. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - link: { - key: 'link', - default: false, - type: Boolean, - description: ` - Used with \`npm ls\`, limiting output to only those packages that are - linked. - `, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - 'local-address': { - key: 'local-address', - default: null, - type: [ - null, - '127.0.0.1', - '::1', - 'fe80::1', - 'fe80::aede:48ff:fe00:1122', - 'fe80::18fe:6168:6908:4239', - '2600:1700:87d0:b28f:481:1fd0:2067:5a90', - '2600:1700:87d0:b28f:11be:d3f3:278c:ade9', - 'fd2e:635c:9594:10:109e:699c:6fdc:41b9', - 'fd2e:635c:9594:10:69ce:d360:4ab9:1632', - '192.168.103.122', - 'fe80::715:4a5e:3af5:99e5', - 'fe80::d32a:27b1:2ac:1155', - 'fe80::bbb2:6e76:3877:9f2f', - 'fe80::8e1f:15b0:b70:2d70', - ], - typeDescription: 'IP Address', - description: ` - The IP address of the local interface to use when making connections to - the npm registry. Must be IPv4 in versions of Node prior to 0.12. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'null', - }, - location: { - key: 'location', - default: 'user', - type: ['global', 'user', 'project'], - description: ` - When passed to \`npm config\` this refers to which config file to use. - `, - defaultDescription: ` - "user" unless \`--global\` is passed, which will also set this value to "global" - `, - typeDescription: '"global", "user", or "project"', - }, - loglevel: { - key: 'loglevel', - default: 'notice', - type: [ - 'silent', - 'error', - 'warn', - 'notice', - 'http', - 'timing', - 'info', - 'verbose', - 'silly', - ], - description: ` - What level of logs to report. All logs are written to a debug log, - with the path to that file printed if the execution of a command fails. - - Any logs of a higher level than the setting are shown. The default is - "notice". - - See also the \`foreground-scripts\` config. - `, - defaultDescription: '"notice"', - typeDescription: '"silent", "error", "warn", "notice", "http", "timing", "info", "verbose",' + - ' or "silly"', - }, - 'logs-max': { - key: 'logs-max', - default: 10, - type: Number, - description: ` - The maximum number of log files to store. - `, - defaultDescription: '10', - typeDescription: 'Number', - }, - long: { - key: 'long', - default: false, - type: Boolean, - short: 'l', - description: ` - Show extended information in \`ls\`, \`search\`, and \`help-search\`. - `, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - maxsockets: { - key: 'maxsockets', - default: null, - type: Number, - description: ` - The maximum number of connections to use per origin (protocol/host/port - combination). - `, - flatten (key, obj, flatOptions) { - flatOptions.maxSockets = obj[key] - }, - defaultDescription: 'Infinity', - typeDescription: 'Number', - }, - message: { - key: 'message', - default: '%s', - type: String, - short: 'm', - description: ` - Commit message which is used by \`npm version\` when creating version commit. - - Any "%s" in the message will be replaced with the version number. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: '"%s"', - typeDescription: 'String', - }, - 'node-options': { - key: 'node-options', - default: null, - type: [ - null, - String, - ], - description: ` - Options to pass through to Node.js via the \`NODE_OPTIONS\` environment - variable. This does not impact how npm itself is executed but it does - impact how lifecycle scripts are called. - `, - defaultDescription: 'null', - typeDescription: 'null or String', - }, - 'node-version': { - key: 'node-version', - default: 'v15.3.0', - defaultDescription: 'Node.js `process.version` value', - type: semver, - description: ` - The node version to use when checking a package's \`engines\` setting. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - typeDescription: 'SemVer string', - }, - noproxy: { - key: 'noproxy', - default: '', - defaultDescription: ` - The value of the NO_PROXY environment variable - `, - type: [ - String, - Array, - ], - description: ` - Domain extensions that should bypass any proxies. - - Also accepts a comma-delimited string. - `, - flatten (key, obj, flatOptions) { - flatOptions.noProxy = obj[key].join(',') - }, - typeDescription: 'String (can be set multiple times)', - }, - 'npm-version': { - key: 'npm-version', - default: '7.6.3', - defaultDescription: 'Output of `npm --version`', - type: semver, - description: ` - The npm version to use when checking a package's \`engines\` setting. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - typeDescription: 'SemVer string', - }, - offline: { - key: 'offline', - default: false, - type: Boolean, - description: ` - Force offline mode: no network requests will be done during install. To allow - the CLI to fill in missing cache data, see \`--prefer-offline\`. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - omit: { - key: 'omit', - default: [], - defaultDescription: ` - 'dev' if the \`NODE_ENV\` environment variable is set to 'production', - otherwise empty. - `, - type: [ - Array, - 'dev', - 'optional', - 'peer', - ], - description: ` - Dependency types to omit from the installation tree on disk. - - Note that these dependencies _are_ still resolved and added to the - \`package-lock.json\` or \`npm-shrinkwrap.json\` file. They are just - not physically installed on disk. - - If a package type appears in both the \`--include\` and \`--omit\` - lists, then it will be included. - - If the resulting omit list includes \`'dev'\`, then the \`NODE_ENV\` - environment variable will be set to \`'production'\` for all lifecycle - scripts. - `, - flatten (key, obj, flatOptions) { - const include = obj.include || [] - const omit = flatOptions.omit || [] - flatOptions.omit = omit.concat(obj[key]) - .filter(type => type && !include.includes(type)) - }, - typeDescription: '"dev", "optional", or "peer" (can be set multiple times)', - }, - only: { - key: 'only', - default: null, - type: [ - null, - 'prod', - 'production', - ], - deprecated: ` - Use \`--omit=dev\` to omit dev dependencies from the install. - `, - description: ` - When set to \`prod\` or \`production\`, this is an alias for - \`--omit=dev\`. - `, - flatten (key, obj, flatOptions) { - const value = obj[key] - if (!/^prod(uction)?$/.test(value)) { - return - } - - obj.omit = obj.omit || [] - obj.omit.push('dev') - definitions.omit.flatten('omit', obj, flatOptions) - }, - defaultDescription: 'null', - typeDescription: 'null, "prod", or "production"', - }, - optional: { - key: 'optional', - default: null, - type: [ - null, - Boolean, - ], - deprecated: ` - Use \`--omit=optional\` to exclude optional dependencies, or - \`--include=optional\` to include them. - - Default value does install optional deps unless otherwise omitted. - `, - description: ` - Alias for --include=optional or --omit=optional - `, - flatten (key, obj, flatOptions) { - const value = obj[key] - if (value === null) { - return - } else if (value === true) { - obj.include = obj.include || [] - obj.include.push('optional') - } else { - obj.omit = obj.omit || [] - obj.omit.push('optional') - } - definitions.omit.flatten('omit', obj, flatOptions) - }, - defaultDescription: 'null', - typeDescription: 'null or Boolean', - }, - otp: { - key: 'otp', - default: null, - type: [ - null, - String, - ], - description: ` - This is a one-time password from a two-factor authenticator. It's needed - when publishing or changing package permissions with \`npm access\`. - - If not set, and a registry response fails with a challenge for a one-time - password, npm will prompt on the command line for one. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'null', - typeDescription: 'null or String', - }, - package: { - key: 'package', - default: [], - type: [ - String, - Array, - ], - description: ` - The package to install for [\`npm exec\`](/commands/npm-exec) - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: '', - typeDescription: 'String (can be set multiple times)', - }, - 'package-lock': { - key: 'package-lock', - default: true, - type: Boolean, - description: ` - If set to false, then ignore \`package-lock.json\` files when installing. - This will also prevent _writing_ \`package-lock.json\` if \`save\` is - true. - - When package package-locks are disabled, automatic pruning of extraneous - modules will also be disabled. To remove extraneous modules with - package-locks disabled use \`npm prune\`. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'true', - typeDescription: 'Boolean', - }, - 'package-lock-only': { - key: 'package-lock-only', - default: false, - type: Boolean, - description: ` - If set to true, the current operation will only use the \`package-lock.json\`, - ignoring \`node_modules\`. - - For \`update\` this means only the \`package-lock.json\` will be updated, - instead of checking \`node_modules\` and downloading dependencies. - - For \`list\` this means the output will be based on the tree described by the - \`package-lock.json\`, rather than the contents of \`node_modules\`. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - parseable: { - key: 'parseable', - default: false, - type: Boolean, - short: 'p', - description: ` - Output parseable results from commands that write to standard output. For - \`npm search\`, this will be tab-separated table format. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - 'prefer-offline': { - key: 'prefer-offline', - default: false, - type: Boolean, - description: ` - If true, staleness checks for cached data will be bypassed, but missing - data will be requested from the server. To force full offline mode, use - \`--offline\`. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - 'prefer-online': { - key: 'prefer-online', - default: false, - type: Boolean, - description: ` - If true, staleness checks for cached data will be forced, making the CLI - look for updates immediately even for fresh package data. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - prefix: { - key: 'prefix', - type: path, - short: 'C', - default: '', - defaultDescription: ` - In global mode, the folder where the node executable is installed. In - local mode, the nearest parent folder containing either a package.json - file or a node_modules folder. - `, - description: ` - The location to install global items. If set on the command line, then - it forces non-global commands to run in the specified folder. - `, - typeDescription: 'Path', - }, - preid: { - key: 'preid', - default: '', - type: String, - description: ` - The "prerelease identifier" to use as a prefix for the "prerelease" part - of a semver. Like the \`rc\` in \`1.2.0-rc.8\`. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: '""', - typeDescription: 'String', - }, - production: { - key: 'production', - default: false, - type: Boolean, - deprecated: 'Use `--omit=dev` instead.', - description: 'Alias for `--omit=dev`', - flatten (key, obj, flatOptions) { - const value = obj[key] - if (!value) { - return - } - - obj.omit = obj.omit || [] - obj.omit.push('dev') - definitions.omit.flatten('omit', obj, flatOptions) - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - progress: { - key: 'progress', - default: true, - defaultDescription: '\n `true` unless running in a known CI system\n ', - type: Boolean, - description: ` - When set to \`true\`, npm will display a progress bar during time - intensive operations, if \`process.stderr\` is a TTY. - - Set to \`false\` to suppress the progress bar. - `, - typeDescription: 'Boolean', - }, - proxy: { - key: 'proxy', - default: null, - type: [ - null, - false, - url, - ], - description: ` - A proxy to use for outgoing http requests. If the \`HTTP_PROXY\` or - \`http_proxy\` environment variables are set, proxy settings will be - honored by the underlying \`request\` library. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'null', - typeDescription: 'null, false, or URL', - }, - 'read-only': { - key: 'read-only', - default: false, - type: Boolean, - description: ` - This is used to mark a token as unable to publish when configuring - limited access tokens with the \`npm token create\` command. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - 'rebuild-bundle': { - key: 'rebuild-bundle', - default: true, - type: Boolean, - description: ` - Rebuild bundled dependencies after installation. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'true', - typeDescription: 'Boolean', - }, - registry: { - key: 'registry', - default: 'https://registry.npmjs.org/', - type: [null, url], - description: ` - The base URL of the npm registry. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: '"https://registry.npmjs.org/"', - typeDescription: 'URL', - }, - save: { - key: 'save', - default: true, - type: Boolean, - short: 'S', - description: ` - Save installed packages to a \`package.json\` file as dependencies. - - When used with the \`npm rm\` command, removes the dependency from - \`package.json\`. - - Will also prevent writing to \`package-lock.json\` if set to \`false\`. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'true', - typeDescription: 'Boolean', - }, - 'save-bundle': { - key: 'save-bundle', - default: false, - type: Boolean, - short: 'B', - description: ` - If a package would be saved at install time by the use of \`--save\`, - \`--save-dev\`, or \`--save-optional\`, then also put it in the - \`bundleDependencies\` list. - - Ignore if \`--save-peer\` is set, since peerDependencies cannot be bundled. - `, - flatten (key, obj, flatOptions) { - // XXX update arborist to just ignore it if resulting saveType is peer - // otherwise this won't have the expected effect: - // - // npm config set save-peer true - // npm i foo --save-bundle --save-prod <-- should bundle - flatOptions.saveBundle = obj['save-bundle'] && !obj['save-peer'] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - 'save-dev': { - key: 'save-dev', - default: false, - type: Boolean, - short: 'D', - description: ` - Save installed packages to a package.json file as \`devDependencies\`. - `, - flatten (key, obj, flatOptions) { - if (!obj[key]) { - if (flatOptions.saveType === 'dev') { - delete flatOptions.saveType - } - return - } - - flatOptions.saveType = 'dev' - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - 'save-exact': { - key: 'save-exact', - default: false, - type: Boolean, - short: 'E', - description: ` - Dependencies saved to package.json will be configured with an exact - version rather than using npm's default semver range operator. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - 'save-optional': { - key: 'save-optional', - default: false, - type: Boolean, - short: 'O', - description: ` - Save installed packages to a package.json file as - \`optionalDependencies\`. - `, - flatten (key, obj, flatOptions) { - if (!obj[key]) { - if (flatOptions.saveType === 'optional') { - delete flatOptions.saveType - } else if (flatOptions.saveType === 'peerOptional') { - flatOptions.saveType = 'peer' - } - return - } - - if (flatOptions.saveType === 'peerOptional') { - return - } - - if (flatOptions.saveType === 'peer') { - flatOptions.saveType = 'peerOptional' - } else { - flatOptions.saveType = 'optional' - } - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - 'save-peer': { - key: 'save-peer', - default: false, - type: Boolean, - description: ` - Save installed packages to a package.json file as \`peerDependencies\` - `, - flatten (key, obj, flatOptions) { - if (!obj[key]) { - if (flatOptions.saveType === 'peer') { - delete flatOptions.saveType - } else if (flatOptions.saveType === 'peerOptional') { - flatOptions.saveType = 'optional' - } - return - } - - if (flatOptions.saveType === 'peerOptional') { - return - } - - if (flatOptions.saveType === 'optional') { - flatOptions.saveType = 'peerOptional' - } else { - flatOptions.saveType = 'peer' - } - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - 'save-prefix': { - key: 'save-prefix', - default: '^', - type: String, - description: ` - Configure how versions of packages installed to a package.json file via - \`--save\` or \`--save-dev\` get prefixed. - - For example if a package has version \`1.2.3\`, by default its version is - set to \`^1.2.3\` which allows minor upgrades for that package, but after - \`npm config set save-prefix='~'\` it would be set to \`~1.2.3\` which - only allows patch upgrades. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: '"^"', - typeDescription: 'String', - }, - 'save-prod': { - key: 'save-prod', - default: false, - type: Boolean, - short: 'P', - description: ` - Save installed packages into \`dependencies\` specifically. This is - useful if a package already exists in \`devDependencies\` or - \`optionalDependencies\`, but you want to move it to be a non-optional - production dependency. - - This is the default behavior if \`--save\` is true, and neither - \`--save-dev\` or \`--save-optional\` are true. - `, - flatten (key, obj, flatOptions) { - if (!obj[key]) { - if (flatOptions.saveType === 'prod') { - delete flatOptions.saveType - } - return - } - - flatOptions.saveType = 'prod' - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - scope: { - key: 'scope', - default: '', - defaultDescription: ` - the scope of the current project, if any, or "" - `, - type: String, - description: ` - Associate an operation with a scope for a scoped registry. - - Useful when logging in to or out of a private registry: - - \`\`\` - # log in, linking the scope to the custom registry - npm login --scope=@mycorp --registry=https://registry.mycorp.com - - # log out, removing the link and the auth token - npm logout --scope=@mycorp - \`\`\` - - This will cause \`@mycorp\` to be mapped to the registry for future - installation of packages specified according to the pattern - \`@mycorp/package\`. - - This will also cause \`npm init\` to create a scoped package. - - \`\`\` - # accept all defaults, and create a package named "@foo/whatever", - # instead of just named "whatever" - npm init --scope=@foo --yes - \`\`\` - `, - flatten (key, obj, flatOptions) { - const value = obj[key] - flatOptions.projectScope = value && !/^@/.test(value) ? `@${value}` : value - }, - typeDescription: 'String', - }, - 'script-shell': { - key: 'script-shell', - default: null, - defaultDescription: ` - '/bin/sh' on POSIX systems, 'cmd.exe' on Windows - `, - type: [ - null, - String, - ], - description: ` - The shell to use for scripts run with the \`npm exec\`, - \`npm run\` and \`npm init \` commands. - `, - flatten (key, obj, flatOptions) { - flatOptions.scriptShell = obj[key] || undefined - }, - typeDescription: 'null or String', - }, - searchexclude: { - key: 'searchexclude', - default: '', - type: String, - description: ` - Space-separated options that limit the results from search. - `, - flatten (key, obj, flatOptions) { - flatOptions.search = flatOptions.search || { limit: 20 } - flatOptions.search.exclude = obj[key] - }, - defaultDescription: '""', - typeDescription: 'String', - }, - searchlimit: { - key: 'searchlimit', - default: 20, - type: Number, - description: ` - Number of items to limit search results to. Will not apply at all to - legacy searches. - `, - flatten (key, obj, flatOptions) { - flatOptions.search = flatOptions.search || {} - flatOptions.search.limit = obj[key] - }, - defaultDescription: '20', - typeDescription: 'Number', - }, - searchopts: { - key: 'searchopts', - default: '', - type: String, - description: ` - Space-separated options that are always passed to search. - `, - flatten (key, obj, flatOptions) { - flatOptions.search = flatOptions.search || { limit: 20 } - flatOptions.search.opts = querystring.parse(obj[key]) - }, - defaultDescription: '""', - typeDescription: 'String', - }, - searchstaleness: { - key: 'searchstaleness', - default: 900, - type: Number, - description: ` - The age of the cache, in seconds, before another registry request is made - if using legacy search endpoint. - `, - flatten (key, obj, flatOptions) { - flatOptions.search = flatOptions.search || { limit: 20 } - flatOptions.search.staleness = obj[key] - }, - defaultDescription: '900', - typeDescription: 'Number', - }, - shell: { - key: 'shell', - default: '/usr/local/bin/bash', - defaultDescription: ` - SHELL environment variable, or "bash" on Posix, or "cmd.exe" on Windows - `, - type: String, - description: ` - The shell to run for the \`npm explore\` command. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - typeDescription: 'String', - }, - shrinkwrap: { - key: 'shrinkwrap', - default: true, - type: Boolean, - deprecated: ` - Use the --package-lock setting instead. - `, - description: ` - Alias for --package-lock - `, - flatten (key, obj, flatOptions) { - obj['package-lock'] = obj.shrinkwrap - definitions['package-lock'].flatten('package-lock', obj, flatOptions) - }, - defaultDescription: 'true', - typeDescription: 'Boolean', - }, - 'sign-git-commit': { - key: 'sign-git-commit', - default: false, - type: Boolean, - description: ` - If set to true, then the \`npm version\` command will commit the new - package version using \`-S\` to add a signature. - - Note that git requires you to have set up GPG keys in your git configs - for this to work properly. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - 'sign-git-tag': { - key: 'sign-git-tag', - default: false, - type: Boolean, - description: ` - If set to true, then the \`npm version\` command will tag the version - using \`-s\` to add a signature. - - Note that git requires you to have set up GPG keys in your git configs - for this to work properly. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - 'sso-poll-frequency': { - key: 'sso-poll-frequency', - default: 500, - type: Number, - deprecated: ` - The --auth-type method of SSO/SAML/OAuth will be removed in a future - version of npm in favor of web-based login. - `, - description: ` - When used with SSO-enabled \`auth-type\`s, configures how regularly the - registry should be polled while the user is completing authentication. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: '500', - typeDescription: 'Number', - }, - 'sso-type': { - key: 'sso-type', - default: 'oauth', - type: [ - null, - 'oauth', - 'saml', - ], - deprecated: ` - The --auth-type method of SSO/SAML/OAuth will be removed in a future - version of npm in favor of web-based login. - `, - description: ` - If \`--auth-type=sso\`, the type of SSO type to use. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: '"oauth"', - typeDescription: 'null, "oauth", or "saml"', - }, - 'strict-peer-deps': { - key: 'strict-peer-deps', - default: false, - type: Boolean, - description: ` - If set to \`true\`, and \`--legacy-peer-deps\` is not set, then _any_ - conflicting \`peerDependencies\` will be treated as an install failure, - even if npm could reasonably guess the appropriate resolution based on - non-peer dependency relationships. - - By default, conflicting \`peerDependencies\` deep in the dependency graph - will be resolved using the nearest non-peer dependency specification, - even if doing so will result in some packages receiving a peer dependency - outside the range set in their package's \`peerDependencies\` object. - - When such and override is performed, a warning is printed, explaining the - conflict and the packages involved. If \`--strict-peer-deps\` is set, - then this warning is treated as a failure. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - 'strict-ssl': { - key: 'strict-ssl', - default: true, - type: Boolean, - description: ` - Whether or not to do SSL key validation when making requests to the - registry via https. - - See also the \`ca\` config. - `, - flatten (key, obj, flatOptions) { - flatOptions.strictSSL = obj[key] - }, - defaultDescription: 'true', - typeDescription: 'Boolean', - }, - tag: { - key: 'tag', - default: 'latest', - type: String, - description: ` - If you ask npm to install a package and don't tell it a specific version, - then it will install the specified tag. - - Also the tag that is added to the package@version specified by the \`npm - tag\` command, if no explicit tag is given. - - When used by the \`npm diff\` command, this is the tag used to fetch the - tarball that will be compared with the local files by default. - `, - flatten (key, obj, flatOptions) { - flatOptions.defaultTag = obj[key] - }, - defaultDescription: '"latest"', - typeDescription: 'String', - }, - 'tag-version-prefix': { - key: 'tag-version-prefix', - default: 'v', - type: String, - description: ` - If set, alters the prefix used when tagging a new version when performing - a version increment using \`npm-version\`. To remove the prefix - altogether, set it to the empty string: \`""\`. - - Because other tools may rely on the convention that npm version tags look - like \`v1.0.0\`, _only use this property if it is absolutely necessary_. - In particular, use care when overriding this setting for public packages. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: '"v"', - typeDescription: 'String', - }, - timing: { - key: 'timing', - default: false, - type: Boolean, - description: ` - If true, writes an \`npm-debug\` log to \`_logs\` and timing information - to \`_timing.json\`, both in your cache, even if the command completes - successfully. \`_timing.json\` is a newline delimited list of JSON - objects. - - You can quickly view it with this [json](https://npm.im/json) command - line: \`npm exec -- json -g < ~/.npm/_timing.json\`. - `, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - tmp: { - key: 'tmp', - default: '/var/folders/zc/5n20yjzn7mn7cz_qckj3b3440000gn/T', - defaultDescription: ` - The value returned by the Node.js \`os.tmpdir()\` method - - `, - type: path, - deprecated: ` - This setting is no longer used. npm stores temporary files in a special - location in the cache, and they are managed by - [\`cacache\`](http://npm.im/cacache). - `, - description: ` - Historically, the location where temporary files were stored. No longer - relevant. - `, - typeDescription: 'Path', - }, - umask: { - key: 'umask', - default: 0, - type: Umask, - description: ` - The "umask" value to use when setting the file creation mode on files and - folders. - - Folders and executables are given a mode which is \`0o777\` masked - against this value. Other files are given a mode which is \`0o666\` - masked against this value. - - Note that the underlying system will _also_ apply its own umask value to - files and folders that are created, and npm does not circumvent this, but - rather adds the \`--umask\` config to it. - - Thus, the effective default umask value on most POSIX systems is 0o22, - meaning that folders and executables are created with a mode of 0o755 and - other files are created with a mode of 0o644. - `, - flatten: (key, obj, flatOptions) => { - const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) - flatOptions[camel] = obj[key] - }, - defaultDescription: '0', - typeDescription: 'Octal numeric string in range 0000..0777 (0..511)', - }, - unicode: { - key: 'unicode', - default: true, - defaultDescription: ` - false on windows, true on mac/unix systems with a unicode locale, as - defined by the \`LC_ALL\`, \`LC_CTYPE\`, or \`LANG\` environment variables. - `, - type: Boolean, - description: ` - When set to true, npm uses unicode characters in the tree output. When - false, it uses ascii characters instead of unicode glyphs. - `, - typeDescription: 'Boolean', - }, - 'update-notifier': { - key: 'update-notifier', - default: true, - type: Boolean, - description: ` - Set to false to suppress the update notification when using an older - version of npm than the latest. - `, - defaultDescription: 'true', - typeDescription: 'Boolean', - }, - usage: { - key: 'usage', - default: false, - type: Boolean, - short: [ - '?', - 'H', - 'h', - ], - description: ` - Show short usage output about the command specified. - `, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - 'user-agent': { - key: 'user-agent', - default: 'npm/{npm-version} node/{node-version} {platform} {arch} {ci}', - type: String, - description: ` - Sets the User-Agent request header. The following fields are replaced - with their actual counterparts: - - * \`{npm-version}\` - The npm version in use - * \`{node-version}\` - The Node.js version in use - * \`{platform}\` - The value of \`process.platform\` - * \`{arch}\` - The value of \`process.arch\` - * \`{workspaces}\` - Set to \`true\` if the \`workspaces\` or \`workspace\` - options are set. - * \`{ci}\` - The value of the \`ci-name\` config, if set, prefixed with - \`ci/\`, or an empty string if \`ci-name\` is empty. - `, - flatten (key, obj, flatOptions) { - const value = obj[key] - const ciName = obj['ci-name'] - flatOptions.userAgent = - value.replace(/\{node-version\}/gi, obj['node-version']) - .replace(/\{npm-version\}/gi, obj['npm-version']) - .replace(/\{platform\}/gi, process.platform) - .replace(/\{arch\}/gi, process.arch) - .replace(/\{ci\}/gi, ciName ? `ci/${ciName}` : '') - .trim() - }, - defaultDescription: '"npm/{npm-version} node/{node-version} {platform} {arch} {ci}"', - typeDescription: 'String', - }, - userconfig: { - key: 'userconfig', - default: '~/.npmrc', - type: path, - description: ` - The location of user-level configuration settings. - - This may be overridden by the \`npm_config_userconfig\` environment - variable or the \`--userconfig\` command line option, but may _not_ - be overridden by settings in the \`globalconfig\` file. - `, - defaultDescription: '"~/.npmrc"', - typeDescription: 'Path', - }, - version: { - key: 'version', - default: false, - type: Boolean, - short: 'v', - description: ` - If true, output the npm version and exit successfully. - - Only relevant when specified explicitly on the command line. - `, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - versions: { - key: 'versions', - default: false, - type: Boolean, - description: ` - If true, output the npm version as well as node's \`process.versions\` - map and the version in the current working directory's \`package.json\` - file if one exists, and exit successfully. - - Only relevant when specified explicitly on the command line. - `, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - viewer: { - key: 'viewer', - default: 'man', - defaultDescription: '\n "man" on Posix, "browser" on Windows\n ', - type: String, - description: ` - The program to use to view help content. - - Set to \`"browser"\` to view html help content in the default web browser. - `, - typeDescription: 'String', - }, - workspace: { - key: 'workspace', - default: [], - type: [String, Array], - short: 'w', - envExport: false, - description: ` - Enable running a command in the context of the configured workspaces of the - current project while filtering by running only the workspaces defined by - this configuration option. - - Valid values for the \`workspace\` config are either: - - * Workspace names - * Path to a workspace directory - * Path to a parent workspace directory (will result in selecting all - workspaces within that folder) - - When set for the \`npm init\` command, this may be set to the folder of - a workspace which does not yet exist, to create the folder and set it - up as a brand new workspace within the project. - `, - defaultDescription: '', - typeDescription: 'String (can be set multiple times)', - flatten: (key, obj, flatOptions) => { - definitions['user-agent'].flatten('user-agent', obj, flatOptions) - }, - }, - yes: { - key: 'yes', - default: false, - type: Boolean, - short: 'y', - description: ` - Automatically answer "yes" to any prompts that npm might print on - the command line. - `, - defaultDescription: 'false', - typeDescription: 'Boolean', - }, - truth: { - key: 'truth', - default: false, - type: Boolean, - description: 'The Truth', - exclusive: ['lie'], - }, - lie: { - key: 'lie', - default: false, - type: Boolean, - description: 'A Lie', - exclusive: ['truth'], - }, -} diff --git a/workspaces/config/test/fixtures/flatten.js b/workspaces/config/test/fixtures/flatten.js deleted file mode 100644 index 588d05bf0d77d..0000000000000 --- a/workspaces/config/test/fixtures/flatten.js +++ /dev/null @@ -1,33 +0,0 @@ -// 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 diff --git a/workspaces/config/test/fixtures/shorthands.js b/workspaces/config/test/fixtures/shorthands.js deleted file mode 100644 index 5c460c6617175..0000000000000 --- a/workspaces/config/test/fixtures/shorthands.js +++ /dev/null @@ -1,41 +0,0 @@ -module.exports = { - 'enjoy-by': ['--before'], - a: ['--all'], - c: ['--call'], - s: ['--loglevel', 'silent'], - d: ['--loglevel', 'info'], - dd: ['--loglevel', 'verbose'], - ddd: ['--loglevel', 'silly'], - noreg: ['--no-registry'], - N: ['--no-registry'], - reg: ['--registry'], - 'no-reg': ['--no-registry'], - silent: ['--loglevel', 'silent'], - verbose: ['--loglevel', 'verbose'], - quiet: ['--loglevel', 'warn'], - q: ['--loglevel', 'warn'], - h: ['--usage'], - H: ['--usage'], - '?': ['--usage'], - help: ['--usage'], - v: ['--version'], - f: ['--force'], - desc: ['--description'], - 'no-desc': ['--no-description'], - local: ['--no-global'], - l: ['--long'], - m: ['--message'], - p: ['--parseable'], - porcelain: ['--parseable'], - readonly: ['--read-only'], - g: ['--global'], - S: ['--save'], - D: ['--save-dev'], - E: ['--save-exact'], - O: ['--save-optional'], - P: ['--save-prod'], - y: ['--yes'], - n: ['--no-yes'], - B: ['--save-bundle'], - C: ['--prefix'], -} diff --git a/workspaces/config/test/fixtures/types.js b/workspaces/config/test/fixtures/types.js deleted file mode 100644 index 924433e3ec67c..0000000000000 --- a/workspaces/config/test/fixtures/types.js +++ /dev/null @@ -1,152 +0,0 @@ -const { - String: { type: String }, - Boolean: { type: Boolean }, - url: { type: url }, - Number: { type: Number }, - path: { type: path }, - Date: { type: Date }, - semver: { type: semver }, - Umask: { type: Umask }, -} = require('../../lib/type-defs.js') - -const { networkInterfaces } = require('os') -const getLocalAddresses = () => { - try { - return Object.values(networkInterfaces()).map( - int => int.map(({ address }) => address) - ).reduce((set, addrs) => set.concat(addrs), [undefined]) - } catch (e) { - return [undefined] - } -} - -module.exports = { - access: [null, 'restricted', 'public'], - all: Boolean, - 'allow-same-version': Boolean, - 'always-auth': Boolean, - also: [null, 'dev', 'development'], - audit: Boolean, - 'audit-level': ['low', 'moderate', 'high', 'critical', 'none', null], - 'auth-type': ['legacy', 'sso', 'saml', 'oauth'], - before: [null, Date], - 'bin-links': Boolean, - browser: [null, Boolean, String], - ca: [null, String, Array], - cafile: path, - cache: path, - 'cache-lock-stale': Number, - 'cache-lock-retries': Number, - 'cache-lock-wait': Number, - 'cache-max': Number, - 'cache-min': Number, - cert: [null, String], - cidr: [null, String, Array], - color: ['always', Boolean], - call: String, - depth: Number, - description: Boolean, - dev: Boolean, - 'dry-run': Boolean, - editor: String, - 'engine-strict': Boolean, - force: Boolean, - fund: Boolean, - 'format-package-lock': Boolean, - 'fetch-retries': Number, - 'fetch-retry-factor': Number, - 'fetch-retry-mintimeout': Number, - 'fetch-retry-maxtimeout': Number, - git: String, - 'git-tag-version': Boolean, - 'commit-hooks': Boolean, - global: Boolean, - globalconfig: path, - 'global-style': Boolean, - 'https-proxy': [null, url], - 'user-agent': String, - heading: String, - 'if-present': Boolean, - include: [Array, 'prod', 'dev', 'optional', 'peer'], - 'include-staged': Boolean, - 'ignore-prepublish': Boolean, - 'ignore-scripts': Boolean, - 'init-module': path, - 'init-author-name': String, - 'init-author-email': String, - 'init-author-url': ['', url], - 'init-license': String, - 'init-version': semver, - json: Boolean, - key: [null, String], - 'legacy-bundling': Boolean, - 'legacy-peer-deps': Boolean, - link: Boolean, - 'local-address': getLocalAddresses(), - loglevel: ['silent', 'error', 'warn', 'notice', 'http', 'timing', 'info', 'verbose', 'silly'], - 'logs-max': Number, - long: Boolean, - 'multiple-numbers': [Array, Number], - maxsockets: Number, - message: String, - 'metrics-registry': [null, String], - 'node-options': [null, String], - 'node-version': [null, semver], - noproxy: [null, String, Array], - offline: Boolean, - omit: [Array, 'dev', 'optional', 'peer'], - only: [null, 'dev', 'development', 'prod', 'production'], - optional: Boolean, - otp: [null, String], - package: [String, Array], - 'package-lock': Boolean, - 'package-lock-only': Boolean, - parseable: Boolean, - 'prefer-offline': Boolean, - 'prefer-online': Boolean, - prefix: path, - preid: String, - production: Boolean, - progress: Boolean, - proxy: [null, false, url], // allow proxy to be disabled explicitly - 'read-only': Boolean, - 'rebuild-bundle': Boolean, - registry: [null, url], - rollback: Boolean, - save: Boolean, - 'save-bundle': Boolean, - 'save-dev': Boolean, - 'save-exact': Boolean, - 'save-optional': Boolean, - 'save-prefix': String, - 'save-prod': Boolean, - scope: String, - 'script-shell': [null, String], - 'scripts-prepend-node-path': [Boolean, 'auto', 'warn-only'], - searchopts: String, - searchexclude: [null, String], - searchlimit: Number, - searchstaleness: Number, - 'send-metrics': Boolean, - shell: String, - shrinkwrap: Boolean, - 'sign-git-commit': Boolean, - 'sign-git-tag': Boolean, - 'sso-poll-frequency': Number, - 'sso-type': [null, 'oauth', 'saml'], - 'strict-ssl': Boolean, - tag: String, - timing: Boolean, - tmp: path, - unicode: Boolean, - 'update-notifier': Boolean, - usage: Boolean, - userconfig: path, - umask: Umask, - version: Boolean, - 'tag-version-prefix': String, - versions: Boolean, - viewer: String, - _exit: Boolean, - _single: { exotic: 'not part of normal typedefs' }, -} diff --git a/workspaces/config/test/index.js b/workspaces/config/test/index.js index 0d72cbee526bb..520d554436ea8 100644 --- a/workspaces/config/test/index.js +++ b/workspaces/config/test/index.js @@ -11,14 +11,14 @@ Object.keys(process.env) delete process.env.PREFIX delete process.env.DESTDIR -const definitions = require('./fixtures/definitions.js') -const shorthands = require('./fixtures/shorthands.js') -const flatten = require('./fixtures/flatten.js') +const Definition = require('../lib/definitions/definition.js') +const createDef = (key, value) => ({ [key]: new Definition(key, { key, ...value }) }) + const typeDefs = require('../lib/type-defs.js') const { resolve, join, dirname } = require('path') -const Config = t.mock('../', { +const fsMocks = { 'fs/promises': { ...fs.promises, readFile: async (path, ...args) => { @@ -29,11 +29,25 @@ const Config = t.mock('../', { return fs.promises.readFile(path, ...args) }, }, -}) + fs: { + ...fs, + readFileSync: (path, ...args) => { + if (path.includes('WEIRD-ERROR')) { + throw Object.assign(new Error('weird error'), { code: 'EWEIRD' }) + } + + return fs.readFileSync(path, ...args) + }, + }, +} + +const { definitions, shorthands, flatten } = t.mock('../lib/definitions/index.js', fsMocks) +const Config = t.mock('../', fsMocks) // because we used t.mock above, the require cache gets blown and we lose our direct equality // on the typeDefs. to get around that, we require an un-mocked Config and assert against that const RealConfig = require('../') + t.equal(typeDefs, RealConfig.typeDefs, 'exposes type definitions') t.test('construct with no settings, get default values for stuff', t => { @@ -152,6 +166,7 @@ loglevel = yolo 'foo', '--also=dev', '--registry=hello', + '--proxy=hello', '--omit=cucumber', '--access=blueberry', '--multiple-numbers=what kind of fruit is not a number', @@ -240,7 +255,23 @@ loglevel = yolo argv, cwd: join(`${path}/project`), shorthands, - definitions, + definitions: { + ...definitions, + ...createDef('multiple-numbers', { + default: [], + type: [Array, Number], + description: 'one or more numbers', + }), + ...createDef('methane', { + envExport: false, + type: String, + typeDescription: 'Greenhouse Gas', + default: 'CH4', + description: ` + This is bad for the environment, for our children, do not put it there. + `, + }), + }, }) t.equal(config.globalPrefix, null, 'globalPrefix missing before load') @@ -347,6 +378,8 @@ loglevel = yolo t.strictSame(logs, [ ['warn', 'invalid config', 'registry="hello"', 'set in command line options'], ['warn', 'invalid config', 'Must be', 'full url with "http://"'], + ['warn', 'invalid config', 'proxy="hello"', 'set in command line options'], + ['warn', 'invalid config', 'Must be', 'full url with "http://"'], ['warn', 'invalid config', 'omit="cucumber"', 'set in command line options'], ['warn', 'invalid config', 'Must be one or more of:', 'dev, optional, peer'], ['warn', 'invalid config', 'access="blueberry"', 'set in command line options'], @@ -362,8 +395,7 @@ loglevel = yolo ['warn', 'invalid config', 'loglevel="yolo"', `set in ${resolve(path, 'project/.npmrc')}`], ['warn', 'invalid config', 'Must be one of:', - ['silent', 'error', 'warn', 'notice', 'http', 'timing', 'info', - 'verbose', 'silly'].join(', '), + ['silent', 'error', 'warn', 'notice', 'http', 'info', 'verbose', 'silly'].join(', '), ], ]) t.equal(config.valid, false) @@ -494,7 +526,14 @@ loglevel = yolo platform: 'posix', shorthands, - definitions, + definitions: { + ...definitions, + ...createDef('multiple-numbers', { + default: [], + type: [Array, Number], + description: 'one or more numbers', + }), + }, }) await config.load() @@ -534,6 +573,8 @@ loglevel = yolo t.strictSame(logs, [ ['warn', 'invalid config', 'registry="hello"', 'set in command line options'], ['warn', 'invalid config', 'Must be', 'full url with "http://"'], + ['warn', 'invalid config', 'proxy="hello"', 'set in command line options'], + ['warn', 'invalid config', 'Must be', 'full url with "http://"'], ['warn', 'invalid config', 'omit="cucumber"', 'set in command line options'], ['warn', 'invalid config', 'Must be one or more of:', 'dev, optional, peer'], ['warn', 'invalid config', 'access="blueberry"', 'set in command line options'], @@ -547,6 +588,7 @@ loglevel = yolo ['warn', 'invalid config', 'Must be', 'valid filesystem path'], ['warn', 'config', 'also', 'Please use --include=dev instead.'], ]) + logs.length = 0 }) t.end() @@ -636,9 +678,9 @@ t.test('ignore cafile if it does not load', async t => { }) t.test('raise error if reading ca file error other than ENOENT', async t => { - const cafile = resolve(__dirname, 'fixtures', 'WEIRD-ERROR') const dir = t.testdir({ - '.npmrc': `cafile = ${cafile}`, + '.npmrc': `cafile = ~/WEIRD-ERROR`, + 'WEIRD-ERROR': '', }) const config = new Config({ shorthands, @@ -1330,7 +1372,21 @@ t.test('exclusive options conflict', async t => { ], cwd: join(`${path}/project`), shorthands, - definitions, + definitions: { + ...definitions, + ...createDef('truth', { + default: false, + type: Boolean, + description: 'The Truth', + exclusive: ['lie'], + }), + ...createDef('lie', { + default: false, + type: Boolean, + description: 'A Lie', + exclusive: ['truth'], + }), + }, flatten, }) await t.rejects(config.load(), { @@ -1338,6 +1394,7 @@ t.test('exclusive options conflict', async t => { message: '--lie can not be provided when using --truth', }) }) + t.test('env-replaced config from files is not clobbered when saving', async (t) => { const path = t.testdir() const opts = { @@ -1360,3 +1417,33 @@ t.test('env-replaced config from files is not clobbered when saving', async (t) const rc = readFileSync(`${path}/.npmrc`, 'utf8') t.match(rc, 'test=${TEST}', '${TEST} is present, not parsed') }) + +t.test('umask', async t => { + const mockUmask = async (t, umask) => { + const path = t.testdir() + const config = new Config({ + env: {}, + npmPath: __dirname, + argv: [ + process.execPath, + __filename, + `--umask=${umask}`, + ], + cwd: join(`${path}/project`), + shorthands, + definitions, + flatten, + }) + await config.load() + return config.get('umask') + } + + t.test('valid', async t => { + const umask = await mockUmask(t, '777') + t.equal(umask, 777) + }) + t.test('invalid', async t => { + const umask = await mockUmask(t, true) + t.equal(umask, 0) + }) +}) diff --git a/workspaces/config/test/parse-field.js b/workspaces/config/test/parse-field.js index 29e8ccd68b69b..3dbeeae5f698f 100644 --- a/workspaces/config/test/parse-field.js +++ b/workspaces/config/test/parse-field.js @@ -2,11 +2,14 @@ const parseField = require('../lib/parse-field.js') const t = require('tap') const { resolve } = require('path') +const defs = require('../lib/definitions/definitions.js') +const types = Object.entries(defs).map(([k, v]) => [k, v.type]) + t.strictSame(parseField({ a: 1 }, 'a'), { a: 1 }) const opts = { platform: 'posix', - types: require('./fixtures/types.js'), + types: Object.fromEntries(types), home: '/home/user', env: { foo: 'bar' }, } @@ -34,5 +37,6 @@ t.equal(parseField('1234', 'maxsockets', opts), 1234, 'number is parsed') t.equal(parseField('0888', 'umask', opts), '0888', 'invalid umask is not parsed (will warn later)') t.equal(parseField('0777', 'umask', opts), 0o777, 'valid umask is parsed') +t.equal(parseField('777', 'umask', opts), 777, 'valid umask is parsed') t.same(parseField('2020', 'before', opts), new Date('2020'), 'date is parsed') diff --git a/workspaces/config/test/set-envs.js b/workspaces/config/test/set-envs.js index c663c2236d203..74eba262aa161 100644 --- a/workspaces/config/test/set-envs.js +++ b/workspaces/config/test/set-envs.js @@ -1,16 +1,22 @@ const setEnvs = require('../lib/set-envs.js') +const mockGlobals = require('@npmcli/mock-globals') const { join } = require('path') const t = require('tap') -const defaults = require('./fixtures/defaults.js') -const definitions = require('./fixtures/definitions.js') const { execPath } = process const cwd = process.cwd() const globalPrefix = join(cwd, 'global') const localPrefix = join(cwd, 'local') const NODE = execPath +const mockDefinitions = (t) => { + mockGlobals(t, { 'process.env': { EDITOR: 'vim' } }) + const { definitions, defaults } = t.mock('../lib/definitions/index.js') + return { definitions, defaults } +} + t.test('set envs that are not defaults and not already in env', t => { + const { definitions, defaults } = mockDefinitions(t) const envConf = Object.create(defaults) const cliConf = Object.create(envConf) const extras = { @@ -61,6 +67,7 @@ t.test('set envs that are not defaults and not already in env', t => { }) t.test('set envs that are not defaults and not already in env, array style', t => { + const { definitions, defaults } = mockDefinitions(t) const envConf = Object.create(defaults) const cliConf = Object.create(envConf) const extras = { @@ -108,6 +115,7 @@ t.test('set envs that are not defaults and not already in env, array style', t = }) t.test('set envs that are not defaults and not already in env, boolean edition', t => { + const { definitions, defaults } = mockDefinitions(t) const envConf = Object.create(defaults) const cliConf = Object.create(envConf) const extras = { @@ -157,6 +165,7 @@ t.test('set envs that are not defaults and not already in env, boolean edition', }) t.test('dont set npm_execpath if require.main.filename is not set', t => { + const { definitions, defaults } = mockDefinitions(t) const { filename } = require.main t.teardown(() => require.main.filename = filename) require.main.filename = null @@ -180,6 +189,7 @@ t.test('dont set npm_execpath if require.main.filename is not set', t => { }) t.test('dont set configs marked as envExport:false', t => { + const { definitions, defaults } = mockDefinitions(t) const envConf = Object.create(defaults) const cliConf = Object.create(envConf) const extras = { @@ -205,7 +215,7 @@ t.test('dont set configs marked as envExport:false', t => { } setEnvs(config) t.strictSame(env, { ...extras }, 'no new environment vars to create') - cliConf.methane = 'CO2' + cliConf['include-workspace-root'] = true setEnvs(config) t.strictSame(env, { ...extras }, 'not exported, because envExport=false') t.end() diff --git a/workspaces/config/test/type-description.js b/workspaces/config/test/type-description.js index d487c118940ec..6b6c56e8c824e 100644 --- a/workspaces/config/test/type-description.js +++ b/workspaces/config/test/type-description.js @@ -1,8 +1,10 @@ const t = require('tap') const typeDescription = require('../lib/type-description.js') -const types = require('./fixtures/types.js') +const defs = require('../lib/definitions/definitions.js') + const descriptions = {} -for (const [name, type] of Object.entries(types)) { + +for (const [name, { type }] of Object.entries(defs)) { const desc = typeDescription(type) if (name === 'local-address') { t.strictSame(desc.sort(), type.filter(t => t !== undefined).sort()) @@ -12,3 +14,6 @@ for (const [name, type] of Object.entries(types)) { } t.matchSnapshot(descriptions) + +class Unknown {} +t.strictSame(typeDescription(Unknown), [Unknown], 'unknown class returns itself')