Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/npm/cli.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNathan Fritz <fritzy@github.com>2022-10-31 03:42:46 +0300
committerGar <wraithgar@github.com>2022-11-01 23:33:54 +0300
commit6df246fd721c8c6697035ca70ec109c689cafeef (patch)
treed54ac9fba86e393b51d0f3652de120fae7aa4e8b
parentc09abca9c63466b31f78c9b54b7d1877bf986008 (diff)
chore: bring in @npmcli/config as a workspace
-rw-r--r--.github/workflows/ci-npmcli-config.yml94
-rw-r--r--.gitignore1
-rw-r--r--.release-please-manifest.json3
-rw-r--r--DEPENDENCIES.md7
l---------node_modules/@npmcli/config1
-rw-r--r--package-lock.json41
-rw-r--r--release-please-config.json3
-rw-r--r--workspaces/config/.eslintrc.js17
-rw-r--r--workspaces/config/.gitignore21
-rw-r--r--workspaces/config/CHANGELOG.md133
-rw-r--r--workspaces/config/LICENSE (renamed from node_modules/@npmcli/config/LICENSE)0
-rw-r--r--workspaces/config/README.md260
-rw-r--r--workspaces/config/lib/env-replace.js (renamed from node_modules/@npmcli/config/lib/env-replace.js)0
-rw-r--r--workspaces/config/lib/errors.js (renamed from node_modules/@npmcli/config/lib/errors.js)0
-rw-r--r--workspaces/config/lib/index.js (renamed from node_modules/@npmcli/config/lib/index.js)0
-rw-r--r--workspaces/config/lib/nerf-dart.js (renamed from node_modules/@npmcli/config/lib/nerf-dart.js)0
-rw-r--r--workspaces/config/lib/parse-field.js (renamed from node_modules/@npmcli/config/lib/parse-field.js)0
-rw-r--r--workspaces/config/lib/set-envs.js (renamed from node_modules/@npmcli/config/lib/set-envs.js)0
-rw-r--r--workspaces/config/lib/type-defs.js (renamed from node_modules/@npmcli/config/lib/type-defs.js)0
-rw-r--r--workspaces/config/lib/type-description.js (renamed from node_modules/@npmcli/config/lib/type-description.js)0
-rw-r--r--workspaces/config/lib/umask.js (renamed from node_modules/@npmcli/config/lib/umask.js)0
-rw-r--r--workspaces/config/map.js1
-rw-r--r--workspaces/config/package.json (renamed from node_modules/@npmcli/config/package.json)11
-rw-r--r--workspaces/config/scripts/example.js43
-rw-r--r--workspaces/config/tap-snapshots/test/index.js.test.cjs240
-rw-r--r--workspaces/config/tap-snapshots/test/type-description.js.test.cjs449
-rw-r--r--workspaces/config/test/env-replace.js13
-rw-r--r--workspaces/config/test/fixtures/cafile32
-rw-r--r--workspaces/config/test/fixtures/defaults.js143
-rw-r--r--workspaces/config/test/fixtures/definitions.js2609
-rw-r--r--workspaces/config/test/fixtures/flatten.js33
-rw-r--r--workspaces/config/test/fixtures/shorthands.js41
-rw-r--r--workspaces/config/test/fixtures/types.js151
-rw-r--r--workspaces/config/test/index.js1295
-rw-r--r--workspaces/config/test/nerf-dart.js44
-rw-r--r--workspaces/config/test/parse-field.js36
-rw-r--r--workspaces/config/test/set-envs.js212
-rw-r--r--workspaces/config/test/type-defs.js22
-rw-r--r--workspaces/config/test/type-description.js14
39 files changed, 5945 insertions, 25 deletions
diff --git a/.github/workflows/ci-npmcli-config.yml b/.github/workflows/ci-npmcli-config.yml
new file mode 100644
index 000000000..72cc302e7
--- /dev/null
+++ b/.github/workflows/ci-npmcli-config.yml
@@ -0,0 +1,94 @@
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+name: CI - @npmcli/config
+
+on:
+ workflow_dispatch:
+ pull_request:
+ paths:
+ - workspaces/config/**
+ push:
+ branches:
+ - main
+ - latest
+ paths:
+ - workspaces/config/**
+ schedule:
+ # "At 09:00 UTC (02:00 PT) on Monday" https://crontab.guru/#0_9_*_*_1
+ - cron: "0 9 * * 1"
+
+jobs:
+ lint:
+ name: Lint
+ if: github.repository_owner == 'npm'
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ shell: bash
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Setup Git User
+ run: |
+ git config --global user.email "npm-cli+bot@github.com"
+ git config --global user.name "npm CLI robot"
+ - name: Setup Node
+ uses: actions/setup-node@v3
+ with:
+ node-version: 18.x
+ cache: npm
+ - name: Reset Deps
+ run: node . run resetdeps
+ - name: Lint
+ run: node . run lint --ignore-scripts -w @npmcli/config
+ - name: Post Lint
+ run: node . run postlint --ignore-scripts -w @npmcli/config
+
+ test:
+ name: Test - ${{ matrix.platform.name }} - ${{ matrix.node-version }}
+ if: github.repository_owner == 'npm'
+ strategy:
+ fail-fast: false
+ matrix:
+ platform:
+ - name: Linux
+ os: ubuntu-latest
+ shell: bash
+ - name: macOS
+ os: macos-latest
+ shell: bash
+ - name: Windows
+ os: windows-latest
+ shell: cmd
+ node-version:
+ - 14.17.0
+ - 14.x
+ - 16.13.0
+ - 16.x
+ - 18.0.0
+ - 18.x
+ runs-on: ${{ matrix.platform.os }}
+ defaults:
+ run:
+ shell: ${{ matrix.platform.shell }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Setup Git User
+ run: |
+ git config --global user.email "npm-cli+bot@github.com"
+ git config --global user.name "npm CLI robot"
+ - name: Setup Node
+ uses: actions/setup-node@v3
+ with:
+ node-version: ${{ matrix.node-version }}
+ cache: npm
+ - name: Reset Deps
+ run: node . run resetdeps
+ - name: Add Problem Matcher
+ run: echo "::add-matcher::.github/matchers/tap.json"
+ - name: Test
+ run: node . test --ignore-scripts -w @npmcli/config
+ - name: Check Git Status
+ if: matrix && matrix.platform.os != 'windows-latest'
+ run: node scripts/git-dirty.js
diff --git a/.gitignore b/.gitignore
index c857a68a6..2ab23adf7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,6 +43,7 @@
!/workspaces/
/workspaces/*
!/workspaces/arborist/
+!/workspaces/config/
!/workspaces/libnpmaccess/
!/workspaces/libnpmdiff/
!/workspaces/libnpmexec/
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 60c37e232..ebe465127 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -11,5 +11,6 @@
"workspaces/libnpmpublish": "7.0.1",
"workspaces/libnpmsearch": "6.0.0",
"workspaces/libnpmteam": "5.0.0",
- "workspaces/libnpmversion": "4.0.0"
+ "workspaces/libnpmversion": "4.0.0",
+ "workspaces/config": "6.0.1"
}
diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md
index efe532d54..7e9e26ec9 100644
--- a/DEPENDENCIES.md
+++ b/DEPENDENCIES.md
@@ -188,7 +188,9 @@ graph LR;
npmcli-arborist-->treeverse;
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-template-oss["@npmcli/template-oss"];
npmcli-config-->proc-log;
npmcli-config-->read-package-json-fast;
npmcli-config-->semver;
@@ -613,10 +615,13 @@ graph LR;
npmcli-arborist-->walk-up-path;
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-template-oss["@npmcli/template-oss"];
npmcli-config-->proc-log;
npmcli-config-->read-package-json-fast;
npmcli-config-->semver;
+ npmcli-config-->tap;
npmcli-config-->walk-up-path;
npmcli-disparity-colors-->ansi-styles;
npmcli-docs-->cmark-gfm;
@@ -763,4 +768,4 @@ packages higher up the chain.
- @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, readdir-scoped-modules, promzard
- @npmcli/docs, npm-bundled, read-package-json-fast, @npmcli/fs, unique-filename, npm-install-checks, npm-package-arg, npm-packlist, normalize-package-data, @npmcli/package-json, bin-links, nopt, npmlog, parse-conflict-json, dezalgo, read
- - @npmcli/eslint-config, @npmcli/template-oss, ignore-walk, npm-normalize-package-bin, @npmcli/name-from-folder, json-parse-even-better-errors, semver, @npmcli/move-file, 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, wrappy, treeverse, minify-registry-metadata, @npmcli/disparity-colors, @npmcli/ci-detect, mute-stream, ini, npm-audit-report, npm-user-validate
+ - @npmcli/eslint-config, @npmcli/template-oss, ignore-walk, npm-normalize-package-bin, @npmcli/name-from-folder, json-parse-even-better-errors, semver, @npmcli/move-file, 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, wrappy, treeverse, minify-registry-metadata, ini, @npmcli/disparity-colors, @npmcli/ci-detect, mute-stream, npm-audit-report, npm-user-validate
diff --git a/node_modules/@npmcli/config b/node_modules/@npmcli/config
new file mode 120000
index 000000000..bf09f370d
--- /dev/null
+++ b/node_modules/@npmcli/config
@@ -0,0 +1 @@
+../../workspaces/config \ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index b28986fb3..2b468079d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2052,22 +2052,8 @@
}
},
"node_modules/@npmcli/config": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/@npmcli/config/-/config-6.0.1.tgz",
- "integrity": "sha512-f8PGjhM7kKbMfEMmE8n1dW+m/7XFuvatLXqItO89ZKJwYl9Zs5d7CmsIe8n8i+4YmGYL3HqR26/mVb4oK2b6Zw==",
- "inBundle": true,
- "dependencies": {
- "@npmcli/map-workspaces": "^3.0.0",
- "ini": "^3.0.0",
- "nopt": "^6.0.0",
- "proc-log": "^3.0.0",
- "read-package-json-fast": "^3.0.0",
- "semver": "^7.3.5",
- "walk-up-path": "^1.0.0"
- },
- "engines": {
- "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
- }
+ "resolved": "workspaces/config",
+ "link": true
},
"node_modules/@npmcli/disparity-colors": {
"version": "3.0.0",
@@ -13488,7 +13474,6 @@
},
"node_modules/walk-up-path": {
"version": "1.0.0",
- "inBundle": true,
"license": "ISC"
},
"node_modules/wcwidth": {
@@ -13925,6 +13910,28 @@
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
+ "workspaces/config": {
+ "name": "@npmcli/config",
+ "version": "6.0.1",
+ "license": "ISC",
+ "dependencies": {
+ "@npmcli/map-workspaces": "^3.0.0",
+ "ini": "^3.0.0",
+ "nopt": "^6.0.0",
+ "proc-log": "^3.0.0",
+ "read-package-json-fast": "^3.0.0",
+ "semver": "^7.3.5",
+ "walk-up-path": "^1.0.0"
+ },
+ "devDependencies": {
+ "@npmcli/eslint-config": "^4.0.0",
+ "@npmcli/template-oss": "4.8.0",
+ "tap": "^16.0.1"
+ },
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
"workspaces/libnpmaccess": {
"version": "7.0.0",
"license": "ISC",
diff --git a/release-please-config.json b/release-please-config.json
index f5cc46284..389df4270 100644
--- a/release-please-config.json
+++ b/release-please-config.json
@@ -69,7 +69,8 @@
},
"workspaces/libnpmversion": {
"prerelease": false
- }
+ },
+ "workspaces/config": {}
},
"exclude-packages-from-root": true,
"group-pull-request-title-pattern": "chore: release ${version}",
diff --git a/workspaces/config/.eslintrc.js b/workspaces/config/.eslintrc.js
new file mode 100644
index 000000000..5db9f8155
--- /dev/null
+++ b/workspaces/config/.eslintrc.js
@@ -0,0 +1,17 @@
+/* This file is automatically added by @npmcli/template-oss. Do not edit. */
+
+'use strict'
+
+const { readdirSync: readdir } = require('fs')
+
+const localConfigs = readdir(__dirname)
+ .filter((file) => file.startsWith('.eslintrc.local.'))
+ .map((file) => `./${file}`)
+
+module.exports = {
+ root: true,
+ extends: [
+ '@npmcli',
+ ...localConfigs,
+ ],
+}
diff --git a/workspaces/config/.gitignore b/workspaces/config/.gitignore
new file mode 100644
index 000000000..79af2bfca
--- /dev/null
+++ b/workspaces/config/.gitignore
@@ -0,0 +1,21 @@
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+# ignore everything in the root
+/*
+
+# keep these
+!**/.gitignore
+!/.eslintrc.js
+!/.eslintrc.local.*
+!/.gitignore
+!/bin/
+!/CHANGELOG*
+!/docs/
+!/lib/
+!/LICENSE*
+!/map.js
+!/package.json
+!/README*
+!/scripts/
+!/tap-snapshots/
+!/test/
diff --git a/workspaces/config/CHANGELOG.md b/workspaces/config/CHANGELOG.md
new file mode 100644
index 000000000..b3b30f863
--- /dev/null
+++ b/workspaces/config/CHANGELOG.md
@@ -0,0 +1,133 @@
+# Changelog
+
+## [6.0.1](https://github.com/npm/config/compare/v6.0.0...v6.0.1) (2022-10-17)
+
+### Dependencies
+
+* [`dca20cc`](https://github.com/npm/config/commit/dca20cc00c0cbebd9d1a1cf1962e32e99057ea8e) [#99](https://github.com/npm/config/pull/99) bump @npmcli/map-workspaces from 2.0.4 to 3.0.0
+* [`fc42456`](https://github.com/npm/config/commit/fc424565014cc155e902940221b6283cbb40faf4) [#100](https://github.com/npm/config/pull/100) bump proc-log from 2.0.1 to 3.0.0
+
+## [6.0.0](https://github.com/npm/config/compare/v5.0.0...v6.0.0) (2022-10-13)
+
+### ⚠️ BREAKING CHANGES
+
+* this module no longer attempts to change file ownership automatically
+
+### Features
+
+* [`805535f`](https://github.com/npm/config/commit/805535ff6b7255a3a2fb5e7da392f53b1c2f3c04) [#96](https://github.com/npm/config/pull/96) do not alter file ownership (#96) (@nlf)
+
+### Dependencies
+
+* [`c62c19c`](https://github.com/npm/config/commit/c62c19cffc65a8b6e89cbd071bd7578f246312a9) [#95](https://github.com/npm/config/pull/95) bump read-package-json-fast from 2.0.3 to 3.0.0
+
+## [5.0.0](https://github.com/npm/config/compare/v4.2.2...v5.0.0) (2022-10-06)
+
+### ⚠️ BREAKING CHANGES
+
+* unscoped auth configuration is no longer automatically scoped to a registry. the `validate` method is no longer called automatically. the `_auth` configuration key is no longer split into `username` and `_password`. errors will be thrown by `validate()` if problems are found.
+* `@npmcli/config` is now compatible with the following semver range for node: `^14.17.0 || ^16.13.0 || >=18.0.0`
+
+### Features
+
+* [`344ccd3`](https://github.com/npm/config/commit/344ccd3d07979d0cb36dad8a7fe2e9cbbdbdbc9e) [#92](https://github.com/npm/config/pull/92) throw errors for invalid auth configuration (#92) (@nlf)
+* [`aa25682`](https://github.com/npm/config/commit/aa256827d76ec9b1aea06eb3ebdd033067a5e604) [#87](https://github.com/npm/config/pull/87) postinstall for dependabot template-oss PR (@lukekarrys)
+
+## [4.2.2](https://github.com/npm/config/compare/v4.2.1...v4.2.2) (2022-08-25)
+
+
+### Bug Fixes
+
+* warn on bare auth related configs ([#78](https://github.com/npm/config/issues/78)) ([d4e582a](https://github.com/npm/config/commit/d4e582ab7d8d9f4a8615619bb7d3263df5de66e6))
+
+## [4.2.1](https://github.com/npm/config/compare/v4.2.0...v4.2.1) (2022-08-09)
+
+
+### Bug Fixes
+
+* correctly handle nerf-darted env vars ([#74](https://github.com/npm/config/issues/74)) ([71f559b](https://github.com/npm/config/commit/71f559b08e01616b53f61e1cf385fc44162e2d66))
+* linting ([#75](https://github.com/npm/config/issues/75)) ([deb1001](https://github.com/npm/config/commit/deb10011d1b5e3df84b7d13284ea55b07dd62b63))
+
+
+### Dependencies
+
+* bump nopt from 5.0.0 to 6.0.0 ([#72](https://github.com/npm/config/issues/72)) ([d825726](https://github.com/npm/config/commit/d825726049644f5bbe0edf27b5600cc60ae14ee5))
+
+## [4.2.0](https://github.com/npm/config/compare/v4.1.0...v4.2.0) (2022-07-18)
+
+
+### Features
+
+* detect registry-scoped certfile and keyfile options ([#69](https://github.com/npm/config/issues/69)) ([e58a4f1](https://github.com/npm/config/commit/e58a4f18f0ec0820fe57ccaff34c4135ece12558))
+
+## [4.1.0](https://github.com/npm/config/compare/v4.0.2...v4.1.0) (2022-04-13)
+
+
+### Features
+
+* warn on deprecated config ([#62](https://github.com/npm/config/issues/62)) ([190065e](https://github.com/npm/config/commit/190065ef53d39a1e09486639c710dabdd73d8a7c))
+
+### [4.0.2](https://github.com/npm/config/compare/v4.0.1...v4.0.2) (2022-04-05)
+
+
+### Bug Fixes
+
+* replace deprecated String.prototype.substr() ([#59](https://github.com/npm/config/issues/59)) ([43893b6](https://github.com/npm/config/commit/43893b638f82ade945cba27fe9e483b32eea99ae))
+
+
+### Dependencies
+
+* bump ini from 2.0.0 to 3.0.0 ([#60](https://github.com/npm/config/issues/60)) ([965e2a4](https://github.com/npm/config/commit/965e2a40c7649ffd6e84fb83823a2b751bcda294))
+* update @npmcli/map-workspaces requirement from ^2.0.1 to ^2.0.2 ([#49](https://github.com/npm/config/issues/49)) ([9a0f182](https://github.com/npm/config/commit/9a0f182c4fa46dadccc631a244678a3c469ad63a))
+
+### [4.0.1](https://www.github.com/npm/config/compare/v4.0.0...v4.0.1) (2022-03-02)
+
+
+### Bug Fixes
+
+* skip workspace detection when in global mode ([#47](https://www.github.com/npm/config/issues/47)) ([bedff61](https://www.github.com/npm/config/commit/bedff61c6f074f21c1586afe391dc2cb6e821619))
+
+
+### Dependencies
+
+* update @npmcli/map-workspaces requirement from ^2.0.0 to ^2.0.1 ([#43](https://www.github.com/npm/config/issues/43)) ([c397ab8](https://www.github.com/npm/config/commit/c397ab88c459fc477ae9094ec0ee0b571e6bb8ed))
+
+## [4.0.0](https://www.github.com/npm/config/compare/v3.0.1...v4.0.0) (2022-02-14)
+
+
+### ⚠ BREAKING CHANGES
+
+* drop support for the `log` option
+
+### Features
+
+* remove `log` option ([#40](https://www.github.com/npm/config/issues/40)) ([bbf5128](https://www.github.com/npm/config/commit/bbf512818f30d0764e3951449c8f07856d70991e))
+
+
+### Bug Fixes
+
+* correct a polynomial regex ([#39](https://www.github.com/npm/config/issues/39)) ([9af098f](https://www.github.com/npm/config/commit/9af098fb874c1a8122ab7a5e009235a1f7df72f5))
+
+### [3.0.1](https://www.github.com/npm/config/compare/v3.0.0...v3.0.1) (2022-02-10)
+
+
+### Dependencies
+
+* update semver requirement from ^7.3.4 to ^7.3.5 ([2cb225a](https://www.github.com/npm/config/commit/2cb225a907180a3b569c8c9baf23da1a989a2f1f))
+* use proc-log instead of process.emit ([fd4cd42](https://www.github.com/npm/config/commit/fd4cd429ef875ce68aa0be9bba329cae4e7adfe3))
+
+## [3.0.0](https://www.github.com/npm/config/compare/v2.4.0...v3.0.0) (2022-02-01)
+
+
+### ⚠ BREAKING CHANGES
+
+* this drops support for node10 and non-LTS versions of node12 and node14
+
+### Features
+
+* automatically detect workspace roots ([#28](https://www.github.com/npm/config/issues/28)) ([a3dc623](https://www.github.com/npm/config/commit/a3dc6234d57c7c80c66a8c33e17cf1d97f86f8d9))
+
+
+### Bug Fixes
+
+* template-oss ([#29](https://www.github.com/npm/config/issues/29)) ([6440fba](https://www.github.com/npm/config/commit/6440fba6e04b1f87e57b4c2ccc5ea84d8a69b823))
diff --git a/node_modules/@npmcli/config/LICENSE b/workspaces/config/LICENSE
index 19cec97b1..19cec97b1 100644
--- a/node_modules/@npmcli/config/LICENSE
+++ b/workspaces/config/LICENSE
diff --git a/workspaces/config/README.md b/workspaces/config/README.md
new file mode 100644
index 000000000..32418381a
--- /dev/null
+++ b/workspaces/config/README.md
@@ -0,0 +1,260 @@
+# `@npmcli/config`
+
+Configuration management for the npm cli.
+
+This module is the spiritual descendant of
+[`npmconf`](http://npm.im/npmconf), and the code that once lived in npm's
+`lib/config/` folder.
+
+It does the management of configuration files that npm uses, but
+importantly, does _not_ define all the configuration defaults or types, as
+those parts make more sense to live within the npm CLI itself.
+
+The only exceptions:
+
+- The `prefix` config value has some special semantics, setting the local
+ prefix if specified on the CLI options and not in global mode, or the
+ global prefix otherwise.
+- The `project` config file is loaded based on the local prefix (which can
+ only be set by the CLI config options, and otherwise defaults to a walk
+ up the folder tree to the first parent containing a `node_modules`
+ folder, `package.json` file, or `package-lock.json` file.)
+- The `userconfig` value, as set by the environment and CLI (defaulting to
+ `~/.npmrc`, is used to load user configs.
+- The `globalconfig` value, as set by the environment, CLI, and
+ `userconfig` file (defaulting to `$PREFIX/etc/npmrc`) is used to load
+ global configs.
+- A `builtin` config, read from a `npmrc` file in the root of the npm
+ project itself, overrides all defaults.
+
+The resulting hierarchy of configs:
+
+- CLI switches. eg `--some-key=some-value` on the command line. These are
+ parsed by [`nopt`](http://npm.im/nopt), which is not a great choice, but
+ it's the one that npm has used forever, and changing it will be
+ difficult.
+- Environment variables. eg `npm_config_some_key=some_value` in the
+ environment. There is no way at this time to modify this prefix.
+- INI-formatted project configs. eg `some-key = some-value` in the
+ `localPrefix` folder (ie, the `cwd`, or its nearest parent that contains
+ either a `node_modules` folder or `package.json` file.)
+- INI-formatted userconfig file. eg `some-key = some-value` in `~/.npmrc`.
+ The `userconfig` config value can be overridden by the `cli`, `env`, or
+ `project` configs to change this value.
+- INI-formatted globalconfig file. eg `some-key = some-value` in
+ the `globalPrefix` folder, which is inferred by looking at the location
+ of the node executable, or the `prefix` setting in the `cli`, `env`,
+ `project`, or `userconfig`. The `globalconfig` value at any of those
+ levels can override this.
+- INI-formatted builtin config file. eg `some-key = some-value` in
+ `/usr/local/lib/node_modules/npm/npmrc`. This is not configurable, and
+ is determined by looking in the `npmPath` folder.
+- Default values (passed in by npm when it loads this module).
+
+## USAGE
+
+```js
+const Config = require('@npmcli/config')
+// the types of all the configs we know about
+const types = require('./config/types.js')
+// default values for all the configs we know about
+const defaults = require('./config/defaults.js')
+// if you want -c to be short for --call and so on, define it here
+const shorthands = require('./config/shorthands.js')
+
+const conf = new Config({
+ // path to the npm module being run
+ npmPath: resolve(__dirname, '..'),
+ types,
+ shorthands,
+ defaults,
+ // optional, defaults to process.argv
+ argv: process.argv,
+ // optional, defaults to process.env
+ env: process.env,
+ // optional, defaults to process.execPath
+ execPath: process.execPath,
+ // optional, defaults to process.platform
+ platform: process.platform,
+ // optional, defaults to process.cwd()
+ cwd: process.cwd(),
+})
+
+// emits log events on the process object
+// see `proc-log` for more info
+process.on('log', (level, ...args) => {
+ console.log(level, ...args)
+})
+
+// returns a promise that fails if config loading fails, and
+// resolves when the config object is ready for action
+conf.load().then(() => {
+ conf.validate()
+ console.log('loaded ok! some-key = ' + conf.get('some-key'))
+}).catch(er => {
+ console.error('error loading configs!', er)
+})
+```
+
+## API
+
+The `Config` class is the sole export.
+
+```js
+const Config = require('@npmcli/config')
+```
+
+### static `Config.typeDefs`
+
+The type definitions passed to `nopt` for CLI option parsing and known
+configuration validation.
+
+### constructor `new Config(options)`
+
+Options:
+
+- `types` Types of all known config values. Note that some are effectively
+ given semantic value in the config loading process itself.
+- `shorthands` An object mapping a shorthand value to an array of CLI
+ arguments that replace it.
+- `defaults` Default values for each of the known configuration keys.
+ These should be defined for all configs given a type, and must be valid.
+- `npmPath` The path to the `npm` module, for loading the `builtin` config
+ file.
+- `cwd` Optional, defaults to `process.cwd()`, used for inferring the
+ `localPrefix` and loading the `project` config.
+- `platform` Optional, defaults to `process.platform`. Used when inferring
+ the `globalPrefix` from the `execPath`, since this is done diferently on
+ Windows.
+- `execPath` Optional, defaults to `process.execPath`. Used to infer the
+ `globalPrefix`.
+- `env` Optional, defaults to `process.env`. Source of the environment
+ variables for configuration.
+- `argv` Optional, defaults to `process.argv`. Source of the CLI options
+ used for configuration.
+
+Returns a `config` object, which is not yet loaded.
+
+Fields:
+
+- `config.globalPrefix` The prefix for `global` operations. Set by the
+ `prefix` config value, or defaults based on the location of the
+ `execPath` option.
+- `config.localPrefix` The prefix for `local` operations. Set by the
+ `prefix` config value on the CLI only, or defaults to either the `cwd` or
+ its nearest ancestor containing a `node_modules` folder or `package.json`
+ file.
+- `config.sources` A read-only `Map` of the file (or a comment, if no file
+ found, or relevant) to the config level loaded from that source.
+- `config.data` A `Map` of config level to `ConfigData` objects. These
+ objects should not be modified directly under any circumstances.
+ - `source` The source where this data was loaded from.
+ - `raw` The raw data used to generate this config data, as it was parsed
+ initially from the environment, config file, or CLI options.
+ - `data` The data object reflecting the inheritance of configs up to this
+ point in the chain.
+ - `loadError` Any errors encountered that prevented the loading of this
+ config data.
+- `config.list` A list sorted in priority of all the config data objects in
+ the prototype chain. `config.list[0]` is the `cli` level,
+ `config.list[1]` is the `env` level, and so on.
+- `cwd` The `cwd` param
+- `env` The `env` param
+- `argv` The `argv` param
+- `execPath` The `execPath` param
+- `platform` The `platform` param
+- `defaults` The `defaults` param
+- `shorthands` The `shorthands` param
+- `types` The `types` param
+- `npmPath` The `npmPath` param
+- `globalPrefix` The effective `globalPrefix`
+- `localPrefix` The effective `localPrefix`
+- `prefix` If `config.get('global')` is true, then `globalPrefix`,
+ otherwise `localPrefix`
+- `home` The user's home directory, found by looking at `env.HOME` or
+ calling `os.homedir()`.
+- `loaded` A boolean indicating whether or not configs are loaded
+- `valid` A getter that returns `true` if all the config objects are valid.
+ Any data objects that have been modified with `config.set(...)` will be
+ re-evaluated when `config.valid` is read.
+
+### `config.load()`
+
+Load configuration from the various sources of information.
+
+Returns a `Promise` that resolves when configuration is loaded, and fails
+if a fatal error is encountered.
+
+### `config.find(key)`
+
+Find the effective place in the configuration levels a given key is set.
+Returns one of: `cli`, `env`, `project`, `user`, `global`, `builtin`, or
+`default`.
+
+Returns `null` if the key is not set.
+
+### `config.get(key, where = 'cli')`
+
+Load the given key from the config stack.
+
+### `config.set(key, value, where = 'cli')`
+
+Set the key to the specified value, at the specified level in the config
+stack.
+
+### `config.delete(key, where = 'cli')`
+
+Delete the configuration key from the specified level in the config stack.
+
+### `config.validate(where)`
+
+Verify that all known configuration options are set to valid values, and
+log a warning if they are invalid.
+
+Invalid auth options will cause this method to throw an error with a `code`
+property of `ERR_INVALID_AUTH`, and a `problems` property listing the specific
+concerns with the current configuration.
+
+If `where` is not set, then all config objects are validated.
+
+Returns `true` if all configs are valid.
+
+Note that it's usually enough (and more efficient) to just check
+`config.valid`, since each data object is marked for re-evaluation on every
+`config.set()` operation.
+
+### `config.repair(problems)`
+
+Accept an optional array of problems (as thrown by `config.validate()`) and
+perform the necessary steps to resolve them. If no problems are provided,
+this method will call `config.validate()` internally to retrieve them.
+
+Note that you must `await config.save('user')` in order to persist the changes.
+
+### `config.isDefault(key)`
+
+Returns `true` if the value is coming directly from the
+default definitions, if the current value for the key config is
+coming from any other source, returns `false`.
+
+This method can be used for avoiding or tweaking default values, e.g:
+
+> Given a global default definition of foo='foo' it's possible to read that
+> value such as:
+>
+> ```js
+> const save = config.get('foo')
+> ```
+>
+> Now in a different place of your app it's possible to avoid using the `foo`
+> default value, by checking to see if the current config value is currently
+> one that was defined by the default definitions:
+>
+> ```js
+> const save = config.isDefault('foo') ? 'bar' : config.get('foo')
+> ```
+
+### `config.save(where)`
+
+Save the config file specified by the `where` param. Must be one of
+`project`, `user`, `global`, `builtin`.
diff --git a/node_modules/@npmcli/config/lib/env-replace.js b/workspaces/config/lib/env-replace.js
index c851f6e4d..c851f6e4d 100644
--- a/node_modules/@npmcli/config/lib/env-replace.js
+++ b/workspaces/config/lib/env-replace.js
diff --git a/node_modules/@npmcli/config/lib/errors.js b/workspaces/config/lib/errors.js
index fa3e20798..fa3e20798 100644
--- a/node_modules/@npmcli/config/lib/errors.js
+++ b/workspaces/config/lib/errors.js
diff --git a/node_modules/@npmcli/config/lib/index.js b/workspaces/config/lib/index.js
index e1d47ffcd..e1d47ffcd 100644
--- a/node_modules/@npmcli/config/lib/index.js
+++ b/workspaces/config/lib/index.js
diff --git a/node_modules/@npmcli/config/lib/nerf-dart.js b/workspaces/config/lib/nerf-dart.js
index d6ae4aa2a..d6ae4aa2a 100644
--- a/node_modules/@npmcli/config/lib/nerf-dart.js
+++ b/workspaces/config/lib/nerf-dart.js
diff --git a/node_modules/@npmcli/config/lib/parse-field.js b/workspaces/config/lib/parse-field.js
index 0c905bf23..0c905bf23 100644
--- a/node_modules/@npmcli/config/lib/parse-field.js
+++ b/workspaces/config/lib/parse-field.js
diff --git a/node_modules/@npmcli/config/lib/set-envs.js b/workspaces/config/lib/set-envs.js
index 0f5781aaf..0f5781aaf 100644
--- a/node_modules/@npmcli/config/lib/set-envs.js
+++ b/workspaces/config/lib/set-envs.js
diff --git a/node_modules/@npmcli/config/lib/type-defs.js b/workspaces/config/lib/type-defs.js
index 20a827c3d..20a827c3d 100644
--- a/node_modules/@npmcli/config/lib/type-defs.js
+++ b/workspaces/config/lib/type-defs.js
diff --git a/node_modules/@npmcli/config/lib/type-description.js b/workspaces/config/lib/type-description.js
index f5e0d164f..f5e0d164f 100644
--- a/node_modules/@npmcli/config/lib/type-description.js
+++ b/workspaces/config/lib/type-description.js
diff --git a/node_modules/@npmcli/config/lib/umask.js b/workspaces/config/lib/umask.js
index 195fad238..195fad238 100644
--- a/node_modules/@npmcli/config/lib/umask.js
+++ b/workspaces/config/lib/umask.js
diff --git a/workspaces/config/map.js b/workspaces/config/map.js
new file mode 100644
index 000000000..0b263fbec
--- /dev/null
+++ b/workspaces/config/map.js
@@ -0,0 +1 @@
+module.exports = t => t.replace(/^test/, 'lib')
diff --git a/node_modules/@npmcli/config/package.json b/workspaces/config/package.json
index 3293ffe5c..b9f41d29b 100644
--- a/node_modules/@npmcli/config/package.json
+++ b/workspaces/config/package.json
@@ -9,7 +9,8 @@
"description": "Configuration management for the npm cli",
"repository": {
"type": "git",
- "url": "https://github.com/npm/config.git"
+ "url": "https://github.com/npm/cli.git",
+ "directory": "workspaces/config"
},
"author": "GitHub Inc.",
"license": "ISC",
@@ -18,8 +19,8 @@
"snap": "tap",
"lint": "eslint \"**/*.js\"",
"postlint": "template-oss-check",
- "lintfix": "npm run lint -- --fix",
- "posttest": "npm run lint",
+ "lintfix": "node ../.. run lint -- --fix",
+ "posttest": "node ../.. run lint",
"template-oss-apply": "template-oss-apply --force"
},
"tap": {
@@ -32,7 +33,7 @@
},
"devDependencies": {
"@npmcli/eslint-config": "^4.0.0",
- "@npmcli/template-oss": "4.5.1",
+ "@npmcli/template-oss": "4.8.0",
"tap": "^16.0.1"
},
"dependencies": {
@@ -49,6 +50,6 @@
},
"templateOSS": {
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
- "version": "4.5.1"
+ "version": "4.8.0"
}
}
diff --git a/workspaces/config/scripts/example.js b/workspaces/config/scripts/example.js
new file mode 100644
index 000000000..bbb2992a5
--- /dev/null
+++ b/workspaces/config/scripts/example.js
@@ -0,0 +1,43 @@
+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/workspaces/config/tap-snapshots/test/index.js.test.cjs b/workspaces/config/tap-snapshots/test/index.js.test.cjs
new file mode 100644
index 000000000..6680fd237
--- /dev/null
+++ b/workspaces/config/tap-snapshots/test/index.js.test.cjs
@@ -0,0 +1,240 @@
+/* IMPORTANT
+ * This snapshot file is auto-generated, but designed for humans.
+ * It should be checked into source control and tracked carefully.
+ * Re-generate by setting TAP_SNAPSHOT=1 and running tests.
+ * Make sure to inspect the output below. Do not ignore changes!
+ */
+'use strict'
+exports[`test/index.js TAP credentials management def_auth > default registry 1`] = `
+Object {
+ "auth": "aGVsbG86d29ybGQ=",
+ "password": "world",
+ "username": "hello",
+}
+`
+
+exports[`test/index.js TAP credentials management def_auth > default registry after set 1`] = `
+Object {
+ "auth": "aGVsbG86d29ybGQ=",
+ "password": "world",
+ "username": "hello",
+}
+`
+
+exports[`test/index.js TAP credentials management def_auth > other registry 1`] = `
+Object {}
+`
+
+exports[`test/index.js TAP credentials management def_passNoUser > default registry 1`] = `
+Object {
+ "email": "i@izs.me",
+}
+`
+
+exports[`test/index.js TAP credentials management def_passNoUser > other registry 1`] = `
+Object {
+ "email": "i@izs.me",
+}
+`
+
+exports[`test/index.js TAP credentials management def_userNoPass > default registry 1`] = `
+Object {
+ "email": "i@izs.me",
+}
+`
+
+exports[`test/index.js TAP credentials management def_userNoPass > other registry 1`] = `
+Object {
+ "email": "i@izs.me",
+}
+`
+
+exports[`test/index.js TAP credentials management def_userpass > default registry 1`] = `
+Object {
+ "auth": "aGVsbG86d29ybGQ=",
+ "email": "i@izs.me",
+ "password": "world",
+ "username": "hello",
+}
+`
+
+exports[`test/index.js TAP credentials management def_userpass > default registry after set 1`] = `
+Object {
+ "auth": "aGVsbG86d29ybGQ=",
+ "email": "i@izs.me",
+ "password": "world",
+ "username": "hello",
+}
+`
+
+exports[`test/index.js TAP credentials management def_userpass > other registry 1`] = `
+Object {
+ "email": "i@izs.me",
+}
+`
+
+exports[`test/index.js TAP credentials management nerfed_auth > default registry 1`] = `
+Object {
+ "auth": "aGVsbG86d29ybGQ=",
+ "password": "world",
+ "username": "hello",
+}
+`
+
+exports[`test/index.js TAP credentials management nerfed_auth > default registry after set 1`] = `
+Object {
+ "auth": "aGVsbG86d29ybGQ=",
+ "password": "world",
+ "username": "hello",
+}
+`
+
+exports[`test/index.js TAP credentials management nerfed_auth > other registry 1`] = `
+Object {}
+`
+
+exports[`test/index.js TAP credentials management nerfed_authToken > default registry 1`] = `
+Object {
+ "token": "0bad1de4",
+}
+`
+
+exports[`test/index.js TAP credentials management nerfed_authToken > default registry after set 1`] = `
+Object {
+ "token": "0bad1de4",
+}
+`
+
+exports[`test/index.js TAP credentials management nerfed_authToken > other registry 1`] = `
+Object {}
+`
+
+exports[`test/index.js TAP credentials management nerfed_mtls > default registry 1`] = `
+Object {
+ "certfile": "/path/to/cert",
+ "keyfile": "/path/to/key",
+}
+`
+
+exports[`test/index.js TAP credentials management nerfed_mtls > default registry after set 1`] = `
+Object {
+ "certfile": "/path/to/cert",
+ "keyfile": "/path/to/key",
+}
+`
+
+exports[`test/index.js TAP credentials management nerfed_mtls > other registry 1`] = `
+Object {}
+`
+
+exports[`test/index.js TAP credentials management nerfed_mtlsAuthToken > default registry 1`] = `
+Object {
+ "certfile": "/path/to/cert",
+ "keyfile": "/path/to/key",
+ "token": "0bad1de4",
+}
+`
+
+exports[`test/index.js TAP credentials management nerfed_mtlsAuthToken > default registry after set 1`] = `
+Object {
+ "certfile": "/path/to/cert",
+ "keyfile": "/path/to/key",
+ "token": "0bad1de4",
+}
+`
+
+exports[`test/index.js TAP credentials management nerfed_mtlsAuthToken > other registry 1`] = `
+Object {}
+`
+
+exports[`test/index.js TAP credentials management nerfed_mtlsUserPass > default registry 1`] = `
+Object {
+ "auth": "aGVsbG86d29ybGQ=",
+ "certfile": "/path/to/cert",
+ "email": "i@izs.me",
+ "keyfile": "/path/to/key",
+ "password": "world",
+ "username": "hello",
+}
+`
+
+exports[`test/index.js TAP credentials management nerfed_mtlsUserPass > default registry after set 1`] = `
+Object {
+ "auth": "aGVsbG86d29ybGQ=",
+ "certfile": "/path/to/cert",
+ "email": "i@izs.me",
+ "keyfile": "/path/to/key",
+ "password": "world",
+ "username": "hello",
+}
+`
+
+exports[`test/index.js TAP credentials management nerfed_mtlsUserPass > other registry 1`] = `
+Object {
+ "email": "i@izs.me",
+}
+`
+
+exports[`test/index.js TAP credentials management nerfed_userpass > default registry 1`] = `
+Object {
+ "auth": "aGVsbG86d29ybGQ=",
+ "email": "i@izs.me",
+ "password": "world",
+ "username": "hello",
+}
+`
+
+exports[`test/index.js TAP credentials management nerfed_userpass > default registry after set 1`] = `
+Object {
+ "auth": "aGVsbG86d29ybGQ=",
+ "email": "i@izs.me",
+ "password": "world",
+ "username": "hello",
+}
+`
+
+exports[`test/index.js TAP credentials management nerfed_userpass > other registry 1`] = `
+Object {
+ "email": "i@izs.me",
+}
+`
+
+exports[`test/index.js TAP credentials management none_authToken > default registry 1`] = `
+Object {
+ "token": "0bad1de4",
+}
+`
+
+exports[`test/index.js TAP credentials management none_authToken > default registry after set 1`] = `
+Object {
+ "token": "0bad1de4",
+}
+`
+
+exports[`test/index.js TAP credentials management none_authToken > other registry 1`] = `
+Object {}
+`
+
+exports[`test/index.js TAP credentials management none_emptyConfig > default registry 1`] = `
+Object {}
+`
+
+exports[`test/index.js TAP credentials management none_emptyConfig > other registry 1`] = `
+Object {}
+`
+
+exports[`test/index.js TAP credentials management none_lcAuthToken > default registry 1`] = `
+Object {}
+`
+
+exports[`test/index.js TAP credentials management none_lcAuthToken > other registry 1`] = `
+Object {}
+`
+
+exports[`test/index.js TAP credentials management none_noConfig > default registry 1`] = `
+Object {}
+`
+
+exports[`test/index.js TAP credentials management none_noConfig > other registry 1`] = `
+Object {}
+`
diff --git a/workspaces/config/tap-snapshots/test/type-description.js.test.cjs b/workspaces/config/tap-snapshots/test/type-description.js.test.cjs
new file mode 100644
index 000000000..9d80f7e09
--- /dev/null
+++ b/workspaces/config/tap-snapshots/test/type-description.js.test.cjs
@@ -0,0 +1,449 @@
+/* IMPORTANT
+ * This snapshot file is auto-generated, but designed for humans.
+ * It should be checked into source control and tracked carefully.
+ * Re-generate by setting TAP_SNAPSHOT=1 and running tests.
+ * Make sure to inspect the output below. Do not ignore changes!
+ */
+'use strict'
+exports[`test/type-description.js TAP > must match snapshot 1`] = `
+Object {
+ "_exit": Array [
+ "boolean value (true or false)",
+ ],
+ "access": Array [
+ null,
+ "restricted",
+ "public",
+ ],
+ "all": Array [
+ "boolean value (true or false)",
+ ],
+ "allow-same-version": Array [
+ "boolean value (true or false)",
+ ],
+ "also": Array [
+ null,
+ "dev",
+ "development",
+ ],
+ "always-auth": Array [
+ "boolean value (true or false)",
+ ],
+ "audit": Array [
+ "boolean value (true or false)",
+ ],
+ "audit-level": Array [
+ "low",
+ "moderate",
+ "high",
+ "critical",
+ "none",
+ null,
+ ],
+ "auth-type": Array [
+ "legacy",
+ "sso",
+ "saml",
+ "oauth",
+ ],
+ "before": Array [
+ null,
+ "valid Date string",
+ ],
+ "bin-links": Array [
+ "boolean value (true or false)",
+ ],
+ "browser": Array [
+ null,
+ "boolean value (true or false)",
+ Function String(),
+ ],
+ "ca": Array [
+ null,
+ Function String(),
+ Function Array(),
+ ],
+ "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",
+ ],
+ "cache-min": Array [
+ "numeric value",
+ ],
+ "cafile": Array [
+ "valid filesystem path",
+ ],
+ "call": Array [
+ Function String(),
+ ],
+ "cert": Array [
+ null,
+ Function String(),
+ ],
+ "cidr": Array [
+ null,
+ Function String(),
+ Function Array(),
+ ],
+ "color": Array [
+ "always",
+ "boolean value (true or false)",
+ ],
+ "commit-hooks": Array [
+ "boolean value (true or false)",
+ ],
+ "depth": Array [
+ "numeric value",
+ ],
+ "description": Array [
+ "boolean value (true or false)",
+ ],
+ "dev": Array [
+ "boolean value (true or false)",
+ ],
+ "dry-run": Array [
+ "boolean value (true or false)",
+ ],
+ "editor": Array [
+ Function String(),
+ ],
+ "engine-strict": Array [
+ "boolean value (true or false)",
+ ],
+ "fetch-retries": Array [
+ "numeric value",
+ ],
+ "fetch-retry-factor": Array [
+ "numeric value",
+ ],
+ "fetch-retry-maxtimeout": Array [
+ "numeric value",
+ ],
+ "fetch-retry-mintimeout": Array [
+ "numeric value",
+ ],
+ "force": Array [
+ "boolean value (true or false)",
+ ],
+ "format-package-lock": Array [
+ "boolean value (true or false)",
+ ],
+ "fund": Array [
+ "boolean value (true or false)",
+ ],
+ "git": Array [
+ Function String(),
+ ],
+ "git-tag-version": Array [
+ "boolean value (true or false)",
+ ],
+ "global": Array [
+ "boolean value (true or false)",
+ ],
+ "global-style": Array [
+ "boolean value (true or false)",
+ ],
+ "globalconfig": Array [
+ "valid filesystem path",
+ ],
+ "heading": Array [
+ Function String(),
+ ],
+ "https-proxy": Array [
+ null,
+ "full url with \\"http://\\"",
+ ],
+ "if-present": Array [
+ "boolean value (true or false)",
+ ],
+ "ignore-prepublish": Array [
+ "boolean value (true or false)",
+ ],
+ "ignore-scripts": Array [
+ "boolean value (true or false)",
+ ],
+ "include": Array [
+ Function Array(),
+ "prod",
+ "dev",
+ "optional",
+ "peer",
+ ],
+ "include-staged": Array [
+ "boolean value (true or false)",
+ ],
+ "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",
+ ],
+ "json": Array [
+ "boolean value (true or false)",
+ ],
+ "key": Array [
+ null,
+ Function String(),
+ ],
+ "legacy-bundling": Array [
+ "boolean value (true or false)",
+ ],
+ "legacy-peer-deps": Array [
+ "boolean value (true or false)",
+ ],
+ "link": Array [
+ "boolean value (true or false)",
+ ],
+ "loglevel": Array [
+ "silent",
+ "error",
+ "warn",
+ "notice",
+ "http",
+ "timing",
+ "info",
+ "verbose",
+ "silly",
+ ],
+ "logs-max": Array [
+ "numeric value",
+ ],
+ "long": Array [
+ "boolean value (true or false)",
+ ],
+ "maxsockets": Array [
+ "numeric value",
+ ],
+ "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(),
+ ],
+ "offline": Array [
+ "boolean value (true or false)",
+ ],
+ "omit": Array [
+ Function Array(),
+ "dev",
+ "optional",
+ "peer",
+ ],
+ "only": Array [
+ null,
+ "dev",
+ "development",
+ "prod",
+ "production",
+ ],
+ "optional": Array [
+ "boolean value (true or false)",
+ ],
+ "otp": Array [
+ null,
+ Function String(),
+ ],
+ "package": Array [
+ Function String(),
+ Function Array(),
+ ],
+ "package-lock": Array [
+ "boolean value (true or false)",
+ ],
+ "package-lock-only": Array [
+ "boolean value (true or false)",
+ ],
+ "parseable": Array [
+ "boolean value (true or false)",
+ ],
+ "prefer-offline": Array [
+ "boolean value (true or false)",
+ ],
+ "prefer-online": Array [
+ "boolean value (true or false)",
+ ],
+ "prefix": Array [
+ "valid filesystem path",
+ ],
+ "preid": Array [
+ Function String(),
+ ],
+ "production": Array [
+ "boolean value (true or false)",
+ ],
+ "progress": Array [
+ "boolean value (true or false)",
+ ],
+ "proxy": Array [
+ null,
+ false,
+ "full url with \\"http://\\"",
+ ],
+ "read-only": Array [
+ "boolean value (true or false)",
+ ],
+ "rebuild-bundle": Array [
+ "boolean value (true or false)",
+ ],
+ "registry": Array [
+ null,
+ "full url with \\"http://\\"",
+ ],
+ "rollback": Array [
+ "boolean value (true or false)",
+ ],
+ "save": Array [
+ "boolean value (true or false)",
+ ],
+ "save-bundle": Array [
+ "boolean value (true or false)",
+ ],
+ "save-dev": Array [
+ "boolean value (true or false)",
+ ],
+ "save-exact": Array [
+ "boolean value (true or false)",
+ ],
+ "save-optional": Array [
+ "boolean value (true or false)",
+ ],
+ "save-prefix": Array [
+ Function String(),
+ ],
+ "save-prod": Array [
+ "boolean value (true or false)",
+ ],
+ "scope": Array [
+ Function String(),
+ ],
+ "script-shell": Array [
+ null,
+ Function String(),
+ ],
+ "scripts-prepend-node-path": Array [
+ "boolean value (true or false)",
+ "auto",
+ "warn-only",
+ ],
+ "searchexclude": Array [
+ null,
+ Function String(),
+ ],
+ "searchlimit": Array [
+ "numeric value",
+ ],
+ "searchopts": Array [
+ Function String(),
+ ],
+ "searchstaleness": Array [
+ "numeric value",
+ ],
+ "send-metrics": Array [
+ "boolean value (true or false)",
+ ],
+ "shell": Array [
+ Function String(),
+ ],
+ "shrinkwrap": Array [
+ "boolean value (true or false)",
+ ],
+ "sign-git-commit": Array [
+ "boolean value (true or false)",
+ ],
+ "sign-git-tag": Array [
+ "boolean value (true or false)",
+ ],
+ "sso-poll-frequency": Array [
+ "numeric value",
+ ],
+ "sso-type": Array [
+ null,
+ "oauth",
+ "saml",
+ ],
+ "strict-ssl": Array [
+ "boolean value (true or false)",
+ ],
+ "tag": Array [
+ Function String(),
+ ],
+ "tag-version-prefix": Array [
+ Function String(),
+ ],
+ "timing": Array [
+ "boolean value (true or false)",
+ ],
+ "tmp": Array [
+ "valid filesystem path",
+ ],
+ "umask": Array [
+ "octal number in range 0o000..0o777 (0..511)",
+ ],
+ "unicode": Array [
+ "boolean value (true or false)",
+ ],
+ "update-notifier": Array [
+ "boolean value (true or false)",
+ ],
+ "usage": Array [
+ "boolean value (true or false)",
+ ],
+ "user-agent": Array [
+ Function String(),
+ ],
+ "userconfig": Array [
+ "valid filesystem path",
+ ],
+ "version": Array [
+ "boolean value (true or false)",
+ ],
+ "versions": Array [
+ "boolean value (true or false)",
+ ],
+ "viewer": Array [
+ Function String(),
+ ],
+}
+`
diff --git a/workspaces/config/test/env-replace.js b/workspaces/config/test/env-replace.js
new file mode 100644
index 000000000..c2b570364
--- /dev/null
+++ b/workspaces/config/test/env-replace.js
@@ -0,0 +1,13 @@
+const envReplace = require('../lib/env-replace.js')
+const t = require('tap')
+
+const env = {
+ foo: 'bar',
+ bar: 'baz',
+}
+
+t.equal(envReplace('\\${foo}', env), '${foo}')
+t.equal(envReplace('\\\\${foo}', env), '\\bar')
+t.equal(envReplace('${baz}', env), '${baz}')
+t.equal(envReplace('\\${baz}', env), '${baz}')
+t.equal(envReplace('\\\\${baz}', env), '\\${baz}')
diff --git a/workspaces/config/test/fixtures/cafile b/workspaces/config/test/fixtures/cafile
new file mode 100644
index 000000000..0bc922b25
--- /dev/null
+++ b/workspaces/config/test/fixtures/cafile
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIICjTCCAfigAwIBAgIEMaYgRzALBgkqhkiG9w0BAQQwRTELMAkGA1UEBhMCVVMx
+NjA0BgNVBAoTLU5hdGlvbmFsIEFlcm9uYXV0aWNzIGFuZCBTcGFjZSBBZG1pbmlz
+dHJhdGlvbjAmFxE5NjA1MjgxMzQ5MDUrMDgwMBcROTgwNTI4MTM0OTA1KzA4MDAw
+ZzELMAkGA1UEBhMCVVMxNjA0BgNVBAoTLU5hdGlvbmFsIEFlcm9uYXV0aWNzIGFu
+ZCBTcGFjZSBBZG1pbmlzdHJhdGlvbjEgMAkGA1UEBRMCMTYwEwYDVQQDEwxTdGV2
+ZSBTY2hvY2gwWDALBgkqhkiG9w0BAQEDSQAwRgJBALrAwyYdgxmzNP/ts0Uyf6Bp
+miJYktU/w4NG67ULaN4B5CnEz7k57s9o3YY3LecETgQ5iQHmkwlYDTL2fTgVfw0C
+AQOjgaswgagwZAYDVR0ZAQH/BFowWDBWMFQxCzAJBgNVBAYTAlVTMTYwNAYDVQQK
+Ey1OYXRpAAAAACBBZXJvbmF1dGljcyBhbmQgU3BhY2UgQWRtaW5pc3RyYXRpb24x
+DTALBgNVBAMTBENSTDEwFwYDVR0BAQH/BA0wC4AJODMyOTcwODEwMBgGA1UdAgQR
+MA8ECTgzMjk3MDgyM4ACBSAwDQYDVR0KBAYwBAMCBkAwCwYJKoZIhvcNAQEEA4GB
+AH2y1VCEw/A4zaXzSYZJTTUi3uawbbFiS2yxHvgf28+8Js0OHXk1H1w2d6qOHH21
+X82tZXd/0JtG0g1T9usFFBDvYK8O0ebgz/P5ELJnBL2+atObEuJy1ZZ0pBDWINR3
+WkDNLCGiTkCKp0F5EWIrVDwh54NNevkCQRZita+z4IBO
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+AAAAAACCAfigAwIBAgIEMaYgRzALBgkqhkiG9w0BAQQwRTELMAkGA1UEBhMCVVMx
+NjA0BgNVBAoTLU5hdGlvbmFsIEFlcm9uYXV0aWNzIGFuZCBTcGFjZSBBZG1pbmlz
+dHJhdGlvbjAmFxE5NjA1MjgxMzQ5MDUrMDgwMBcROTgwNTI4MTM0OTA1KzA4MDAw
+ZzELMAkGA1UEBhMCVVMxNjA0BgNVBAoTLU5hdGlvbmFsIEFlcm9uYXV0aWNzIGFu
+ZCBTcGFjZSBBZG1pbmlzdHJhdGlvbjEgMAkGA1UEBRMCMTYwEwYDVQQDEwxTdGV2
+ZSBTY2hvY2gwWDALBgkqhkiG9w0BAQEDSQAwRgJBALrAwyYdgxmzNP/ts0Uyf6Bp
+miJYktU/w4NG67ULaN4B5CnEz7k57s9o3YY3LecETgQ5iQHmkwlYDTL2fTgVfw0C
+AQOjgaswgagwZAYDVR0ZAQH/BFowWDBWMFQxCzAJBgNVBAYTAlVTMTYwNAYDVQQK
+Ey1OYXRpb25hbCBBZXJvbmF1dGljcyBhbmQgU3BhY2UgQWRtaW5pc3RyYXRpb24x
+DTALBgNVBAMTBENSTDEwFwYDVR0BAQH/BA0wC4AJODMyOTcwODEwMBgGA1UdAgQR
+MA8ECTgzMjk3MDgyM4ACBSAwDQYDVR0KBAYwBAMCBkAwCwYJKoZIhvcNAQEEA4GB
+AH2y1VCEw/A4zaXzSYZJTTUi3uawbbFiS2yxHvgf28+8Js0OHXk1H1w2d6qOHH21
+X82tZXd/0JtG0g1T9usFFBDvYK8O0ebgz/P5ELJnBL2+atObEuJy1ZZ0pBDWINR3
+WkDNLCGiTkCKp0F5EWIrVDwh54NNevkCQRZita+z4IBO
+-----END CERTIFICATE-----
diff --git a/workspaces/config/test/fixtures/defaults.js b/workspaces/config/test/fixtures/defaults.js
new file mode 100644
index 000000000..322ceb018
--- /dev/null
+++ b/workspaces/config/test/fixtures/defaults.js
@@ -0,0 +1,143 @@
+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
new file mode 100644
index 000000000..ce0aff6f3
--- /dev/null
+++ b/workspaces/config/test/fixtures/definitions.js
@@ -0,0 +1,2609 @@
+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=<type>\`.
+
+ 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 <pkg>\` 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
+ <https://nodejs.org/api/os.html#os_os_tmpdir>
+ `,
+ 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',
+ },
+}
diff --git a/workspaces/config/test/fixtures/flatten.js b/workspaces/config/test/fixtures/flatten.js
new file mode 100644
index 000000000..588d05bf0
--- /dev/null
+++ b/workspaces/config/test/fixtures/flatten.js
@@ -0,0 +1,33 @@
+// use the defined flattening function, and copy over any scoped
+// registries and registry-specific "nerfdart" configs verbatim
+//
+// TODO: make these getters so that we only have to make dirty
+// the thing that changed, and then flatten the fields that
+// could have changed when a config.set is called.
+//
+// TODO: move nerfdart auth stuff into a nested object that
+// is only passed along to paths that end up calling npm-registry-fetch.
+const definitions = require('./definitions.js')
+const flatten = (obj, flat = {}) => {
+ for (const [key, val] of Object.entries(obj)) {
+ const def = definitions[key]
+ if (def && def.flatten) {
+ def.flatten(key, obj, flat)
+ } else if (/@.*:registry$/i.test(key) || /^\/\//.test(key)) {
+ flat[key] = val
+ }
+ }
+
+ // XXX make this the bin/npm-cli.js file explicitly instead
+ // otherwise using npm programmatically is a bit of a pain.
+ flat.npmBin = require.main ? require.main.filename
+ : /* istanbul ignore next - not configurable property */ undefined
+ flat.nodeBin = process.env.NODE || process.execPath
+
+ // XXX should this be sha512? is it even relevant?
+ flat.hashAlgorithm = 'sha1'
+
+ return flat
+}
+
+module.exports = flatten
diff --git a/workspaces/config/test/fixtures/shorthands.js b/workspaces/config/test/fixtures/shorthands.js
new file mode 100644
index 000000000..5c460c661
--- /dev/null
+++ b/workspaces/config/test/fixtures/shorthands.js
@@ -0,0 +1,41 @@
+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
new file mode 100644
index 000000000..0f8cedfd6
--- /dev/null
+++ b/workspaces/config/test/fixtures/types.js
@@ -0,0 +1,151 @@
+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,
+}
diff --git a/workspaces/config/test/index.js b/workspaces/config/test/index.js
new file mode 100644
index 000000000..8dbee0588
--- /dev/null
+++ b/workspaces/config/test/index.js
@@ -0,0 +1,1295 @@
+const t = require('tap')
+
+const fs = require('fs')
+const { readFileSync } = fs
+
+// when running with `npm test` it adds environment variables that
+// mess with the things we expect here, so delete all of those.
+Object.keys(process.env)
+ .filter(k => /^npm_/.test(k))
+ .forEach(k => delete process.env[k])
+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 typeDefs = require('../lib/type-defs.js')
+
+const { resolve, join, dirname } = require('path')
+
+const Config = t.mock('../', {
+ 'fs/promises': {
+ ...fs.promises,
+ readFile: async (path, ...args) => {
+ if (path.includes('WEIRD-ERROR')) {
+ throw Object.assign(new Error('weird error'), { code: 'EWEIRD' })
+ }
+
+ return fs.promises.readFile(path, ...args)
+ },
+ },
+})
+
+// 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 => {
+ const npmPath = t.testdir()
+ const c = new Config({
+ definitions: {},
+ npmPath,
+ })
+
+ t.test('default some values from process object', t => {
+ const { env, execPath, platform } = process
+ const cwd = process.cwd()
+ t.equal(c.env, env, 'env')
+ t.equal(c.execPath, execPath, 'execPath')
+ t.equal(c.cwd, cwd, 'cwd')
+ t.equal(c.platform, platform, 'platform')
+ t.end()
+ })
+
+ t.test('not loaded yet', t => {
+ t.equal(c.loaded, false, 'not loaded yet')
+ t.throws(() => c.get('foo'), {
+ message: 'call config.load() before reading values',
+ })
+ t.throws(() => c.find('foo'), {
+ message: 'call config.load() before reading values',
+ })
+ t.throws(() => c.set('foo', 'bar'), {
+ message: 'call config.load() before setting values',
+ })
+ t.throws(() => c.delete('foo'), {
+ message: 'call config.load() before deleting values',
+ })
+ t.rejects(() => c.save('user'), {
+ message: 'call config.load() before saving',
+ })
+ t.throws(() => c.data.set('user', {}), {
+ message: 'cannot change internal config data structure',
+ })
+ t.throws(() => c.data.delete('user'), {
+ message: 'cannot change internal config data structure',
+ })
+ t.end()
+ })
+
+ t.test('data structure all wired up properly', t => {
+ // verify that the proto objects are all wired up properly
+ c.list.forEach((data, i) => {
+ t.equal(Object.getPrototypeOf(data), c.list[i + 1] || null)
+ })
+ t.equal(c.data.get('default').data, c.list[c.list.length - 1])
+ t.equal(c.data.get('cli').data, c.list[0])
+ t.end()
+ })
+
+ t.end()
+})
+
+t.test('load from files and environment variables', t => {
+ // need to get the dir because we reference it in the contents
+ const path = t.testdir()
+ t.testdir({
+ npm: {
+ npmrc: `
+builtin-config = true
+foo = from-builtin
+userconfig = ${path}/user/.npmrc-from-builtin
+`,
+ },
+ global: {
+ etc: {
+ npmrc: `
+global-config = true
+foo = from-global
+userconfig = ${path}/should-not-load-this-file
+`,
+ },
+ },
+ user: {
+ '.npmrc': `
+default-user-config-in-home = true
+foo = from-default-userconfig
+prefix = ${path}/global
+`,
+ '.npmrc-from-builtin': `
+user-config-from-builtin = true
+foo = from-custom-userconfig
+globalconfig = ${path}/global/etc/npmrc
+`,
+ },
+ project: {
+ node_modules: {},
+ '.npmrc': `
+project-config = true
+foo = from-project-config
+loglevel = yolo
+`,
+ },
+ 'project-no-config': {
+ 'package.json': '{"name":"@scope/project"}',
+ },
+ })
+
+ const logs = []
+ const logHandler = (...args) => logs.push(args)
+ process.on('log', logHandler)
+ t.teardown(() => process.off('log', logHandler))
+
+ const argv = [
+ process.execPath,
+ __filename,
+ '-v',
+ '--no-audit',
+ 'config',
+ 'get',
+ 'foo',
+ '--also=dev',
+ '--registry=hello',
+ '--omit=cucumber',
+ '--access=blueberry',
+ '--multiple-numbers=what kind of fruit is not a number',
+ '--multiple-numbers=a baNaNa!!',
+ '-C',
+ ]
+
+ t.test('dont let userconfig be the same as builtin config', async t => {
+ const config = new Config({
+ npmPath: `${path}/npm`,
+ env: {},
+ argv: [process.execPath, __filename, '--userconfig', `${path}/npm/npmrc`],
+ cwd: `${path}/project`,
+ shorthands,
+ definitions,
+ })
+ await t.rejects(() => config.load(), {
+ message: `double-loading config "${resolve(path, 'npm/npmrc')}" as "user",` +
+ ' previously loaded as "builtin"',
+ })
+ })
+
+ t.test('dont load project config if global is true', async t => {
+ const config = new Config({
+ npmPath: `${path}/npm`,
+ env: {},
+ argv: [process.execPath, __filename, '--global'],
+ cwd: `${path}/project`,
+ shorthands,
+ definitions,
+ })
+
+ await config.load()
+ const source = config.data.get('project').source
+ t.equal(source, '(global mode enabled, ignored)', 'data has placeholder')
+ t.equal(config.sources.get(source), 'project', 'sources has project')
+ })
+
+ t.test('dont load project config if location is global', async t => {
+ const config = new Config({
+ npmPath: `${path}/npm`,
+ env: {},
+ argv: [process.execPath, __filename, '--location', 'global'],
+ cwd: `${path}/project`,
+ shorthands,
+ definitions,
+ })
+
+ await config.load()
+ const source = config.data.get('project').source
+ t.equal(source, '(global mode enabled, ignored)', 'data has placeholder')
+ t.equal(config.sources.get(source), 'project', 'sources has project')
+ t.ok(config.localPrefix, 'localPrefix is set')
+ })
+
+ t.test('verbose log if config file read is weird error', async t => {
+ const config = new Config({
+ npmPath: path,
+ env: {},
+ argv: [process.execPath,
+ __filename,
+ '--userconfig',
+ `${path}/WEIRD-ERROR`,
+ '--no-workspaces'],
+ cwd: path,
+ shorthands,
+ definitions,
+ })
+ logs.length = 0
+ await config.load()
+ t.match(logs, [['verbose', 'config', 'error loading user config', {
+ message: 'weird error',
+ }]])
+ logs.length = 0
+ })
+
+ t.test('load configs from all files, cli, and env', async t => {
+ const env = {
+ npm_config_foo: 'from-env',
+ npm_config_global: '',
+ npm_config_prefix: '/something',
+ }
+ const config = new Config({
+ npmPath: `${path}/npm`,
+ env,
+ argv,
+ cwd: `${path}/project`,
+
+ shorthands,
+ definitions,
+ })
+
+ t.equal(config.globalPrefix, null, 'globalPrefix missing before load')
+
+ await config.load()
+
+ t.equal(config.globalPrefix, resolve('/something'), 'env-defined prefix should be loaded')
+
+ t.equal(config.get('global', 'env'), undefined, 'empty env is missing')
+ t.equal(config.get('global'), false, 'empty env is missing')
+
+ config.set('asdf', 'quux', 'global')
+ await config.save('global')
+ const gres = readFileSync(`${path}/global/etc/npmrc`, 'utf8')
+ t.match(gres, 'asdf=quux')
+
+ const cliData = config.data.get('cli')
+ t.throws(() => cliData.loadError = true, {
+ message: 'cannot set ConfigData loadError after load',
+ })
+ t.throws(() => cliData.source = 'foo', {
+ message: 'cannot set ConfigData source more than once',
+ })
+ t.throws(() => cliData.raw = 1234, {
+ message: 'cannot set ConfigData raw after load',
+ })
+
+ config.argv = []
+
+ t.throws(() => config.loadCLI(), {
+ message: 'double-loading "cli" configs from command line options, previously loaded from' +
+ ' command line options',
+ })
+ t.rejects(() => config.loadUserConfig(), {
+ message: `double-loading "user" configs from ${resolve(path, 'should-not-load-this-file')}` +
+ `, previously loaded from ${resolve(path, 'user/.npmrc-from-builtin')}`,
+ })
+
+ t.equal(config.loaded, true, 'config is loaded')
+
+ await t.rejects(() => config.load(), {
+ message: 'attempting to load npm config multiple times',
+ })
+ t.equal(config.find('no config value here'), null)
+
+ t.equal(config.prefix, config.localPrefix, 'prefix is local prefix when not global')
+ config.set('global', true)
+ t.equal(config.prefix, config.globalPrefix, 'prefix is global prefix when global')
+ config.set('global', false)
+ t.equal(config.find('global'), 'cli')
+ config.delete('global')
+ t.equal(config.find('global'), 'default')
+
+ t.throws(() => config.get('foo', 'barbaz'), {
+ message: 'invalid config location param: barbaz',
+ })
+ t.throws(() => config.set('foo', 1234, 'barbaz'), {
+ message: 'invalid config location param: barbaz',
+ })
+ t.throws(() => config.delete('foo', 'barbaz'), {
+ message: 'invalid config location param: barbaz',
+ })
+
+ t.match(config.sources, new Map([
+ ['default values', 'default'],
+ [resolve(path, 'npm/npmrc'), 'builtin'],
+ ['command line options', 'cli'],
+ ['environment', 'env'],
+ [resolve(path, 'project/.npmrc'), 'project'],
+ [resolve(path, 'user/.npmrc-from-builtin'), 'user'],
+ [resolve(path, 'global/etc/npmrc'), 'global'],
+ ]))
+
+ t.strictSame({
+ version: config.get('version'),
+ audit: config.get('audit'),
+ 'project-config': config.get('project-config'),
+ foo: config.get('foo'),
+ 'user-config-from-builtin': config.get('user-config-from-builtin'),
+ 'global-config': config.get('global-config'),
+ 'builtin-config': config.get('builtin-config'),
+ all: config.get('all'),
+ }, {
+ version: true,
+ audit: false,
+ 'project-config': true,
+ foo: 'from-env',
+ 'user-config-from-builtin': true,
+ 'global-config': true,
+ 'builtin-config': true,
+ all: config.get('all'),
+ })
+
+ t.match(env, {
+ npm_config_user_config_from_builtin: 'true',
+ npm_config_audit: '',
+ npm_config_version: 'true',
+ npm_config_foo: 'from-env',
+ npm_config_builtin_config: 'true',
+ }, 'set env values')
+
+ // warn logs are emitted as a side effect of validate
+ config.validate()
+ 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', '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'],
+ ['warn', 'invalid config', 'Must be one of:', 'null, restricted, public'],
+ ['warn', 'invalid config', 'multiple-numbers="what kind of fruit is not a number"',
+ 'set in command line options'],
+ ['warn', 'invalid config', 'Must be one or more', 'numeric value'],
+ ['warn', 'invalid config', 'multiple-numbers="a baNaNa!!"', 'set in command line options'],
+ ['warn', 'invalid config', 'Must be one or more', 'numeric value'],
+ ['warn', 'invalid config', 'prefix=true', 'set in command line options'],
+ ['warn', 'invalid config', 'Must be', 'valid filesystem path'],
+ ['warn', 'config', 'also', 'Please use --include=dev instead.'],
+ ['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(', '),
+ ],
+ ])
+ t.equal(config.valid, false)
+ logs.length = 0
+
+ // set a new value that defaults to cli source
+ config.set('cli-config', 1)
+
+ t.ok(config.isDefault('methane'),
+ 'should return true if value is retrieved from default definitions')
+ t.notOk(config.isDefault('cli-config'),
+ 'should return false for a cli-defined value')
+ t.notOk(config.isDefault('foo'),
+ 'should return false for a env-defined value')
+ t.notOk(config.isDefault('project-config'),
+ 'should return false for a project-defined value')
+ t.notOk(config.isDefault('default-user-config-in-home'),
+ 'should return false for a user-defined value')
+ t.notOk(config.isDefault('global-config'),
+ 'should return false for a global-defined value')
+ t.notOk(config.isDefault('builtin-config'),
+ 'should return false for a builtin-defined value')
+
+ // make sure isDefault still works as intended after
+ // setting and deleting values in differente sources
+ config.set('methane', 'H2O', 'cli')
+ t.notOk(config.isDefault('methane'),
+ 'should no longer return true now that a cli value was defined')
+ config.delete('methane', 'cli')
+ t.ok(config.isDefault('methane'),
+ 'should return true once again now that values is retrieved from defaults')
+ })
+
+ t.test('normalize config env keys', async t => {
+ const env = {
+ npm_config_bAr: 'bAr env',
+ NPM_CONFIG_FOO: 'FOO env',
+ 'npm_config_//reg.example/UP_CASE/:username': 'ME',
+ 'npm_config_//reg.example/UP_CASE/:_password': 'Shhhh!',
+ 'NPM_CONFIG_//reg.example/UP_CASE/:_authToken': 'sEcReT',
+ }
+ const config = new Config({
+ npmPath: `${path}/npm`,
+ env,
+ argv,
+ cwd: `${path}/project`,
+
+ shorthands,
+ definitions,
+ })
+
+ await config.load()
+
+ t.strictSame({
+ bar: config.get('bar'),
+ foo: config.get('foo'),
+ '//reg.example/UP_CASE/:username': config.get('//reg.example/UP_CASE/:username'),
+ '//reg.example/UP_CASE/:_password': config.get('//reg.example/UP_CASE/:_password'),
+ '//reg.example/UP_CASE/:_authToken': config.get('//reg.example/UP_CASE/:_authToken'),
+ }, {
+ bar: 'bAr env',
+ foo: 'FOO env',
+ '//reg.example/UP_CASE/:username': 'ME',
+ '//reg.example/UP_CASE/:_password': 'Shhhh!',
+ '//reg.example/UP_CASE/:_authToken': 'sEcReT',
+ })
+ })
+
+ t.test('do not double-load project/user config', async t => {
+ const env = {
+ npm_config_foo: 'from-env',
+ npm_config_globalconfig: '/this/path/does/not/exist',
+ }
+
+ const config = new Config({
+ npmPath: `${path}/npm`,
+ env,
+ argv: [process.execPath, __filename, '--userconfig', `${path}/project/.npmrc`],
+ cwd: `${path}/project`,
+
+ shorthands,
+ definitions,
+ })
+ await config.load()
+
+ config.argv = []
+ t.equal(config.loaded, true, 'config is loaded')
+
+ t.match(config.data.get('global').loadError, { code: 'ENOENT' })
+ t.strictSame(config.data.get('env').raw, Object.assign(Object.create(null), {
+ foo: 'from-env',
+ globalconfig: '/this/path/does/not/exist',
+ }))
+
+ t.match(config.sources, new Map([
+ ['default values', 'default'],
+ [resolve(path, 'npm/npmrc'), 'builtin'],
+ ['command line options', 'cli'],
+ ['environment', 'env'],
+ ['(same as "user" config, ignored)', 'project'],
+ [resolve(path, 'project/.npmrc'), 'user'],
+ ]))
+
+ t.rejects(() => config.save('yolo'), {
+ message: 'invalid config location param: yolo',
+ })
+ config.validate()
+ t.equal(config.valid, false, 'config should not be valid')
+ logs.length = 0
+ })
+
+ t.test('load configs from files, cli, and env, no builtin or project', async t => {
+ const env = {
+ npm_config_foo: 'from-env',
+ HOME: `${path}/user`,
+ }
+
+ const config = new Config({
+ // no builtin
+ npmPath: path,
+ env,
+ argv,
+ cwd: `${path}/project-no-config`,
+
+ // should prepend DESTDIR to /global
+ DESTDIR: path,
+ PREFIX: '/global',
+ platform: 'posix',
+
+ shorthands,
+ definitions,
+ })
+ await config.load()
+
+ t.match(config.sources, new Map([
+ ['default values', 'default'],
+ ['command line options', 'cli'],
+ ['environment', 'env'],
+ [resolve(path, 'user/.npmrc'), 'user'],
+ [resolve(path, 'global/etc/npmrc'), 'global'],
+ ]))
+ // no builtin or project config
+ t.equal(config.sources.get(resolve(path, 'npm/npmrc')), undefined)
+ t.equal(config.sources.get(resolve(path, 'project/.npmrc')), undefined)
+
+ t.strictSame({
+ version: config.get('version'),
+ audit: config.get('audit'),
+ 'project-config': config.get('project-config'),
+ foo: config.get('foo'),
+ 'user-config-from-builtin': config.get('user-config-from-builtin'),
+ 'default-user-config-in-home': config.get('default-user-config-in-home'),
+ 'global-config': config.get('global-config'),
+ 'builtin-config': config.get('builtin-config'),
+ all: config.get('all'),
+ }, {
+ version: true,
+ audit: false,
+ 'project-config': undefined,
+ foo: 'from-env',
+ 'user-config-from-builtin': undefined,
+ 'default-user-config-in-home': true,
+ 'global-config': true,
+ 'builtin-config': undefined,
+ all: config.get('all'),
+ })
+
+ 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', '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'],
+ ['warn', 'invalid config', 'Must be one of:', 'null, restricted, public'],
+ ['warn', 'invalid config', 'multiple-numbers="what kind of fruit is not a number"',
+ 'set in command line options'],
+ ['warn', 'invalid config', 'Must be one or more', 'numeric value'],
+ ['warn', 'invalid config', 'multiple-numbers="a baNaNa!!"', 'set in command line options'],
+ ['warn', 'invalid config', 'Must be one or more', 'numeric value'],
+ ['warn', 'invalid config', 'prefix=true', 'set in command line options'],
+ ['warn', 'invalid config', 'Must be', 'valid filesystem path'],
+ ['warn', 'config', 'also', 'Please use --include=dev instead.'],
+ ])
+ })
+
+ t.end()
+})
+
+t.test('cafile loads as ca (and some saving tests)', async t => {
+ const cafile = resolve(__dirname, 'fixtures', 'cafile')
+ const dir = t.testdir({
+ '.npmrc': `cafile = ${cafile}
+//registry.npmjs.org/:_authToken = deadbeefcafebadfoobarbaz42069
+`,
+ })
+ const expect = `cafile=${cafile}
+//registry.npmjs.org/:_authToken=deadbeefcafebadfoobarbaz42069
+`
+
+ const config = new Config({
+ shorthands,
+ definitions,
+ npmPath: __dirname,
+ env: { HOME: dir, PREFIX: dir },
+ flatten,
+ })
+ await config.load()
+ t.equal(config.get('ca'), null, 'does not overwrite config.get')
+ const { flat } = config
+ t.equal(config.flat, flat, 'getter returns same value again')
+ const ca = flat.ca
+ t.equal(ca.join('\n').replace(/\r\n/g, '\n').trim(), readFileSync(cafile, 'utf8')
+ .replace(/\r\n/g, '\n').trim())
+ await config.save('user')
+ const res = readFileSync(`${dir}/.npmrc`, 'utf8').replace(/\r\n/g, '\n')
+ t.equal(res, expect, 'did not write back ca, only cafile')
+ // while we're here, test that saving an empty config file deletes it
+ config.delete('cafile', 'user')
+ config.clearCredentialsByURI(config.get('registry'))
+ await config.save('user')
+ t.throws(() => readFileSync(`${dir}/.npmrc`, 'utf8'), { code: 'ENOENT' })
+ // do it again to verify we ignore the unlink error
+ await config.save('user')
+ t.throws(() => readFileSync(`${dir}/.npmrc`, 'utf8'), { code: 'ENOENT' })
+ t.equal(config.valid, true)
+})
+
+t.test('cafile ignored if ca set', async t => {
+ const cafile = resolve(__dirname, 'fixtures', 'cafile')
+ const dir = t.testdir({
+ '.npmrc': `cafile = ${cafile}`,
+ })
+ const ca = `
+-----BEGIN CERTIFICATE-----
+fakey mc fakerson
+-----END CERTIFICATE-----
+`
+ const config = new Config({
+ shorthands,
+ definitions,
+ npmPath: __dirname,
+ env: {
+ HOME: dir,
+ npm_config_ca: ca,
+ },
+ })
+ await config.load()
+ t.strictSame(config.get('ca'), [ca.trim()])
+ await config.save('user')
+ const res = readFileSync(`${dir}/.npmrc`, 'utf8')
+ t.equal(res.trim(), `cafile=${cafile}`)
+})
+
+t.test('ignore cafile if it does not load', async t => {
+ const cafile = resolve(__dirname, 'fixtures', 'cafile-does-not-exist')
+ const dir = t.testdir({
+ '.npmrc': `cafile = ${cafile}`,
+ })
+ const config = new Config({
+ shorthands,
+ definitions,
+ npmPath: __dirname,
+ env: { HOME: dir },
+ })
+ await config.load()
+ t.equal(config.get('ca'), null)
+ await config.save('user')
+ const res = readFileSync(`${dir}/.npmrc`, 'utf8')
+ t.equal(res.trim(), `cafile=${cafile}`)
+})
+
+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}`,
+ })
+ const config = new Config({
+ shorthands,
+ definitions,
+ npmPath: __dirname,
+ env: { HOME: dir },
+ flatten,
+ })
+ await config.load()
+ t.throws(() => config.flat.ca, { code: 'EWEIRD' })
+})
+
+t.test('credentials management', async t => {
+ const fixtures = {
+ nerfed_authToken: { '.npmrc': '//registry.example/:_authToken = 0bad1de4' },
+ nerfed_userpass: {
+ '.npmrc': `//registry.example/:username = hello
+//registry.example/:_password = ${Buffer.from('world').toString('base64')}
+//registry.example/:email = i@izs.me
+//registry.example/:always-auth = "false"`,
+ },
+ nerfed_auth: { // note: does not load, because we don't do _auth per reg
+ '.npmrc': `//registry.example/:_auth = ${Buffer.from('hello:world').toString('base64')}`,
+ },
+ nerfed_mtls: { '.npmrc': `//registry.example/:certfile = /path/to/cert
+//registry.example/:keyfile = /path/to/key`,
+ },
+ nerfed_mtlsAuthToken: { '.npmrc': `//registry.example/:_authToken = 0bad1de4
+//registry.example/:certfile = /path/to/cert
+//registry.example/:keyfile = /path/to/key`,
+ },
+ nerfed_mtlsUserPass: { '.npmrc': `//registry.example/:username = hello
+//registry.example/:_password = ${Buffer.from('world').toString('base64')}
+//registry.example/:email = i@izs.me
+//registry.example/:always-auth = "false"
+//registry.example/:certfile = /path/to/cert
+//registry.example/:keyfile = /path/to/key`,
+ },
+ def_userpass: {
+ '.npmrc': `username = hello
+_password = ${Buffer.from('world').toString('base64')}
+email = i@izs.me
+//registry.example/:always-auth = true
+`,
+ },
+ def_userNoPass: {
+ '.npmrc': `username = hello
+email = i@izs.me
+//registry.example/:always-auth = true
+`,
+ },
+ def_passNoUser: {
+ '.npmrc': `_password = ${Buffer.from('world').toString('base64')}
+email = i@izs.me
+//registry.example/:always-auth = true
+`,
+ },
+ def_auth: {
+ '.npmrc': `_auth = ${Buffer.from('hello:world').toString('base64')}
+always-auth = true`,
+ },
+ none_authToken: { '.npmrc': '_authToken = 0bad1de4' },
+ none_lcAuthToken: { '.npmrc': '_authtoken = 0bad1de4' },
+ none_emptyConfig: { '.npmrc': '' },
+ none_noConfig: {},
+ }
+ const path = t.testdir(fixtures)
+
+ const defReg = 'https://registry.example/'
+ const otherReg = 'https://other.registry/'
+ for (const testCase of Object.keys(fixtures)) {
+ t.test(testCase, async t => {
+ const c = new Config({
+ npmPath: path,
+ shorthands,
+ definitions,
+ env: { HOME: resolve(path, testCase) },
+ argv: ['node', 'file', '--registry', defReg],
+ })
+ await c.load()
+
+ // only have to do this the first time, it's redundant otherwise
+ if (testCase === 'none_noConfig') {
+ t.throws(() => c.setCredentialsByURI('http://x.com', {
+ username: 'foo',
+ email: 'bar@baz.com',
+ }), { message: 'must include password' })
+ t.throws(() => c.setCredentialsByURI('http://x.com', {
+ password: 'foo',
+ email: 'bar@baz.com',
+ }), { message: 'must include username' })
+ c.setCredentialsByURI('http://x.com', {
+ username: 'foo',
+ password: 'bar',
+ email: 'asdf@quux.com',
+ })
+ }
+
+ // the def_ and none_ prefixed cases have unscoped auth values and should throw
+ if (testCase.startsWith('def_') ||
+ testCase === 'none_authToken' ||
+ testCase === 'none_lcAuthToken') {
+ try {
+ c.validate()
+ // validate should throw, fail the test here if it doesn't
+ t.fail('validate should have thrown')
+ } catch (err) {
+ if (err.code !== 'ERR_INVALID_AUTH') {
+ throw err
+ }
+
+ // we got our expected invalid auth error, so now repair it
+ c.repair(err.problems)
+ t.ok(c.valid, 'config is valid')
+ }
+ } else {
+ // validate won't throw for these ones, so let's prove it and repair are no-ops
+ c.validate()
+ c.repair()
+ }
+
+ const d = c.getCredentialsByURI(defReg)
+ const o = c.getCredentialsByURI(otherReg)
+
+ t.matchSnapshot(d, 'default registry')
+ t.matchSnapshot(o, 'other registry')
+
+ c.clearCredentialsByURI(defReg)
+ const defAfterDelete = c.getCredentialsByURI(defReg)
+ {
+ const expectKeys = []
+ if (defAfterDelete.email) {
+ expectKeys.push('email')
+ }
+ t.strictSame(Object.keys(defAfterDelete), expectKeys)
+ }
+
+ c.clearCredentialsByURI(otherReg)
+ const otherAfterDelete = c.getCredentialsByURI(otherReg)
+ {
+ const expectKeys = []
+ if (otherAfterDelete.email) {
+ expectKeys.push('email')
+ }
+ t.strictSame(Object.keys(otherAfterDelete), expectKeys)
+ }
+
+ // need both or none of user/pass
+ if (!d.token && (!d.username || !d.password) && (!d.certfile || !d.keyfile)) {
+ t.throws(() => c.setCredentialsByURI(defReg, d))
+ } else {
+ c.setCredentialsByURI(defReg, d)
+ t.matchSnapshot(c.getCredentialsByURI(defReg), 'default registry after set')
+ }
+
+ if (!o.token && (!o.username || !o.password) && (!o.certfile || !o.keyfile)) {
+ t.throws(() => c.setCredentialsByURI(otherReg, o), {}, { otherReg, o })
+ } else {
+ c.setCredentialsByURI(otherReg, o)
+ t.matchSnapshot(c.getCredentialsByURI(otherReg), 'other registry after set')
+ }
+ })
+ }
+ t.end()
+})
+
+t.test('finding the global prefix', t => {
+ const npmPath = __dirname
+ t.test('load from PREFIX env', t => {
+ const c = new Config({
+ env: {
+ PREFIX: '/prefix/env',
+ },
+ shorthands,
+ definitions,
+ npmPath,
+ })
+ c.loadGlobalPrefix()
+ t.throws(() => c.loadGlobalPrefix(), {
+ message: 'cannot load default global prefix more than once',
+ })
+ t.equal(c.globalPrefix, '/prefix/env')
+ t.end()
+ })
+ t.test('load from execPath, win32', t => {
+ const c = new Config({
+ platform: 'win32',
+ execPath: '/path/to/nodejs/node.exe',
+ shorthands,
+ definitions,
+ npmPath,
+ })
+ c.loadGlobalPrefix()
+ t.equal(c.globalPrefix, dirname('/path/to/nodejs/node.exe'))
+ t.end()
+ })
+ t.test('load from execPath, posix', t => {
+ const c = new Config({
+ platform: 'posix',
+ execPath: '/path/to/nodejs/bin/node',
+ shorthands,
+ definitions,
+ npmPath,
+ })
+ c.loadGlobalPrefix()
+ t.equal(c.globalPrefix, dirname(dirname('/path/to/nodejs/bin/node')))
+ t.end()
+ })
+ t.test('load from execPath with destdir, posix', t => {
+ const c = new Config({
+ platform: 'posix',
+ execPath: '/path/to/nodejs/bin/node',
+ env: { DESTDIR: '/some/dest/dir' },
+ shorthands,
+ definitions,
+ npmPath,
+ })
+ c.loadGlobalPrefix()
+ t.equal(c.globalPrefix, join('/some/dest/dir', dirname(dirname('/path/to/nodejs/bin/node'))))
+ t.end()
+ })
+ t.end()
+})
+
+t.test('finding the local prefix', t => {
+ const path = t.testdir({
+ hasNM: {
+ node_modules: {},
+ x: { y: { z: {} } },
+ },
+ hasPJ: {
+ 'package.json': '{}',
+ x: { y: { z: {} } },
+ },
+ })
+ t.test('explicit cli prefix', async t => {
+ const c = new Config({
+ argv: [process.execPath, __filename, '-C', path],
+ shorthands,
+ definitions,
+ npmPath: path,
+ })
+ await c.load()
+ t.equal(c.localPrefix, resolve(path))
+ })
+ t.test('has node_modules', async t => {
+ const c = new Config({
+ cwd: `${path}/hasNM/x/y/z`,
+ shorthands,
+ definitions,
+ npmPath: path,
+ })
+ await c.load()
+ t.equal(c.localPrefix, resolve(path, 'hasNM'))
+ })
+ t.test('has package.json', async t => {
+ const c = new Config({
+ cwd: `${path}/hasPJ/x/y/z`,
+ shorthands,
+ definitions,
+ npmPath: path,
+ })
+ await c.load()
+ t.equal(c.localPrefix, resolve(path, 'hasPJ'))
+ })
+ t.test('nada, just use cwd', async t => {
+ const c = new Config({
+ cwd: '/this/path/does/not/exist/x/y/z',
+ shorthands,
+ definitions,
+ npmPath: path,
+ })
+ await c.load()
+ t.equal(c.localPrefix, '/this/path/does/not/exist/x/y/z')
+ })
+ t.end()
+})
+
+t.test('setting basic auth creds and email', async t => {
+ const registry = 'https://registry.npmjs.org/'
+ const path = t.testdir()
+ const _auth = Buffer.from('admin:admin').toString('base64')
+ const opts = {
+ shorthands: {},
+ argv: ['node', __filename, `--userconfig=${path}/.npmrc`],
+ definitions: {
+ registry: { default: registry },
+ },
+ npmPath: process.cwd(),
+ }
+ const c = new Config(opts)
+ await c.load()
+ c.set('email', 'name@example.com', 'user')
+ t.equal(c.get('email', 'user'), 'name@example.com', 'email was set')
+ await c.save('user')
+ t.equal(c.get('email', 'user'), 'name@example.com', 'email still top level')
+ t.strictSame(c.getCredentialsByURI(registry), { email: 'name@example.com' })
+ const d = new Config(opts)
+ await d.load()
+ t.strictSame(d.getCredentialsByURI(registry), { email: 'name@example.com' })
+ d.set('_auth', _auth, 'user')
+ t.equal(d.get('_auth', 'user'), _auth, '_auth was set')
+ d.repair()
+ await d.save('user')
+ const e = new Config(opts)
+ await e.load()
+ t.equal(e.get('_auth', 'user'), undefined, 'un-nerfed _auth deleted')
+ t.strictSame(e.getCredentialsByURI(registry), {
+ email: 'name@example.com',
+ username: 'admin',
+ password: 'admin',
+ auth: _auth,
+ }, 'credentials saved and nerfed')
+})
+
+t.test('setting username/password/email individually', async t => {
+ const registry = 'https://registry.npmjs.org/'
+ const path = t.testdir()
+ const opts = {
+ shorthands: {},
+ argv: ['node', __filename, `--userconfig=${path}/.npmrc`],
+ definitions: {
+ registry: { default: registry },
+ },
+ npmPath: process.cwd(),
+ }
+ const c = new Config(opts)
+ await c.load()
+ c.set('email', 'name@example.com', 'user')
+ t.equal(c.get('email'), 'name@example.com')
+ c.set('username', 'admin', 'user')
+ t.equal(c.get('username'), 'admin')
+ c.set('_password', Buffer.from('admin').toString('base64'), 'user')
+ t.equal(c.get('_password'), Buffer.from('admin').toString('base64'))
+ t.equal(c.get('_auth'), undefined)
+ c.repair()
+ await c.save('user')
+
+ const d = new Config(opts)
+ await d.load()
+ t.equal(d.get('email'), 'name@example.com')
+ t.equal(d.get('username'), undefined)
+ t.equal(d.get('_password'), undefined)
+ t.equal(d.get('_auth'), undefined)
+ t.strictSame(d.getCredentialsByURI(registry), {
+ email: 'name@example.com',
+ username: 'admin',
+ password: 'admin',
+ auth: Buffer.from('admin:admin').toString('base64'),
+ })
+})
+
+t.test('nerfdart auths set at the top level into the registry', async t => {
+ const registry = 'https://registry.npmjs.org/'
+ const _auth = Buffer.from('admin:admin').toString('base64')
+ const username = 'admin'
+ const _password = Buffer.from('admin').toString('base64')
+ const email = 'i@izs.me'
+ const _authToken = 'deadbeefblahblah'
+
+ // name: [ini, expect, wontThrow]
+ const cases = {
+ '_auth only, no email': [`_auth=${_auth}`, {
+ '//registry.npmjs.org/:_auth': _auth,
+ }],
+ '_auth with email': [`_auth=${_auth}\nemail=${email}`, {
+ '//registry.npmjs.org/:_auth': _auth,
+ email,
+ }],
+ '_authToken alone': [`_authToken=${_authToken}`, {
+ '//registry.npmjs.org/:_authToken': _authToken,
+ }],
+ '_authToken and email': [`_authToken=${_authToken}\nemail=${email}`, {
+ '//registry.npmjs.org/:_authToken': _authToken,
+ email,
+ }],
+ 'username and _password': [`username=${username}\n_password=${_password}`, {
+ '//registry.npmjs.org/:username': username,
+ '//registry.npmjs.org/:_password': _password,
+ }],
+ 'username, password, email': [`username=${username}\n_password=${_password}\nemail=${email}`, {
+ '//registry.npmjs.org/:username': username,
+ '//registry.npmjs.org/:_password': _password,
+ email,
+ }],
+ // handled invalid/legacy cases
+ 'username, no _password': [`username=${username}`, {}],
+ '_password, no username': [`_password=${_password}`, {}],
+ '_authtoken instead of _authToken': [`_authtoken=${_authToken}`, {}],
+ '-authtoken instead of _authToken': [`-authtoken=${_authToken}`, {}],
+ // de-nerfdart the email, if present in that way
+ 'nerf-darted email': [`//registry.npmjs.org/:email=${email}`, {
+ email,
+ }, true],
+ }
+
+ const logs = []
+ const logHandler = (...args) => logs.push(args)
+ process.on('log', logHandler)
+ t.teardown(() => {
+ process.removeListener('log', logHandler)
+ })
+ const cwd = process.cwd()
+ for (const [name, [ini, expect, wontThrow]] of Object.entries(cases)) {
+ t.test(name, async t => {
+ t.teardown(() => {
+ process.chdir(cwd)
+ logs.length = 0
+ })
+ const path = t.testdir({
+ '.npmrc': ini,
+ 'package.json': JSON.stringify({}),
+ })
+ process.chdir(path)
+ const argv = [
+ 'node',
+ __filename,
+ `--prefix=${path}`,
+ `--userconfig=${path}/.npmrc`,
+ `--globalconfig=${path}/etc/npmrc`,
+ ]
+ const opts = {
+ shorthands: {},
+ argv,
+ env: {},
+ definitions: {
+ registry: { default: registry },
+ },
+ npmPath: process.cwd(),
+ }
+
+ const c = new Config(opts)
+ await c.load()
+
+ if (!wontThrow) {
+ t.throws(() => c.validate(), { code: 'ERR_INVALID_AUTH' })
+ }
+
+ // now we go ahead and do the repair, and save
+ c.repair()
+ await c.save('user')
+ t.same(c.list[3], expect)
+ })
+ }
+})
+
+t.test('workspaces', async (t) => {
+ const path = resolve(t.testdir({
+ 'package.json': JSON.stringify({
+ name: 'root',
+ version: '1.0.0',
+ workspaces: ['./workspaces/*'],
+ }),
+ workspaces: {
+ one: {
+ 'package.json': JSON.stringify({
+ name: 'one',
+ version: '1.0.0',
+ }),
+ },
+ two: {
+ 'package.json': JSON.stringify({
+ name: 'two',
+ version: '1.0.0',
+ }),
+ },
+ three: {
+ 'package.json': JSON.stringify({
+ name: 'three',
+ version: '1.0.0',
+ }),
+ '.npmrc': 'package-lock=false',
+ },
+ },
+ }))
+
+ const logs = []
+ const logHandler = (...args) => logs.push(args)
+ process.on('log', logHandler)
+ t.teardown(() => process.off('log', logHandler))
+ t.afterEach(() => logs.length = 0)
+
+ t.test('finds own parent', async (t) => {
+ const cwd = process.cwd()
+ t.teardown(() => process.chdir(cwd))
+ process.chdir(`${path}/workspaces/one`)
+
+ const config = new Config({
+ npmPath: cwd,
+ env: {},
+ argv: [process.execPath, __filename],
+ cwd: `${path}/workspaces/one`,
+ shorthands,
+ definitions,
+ })
+
+ await config.load()
+ t.equal(config.localPrefix, path, 'localPrefix is the root')
+ t.same(config.get('workspace'), [join(path, 'workspaces', 'one')], 'set the workspace')
+ t.equal(logs.length, 1, 'got one log message')
+ t.match(logs[0], ['info', /^found workspace root at/], 'logged info about workspace root')
+ })
+
+ t.test('finds other workspace parent', async (t) => {
+ const cwd = process.cwd()
+ t.teardown(() => process.chdir(cwd))
+ process.chdir(`${path}/workspaces/one`)
+
+ const config = new Config({
+ npmPath: process.cwd(),
+ env: {},
+ argv: [process.execPath, __filename, '--workspace', '../two'],
+ cwd: `${path}/workspaces/one`,
+ shorthands,
+ definitions,
+ })
+
+ await config.load()
+ t.equal(config.localPrefix, path, 'localPrefix is the root')
+ t.same(config.get('workspace'), ['../two'], 'kept the specified workspace')
+ t.equal(logs.length, 1, 'got one log message')
+ t.match(logs[0], ['info', /^found workspace root at/], 'logged info about workspace root')
+ })
+
+ t.test('warns when workspace has .npmrc', async (t) => {
+ const cwd = process.cwd()
+ t.teardown(() => process.chdir(cwd))
+ process.chdir(`${path}/workspaces/three`)
+
+ const config = new Config({
+ npmPath: process.cwd(),
+ env: {},
+ argv: [process.execPath, __filename],
+ cwd: `${path}/workspaces/three`,
+ shorthands,
+ definitions,
+ })
+
+ await config.load()
+ t.equal(config.localPrefix, path, 'localPrefix is the root')
+ t.same(config.get('workspace'), [join(path, 'workspaces', 'three')], 'kept the workspace')
+ t.equal(logs.length, 2, 'got two log messages')
+ t.match(logs[0], ['warn', /^ignoring workspace config/], 'warned about ignored config')
+ t.match(logs[1], ['info', /^found workspace root at/], 'logged info about workspace root')
+ })
+
+ t.test('prefix skips auto detect', async (t) => {
+ const cwd = process.cwd()
+ t.teardown(() => process.chdir(cwd))
+ process.chdir(`${path}/workspaces/one`)
+
+ const config = new Config({
+ npmPath: process.cwd(),
+ env: {},
+ argv: [process.execPath, __filename, '--prefix', './'],
+ cwd: `${path}/workspaces/one`,
+ shorthands,
+ definitions,
+ })
+
+ await config.load()
+ t.equal(config.localPrefix, join(path, 'workspaces', 'one'), 'localPrefix is the root')
+ t.same(config.get('workspace'), [], 'did not set workspace')
+ t.equal(logs.length, 0, 'got no log messages')
+ })
+
+ t.test('no-workspaces skips auto detect', async (t) => {
+ const cwd = process.cwd()
+ t.teardown(() => process.chdir(cwd))
+ process.chdir(`${path}/workspaces/one`)
+
+ const config = new Config({
+ npmPath: process.cwd(),
+ env: {},
+ argv: [process.execPath, __filename, '--no-workspaces'],
+ cwd: `${path}/workspaces/one`,
+ shorthands,
+ definitions,
+ })
+
+ await config.load()
+ t.equal(config.localPrefix, join(path, 'workspaces', 'one'), 'localPrefix is the root')
+ t.same(config.get('workspace'), [], 'did not set workspace')
+ t.equal(logs.length, 0, 'got no log messages')
+ })
+
+ t.test('global skips auto detect', async (t) => {
+ const cwd = process.cwd()
+ t.teardown(() => process.chdir(cwd))
+ process.chdir(`${path}/workspaces/one`)
+
+ const config = new Config({
+ npmPath: process.cwd(),
+ env: {},
+ argv: [process.execPath, __filename, '--global'],
+ cwd: `${path}/workspaces/one`,
+ shorthands,
+ definitions,
+ })
+
+ await config.load()
+ t.equal(config.localPrefix, join(path, 'workspaces', 'one'), 'localPrefix is the root')
+ t.same(config.get('workspace'), [], 'did not set workspace')
+ t.equal(logs.length, 0, 'got no log messages')
+ })
+
+ t.test('location=global skips auto detect', async (t) => {
+ const cwd = process.cwd()
+ t.teardown(() => process.chdir(cwd))
+ process.chdir(`${path}/workspaces/one`)
+
+ const config = new Config({
+ npmPath: process.cwd(),
+ env: {},
+ argv: [process.execPath, __filename, '--location=global'],
+ cwd: `${path}/workspaces/one`,
+ shorthands,
+ definitions,
+ })
+
+ await config.load()
+ t.equal(config.localPrefix, join(path, 'workspaces', 'one'), 'localPrefix is the root')
+ t.same(config.get('workspace'), [], 'did not set workspace')
+ t.equal(logs.length, 0, 'got no log messages')
+ })
+
+ t.test('does not error for invalid package.json', async (t) => {
+ const invalidPkg = join(path, 'workspaces', 'package.json')
+ const cwd = process.cwd()
+ t.teardown(() => {
+ fs.unlinkSync(invalidPkg)
+ process.chdir(cwd)
+ })
+ process.chdir(`${path}/workspaces/one`)
+
+ // write some garbage to the file so read-package-json-fast will throw
+ fs.writeFileSync(invalidPkg, 'not-json')
+ const config = new Config({
+ npmPath: cwd,
+ env: {},
+ argv: [process.execPath, __filename],
+ cwd: `${path}/workspaces/one`,
+ shorthands,
+ definitions,
+ })
+
+ await config.load()
+ t.equal(config.localPrefix, path, 'localPrefix is the root')
+ t.same(config.get('workspace'), [join(path, 'workspaces', 'one')], 'set the workspace')
+ t.equal(logs.length, 1, 'got one log message')
+ t.match(logs[0], ['info', /^found workspace root at/], 'logged info about workspace root')
+ })
+})
diff --git a/workspaces/config/test/nerf-dart.js b/workspaces/config/test/nerf-dart.js
new file mode 100644
index 000000000..8c175a51f
--- /dev/null
+++ b/workspaces/config/test/nerf-dart.js
@@ -0,0 +1,44 @@
+const t = require('tap')
+const nerfDart = require('../lib/nerf-dart.js')
+
+const cases = [
+ ['//registry.npmjs.org/', [
+ 'https://registry.npmjs.org',
+ 'https://registry.npmjs.org/package-name',
+ 'https://registry.npmjs.org/package-name?write=true',
+ 'https://registry.npmjs.org/@scope%2fpackage-name',
+ 'https://registry.npmjs.org/@scope%2fpackage-name?write=true',
+ 'https://username:password@registry.npmjs.org/package-name?write=true',
+ 'https://registry.npmjs.org/#hash',
+ 'https://registry.npmjs.org/?write=true#hash',
+ 'https://registry.npmjs.org/package-name?write=true#hash',
+ 'https://registry.npmjs.org/package-name#hash',
+ 'https://registry.npmjs.org/@scope%2fpackage-name?write=true#hash',
+ 'https://registry.npmjs.org/@scope%2fpackage-name#hash',
+ ]],
+ ['//my-couch:5984/registry/_design/app/rewrite/', [
+ 'https://my-couch:5984/registry/_design/app/rewrite/',
+ 'https://my-couch:5984/registry/_design/app/rewrite/package-name',
+ 'https://my-couch:5984/registry/_design/app/rewrite/package-name?write=true',
+ 'https://my-couch:5984/registry/_design/app/rewrite/@scope%2fpackage-name',
+ 'https://my-couch:5984/registry/_design/app/rewrite/@scope%2fpackage-name?write=true',
+ 'https://username:password@my-couch:5984/registry/_design/app/rewrite/package-name?write=true',
+ 'https://my-couch:5984/registry/_design/app/rewrite/#hash',
+ 'https://my-couch:5984/registry/_design/app/rewrite/?write=true#hash',
+ 'https://my-couch:5984/registry/_design/app/rewrite/package-name?write=true#hash',
+ 'https://my-couch:5984/registry/_design/app/rewrite/package-name#hash',
+ 'https://my-couch:5984/registry/_design/app/rewrite/@scope%2fpackage-name?write=true#hash',
+ 'https://my-couch:5984/registry/_design/app/rewrite/@scope%2fpackage-name#hash',
+ ]],
+]
+
+for (const [dart, tests] of cases) {
+ t.test(dart, t => {
+ t.plan(tests.length)
+ for (const url of tests) {
+ t.equal(nerfDart(url), dart, url)
+ }
+ })
+}
+
+t.throws(() => nerfDart('not a valid url'))
diff --git a/workspaces/config/test/parse-field.js b/workspaces/config/test/parse-field.js
new file mode 100644
index 000000000..1c4193b73
--- /dev/null
+++ b/workspaces/config/test/parse-field.js
@@ -0,0 +1,36 @@
+const parseField = require('../lib/parse-field.js')
+const t = require('tap')
+const { resolve } = require('path')
+
+t.strictSame(parseField({ a: 1 }, 'a'), { a: 1 })
+
+const opts = {
+ platform: 'posix',
+ types: require('./fixtures/types.js'),
+ home: '/home/user',
+ env: { foo: 'bar' },
+}
+
+t.equal(parseField('', 'global', opts), true, 'boolean flag')
+t.equal(parseField('true', 'global', opts), true, 'boolean flag "true"')
+t.equal(parseField('false', 'global', opts), false, 'boolean flag "false"')
+t.equal(parseField('null', 'access', opts), null, '"null" is null')
+t.equal(parseField('undefined', 'access', opts), undefined, '"undefined" is undefined')
+t.equal(parseField('blerg', 'access', opts), 'blerg', '"blerg" just is a string')
+t.equal(parseField('blerg', 'message', opts), 'blerg', '"blerg" just is a string')
+t.strictSame(parseField([], 'global', opts), [], 'array passed to non-list type')
+t.strictSame(parseField([' dev '], 'omit', opts), ['dev'], 'array to list type')
+t.strictSame(parseField('dev\n\noptional', 'omit', opts), ['dev', 'optional'],
+ 'double-LF delimited list, like we support in env vals')
+t.equal(parseField('~/foo', 'userconfig', opts), resolve('/home/user/foo'),
+ 'path supports ~/')
+t.equal(parseField('~\\foo', 'userconfig', { ...opts, platform: 'win32' }),
+ resolve('/home/user/foo'), 'path supports ~\\ on windows')
+t.equal(parseField('foo', 'userconfig', opts), resolve('foo'),
+ 'path gets resolved')
+
+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')
diff --git a/workspaces/config/test/set-envs.js b/workspaces/config/test/set-envs.js
new file mode 100644
index 000000000..c663c2236
--- /dev/null
+++ b/workspaces/config/test/set-envs.js
@@ -0,0 +1,212 @@
+const setEnvs = require('../lib/set-envs.js')
+
+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
+
+t.test('set envs that are not defaults and not already in env', t => {
+ const envConf = Object.create(defaults)
+ const cliConf = Object.create(envConf)
+ const extras = {
+ NODE,
+ INIT_CWD: cwd,
+ EDITOR: 'vim',
+ HOME: undefined,
+ npm_execpath: require.main.filename,
+ npm_node_execpath: execPath,
+ npm_config_global_prefix: globalPrefix,
+ npm_config_local_prefix: localPrefix,
+ }
+
+ const env = {}
+ const config = {
+ list: [cliConf, envConf],
+ env,
+ defaults,
+ definitions,
+ execPath,
+ globalPrefix,
+ localPrefix,
+ }
+
+ setEnvs(config)
+ t.strictSame(env, { ...extras }, 'no new environment vars to create')
+ envConf.call = 'me, maybe'
+ setEnvs(config)
+ t.strictSame(env, { ...extras }, 'no new environment vars to create, already in env')
+ delete envConf.call
+ cliConf.call = 'me, maybe'
+ setEnvs(config)
+ t.strictSame(env, {
+ ...extras,
+ npm_config_call: 'me, maybe',
+ }, 'set in env, because changed from default in cli')
+ envConf.call = 'me, maybe'
+ cliConf.call = ''
+ cliConf['node-options'] = 'some options for node'
+ setEnvs(config)
+ t.strictSame(env, {
+ ...extras,
+ npm_config_call: '',
+ npm_config_node_options: 'some options for node',
+ NODE_OPTIONS: 'some options for node',
+ }, 'set in env, because changed from default in env, back to default in cli')
+ t.end()
+})
+
+t.test('set envs that are not defaults and not already in env, array style', t => {
+ const envConf = Object.create(defaults)
+ const cliConf = Object.create(envConf)
+ const extras = {
+ NODE,
+ INIT_CWD: cwd,
+ EDITOR: 'vim',
+ HOME: undefined,
+ npm_execpath: require.main.filename,
+ npm_node_execpath: execPath,
+ npm_config_global_prefix: globalPrefix,
+ npm_config_local_prefix: localPrefix,
+ }
+ // make sure it's not sticky
+ const env = { INIT_CWD: '/some/other/path' }
+ const config = {
+ list: [cliConf, envConf],
+ env,
+ defaults,
+ definitions,
+ execPath,
+ globalPrefix,
+ localPrefix,
+ }
+ setEnvs(config)
+ t.strictSame(env, { ...extras }, 'no new environment vars to create')
+
+ envConf.omit = ['dev']
+ setEnvs(config)
+ t.strictSame(env, { ...extras }, 'no new environment vars to create, already in env')
+ delete envConf.omit
+ cliConf.omit = ['dev', 'optional']
+ setEnvs(config)
+ t.strictSame(env, {
+ ...extras,
+ npm_config_omit: 'dev\n\noptional',
+ }, 'set in env, because changed from default in cli')
+ envConf.omit = ['optional', 'peer']
+ cliConf.omit = []
+ setEnvs(config)
+ t.strictSame(env, {
+ ...extras,
+ npm_config_omit: '',
+ }, 'set in env, because changed from default in env, back to default in cli')
+ t.end()
+})
+
+t.test('set envs that are not defaults and not already in env, boolean edition', t => {
+ const envConf = Object.create(defaults)
+ const cliConf = Object.create(envConf)
+ const extras = {
+ NODE,
+ INIT_CWD: cwd,
+ EDITOR: 'vim',
+ HOME: undefined,
+ npm_execpath: require.main.filename,
+ npm_node_execpath: execPath,
+ npm_config_global_prefix: globalPrefix,
+ npm_config_local_prefix: localPrefix,
+ }
+
+ const env = {}
+ const config = {
+ list: [cliConf, envConf],
+ env,
+ defaults,
+ definitions,
+ execPath,
+ globalPrefix,
+ localPrefix,
+ }
+ setEnvs(config)
+ t.strictSame(env, { ...extras }, 'no new environment vars to create')
+ envConf.audit = false
+ setEnvs(config)
+ t.strictSame(env, { ...extras }, 'no new environment vars to create, already in env')
+ delete envConf.audit
+ cliConf.audit = false
+ cliConf.ignoreObjects = {
+ some: { object: 12345 },
+ }
+ setEnvs(config)
+ t.strictSame(env, {
+ ...extras,
+ npm_config_audit: '',
+ }, 'set in env, because changed from default in cli')
+ envConf.audit = false
+ cliConf.audit = true
+ setEnvs(config)
+ t.strictSame(env, {
+ ...extras,
+ npm_config_audit: 'true',
+ }, 'set in env, because changed from default in env, back to default in cli')
+ t.end()
+})
+
+t.test('dont set npm_execpath if require.main.filename is not set', t => {
+ const { filename } = require.main
+ t.teardown(() => require.main.filename = filename)
+ require.main.filename = null
+ // also, don't set editor
+ const d = { ...defaults, editor: null }
+ const envConf = Object.create(d)
+ const cliConf = Object.create(envConf)
+ const env = { DESTDIR: '/some/dest' }
+ const config = {
+ list: [cliConf, envConf],
+ env,
+ defaults: d,
+ definitions,
+ execPath,
+ globalPrefix,
+ localPrefix,
+ }
+ setEnvs(config)
+ t.equal(env.npm_execpath, undefined, 'did not set npm_execpath')
+ t.end()
+})
+
+t.test('dont set configs marked as envExport:false', t => {
+ const envConf = Object.create(defaults)
+ const cliConf = Object.create(envConf)
+ const extras = {
+ NODE,
+ INIT_CWD: cwd,
+ EDITOR: 'vim',
+ HOME: undefined,
+ npm_execpath: require.main.filename,
+ npm_node_execpath: execPath,
+ npm_config_global_prefix: globalPrefix,
+ npm_config_local_prefix: localPrefix,
+ }
+
+ const env = {}
+ const config = {
+ list: [cliConf, envConf],
+ env,
+ defaults,
+ definitions,
+ execPath,
+ globalPrefix,
+ localPrefix,
+ }
+ setEnvs(config)
+ t.strictSame(env, { ...extras }, 'no new environment vars to create')
+ cliConf.methane = 'CO2'
+ setEnvs(config)
+ t.strictSame(env, { ...extras }, 'not exported, because envExport=false')
+ t.end()
+})
diff --git a/workspaces/config/test/type-defs.js b/workspaces/config/test/type-defs.js
new file mode 100644
index 000000000..2ce0ac91d
--- /dev/null
+++ b/workspaces/config/test/type-defs.js
@@ -0,0 +1,22 @@
+const typeDefs = require('../lib/type-defs.js')
+const t = require('tap')
+const {
+ semver: {
+ validate: validateSemver,
+ },
+ path: {
+ validate: validatePath,
+ },
+} = typeDefs
+const { resolve } = require('path')
+
+const d = { semver: 'foobar', somePath: true }
+t.equal(validateSemver(d, 'semver', 'foobar'), false)
+t.equal(validateSemver(d, 'semver', 'v1.2.3'), undefined)
+t.equal(d.semver, '1.2.3')
+t.equal(validatePath(d, 'somePath', true), false)
+t.equal(validatePath(d, 'somePath', false), false)
+t.equal(validatePath(d, 'somePath', null), false)
+t.equal(validatePath(d, 'somePath', 1234), false)
+t.equal(validatePath(d, 'somePath', 'false'), true)
+t.equal(d.somePath, resolve('false'))
diff --git a/workspaces/config/test/type-description.js b/workspaces/config/test/type-description.js
new file mode 100644
index 000000000..d487c1189
--- /dev/null
+++ b/workspaces/config/test/type-description.js
@@ -0,0 +1,14 @@
+const t = require('tap')
+const typeDescription = require('../lib/type-description.js')
+const types = require('./fixtures/types.js')
+const descriptions = {}
+for (const [name, type] of Object.entries(types)) {
+ const desc = typeDescription(type)
+ if (name === 'local-address') {
+ t.strictSame(desc.sort(), type.filter(t => t !== undefined).sort())
+ } else {
+ descriptions[name] = desc
+ }
+}
+
+t.matchSnapshot(descriptions)