From 111e2848cf744c63617349db42430d07ef9a3f7d Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Thu, 18 Mar 2021 13:03:14 -0400 Subject: [PATCH 01/42] npm-package-arg@8.1.2 --- .../node_modules/hosted-git-info/CHANGELOG.md | 185 ++++++++++++++ .../node_modules/hosted-git-info/LICENSE | 13 + .../node_modules/hosted-git-info/README.md | 133 ++++++++++ .../hosted-git-info/git-host-info.js | 154 ++++++++++++ .../node_modules/hosted-git-info/git-host.js | 110 ++++++++ .../node_modules/hosted-git-info/index.js | 237 ++++++++++++++++++ .../node_modules/hosted-git-info/package.json | 52 ++++ node_modules/npm-package-arg/package.json | 8 +- package-lock.json | 44 +++- package.json | 2 +- 10 files changed, 922 insertions(+), 16 deletions(-) create mode 100644 node_modules/npm-package-arg/node_modules/hosted-git-info/CHANGELOG.md create mode 100644 node_modules/npm-package-arg/node_modules/hosted-git-info/LICENSE create mode 100644 node_modules/npm-package-arg/node_modules/hosted-git-info/README.md create mode 100644 node_modules/npm-package-arg/node_modules/hosted-git-info/git-host-info.js create mode 100644 node_modules/npm-package-arg/node_modules/hosted-git-info/git-host.js create mode 100644 node_modules/npm-package-arg/node_modules/hosted-git-info/index.js create mode 100644 node_modules/npm-package-arg/node_modules/hosted-git-info/package.json diff --git a/node_modules/npm-package-arg/node_modules/hosted-git-info/CHANGELOG.md b/node_modules/npm-package-arg/node_modules/hosted-git-info/CHANGELOG.md new file mode 100644 index 0000000000000..3ffcacacc575c --- /dev/null +++ b/node_modules/npm-package-arg/node_modules/hosted-git-info/CHANGELOG.md @@ -0,0 +1,185 @@ +# Change Log + +All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + + +## [3.0.8](https://github.com/npm/hosted-git-info/compare/v3.0.7...v3.0.8) (2021-01-28) + + +### Bug Fixes + +* simplify the regular expression for shortcut matching ([bede0dc](https://github.com/npm/hosted-git-info/commit/bede0dc)), closes [#76](https://github.com/npm/hosted-git-info/issues/76) + + + + +## [3.0.7](https://github.com/npm/hosted-git-info/compare/v3.0.6...v3.0.7) (2020-10-15) + + +### Bug Fixes + +* correctly filter out urls for tarballs in gitlab ([eb5bd5a](https://github.com/npm/hosted-git-info/commit/eb5bd5a)), closes [#69](https://github.com/npm/hosted-git-info/issues/69) + + + + +## [3.0.6](https://github.com/npm/hosted-git-info/compare/v3.0.5...v3.0.6) (2020-10-12) + + +### Bug Fixes + +* support to github gist legacy hash length ([c067102](https://github.com/npm/hosted-git-info/commit/c067102)), closes [#68](https://github.com/npm/hosted-git-info/issues/68) + + + + +## [3.0.5](https://github.com/npm/hosted-git-info/compare/v3.0.4...v3.0.5) (2020-07-11) + + + + +## [3.0.4](https://github.com/npm/hosted-git-info/compare/v3.0.3...v3.0.4) (2020-02-26) + + +### Bug Fixes + +* Do not pass scp-style URLs to the WhatWG url.URL ([0835306](https://github.com/npm/hosted-git-info/commit/0835306)), closes [#60](https://github.com/npm/hosted-git-info/issues/60) [#63](https://github.com/npm/hosted-git-info/issues/63) + + + + +## [3.0.3](https://github.com/npm/hosted-git-info/compare/v3.0.2...v3.0.3) (2020-02-25) + + + + +## [3.0.2](https://github.com/npm/hosted-git-info/compare/v3.0.1...v3.0.2) (2019-10-08) + + +### Bug Fixes + +* do not encodeURIComponent the domain ([3e5fbec](https://github.com/npm/hosted-git-info/commit/3e5fbec)), closes [#53](https://github.com/npm/hosted-git-info/issues/53) + + + + +## [3.0.1](https://github.com/npm/hosted-git-info/compare/v3.0.0...v3.0.1) (2019-10-07) + + +### Bug Fixes + +* update pathmatch for gitlab ([e3e3054](https://github.com/npm/hosted-git-info/commit/e3e3054)), closes [#52](https://github.com/npm/hosted-git-info/issues/52) +* updated pathmatch for gitlab ([fa87af7](https://github.com/npm/hosted-git-info/commit/fa87af7)) + + + + +# [3.0.0](https://github.com/npm/hosted-git-info/compare/v2.8.3...v3.0.0) (2019-08-12) + + +### Bug Fixes + +* **cache:** Switch to lru-cache to save ourselves from unlimited memory consumption ([37c2891](https://github.com/npm/hosted-git-info/commit/37c2891)), closes [#38](https://github.com/npm/hosted-git-info/issues/38) + + +### BREAKING CHANGES + +* **cache:** Drop support for node 0.x + + + + +## [2.8.3](https://github.com/npm/hosted-git-info/compare/v2.8.2...v2.8.3) (2019-08-12) + + + + +## [2.8.2](https://github.com/npm/hosted-git-info/compare/v2.8.1...v2.8.2) (2019-08-05) + + +### Bug Fixes + +* http protocol use sshurl by default ([3b1d629](https://github.com/npm/hosted-git-info/commit/3b1d629)), closes [#48](https://github.com/npm/hosted-git-info/issues/48) + + + + +## [2.8.1](https://github.com/npm/hosted-git-info/compare/v2.8.0...v2.8.1) (2019-08-05) + + +### Bug Fixes + +* ignore noCommittish on tarball url generation ([5d4a8d7](https://github.com/npm/hosted-git-info/commit/5d4a8d7)) +* use gist tarball url that works for anonymous gists ([1692435](https://github.com/npm/hosted-git-info/commit/1692435)) + + + + +# [2.8.0](https://github.com/npm/hosted-git-info/compare/v2.7.1...v2.8.0) (2019-08-05) + + +### Bug Fixes + +* Allow slashes in gitlab project section ([bbcf7b2](https://github.com/npm/hosted-git-info/commit/bbcf7b2)), closes [#46](https://github.com/npm/hosted-git-info/issues/46) [#43](https://github.com/npm/hosted-git-info/issues/43) +* **git-host:** disallow URI-encoded slash (%2F) in `path` ([3776fa5](https://github.com/npm/hosted-git-info/commit/3776fa5)), closes [#44](https://github.com/npm/hosted-git-info/issues/44) +* **gitlab:** Do not URL encode slashes in project name for GitLab https URL ([cbf04f9](https://github.com/npm/hosted-git-info/commit/cbf04f9)), closes [#47](https://github.com/npm/hosted-git-info/issues/47) +* do not allow invalid gist urls ([d5cf830](https://github.com/npm/hosted-git-info/commit/d5cf830)) +* **cache:** Switch to lru-cache to save ourselves from unlimited memory consumption ([e518222](https://github.com/npm/hosted-git-info/commit/e518222)), closes [#38](https://github.com/npm/hosted-git-info/issues/38) + + +### Features + +* give these objects a name ([60abaea](https://github.com/npm/hosted-git-info/commit/60abaea)) + + + + +## [2.7.1](https://github.com/npm/hosted-git-info/compare/v2.7.0...v2.7.1) (2018-07-07) + + +### Bug Fixes + +* **index:** Guard against non-string types ([5bc580d](https://github.com/npm/hosted-git-info/commit/5bc580d)) +* **parse:** Crash on strings that parse to having no host ([c931482](https://github.com/npm/hosted-git-info/commit/c931482)), closes [#35](https://github.com/npm/hosted-git-info/issues/35) + + + + +# [2.7.0](https://github.com/npm/hosted-git-info/compare/v2.6.1...v2.7.0) (2018-07-06) + + +### Bug Fixes + +* **github tarball:** update github tarballtemplate ([6efd582](https://github.com/npm/hosted-git-info/commit/6efd582)), closes [#34](https://github.com/npm/hosted-git-info/issues/34) +* **gitlab docs:** switched to lowercase anchors for readmes ([701bcd1](https://github.com/npm/hosted-git-info/commit/701bcd1)) + + +### Features + +* **all:** Support www. prefixes on hostnames ([3349575](https://github.com/npm/hosted-git-info/commit/3349575)), closes [#32](https://github.com/npm/hosted-git-info/issues/32) + + + + +## [2.6.1](https://github.com/npm/hosted-git-info/compare/v2.6.0...v2.6.1) (2018-06-25) + +### Bug Fixes + +* **Revert:** "compat: remove Object.assign fallback ([#25](https://github.com/npm/hosted-git-info/issues/25))" ([cce5a62](https://github.com/npm/hosted-git-info/commit/cce5a62)) +* **Revert:** "git-host: fix forgotten extend()" ([a815ec9](https://github.com/npm/hosted-git-info/commit/a815ec9)) + + + + +# [2.6.0](https://github.com/npm/hosted-git-info/compare/v2.5.0...v2.6.0) (2018-03-07) + + +### Bug Fixes + +* **compat:** remove Object.assign fallback ([#25](https://github.com/npm/hosted-git-info/issues/25)) ([627ab55](https://github.com/npm/hosted-git-info/commit/627ab55)) +* **git-host:** fix forgotten extend() ([eba1f7b](https://github.com/npm/hosted-git-info/commit/eba1f7b)) + + +### Features + +* **browse:** fragment support for browse() ([#28](https://github.com/npm/hosted-git-info/issues/28)) ([cd5e5bb](https://github.com/npm/hosted-git-info/commit/cd5e5bb)) diff --git a/node_modules/npm-package-arg/node_modules/hosted-git-info/LICENSE b/node_modules/npm-package-arg/node_modules/hosted-git-info/LICENSE new file mode 100644 index 0000000000000..45055763dc838 --- /dev/null +++ b/node_modules/npm-package-arg/node_modules/hosted-git-info/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2015, Rebecca Turner + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/node_modules/npm-package-arg/node_modules/hosted-git-info/README.md b/node_modules/npm-package-arg/node_modules/hosted-git-info/README.md new file mode 100644 index 0000000000000..8740406029626 --- /dev/null +++ b/node_modules/npm-package-arg/node_modules/hosted-git-info/README.md @@ -0,0 +1,133 @@ +# hosted-git-info + +This will let you identify and transform various git hosts URLs between +protocols. It also can tell you what the URL is for the raw path for +particular file for direct access without git. + +## Example + +```javascript +var hostedGitInfo = require("hosted-git-info") +var info = hostedGitInfo.fromUrl("git@github.com:npm/hosted-git-info.git", opts) +/* info looks like: +{ + type: "github", + domain: "github.com", + user: "npm", + project: "hosted-git-info" +} +*/ +``` + +If the URL can't be matched with a git host, `null` will be returned. We +can match git, ssh and https urls. Additionally, we can match ssh connect +strings (`git@github.com:npm/hosted-git-info`) and shortcuts (eg, +`github:npm/hosted-git-info`). GitHub specifically, is detected in the case +of a third, unprefixed, form: `npm/hosted-git-info`. + +If it does match, the returned object has properties of: + +* info.type -- The short name of the service +* info.domain -- The domain for git protocol use +* info.user -- The name of the user/org on the git host +* info.project -- The name of the project on the git host + +## Version Contract + +The major version will be bumped any time… + +* The constructor stops accepting URLs that it previously accepted. +* A method is removed. +* A method can no longer accept the number and type of arguments it previously accepted. +* A method can return a different type than it currently returns. + +Implications: + +* I do not consider the specific format of the urls returned from, say + `.https()` to be a part of the contract. The contract is that it will + return a string that can be used to fetch the repo via HTTPS. But what + that string looks like, specifically, can change. +* Dropping support for a hosted git provider would constitute a breaking + change. + +## Usage + +### var info = hostedGitInfo.fromUrl(gitSpecifier[, options]) + +* *gitSpecifer* is a URL of a git repository or a SCP-style specifier of one. +* *options* is an optional object. It can have the following properties: + * *noCommittish* — If true then committishes won't be included in generated URLs. + * *noGitPlus* — If true then `git+` won't be prefixed on URLs. + +## Methods + +All of the methods take the same options as the `fromUrl` factory. Options +provided to a method override those provided to the constructor. + +* info.file(path, opts) + +Given the path of a file relative to the repository, returns a URL for +directly fetching it from the githost. If no committish was set then +`master` will be used as the default. + +For example `hostedGitInfo.fromUrl("git@github.com:npm/hosted-git-info.git#v1.0.0").file("package.json")` +would return `https://raw.githubusercontent.com/npm/hosted-git-info/v1.0.0/package.json` + +* info.shortcut(opts) + +eg, `github:npm/hosted-git-info` + +* info.browse(path, fragment, opts) + +eg, `https://github.com/npm/hosted-git-info/tree/v1.2.0`, +`https://github.com/npm/hosted-git-info/tree/v1.2.0/package.json`, +`https://github.com/npm/hosted-git-info/tree/v1.2.0/REAMDE.md#supported-hosts` + +* info.bugs(opts) + +eg, `https://github.com/npm/hosted-git-info/issues` + +* info.docs(opts) + +eg, `https://github.com/npm/hosted-git-info/tree/v1.2.0#readme` + +* info.https(opts) + +eg, `git+https://github.com/npm/hosted-git-info.git` + +* info.sshurl(opts) + +eg, `git+ssh://git@github.com/npm/hosted-git-info.git` + +* info.ssh(opts) + +eg, `git@github.com:npm/hosted-git-info.git` + +* info.path(opts) + +eg, `npm/hosted-git-info` + +* info.tarball(opts) + +eg, `https://github.com/npm/hosted-git-info/archive/v1.2.0.tar.gz` + +* info.getDefaultRepresentation() + +Returns the default output type. The default output type is based on the +string you passed in to be parsed + +* info.toString(opts) + +Uses the getDefaultRepresentation to call one of the other methods to get a URL for +this resource. As such `hostedGitInfo.fromUrl(url).toString()` will give +you a normalized version of the URL that still uses the same protocol. + +Shortcuts will still be returned as shortcuts, but the special case github +form of `org/project` will be normalized to `github:org/project`. + +SSH connect strings will be normalized into `git+ssh` URLs. + +## Supported hosts + +Currently this supports GitHub, Bitbucket and GitLab. Pull requests for +additional hosts welcome. diff --git a/node_modules/npm-package-arg/node_modules/hosted-git-info/git-host-info.js b/node_modules/npm-package-arg/node_modules/hosted-git-info/git-host-info.js new file mode 100644 index 0000000000000..360d7b096be61 --- /dev/null +++ b/node_modules/npm-package-arg/node_modules/hosted-git-info/git-host-info.js @@ -0,0 +1,154 @@ +'use strict' +const maybeJoin = (...args) => args.every(arg => arg) ? args.join('') : '' +const maybeEncode = (arg) => arg ? encodeURIComponent(arg) : '' + +const defaults = { + sshtemplate: ({ domain, user, project, committish }) => `git@${domain}:${user}/${project}.git${maybeJoin('#', committish)}`, + sshurltemplate: ({ domain, user, project, committish }) => `git+ssh://git@${domain}/${user}/${project}.git${maybeJoin('#', committish)}`, + browsetemplate: ({ domain, user, project, committish, treepath }) => `https://${domain}/${user}/${project}${maybeJoin('/', treepath, '/', maybeEncode(committish))}`, + browsefiletemplate: ({ domain, user, project, committish, treepath, path, fragment, hashformat }) => `https://${domain}/${user}/${project}/${treepath}/${maybeEncode(committish || 'master')}/${path}${maybeJoin('#', hashformat(fragment || ''))}`, + docstemplate: ({ domain, user, project, treepath, committish }) => `https://${domain}/${user}/${project}${maybeJoin('/', treepath, '/', maybeEncode(committish))}#readme`, + httpstemplate: ({ auth, domain, user, project, committish }) => `git+https://${maybeJoin(auth, '@')}${domain}/${user}/${project}.git${maybeJoin('#', committish)}`, + filetemplate: ({ domain, user, project, committish, path }) => `https://${domain}/${user}/${project}/raw/${maybeEncode(committish) || 'master'}/${path}`, + shortcuttemplate: ({ type, user, project, committish }) => `${type}:${user}/${project}${maybeJoin('#', committish)}`, + pathtemplate: ({ user, project, committish }) => `${user}/${project}${maybeJoin('#', committish)}`, + bugstemplate: ({ domain, user, project }) => `https://${domain}/${user}/${project}/issues`, + hashformat: formatHashFragment +} + +const gitHosts = {} +gitHosts.github = Object.assign({}, defaults, { + // First two are insecure and generally shouldn't be used any more, but + // they are still supported. + protocols: ['git:', 'http:', 'git+ssh:', 'git+https:', 'ssh:', 'https:'], + domain: 'github.com', + treepath: 'tree', + filetemplate: ({ auth, user, project, committish, path }) => `https://${maybeJoin(auth, '@')}raw.githubusercontent.com/${user}/${project}/${maybeEncode(committish) || 'master'}/${path}`, + gittemplate: ({ auth, domain, user, project, committish }) => `git://${maybeJoin(auth, '@')}${domain}/${user}/${project}.git${maybeJoin('#', committish)}`, + tarballtemplate: ({ domain, user, project, committish }) => `https://codeload.${domain}/${user}/${project}/tar.gz/${maybeEncode(committish) || 'master'}`, + extract: (url) => { + let [, user, project, type, committish] = url.pathname.split('/', 5) + if (type && type !== 'tree') { + return + } + + if (!type) { + committish = url.hash.slice(1) + } + + if (project && project.endsWith('.git')) { + project = project.slice(0, -4) + } + + if (!user || !project) { + return + } + + return { user, project, committish } + } +}) + +gitHosts.bitbucket = Object.assign({}, defaults, { + protocols: ['git+ssh:', 'git+https:', 'ssh:', 'https:'], + domain: 'bitbucket.org', + treepath: 'src', + tarballtemplate: ({ domain, user, project, committish }) => `https://${domain}/${user}/${project}/get/${maybeEncode(committish) || 'master'}.tar.gz`, + extract: (url) => { + let [, user, project, aux] = url.pathname.split('/', 4) + if (['get'].includes(aux)) { + return + } + + if (project && project.endsWith('.git')) { + project = project.slice(0, -4) + } + + if (!user || !project) { + return + } + + return { user, project, committish: url.hash.slice(1) } + } +}) + +gitHosts.gitlab = Object.assign({}, defaults, { + protocols: ['git+ssh:', 'git+https:', 'ssh:', 'https:'], + domain: 'gitlab.com', + treepath: 'tree', + httpstemplate: ({ auth, domain, user, project, committish }) => `git+https://${maybeJoin(auth, '@')}${domain}/${user}/${project}.git${maybeJoin('#', committish)}`, + tarballtemplate: ({ domain, user, project, committish }) => `https://${domain}/${user}/${project}/repository/archive.tar.gz?ref=${maybeEncode(committish) || 'master'}`, + extract: (url) => { + const path = url.pathname.slice(1) + if (path.includes('/-/')) { + return + } + + const segments = path.split('/') + let project = segments.pop() + if (project.endsWith('.git')) { + project = project.slice(0, -4) + } + + const user = segments.join('/') + if (!user || !project) { + return + } + + return { user, project, committish: url.hash.slice(1) } + } +}) + +gitHosts.gist = Object.assign({}, defaults, { + protocols: ['git:', 'git+ssh:', 'git+https:', 'ssh:', 'https:'], + domain: 'gist.github.com', + sshtemplate: ({ domain, project, committish }) => `git@${domain}:${project}.git${maybeJoin('#', committish)}`, + sshurltemplate: ({ domain, project, committish }) => `git+ssh://git@${domain}/${project}.git${maybeJoin('#', committish)}`, + browsetemplate: ({ domain, project, committish }) => `https://${domain}/${project}${maybeJoin('/', maybeEncode(committish))}`, + browsefiletemplate: ({ domain, project, committish, path, hashformat }) => `https://${domain}/${project}${maybeJoin('/', maybeEncode(committish))}${maybeJoin('#', hashformat(path))}`, + docstemplate: ({ domain, project, committish }) => `https://${domain}/${project}${maybeJoin('/', maybeEncode(committish))}`, + httpstemplate: ({ domain, project, committish }) => `git+https://${domain}/${project}.git${maybeJoin('#', committish)}`, + filetemplate: ({ user, project, committish, path }) => `https://gist.githubusercontent.com/${user}/${project}/raw${maybeJoin('/', maybeEncode(committish))}/${path}`, + shortcuttemplate: ({ type, project, committish }) => `${type}:${project}${maybeJoin('#', committish)}`, + pathtemplate: ({ project, committish }) => `${project}${maybeJoin('#', committish)}`, + bugstemplate: ({ domain, project }) => `https://${domain}/${project}`, + gittemplate: ({ domain, project, committish }) => `git://${domain}/${project}.git${maybeJoin('#', committish)}`, + tarballtemplate: ({ project, committish }) => `https://codeload.github.com/gist/${project}/tar.gz/${maybeEncode(committish) || 'master'}`, + extract: (url) => { + let [, user, project, aux] = url.pathname.split('/', 4) + if (aux === 'raw') { + return + } + + if (!project) { + if (!user) { + return + } + + project = user + user = null + } + + if (project.endsWith('.git')) { + project = project.slice(0, -4) + } + + return { user, project, committish: url.hash.slice(1) } + }, + hashformat: function (fragment) { + return fragment && 'file-' + formatHashFragment(fragment) + } +}) + +const names = Object.keys(gitHosts) +gitHosts.byShortcut = {} +gitHosts.byDomain = {} +for (const name of names) { + gitHosts.byShortcut[`${name}:`] = name + gitHosts.byDomain[gitHosts[name].domain] = name +} + +function formatHashFragment (fragment) { + return fragment.toLowerCase().replace(/^\W+|\/|\W+$/g, '').replace(/\W+/g, '-') +} + +module.exports = gitHosts diff --git a/node_modules/npm-package-arg/node_modules/hosted-git-info/git-host.js b/node_modules/npm-package-arg/node_modules/hosted-git-info/git-host.js new file mode 100644 index 0000000000000..8a975e92e58bb --- /dev/null +++ b/node_modules/npm-package-arg/node_modules/hosted-git-info/git-host.js @@ -0,0 +1,110 @@ +'use strict' +const gitHosts = require('./git-host-info.js') + +class GitHost { + constructor (type, user, auth, project, committish, defaultRepresentation, opts = {}) { + Object.assign(this, gitHosts[type]) + this.type = type + this.user = user + this.auth = auth + this.project = project + this.committish = committish + this.default = defaultRepresentation + this.opts = opts + } + + hash () { + return this.committish ? `#${this.committish}` : '' + } + + ssh (opts) { + return this._fill(this.sshtemplate, opts) + } + + _fill (template, opts) { + if (typeof template === 'function') { + const options = { ...this, ...this.opts, ...opts } + + // the path should always be set so we don't end up with 'undefined' in urls + if (!options.path) { + options.path = '' + } + + // template functions will insert the leading slash themselves + if (options.path.startsWith('/')) { + options.path = options.path.slice(1) + } + + if (options.noCommittish) { + options.committish = null + } + + const result = template(options) + return options.noGitPlus && result.startsWith('git+') ? result.slice(4) : result + } + + return null + } + + sshurl (opts) { + return this._fill(this.sshurltemplate, opts) + } + + browse (path, fragment, opts) { + // not a string, treat path as opts + if (typeof path !== 'string') { + return this._fill(this.browsetemplate, path) + } + + if (typeof fragment !== 'string') { + opts = fragment + fragment = null + } + return this._fill(this.browsefiletemplate, { ...opts, fragment, path }) + } + + docs (opts) { + return this._fill(this.docstemplate, opts) + } + + bugs (opts) { + return this._fill(this.bugstemplate, opts) + } + + https (opts) { + return this._fill(this.httpstemplate, opts) + } + + git (opts) { + return this._fill(this.gittemplate, opts) + } + + shortcut (opts) { + return this._fill(this.shortcuttemplate, opts) + } + + path (opts) { + return this._fill(this.pathtemplate, opts) + } + + tarball (opts) { + return this._fill(this.tarballtemplate, { ...opts, noCommittish: false }) + } + + file (path, opts) { + return this._fill(this.filetemplate, { ...opts, path }) + } + + getDefaultRepresentation () { + return this.default + } + + toString (opts) { + if (this.default && typeof this[this.default] === 'function') { + return this[this.default](opts) + } + + return this.sshurl(opts) + } +} +module.exports = GitHost diff --git a/node_modules/npm-package-arg/node_modules/hosted-git-info/index.js b/node_modules/npm-package-arg/node_modules/hosted-git-info/index.js new file mode 100644 index 0000000000000..f35c570c46b59 --- /dev/null +++ b/node_modules/npm-package-arg/node_modules/hosted-git-info/index.js @@ -0,0 +1,237 @@ +'use strict' +const url = require('url') +const gitHosts = require('./git-host-info.js') +const GitHost = module.exports = require('./git-host.js') +const LRU = require('lru-cache') +const cache = new LRU({ max: 1000 }) + +const protocolToRepresentationMap = { + 'git+ssh:': 'sshurl', + 'git+https:': 'https', + 'ssh:': 'sshurl', + 'git:': 'git' +} + +function protocolToRepresentation (protocol) { + return protocolToRepresentationMap[protocol] || protocol.slice(0, -1) +} + +const authProtocols = { + 'git:': true, + 'https:': true, + 'git+https:': true, + 'http:': true, + 'git+http:': true +} + +const knownProtocols = Object.keys(gitHosts.byShortcut).concat(['http:', 'https:', 'git:', 'git+ssh:', 'git+https:', 'ssh:']) + +module.exports.fromUrl = function (giturl, opts) { + if (typeof giturl !== 'string') { + return + } + + const key = giturl + JSON.stringify(opts || {}) + + if (!cache.has(key)) { + cache.set(key, fromUrl(giturl, opts)) + } + + return cache.get(key) +} + +function fromUrl (giturl, opts) { + if (!giturl) { + return + } + + const url = isGitHubShorthand(giturl) ? 'github:' + giturl : correctProtocol(giturl) + const parsed = parseGitUrl(url) + if (!parsed) { + return parsed + } + + const gitHostShortcut = gitHosts.byShortcut[parsed.protocol] + const gitHostDomain = gitHosts.byDomain[parsed.hostname.startsWith('www.') ? parsed.hostname.slice(4) : parsed.hostname] + const gitHostName = gitHostShortcut || gitHostDomain + if (!gitHostName) { + return + } + + const gitHostInfo = gitHosts[gitHostShortcut || gitHostDomain] + let auth = null + if (authProtocols[parsed.protocol] && (parsed.username || parsed.password)) { + auth = `${parsed.username}${parsed.password ? ':' + parsed.password : ''}` + } + + let committish = null + let user = null + let project = null + let defaultRepresentation = null + + try { + if (gitHostShortcut) { + let pathname = parsed.pathname.startsWith('/') ? parsed.pathname.slice(1) : parsed.pathname + const firstAt = pathname.indexOf('@') + // we ignore auth for shortcuts, so just trim it out + if (firstAt > -1) { + pathname = pathname.slice(firstAt + 1) + } + + const lastSlash = pathname.lastIndexOf('/') + if (lastSlash > -1) { + user = decodeURIComponent(pathname.slice(0, lastSlash)) + // we want nulls only, never empty strings + if (!user) { + user = null + } + project = decodeURIComponent(pathname.slice(lastSlash + 1)) + } else { + project = decodeURIComponent(pathname) + } + + if (project.endsWith('.git')) { + project = project.slice(0, -4) + } + + if (parsed.hash) { + committish = decodeURIComponent(parsed.hash.slice(1)) + } + + defaultRepresentation = 'shortcut' + } else { + if (!gitHostInfo.protocols.includes(parsed.protocol)) { + return + } + + const segments = gitHostInfo.extract(parsed) + if (!segments) { + return + } + + user = segments.user && decodeURIComponent(segments.user) + project = decodeURIComponent(segments.project) + committish = decodeURIComponent(segments.committish) + defaultRepresentation = protocolToRepresentation(parsed.protocol) + } + } catch (err) { + /* istanbul ignore else */ + if (err instanceof URIError) { + return + } else { + throw err + } + } + + return new GitHost(gitHostName, user, auth, project, committish, defaultRepresentation, opts) +} + +// accepts input like git:github.com:user/repo and inserts the // after the first : +const correctProtocol = (arg) => { + const firstColon = arg.indexOf(':') + const proto = arg.slice(0, firstColon + 1) + if (knownProtocols.includes(proto)) { + return arg + } + + const firstAt = arg.indexOf('@') + if (firstAt > -1) { + if (firstAt > firstColon) { + return `git+ssh://${arg}` + } else { + return arg + } + } + + const doubleSlash = arg.indexOf('//') + if (doubleSlash === firstColon + 1) { + return arg + } + + return arg.slice(0, firstColon + 1) + '//' + arg.slice(firstColon + 1) +} + +// look for github shorthand inputs, such as npm/cli +const isGitHubShorthand = (arg) => { + // it cannot contain whitespace before the first # + // it cannot start with a / because that's probably an absolute file path + // but it must include a slash since repos are username/repository + // it cannot start with a . because that's probably a relative file path + // it cannot start with an @ because that's a scoped package if it passes the other tests + // it cannot contain a : before a # because that tells us that there's a protocol + // a second / may not exist before a # + const firstHash = arg.indexOf('#') + const firstSlash = arg.indexOf('/') + const secondSlash = arg.indexOf('/', firstSlash + 1) + const firstColon = arg.indexOf(':') + const firstSpace = /\s/.exec(arg) + const firstAt = arg.indexOf('@') + + const spaceOnlyAfterHash = !firstSpace || (firstHash > -1 && firstSpace.index > firstHash) + const atOnlyAfterHash = firstAt === -1 || (firstHash > -1 && firstAt > firstHash) + const colonOnlyAfterHash = firstColon === -1 || (firstHash > -1 && firstColon > firstHash) + const secondSlashOnlyAfterHash = secondSlash === -1 || (firstHash > -1 && secondSlash > firstHash) + const hasSlash = firstSlash > 0 + // if a # is found, what we really want to know is that the character immediately before # is not a / + const doesNotEndWithSlash = firstHash > -1 ? arg[firstHash - 1] !== '/' : !arg.endsWith('/') + const doesNotStartWithDot = !arg.startsWith('.') + + return spaceOnlyAfterHash && hasSlash && doesNotEndWithSlash && doesNotStartWithDot && atOnlyAfterHash && colonOnlyAfterHash && secondSlashOnlyAfterHash +} + +// attempt to correct an scp style url so that it will parse with `new URL()` +const correctUrl = (giturl) => { + const firstAt = giturl.indexOf('@') + const lastHash = giturl.lastIndexOf('#') + let firstColon = giturl.indexOf(':') + let lastColon = giturl.lastIndexOf(':', lastHash > -1 ? lastHash : Infinity) + + let corrected + if (lastColon > firstAt) { + // the last : comes after the first @ (or there is no @) + // like it would in: + // proto://hostname.com:user/repo + // username@hostname.com:user/repo + // :password@hostname.com:user/repo + // username:password@hostname.com:user/repo + // proto://username@hostname.com:user/repo + // proto://:password@hostname.com:user/repo + // proto://username:password@hostname.com:user/repo + // then we replace the last : with a / to create a valid path + corrected = giturl.slice(0, lastColon) + '/' + giturl.slice(lastColon + 1) + // // and we find our new : positions + firstColon = corrected.indexOf(':') + lastColon = corrected.lastIndexOf(':') + } + + if (firstColon === -1 && giturl.indexOf('//') === -1) { + // we have no : at all + // as it would be in: + // username@hostname.com/user/repo + // then we prepend a protocol + corrected = `git+ssh://${corrected}` + } + + return corrected +} + +// try to parse the url as its given to us, if that throws +// then we try to clean the url and parse that result instead +// THIS FUNCTION SHOULD NEVER THROW +const parseGitUrl = (giturl) => { + let result + try { + result = new url.URL(giturl) + } catch (err) {} + + if (result) { + return result + } + + const correctedUrl = correctUrl(giturl) + try { + result = new url.URL(correctedUrl) + } catch (err) {} + + return result +} diff --git a/node_modules/npm-package-arg/node_modules/hosted-git-info/package.json b/node_modules/npm-package-arg/node_modules/hosted-git-info/package.json new file mode 100644 index 0000000000000..930e3b693b980 --- /dev/null +++ b/node_modules/npm-package-arg/node_modules/hosted-git-info/package.json @@ -0,0 +1,52 @@ +{ + "name": "hosted-git-info", + "version": "4.0.1", + "description": "Provides metadata and conversions from repository urls for GitHub, Bitbucket and GitLab", + "main": "index.js", + "repository": { + "type": "git", + "url": "git+https://github.com/npm/hosted-git-info.git" + }, + "keywords": [ + "git", + "github", + "bitbucket", + "gitlab" + ], + "author": "Rebecca Turner (http://re-becca.org)", + "license": "ISC", + "bugs": { + "url": "https://github.com/npm/hosted-git-info/issues" + }, + "homepage": "https://github.com/npm/hosted-git-info", + "scripts": { + "posttest": "standard", + "postversion": "npm publish", + "prepublishOnly": "git push origin --follow-tags", + "preversion": "npm test", + "snap": "tap", + "test": "tap", + "test:coverage": "tap --coverage-report=html" + }, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "devDependencies": { + "standard": "^16.0.3", + "standard-version": "^9.1.0", + "tap": "^14.11.0" + }, + "files": [ + "index.js", + "git-host.js", + "git-host-info.js" + ], + "engines": { + "node": ">=10" + }, + "tap": { + "color": 1, + "coverage": true, + "esm": false + } +} diff --git a/node_modules/npm-package-arg/package.json b/node_modules/npm-package-arg/package.json index c460be828efcb..ed3b364442c2c 100644 --- a/node_modules/npm-package-arg/package.json +++ b/node_modules/npm-package-arg/package.json @@ -1,6 +1,6 @@ { "name": "npm-package-arg", - "version": "8.1.1", + "version": "8.1.2", "description": "Parse the things that can be arguments to `npm install`", "main": "npa.js", "directories": { @@ -10,12 +10,12 @@ "npa.js" ], "dependencies": { - "hosted-git-info": "^3.0.6", - "semver": "^7.0.0", + "hosted-git-info": "^4.0.1", + "semver": "^7.3.4", "validate-npm-package-name": "^3.0.0" }, "devDependencies": { - "tap": "^14.10.2" + "tap": "^14.11.0" }, "scripts": { "preversion": "npm test", diff --git a/package-lock.json b/package-lock.json index 42c635fbfe4bc..56eb35d721ab8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -294,7 +294,7 @@ "node-gyp": "^7.1.2", "nopt": "^5.0.0", "npm-audit-report": "^2.1.4", - "npm-package-arg": "^8.1.1", + "npm-package-arg": "^8.1.2", "npm-pick-manifest": "^6.1.0", "npm-profile": "^5.0.2", "npm-registry-fetch": "^9.0.0", @@ -5550,19 +5550,31 @@ "inBundle": true }, "node_modules/npm-package-arg": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.1.tgz", - "integrity": "sha512-CsP95FhWQDwNqiYS+Q0mZ7FAEDytDZAkNxQqea6IaAFJTAY9Lhhqyl0irU/6PMc7BGfUmnsbHcqxJD7XuVM/rg==", + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.2.tgz", + "integrity": "sha512-6Eem455JsSMJY6Kpd3EyWE+n5hC+g9bSyHr9K9U2zqZb7+02+hObQ2c0+8iDk/mNF+8r1MhY44WypKJAkySIYA==", "inBundle": true, "dependencies": { - "hosted-git-info": "^3.0.6", - "semver": "^7.0.0", + "hosted-git-info": "^4.0.1", + "semver": "^7.3.4", "validate-npm-package-name": "^3.0.0" }, "engines": { "node": ">=10" } }, + "node_modules/npm-package-arg/node_modules/hosted-git-info": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.1.tgz", + "integrity": "sha512-eT7NrxAsppPRQEBSwKSosReE+v8OzABwEScQYk5d4uxaEPlzxTIku7LINXtBGalthkLhJnq5lBI89PfK43zAKg==", + "inBundle": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/npm-packlist": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-2.1.4.tgz", @@ -14504,13 +14516,23 @@ "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" }, "npm-package-arg": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.1.tgz", - "integrity": "sha512-CsP95FhWQDwNqiYS+Q0mZ7FAEDytDZAkNxQqea6IaAFJTAY9Lhhqyl0irU/6PMc7BGfUmnsbHcqxJD7XuVM/rg==", + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.2.tgz", + "integrity": "sha512-6Eem455JsSMJY6Kpd3EyWE+n5hC+g9bSyHr9K9U2zqZb7+02+hObQ2c0+8iDk/mNF+8r1MhY44WypKJAkySIYA==", "requires": { - "hosted-git-info": "^3.0.6", - "semver": "^7.0.0", + "hosted-git-info": "^4.0.1", + "semver": "^7.3.4", "validate-npm-package-name": "^3.0.0" + }, + "dependencies": { + "hosted-git-info": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.1.tgz", + "integrity": "sha512-eT7NrxAsppPRQEBSwKSosReE+v8OzABwEScQYk5d4uxaEPlzxTIku7LINXtBGalthkLhJnq5lBI89PfK43zAKg==", + "requires": { + "lru-cache": "^6.0.0" + } + } } }, "npm-packlist": { diff --git a/package.json b/package.json index afd3b36cb08f7..26a502586caaa 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "node-gyp": "^7.1.2", "nopt": "^5.0.0", "npm-audit-report": "^2.1.4", - "npm-package-arg": "^8.1.1", + "npm-package-arg": "^8.1.2", "npm-pick-manifest": "^6.1.0", "npm-profile": "^5.0.2", "npm-registry-fetch": "^9.0.0", From 33c853618db1540a2432807ebbef0ed52e90cbb5 Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Thu, 18 Mar 2021 13:15:16 -0400 Subject: [PATCH 02/42] normalize-package-data@3.0.2 --- node_modules/normalize-package-data/README.md | 4 +- .../node_modules/hosted-git-info/CHANGELOG.md | 185 ++++++++++++++ .../node_modules/hosted-git-info/LICENSE | 13 + .../node_modules/hosted-git-info/README.md | 133 ++++++++++ .../hosted-git-info/git-host-info.js | 154 ++++++++++++ .../node_modules/hosted-git-info/git-host.js | 110 ++++++++ .../node_modules/hosted-git-info/index.js | 237 ++++++++++++++++++ .../node_modules/hosted-git-info/package.json | 52 ++++ .../normalize-package-data/package.json | 13 +- package-lock.json | 46 +++- 10 files changed, 929 insertions(+), 18 deletions(-) create mode 100644 node_modules/normalize-package-data/node_modules/hosted-git-info/CHANGELOG.md create mode 100644 node_modules/normalize-package-data/node_modules/hosted-git-info/LICENSE create mode 100644 node_modules/normalize-package-data/node_modules/hosted-git-info/README.md create mode 100644 node_modules/normalize-package-data/node_modules/hosted-git-info/git-host-info.js create mode 100644 node_modules/normalize-package-data/node_modules/hosted-git-info/git-host.js create mode 100644 node_modules/normalize-package-data/node_modules/hosted-git-info/index.js create mode 100644 node_modules/normalize-package-data/node_modules/hosted-git-info/package.json diff --git a/node_modules/normalize-package-data/README.md b/node_modules/normalize-package-data/README.md index d2bd7bc7ff606..84da5e8bcf2dd 100644 --- a/node_modules/normalize-package-data/README.md +++ b/node_modules/normalize-package-data/README.md @@ -1,4 +1,6 @@ -# normalize-package-data [![Build Status](https://travis-ci.org/npm/normalize-package-data.png?branch=master)](https://travis-ci.org/npm/normalize-package-data) +# normalize-package-data + +[![Build Status](https://travis-ci.org/npm/normalize-package-data.svg?branch=master)](https://travis-ci.org/npm/normalize-package-data) normalize-package-data exports a function that normalizes package metadata. This data is typically found in a package.json file, but in principle could come from any source - for example the npm registry. diff --git a/node_modules/normalize-package-data/node_modules/hosted-git-info/CHANGELOG.md b/node_modules/normalize-package-data/node_modules/hosted-git-info/CHANGELOG.md new file mode 100644 index 0000000000000..3ffcacacc575c --- /dev/null +++ b/node_modules/normalize-package-data/node_modules/hosted-git-info/CHANGELOG.md @@ -0,0 +1,185 @@ +# Change Log + +All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + + +## [3.0.8](https://github.com/npm/hosted-git-info/compare/v3.0.7...v3.0.8) (2021-01-28) + + +### Bug Fixes + +* simplify the regular expression for shortcut matching ([bede0dc](https://github.com/npm/hosted-git-info/commit/bede0dc)), closes [#76](https://github.com/npm/hosted-git-info/issues/76) + + + + +## [3.0.7](https://github.com/npm/hosted-git-info/compare/v3.0.6...v3.0.7) (2020-10-15) + + +### Bug Fixes + +* correctly filter out urls for tarballs in gitlab ([eb5bd5a](https://github.com/npm/hosted-git-info/commit/eb5bd5a)), closes [#69](https://github.com/npm/hosted-git-info/issues/69) + + + + +## [3.0.6](https://github.com/npm/hosted-git-info/compare/v3.0.5...v3.0.6) (2020-10-12) + + +### Bug Fixes + +* support to github gist legacy hash length ([c067102](https://github.com/npm/hosted-git-info/commit/c067102)), closes [#68](https://github.com/npm/hosted-git-info/issues/68) + + + + +## [3.0.5](https://github.com/npm/hosted-git-info/compare/v3.0.4...v3.0.5) (2020-07-11) + + + + +## [3.0.4](https://github.com/npm/hosted-git-info/compare/v3.0.3...v3.0.4) (2020-02-26) + + +### Bug Fixes + +* Do not pass scp-style URLs to the WhatWG url.URL ([0835306](https://github.com/npm/hosted-git-info/commit/0835306)), closes [#60](https://github.com/npm/hosted-git-info/issues/60) [#63](https://github.com/npm/hosted-git-info/issues/63) + + + + +## [3.0.3](https://github.com/npm/hosted-git-info/compare/v3.0.2...v3.0.3) (2020-02-25) + + + + +## [3.0.2](https://github.com/npm/hosted-git-info/compare/v3.0.1...v3.0.2) (2019-10-08) + + +### Bug Fixes + +* do not encodeURIComponent the domain ([3e5fbec](https://github.com/npm/hosted-git-info/commit/3e5fbec)), closes [#53](https://github.com/npm/hosted-git-info/issues/53) + + + + +## [3.0.1](https://github.com/npm/hosted-git-info/compare/v3.0.0...v3.0.1) (2019-10-07) + + +### Bug Fixes + +* update pathmatch for gitlab ([e3e3054](https://github.com/npm/hosted-git-info/commit/e3e3054)), closes [#52](https://github.com/npm/hosted-git-info/issues/52) +* updated pathmatch for gitlab ([fa87af7](https://github.com/npm/hosted-git-info/commit/fa87af7)) + + + + +# [3.0.0](https://github.com/npm/hosted-git-info/compare/v2.8.3...v3.0.0) (2019-08-12) + + +### Bug Fixes + +* **cache:** Switch to lru-cache to save ourselves from unlimited memory consumption ([37c2891](https://github.com/npm/hosted-git-info/commit/37c2891)), closes [#38](https://github.com/npm/hosted-git-info/issues/38) + + +### BREAKING CHANGES + +* **cache:** Drop support for node 0.x + + + + +## [2.8.3](https://github.com/npm/hosted-git-info/compare/v2.8.2...v2.8.3) (2019-08-12) + + + + +## [2.8.2](https://github.com/npm/hosted-git-info/compare/v2.8.1...v2.8.2) (2019-08-05) + + +### Bug Fixes + +* http protocol use sshurl by default ([3b1d629](https://github.com/npm/hosted-git-info/commit/3b1d629)), closes [#48](https://github.com/npm/hosted-git-info/issues/48) + + + + +## [2.8.1](https://github.com/npm/hosted-git-info/compare/v2.8.0...v2.8.1) (2019-08-05) + + +### Bug Fixes + +* ignore noCommittish on tarball url generation ([5d4a8d7](https://github.com/npm/hosted-git-info/commit/5d4a8d7)) +* use gist tarball url that works for anonymous gists ([1692435](https://github.com/npm/hosted-git-info/commit/1692435)) + + + + +# [2.8.0](https://github.com/npm/hosted-git-info/compare/v2.7.1...v2.8.0) (2019-08-05) + + +### Bug Fixes + +* Allow slashes in gitlab project section ([bbcf7b2](https://github.com/npm/hosted-git-info/commit/bbcf7b2)), closes [#46](https://github.com/npm/hosted-git-info/issues/46) [#43](https://github.com/npm/hosted-git-info/issues/43) +* **git-host:** disallow URI-encoded slash (%2F) in `path` ([3776fa5](https://github.com/npm/hosted-git-info/commit/3776fa5)), closes [#44](https://github.com/npm/hosted-git-info/issues/44) +* **gitlab:** Do not URL encode slashes in project name for GitLab https URL ([cbf04f9](https://github.com/npm/hosted-git-info/commit/cbf04f9)), closes [#47](https://github.com/npm/hosted-git-info/issues/47) +* do not allow invalid gist urls ([d5cf830](https://github.com/npm/hosted-git-info/commit/d5cf830)) +* **cache:** Switch to lru-cache to save ourselves from unlimited memory consumption ([e518222](https://github.com/npm/hosted-git-info/commit/e518222)), closes [#38](https://github.com/npm/hosted-git-info/issues/38) + + +### Features + +* give these objects a name ([60abaea](https://github.com/npm/hosted-git-info/commit/60abaea)) + + + + +## [2.7.1](https://github.com/npm/hosted-git-info/compare/v2.7.0...v2.7.1) (2018-07-07) + + +### Bug Fixes + +* **index:** Guard against non-string types ([5bc580d](https://github.com/npm/hosted-git-info/commit/5bc580d)) +* **parse:** Crash on strings that parse to having no host ([c931482](https://github.com/npm/hosted-git-info/commit/c931482)), closes [#35](https://github.com/npm/hosted-git-info/issues/35) + + + + +# [2.7.0](https://github.com/npm/hosted-git-info/compare/v2.6.1...v2.7.0) (2018-07-06) + + +### Bug Fixes + +* **github tarball:** update github tarballtemplate ([6efd582](https://github.com/npm/hosted-git-info/commit/6efd582)), closes [#34](https://github.com/npm/hosted-git-info/issues/34) +* **gitlab docs:** switched to lowercase anchors for readmes ([701bcd1](https://github.com/npm/hosted-git-info/commit/701bcd1)) + + +### Features + +* **all:** Support www. prefixes on hostnames ([3349575](https://github.com/npm/hosted-git-info/commit/3349575)), closes [#32](https://github.com/npm/hosted-git-info/issues/32) + + + + +## [2.6.1](https://github.com/npm/hosted-git-info/compare/v2.6.0...v2.6.1) (2018-06-25) + +### Bug Fixes + +* **Revert:** "compat: remove Object.assign fallback ([#25](https://github.com/npm/hosted-git-info/issues/25))" ([cce5a62](https://github.com/npm/hosted-git-info/commit/cce5a62)) +* **Revert:** "git-host: fix forgotten extend()" ([a815ec9](https://github.com/npm/hosted-git-info/commit/a815ec9)) + + + + +# [2.6.0](https://github.com/npm/hosted-git-info/compare/v2.5.0...v2.6.0) (2018-03-07) + + +### Bug Fixes + +* **compat:** remove Object.assign fallback ([#25](https://github.com/npm/hosted-git-info/issues/25)) ([627ab55](https://github.com/npm/hosted-git-info/commit/627ab55)) +* **git-host:** fix forgotten extend() ([eba1f7b](https://github.com/npm/hosted-git-info/commit/eba1f7b)) + + +### Features + +* **browse:** fragment support for browse() ([#28](https://github.com/npm/hosted-git-info/issues/28)) ([cd5e5bb](https://github.com/npm/hosted-git-info/commit/cd5e5bb)) diff --git a/node_modules/normalize-package-data/node_modules/hosted-git-info/LICENSE b/node_modules/normalize-package-data/node_modules/hosted-git-info/LICENSE new file mode 100644 index 0000000000000..45055763dc838 --- /dev/null +++ b/node_modules/normalize-package-data/node_modules/hosted-git-info/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2015, Rebecca Turner + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/node_modules/normalize-package-data/node_modules/hosted-git-info/README.md b/node_modules/normalize-package-data/node_modules/hosted-git-info/README.md new file mode 100644 index 0000000000000..8740406029626 --- /dev/null +++ b/node_modules/normalize-package-data/node_modules/hosted-git-info/README.md @@ -0,0 +1,133 @@ +# hosted-git-info + +This will let you identify and transform various git hosts URLs between +protocols. It also can tell you what the URL is for the raw path for +particular file for direct access without git. + +## Example + +```javascript +var hostedGitInfo = require("hosted-git-info") +var info = hostedGitInfo.fromUrl("git@github.com:npm/hosted-git-info.git", opts) +/* info looks like: +{ + type: "github", + domain: "github.com", + user: "npm", + project: "hosted-git-info" +} +*/ +``` + +If the URL can't be matched with a git host, `null` will be returned. We +can match git, ssh and https urls. Additionally, we can match ssh connect +strings (`git@github.com:npm/hosted-git-info`) and shortcuts (eg, +`github:npm/hosted-git-info`). GitHub specifically, is detected in the case +of a third, unprefixed, form: `npm/hosted-git-info`. + +If it does match, the returned object has properties of: + +* info.type -- The short name of the service +* info.domain -- The domain for git protocol use +* info.user -- The name of the user/org on the git host +* info.project -- The name of the project on the git host + +## Version Contract + +The major version will be bumped any time… + +* The constructor stops accepting URLs that it previously accepted. +* A method is removed. +* A method can no longer accept the number and type of arguments it previously accepted. +* A method can return a different type than it currently returns. + +Implications: + +* I do not consider the specific format of the urls returned from, say + `.https()` to be a part of the contract. The contract is that it will + return a string that can be used to fetch the repo via HTTPS. But what + that string looks like, specifically, can change. +* Dropping support for a hosted git provider would constitute a breaking + change. + +## Usage + +### var info = hostedGitInfo.fromUrl(gitSpecifier[, options]) + +* *gitSpecifer* is a URL of a git repository or a SCP-style specifier of one. +* *options* is an optional object. It can have the following properties: + * *noCommittish* — If true then committishes won't be included in generated URLs. + * *noGitPlus* — If true then `git+` won't be prefixed on URLs. + +## Methods + +All of the methods take the same options as the `fromUrl` factory. Options +provided to a method override those provided to the constructor. + +* info.file(path, opts) + +Given the path of a file relative to the repository, returns a URL for +directly fetching it from the githost. If no committish was set then +`master` will be used as the default. + +For example `hostedGitInfo.fromUrl("git@github.com:npm/hosted-git-info.git#v1.0.0").file("package.json")` +would return `https://raw.githubusercontent.com/npm/hosted-git-info/v1.0.0/package.json` + +* info.shortcut(opts) + +eg, `github:npm/hosted-git-info` + +* info.browse(path, fragment, opts) + +eg, `https://github.com/npm/hosted-git-info/tree/v1.2.0`, +`https://github.com/npm/hosted-git-info/tree/v1.2.0/package.json`, +`https://github.com/npm/hosted-git-info/tree/v1.2.0/REAMDE.md#supported-hosts` + +* info.bugs(opts) + +eg, `https://github.com/npm/hosted-git-info/issues` + +* info.docs(opts) + +eg, `https://github.com/npm/hosted-git-info/tree/v1.2.0#readme` + +* info.https(opts) + +eg, `git+https://github.com/npm/hosted-git-info.git` + +* info.sshurl(opts) + +eg, `git+ssh://git@github.com/npm/hosted-git-info.git` + +* info.ssh(opts) + +eg, `git@github.com:npm/hosted-git-info.git` + +* info.path(opts) + +eg, `npm/hosted-git-info` + +* info.tarball(opts) + +eg, `https://github.com/npm/hosted-git-info/archive/v1.2.0.tar.gz` + +* info.getDefaultRepresentation() + +Returns the default output type. The default output type is based on the +string you passed in to be parsed + +* info.toString(opts) + +Uses the getDefaultRepresentation to call one of the other methods to get a URL for +this resource. As such `hostedGitInfo.fromUrl(url).toString()` will give +you a normalized version of the URL that still uses the same protocol. + +Shortcuts will still be returned as shortcuts, but the special case github +form of `org/project` will be normalized to `github:org/project`. + +SSH connect strings will be normalized into `git+ssh` URLs. + +## Supported hosts + +Currently this supports GitHub, Bitbucket and GitLab. Pull requests for +additional hosts welcome. diff --git a/node_modules/normalize-package-data/node_modules/hosted-git-info/git-host-info.js b/node_modules/normalize-package-data/node_modules/hosted-git-info/git-host-info.js new file mode 100644 index 0000000000000..360d7b096be61 --- /dev/null +++ b/node_modules/normalize-package-data/node_modules/hosted-git-info/git-host-info.js @@ -0,0 +1,154 @@ +'use strict' +const maybeJoin = (...args) => args.every(arg => arg) ? args.join('') : '' +const maybeEncode = (arg) => arg ? encodeURIComponent(arg) : '' + +const defaults = { + sshtemplate: ({ domain, user, project, committish }) => `git@${domain}:${user}/${project}.git${maybeJoin('#', committish)}`, + sshurltemplate: ({ domain, user, project, committish }) => `git+ssh://git@${domain}/${user}/${project}.git${maybeJoin('#', committish)}`, + browsetemplate: ({ domain, user, project, committish, treepath }) => `https://${domain}/${user}/${project}${maybeJoin('/', treepath, '/', maybeEncode(committish))}`, + browsefiletemplate: ({ domain, user, project, committish, treepath, path, fragment, hashformat }) => `https://${domain}/${user}/${project}/${treepath}/${maybeEncode(committish || 'master')}/${path}${maybeJoin('#', hashformat(fragment || ''))}`, + docstemplate: ({ domain, user, project, treepath, committish }) => `https://${domain}/${user}/${project}${maybeJoin('/', treepath, '/', maybeEncode(committish))}#readme`, + httpstemplate: ({ auth, domain, user, project, committish }) => `git+https://${maybeJoin(auth, '@')}${domain}/${user}/${project}.git${maybeJoin('#', committish)}`, + filetemplate: ({ domain, user, project, committish, path }) => `https://${domain}/${user}/${project}/raw/${maybeEncode(committish) || 'master'}/${path}`, + shortcuttemplate: ({ type, user, project, committish }) => `${type}:${user}/${project}${maybeJoin('#', committish)}`, + pathtemplate: ({ user, project, committish }) => `${user}/${project}${maybeJoin('#', committish)}`, + bugstemplate: ({ domain, user, project }) => `https://${domain}/${user}/${project}/issues`, + hashformat: formatHashFragment +} + +const gitHosts = {} +gitHosts.github = Object.assign({}, defaults, { + // First two are insecure and generally shouldn't be used any more, but + // they are still supported. + protocols: ['git:', 'http:', 'git+ssh:', 'git+https:', 'ssh:', 'https:'], + domain: 'github.com', + treepath: 'tree', + filetemplate: ({ auth, user, project, committish, path }) => `https://${maybeJoin(auth, '@')}raw.githubusercontent.com/${user}/${project}/${maybeEncode(committish) || 'master'}/${path}`, + gittemplate: ({ auth, domain, user, project, committish }) => `git://${maybeJoin(auth, '@')}${domain}/${user}/${project}.git${maybeJoin('#', committish)}`, + tarballtemplate: ({ domain, user, project, committish }) => `https://codeload.${domain}/${user}/${project}/tar.gz/${maybeEncode(committish) || 'master'}`, + extract: (url) => { + let [, user, project, type, committish] = url.pathname.split('/', 5) + if (type && type !== 'tree') { + return + } + + if (!type) { + committish = url.hash.slice(1) + } + + if (project && project.endsWith('.git')) { + project = project.slice(0, -4) + } + + if (!user || !project) { + return + } + + return { user, project, committish } + } +}) + +gitHosts.bitbucket = Object.assign({}, defaults, { + protocols: ['git+ssh:', 'git+https:', 'ssh:', 'https:'], + domain: 'bitbucket.org', + treepath: 'src', + tarballtemplate: ({ domain, user, project, committish }) => `https://${domain}/${user}/${project}/get/${maybeEncode(committish) || 'master'}.tar.gz`, + extract: (url) => { + let [, user, project, aux] = url.pathname.split('/', 4) + if (['get'].includes(aux)) { + return + } + + if (project && project.endsWith('.git')) { + project = project.slice(0, -4) + } + + if (!user || !project) { + return + } + + return { user, project, committish: url.hash.slice(1) } + } +}) + +gitHosts.gitlab = Object.assign({}, defaults, { + protocols: ['git+ssh:', 'git+https:', 'ssh:', 'https:'], + domain: 'gitlab.com', + treepath: 'tree', + httpstemplate: ({ auth, domain, user, project, committish }) => `git+https://${maybeJoin(auth, '@')}${domain}/${user}/${project}.git${maybeJoin('#', committish)}`, + tarballtemplate: ({ domain, user, project, committish }) => `https://${domain}/${user}/${project}/repository/archive.tar.gz?ref=${maybeEncode(committish) || 'master'}`, + extract: (url) => { + const path = url.pathname.slice(1) + if (path.includes('/-/')) { + return + } + + const segments = path.split('/') + let project = segments.pop() + if (project.endsWith('.git')) { + project = project.slice(0, -4) + } + + const user = segments.join('/') + if (!user || !project) { + return + } + + return { user, project, committish: url.hash.slice(1) } + } +}) + +gitHosts.gist = Object.assign({}, defaults, { + protocols: ['git:', 'git+ssh:', 'git+https:', 'ssh:', 'https:'], + domain: 'gist.github.com', + sshtemplate: ({ domain, project, committish }) => `git@${domain}:${project}.git${maybeJoin('#', committish)}`, + sshurltemplate: ({ domain, project, committish }) => `git+ssh://git@${domain}/${project}.git${maybeJoin('#', committish)}`, + browsetemplate: ({ domain, project, committish }) => `https://${domain}/${project}${maybeJoin('/', maybeEncode(committish))}`, + browsefiletemplate: ({ domain, project, committish, path, hashformat }) => `https://${domain}/${project}${maybeJoin('/', maybeEncode(committish))}${maybeJoin('#', hashformat(path))}`, + docstemplate: ({ domain, project, committish }) => `https://${domain}/${project}${maybeJoin('/', maybeEncode(committish))}`, + httpstemplate: ({ domain, project, committish }) => `git+https://${domain}/${project}.git${maybeJoin('#', committish)}`, + filetemplate: ({ user, project, committish, path }) => `https://gist.githubusercontent.com/${user}/${project}/raw${maybeJoin('/', maybeEncode(committish))}/${path}`, + shortcuttemplate: ({ type, project, committish }) => `${type}:${project}${maybeJoin('#', committish)}`, + pathtemplate: ({ project, committish }) => `${project}${maybeJoin('#', committish)}`, + bugstemplate: ({ domain, project }) => `https://${domain}/${project}`, + gittemplate: ({ domain, project, committish }) => `git://${domain}/${project}.git${maybeJoin('#', committish)}`, + tarballtemplate: ({ project, committish }) => `https://codeload.github.com/gist/${project}/tar.gz/${maybeEncode(committish) || 'master'}`, + extract: (url) => { + let [, user, project, aux] = url.pathname.split('/', 4) + if (aux === 'raw') { + return + } + + if (!project) { + if (!user) { + return + } + + project = user + user = null + } + + if (project.endsWith('.git')) { + project = project.slice(0, -4) + } + + return { user, project, committish: url.hash.slice(1) } + }, + hashformat: function (fragment) { + return fragment && 'file-' + formatHashFragment(fragment) + } +}) + +const names = Object.keys(gitHosts) +gitHosts.byShortcut = {} +gitHosts.byDomain = {} +for (const name of names) { + gitHosts.byShortcut[`${name}:`] = name + gitHosts.byDomain[gitHosts[name].domain] = name +} + +function formatHashFragment (fragment) { + return fragment.toLowerCase().replace(/^\W+|\/|\W+$/g, '').replace(/\W+/g, '-') +} + +module.exports = gitHosts diff --git a/node_modules/normalize-package-data/node_modules/hosted-git-info/git-host.js b/node_modules/normalize-package-data/node_modules/hosted-git-info/git-host.js new file mode 100644 index 0000000000000..8a975e92e58bb --- /dev/null +++ b/node_modules/normalize-package-data/node_modules/hosted-git-info/git-host.js @@ -0,0 +1,110 @@ +'use strict' +const gitHosts = require('./git-host-info.js') + +class GitHost { + constructor (type, user, auth, project, committish, defaultRepresentation, opts = {}) { + Object.assign(this, gitHosts[type]) + this.type = type + this.user = user + this.auth = auth + this.project = project + this.committish = committish + this.default = defaultRepresentation + this.opts = opts + } + + hash () { + return this.committish ? `#${this.committish}` : '' + } + + ssh (opts) { + return this._fill(this.sshtemplate, opts) + } + + _fill (template, opts) { + if (typeof template === 'function') { + const options = { ...this, ...this.opts, ...opts } + + // the path should always be set so we don't end up with 'undefined' in urls + if (!options.path) { + options.path = '' + } + + // template functions will insert the leading slash themselves + if (options.path.startsWith('/')) { + options.path = options.path.slice(1) + } + + if (options.noCommittish) { + options.committish = null + } + + const result = template(options) + return options.noGitPlus && result.startsWith('git+') ? result.slice(4) : result + } + + return null + } + + sshurl (opts) { + return this._fill(this.sshurltemplate, opts) + } + + browse (path, fragment, opts) { + // not a string, treat path as opts + if (typeof path !== 'string') { + return this._fill(this.browsetemplate, path) + } + + if (typeof fragment !== 'string') { + opts = fragment + fragment = null + } + return this._fill(this.browsefiletemplate, { ...opts, fragment, path }) + } + + docs (opts) { + return this._fill(this.docstemplate, opts) + } + + bugs (opts) { + return this._fill(this.bugstemplate, opts) + } + + https (opts) { + return this._fill(this.httpstemplate, opts) + } + + git (opts) { + return this._fill(this.gittemplate, opts) + } + + shortcut (opts) { + return this._fill(this.shortcuttemplate, opts) + } + + path (opts) { + return this._fill(this.pathtemplate, opts) + } + + tarball (opts) { + return this._fill(this.tarballtemplate, { ...opts, noCommittish: false }) + } + + file (path, opts) { + return this._fill(this.filetemplate, { ...opts, path }) + } + + getDefaultRepresentation () { + return this.default + } + + toString (opts) { + if (this.default && typeof this[this.default] === 'function') { + return this[this.default](opts) + } + + return this.sshurl(opts) + } +} +module.exports = GitHost diff --git a/node_modules/normalize-package-data/node_modules/hosted-git-info/index.js b/node_modules/normalize-package-data/node_modules/hosted-git-info/index.js new file mode 100644 index 0000000000000..f35c570c46b59 --- /dev/null +++ b/node_modules/normalize-package-data/node_modules/hosted-git-info/index.js @@ -0,0 +1,237 @@ +'use strict' +const url = require('url') +const gitHosts = require('./git-host-info.js') +const GitHost = module.exports = require('./git-host.js') +const LRU = require('lru-cache') +const cache = new LRU({ max: 1000 }) + +const protocolToRepresentationMap = { + 'git+ssh:': 'sshurl', + 'git+https:': 'https', + 'ssh:': 'sshurl', + 'git:': 'git' +} + +function protocolToRepresentation (protocol) { + return protocolToRepresentationMap[protocol] || protocol.slice(0, -1) +} + +const authProtocols = { + 'git:': true, + 'https:': true, + 'git+https:': true, + 'http:': true, + 'git+http:': true +} + +const knownProtocols = Object.keys(gitHosts.byShortcut).concat(['http:', 'https:', 'git:', 'git+ssh:', 'git+https:', 'ssh:']) + +module.exports.fromUrl = function (giturl, opts) { + if (typeof giturl !== 'string') { + return + } + + const key = giturl + JSON.stringify(opts || {}) + + if (!cache.has(key)) { + cache.set(key, fromUrl(giturl, opts)) + } + + return cache.get(key) +} + +function fromUrl (giturl, opts) { + if (!giturl) { + return + } + + const url = isGitHubShorthand(giturl) ? 'github:' + giturl : correctProtocol(giturl) + const parsed = parseGitUrl(url) + if (!parsed) { + return parsed + } + + const gitHostShortcut = gitHosts.byShortcut[parsed.protocol] + const gitHostDomain = gitHosts.byDomain[parsed.hostname.startsWith('www.') ? parsed.hostname.slice(4) : parsed.hostname] + const gitHostName = gitHostShortcut || gitHostDomain + if (!gitHostName) { + return + } + + const gitHostInfo = gitHosts[gitHostShortcut || gitHostDomain] + let auth = null + if (authProtocols[parsed.protocol] && (parsed.username || parsed.password)) { + auth = `${parsed.username}${parsed.password ? ':' + parsed.password : ''}` + } + + let committish = null + let user = null + let project = null + let defaultRepresentation = null + + try { + if (gitHostShortcut) { + let pathname = parsed.pathname.startsWith('/') ? parsed.pathname.slice(1) : parsed.pathname + const firstAt = pathname.indexOf('@') + // we ignore auth for shortcuts, so just trim it out + if (firstAt > -1) { + pathname = pathname.slice(firstAt + 1) + } + + const lastSlash = pathname.lastIndexOf('/') + if (lastSlash > -1) { + user = decodeURIComponent(pathname.slice(0, lastSlash)) + // we want nulls only, never empty strings + if (!user) { + user = null + } + project = decodeURIComponent(pathname.slice(lastSlash + 1)) + } else { + project = decodeURIComponent(pathname) + } + + if (project.endsWith('.git')) { + project = project.slice(0, -4) + } + + if (parsed.hash) { + committish = decodeURIComponent(parsed.hash.slice(1)) + } + + defaultRepresentation = 'shortcut' + } else { + if (!gitHostInfo.protocols.includes(parsed.protocol)) { + return + } + + const segments = gitHostInfo.extract(parsed) + if (!segments) { + return + } + + user = segments.user && decodeURIComponent(segments.user) + project = decodeURIComponent(segments.project) + committish = decodeURIComponent(segments.committish) + defaultRepresentation = protocolToRepresentation(parsed.protocol) + } + } catch (err) { + /* istanbul ignore else */ + if (err instanceof URIError) { + return + } else { + throw err + } + } + + return new GitHost(gitHostName, user, auth, project, committish, defaultRepresentation, opts) +} + +// accepts input like git:github.com:user/repo and inserts the // after the first : +const correctProtocol = (arg) => { + const firstColon = arg.indexOf(':') + const proto = arg.slice(0, firstColon + 1) + if (knownProtocols.includes(proto)) { + return arg + } + + const firstAt = arg.indexOf('@') + if (firstAt > -1) { + if (firstAt > firstColon) { + return `git+ssh://${arg}` + } else { + return arg + } + } + + const doubleSlash = arg.indexOf('//') + if (doubleSlash === firstColon + 1) { + return arg + } + + return arg.slice(0, firstColon + 1) + '//' + arg.slice(firstColon + 1) +} + +// look for github shorthand inputs, such as npm/cli +const isGitHubShorthand = (arg) => { + // it cannot contain whitespace before the first # + // it cannot start with a / because that's probably an absolute file path + // but it must include a slash since repos are username/repository + // it cannot start with a . because that's probably a relative file path + // it cannot start with an @ because that's a scoped package if it passes the other tests + // it cannot contain a : before a # because that tells us that there's a protocol + // a second / may not exist before a # + const firstHash = arg.indexOf('#') + const firstSlash = arg.indexOf('/') + const secondSlash = arg.indexOf('/', firstSlash + 1) + const firstColon = arg.indexOf(':') + const firstSpace = /\s/.exec(arg) + const firstAt = arg.indexOf('@') + + const spaceOnlyAfterHash = !firstSpace || (firstHash > -1 && firstSpace.index > firstHash) + const atOnlyAfterHash = firstAt === -1 || (firstHash > -1 && firstAt > firstHash) + const colonOnlyAfterHash = firstColon === -1 || (firstHash > -1 && firstColon > firstHash) + const secondSlashOnlyAfterHash = secondSlash === -1 || (firstHash > -1 && secondSlash > firstHash) + const hasSlash = firstSlash > 0 + // if a # is found, what we really want to know is that the character immediately before # is not a / + const doesNotEndWithSlash = firstHash > -1 ? arg[firstHash - 1] !== '/' : !arg.endsWith('/') + const doesNotStartWithDot = !arg.startsWith('.') + + return spaceOnlyAfterHash && hasSlash && doesNotEndWithSlash && doesNotStartWithDot && atOnlyAfterHash && colonOnlyAfterHash && secondSlashOnlyAfterHash +} + +// attempt to correct an scp style url so that it will parse with `new URL()` +const correctUrl = (giturl) => { + const firstAt = giturl.indexOf('@') + const lastHash = giturl.lastIndexOf('#') + let firstColon = giturl.indexOf(':') + let lastColon = giturl.lastIndexOf(':', lastHash > -1 ? lastHash : Infinity) + + let corrected + if (lastColon > firstAt) { + // the last : comes after the first @ (or there is no @) + // like it would in: + // proto://hostname.com:user/repo + // username@hostname.com:user/repo + // :password@hostname.com:user/repo + // username:password@hostname.com:user/repo + // proto://username@hostname.com:user/repo + // proto://:password@hostname.com:user/repo + // proto://username:password@hostname.com:user/repo + // then we replace the last : with a / to create a valid path + corrected = giturl.slice(0, lastColon) + '/' + giturl.slice(lastColon + 1) + // // and we find our new : positions + firstColon = corrected.indexOf(':') + lastColon = corrected.lastIndexOf(':') + } + + if (firstColon === -1 && giturl.indexOf('//') === -1) { + // we have no : at all + // as it would be in: + // username@hostname.com/user/repo + // then we prepend a protocol + corrected = `git+ssh://${corrected}` + } + + return corrected +} + +// try to parse the url as its given to us, if that throws +// then we try to clean the url and parse that result instead +// THIS FUNCTION SHOULD NEVER THROW +const parseGitUrl = (giturl) => { + let result + try { + result = new url.URL(giturl) + } catch (err) {} + + if (result) { + return result + } + + const correctedUrl = correctUrl(giturl) + try { + result = new url.URL(correctedUrl) + } catch (err) {} + + return result +} diff --git a/node_modules/normalize-package-data/node_modules/hosted-git-info/package.json b/node_modules/normalize-package-data/node_modules/hosted-git-info/package.json new file mode 100644 index 0000000000000..930e3b693b980 --- /dev/null +++ b/node_modules/normalize-package-data/node_modules/hosted-git-info/package.json @@ -0,0 +1,52 @@ +{ + "name": "hosted-git-info", + "version": "4.0.1", + "description": "Provides metadata and conversions from repository urls for GitHub, Bitbucket and GitLab", + "main": "index.js", + "repository": { + "type": "git", + "url": "git+https://github.com/npm/hosted-git-info.git" + }, + "keywords": [ + "git", + "github", + "bitbucket", + "gitlab" + ], + "author": "Rebecca Turner (http://re-becca.org)", + "license": "ISC", + "bugs": { + "url": "https://github.com/npm/hosted-git-info/issues" + }, + "homepage": "https://github.com/npm/hosted-git-info", + "scripts": { + "posttest": "standard", + "postversion": "npm publish", + "prepublishOnly": "git push origin --follow-tags", + "preversion": "npm test", + "snap": "tap", + "test": "tap", + "test:coverage": "tap --coverage-report=html" + }, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "devDependencies": { + "standard": "^16.0.3", + "standard-version": "^9.1.0", + "tap": "^14.11.0" + }, + "files": [ + "index.js", + "git-host.js", + "git-host-info.js" + ], + "engines": { + "node": ">=10" + }, + "tap": { + "color": 1, + "coverage": true, + "esm": false + } +} diff --git a/node_modules/normalize-package-data/package.json b/node_modules/normalize-package-data/package.json index 8df2f8fcac21e..09b184c19d563 100644 --- a/node_modules/normalize-package-data/package.json +++ b/node_modules/normalize-package-data/package.json @@ -1,6 +1,6 @@ { "name": "normalize-package-data", - "version": "3.0.0", + "version": "3.0.2", "author": "Meryn Stol ", "description": "Normalizes data that can be found in package.json files.", "license": "BSD-2-Clause", @@ -10,16 +10,19 @@ }, "main": "lib/normalize.js", "scripts": { + "postversion": "npm publish", + "prepublishOnly": "git push origin --follow-tags", + "preversion": "npm test", "test": "tap test/*.js" }, "dependencies": { - "hosted-git-info": "^3.0.6", - "resolve": "^1.17.0", - "semver": "^7.3.2", + "hosted-git-info": "^4.0.1", + "resolve": "^1.20.0", + "semver": "^7.3.4", "validate-npm-package-license": "^3.0.1" }, "devDependencies": { - "tap": "^14.10.8" + "tap": "^14.11.0" }, "files": [ "lib/*.js", diff --git a/package-lock.json b/package-lock.json index 56eb35d721ab8..617bf031ba9ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5481,20 +5481,32 @@ } }, "node_modules/normalize-package-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.0.tgz", - "integrity": "sha512-6lUjEI0d3v6kFrtgA/lOx4zHCWULXsFNIjHolnZCKCTLA6m/G625cdn3O7eNmT0iD3jfo6HZ9cdImGZwf21prw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.2.tgz", + "integrity": "sha512-6CdZocmfGaKnIHPVFhJJZ3GuR8SsLKvDANFp47Jmy51aKIr8akjAWTSxtpI+MBgBFdSMRyo4hMpDlT6dTffgZg==", "inBundle": true, "dependencies": { - "hosted-git-info": "^3.0.6", - "resolve": "^1.17.0", - "semver": "^7.3.2", + "hosted-git-info": "^4.0.1", + "resolve": "^1.20.0", + "semver": "^7.3.4", "validate-npm-package-license": "^3.0.1" }, "engines": { "node": ">=10" } }, + "node_modules/normalize-package-data/node_modules/hosted-git-info": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.1.tgz", + "integrity": "sha512-eT7NrxAsppPRQEBSwKSosReE+v8OzABwEScQYk5d4uxaEPlzxTIku7LINXtBGalthkLhJnq5lBI89PfK43zAKg==", + "inBundle": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -14464,14 +14476,24 @@ } }, "normalize-package-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.0.tgz", - "integrity": "sha512-6lUjEI0d3v6kFrtgA/lOx4zHCWULXsFNIjHolnZCKCTLA6m/G625cdn3O7eNmT0iD3jfo6HZ9cdImGZwf21prw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.2.tgz", + "integrity": "sha512-6CdZocmfGaKnIHPVFhJJZ3GuR8SsLKvDANFp47Jmy51aKIr8akjAWTSxtpI+MBgBFdSMRyo4hMpDlT6dTffgZg==", "requires": { - "hosted-git-info": "^3.0.6", - "resolve": "^1.17.0", - "semver": "^7.3.2", + "hosted-git-info": "^4.0.1", + "resolve": "^1.20.0", + "semver": "^7.3.4", "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "hosted-git-info": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.1.tgz", + "integrity": "sha512-eT7NrxAsppPRQEBSwKSosReE+v8OzABwEScQYk5d4uxaEPlzxTIku7LINXtBGalthkLhJnq5lBI89PfK43zAKg==", + "requires": { + "lru-cache": "^6.0.0" + } + } } }, "normalize-path": { From af7eaac5018ed821d72d43d08f1d7e49e7491453 Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Thu, 18 Mar 2021 13:15:50 -0400 Subject: [PATCH 03/42] hosted-git-info@4.0.1 --- node_modules/hosted-git-info/README.md | 4 +- node_modules/hosted-git-info/git-host-info.js | 211 ++++++++---- node_modules/hosted-git-info/git-host.js | 222 ++++++------- node_modules/hosted-git-info/index.js | 299 ++++++++++++------ node_modules/hosted-git-info/package.json | 26 +- package-lock.json | 14 +- package.json | 2 +- 7 files changed, 452 insertions(+), 326 deletions(-) diff --git a/node_modules/hosted-git-info/README.md b/node_modules/hosted-git-info/README.md index 7b723f6b9e213..8740406029626 100644 --- a/node_modules/hosted-git-info/README.md +++ b/node_modules/hosted-git-info/README.md @@ -22,7 +22,7 @@ var info = hostedGitInfo.fromUrl("git@github.com:npm/hosted-git-info.git", opts) If the URL can't be matched with a git host, `null` will be returned. We can match git, ssh and https urls. Additionally, we can match ssh connect strings (`git@github.com:npm/hosted-git-info`) and shortcuts (eg, -`github:npm/hosted-git-info`). Github specifically, is detected in the case +`github:npm/hosted-git-info`). GitHub specifically, is detected in the case of a third, unprefixed, form: `npm/hosted-git-info`. If it does match, the returned object has properties of: @@ -129,5 +129,5 @@ SSH connect strings will be normalized into `git+ssh` URLs. ## Supported hosts -Currently this supports Github, Bitbucket and Gitlab. Pull requests for +Currently this supports GitHub, Bitbucket and GitLab. Pull requests for additional hosts welcome. diff --git a/node_modules/hosted-git-info/git-host-info.js b/node_modules/hosted-git-info/git-host-info.js index da3348fa7b817..360d7b096be61 100644 --- a/node_modules/hosted-git-info/git-host-info.js +++ b/node_modules/hosted-git-info/git-host-info.js @@ -1,79 +1,154 @@ 'use strict' +const maybeJoin = (...args) => args.every(arg => arg) ? args.join('') : '' +const maybeEncode = (arg) => arg ? encodeURIComponent(arg) : '' -var gitHosts = module.exports = { - github: { - // First two are insecure and generally shouldn't be used any more, but - // they are still supported. - 'protocols': [ 'git', 'http', 'git+ssh', 'git+https', 'ssh', 'https' ], - 'domain': 'github.com', - 'treepath': 'tree', - 'filetemplate': 'https://{auth@}raw.githubusercontent.com/{user}/{project}/{committish}/{path}', - 'bugstemplate': 'https://{domain}/{user}/{project}/issues', - 'gittemplate': 'git://{auth@}{domain}/{user}/{project}.git{#committish}', - 'tarballtemplate': 'https://codeload.{domain}/{user}/{project}/tar.gz/{committish}' - }, - bitbucket: { - 'protocols': [ 'git+ssh', 'git+https', 'ssh', 'https' ], - 'domain': 'bitbucket.org', - 'treepath': 'src', - 'tarballtemplate': 'https://{domain}/{user}/{project}/get/{committish}.tar.gz' - }, - gitlab: { - 'protocols': [ 'git+ssh', 'git+https', 'ssh', 'https' ], - 'domain': 'gitlab.com', - 'treepath': 'tree', - 'bugstemplate': 'https://{domain}/{user}/{project}/issues', - 'httpstemplate': 'git+https://{auth@}{domain}/{user}/{projectPath}.git{#committish}', - 'tarballtemplate': 'https://{domain}/{user}/{project}/repository/archive.tar.gz?ref={committish}', - 'pathmatch': /^\/([^/]+)\/((?!.*(\/-\/|\/repository(\/[^/]+)?\/archive\.tar\.gz)).*?)(?:\.git|\/)?$/ - }, - gist: { - 'protocols': [ 'git', 'git+ssh', 'git+https', 'ssh', 'https' ], - 'domain': 'gist.github.com', - 'pathmatch': /^[/](?:([^/]+)[/])?([a-z0-9]{7,})(?:[.]git)?$/, - 'filetemplate': 'https://gist.githubusercontent.com/{user}/{project}/raw{/committish}/{path}', - 'bugstemplate': 'https://{domain}/{project}', - 'gittemplate': 'git://{domain}/{project}.git{#committish}', - 'sshtemplate': 'git@{domain}:/{project}.git{#committish}', - 'sshurltemplate': 'git+ssh://git@{domain}/{project}.git{#committish}', - 'browsetemplate': 'https://{domain}/{project}{/committish}', - 'browsefiletemplate': 'https://{domain}/{project}{/committish}{#path}', - 'docstemplate': 'https://{domain}/{project}{/committish}', - 'httpstemplate': 'git+https://{domain}/{project}.git{#committish}', - 'shortcuttemplate': '{type}:{project}{#committish}', - 'pathtemplate': '{project}{#committish}', - 'tarballtemplate': 'https://codeload.github.com/gist/{project}/tar.gz/{committish}', - 'hashformat': function (fragment) { - return 'file-' + formatHashFragment(fragment) +const defaults = { + sshtemplate: ({ domain, user, project, committish }) => `git@${domain}:${user}/${project}.git${maybeJoin('#', committish)}`, + sshurltemplate: ({ domain, user, project, committish }) => `git+ssh://git@${domain}/${user}/${project}.git${maybeJoin('#', committish)}`, + browsetemplate: ({ domain, user, project, committish, treepath }) => `https://${domain}/${user}/${project}${maybeJoin('/', treepath, '/', maybeEncode(committish))}`, + browsefiletemplate: ({ domain, user, project, committish, treepath, path, fragment, hashformat }) => `https://${domain}/${user}/${project}/${treepath}/${maybeEncode(committish || 'master')}/${path}${maybeJoin('#', hashformat(fragment || ''))}`, + docstemplate: ({ domain, user, project, treepath, committish }) => `https://${domain}/${user}/${project}${maybeJoin('/', treepath, '/', maybeEncode(committish))}#readme`, + httpstemplate: ({ auth, domain, user, project, committish }) => `git+https://${maybeJoin(auth, '@')}${domain}/${user}/${project}.git${maybeJoin('#', committish)}`, + filetemplate: ({ domain, user, project, committish, path }) => `https://${domain}/${user}/${project}/raw/${maybeEncode(committish) || 'master'}/${path}`, + shortcuttemplate: ({ type, user, project, committish }) => `${type}:${user}/${project}${maybeJoin('#', committish)}`, + pathtemplate: ({ user, project, committish }) => `${user}/${project}${maybeJoin('#', committish)}`, + bugstemplate: ({ domain, user, project }) => `https://${domain}/${user}/${project}/issues`, + hashformat: formatHashFragment +} + +const gitHosts = {} +gitHosts.github = Object.assign({}, defaults, { + // First two are insecure and generally shouldn't be used any more, but + // they are still supported. + protocols: ['git:', 'http:', 'git+ssh:', 'git+https:', 'ssh:', 'https:'], + domain: 'github.com', + treepath: 'tree', + filetemplate: ({ auth, user, project, committish, path }) => `https://${maybeJoin(auth, '@')}raw.githubusercontent.com/${user}/${project}/${maybeEncode(committish) || 'master'}/${path}`, + gittemplate: ({ auth, domain, user, project, committish }) => `git://${maybeJoin(auth, '@')}${domain}/${user}/${project}.git${maybeJoin('#', committish)}`, + tarballtemplate: ({ domain, user, project, committish }) => `https://codeload.${domain}/${user}/${project}/tar.gz/${maybeEncode(committish) || 'master'}`, + extract: (url) => { + let [, user, project, type, committish] = url.pathname.split('/', 5) + if (type && type !== 'tree') { + return + } + + if (!type) { + committish = url.hash.slice(1) + } + + if (project && project.endsWith('.git')) { + project = project.slice(0, -4) + } + + if (!user || !project) { + return } + + return { user, project, committish } } -} +}) -var gitHostDefaults = { - 'sshtemplate': 'git@{domain}:{user}/{project}.git{#committish}', - 'sshurltemplate': 'git+ssh://git@{domain}/{user}/{project}.git{#committish}', - 'browsetemplate': 'https://{domain}/{user}/{project}{/tree/committish}', - 'browsefiletemplate': 'https://{domain}/{user}/{project}/{treepath}/{committish}/{path}{#fragment}', - 'docstemplate': 'https://{domain}/{user}/{project}{/tree/committish}#readme', - 'httpstemplate': 'git+https://{auth@}{domain}/{user}/{project}.git{#committish}', - 'filetemplate': 'https://{domain}/{user}/{project}/raw/{committish}/{path}', - 'shortcuttemplate': '{type}:{user}/{project}{#committish}', - 'pathtemplate': '{user}/{project}{#committish}', - 'pathmatch': /^[/]([^/]+)[/]([^/]+?)(?:[.]git|[/])?$/, - 'hashformat': formatHashFragment -} +gitHosts.bitbucket = Object.assign({}, defaults, { + protocols: ['git+ssh:', 'git+https:', 'ssh:', 'https:'], + domain: 'bitbucket.org', + treepath: 'src', + tarballtemplate: ({ domain, user, project, committish }) => `https://${domain}/${user}/${project}/get/${maybeEncode(committish) || 'master'}.tar.gz`, + extract: (url) => { + let [, user, project, aux] = url.pathname.split('/', 4) + if (['get'].includes(aux)) { + return + } + + if (project && project.endsWith('.git')) { + project = project.slice(0, -4) + } + + if (!user || !project) { + return + } + + return { user, project, committish: url.hash.slice(1) } + } +}) + +gitHosts.gitlab = Object.assign({}, defaults, { + protocols: ['git+ssh:', 'git+https:', 'ssh:', 'https:'], + domain: 'gitlab.com', + treepath: 'tree', + httpstemplate: ({ auth, domain, user, project, committish }) => `git+https://${maybeJoin(auth, '@')}${domain}/${user}/${project}.git${maybeJoin('#', committish)}`, + tarballtemplate: ({ domain, user, project, committish }) => `https://${domain}/${user}/${project}/repository/archive.tar.gz?ref=${maybeEncode(committish) || 'master'}`, + extract: (url) => { + const path = url.pathname.slice(1) + if (path.includes('/-/')) { + return + } + + const segments = path.split('/') + let project = segments.pop() + if (project.endsWith('.git')) { + project = project.slice(0, -4) + } + + const user = segments.join('/') + if (!user || !project) { + return + } + + return { user, project, committish: url.hash.slice(1) } + } +}) + +gitHosts.gist = Object.assign({}, defaults, { + protocols: ['git:', 'git+ssh:', 'git+https:', 'ssh:', 'https:'], + domain: 'gist.github.com', + sshtemplate: ({ domain, project, committish }) => `git@${domain}:${project}.git${maybeJoin('#', committish)}`, + sshurltemplate: ({ domain, project, committish }) => `git+ssh://git@${domain}/${project}.git${maybeJoin('#', committish)}`, + browsetemplate: ({ domain, project, committish }) => `https://${domain}/${project}${maybeJoin('/', maybeEncode(committish))}`, + browsefiletemplate: ({ domain, project, committish, path, hashformat }) => `https://${domain}/${project}${maybeJoin('/', maybeEncode(committish))}${maybeJoin('#', hashformat(path))}`, + docstemplate: ({ domain, project, committish }) => `https://${domain}/${project}${maybeJoin('/', maybeEncode(committish))}`, + httpstemplate: ({ domain, project, committish }) => `git+https://${domain}/${project}.git${maybeJoin('#', committish)}`, + filetemplate: ({ user, project, committish, path }) => `https://gist.githubusercontent.com/${user}/${project}/raw${maybeJoin('/', maybeEncode(committish))}/${path}`, + shortcuttemplate: ({ type, project, committish }) => `${type}:${project}${maybeJoin('#', committish)}`, + pathtemplate: ({ project, committish }) => `${project}${maybeJoin('#', committish)}`, + bugstemplate: ({ domain, project }) => `https://${domain}/${project}`, + gittemplate: ({ domain, project, committish }) => `git://${domain}/${project}.git${maybeJoin('#', committish)}`, + tarballtemplate: ({ project, committish }) => `https://codeload.github.com/gist/${project}/tar.gz/${maybeEncode(committish) || 'master'}`, + extract: (url) => { + let [, user, project, aux] = url.pathname.split('/', 4) + if (aux === 'raw') { + return + } + + if (!project) { + if (!user) { + return + } + + project = user + user = null + } + + if (project.endsWith('.git')) { + project = project.slice(0, -4) + } -Object.keys(gitHosts).forEach(function (name) { - Object.keys(gitHostDefaults).forEach(function (key) { - if (gitHosts[name][key]) return - gitHosts[name][key] = gitHostDefaults[key] - }) - gitHosts[name].protocols_re = RegExp('^(' + - gitHosts[name].protocols.map(function (protocol) { - return protocol.replace(/([\\+*{}()[\]$^|])/g, '\\$1') - }).join('|') + '):$') + return { user, project, committish: url.hash.slice(1) } + }, + hashformat: function (fragment) { + return fragment && 'file-' + formatHashFragment(fragment) + } }) +const names = Object.keys(gitHosts) +gitHosts.byShortcut = {} +gitHosts.byDomain = {} +for (const name of names) { + gitHosts.byShortcut[`${name}:`] = name + gitHosts.byDomain[gitHosts[name].domain] = name +} + function formatHashFragment (fragment) { return fragment.toLowerCase().replace(/^\W+|\/|\W+$/g, '').replace(/\W+/g, '-') } + +module.exports = gitHosts diff --git a/node_modules/hosted-git-info/git-host.js b/node_modules/hosted-git-info/git-host.js index f9b1ec7456320..8a975e92e58bb 100644 --- a/node_modules/hosted-git-info/git-host.js +++ b/node_modules/hosted-git-info/git-host.js @@ -1,156 +1,110 @@ 'use strict' -var gitHosts = require('./git-host-info.js') -/* eslint-disable node/no-deprecated-api */ - -// copy-pasta util._extend from node's source, to avoid pulling -// the whole util module into peoples' webpack bundles. -/* istanbul ignore next */ -var extend = Object.assign || function _extend (target, source) { - // Don't do anything if source isn't an object - if (source === null || typeof source !== 'object') return target - - const keys = Object.keys(source) - let i = keys.length - while (i--) { - target[keys[i]] = source[keys[i]] - } - return target -} +const gitHosts = require('./git-host-info.js') + +class GitHost { + constructor (type, user, auth, project, committish, defaultRepresentation, opts = {}) { + Object.assign(this, gitHosts[type]) + this.type = type + this.user = user + this.auth = auth + this.project = project + this.committish = committish + this.default = defaultRepresentation + this.opts = opts + } -module.exports = GitHost -function GitHost (type, user, auth, project, committish, defaultRepresentation, opts) { - var gitHostInfo = this - gitHostInfo.type = type - Object.keys(gitHosts[type]).forEach(function (key) { - gitHostInfo[key] = gitHosts[type][key] - }) - gitHostInfo.user = user - gitHostInfo.auth = auth - gitHostInfo.project = project - gitHostInfo.committish = committish - gitHostInfo.default = defaultRepresentation - gitHostInfo.opts = opts || {} -} + hash () { + return this.committish ? `#${this.committish}` : '' + } -GitHost.prototype.hash = function () { - return this.committish ? '#' + this.committish : '' -} + ssh (opts) { + return this._fill(this.sshtemplate, opts) + } + + _fill (template, opts) { + if (typeof template === 'function') { + const options = { ...this, ...this.opts, ...opts } + + // the path should always be set so we don't end up with 'undefined' in urls + if (!options.path) { + options.path = '' + } + + // template functions will insert the leading slash themselves + if (options.path.startsWith('/')) { + options.path = options.path.slice(1) + } -GitHost.prototype._fill = function (template, opts) { - if (!template) return - var vars = extend({}, opts) - vars.path = vars.path ? vars.path.replace(/^[/]+/g, '') : '' - opts = extend(extend({}, this.opts), opts) - var self = this - Object.keys(this).forEach(function (key) { - if (self[key] != null && vars[key] == null) vars[key] = self[key] - }) - var rawAuth = vars.auth - var rawcommittish = vars.committish - var rawFragment = vars.fragment - var rawPath = vars.path - var rawProject = vars.project - Object.keys(vars).forEach(function (key) { - var value = vars[key] - if ((key === 'path' || key === 'project') && typeof value === 'string') { - vars[key] = value.split('/').map(function (pathComponent) { - return encodeURIComponent(pathComponent) - }).join('/') - } else if (key !== 'domain') { - vars[key] = encodeURIComponent(value) + if (options.noCommittish) { + options.committish = null + } + + const result = template(options) + return options.noGitPlus && result.startsWith('git+') ? result.slice(4) : result } - }) - vars['auth@'] = rawAuth ? rawAuth + '@' : '' - vars['#fragment'] = rawFragment ? '#' + this.hashformat(rawFragment) : '' - vars.fragment = vars.fragment ? vars.fragment : '' - vars['#path'] = rawPath ? '#' + this.hashformat(rawPath) : '' - vars['/path'] = vars.path ? '/' + vars.path : '' - vars.projectPath = rawProject.split('/').map(encodeURIComponent).join('/') - if (opts.noCommittish) { - vars['#committish'] = '' - vars['/tree/committish'] = '' - vars['/committish'] = '' - vars.committish = '' - } else { - vars['#committish'] = rawcommittish ? '#' + rawcommittish : '' - vars['/tree/committish'] = vars.committish - ? '/' + vars.treepath + '/' + vars.committish - : '' - vars['/committish'] = vars.committish ? '/' + vars.committish : '' - vars.committish = vars.committish || 'master' - } - var res = template - Object.keys(vars).forEach(function (key) { - res = res.replace(new RegExp('[{]' + key + '[}]', 'g'), vars[key]) - }) - if (opts.noGitPlus) { - return res.replace(/^git[+]/, '') - } else { - return res + + return null } -} -GitHost.prototype.ssh = function (opts) { - return this._fill(this.sshtemplate, opts) -} + sshurl (opts) { + return this._fill(this.sshurltemplate, opts) + } -GitHost.prototype.sshurl = function (opts) { - return this._fill(this.sshurltemplate, opts) -} + browse (path, fragment, opts) { + // not a string, treat path as opts + if (typeof path !== 'string') { + return this._fill(this.browsetemplate, path) + } -GitHost.prototype.browse = function (P, F, opts) { - if (typeof P === 'string') { - if (typeof F !== 'string') { - opts = F - F = null + if (typeof fragment !== 'string') { + opts = fragment + fragment = null } - return this._fill(this.browsefiletemplate, extend({ - fragment: F, - path: P - }, opts)) - } else { - return this._fill(this.browsetemplate, P) + return this._fill(this.browsefiletemplate, { ...opts, fragment, path }) } -} -GitHost.prototype.docs = function (opts) { - return this._fill(this.docstemplate, opts) -} + docs (opts) { + return this._fill(this.docstemplate, opts) + } -GitHost.prototype.bugs = function (opts) { - return this._fill(this.bugstemplate, opts) -} + bugs (opts) { + return this._fill(this.bugstemplate, opts) + } -GitHost.prototype.https = function (opts) { - return this._fill(this.httpstemplate, opts) -} + https (opts) { + return this._fill(this.httpstemplate, opts) + } -GitHost.prototype.git = function (opts) { - return this._fill(this.gittemplate, opts) -} + git (opts) { + return this._fill(this.gittemplate, opts) + } -GitHost.prototype.shortcut = function (opts) { - return this._fill(this.shortcuttemplate, opts) -} + shortcut (opts) { + return this._fill(this.shortcuttemplate, opts) + } -GitHost.prototype.path = function (opts) { - return this._fill(this.pathtemplate, opts) -} + path (opts) { + return this._fill(this.pathtemplate, opts) + } -GitHost.prototype.tarball = function (opts_) { - var opts = extend({}, opts_, { noCommittish: false }) - return this._fill(this.tarballtemplate, opts) -} + tarball (opts) { + return this._fill(this.tarballtemplate, { ...opts, noCommittish: false }) + } -GitHost.prototype.file = function (P, opts) { - return this._fill(this.filetemplate, extend({ path: P }, opts)) -} + file (path, opts) { + return this._fill(this.filetemplate, { ...opts, path }) + } -GitHost.prototype.getDefaultRepresentation = function () { - return this.default -} + getDefaultRepresentation () { + return this.default + } + + toString (opts) { + if (this.default && typeof this[this.default] === 'function') { + return this[this.default](opts) + } -GitHost.prototype.toString = function (opts) { - if (this.default && typeof this[this.default] === 'function') return this[this.default](opts) - return this.sshurl(opts) + return this.sshurl(opts) + } } +module.exports = GitHost diff --git a/node_modules/hosted-git-info/index.js b/node_modules/hosted-git-info/index.js index 8b3eaba3da7fb..f35c570c46b59 100644 --- a/node_modules/hosted-git-info/index.js +++ b/node_modules/hosted-git-info/index.js @@ -1,11 +1,11 @@ 'use strict' -var url = require('url') -var gitHosts = require('./git-host-info.js') -var GitHost = module.exports = require('./git-host.js') -var LRU = require('lru-cache') -var cache = new LRU({max: 1000}) +const url = require('url') +const gitHosts = require('./git-host-info.js') +const GitHost = module.exports = require('./git-host.js') +const LRU = require('lru-cache') +const cache = new LRU({ max: 1000 }) -var protocolToRepresentationMap = { +const protocolToRepresentationMap = { 'git+ssh:': 'sshurl', 'git+https:': 'https', 'ssh:': 'sshurl', @@ -16,7 +16,7 @@ function protocolToRepresentation (protocol) { return protocolToRepresentationMap[protocol] || protocol.slice(0, -1) } -var authProtocols = { +const authProtocols = { 'git:': true, 'https:': true, 'git+https:': true, @@ -24,9 +24,14 @@ var authProtocols = { 'git+http:': true } +const knownProtocols = Object.keys(gitHosts.byShortcut).concat(['http:', 'https:', 'git:', 'git+ssh:', 'git+https:', 'ssh:']) + module.exports.fromUrl = function (giturl, opts) { - if (typeof giturl !== 'string') return - var key = giturl + JSON.stringify(opts || {}) + if (typeof giturl !== 'string') { + return + } + + const key = giturl + JSON.stringify(opts || {}) if (!cache.has(key)) { cache.set(key, fromUrl(giturl, opts)) @@ -36,111 +41,197 @@ module.exports.fromUrl = function (giturl, opts) { } function fromUrl (giturl, opts) { - if (giturl == null || giturl === '') return - var url = fixupUnqualifiedGist( - isGitHubShorthand(giturl) ? 'github:' + giturl : giturl - ) - var parsed = parseGitUrl(url) - var shortcutMatch = url.match(/^([^:]+):(?:[^@]+@)?(?:([^/]*)\/)?([^#]+)/) - var matches = Object.keys(gitHosts).map(function (gitHostName) { - try { - var gitHostInfo = gitHosts[gitHostName] - var auth = null - if (parsed.auth && authProtocols[parsed.protocol]) { - auth = parsed.auth + if (!giturl) { + return + } + + const url = isGitHubShorthand(giturl) ? 'github:' + giturl : correctProtocol(giturl) + const parsed = parseGitUrl(url) + if (!parsed) { + return parsed + } + + const gitHostShortcut = gitHosts.byShortcut[parsed.protocol] + const gitHostDomain = gitHosts.byDomain[parsed.hostname.startsWith('www.') ? parsed.hostname.slice(4) : parsed.hostname] + const gitHostName = gitHostShortcut || gitHostDomain + if (!gitHostName) { + return + } + + const gitHostInfo = gitHosts[gitHostShortcut || gitHostDomain] + let auth = null + if (authProtocols[parsed.protocol] && (parsed.username || parsed.password)) { + auth = `${parsed.username}${parsed.password ? ':' + parsed.password : ''}` + } + + let committish = null + let user = null + let project = null + let defaultRepresentation = null + + try { + if (gitHostShortcut) { + let pathname = parsed.pathname.startsWith('/') ? parsed.pathname.slice(1) : parsed.pathname + const firstAt = pathname.indexOf('@') + // we ignore auth for shortcuts, so just trim it out + if (firstAt > -1) { + pathname = pathname.slice(firstAt + 1) } - var committish = parsed.hash ? decodeURIComponent(parsed.hash.substr(1)) : null - var user = null - var project = null - var defaultRepresentation = null - if (shortcutMatch && shortcutMatch[1] === gitHostName) { - user = shortcutMatch[2] && decodeURIComponent(shortcutMatch[2]) - project = decodeURIComponent(shortcutMatch[3].replace(/\.git$/, '')) - defaultRepresentation = 'shortcut' - } else { - if (parsed.host && parsed.host !== gitHostInfo.domain && parsed.host.replace(/^www[.]/, '') !== gitHostInfo.domain) return - if (!gitHostInfo.protocols_re.test(parsed.protocol)) return - if (!parsed.path) return - var pathmatch = gitHostInfo.pathmatch - var matched = parsed.path.match(pathmatch) - if (!matched) return - /* istanbul ignore else */ - if (matched[1] !== null && matched[1] !== undefined) { - user = decodeURIComponent(matched[1].replace(/^:/, '')) + + const lastSlash = pathname.lastIndexOf('/') + if (lastSlash > -1) { + user = decodeURIComponent(pathname.slice(0, lastSlash)) + // we want nulls only, never empty strings + if (!user) { + user = null } - project = decodeURIComponent(matched[2]) - defaultRepresentation = protocolToRepresentation(parsed.protocol) + project = decodeURIComponent(pathname.slice(lastSlash + 1)) + } else { + project = decodeURIComponent(pathname) + } + + if (project.endsWith('.git')) { + project = project.slice(0, -4) + } + + if (parsed.hash) { + committish = decodeURIComponent(parsed.hash.slice(1)) + } + + defaultRepresentation = 'shortcut' + } else { + if (!gitHostInfo.protocols.includes(parsed.protocol)) { + return } - return new GitHost(gitHostName, user, auth, project, committish, defaultRepresentation, opts) - } catch (ex) { - /* istanbul ignore else */ - if (ex instanceof URIError) { - } else throw ex + + const segments = gitHostInfo.extract(parsed) + if (!segments) { + return + } + + user = segments.user && decodeURIComponent(segments.user) + project = decodeURIComponent(segments.project) + committish = decodeURIComponent(segments.committish) + defaultRepresentation = protocolToRepresentation(parsed.protocol) } - }).filter(function (gitHostInfo) { return gitHostInfo }) - if (matches.length !== 1) return - return matches[0] -} + } catch (err) { + /* istanbul ignore else */ + if (err instanceof URIError) { + return + } else { + throw err + } + } -function isGitHubShorthand (arg) { - // Note: This does not fully test the git ref format. - // See https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html - // - // The only way to do this properly would be to shell out to - // git-check-ref-format, and as this is a fast sync function, - // we don't want to do that. Just let git fail if it turns - // out that the commit-ish is invalid. - // GH usernames cannot start with . or - - return /^[^:@%/\s.-][^:@%/\s]*[/][^:@\s/%]+(?:#.*)?$/.test(arg) + return new GitHost(gitHostName, user, auth, project, committish, defaultRepresentation, opts) } -function fixupUnqualifiedGist (giturl) { - // necessary for round-tripping gists - var parsed = url.parse(giturl) - if (parsed.protocol === 'gist:' && parsed.host && !parsed.path) { - return parsed.protocol + '/' + parsed.host - } else { - return giturl +// accepts input like git:github.com:user/repo and inserts the // after the first : +const correctProtocol = (arg) => { + const firstColon = arg.indexOf(':') + const proto = arg.slice(0, firstColon + 1) + if (knownProtocols.includes(proto)) { + return arg } -} -function parseGitUrl (giturl) { - var matched = giturl.match(/^([^@]+)@([^:/]+):[/]?((?:[^/]+[/])?[^/]+?)(?:[.]git)?(#.*)?$/) - if (!matched) { - var legacy = url.parse(giturl) - if (legacy.auth) { - // git urls can be in the form of scp-style/ssh-connect strings, like - // git+ssh://user@host.com:some/path, which the legacy url parser - // supports, but WhatWG url.URL class does not. However, the legacy - // parser de-urlencodes the username and password, so something like - // https://user%3An%40me:p%40ss%3Aword@x.com/ becomes - // https://user:n@me:p@ss:word@x.com/ which is all kinds of wrong. - // Pull off just the auth and host, so we dont' get the confusing - // scp-style URL, then pass that to the WhatWG parser to get the - // auth properly escaped. - const authmatch = giturl.match(/[^@]+@[^:/]+/) - /* istanbul ignore else - this should be impossible */ - if (authmatch) { - var whatwg = new url.URL(authmatch[0]) - legacy.auth = whatwg.username || '' - if (whatwg.password) legacy.auth += ':' + whatwg.password - } + const firstAt = arg.indexOf('@') + if (firstAt > -1) { + if (firstAt > firstColon) { + return `git+ssh://${arg}` + } else { + return arg } - return legacy } - return { - protocol: 'git+ssh:', - slashes: true, - auth: matched[1], - host: matched[2], - port: null, - hostname: matched[2], - hash: matched[4], - search: null, - query: null, - pathname: '/' + matched[3], - path: '/' + matched[3], - href: 'git+ssh://' + matched[1] + '@' + matched[2] + - '/' + matched[3] + (matched[4] || '') + + const doubleSlash = arg.indexOf('//') + if (doubleSlash === firstColon + 1) { + return arg + } + + return arg.slice(0, firstColon + 1) + '//' + arg.slice(firstColon + 1) +} + +// look for github shorthand inputs, such as npm/cli +const isGitHubShorthand = (arg) => { + // it cannot contain whitespace before the first # + // it cannot start with a / because that's probably an absolute file path + // but it must include a slash since repos are username/repository + // it cannot start with a . because that's probably a relative file path + // it cannot start with an @ because that's a scoped package if it passes the other tests + // it cannot contain a : before a # because that tells us that there's a protocol + // a second / may not exist before a # + const firstHash = arg.indexOf('#') + const firstSlash = arg.indexOf('/') + const secondSlash = arg.indexOf('/', firstSlash + 1) + const firstColon = arg.indexOf(':') + const firstSpace = /\s/.exec(arg) + const firstAt = arg.indexOf('@') + + const spaceOnlyAfterHash = !firstSpace || (firstHash > -1 && firstSpace.index > firstHash) + const atOnlyAfterHash = firstAt === -1 || (firstHash > -1 && firstAt > firstHash) + const colonOnlyAfterHash = firstColon === -1 || (firstHash > -1 && firstColon > firstHash) + const secondSlashOnlyAfterHash = secondSlash === -1 || (firstHash > -1 && secondSlash > firstHash) + const hasSlash = firstSlash > 0 + // if a # is found, what we really want to know is that the character immediately before # is not a / + const doesNotEndWithSlash = firstHash > -1 ? arg[firstHash - 1] !== '/' : !arg.endsWith('/') + const doesNotStartWithDot = !arg.startsWith('.') + + return spaceOnlyAfterHash && hasSlash && doesNotEndWithSlash && doesNotStartWithDot && atOnlyAfterHash && colonOnlyAfterHash && secondSlashOnlyAfterHash +} + +// attempt to correct an scp style url so that it will parse with `new URL()` +const correctUrl = (giturl) => { + const firstAt = giturl.indexOf('@') + const lastHash = giturl.lastIndexOf('#') + let firstColon = giturl.indexOf(':') + let lastColon = giturl.lastIndexOf(':', lastHash > -1 ? lastHash : Infinity) + + let corrected + if (lastColon > firstAt) { + // the last : comes after the first @ (or there is no @) + // like it would in: + // proto://hostname.com:user/repo + // username@hostname.com:user/repo + // :password@hostname.com:user/repo + // username:password@hostname.com:user/repo + // proto://username@hostname.com:user/repo + // proto://:password@hostname.com:user/repo + // proto://username:password@hostname.com:user/repo + // then we replace the last : with a / to create a valid path + corrected = giturl.slice(0, lastColon) + '/' + giturl.slice(lastColon + 1) + // // and we find our new : positions + firstColon = corrected.indexOf(':') + lastColon = corrected.lastIndexOf(':') + } + + if (firstColon === -1 && giturl.indexOf('//') === -1) { + // we have no : at all + // as it would be in: + // username@hostname.com/user/repo + // then we prepend a protocol + corrected = `git+ssh://${corrected}` + } + + return corrected +} + +// try to parse the url as its given to us, if that throws +// then we try to clean the url and parse that result instead +// THIS FUNCTION SHOULD NEVER THROW +const parseGitUrl = (giturl) => { + let result + try { + result = new url.URL(giturl) + } catch (err) {} + + if (result) { + return result } + + const correctedUrl = correctUrl(giturl) + try { + result = new url.URL(correctedUrl) + } catch (err) {} + + return result } diff --git a/node_modules/hosted-git-info/package.json b/node_modules/hosted-git-info/package.json index 32712269f0427..930e3b693b980 100644 --- a/node_modules/hosted-git-info/package.json +++ b/node_modules/hosted-git-info/package.json @@ -1,7 +1,7 @@ { "name": "hosted-git-info", - "version": "3.0.8", - "description": "Provides metadata and conversions from repository urls for Github, Bitbucket and Gitlab", + "version": "4.0.1", + "description": "Provides metadata and conversions from repository urls for GitHub, Bitbucket and GitLab", "main": "index.js", "repository": { "type": "git", @@ -20,20 +20,21 @@ }, "homepage": "https://github.com/npm/hosted-git-info", "scripts": { - "prerelease": "npm t", - "postrelease": "npm publish && git push --follow-tags", "posttest": "standard", - "release": "standard-version -s", - "test:coverage": "tap --coverage-report=html -J --100 --no-esm test/*.js", - "test": "tap -J --100 --no-esm test/*.js" + "postversion": "npm publish", + "prepublishOnly": "git push origin --follow-tags", + "preversion": "npm test", + "snap": "tap", + "test": "tap", + "test:coverage": "tap --coverage-report=html" }, "dependencies": { "lru-cache": "^6.0.0" }, "devDependencies": { - "standard": "^11.0.1", - "standard-version": "^4.4.0", - "tap": "^12.7.0" + "standard": "^16.0.3", + "standard-version": "^9.1.0", + "tap": "^14.11.0" }, "files": [ "index.js", @@ -42,5 +43,10 @@ ], "engines": { "node": ">=10" + }, + "tap": { + "color": 1, + "coverage": true, + "esm": false } } diff --git a/package-lock.json b/package-lock.json index 617bf031ba9ec..0ba6c23bb5867 100644 --- a/package-lock.json +++ b/package-lock.json @@ -269,7 +269,7 @@ "columnify": "~1.5.4", "glob": "^7.1.4", "graceful-fs": "^4.2.6", - "hosted-git-info": "^3.0.8", + "hosted-git-info": "^4.0.1", "ini": "^2.0.0", "init-package-json": "^2.0.2", "is-cidr": "^4.0.2", @@ -3763,9 +3763,9 @@ } }, "node_modules/hosted-git-info": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz", - "integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.1.tgz", + "integrity": "sha512-eT7NrxAsppPRQEBSwKSosReE+v8OzABwEScQYk5d4uxaEPlzxTIku7LINXtBGalthkLhJnq5lBI89PfK43zAKg==", "inBundle": true, "dependencies": { "lru-cache": "^6.0.0" @@ -13222,9 +13222,9 @@ } }, "hosted-git-info": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz", - "integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.1.tgz", + "integrity": "sha512-eT7NrxAsppPRQEBSwKSosReE+v8OzABwEScQYk5d4uxaEPlzxTIku7LINXtBGalthkLhJnq5lBI89PfK43zAKg==", "requires": { "lru-cache": "^6.0.0" } diff --git a/package.json b/package.json index 26a502586caaa..874a48fcd988b 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "columnify": "~1.5.4", "glob": "^7.1.4", "graceful-fs": "^4.2.6", - "hosted-git-info": "^3.0.8", + "hosted-git-info": "^4.0.1", "ini": "^2.0.0", "init-package-json": "^2.0.2", "is-cidr": "^4.0.2", From 91662305643509eebd2f79ed7e3ff01562aa4968 Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Thu, 18 Mar 2021 13:16:51 -0400 Subject: [PATCH 04/42] @npmcli/run-script@1.8.4 --- .../@npmcli/run-script/lib/make-spawn-args.js | 20 +- node_modules/@npmcli/run-script/package.json | 3 +- node_modules/puka/CHANGELOG.md | 31 - node_modules/puka/LICENSE.txt | 18 - node_modules/puka/README.md | 411 --------- node_modules/puka/index.js | 804 ------------------ node_modules/puka/package.json | 38 - package-lock.json | 31 +- package.json | 2 +- 9 files changed, 10 insertions(+), 1348 deletions(-) delete mode 100644 node_modules/puka/CHANGELOG.md delete mode 100644 node_modules/puka/LICENSE.txt delete mode 100644 node_modules/puka/README.md delete mode 100644 node_modules/puka/index.js delete mode 100644 node_modules/puka/package.json diff --git a/node_modules/@npmcli/run-script/lib/make-spawn-args.js b/node_modules/@npmcli/run-script/lib/make-spawn-args.js index 4c38b9401ddf0..8ee24c06daf7b 100644 --- a/node_modules/@npmcli/run-script/lib/make-spawn-args.js +++ b/node_modules/@npmcli/run-script/lib/make-spawn-args.js @@ -3,24 +3,6 @@ const isWindows = require('./is-windows.js') const setPATH = require('./set-path.js') const {resolve} = require('path') const npm_config_node_gyp = require.resolve('node-gyp/bin/node-gyp.js') -const { quoteForShell, ShellString, ShellStringText, ShellStringUnquoted } = require('puka') - -const escapeCmd = cmd => { - const result = [] - const parsed = ShellString.sh([cmd]) - for (const child of parsed.children) { - if (child instanceof ShellStringText) { - const children = child.contents.filter(segment => segment !== null).map(segment => quoteForShell(segment, false, isWindows && 'win32')) - result.push(...children) - } else if (child instanceof ShellStringUnquoted) { - result.push(child.value) - } else { - result.push(isWindows ? '&' : ';') - } - } - - return result.join('') -} const makeSpawnArgs = options => { const { @@ -34,7 +16,7 @@ const makeSpawnArgs = options => { } = options const isCmd = /(?:^|\\)cmd(?:\.exe)?$/i.test(scriptShell) - const args = isCmd ? ['/d', '/s', '/c', escapeCmd(cmd)] : ['-c', cmd] + const args = isCmd ? ['/d', '/s', '/c', cmd] : ['-c', cmd] const spawnOpts = { env: setPATH(path, { diff --git a/node_modules/@npmcli/run-script/package.json b/node_modules/@npmcli/run-script/package.json index 9df5b31178747..7e0e5255de410 100644 --- a/node_modules/@npmcli/run-script/package.json +++ b/node_modules/@npmcli/run-script/package.json @@ -1,6 +1,6 @@ { "name": "@npmcli/run-script", - "version": "1.8.3", + "version": "1.8.4", "description": "Run a lifecycle script for a package (descendant of npm-lifecycle)", "author": "Isaac Z. Schlueter (https://izs.me)", "license": "ISC", @@ -32,7 +32,6 @@ "@npmcli/promise-spawn": "^1.3.2", "infer-owner": "^1.0.4", "node-gyp": "^7.1.0", - "puka": "^1.0.1", "read-package-json-fast": "^2.0.1" }, "files": [ diff --git a/node_modules/puka/CHANGELOG.md b/node_modules/puka/CHANGELOG.md deleted file mode 100644 index 781b81295a4a7..0000000000000 --- a/node_modules/puka/CHANGELOG.md +++ /dev/null @@ -1,31 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [1.0.1](https://gitlab.com/rhendric/puka/-/compare/v1.0.0...v1.0.1) - 2020-05-16 - -### Fixed - -- Add more carets to win32 command arguments ([45965ca](https://gitlab.com/rhendric/puka/-/commit/45965ca60fcc518082e0b085d8e81f3f3279ffb4)) - - As previously documented and implemented, Puka assumed that all programs - are batch files for the purpose of multi-escaping commands that appear - in pipelines. However, regardless of whether a command is in a pipeline, - one extra layer of escaping is needed if the command invokes a batch - file, which Puka was not producing. This only applies to the arguments - to the command, not to the batch file path, nor to paths used in - redirects. (The property-based spawn test which was supposed to catch - such oversights missed this one because it was invoking the Node.js - executable directly, not, as recommended in the documentation, a batch - file.) - - Going forward, the caveats described in the documentation continue to - apply: if you are running programs on Windows with Puka, make sure they - are batch files, or you may find arguments are being escaped with too - many carets. As the documentation says, if this causes problems for you, - please open an issue so we can work out the details of what a good - workaround looks like. - -## [1.0.0](https://gitlab.com/rhendric/puka/-/tags/v1.0.0) - 2017-09-29 diff --git a/node_modules/puka/LICENSE.txt b/node_modules/puka/LICENSE.txt deleted file mode 100644 index 0141196a59337..0000000000000 --- a/node_modules/puka/LICENSE.txt +++ /dev/null @@ -1,18 +0,0 @@ -Copyright 2017 Ryan Hendrickson - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/puka/README.md b/node_modules/puka/README.md deleted file mode 100644 index 2670f742b3677..0000000000000 --- a/node_modules/puka/README.md +++ /dev/null @@ -1,411 +0,0 @@ -# Puka - -[![GitLab CI pipeline status](https://gitlab.com/rhendric/puka/badges/master/pipeline.svg)](https://gitlab.com/rhendric/puka/commits/master) [![AppVeyor build status](https://img.shields.io/appveyor/ci/rhendric/puka.svg?label=windows%20tests)](https://ci.appveyor.com/project/rhendric/puka) [![Codecov status](https://img.shields.io/codecov/c/gl/rhendric/puka.svg)](https://codecov.io/gl/rhendric/puka) - -Puka is a cross-platform library for safely passing strings through shells. - -#### Contents - -- [Introduction](#introduction) - - [Why would I use Puka?](#why-would-i-use-puka) - - [How do I use Puka?](#how-do-i-use-puka) - - [What's the catch?](#whats-the-catch) -- [API Documentation](#api-documentation) - - [Basic API](#basic-api) - - [sh](#sh) - - [unquoted](#unquoted) - - [Advanced API](#advanced-api) - - [quoteForShell](#quoteforshell) - - [quoteForCmd](#quoteforcmd) - - [quoteForSh](#quoteforsh) - - [ShellString](#shellstring) - - [Secret API](#secret-api) -- [The sh DSL](#the-sh-dsl) - - [Syntax](#syntax) - - [Semantics](#semantics) - - [Types of placeholders](#types-of-placeholders) - -## Introduction - -### Why would I use Puka? - -When launching a child process from Node, you have a choice between launching -directly from the operating system (as with [child_process.spawn](https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options), -if you don't use the `{ shell: true }` option), and running the command through -a shell (as with [child_process.exec](https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback)). -Using a shell gives you more power, such as the ability to chain multiple -commands together or use redirection, but you have to construct your command as -a single string instead of using an array of arguments. And doing that can be -buggy (if not dangerous) if you don't take care to quote any arguments -correctly for the shell you're targeting, _and_ the quoting has to be done -differently on Windows and non-Windows shells. - -Puka solves that problem by giving you a simple and platform-agnostic way to -build shell commands with arguments that pass through your shell unaltered and -with no unsafe side effects, **whether you are running on Windows or a -Unix-based OS**. - -### How do I use Puka? - -Puka gives you an `sh` function intended for tagging -[template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals), -which quotes (if necessary) any values interpolated into the template. A simple -example: - -```javascript -const { sh } = require('puka'); -const { execSync } = require('child_process'); - -const arg = 'file with spaces.txt'; -execSync(sh`some-command ${arg}`); -``` - -But Puka supports more than this! See [the `sh` DSL documentation](#the-sh-dsl) -for a detailed description of all the features currently supported. - -### What's the catch? - -Here are the ones I know about: - -Puka does _not_ ensure that the actual commands you're running are -cross-platform. If you're running npm programs, you generally won't have a -problem with that, but if you want to run ``sh`cat file` `` on Windows, you'll -need to depend on something like -[cash-cat](https://www.npmjs.com/package/cash-cat). - -I searched for days for a way to quote or escape line breaks in arguments to -`cmd.exe`, but couldn't find one (regular `^`-prepending and quotation marks -don't seem to cut it). If you know of a way that works, please [open an -issue](https://gitlab.com/rhendric/puka/issues/new) to tell me about it! Until -then, any line break characters (`\r` or `\n`) in values being interpolated by -`sh` will cause an error to be thrown on Windows only. - -Also on Windows, you may notice quoting mistakes if you run commands that -involve invoking a native executable (not a batch file ending in `.cmd` or -`.bat`). Unfortunately, batch files require some extra escaping on Windows, and -Puka assumes all programs are batch files because npm creates batch file shims -for programs it installs (and, if you care about cross-platform, you'll be -using npm programs in your commands). If this causes problems for you, please -[open an issue](https://gitlab.com/rhendric/puka/issues/new); if your situation -is specific enough, there may be workarounds or improvements to Puka to be -found. - -## API Documentation - -### Basic API - - - - -#### sh - -A string template tag for safely constructing cross-platform shell commands. - -An `sh` template is not actually treated as a literal string to be -interpolated; instead, it is a tiny DSL designed to make working with shell -strings safe, simple, and straightforward. To get started quickly, see the -examples below. [More detailed documentation][1] is available -further down. - -##### Examples - -```javascript -const title = '"this" & "that"'; -sh`script --title=${title}`; // => "script '--title=\"this\" & \"that\"'" -// Note: these examples show results for non-Windows platforms. -// On Windows, the above would instead be -// 'script ^^^"--title=\\^^^"this\\^^^" ^^^& \\^^^"that\\^^^"^^^"'. - -const names = ['file1', 'file 2']; -sh`rimraf ${names}.txt`; // => "rimraf file1.txt 'file 2.txt'" - -const cmd1 = ['cat', 'file 1.txt', 'file 2.txt']; -const cmd2 = ['use-input', '-abc']; -sh`${cmd1}|${cmd2}`; // => "cat 'file 1.txt' 'file 2.txt'|use-input -abc" -``` - -Returns **[String][2]** a string formatted for the platform Node is currently -running on. - -#### unquoted - -This function permits raw strings to be interpolated into a `sh` template. - -**IMPORTANT**: If you're using Puka due to security concerns, make sure you -don't pass any untrusted content to `unquoted`. This may be obvious, but -stray punctuation in an `unquoted` section can compromise the safety of the -entire shell command. - -##### Parameters - -- `value` any value (it will be treated as a string) - -##### Examples - -```javascript -const both = true; -sh`foo ${unquoted(both ? '&&' : '||')} bar`; // => 'foo && bar' -``` - -### Advanced API - -If these functions make life easier for you, go ahead and use them; they -are just as well supported as the above. But if you aren't certain you -need them, you probably don't. - - -#### quoteForShell - -Quotes a string for injecting into a shell command. - -This function is exposed for some hypothetical case when the `sh` DSL simply -won't do; `sh` is expected to be the more convenient option almost always. -Compare: - -```javascript -console.log('cmd' + args.map(a => ' ' + quoteForShell(a)).join('')); -console.log(sh`cmd ${args}`); // same as above - -console.log('cmd' + args.map(a => ' ' + quoteForShell(a, true)).join('')); -console.log(sh`cmd "${args}"`); // same as above -``` - -Additionally, on Windows, `sh` checks the entire command string for pipes, -which subtly change how arguments need to be quoted. If your commands may -involve pipes, you are strongly encouraged to use `sh` and not try to roll -your own with `quoteForShell`. - -##### Parameters - -- `text` **[String][2]** to be quoted -- `forceQuote` **[Boolean][3]?** whether to always add quotes even if the string - is already safe. Defaults to `false`. -- `platform` **[String][2]?** a value that `process.platform` might take: - `'win32'`, `'linux'`, etc.; determines how the string is to be formatted. - When omitted, effectively the same as `process.platform`. - -Returns **[String][2]** a string that is safe for the current (or specified) -platform. - -#### quoteForCmd - -A Windows-specific version of [quoteForShell][4]. - -##### Parameters - -- `text` **[String][2]** to be quoted -- `forceQuote` **[Boolean][3]?** whether to always add quotes even if the string - is already safe. Defaults to `false`. - -#### quoteForSh - -A Unix-specific version of [quoteForShell][4]. - -##### Parameters - -- `text` **[String][2]** to be quoted -- `forceQuote` **[Boolean][3]?** whether to always add quotes even if the string - is already safe. Defaults to `false`. - -#### ShellString - -A ShellString represents a shell command after it has been interpolated, but -before it has been formatted for a particular platform. ShellStrings are -useful if you want to prepare a command for a different platform than the -current one, for instance. - -To create a ShellString, use `ShellString.sh` the same way you would use -top-level `sh`. - -##### toString - -A method to format a ShellString into a regular String formatted for a -particular platform. - -###### Parameters - -- `platform` **[String][2]?** a value that `process.platform` might take: - `'win32'`, `'linux'`, etc.; determines how the string is to be formatted. - When omitted, effectively the same as `process.platform`. - -Returns **[String][2]** - -##### sh - -`ShellString.sh` is a template tag just like `sh`; the only difference is -that this function returns a ShellString which has not yet been formatted -into a String. - -Returns **[ShellString][5]** - -### Secret API - -Some internals of string formatting have been exposed for the ambitious and -brave souls who want to try to extend Puka to handle more shells or custom -interpolated values. This ‘secret’ API is partially documented in the code -but not here, and the semantic versioning guarantees on this API are bumped -down by one level: in other words, minor version releases of Puka can change -the secret API in backward-incompatible ways, and patch releases can add or -deprecate functionality. - -If it's not even documented in the code, use at your own risk—no semver -guarantees apply. - - -[1]: #the-sh-dsl - -[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String - -[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean - -[4]: #quoteforshell - -[5]: #shellstring - -## The sh DSL - -### Syntax - -An `sh` template comprises words, separated by whitespace. Words can contain: - -- text, which is composed of any characters that are not whitespace, single or - double quotes, or any of the special characters - ``# $ & ( ) ; < > \ ` |``; -- quotations, which are matching single or double quotes surrounding any - characters other than the delimiting quote; and -- placeholders, using the standard JavaScript template syntax (`${}`). - (Placeholders may also appear inside quotations.) - -The special characters ``# $ & ( ) ; < > \ ` |``, if unquoted, form their own -words. - -Redirect operators (`<`, `>`, `>>`, `2>`, etc.) receive their own special -handling, as do semicolons. Other than these two exceptions, no attempt is made -to understand any more sophisticated features of shell syntax. - -Standard JavaScript escape sequences, such as `\t`, are honored in the template -literal, and are treated equivalently to the characters they represent. There -is no further mechanism for escaping within the `sh` DSL itself; in particular, -if you want to put quotes inside quotes, you have to use interpolation, like -this: - -```javascript -sh`echo "${'single = \', double = "'}"` // => "echo 'single = '\\'', double = \"'" -``` - -### Semantics - -Words that do not contain placeholders are emitted mostly verbatim to the -output string. Quotations are formatted in the expected style for the target -platform (single quotes for Unix, double quotes for Windows) regardless of the -quotes used in the template literal—as with JavaScript, single and double quotes -are interchangeable, except for the requirement to pair like with like. Unquoted -semicolons are translated to ampersands on Windows; all other special characters -(as enumerated above), when unquoted, are passed as-is to the output for the -shell to interpret. - -Puka may still quote words not containing the above special characters, if they -contain characters that need quoting on the target platform. For example, on -Windows, the character `%` is used for variable interpolation in `cmd.exe`, and -Puka quotes it on on that platform even if it appears unquoted in the template -literal. Consequently, there is no need to be paranoid about quoting anything -that doesn't look alphanumeric inside a `sh` template literal, for fear of being -burned on a different operating system; anything that matches the definition of -‘text’ above will never need manual quoting. - -#### Types of placeholders - -##### Strings - -If a word contains a string placeholder, then the value of the placeholder is -interpolated into the word and the entire word, if necessary, is quoted. If -the placeholder occurs within quotes, no further quoting is performed: - -```javascript -sh`script --file="${'herp derp'}.txt"`; // => "script --file='herp derp.txt'" -``` - -This behavior can be exploited to force consistent quoting, if desired; but -both of the examples below are safe on all platforms: - -```javascript -const words = ['oneword', 'two words']; -sh`minimal ${words[0]}`; // => "minimal oneword" -sh`minimal ${words[1]}`; // => "minimal 'two words'" -sh`consistent '${words[0]}'`; // => "consistent 'oneword'" -sh`consistent '${words[1]}'`; // => "consistent 'two words'" -``` - -##### Arrays and iterables - -If a word contains a placeholder for an array (or other iterable object), then -the entire word is repeated once for each value in the array, separated by -spaces. If the array is empty, then the word is not emitted at all, and neither -is any leading whitespace. - -```javascript -const files = ['foo', 'bar']; -sh`script ${files}`; // => "script foo bar" -sh`script --file=${files}`; // => "script --file=foo --file=bar" -sh`script --file=${[]}`; // => "script" -``` - -Note that, since special characters are their own words, the pipe operator here -is not repeated: - -```javascript -const cmd = ['script', 'foo', 'bar']; -sh`${cmd}|another-script`; // => "script foo bar|another-script" -``` - -Multiple arrays in the same word generate a Cartesian product: - -```javascript -const names = ['foo', 'bar'], exts = ['log', 'txt']; -// Same word -sh`... ${names}.${exts}`; // => "... foo.log foo.txt bar.log bar.txt" -sh`... "${names} ${exts}"`; // => "... 'foo log' 'foo txt' 'bar log' 'bar txt'" - -// Not the same word (extra space just for emphasis): -sh`... ${names} ${exts}`; // => "... foo bar log txt" -sh`... ${names};${exts}`; // => "... foo bar;log txt" -``` - -Finally, if a placeholder appears in the object of a redirect operator, the -entire redirect is repeated as necessary: - -```javascript -sh`script > ${['foo', 'bar']}.txt`; // => "script > foo.txt > bar.txt" -sh`script > ${[]}.txt`; // => "script" -``` - -##### unquoted - -The `unquoted` function returns a value that will skip being quoted when used -in a placeholder, alone or in an array. - -```javascript -const cmd = 'script < input.txt'; -const fields = ['foo', 'bar']; -sh`${unquoted(cmd)} | json ${fields}`; // => "script < input.txt | json foo bar" -``` - -##### ShellString - -If `ShellString.sh` is used to construct an unformatted ShellString, that value -can be used in a placeholder to insert the contents of the ShellString into the -outer template literal. This is safer than using `unquoted` as in the previous -example, but `unquoted` can be used when all you have is a string from another -(trusted!) source. - -```javascript -const url = 'http://example.com/data.json?x=1&y=2'; -const curl = ShellString.sh`curl -L ${url}`; -const fields = ['foo', 'bar']; -sh`${curl} | json ${fields}`; // => "curl -L 'http://example.com/data.json?x=1&y=2' | json foo bar" -``` - -##### Anything else - -... is treated like a string—namely, a value `x` is equivalent to `'' + x`, if -not in one of the above categories. diff --git a/node_modules/puka/index.js b/node_modules/puka/index.js deleted file mode 100644 index b69e47d7639db..0000000000000 --- a/node_modules/puka/index.js +++ /dev/null @@ -1,804 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, '__esModule', { value: true }); - -/** - * Key a method on your object with this symbol and you can get special - * formatting for that value! See ShellStringText, ShellStringUnquoted, or - * shellStringSemicolon for examples. - * @ignore - */ -const formatSymbol = Symbol('format'); -/** - * This symbol is for implementing advanced behaviors like the need for extra - * carets in Windows shell strings that use pipes. If present, it's called in - * an earlier phase than formatSymbol, and is passed a mutable context that can - * be read during the format phase to influence formatting. - * @ignore - */ -const preformatSymbol = Symbol('preformat'); - -// When minimum Node version becomes 6, replace calls to sticky with /.../y and -// inline execFrom. -let stickySupported = true; -try { - new RegExp('', 'y'); -} catch (e) { - stickySupported = false; -} -const sticky = stickySupported ? source => new RegExp(source, 'y') : source => new RegExp(`^(?:${source})`); -const execFrom = stickySupported ? (re, haystack, index) => (re.lastIndex = index, re.exec(haystack)) : (re, haystack, index) => re.exec(haystack.substr(index)); - -function quoteForCmd(text, forceQuote) { - let caretDepth = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; - // See the below blog post for an explanation of this function and - // quoteForWin32: - // https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ - if (!text.length) { - return '""'; - } - if (/[\n\r]/.test(text)) { - throw new Error("Line breaks can't be quoted on Windows"); - } - const caretEscape = /["%]/.test(text); - text = quoteForWin32(text, forceQuote || !caretEscape && /[&()<>^|]/.test(text)); - if (caretEscape) { - // See Win32Context for explanation of what caretDepth is for. - do { - text = text.replace(/[\t "%&()<>^|]/g, '^$&'); - } while (caretDepth--); - } - return text; -} -const quoteForWin32 = (text, forceQuote) => forceQuote || /[\t "]/.test(text) ? `"${text.replace(/\\+(?=$|")/g, '$&$&').replace(/"/g, '\\"')}"` : text; -const cmdMetaChars = /[\t\n\r "%&()<>^|]/; -class Win32Context { - constructor() { - this.currentScope = newScope(null); - this.scopesByObject = new Map(); - this.argDetectState = 0; - this.argSet = new Set(); - } - read(text) { - // When cmd.exe executes a batch file, or pipes to or from one, it spawns a - // second copy of itself to run the inner command. This necessitates - // doubling up on carets so that escaped characters survive both cmd.exe - // invocations. See: - // https://stackoverflow.com/questions/8192318/why-does-delayed-expansion-fail-when-inside-a-piped-block-of-code#8194279 - // https://ss64.com/nt/syntax-redirection.html - // - // Parentheses can create an additional subshell, requiring additional - // escaping... it's a mess. - // - // So here's what we do about it: we read all unquoted text in a shell - // string and put it through this tiny parser that looks for pipes, - // sequence operators (&, &&, ||), redirects, and parentheses. This can't - // be part of the main Puka parsing, because it can be affected by - // `unquoted(...)` values provided at evaluation time. - // - // Then, after associating each thing that needs to be quoted with a scope - // (via `mark()`), and identifying whether or not it's an argument to a - // command, we can determine the depth of caret escaping required in each - // scope and pass it (via `Formatter::quote()`) to `quoteForCmd()`. - // - // See also `ShellStringText`, which holds the logic for the previous - // paragraph. - const length = text.length; - for (let pos = 0, match; pos < length;) { - while (match = execFrom(reUnimportant, text, pos)) { - if (match[2] == null) { - // (not whitespace) - if (match[1] != null) { - // (>&) - this.argDetectState = this.argDetectState === 0 ? ADS_FLAG_INITIAL_REDIRECT : 0; - } else if (this.argDetectState !== ADS_FLAG_ARGS) { - this.argDetectState |= ADS_FLAG_WORD; - } - } else { - // (whitespace) - if ((this.argDetectState & ADS_FLAG_WORD) !== 0) { - this.argDetectState = ADS_FLAG_ARGS & ~this.argDetectState >> 1; - } - } - pos += match[0].length; - } - if (pos >= length) break; - if (match = execFrom(reSeqOp, text, pos)) { - this.seq(); - pos += match[0].length; - } else { - const char = text.charCodeAt(pos); - if (char === CARET) { - pos += 2; - } else if (char === QUOTE) { - // If you were foolish enough to leave a dangling quotation mark in - // an unquoted span... you're likely to have bigger problems than - // incorrect escaping. So we just do the simplest thing of looking for - // the end quote only in this piece of text. - pos += execFrom(reNotQuote, text, pos + 1)[0].length + 2; - } else { - if (char === OPEN_PAREN) { - this.enterScope(); - } else if (char === CLOSE_PAREN) { - this.exitScope(); - } else if (char === PIPE) { - this.pipe(); - } else { - // (char === '<' or '>') - this.argDetectState = this.argDetectState === 0 ? ADS_FLAG_INITIAL_REDIRECT : 0; - } - pos++; - } - } - } - } - enterScope() { - this.currentScope = newScope(this.currentScope); - this.argDetectState = 0; - } - exitScope() { - this.currentScope = this.currentScope.parent || (this.currentScope.parent = newScope(null)); - this.argDetectState = ADS_FLAG_ARGS; - } - seq() { - // | binds tighter than sequence operators, so the latter create new sibling - // scopes for future |s to mutate. - this.currentScope = newScope(this.currentScope.parent); - this.argDetectState = 0; - } - pipe() { - this.currentScope.depthDelta = 1; - this.argDetectState = 0; - } - mark(obj) { - this.scopesByObject.set(obj, this.currentScope); - if (this.argDetectState === ADS_FLAG_ARGS) { - this.argSet.add(obj); - } else { - this.argDetectState |= ADS_FLAG_WORD; - } - } - at(obj) { - const scope = this.scopesByObject.get(obj); - return { - depth: getDepth(scope), - isArgument: this.argSet.has(obj), - isNative: scope.isNative - }; - } -} -// These flags span the Win32Context's argument detection state machine. WORD -// is set when the context is inside a word that is not an argument (meaning it -// is either the first word in the command, or it is the object of a redirect). -// ARGS is set when the context has reached the arguments of a command. -// INITIAL_REDIRECT tracks the edge case when a redirect occurs before the -// first word of the command (if this flag is set, reaching the end of a word -// should take the state machine back to 0 instead of setting ADS_FLAG_ARGS). -const ADS_FLAG_WORD = 0x1; -const ADS_FLAG_ARGS = 0x2; -const ADS_FLAG_INITIAL_REDIRECT = 0x4; -const getDepth = scope => scope === null ? 0 : scope.depth !== -1 ? scope.depth : scope.depth = getDepth(scope.parent) + scope.depthDelta; -const newScope = parent => ({ - parent, - depthDelta: 0, - depth: -1, - isNative: false -}); -const CARET = '^'.charCodeAt(); -const QUOTE = '"'.charCodeAt(); -const OPEN_PAREN = '('.charCodeAt(); -const CLOSE_PAREN = ')'.charCodeAt(); -const PIPE = '|'.charCodeAt(); -const reNotQuote = sticky('[^"]*'); -const reSeqOp = sticky('&&?|\\|\\|'); -const reUnimportant = sticky('(\\d*>&)|[^\\s"$&()<>^|]+|(\\s+)'); - -const quoteForSh = (text, forceQuote) => text.length ? forceQuote || shMetaChars.test(text) ? `'${text.replace(/'/g, "'\\''")}'`.replace(/^(?:'')+(?!$)/, '').replace(/\\'''/g, "\\'") : text : "''"; -const shMetaChars = /[\t\n\r "#$&'()*;<>?\\`|~]/; - -/** - * To get a Formatter, call `Formatter.for`. - * - * To create a new Formatter, pass an object to `Formatter.declare`. - * - * To set the global default Formatter, assign to `Formatter.default`. - * - * @class - * @property {Formatter} default - The Formatter to be used when no platform - * is provided—for example, when creating strings with `sh`. - * @ignore - */ -function Formatter() {} -Object.assign(Formatter, -/** @lends Formatter */ -{ - /** - * Gets a Formatter that has been declared for the provided platform, or - * the base `'sh'` formatter if there is no Formatter specific to this - * platform, or the Formatter for the current platform if no specific platform - * is provided. - */ - for(platform) { - return platform == null ? Formatter.default || (Formatter.default = Formatter.for(process.platform)) : Formatter._registry.get(platform) || Formatter._registry.get('sh'); - }, - /** - * Creates a new Formatter or mutates the properties on an existing - * Formatter. The `platform` key on the provided properties object determines - * when the Formatter is retrieved. - */ - declare(props) { - const platform = props && props.platform || 'sh'; - const existingFormatter = Formatter._registry.get(platform); - const formatter = Object.assign(existingFormatter || new Formatter(), props); - formatter.emptyString === void 0 && (formatter.emptyString = formatter.quote('', true)); - existingFormatter || Formatter._registry.set(formatter.platform, formatter); - }, - _registry: new Map(), - prototype: { - platform: 'sh', - quote: quoteForSh, - metaChars: shMetaChars, - hasExtraMetaChars: false, - statementSeparator: ';', - createContext() { - return defaultContext; - } - } -}); -const defaultContext = { - at() {} -}; -Formatter.declare(); -Formatter.declare({ - platform: 'win32', - quote(text, forceQuote, opts) { - const caretDepth = opts ? (opts.depth || 0) + (opts.isArgument && !opts.isNative ? 1 : 0) : 0; - return quoteForCmd(text, forceQuote, caretDepth); - }, - metaChars: cmdMetaChars, - hasExtraMetaChars: true, - statementSeparator: '&', - createContext(root) { - const context = new this.Context(); - root[preformatSymbol](context); - return context; - }, - Context: Win32Context -}); - -const isObject = any => any === Object(any); -function memoize(f) { - const cache = new WeakMap(); - return arg => { - let result = cache.get(arg); - if (result === void 0) { - result = f(arg); - cache.set(arg, result); - } - return result; - }; -} - -/** - * Represents a contiguous span of text that may or must be quoted. The contents - * may already contain quoted segments, which will always be quoted. If unquoted - * segments also require quoting, the entire span will be quoted together. - * @ignore - */ -class ShellStringText { - constructor(contents, untested) { - this.contents = contents; - this.untested = untested; - } - [formatSymbol](formatter, context) { - const unformattedContents = this.contents; - const length = unformattedContents.length; - const contents = new Array(length); - for (let i = 0; i < length; i++) { - const c = unformattedContents[i]; - contents[i] = isObject(c) && formatSymbol in c ? c[formatSymbol](formatter) : c; - } - for (let unquoted = true, i = 0; i < length; i++) { - const content = contents[i]; - if (content === null) { - unquoted = !unquoted; - } else { - if (unquoted && (formatter.hasExtraMetaChars || this.untested && this.untested.has(i)) && formatter.metaChars.test(content)) { - return formatter.quote(contents.join(''), false, context.at(this)); - } - } - } - const parts = []; - for (let quoted = null, i = 0; i < length; i++) { - const content = contents[i]; - if (content === null) { - quoted = quoted ? (parts.push(formatter.quote(quoted.join(''), true, context.at(this))), null) : []; - } else { - (quoted || parts).push(content); - } - } - const result = parts.join(''); - return result.length ? result : formatter.emptyString; - } - [preformatSymbol](context) { - context.mark(this); - } -} - -/** - * Represents a contiguous span of text that will not be quoted. - * @ignore - */ -class ShellStringUnquoted { - constructor(value) { - this.value = value; - } - [formatSymbol]() { - return this.value; - } - [preformatSymbol](context) { - context.read(this.value); - } -} - -/** - * Represents a semicolon... or an ampersand, on Windows. - * @ignore - */ -const shellStringSemicolon = { - [formatSymbol](formatter) { - return formatter.statementSeparator; - }, - [preformatSymbol](context) { - context.seq(); - } -}; - -const PLACEHOLDER = {}; -const parse = memoize(templateSpans => { - // These are the token types our DSL can recognize. Their values won't escape - // this function. - const TOKEN_TEXT = 0; - const TOKEN_QUOTE = 1; - const TOKEN_SEMI = 2; - const TOKEN_UNQUOTED = 3; - const TOKEN_SPACE = 4; - const TOKEN_REDIRECT = 5; - const result = []; - let placeholderCount = 0; - let prefix = null; - let onlyPrefixOnce = false; - let contents = []; - let quote = 0; - const lastSpan = templateSpans.length - 1; - for (let spanIndex = 0; spanIndex <= lastSpan; spanIndex++) { - const templateSpan = templateSpans[spanIndex]; - const posEnd = templateSpan.length; - let tokenStart = 0; - if (spanIndex) { - placeholderCount++; - contents.push(PLACEHOLDER); - } - // For each span, we first do a recognizing pass in which we use regular - // expressions to identify the positions of tokens in the text, and then - // a second pass that actually splits the text into the minimum number of - // substrings necessary. - const recognized = []; // [type1, index1, type2, index2...] - let firstWordBreak = -1; - let lastWordBreak = -1; - { - let pos = 0, - match; - while (pos < posEnd) { - if (quote) { - if (match = execFrom(quote === CHAR_SQUO ? reQuotation1 : reQuotation2, templateSpan, pos)) { - recognized.push(TOKEN_TEXT, pos); - pos += match[0].length; - } - if (pos < posEnd) { - recognized.push(TOKEN_QUOTE, pos++); - quote = 0; - } - } else { - if (match = execFrom(reRedirectOrSpace, templateSpan, pos)) { - firstWordBreak < 0 && (firstWordBreak = pos); - lastWordBreak = pos; - recognized.push(match[1] ? TOKEN_REDIRECT : TOKEN_SPACE, pos); - pos += match[0].length; - } - if (match = execFrom(reText, templateSpan, pos)) { - const setBreaks = match[1] != null; - setBreaks && firstWordBreak < 0 && (firstWordBreak = pos); - recognized.push(setBreaks ? TOKEN_UNQUOTED : TOKEN_TEXT, pos); - pos += match[0].length; - setBreaks && (lastWordBreak = pos); - } - const char = templateSpan.charCodeAt(pos); - if (char === CHAR_SEMI) { - firstWordBreak < 0 && (firstWordBreak = pos); - recognized.push(TOKEN_SEMI, pos++); - lastWordBreak = pos; - } else if (char === CHAR_SQUO || char === CHAR_DQUO) { - recognized.push(TOKEN_QUOTE, pos++); - quote = char; - } - } - } - } - // Word breaks are only important if they separate words with placeholders, - // so we can ignore the first/last break if this is the first/last span. - spanIndex === 0 && (firstWordBreak = -1); - spanIndex === lastSpan && (lastWordBreak = posEnd); - // Here begins the second pass mentioned above. This loop runs one more - // iteration than there are tokens in recognized, because it handles tokens - // on a one-iteration delay; hence the i <= iEnd instead of i < iEnd. - const iEnd = recognized.length; - for (let i = 0, type = -1; i <= iEnd; i += 2) { - let typeNext = -1, - pos; - if (i === iEnd) { - pos = posEnd; - } else { - typeNext = recognized[i]; - pos = recognized[i + 1]; - // If the next token is space or redirect, but there's another word - // break in this span, then we can handle that token the same way we - // would handle unquoted text because it isn't being attached to a - // placeholder. - typeNext >= TOKEN_SPACE && pos !== lastWordBreak && (typeNext = TOKEN_UNQUOTED); - } - const breakHere = pos === firstWordBreak || pos === lastWordBreak; - if (pos && (breakHere || typeNext !== type)) { - let value = type === TOKEN_QUOTE ? null : type === TOKEN_SEMI ? shellStringSemicolon : templateSpan.substring(tokenStart, pos); - if (type >= TOKEN_SEMI) { - // This branch handles semicolons, unquoted text, spaces, and - // redirects. shellStringSemicolon is already a formatSymbol object; - // the rest need to be wrapped. - type === TOKEN_SEMI || (value = new ShellStringUnquoted(value)); - // We don't need to check placeholderCount here like we do below; - // that's only relevant during the first word break of the span, and - // because this iteration of the loop is processing the token that - // was checked for breaks in the previous iteration, it will have - // already been handled. For the same reason, prefix is guaranteed to - // be null. - if (contents.length) { - result.push(new ShellStringText(contents, null)); - contents = []; - } - // Only spaces and redirects become prefixes, but not if they've been - // rewritten to unquoted above. - if (type >= TOKEN_SPACE) { - prefix = value; - onlyPrefixOnce = type === TOKEN_SPACE; - } else { - result.push(value); - } - } else { - contents.push(value); - } - tokenStart = pos; - } - if (breakHere) { - if (placeholderCount) { - result.push({ - contents, - placeholderCount, - prefix, - onlyPrefixOnce - }); - } else { - // There's no prefix to handle in this branch; a prefix prior to this - // span would mean placeholderCount > 0, and a prefix in this span - // can't be created because spaces and redirects get rewritten to - // unquoted before the last word break. - contents.length && result.push(new ShellStringText(contents, null)); - } - placeholderCount = 0; - prefix = null; - onlyPrefixOnce = false; - contents = []; - } - type = typeNext; - } - } - if (quote) { - throw new SyntaxError(`String is missing a ${String.fromCharCode(quote)} character`); - } - return result; -}); -const CHAR_SEMI = ';'.charCodeAt(); -const CHAR_SQUO = "'".charCodeAt(); -const CHAR_DQUO = '"'.charCodeAt(); -const reQuotation1 = sticky("[^']+"); -const reQuotation2 = sticky('[^"]+'); -const reText = sticky('[^\\s"#$&\'();<>\\\\`|]+|([#$&()\\\\`|]+)'); -const reRedirectOrSpace = sticky('(\\s*\\d*[<>]+\\s*)|\\s+'); - -class BitSet { - constructor() { - this.vector = new Int32Array(1); - } - has(n) { - return (this.vector[n >>> 5] & 1 << n) !== 0; - } - add(n) { - const i = n >>> 5, - requiredLength = i + 1; - let vector = this.vector, - _vector = vector, - length = _vector.length; - if (requiredLength > length) { - while (requiredLength > (length *= 2)); - const oldValues = vector; - vector = new Int32Array(length); - vector.set(oldValues); - this.vector = vector; - } - vector[i] |= 1 << n; - } -} - -function evaluate(template, values) { - values = values.map(toStringishArray); - const children = []; - let valuesStart = 0; - for (let i = 0, iMax = template.length; i < iMax; i++) { - const word = template[i]; - if (formatSymbol in word) { - children.push(word); - continue; - } - const contents = word.contents, - placeholderCount = word.placeholderCount, - prefix = word.prefix, - onlyPrefixOnce = word.onlyPrefixOnce; - const kMax = contents.length; - const valuesEnd = valuesStart + placeholderCount; - const tuples = cartesianProduct(values, valuesStart, valuesEnd); - valuesStart = valuesEnd; - for (let j = 0, jMax = tuples.length; j < jMax; j++) { - const needSpace = j > 0; - const tuple = tuples[j]; - (needSpace || prefix) && children.push(needSpace && (onlyPrefixOnce || !prefix) ? unquotedSpace : prefix); - let interpolatedContents = []; - let untested = null; - let quoting = false; - let tupleIndex = 0; - for (let k = 0; k < kMax; k++) { - const content = contents[k]; - if (content === PLACEHOLDER) { - const value = tuple[tupleIndex++]; - if (quoting) { - interpolatedContents.push(value); - } else { - if (isObject(value) && formatSymbol in value) { - if (interpolatedContents.length) { - children.push(new ShellStringText(interpolatedContents, untested)); - interpolatedContents = []; - untested = null; - } - children.push(value); - } else { - (untested || (untested = new BitSet())).add(interpolatedContents.length); - interpolatedContents.push(value); - } - } - } else { - interpolatedContents.push(content); - content === null && (quoting = !quoting); - } - } - if (interpolatedContents.length) { - children.push(new ShellStringText(interpolatedContents, untested)); - } - } - } - return children; -} -const primToStringish = value => value == null ? '' + value : value; -function toStringishArray(value) { - let array; - switch (true) { - default: - if (isObject(value)) { - if (Array.isArray(value)) { - array = value; - break; - } - if (Symbol.iterator in value) { - array = Array.from(value); - break; - } - } - array = [value]; - } - return array.map(primToStringish); -} -function cartesianProduct(arrs, start, end) { - const size = end - start; - let resultLength = 1; - for (let i = start; i < end; i++) { - resultLength *= arrs[i].length; - } - if (resultLength > 1e6) { - throw new RangeError("Far too many elements to interpolate"); - } - const result = new Array(resultLength); - const indices = new Array(size).fill(0); - for (let i = 0; i < resultLength; i++) { - const value = result[i] = new Array(size); - for (let j = 0; j < size; j++) { - value[j] = arrs[j + start][indices[j]]; - } - for (let j = size - 1; j >= 0; j--) { - if (++indices[j] < arrs[j + start].length) break; - indices[j] = 0; - } - } - return result; -} -const unquotedSpace = new ShellStringUnquoted(' '); - -/** - * A ShellString represents a shell command after it has been interpolated, but - * before it has been formatted for a particular platform. ShellStrings are - * useful if you want to prepare a command for a different platform than the - * current one, for instance. - * - * To create a ShellString, use `ShellString.sh` the same way you would use - * top-level `sh`. - */ -class ShellString { - /** @hideconstructor */ - constructor(children) { - this.children = children; - } - /** - * `ShellString.sh` is a template tag just like `sh`; the only difference is - * that this function returns a ShellString which has not yet been formatted - * into a String. - * @returns {ShellString} - * @function sh - * @static - * @memberof ShellString - */ - static sh(templateSpans) { - for (var _len = arguments.length, values = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - values[_key - 1] = arguments[_key]; - } - return new ShellString(evaluate(parse(templateSpans), values)); - } - /** - * A method to format a ShellString into a regular String formatted for a - * particular platform. - * - * @param {String} [platform] a value that `process.platform` might take: - * `'win32'`, `'linux'`, etc.; determines how the string is to be formatted. - * When omitted, effectively the same as `process.platform`. - * @returns {String} - */ - toString(platform) { - return this[formatSymbol](Formatter.for(platform)); - } - [formatSymbol](formatter) { - let context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : formatter.createContext(this); - return this.children.map(child => child[formatSymbol](formatter, context)).join(''); - } - [preformatSymbol](context) { - const children = this.children; - for (let i = 0, iMax = children.length; i < iMax; i++) { - const child = children[i]; - if (preformatSymbol in child) { - child[preformatSymbol](context); - } - } - } -} - -/** - * A Windows-specific version of {@link quoteForShell}. - * @param {String} text to be quoted - * @param {Boolean} [forceQuote] whether to always add quotes even if the string - * is already safe. Defaults to `false`. - */ - -/** - * A Unix-specific version of {@link quoteForShell}. - * @param {String} text to be quoted - * @param {Boolean} [forceQuote] whether to always add quotes even if the string - * is already safe. Defaults to `false`. - */ - -/** - * Quotes a string for injecting into a shell command. - * - * This function is exposed for some hypothetical case when the `sh` DSL simply - * won't do; `sh` is expected to be the more convenient option almost always. - * Compare: - * - * ```javascript - * console.log('cmd' + args.map(a => ' ' + quoteForShell(a)).join('')); - * console.log(sh`cmd ${args}`); // same as above - * - * console.log('cmd' + args.map(a => ' ' + quoteForShell(a, true)).join('')); - * console.log(sh`cmd "${args}"`); // same as above - * ``` - * - * Additionally, on Windows, `sh` checks the entire command string for pipes, - * which subtly change how arguments need to be quoted. If your commands may - * involve pipes, you are strongly encouraged to use `sh` and not try to roll - * your own with `quoteForShell`. - * - * @param {String} text to be quoted - * @param {Boolean} [forceQuote] whether to always add quotes even if the string - * is already safe. Defaults to `false`. - * @param {String} [platform] a value that `process.platform` might take: - * `'win32'`, `'linux'`, etc.; determines how the string is to be formatted. - * When omitted, effectively the same as `process.platform`. - * - * @returns {String} a string that is safe for the current (or specified) - * platform. - */ -function quoteForShell(text, forceQuote, platform) { - return Formatter.for(platform).quote(text, forceQuote); -} - -/** - * A string template tag for safely constructing cross-platform shell commands. - * - * An `sh` template is not actually treated as a literal string to be - * interpolated; instead, it is a tiny DSL designed to make working with shell - * strings safe, simple, and straightforward. To get started quickly, see the - * examples below. {@link #the-sh-dsl More detailed documentation} is available - * further down. - * - * @name sh - * @example - * const title = '"this" & "that"'; - * sh`script --title=${title}`; // => "script '--title=\"this\" & \"that\"'" - * // Note: these examples show results for non-Windows platforms. - * // On Windows, the above would instead be - * // 'script ^^^"--title=\\^^^"this\\^^^" ^^^& \\^^^"that\\^^^"^^^"'. - * - * const names = ['file1', 'file 2']; - * sh`rimraf ${names}.txt`; // => "rimraf file1.txt 'file 2.txt'" - * - * const cmd1 = ['cat', 'file 1.txt', 'file 2.txt']; - * const cmd2 = ['use-input', '-abc']; - * sh`${cmd1}|${cmd2}`; // => "cat 'file 1.txt' 'file 2.txt'|use-input -abc" - * - * @returns {String} - a string formatted for the platform Node is currently - * running on. - */ -const sh = function () { - return ShellString.sh.apply(ShellString, arguments).toString(); -}; - -/** - * This function permits raw strings to be interpolated into a `sh` template. - * - * **IMPORTANT**: If you're using Puka due to security concerns, make sure you - * don't pass any untrusted content to `unquoted`. This may be obvious, but - * stray punctuation in an `unquoted` section can compromise the safety of the - * entire shell command. - * - * @param value - any value (it will be treated as a string) - * - * @example - * const both = true; - * sh`foo ${unquoted(both ? '&&' : '||')} bar`; // => 'foo && bar' - */ -const unquoted = value => new ShellStringUnquoted(value); - -exports.Formatter = Formatter; -exports.ShellString = ShellString; -exports.ShellStringText = ShellStringText; -exports.ShellStringUnquoted = ShellStringUnquoted; -exports.quoteForCmd = quoteForCmd; -exports.quoteForSh = quoteForSh; -exports.quoteForShell = quoteForShell; -exports.sh = sh; -exports.shellStringSemicolon = shellStringSemicolon; -exports.formatSymbol = formatSymbol; -exports.preformatSymbol = preformatSymbol; -exports.unquoted = unquoted; diff --git a/node_modules/puka/package.json b/node_modules/puka/package.json deleted file mode 100644 index 41798dc2493b8..0000000000000 --- a/node_modules/puka/package.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "puka", - "version": "1.0.1", - "description": "A cross-platform library for safely passing strings through shells", - "keywords": [ - "args", - "arguments", - "cmd", - "command", - "command-line", - "cross-platform", - "escape", - "escaping", - "exec", - "linux", - "mac", - "macos", - "osx", - "quote", - "quoting", - "sh", - "shell", - "spawn", - "unix", - "win", - "win32", - "windows" - ], - "homepage": "https://gitlab.com/rhendric/puka", - "bugs": "https://gitlab.com/rhendric/puka/issues", - "license": "MIT", - "author": "Ryan Hendrickson ", - "repository": "gitlab:rhendric/puka", - "dependencies": {}, - "engines": { - "node": ">=4" - } -} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 0ba6c23bb5867..73b6c86ec7f48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -209,7 +209,6 @@ "promise-retry", "promzard", "psl", - "puka", "punycode", "qs", "read-cmd-shim", @@ -255,7 +254,7 @@ "@npmcli/arborist": "^2.2.8", "@npmcli/ci-detect": "^1.2.0", "@npmcli/config": "^1.2.9", - "@npmcli/run-script": "^1.8.3", + "@npmcli/run-script": "^1.8.4", "abbrev": "~1.1.1", "ansicolors": "~0.3.2", "ansistyles": "~0.1.3", @@ -977,16 +976,15 @@ } }, "node_modules/@npmcli/run-script": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-1.8.3.tgz", - "integrity": "sha512-ELPGWAVU/xyU+A+H3pEPj0QOvYwLTX71RArXcClFzeiyJ/b/McsZ+d0QxpznvfFtZzxGN/gz/1cvlqICR4/suQ==", + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-1.8.4.tgz", + "integrity": "sha512-Yd9HXTtF1JGDXZw0+SOn+mWLYS0e7bHBHVC/2C8yqs4wUrs/k8rwBSinD7rfk+3WG/MFGRZKxjyoD34Pch2E/A==", "inBundle": true, "dependencies": { "@npmcli/node-gyp": "^1.0.2", "@npmcli/promise-spawn": "^1.3.2", "infer-owner": "^1.0.4", "node-gyp": "^7.1.0", - "puka": "^1.0.1", "read-package-json-fast": "^2.0.1" } }, @@ -6335,15 +6333,6 @@ "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", "inBundle": true }, - "node_modules/puka": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/puka/-/puka-1.0.1.tgz", - "integrity": "sha512-ssjRZxBd7BT3dte1RR3VoeT2cT/ODH8x+h0rUF1rMqB0srHYf48stSDWfiYakTp5UBZMxroZhB2+ExLDHm7W3g==", - "inBundle": true, - "engines": { - "node": ">=4" - } - }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -11117,15 +11106,14 @@ } }, "@npmcli/run-script": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-1.8.3.tgz", - "integrity": "sha512-ELPGWAVU/xyU+A+H3pEPj0QOvYwLTX71RArXcClFzeiyJ/b/McsZ+d0QxpznvfFtZzxGN/gz/1cvlqICR4/suQ==", + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-1.8.4.tgz", + "integrity": "sha512-Yd9HXTtF1JGDXZw0+SOn+mWLYS0e7bHBHVC/2C8yqs4wUrs/k8rwBSinD7rfk+3WG/MFGRZKxjyoD34Pch2E/A==", "requires": { "@npmcli/node-gyp": "^1.0.2", "@npmcli/promise-spawn": "^1.3.2", "infer-owner": "^1.0.4", "node-gyp": "^7.1.0", - "puka": "^1.0.1", "read-package-json-fast": "^2.0.1" } }, @@ -15113,11 +15101,6 @@ "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" }, - "puka": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/puka/-/puka-1.0.1.tgz", - "integrity": "sha512-ssjRZxBd7BT3dte1RR3VoeT2cT/ODH8x+h0rUF1rMqB0srHYf48stSDWfiYakTp5UBZMxroZhB2+ExLDHm7W3g==" - }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", diff --git a/package.json b/package.json index 874a48fcd988b..940ab0fff1203 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@npmcli/arborist": "^2.2.8", "@npmcli/ci-detect": "^1.2.0", "@npmcli/config": "^1.2.9", - "@npmcli/run-script": "^1.8.3", + "@npmcli/run-script": "^1.8.4", "abbrev": "~1.1.1", "ansicolors": "~0.3.2", "ansistyles": "~0.1.3", From f06c12ec9d732280988f716d3a3053085e505e35 Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Thu, 18 Mar 2021 13:20:13 -0400 Subject: [PATCH 05/42] eslint@7.22.0 --- package-lock.json | 76 ++++++++++++++++++++++++++++++++++++----------- package.json | 2 +- 2 files changed, 60 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index 73b6c86ec7f48..ecaf828435b96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -325,7 +325,7 @@ "devDependencies": { "@mdx-js/mdx": "^1.6.22", "cmark-gfm": "^0.8.5", - "eslint": "^7.21.0", + "eslint": "^7.22.0", "eslint-plugin-import": "^2.22.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^4.3.1", @@ -2628,9 +2628,9 @@ } }, "node_modules/eslint": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.21.0.tgz", - "integrity": "sha512-W2aJbXpMNofUp0ztQaF40fveSsJBjlSCSWpy//gzfTvwC+USs/nceBrKmlJOiM8r1bLwP2EuYkCqArn/6QTIgg==", + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.22.0.tgz", + "integrity": "sha512-3VawOtjSJUQiiqac8MQc+w457iGLfuNGLFn8JmF051tTKbh5/x/0vlcEj8OgDCaw7Ysa2Jn8paGshV7x2abKXg==", "dev": true, "dependencies": { "@babel/code-frame": "7.12.11", @@ -2650,7 +2650,7 @@ "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", "glob-parent": "^5.0.0", - "globals": "^12.1.0", + "globals": "^13.6.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", @@ -2658,7 +2658,7 @@ "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", - "lodash": "^4.17.20", + "lodash": "^4.17.21", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", @@ -2943,6 +2943,21 @@ "node": ">=8" } }, + "node_modules/eslint/node_modules/globals": { + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.7.0.tgz", + "integrity": "sha512-Aipsz6ZKRxa/xQkZhNg0qIWXT6x6rD46f6x/PCnBomlttdIyAPak4YD9jTmKpZ72uROSMU87qJtcgpgHaVchiA==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint/node_modules/strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -2955,6 +2970,18 @@ "node": ">=8" } }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/esm": { "version": "3.2.25", "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", @@ -4966,9 +4993,9 @@ } }, "node_modules/lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "node_modules/lodash.flattendeep": { @@ -12342,9 +12369,9 @@ } }, "eslint": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.21.0.tgz", - "integrity": "sha512-W2aJbXpMNofUp0ztQaF40fveSsJBjlSCSWpy//gzfTvwC+USs/nceBrKmlJOiM8r1bLwP2EuYkCqArn/6QTIgg==", + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.22.0.tgz", + "integrity": "sha512-3VawOtjSJUQiiqac8MQc+w457iGLfuNGLFn8JmF051tTKbh5/x/0vlcEj8OgDCaw7Ysa2Jn8paGshV7x2abKXg==", "dev": true, "requires": { "@babel/code-frame": "7.12.11", @@ -12364,7 +12391,7 @@ "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", "glob-parent": "^5.0.0", - "globals": "^12.1.0", + "globals": "^13.6.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", @@ -12372,7 +12399,7 @@ "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", - "lodash": "^4.17.20", + "lodash": "^4.17.21", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", @@ -12401,6 +12428,15 @@ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, + "globals": { + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.7.0.tgz", + "integrity": "sha512-Aipsz6ZKRxa/xQkZhNg0qIWXT6x6rD46f6x/PCnBomlttdIyAPak4YD9jTmKpZ72uROSMU87qJtcgpgHaVchiA==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -12409,6 +12445,12 @@ "requires": { "ansi-regex": "^5.0.0" } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true } } }, @@ -14079,9 +14121,9 @@ } }, "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "lodash.flattendeep": { diff --git a/package.json b/package.json index 940ab0fff1203..ad9bedfcc6003 100644 --- a/package.json +++ b/package.json @@ -180,7 +180,7 @@ "devDependencies": { "@mdx-js/mdx": "^1.6.22", "cmark-gfm": "^0.8.5", - "eslint": "^7.21.0", + "eslint": "^7.22.0", "eslint-plugin-import": "^2.22.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^4.3.1", From 966057cc343f3060e14498c9bbfc39a82a8d8537 Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Thu, 18 Mar 2021 13:22:09 -0400 Subject: [PATCH 06/42] jsdom@16.5.1 --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index ecaf828435b96..905442dd19c6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -330,7 +330,7 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^4.3.1", "eslint-plugin-standard": "^5.0.0", - "jsdom": "^16.4.0", + "jsdom": "^16.5.1", "licensee": "^8.1.0", "marked-man": "^0.7.0", "require-inject": "^1.4.4", @@ -4594,9 +4594,9 @@ "inBundle": true }, "node_modules/jsdom": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.5.0.tgz", - "integrity": "sha512-QxZH0nmDTnTTVI0YDm4RUlaUPl5dcyn62G5TMDNfMmTW+J1u1v9gCR8WR+WZ6UghAa7nKJjDOFaI00eMMWvJFQ==", + "version": "16.5.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.5.1.tgz", + "integrity": "sha512-pF73EOsJgwZekbDHEY5VO/yKXUkab/DuvrQB/ANVizbr6UAHJsDdHXuotZYwkJSGQl1JM+ivXaqY+XBDDL4TiA==", "dev": true, "dependencies": { "abab": "^2.0.5", @@ -13818,9 +13818,9 @@ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "jsdom": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.5.0.tgz", - "integrity": "sha512-QxZH0nmDTnTTVI0YDm4RUlaUPl5dcyn62G5TMDNfMmTW+J1u1v9gCR8WR+WZ6UghAa7nKJjDOFaI00eMMWvJFQ==", + "version": "16.5.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.5.1.tgz", + "integrity": "sha512-pF73EOsJgwZekbDHEY5VO/yKXUkab/DuvrQB/ANVizbr6UAHJsDdHXuotZYwkJSGQl1JM+ivXaqY+XBDDL4TiA==", "dev": true, "requires": { "abab": "^2.0.5", diff --git a/package.json b/package.json index ad9bedfcc6003..79ca2c7084198 100644 --- a/package.json +++ b/package.json @@ -185,7 +185,7 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^4.3.1", "eslint-plugin-standard": "^5.0.0", - "jsdom": "^16.4.0", + "jsdom": "^16.5.1", "licensee": "^8.1.0", "marked-man": "^0.7.0", "require-inject": "^1.4.4", From b395763f002457866d8bf4bf37858f69459f1d30 Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Thu, 18 Mar 2021 13:22:30 -0400 Subject: [PATCH 07/42] yaml@1.10.2 --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 905442dd19c6d..b258bd6c13dd2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -335,7 +335,7 @@ "marked-man": "^0.7.0", "require-inject": "^1.4.4", "tap": "^14.11.0", - "yaml": "^1.10.0" + "yaml": "^1.10.2" }, "engines": { "node": ">=10" @@ -10375,9 +10375,9 @@ "inBundle": true }, "node_modules/yaml": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", - "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", "dev": true, "engines": { "node": ">= 6" @@ -18060,9 +18060,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yaml": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", - "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", "dev": true }, "yapool": { diff --git a/package.json b/package.json index 79ca2c7084198..856d4bbe67d6e 100644 --- a/package.json +++ b/package.json @@ -190,7 +190,7 @@ "marked-man": "^0.7.0", "require-inject": "^1.4.4", "tap": "^14.11.0", - "yaml": "^1.10.0" + "yaml": "^1.10.2" }, "scripts": { "dumpconf": "env | grep npm | sort | uniq", From 46e14bd0ff566f94e8f86f9d972bf089c000da52 Mon Sep 17 00:00:00 2001 From: Gar Date: Mon, 8 Mar 2021 16:22:54 -0800 Subject: [PATCH 08/42] chore(config): remove flatOptions references Iterative change moving us towards a more unified config. No longer pulling config from flatOptions where we don't have to. PR-URL: https://github.com/npm/cli/pull/2892 Credit: @wraithgar Close: #2892 Reviewed-by: @ruyadorno --- lib/access.js | 2 +- lib/audit.js | 13 +- lib/bin.js | 2 +- lib/cache.js | 5 +- lib/ci.js | 5 +- lib/dedupe.js | 7 +- lib/diff.js | 6 +- lib/dist-tag.js | 2 +- lib/exec.js | 11 +- lib/fund.js | 25 +- lib/help-search.js | 4 +- lib/init.js | 2 +- lib/install.js | 5 +- lib/link.js | 8 +- lib/logout.js | 5 +- lib/ls.js | 22 +- lib/outdated.js | 32 +- lib/pack.js | 4 +- lib/ping.js | 6 +- lib/profile.js | 50 ++- lib/publish.js | 8 +- lib/rebuild.js | 2 +- lib/run-script.js | 10 +- lib/search.js | 2 +- lib/shrinkwrap.js | 2 +- lib/star.js | 2 +- lib/uninstall.js | 3 +- lib/unpublish.js | 6 +- lib/update.js | 4 +- lib/utils/ping.js | 4 +- lib/utils/read-local-package.js | 7 +- lib/version.js | 4 +- lib/view.js | 31 +- lib/whoami.js | 7 +- test/fixtures/mock-npm.js | 22 ++ test/lib/audit.js | 20 +- test/lib/bin.js | 19 +- test/lib/cache.js | 31 +- test/lib/ci.js | 36 +- test/lib/dedupe.js | 31 +- test/lib/diff.js | 190 +++++----- test/lib/dist-tag.js | 48 +-- test/lib/exec.js | 108 +++--- test/lib/fund.js | 106 +++--- test/lib/help-search.js | 20 +- test/lib/init.js | 48 +-- test/lib/install.js | 35 +- test/lib/link.js | 15 +- test/lib/logout.js | 72 ++-- test/lib/ls.js | 522 +++++++++++++-------------- test/lib/outdated.js | 72 ++-- test/lib/pack.js | 29 +- test/lib/ping.js | 39 +- test/lib/profile.js | 105 +++--- test/lib/publish.js | 197 +++++----- test/lib/rebuild.js | 16 +- test/lib/run-script.js | 42 +-- test/lib/search.js | 20 +- test/lib/shrinkwrap.js | 31 +- test/lib/star.js | 24 +- test/lib/uninstall.js | 21 +- test/lib/unpublish.js | 43 +-- test/lib/update.js | 20 +- test/lib/utils/read-local-package.js | 21 +- test/lib/version.js | 20 +- test/lib/view.js | 90 ++--- test/lib/whoami.js | 14 +- 67 files changed, 1228 insertions(+), 1207 deletions(-) create mode 100644 test/fixtures/mock-npm.js diff --git a/lib/access.js b/lib/access.js index 0df36beeac15f..3020719a7da7d 100644 --- a/lib/access.js +++ b/lib/access.js @@ -75,7 +75,7 @@ class Access extends BaseCommand { if (!subcommands.includes(cmd) || !this[cmd]) throw this.usageError(`${cmd} is not a recognized subcommand.`) - return this[cmd](args, { ...this.npm.flatOptions }) + return this[cmd](args, this.npm.flatOptions) } public ([pkg], opts) { diff --git a/lib/audit.js b/lib/audit.js index 6e64987b612ae..27cebff9f89b8 100644 --- a/lib/audit.js +++ b/lib/audit.js @@ -37,11 +37,14 @@ class Audit extends BaseCommand { } async audit (args) { - const arb = new Arborist({ + const reporter = this.npm.config.get('json') ? 'json' : 'detail' + const opts = { ...this.npm.flatOptions, audit: true, path: this.npm.prefix, - }) + reporter, + } + const arb = new Arborist(opts) const fix = args[0] === 'fix' await arb.audit({ fix }) if (fix) @@ -49,11 +52,7 @@ class Audit extends BaseCommand { else { // will throw if there's an error, because this is an audit command auditError(this.npm, arb.auditReport) - const reporter = this.npm.flatOptions.json ? 'json' : 'detail' - const result = auditReport(arb.auditReport, { - ...this.npm.flatOptions, - reporter, - }) + const result = auditReport(arb.auditReport, opts) process.exitCode = process.exitCode || result.exitCode this.npm.output(result.report) } diff --git a/lib/bin.js b/lib/bin.js index 1450fb539bffa..b0acefef92091 100644 --- a/lib/bin.js +++ b/lib/bin.js @@ -17,7 +17,7 @@ class Bin extends BaseCommand { async bin (args) { const b = this.npm.bin this.npm.output(b) - if (this.npm.flatOptions.global && !envPath.includes(b)) + if (this.npm.config.get('global') && !envPath.includes(b)) console.error('(not in PATH env variable)') } } diff --git a/lib/cache.js b/lib/cache.js index 80a5c68ebc0e9..b2d42ab232a4d 100644 --- a/lib/cache.js +++ b/lib/cache.js @@ -63,7 +63,7 @@ class Cache extends BaseCommand { throw new Error('npm cache clear does not accept arguments') const cachePath = path.join(this.npm.cache, '_cacache') - if (!this.npm.flatOptions.force) { + if (!this.npm.config.get('force')) { throw new Error(`As of npm@5, the npm cache self-heals from corruption issues by treating integrity mismatches as cache misses. As a result, data extracted from the cache is guaranteed to be valid. If you @@ -100,7 +100,6 @@ with --force.`) throw Object.assign(new Error(usage), { code: 'EUSAGE' }) log.silly('cache add', 'spec', spec) - const opts = { ...this.npm.flatOptions } // we ask pacote for the thing, and then just throw the data // away so that it tee-pipes it into the cache like it does @@ -108,7 +107,7 @@ with --force.`) await pacote.tarball.stream(spec, stream => { stream.resume() return stream.promise() - }, opts) + }, this.npm.flatOptions) } async verify () { diff --git a/lib/ci.js b/lib/ci.js index 3ea19937616e6..692efe3e5e311 100644 --- a/lib/ci.js +++ b/lib/ci.js @@ -30,14 +30,13 @@ class CI extends BaseCommand { } async ci () { - if (this.npm.flatOptions.global) { + if (this.npm.config.get('global')) { const err = new Error('`npm ci` does not work for global packages') err.code = 'ECIGLOBAL' throw err } const where = this.npm.prefix - const { scriptShell, ignoreScripts } = this.npm.flatOptions const arb = new Arborist({ ...this.npm.flatOptions, path: where }) await Promise.all([ @@ -54,6 +53,7 @@ class CI extends BaseCommand { // npm ci should never modify the lockfile or package.json await arb.reify({ ...this.npm.flatOptions, save: false }) + const ignoreScripts = this.npm.config.get('ignore-scripts') // run the same set of scripts that `npm install` runs. if (!ignoreScripts) { const scripts = [ @@ -65,6 +65,7 @@ class CI extends BaseCommand { 'prepare', 'postprepare', ] + const scriptShell = this.npm.config.get('script-shell') || undefined for (const event of scripts) { await runScript({ path: where, diff --git a/lib/dedupe.js b/lib/dedupe.js index 50a56211fc847..4dd759dac48e7 100644 --- a/lib/dedupe.js +++ b/lib/dedupe.js @@ -23,12 +23,13 @@ class Dedupe extends BaseCommand { const dryRun = this.npm.config.get('dry-run') const where = this.npm.prefix - const arb = new Arborist({ + const opts = { ...this.npm.flatOptions, path: where, dryRun, - }) - await arb.dedupe(this.npm.flatOptions) + } + const arb = new Arborist(opts) + await arb.dedupe(opts) await reifyFinish(this.npm, arb) } } diff --git a/lib/diff.js b/lib/diff.js index 0e322ec643849..2212133652312 100644 --- a/lib/diff.js +++ b/lib/diff.js @@ -30,7 +30,7 @@ class Diff extends BaseCommand { get where () { const globalTop = resolve(this.npm.globalDir, '..') - const { global } = this.npm.flatOptions + const global = this.npm.config.get('global') return global ? globalTop : this.npm.prefix } @@ -39,7 +39,7 @@ class Diff extends BaseCommand { } async diff (args) { - const specs = this.npm.flatOptions.diff.filter(d => d) + const specs = this.npm.config.get('diff').filter(d => d) if (specs.length > 2) { throw new TypeError( 'Can\'t use more than two --diff arguments.\n\n' + @@ -91,7 +91,7 @@ class Diff extends BaseCommand { } return [ - `${pkgName}@${this.npm.flatOptions.defaultTag}`, + `${pkgName}@${this.npm.config.get('tag')}`, `file:${this.npm.prefix}`, ] } diff --git a/lib/dist-tag.js b/lib/dist-tag.js index cdc95ac6f0cd7..81773a40a40d4 100644 --- a/lib/dist-tag.js +++ b/lib/dist-tag.js @@ -61,7 +61,7 @@ class DistTag extends BaseCommand { async add (spec, tag, opts) { spec = npa(spec || '') const version = spec.rawSpec - const defaultTag = tag || opts.defaultTag + const defaultTag = tag || this.npm.config.get('tag') log.verbose('dist-tag add', defaultTag, 'to', spec.name + '@' + version) diff --git a/lib/exec.js b/lib/exec.js index b2443b17accd2..8c8606456caec 100644 --- a/lib/exec.js +++ b/lib/exec.js @@ -24,7 +24,7 @@ const BaseCommand = require('./base-command.js') // // npm x -p pkg@version -- foo --registry=/dev/null // -// const pkg = npm.flatOptions.package || getPackageFrom(args[0]) +// const pkg = npm.config.get('package') || getPackageFrom(args[0]) // const cmd = getCommand(pkg, args[0]) // --> npm x -c 'cmd ...args.slice(1)' // @@ -66,7 +66,10 @@ class Exec extends BaseCommand { // When commands go async and we can dump the boilerplate exec methods this // can be named correctly async _exec (args) { - const { package: packages, call, shell } = this.npm.flatOptions + const call = this.npm.config.get('call') + const shell = this.npm.config.get('shell') + // dereferenced because we manipulate it later + const packages = [...this.npm.config.get('package')] if (call && args.length) throw this.usage @@ -165,9 +168,9 @@ class Exec extends BaseCommand { // no need to install if already present if (add.length) { - if (!this.npm.flatOptions.yes) { + if (!this.npm.config.get('yes')) { // set -n to always say no - if (this.npm.flatOptions.yes === false) + if (this.npm.config.get('yes') === false) throw 'canceled' if (!process.stdin.isTTY || ciDetect()) { diff --git a/lib/fund.js b/lib/fund.js index a723c62d2c3c5..a4986f3c473f7 100644 --- a/lib/fund.js +++ b/lib/fund.js @@ -42,9 +42,8 @@ class Fund extends BaseCommand { } async fund (args) { - const opts = this.npm.flatOptions const spec = args[0] - const numberArg = opts.which + const numberArg = this.npm.config.get('which') const fundingSourceNumber = numberArg && parseInt(numberArg, 10) @@ -58,14 +57,14 @@ class Fund extends BaseCommand { throw err } - if (opts.global) { + if (this.npm.config.get('global')) { const err = new Error('`npm fund` does not support global packages') err.code = 'EFUNDGLOBAL' throw err } const where = this.npm.prefix - const arb = new Arborist({ ...opts, path: where }) + const arb = new Arborist({ ...this.npm.flatOptions, path: where }) const tree = await arb.loadActual() if (spec) { @@ -78,23 +77,19 @@ class Fund extends BaseCommand { return } - const print = opts.json - ? this.printJSON - : this.printHuman - - this.npm.output( - print( - getFundingInfo(tree), - opts - ) - ) + if (this.npm.config.get('json')) + this.npm.output(this.printJSON(getFundingInfo(tree))) + else + this.npm.output(this.printHuman(getFundingInfo(tree))) } printJSON (fundingInfo) { return JSON.stringify(fundingInfo, null, 2) } - printHuman (fundingInfo, { color, unicode }) { + printHuman (fundingInfo) { + const color = !!this.npm.color + const unicode = this.npm.config.get('unicode') const seenUrls = new Map() const tree = obj => diff --git a/lib/help-search.js b/lib/help-search.js index 4e727c3e72954..62e1adb59f2f7 100644 --- a/lib/help-search.js +++ b/lib/help-search.js @@ -166,7 +166,7 @@ class HelpSearch extends BaseCommand { out.push(' '.repeat((Math.max(1, cols - out.join(' ').length - r.length - 1)))) out.push(r) - if (!this.npm.flatOptions.long) + if (!this.npm.config.get('long')) return out.join('') out.unshift('\n\n') @@ -198,7 +198,7 @@ class HelpSearch extends BaseCommand { return out.join('') }).join('\n') - const finalOut = results.length && !this.npm.flatOptions.long + const finalOut = results.length && !this.npm.config.get('long') ? 'Top hits for ' + (args.map(JSON.stringify).join(' ')) + '\n' + '—'.repeat(cols - 1) + '\n' + out + '\n' + diff --git a/lib/init.js b/lib/init.js index 42b02dfdc6a77..3d11050e429cc 100644 --- a/lib/init.js +++ b/lib/init.js @@ -59,7 +59,7 @@ class Init extends BaseCommand { this.npm.log.pause() this.npm.log.disableProgress() const initFile = this.npm.config.get('init-module') - if (!this.npm.flatOptions.yes && !this.npm.flatOptions.force) { + if (!this.npm.config.get('yes') && !this.npm.config.get('force')) { this.npm.output([ 'This utility will walk you through creating a package.json file.', 'It only covers the most common items, and tries to guess sensible defaults.', diff --git a/lib/install.js b/lib/install.js index 8df63a219ef74..34d0b23530ac5 100644 --- a/lib/install.js +++ b/lib/install.js @@ -98,7 +98,8 @@ class Install extends BaseCommand { async install (args) { // the /path/to/node_modules/.. const globalTop = resolve(this.npm.globalDir, '..') - const { ignoreScripts, global: isGlobalInstall } = this.npm.flatOptions + const ignoreScripts = this.npm.config.get('ignore-scripts') + const isGlobalInstall = this.npm.config.get('global') const where = isGlobalInstall ? globalTop : this.npm.prefix // don't try to install the prefix into itself @@ -122,7 +123,7 @@ class Install extends BaseCommand { add: args, }) if (!args.length && !isGlobalInstall && !ignoreScripts) { - const { scriptShell } = this.npm.flatOptions + const scriptShell = this.npm.config.get('script-shell') || undefined const scripts = [ 'preinstall', 'install', diff --git a/lib/link.js b/lib/link.js index 66f83d9f5b0a7..9aad6dc1d4e51 100644 --- a/lib/link.js +++ b/lib/link.js @@ -96,7 +96,13 @@ class Link extends BaseCommand { // npm link should not save=true by default unless you're // using any of --save-dev or other types const save = - Boolean(this.npm.config.find('save') !== 'default' || this.npm.flatOptions.saveType) + Boolean( + this.npm.config.find('save') !== 'default' || + this.npm.config.get('save-optional') || + this.npm.config.get('save-peer') || + this.npm.config.get('save-dev') || + this.npm.config.get('save-prod') + ) // create a new arborist instance for the local prefix and // reify all the pending names as symlinks there diff --git a/lib/logout.js b/lib/logout.js index b3f64f671d326..3784020ec67bc 100644 --- a/lib/logout.js +++ b/lib/logout.js @@ -19,9 +19,10 @@ class Logout extends BaseCommand { } async logout (args) { - const { registry, scope } = this.npm.flatOptions + const registry = this.npm.config.get('registry') + const scope = this.npm.config.get('scope') const regRef = scope ? `${scope}:registry` : 'registry' - const reg = this.npm.flatOptions[regRef] || registry + const reg = this.npm.config.get(regRef) || registry const auth = getAuth(reg, this.npm.flatOptions) diff --git a/lib/ls.js b/lib/ls.js index 9ff2761c2f928..3b73f4d1e4cc5 100644 --- a/lib/ls.js +++ b/lib/ls.js @@ -43,24 +43,22 @@ class LS extends BaseCommand { } async ls (args) { - const { - all, - color, - depth, - json, - long, - global, - parseable, - prefix, - unicode, - } = this.npm.flatOptions - const path = global ? resolve(this.npm.globalDir, '..') : prefix + const all = this.npm.config.get('all') + const color = !!this.npm.color + const depth = this.npm.config.get('depth') const dev = this.npm.config.get('dev') const development = this.npm.config.get('development') + const global = this.npm.config.get('global') + const json = this.npm.config.get('json') const link = this.npm.config.get('link') + const long = this.npm.config.get('long') const only = this.npm.config.get('only') + const parseable = this.npm.config.get('parseable') const prod = this.npm.config.get('prod') const production = this.npm.config.get('production') + const unicode = this.npm.config.get('unicode') + + const path = global ? resolve(this.npm.globalDir, '..') : this.npm.prefix const arb = new Arborist({ global, diff --git a/lib/outdated.js b/lib/outdated.js index 7225577ea42d4..6a7b4e7b3e9e3 100644 --- a/lib/outdated.js +++ b/lib/outdated.js @@ -28,15 +28,13 @@ class Outdated extends BaseCommand { } async outdated (args) { - this.opts = this.npm.flatOptions - const global = path.resolve(this.npm.globalDir, '..') - const where = this.opts.global + const where = this.npm.config.get('global') ? global : this.npm.prefix const arb = new Arborist({ - ...this.opts, + ...this.npm.flatOptions, path: where, }) @@ -51,7 +49,7 @@ class Outdated extends BaseCommand { this.getEdges(nodes, 'edgesIn') } } else { - if (this.opts.all) { + if (this.npm.config.get('all')) { // all deps in tree const nodes = this.tree.inventory.values() this.getEdges(nodes, 'edgesOut') @@ -68,13 +66,13 @@ class Outdated extends BaseCommand { const outdated = this.list.sort((a, b) => a.name.localeCompare(b.name)) // return if no outdated packages - if (outdated.length === 0 && !this.opts.json) + if (outdated.length === 0 && !this.npm.config.get('json')) return // display results - if (this.opts.json) + if (this.npm.config.get('json')) this.npm.output(this.makeJSON(outdated)) - else if (this.opts.parseable) + else if (this.npm.config.get('parseable')) this.npm.output(this.makeParseable(outdated)) else { const outList = outdated.map(x => this.makePretty(x)) @@ -86,11 +84,11 @@ class Outdated extends BaseCommand { 'Depended by', ] - if (this.opts.long) + if (this.npm.config.get('long')) outHead.push('Package Type', 'Homepage') const outTable = [outHead].concat(outList) - if (this.opts.color) + if (this.npm.color) outTable[0] = outTable[0].map(heading => styles.underline(heading)) const tableOpts = { @@ -117,7 +115,7 @@ class Outdated extends BaseCommand { } getEdgesOut (node) { - if (this.opts.global) { + if (this.npm.config.get('global')) { for (const child of node.children.values()) this.edges.add(child) } else { @@ -129,7 +127,7 @@ class Outdated extends BaseCommand { async getPackument (spec) { const packument = await pacote.packument(spec, { ...this.npm.flatOptions, - fullMetadata: this.npm.flatOptions.long, + fullMetadata: this.npm.config.get('long'), preferOnline: true, }) return packument @@ -146,7 +144,7 @@ class Outdated extends BaseCommand { : edge.dev ? 'devDependencies' : 'dependencies' - for (const omitType of this.opts.omit || []) { + for (const omitType of this.npm.config.get('omit') || []) { if (node[omitType]) return } @@ -213,12 +211,12 @@ class Outdated extends BaseCommand { const columns = [name, current, wanted, latest, location, dependent] - if (this.opts.long) { + if (this.npm.config.get('long')) { columns[6] = type columns[7] = homepage } - if (this.opts.color) { + if (this.npm.color) { columns[0] = color[current === wanted ? 'yellow' : 'red'](columns[0]) // current columns[2] = color.green(columns[2]) // wanted columns[3] = color.magenta(columns[3]) // latest @@ -248,7 +246,7 @@ class Outdated extends BaseCommand { name + '@' + latest, dependent, ] - if (this.opts.long) + if (this.npm.config.get('long')) out.push(type, homepage) return out.join(':') @@ -275,7 +273,7 @@ class Outdated extends BaseCommand { dependent, location: path, } - if (this.opts.long) { + if (this.npm.config.get('long')) { out[name].type = type out[name].homepage = homepage } diff --git a/lib/pack.js b/lib/pack.js index 326fcc0cd1441..cdd823430410b 100644 --- a/lib/pack.js +++ b/lib/pack.js @@ -29,12 +29,12 @@ class Pack extends BaseCommand { if (args.length === 0) args = ['.'] - const { unicode } = this.npm.flatOptions + const unicode = this.npm.config.get('unicode') // clone the opts because pacote mutates it with resolved/integrity const tarballs = await Promise.all(args.map(async (arg) => { const spec = npa(arg) - const { dryRun } = this.npm.flatOptions + const dryRun = this.npm.config.get('dry-run') const manifest = await pacote.manifest(spec, this.npm.flatOptions) const filename = `${manifest.name}-${manifest.version}.tgz` .replace(/^@/, '').replace(/\//, '-') diff --git a/lib/ping.js b/lib/ping.js index e60b1f1debd80..53467c93b061d 100644 --- a/lib/ping.js +++ b/lib/ping.js @@ -18,14 +18,14 @@ class Ping extends BaseCommand { } async ping (args) { - log.notice('PING', this.npm.flatOptions.registry) + log.notice('PING', this.npm.config.get('registry')) const start = Date.now() const details = await pingUtil(this.npm.flatOptions) const time = Date.now() - start log.notice('PONG', `${time / 1000}ms`) - if (this.npm.flatOptions.json) { + if (this.npm.config.get('json')) { this.npm.output(JSON.stringify({ - registry: this.npm.flatOptions.registry, + registry: this.npm.config.get('registry'), time, details, }, null, 2)) diff --git a/lib/profile.js b/lib/profile.js index 1c0df49888540..3d4857df02187 100644 --- a/lib/profile.js +++ b/lib/profile.js @@ -108,14 +108,14 @@ class Profile extends BaseCommand { async get (args) { const tfa = 'two-factor auth' - const conf = { ...this.npm.flatOptions } - - const info = await pulseTillDone.withPromise(npmProfile.get(conf)) + const info = await pulseTillDone.withPromise( + npmProfile.get(this.npm.flatOptions) + ) if (!info.cidr_whitelist) delete info.cidr_whitelist - if (conf.json) { + if (this.npm.config.get('json')) { this.npm.output(JSON.stringify(info, null, 2)) return } @@ -147,7 +147,7 @@ class Profile extends BaseCommand { .join('\t') this.npm.output(values) } else { - if (conf.parseable) { + if (this.npm.config.get('parseable')) { for (const key of Object.keys(info)) { if (key === 'tfa') this.npm.output(`${key}\t${cleaned[tfa]}`) @@ -165,7 +165,7 @@ class Profile extends BaseCommand { } async set (args) { - const conf = { ...this.npm.flatOptions } + const conf = this.npm.flatOptions const prop = (args[0] || '').toLowerCase().trim() let value = args.length > 1 ? args.slice(1).join(' ') : null @@ -214,9 +214,9 @@ class Profile extends BaseCommand { const result = await otplease(conf, conf => npmProfile.set(newUser, conf)) - if (conf.json) + if (this.npm.config.get('json')) this.npm.output(JSON.stringify({ [prop]: result[prop] }, null, 2)) - else if (conf.parseable) + else if (this.npm.config.get('parseable')) this.npm.output(prop + '\t' + result[prop]) else if (result[prop] != null) this.npm.output('Set', prop, 'to', result[prop]) @@ -239,11 +239,10 @@ class Profile extends BaseCommand { ) } - const conf = { ...this.npm.flatOptions } - if (conf.json || conf.parseable) { + if (this.npm.config.get('json') || this.npm.config.get('parseable')) { throw new Error( 'Enabling two-factor authentication is an interactive operation and ' + - (conf.json ? 'JSON' : 'parseable') + ' output mode is not available' + (this.npm.config.get('json') ? 'JSON' : 'parseable') + ' output mode is not available' ) } @@ -255,7 +254,7 @@ class Profile extends BaseCommand { // if they're using legacy auth currently then we have to // update them to a bearer token before continuing. - const creds = this.npm.config.getCredentialsByURI(conf.registry) + const creds = this.npm.config.getCredentialsByURI(this.npm.config.get('registry')) const auth = {} if (creds.token) @@ -267,32 +266,29 @@ class Profile extends BaseCommand { auth.basic = { username: basic[0], password: basic[1] } } - if (conf.otp) - auth.otp = conf.otp - if (!auth.basic && !auth.token) { throw new Error( 'You need to be logged in to registry ' + - `${conf.registry} in order to enable 2fa` + `${this.npm.config.get('registry')} in order to enable 2fa` ) } if (auth.basic) { log.info('profile', 'Updating authentication to bearer token') const result = await npmProfile.createToken( - auth.basic.password, false, [], conf + auth.basic.password, false, [], this.npm.flatOptions ) if (!result.token) { throw new Error( - `Your registry ${conf.registry} does not seem to ` + + `Your registry ${this.npm.config.get('registry')} does not seem to ` + 'support bearer tokens. Bearer tokens are required for ' + 'two-factor authentication' ) } this.npm.config.setCredentialsByURI( - conf.registry, + this.npm.config.get('registry'), { token: result.token } ) await this.npm.config.save('user') @@ -303,21 +299,21 @@ class Profile extends BaseCommand { info.tfa.password = password log.info('profile', 'Determine if tfa is pending') - const userInfo = await pulseTillDone.withPromise(npmProfile.get(conf)) + const userInfo = await pulseTillDone.withPromise( + npmProfile.get(this.npm.flatOptions) + ) + const conf = { ...this.npm.flatOptions } if (userInfo && userInfo.tfa && userInfo.tfa.pending) { log.info('profile', 'Resetting two-factor authentication') await pulseTillDone.withPromise( npmProfile.set({ tfa: { password, mode: 'disable' } }, conf) ) } else if (userInfo && userInfo.tfa) { - if (conf.otp) - conf.otp = conf.otp - else { - const otp = await readUserInfo.otp( + if (!conf.otp) { + conf.otp = await readUserInfo.otp( 'Enter one-time password from your authenticator app: ' ) - conf.otp = otp } } @@ -390,9 +386,9 @@ class Profile extends BaseCommand { tfa: { password: password, mode: 'disable' }, }, conf)) - if (conf.json) + if (this.npm.config.get('json')) this.npm.output(JSON.stringify({ tfa: false }, null, 2)) - else if (conf.parseable) + else if (this.npm.config.get('parseable')) this.npm.output('tfa\tfalse') else this.npm.output('Two factor authentication disabled.') diff --git a/lib/publish.js b/lib/publish.js index f8e0eafe11886..f46f70291bebc 100644 --- a/lib/publish.js +++ b/lib/publish.js @@ -43,12 +43,16 @@ class Publish extends BaseCommand { log.verbose('publish', args) - const opts = { ...this.npm.flatOptions } - const { unicode, dryRun, json, defaultTag } = opts + const unicode = this.npm.config.get('unicode') + const dryRun = this.npm.config.get('dry-run') + const json = this.npm.config.get('json') + const defaultTag = this.npm.config.get('tag') if (semver.validRange(defaultTag)) throw new Error('Tag name must not be a valid SemVer range: ' + defaultTag.trim()) + const opts = { ...this.npm.flatOptions } + // you can publish name@version, ./foo.tgz, etc. // even though the default is the 'file:.' cwd. const spec = npa(args[0]) diff --git a/lib/rebuild.js b/lib/rebuild.js index 74f5ae5f6eba5..c11747b97749c 100644 --- a/lib/rebuild.js +++ b/lib/rebuild.js @@ -27,7 +27,7 @@ class Rebuild extends BaseCommand { async rebuild (args) { const globalTop = resolve(this.npm.globalDir, '..') - const where = this.npm.flatOptions.global ? globalTop : this.npm.prefix + const where = this.npm.config.get('global') ? globalTop : this.npm.prefix const arb = new Arborist({ ...this.npm.flatOptions, path: where, diff --git a/lib/run-script.js b/lib/run-script.js index 3ea85b79ffd18..f6ca57b79b4dd 100644 --- a/lib/run-script.js +++ b/lib/run-script.js @@ -49,7 +49,9 @@ class RunScript extends BaseCommand { async run (args) { const path = this.npm.localPrefix const event = args.shift() - const { scriptShell } = this.npm.flatOptions + // this || undefined is because runScript will be unhappy with the default + // null value + const scriptShell = this.npm.config.get('script-shell') || undefined const pkg = await readJson(`${path}/package.json`) const { scripts = {} } = pkg @@ -75,7 +77,7 @@ class RunScript extends BaseCommand { // positional args only added to the main event, not pre/post const events = [[event, args]] - if (!this.npm.flatOptions.ignoreScripts) { + if (!this.npm.config.get('ignore-scripts')) { if (scripts[`pre${event}`]) events.unshift([`pre${event}`, []]) @@ -113,12 +115,12 @@ class RunScript extends BaseCommand { if (log.level === 'silent') return allScripts - if (this.npm.flatOptions.json) { + if (this.npm.config.get('json')) { this.npm.output(JSON.stringify(scripts, null, 2)) return allScripts } - if (this.npm.flatOptions.parseable) { + if (this.npm.config.get('parseable')) { for (const [script, cmd] of Object.entries(scripts)) this.npm.output(`${script}:${cmd}`) diff --git a/lib/search.js b/lib/search.js index c24000156f67a..4c206498f70f8 100644 --- a/lib/search.js +++ b/lib/search.js @@ -84,7 +84,7 @@ class Search extends BaseCommand { }) await p.promise() - if (!anyOutput && !opts.json && !opts.parseable) + if (!anyOutput && !this.npm.config.get('json') && !this.npm.config.get('parseable')) this.npm.output('No matches found for ' + (args.map(JSON.stringify).join(' '))) log.silly('search', 'search completed') diff --git a/lib/shrinkwrap.js b/lib/shrinkwrap.js index b52cf41957b2d..76979b0212ffa 100644 --- a/lib/shrinkwrap.js +++ b/lib/shrinkwrap.js @@ -24,7 +24,7 @@ class Shrinkwrap extends BaseCommand { // // loadVirtual, fall back to loadActual // rename shrinkwrap file type, and tree.meta.save() - if (this.npm.flatOptions.global) { + if (this.npm.config.get('global')) { const er = new Error('`npm shrinkwrap` does not work for global packages') er.code = 'ESHRINKWRAPGLOBAL' throw er diff --git a/lib/star.js b/lib/star.js index 27a3041906a40..9a8ee8c77871b 100644 --- a/lib/star.js +++ b/lib/star.js @@ -26,7 +26,7 @@ class Star extends BaseCommand { // if we're unstarring, then show an empty star image // otherwise, show the full star image - const { unicode } = this.npm.flatOptions + const unicode = this.npm.config.get('unicode') const unstar = this.npm.config.get('star.unstar') const full = unicode ? '\u2605 ' : '(*)' const empty = unicode ? '\u2606 ' : '( )' diff --git a/lib/uninstall.js b/lib/uninstall.js index ee0f338e9fc0a..c1c36ab6b2bde 100644 --- a/lib/uninstall.js +++ b/lib/uninstall.js @@ -28,7 +28,8 @@ class Uninstall extends BaseCommand { async uninstall (args) { // the /path/to/node_modules/.. - const { global, prefix } = this.npm.flatOptions + const global = this.npm.config.get('global') + const prefix = this.npm.config.get('prefix') const path = global ? resolve(this.npm.globalDir, '..') : prefix if (!args.length) { diff --git a/lib/unpublish.js b/lib/unpublish.js index 68a9a0ae64ee5..06b2779b268fe 100644 --- a/lib/unpublish.js +++ b/lib/unpublish.js @@ -63,8 +63,9 @@ class Unpublish extends BaseCommand { throw new Error(this.usage) const spec = args.length && npa(args[0]) - const opts = this.npm.flatOptions - const { force, silent, loglevel } = opts + const force = this.npm.config.get('force') + const silent = this.npm.config.get('silent') + const loglevel = this.npm.config.get('loglevel') let pkgName let pkgVersion @@ -79,6 +80,7 @@ class Unpublish extends BaseCommand { ) } + const opts = this.npm.flatOptions if (!spec || path.resolve(spec.name) === this.npm.localPrefix) { // if there's a package.json in the current folder, then // read the package name and version out of that. diff --git a/lib/update.js b/lib/update.js index 87540b96e07e5..8ce52a442f3df 100644 --- a/lib/update.js +++ b/lib/update.js @@ -30,11 +30,11 @@ class Update extends BaseCommand { async update (args) { const update = args.length === 0 ? true : args const global = path.resolve(this.npm.globalDir, '..') - const where = this.npm.flatOptions.global + const where = this.npm.config.get('global') ? global : this.npm.prefix - if (this.npm.flatOptions.depth) { + if (this.npm.config.get('depth')) { log.warn('update', 'The --depth option no longer has any effect. See RFC0019.\n' + 'https://github.com/npm/rfcs/blob/latest/implemented/0019-remove-update-depth-option.md') } diff --git a/lib/utils/ping.js b/lib/utils/ping.js index f5f7fcc6a6258..00956d0c1630c 100644 --- a/lib/utils/ping.js +++ b/lib/utils/ping.js @@ -1,7 +1,7 @@ // ping the npm registry // used by the ping and doctor commands const fetch = require('npm-registry-fetch') -module.exports = async (opts) => { - const res = await fetch('/-/ping?write=true', opts) +module.exports = async (flatOptions) => { + const res = await fetch('/-/ping?write=true', flatOptions) return res.json().catch(() => ({})) } diff --git a/lib/utils/read-local-package.js b/lib/utils/read-local-package.js index c31bca994704c..21506ca180a0f 100644 --- a/lib/utils/read-local-package.js +++ b/lib/utils/read-local-package.js @@ -1,11 +1,12 @@ const { resolve } = require('path') const readJson = require('read-package-json-fast') async function readLocalPackageName (npm) { - if (npm.flatOptions.global) + if (npm.config.get('global')) return - const filepath = resolve(npm.flatOptions.prefix, 'package.json') - return (await readJson(filepath)).name + const filepath = resolve(npm.prefix, 'package.json') + const json = await readJson(filepath) + return json.name } module.exports = readLocalPackageName diff --git a/lib/version.js b/lib/version.js index 2eda9d11b737c..30097a3c337e9 100644 --- a/lib/version.js +++ b/lib/version.js @@ -45,7 +45,7 @@ class Version extends BaseCommand { } async change (args) { - const prefix = this.npm.flatOptions.tagVersionPrefix + const prefix = this.npm.config.get('tag-version-prefix') const version = await libversion(args[0], { ...this.npm.flatOptions, path: this.npm.prefix, @@ -71,7 +71,7 @@ class Version extends BaseCommand { for (const [key, version] of Object.entries(process.versions)) results[key] = version - if (this.npm.flatOptions.json) + if (this.npm.config.get('json')) this.npm.output(JSON.stringify(results, null, 2)) else this.npm.output(results) diff --git a/lib/view.js b/lib/view.js index 0a6688fc2b13d..7bbedcf65cd8e 100644 --- a/lib/view.js +++ b/lib/view.js @@ -41,9 +41,9 @@ class View extends BaseCommand { fullMetadata: true, preferOnline: true, } - const { defaultTag } = config const spec = npa(opts.conf.argv.remain[2]) const pckmnt = await packument(spec, config) + const defaultTag = this.npm.config.get('tag') const dv = pckmnt.versions[pckmnt['dist-tags'][defaultTag]] pckmnt.versions = Object.keys(pckmnt.versions).sort(semver.compareLoose) @@ -99,7 +99,7 @@ class View extends BaseCommand { const name = nv.name const local = (name === '.' || !name) - if (opts.global && local) + if (this.npm.config.get('global') && local) throw new Error('Cannot use view command in global mode.') if (local) { @@ -114,7 +114,7 @@ class View extends BaseCommand { } // get the data about this package - let version = nv.rawSpec || this.npm.flatOptions.defaultTag + let version = nv.rawSpec || this.npm.config.get('tag') const pckmnt = await packument(nv, opts) @@ -159,42 +159,43 @@ class View extends BaseCommand { } if ( - !opts.json && + !this.npm.config.get('json') && args.length === 1 && args[0] === '' ) { // general view pckmnt.version = version await Promise.all( - results.map((v) => this.prettyView(pckmnt, v[Object.keys(v)[0]][''], opts)) + results.map((v) => this.prettyView(pckmnt, v[Object.keys(v)[0]][''])) ) return retval } else { // view by field name - await this.printData(retval, pckmnt._id, opts) + await this.printData(retval, pckmnt._id) return retval } } - async printData (data, name, opts) { + async printData (data, name) { const versions = Object.keys(data) let msg = '' let msgJson = [] const includeVersions = versions.length > 1 let includeFields + const json = this.npm.config.get('json') versions.forEach((v) => { const fields = Object.keys(data[v]) includeFields = includeFields || (fields.length > 1) - if (opts.json) + if (json) msgJson.push({}) fields.forEach((f) => { let d = cleanup(data[v][f]) - if (fields.length === 1 && opts.json) + if (fields.length === 1 && json) msgJson[msgJson.length - 1][f] = d if (includeVersions || includeFields || typeof d !== 'string') { - if (opts.json) + if (json) msgJson[msgJson.length - 1][f] = d else { d = inspect(d, { @@ -204,10 +205,10 @@ class View extends BaseCommand { maxArrayLength: null, }) } - } else if (typeof d === 'string' && opts.json) + } else if (typeof d === 'string' && json) d = JSON.stringify(d) - if (!opts.json) { + if (!json) { if (f && includeFields) f += ' = ' msg += (includeVersions ? name + '@' + v + ' ' : '') + @@ -216,7 +217,7 @@ class View extends BaseCommand { }) }) - if (opts.json) { + if (json) { if (msgJson.length && Object.keys(msgJson[0]).length === 1) { const k = Object.keys(msgJson[0])[0] msgJson = msgJson.map(m => m[k]) @@ -236,9 +237,9 @@ class View extends BaseCommand { console.log(msg.trim()) } - async prettyView (packument, manifest, opts) { + async prettyView (packument, manifest) { // More modern, pretty printing of default view - const unicode = opts.unicode + const unicode = this.npm.config.get('unicode') const tags = [] Object.keys(packument['dist-tags']).forEach((t) => { diff --git a/lib/whoami.js b/lib/whoami.js index 2322c5fd80e5d..180eb331759ef 100644 --- a/lib/whoami.js +++ b/lib/whoami.js @@ -22,9 +22,10 @@ class Whoami extends BaseCommand { } async whoami (args) { - const opts = this.npm.flatOptions - const username = await getIdentity(this.npm, opts) - this.npm.output(opts.json ? JSON.stringify(username) : username) + const username = await getIdentity(this.npm, this.npm.flatOptions) + this.npm.output( + this.npm.config.get('json') ? JSON.stringify(username) : username + ) } } module.exports = Whoami diff --git a/test/fixtures/mock-npm.js b/test/fixtures/mock-npm.js new file mode 100644 index 0000000000000..c47758111fd40 --- /dev/null +++ b/test/fixtures/mock-npm.js @@ -0,0 +1,22 @@ +// Basic npm fixture that you can give a config object that acts like +// npm.config You still need a separate flatOptions but this is the first step +// to eventually just using npm itself + +const mockNpm = (base = {}) => { + const config = base.config || {} + const flatOptions = base.flatOptions || {} + return { + ...base, + flatOptions, + config: { + // for now just set `find` to what config.find should return + // this works cause `find` is not an existing config entry + find: (k) => config[k], + get: (k) => config[k], + set: (k, v) => config[k] = v, + list: [config] + }, + } +} + +module.exports = mockNpm diff --git a/test/lib/audit.js b/test/lib/audit.js index d291ef87948c9..a25e6b0e27740 100644 --- a/test/lib/audit.js +++ b/test/lib/audit.js @@ -1,5 +1,6 @@ const t = require('tap') const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') t.test('should audit using Arborist', t => { let ARB_ARGS = null @@ -9,15 +10,15 @@ t.test('should audit using Arborist', t => { let OUTPUT_CALLED = false let ARB_OBJ = null - const npm = { + const npm = mockNpm({ prefix: 'foo', - flatOptions: { + config: { json: false, }, output: () => { OUTPUT_CALLED = true }, - } + }) const Audit = requireInject('../../lib/audit.js', { 'npm-audit-report': () => { AUDIT_REPORT_CALLED = true @@ -65,13 +66,13 @@ t.test('should audit using Arborist', t => { }) t.test('should audit - json', t => { - const npm = { + const npm = mockNpm({ prefix: 'foo', - flatOptions: { + config: { json: true, }, output: () => {}, - } + }) const Audit = requireInject('../../lib/audit.js', { 'npm-audit-report': () => ({ @@ -98,9 +99,12 @@ t.test('report endpoint error', t => { t.test(`json=${json}`, t => { const OUTPUT = [] const LOGS = [] - const npm = { + const npm = mockNpm({ prefix: 'foo', command: 'audit', + config: { + json, + }, flatOptions: { json, }, @@ -110,7 +114,7 @@ t.test('report endpoint error', t => { output: (...msg) => { OUTPUT.push(msg) }, - } + }) const Audit = requireInject('../../lib/audit.js', { 'npm-audit-report': () => { throw new Error('should not call audit report when there are errors') diff --git a/test/lib/bin.js b/test/lib/bin.js index 428b2e3bad4ab..1d9341169be15 100644 --- a/test/lib/bin.js +++ b/test/lib/bin.js @@ -1,5 +1,6 @@ const { test } = require('tap') const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') test('bin', (t) => { t.plan(4) @@ -7,13 +8,13 @@ test('bin', (t) => { const Bin = require('../../lib/bin.js') - const npm = { + const npm = mockNpm({ bin: dir, - flatOptions: { global: false }, + config: { global: false }, output: (output) => { t.equal(output, dir, 'prints the correct directory') }, - } + }) const bin = new Bin(npm) t.match(bin.usage, 'bin', 'usage has command name in it') @@ -39,13 +40,13 @@ test('bin -g', (t) => { '../../lib/utils/path.js': [dir], }) - const npm = { + const npm = mockNpm({ bin: dir, - flatOptions: { global: true }, + config: { global: true }, output: (output) => { t.equal(output, dir, 'prints the correct directory') }, - } + }) const bin = new Bin(npm) bin.exec([], (err) => { @@ -69,13 +70,13 @@ test('bin -g (not in path)', (t) => { const Bin = requireInject('../../lib/bin.js', { '../../lib/utils/path.js': ['/not/my/dir'], }) - const npm = { + const npm = mockNpm({ bin: dir, - flatOptions: { global: true }, + config: { global: true }, output: (output) => { t.equal(output, dir, 'prints the correct directory') }, - } + }) const bin = new Bin(npm) bin.exec([], (err) => { diff --git a/test/lib/cache.js b/test/lib/cache.js index 773adc6a8a304..0fdf768568479 100644 --- a/test/lib/cache.js +++ b/test/lib/cache.js @@ -1,23 +1,12 @@ const t = require('tap') const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') const path = require('path') const usageUtil = () => 'usage instructions' -const flatOptions = { - force: false, -} - let outputOutput = [] -const npm = { - flatOptions, - cache: '/fake/path', - output: (msg) => { - outputOutput.push(msg) - }, -} - let rimrafPath = '' const rimraf = (path, cb) => { rimrafPath = path @@ -66,6 +55,14 @@ const Cache = requireInject('../../lib/cache.js', { '../../lib/utils/usage.js': usageUtil, }) +const npm = mockNpm({ + cache: '/fake/path', + flatOptions: { force: false }, + config: { force: false }, + output: (msg) => { + outputOutput.push(msg) + }, +}) const cache = new Cache(npm) t.test('cache no args', t => { @@ -83,10 +80,12 @@ t.test('cache clean', t => { }) t.test('cache clean (force)', t => { - flatOptions.force = true + npm.config.set('force', true) + npm.flatOptions.force = true t.teardown(() => { rimrafPath = '' - flatOptions.force = false + npm.config.force = false + npm.flatOptions.force = false }) cache.exec(['clear'], err => { @@ -131,7 +130,7 @@ t.test('cache add pkg only', t => { ['silly', 'cache add', 'spec', 'mypkg'], ], 'logs correctly') t.equal(tarballStreamSpec, 'mypkg', 'passes the correct spec to pacote') - t.same(tarballStreamOpts, flatOptions, 'passes the correct options to pacote') + t.same(tarballStreamOpts, npm.flatOptions, 'passes the correct options to pacote') t.end() }) }) @@ -150,7 +149,7 @@ t.test('cache add pkg w/ spec modifier', t => { ['silly', 'cache add', 'spec', 'mypkg@latest'], ], 'logs correctly') t.equal(tarballStreamSpec, 'mypkg@latest', 'passes the correct spec to pacote') - t.same(tarballStreamOpts, flatOptions, 'passes the correct options to pacote') + t.same(tarballStreamOpts, npm.flatOptions, 'passes the correct options to pacote') t.end() }) }) diff --git a/test/lib/ci.js b/test/lib/ci.js index 3419218ef9d8b..7f06a6cebcbc1 100644 --- a/test/lib/ci.js +++ b/test/lib/ci.js @@ -5,6 +5,7 @@ const readdir = util.promisify(fs.readdir) const { test } = require('tap') const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') test('should ignore scripts with --ignore-scripts', (t) => { const SCRIPTS = [] @@ -22,17 +23,15 @@ test('should ignore scripts with --ignore-scripts', (t) => { }, }) - const ci = new CI({ + const npm = mockNpm({ globalDir: 'path/to/node_modules/', prefix: 'foo', - flatOptions: { - global: false, - ignoreScripts: true, - }, config: { - get: () => false, + global: false, + 'ignore-scripts': true, }, }) + const ci = new CI(npm) ci.exec([], er => { if (er) @@ -115,12 +114,13 @@ test('should use Arborist and run-script', (t) => { }, }) - const ci = new CI({ + const npm = mockNpm({ prefix: path, - flatOptions: { + config: { global: false, }, }) + const ci = new CI(npm) ci.exec(null, er => { if (er) @@ -146,12 +146,13 @@ test('should pass flatOptions to Arborist.reify', (t) => { } }, }) - const ci = new CI({ + const npm = mockNpm({ prefix: 'foo', flatOptions: { production: true, }, }) + const ci = new CI(npm) ci.exec(null, er => { if (er) throw er @@ -173,14 +174,15 @@ test('should throw if package-lock.json or npm-shrinkwrap missing', (t) => { }, }, }) - const ci = new CI({ + const npm = mockNpm({ prefix: testDir, - flatOptions: { + config: { global: false, }, }) + const ci = new CI(npm) ci.exec(null, (err, res) => { - t.ok(err, 'throws error when there is no package-lock') + t.match(err, /package-lock.json/, 'throws error when there is no package-lock') t.notOk(res) t.end() }) @@ -191,12 +193,13 @@ test('should throw ECIGLOBAL', (t) => { '@npmcli/run-script': opts => {}, '../../lib/utils/reify-finish.js': async () => {}, }) - const ci = new CI({ + const npm = mockNpm({ prefix: 'foo', - flatOptions: { + config: { global: true, }, }) + const ci = new CI(npm) ci.exec(null, (err, res) => { t.equals(err.code, 'ECIGLOBAL', 'throws error with global packages') t.notOk(res) @@ -227,12 +230,13 @@ test('should remove existing node_modules before installing', (t) => { }, }) - const ci = new CI({ + const npm = mockNpm({ prefix: testDir, - flatOptions: { + config: { global: false, }, }) + const ci = new CI(npm) ci.exec(null, er => { if (er) diff --git a/test/lib/dedupe.js b/test/lib/dedupe.js index 3e8b2f4c01347..851163f935e27 100644 --- a/test/lib/dedupe.js +++ b/test/lib/dedupe.js @@ -1,20 +1,13 @@ const { test } = require('tap') const requireInject = require('require-inject') - -const npm = (base) => { - const config = base.config - return { - ...base, - flatOptions: { dryRun: false }, - config: { - get: (k) => config[k], - }, - } -} +const mockNpm = require('../fixtures/mock-npm') test('should throw in global mode', (t) => { const Dedupe = requireInject('../../lib/dedupe.js') - const dedupe = new Dedupe(npm({ config: { global: true }})) + const npm = mockNpm({ + config: { 'dry-run': false, global: true }, + }) + const dedupe = new Dedupe(npm) dedupe.exec([], er => { t.match(er, { code: 'EDEDUPEGLOBAL' }, 'throws EDEDUPEGLOBAL') @@ -36,12 +29,13 @@ test('should remove dupes using Arborist', (t) => { t.ok(arb, 'gets arborist tree') }, }) - const dedupe = new Dedupe(npm({ + const npm = mockNpm({ prefix: 'foo', config: { 'dry-run': 'true', }, - })) + }) + const dedupe = new Dedupe(npm) dedupe.exec([], er => { if (er) throw er @@ -53,17 +47,18 @@ test('should remove dupes using Arborist', (t) => { test('should remove dupes using Arborist - no arguments', (t) => { const Dedupe = requireInject('../../lib/dedupe.js', { '@npmcli/arborist': function (args) { - t.ok(args.dryRun, 'gets dryRun from flatOptions') + t.ok(args.dryRun, 'gets dryRun from config') this.dedupe = () => {} }, '../../lib/utils/reify-output.js': () => {}, }) - const dedupe = new Dedupe(npm({ + const npm = mockNpm({ prefix: 'foo', config: { - 'dry-run': true, + 'dry-run': 'true', }, - })) + }) + const dedupe = new Dedupe(npm) dedupe.exec(null, () => { t.end() }) diff --git a/test/lib/diff.js b/test/lib/diff.js index 9f58505dca6ad..08761c64c86b4 100644 --- a/test/lib/diff.js +++ b/test/lib/diff.js @@ -1,30 +1,35 @@ const { resolve } = require('path') const t = require('tap') const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') const noop = () => null let libnpmdiff = noop let rlp = () => 'foo' -const defaultFlatOptions = { - defaultTag: 'latest', + +const config = { + global: false, + tag: 'latest', diff: [], +} +const flatOptions = { + global: false, diffUnified: null, diffIgnoreAllSpace: false, diffNoPrefix: false, diffSrcPrefix: '', diffDstPrefix: '', diffText: false, - prefix: '.', savePrefix: '^', } -const npm = { +const npm = mockNpm({ globalDir: __dirname, - flatOptions: { ...defaultFlatOptions }, - get prefix () { - return this.flatOptions.prefix - }, + prefix: '.', + config, + flatOptions, output: noop, -} +}) + const mocks = { npmlog: { info: noop, verbose: noop }, libnpmdiff: (...args) => libnpmdiff(...args), @@ -34,10 +39,21 @@ const mocks = { } t.afterEach(cb => { - npm.flatOptions = { ...defaultFlatOptions } + config.global = false + config.tag = 'latest' + config.diff = [] + flatOptions.global = false + flatOptions.diffUnified = null + flatOptions.diffIgnoreAllSpace = false + flatOptions.diffNoPrefix = false + flatOptions.diffSrcPrefix = '' + flatOptions.diffDstPrefix = '' + flatOptions.diffText = false + flatOptions.savePrefix = '^' + npm.globalDir = __dirname + npm.prefix = '..' libnpmdiff = noop rlp = () => 'foo' - npm.globalDir = __dirname cb() }) @@ -55,7 +71,7 @@ t.test('no args', t => { t.match(opts, npm.flatOptions, 'should forward flat options') } - npm.flatOptions.prefix = path + npm.prefix = path diff.exec([], err => { if (err) throw err @@ -102,10 +118,11 @@ t.test('single arg', t => { t.equal(a, 'foo@1.0.0', 'should forward single spec') t.equal(b, `file:${path}`, 'should compare to cwd') t.match(opts, npm.flatOptions, 'should forward flat options') + t.end() } - npm.flatOptions.diff = ['foo@1.0.0'] - npm.flatOptions.prefix = path + config.diff = ['foo@1.0.0'] + npm.prefix = path diff.exec([], err => { if (err) throw err @@ -118,8 +135,8 @@ t.test('single arg', t => { throw new Error('ERR') } - npm.flatOptions.diff = ['foo@1.0.0'] - npm.flatOptions.prefix = path + config.diff = ['foo@1.0.0'] + npm.prefix = path diff.exec([], err => { t.match( err, @@ -140,8 +157,8 @@ t.test('single arg', t => { t.match(opts, npm.flatOptions, 'should forward flat options') } - npm.flatOptions.diff = ['foo@~1.0.0'] - npm.flatOptions.prefix = path + config.diff = ['foo@~1.0.0'] + npm.prefix = path diff.exec([], err => { if (err) throw err @@ -158,8 +175,8 @@ t.test('single arg', t => { t.match(opts, npm.flatOptions, 'should forward flat options') } - npm.flatOptions.diff = ['2.1.4'] - npm.flatOptions.prefix = path + config.diff = ['2.1.4'] + npm.prefix = path diff.exec([], err => { if (err) throw err @@ -171,7 +188,7 @@ t.test('single arg', t => { throw new Error('ERR') } - npm.flatOptions.diff = ['2.1.4'] + config.diff = ['2.1.4'] diff.exec([], err => { t.match( err, @@ -198,8 +215,8 @@ t.test('single arg', t => { }, 'should forward flatOptions and diffFiles') } - npm.flatOptions.diff = ['2.1.4'] - npm.flatOptions.prefix = path + config.diff = ['2.1.4'] + npm.prefix = path diff.exec(['./foo.js', './bar.js'], err => { if (err) throw err @@ -221,8 +238,8 @@ t.test('single arg', t => { t.equal(b, `file:${path}`, 'should compare to cwd') } - npm.flatOptions.diff = ['bar@1.0.0'] - npm.flatOptions.prefix = path + config.diff = ['bar@1.0.0'] + npm.prefix = path diff.exec([], err => { if (err) @@ -248,8 +265,8 @@ t.test('single arg', t => { t.match(opts, npm.flatOptions, 'should forward flat options') } - npm.flatOptions.diff = ['simple-output'] - npm.flatOptions.prefix = path + config.diff = ['simple-output'] + npm.prefix = path diff.exec([], err => { if (err) throw err @@ -262,8 +279,8 @@ t.test('single arg', t => { throw new Error('ERR') } - npm.flatOptions.diff = ['bar'] - npm.flatOptions.prefix = path + config.diff = ['bar'] + npm.prefix = path diff.exec([], err => { t.match( err, @@ -294,8 +311,8 @@ t.test('single arg', t => { }), }) - npm.flatOptions.diff = ['bar'] - npm.flatOptions.prefix = path + config.diff = ['bar'] + npm.prefix = path const Diff = requireInject('../../lib/diff.js', { ...mocks, @@ -355,9 +372,10 @@ t.test('single arg', t => { }, }) - npm.flatOptions.global = true - npm.flatOptions.diff = ['lorem'] - npm.flatOptions.prefix = resolve(path, 'project') + config.global = true + flatOptions.global = true + config.diff = ['lorem'] + npm.prefix = resolve(path, 'project') npm.globalDir = resolve(path, 'globalDir/lib/node_modules') const Diff = requireInject('../../lib/diff.js', { @@ -409,8 +427,8 @@ t.test('single arg', t => { t.equal(b, 'bar@2.0.0', 'should have expected comparison spec') } - npm.flatOptions.diff = ['bar@2.0.0'] - npm.flatOptions.prefix = path + config.diff = ['bar@2.0.0'] + npm.prefix = path diff.exec([], err => { if (err) @@ -466,8 +484,8 @@ t.test('single arg', t => { }) const diff = new Diff(npm) - npm.flatOptions.diff = ['lorem'] - npm.flatOptions.prefix = path + config.diff = ['lorem'] + npm.prefix = path diff.exec([], err => { if (err) @@ -499,8 +517,8 @@ t.test('single arg', t => { }) const diff = new Diff(npm) - npm.flatOptions.diff = ['lorem'] - npm.flatOptions.prefix = path + config.diff = ['lorem'] + npm.prefix = path diff.exec([], err => { if (err) @@ -518,8 +536,8 @@ t.test('single arg', t => { t.equal(b, `file:${path}`, 'should compare to cwd') } - npm.flatOptions.diff = ['bar'] - npm.flatOptions.prefix = path + config.diff = ['bar'] + npm.prefix = path diff.exec([], err => { if (err) @@ -537,8 +555,8 @@ t.test('single arg', t => { t.equal(b, `file:${path}`, 'should compare to cwd') } - npm.flatOptions.diff = ['my-project'] - npm.flatOptions.prefix = path + config.diff = ['my-project'] + npm.prefix = path diff.exec([], err => { if (err) throw err @@ -555,8 +573,8 @@ t.test('single arg', t => { t.equal(b, `file:${path}`, 'should compare to cwd') } - npm.flatOptions.diff = ['/path/to/other-dir'] - npm.flatOptions.prefix = path + config.diff = ['/path/to/other-dir'] + npm.prefix = path diff.exec([], err => { if (err) throw err @@ -566,7 +584,7 @@ t.test('single arg', t => { t.test('unsupported spec type', t => { rlp = async () => 'my-project' - npm.flatOptions.diff = ['git+https://github.com/user/foo'] + config.diff = ['git+https://github.com/user/foo'] diff.exec([], err => { t.match( @@ -591,7 +609,7 @@ t.test('first arg is a qualified spec', t => { t.match(opts, npm.flatOptions, 'should forward flat options') } - npm.flatOptions.diff = ['bar@1.0.0', 'bar@^2.0.0'] + config.diff = ['bar@1.0.0', 'bar@^2.0.0'] diff.exec([], err => { if (err) throw err @@ -624,8 +642,8 @@ t.test('first arg is a qualified spec', t => { t.equal(b, `bar@file:${resolve(path, 'node_modules/bar')}`, 'should target local node_modules pkg') } - npm.flatOptions.prefix = path - npm.flatOptions.diff = ['bar@2.0.0', 'bar'] + npm.prefix = path + config.diff = ['bar@2.0.0', 'bar'] diff.exec([], err => { if (err) throw err @@ -635,7 +653,7 @@ t.test('first arg is a qualified spec', t => { t.test('second arg is a valid semver version', t => { t.plan(2) - npm.flatOptions.diff = ['bar@1.0.0', '2.0.0'] + config.diff = ['bar@1.0.0', '2.0.0'] libnpmdiff = async ([a, b], opts) => { t.equal(a, 'bar@1.0.0', 'should set expected first spec') @@ -656,7 +674,7 @@ t.test('first arg is a qualified spec', t => { t.equal(b, 'bar-fork@latest', 'should target latest tag if not a dep') } - npm.flatOptions.diff = ['bar@1.0.0', 'bar-fork'] + config.diff = ['bar@1.0.0', 'bar-fork'] diff.exec([], err => { if (err) throw err @@ -693,8 +711,8 @@ t.test('first arg is a known dependency name', t => { t.equal(b, 'bar@2.0.0', 'should set expected second spec') } - npm.flatOptions.prefix = path - npm.flatOptions.diff = ['bar', 'bar@2.0.0'] + npm.prefix = path + config.diff = ['bar', 'bar@2.0.0'] diff.exec([], err => { if (err) throw err @@ -733,8 +751,8 @@ t.test('first arg is a known dependency name', t => { t.equal(b, `bar-fork@file:${resolve(path, 'node_modules/bar-fork')}`, 'should target fork local node_modules pkg') } - npm.flatOptions.prefix = path - npm.flatOptions.diff = ['bar', 'bar-fork'] + npm.prefix = path + config.diff = ['bar', 'bar-fork'] diff.exec([], err => { if (err) throw err @@ -767,8 +785,8 @@ t.test('first arg is a known dependency name', t => { t.equal(b, 'bar@2.0.0', 'should use package name from first arg') } - npm.flatOptions.prefix = path - npm.flatOptions.diff = ['bar', '2.0.0'] + npm.prefix = path + config.diff = ['bar', '2.0.0'] diff.exec([], err => { if (err) throw err @@ -801,8 +819,8 @@ t.test('first arg is a known dependency name', t => { t.equal(b, 'bar-fork@latest', 'should set expected second spec') } - npm.flatOptions.prefix = path - npm.flatOptions.diff = ['bar', 'bar-fork'] + npm.prefix = path + config.diff = ['bar', 'bar-fork'] diff.exec([], err => { if (err) throw err @@ -816,7 +834,7 @@ t.test('first arg is a valid semver range', t => { t.test('second arg is a qualified spec', t => { t.plan(2) - npm.flatOptions.diff = ['1.0.0', 'bar@2.0.0'] + config.diff = ['1.0.0', 'bar@2.0.0'] libnpmdiff = async ([a, b], opts) => { t.equal(a, 'bar@1.0.0', 'should use name from second arg') @@ -855,8 +873,8 @@ t.test('first arg is a valid semver range', t => { t.equal(b, `bar@file:${resolve(path, 'node_modules/bar')}`, 'should set expected second spec from nm') } - npm.flatOptions.prefix = path - npm.flatOptions.diff = ['1.0.0', 'bar'] + npm.prefix = path + config.diff = ['1.0.0', 'bar'] diff.exec([], err => { if (err) throw err @@ -872,7 +890,7 @@ t.test('first arg is a valid semver range', t => { t.equal(b, 'my-project@2.0.0', 'should use name from project dir') } - npm.flatOptions.diff = ['1.0.0', '2.0.0'] + config.diff = ['1.0.0', '2.0.0'] diff.exec([], err => { if (err) throw err @@ -885,8 +903,8 @@ t.test('first arg is a valid semver range', t => { throw new Error('ERR') } - npm.flatOptions.diff = ['1.0.0', '2.0.0'] - npm.flatOptions.prefix = path + config.diff = ['1.0.0', '2.0.0'] + npm.prefix = path diff.exec([], err => { t.match( err, @@ -906,7 +924,7 @@ t.test('first arg is a valid semver range', t => { t.equal(b, 'bar@latest', 'should compare against latest tag') } - npm.flatOptions.diff = ['1.0.0', 'bar'] + config.diff = ['1.0.0', 'bar'] diff.exec([], err => { if (err) throw err @@ -937,8 +955,8 @@ t.test('first arg is a valid semver range', t => { }) const diff = new Diff(npm) - npm.flatOptions.diff = ['1.0.0', 'lorem@2.0.0'] - npm.flatOptions.prefix = path + config.diff = ['1.0.0', 'lorem@2.0.0'] + npm.prefix = path diff.exec([], err => { if (err) @@ -960,7 +978,7 @@ t.test('first arg is an unknown dependency name', t => { t.match(opts, { where: '.' }, 'should forward pacote options') } - npm.flatOptions.diff = ['bar', 'bar@2.0.0'] + config.diff = ['bar', 'bar@2.0.0'] diff.exec([], err => { if (err) throw err @@ -993,8 +1011,8 @@ t.test('first arg is an unknown dependency name', t => { t.equal(b, `bar@file:${resolve(path, 'node_modules/bar')}`, 'should target local node_modules pkg') } - npm.flatOptions.prefix = path - npm.flatOptions.diff = ['bar-fork', 'bar'] + npm.prefix = path + config.diff = ['bar-fork', 'bar'] diff.exec([], err => { if (err) throw err @@ -1009,7 +1027,7 @@ t.test('first arg is an unknown dependency name', t => { t.equal(b, 'bar@^1.0.0', 'should use name from first arg') } - npm.flatOptions.diff = ['bar', '^1.0.0'] + config.diff = ['bar', '^1.0.0'] diff.exec([], err => { if (err) throw err @@ -1024,7 +1042,7 @@ t.test('first arg is an unknown dependency name', t => { t.equal(b, 'bar-fork@latest', 'should use latest tag') } - npm.flatOptions.diff = ['bar', 'bar-fork'] + config.diff = ['bar', 'bar-fork'] diff.exec([], err => { if (err) throw err @@ -1043,8 +1061,8 @@ t.test('first arg is an unknown dependency name', t => { t.equal(b, 'bar-fork@latest', 'should use latest tag') } - npm.flatOptions.diff = ['bar', 'bar-fork'] - npm.flatOptions.prefix = path + config.diff = ['bar', 'bar-fork'] + npm.prefix = path diff.exec([], err => { if (err) @@ -1059,7 +1077,7 @@ t.test('various options', t => { t.test('using --name-only option', t => { t.plan(1) - npm.flatOptions.diffNameOnly = true + flatOptions.diffNameOnly = true libnpmdiff = async ([a, b], opts) => { t.match(opts, { @@ -1077,7 +1095,7 @@ t.test('various options', t => { t.test('set files after both versions', t => { t.plan(3) - npm.flatOptions.diff = ['2.1.4', '3.0.0'] + config.diff = ['2.1.4', '3.0.0'] libnpmdiff = async ([a, b], opts) => { t.equal(a, 'foo@2.1.4', 'should use expected spec') @@ -1114,7 +1132,7 @@ t.test('various options', t => { }, 'should forward all remaining items as filenames') } - npm.flatOptions.prefix = path + npm.prefix = path diff.exec(['./foo.js', './bar.js'], err => { if (err) throw err @@ -1124,12 +1142,12 @@ t.test('various options', t => { t.test('using diff option', t => { t.plan(1) - npm.flatOptions.diffContext = 5 - npm.flatOptions.diffIgnoreWhitespace = true - npm.flatOptions.diffNoPrefix = false - npm.flatOptions.diffSrcPrefix = 'foo/' - npm.flatOptions.diffDstPrefix = 'bar/' - npm.flatOptions.diffText = true + flatOptions.diffContext = 5 + flatOptions.diffIgnoreWhitespace = true + flatOptions.diffNoPrefix = false + flatOptions.diffSrcPrefix = 'foo/' + flatOptions.diffDstPrefix = 'bar/' + flatOptions.diffText = true libnpmdiff = async ([a, b], opts) => { t.match(opts, { @@ -1153,7 +1171,7 @@ t.test('various options', t => { }) t.test('too many args', t => { - npm.flatOptions.diff = ['a', 'b', 'c'] + config.diff = ['a', 'b', 'c'] diff.exec([], err => { t.match( err, diff --git a/test/lib/dist-tag.js b/test/lib/dist-tag.js index a3c05bb2b3a15..9415dacbe4756 100644 --- a/test/lib/dist-tag.js +++ b/test/lib/dist-tag.js @@ -1,18 +1,10 @@ const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') const { test } = require('tap') -let prefix let result = '' let log = '' -// these declared opts are used in ./utils/read-local-package.js -const _flatOptions = { - global: false, - get prefix () { - return prefix - }, -} - const routeMap = { '/-/package/@scoped%2fpkg/dist-tags': { latest: '1.0.0', @@ -60,20 +52,18 @@ const DistTag = requireInject('../../lib/dist-tag.js', { }, }) -const distTag = new DistTag({ - flatOptions: _flatOptions, +const npm = mockNpm({ config: { - get (key) { - return _flatOptions[key] - }, + global: false, }, output: msg => { result = msg }, }) +const distTag = new DistTag(npm) test('ls in current package', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: '@scoped/pkg', }), @@ -91,7 +81,7 @@ test('ls in current package', (t) => { }) test('no args in current package', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: '@scoped/pkg', }), @@ -109,7 +99,7 @@ test('no args in current package', (t) => { }) test('borked cmd usage', (t) => { - prefix = t.testdir({}) + npm.prefix = t.testdir({}) distTag.exec(['borked', '@scoped/pkg'], (err) => { t.matchSnapshot(err, 'should show usage error') result = '' @@ -119,7 +109,7 @@ test('borked cmd usage', (t) => { }) test('ls on named package', (t) => { - prefix = t.testdir({}) + npm.prefix = t.testdir({}) distTag.exec(['ls', '@scoped/another'], (err) => { t.ifError(err, 'npm dist-tags ls') t.matchSnapshot( @@ -133,7 +123,7 @@ test('ls on named package', (t) => { }) test('ls on missing package', (t) => { - prefix = t.testdir({}) + npm.prefix = t.testdir({}) distTag.exec(['ls', 'foo'], (err) => { t.matchSnapshot( log, @@ -150,7 +140,7 @@ test('ls on missing package', (t) => { }) test('ls on missing name in current package', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ version: '1.0.0', }), @@ -167,7 +157,7 @@ test('ls on missing name in current package', (t) => { }) test('only named package arg', (t) => { - prefix = t.testdir({}) + npm.prefix = t.testdir({}) distTag.exec(['@scoped/another'], (err) => { t.ifError(err, 'npm dist-tags ls') t.matchSnapshot( @@ -186,7 +176,7 @@ test('add new tag', (t) => { t.equal(opts.method, 'PUT', 'should trigger request to add new tag') t.equal(opts.body, '7.7.7', 'should point to expected version') } - prefix = t.testdir({}) + npm.prefix = t.testdir({}) distTag.exec(['add', '@scoped/another@7.7.7', 'c'], (err) => { t.ifError(err, 'npm dist-tags add') t.matchSnapshot( @@ -201,7 +191,7 @@ test('add new tag', (t) => { }) test('add using valid semver range as name', (t) => { - prefix = t.testdir({}) + npm.prefix = t.testdir({}) distTag.exec(['add', '@scoped/another@7.7.7', '1.0.0'], (err) => { t.match( err, @@ -219,7 +209,7 @@ test('add using valid semver range as name', (t) => { }) test('add missing args', (t) => { - prefix = t.testdir({}) + npm.prefix = t.testdir({}) distTag.exec(['add', '@scoped/another@7.7.7'], (err) => { t.matchSnapshot(err, 'should exit usage error message') result = '' @@ -229,7 +219,7 @@ test('add missing args', (t) => { }) test('add missing pkg name', (t) => { - prefix = t.testdir({}) + npm.prefix = t.testdir({}) distTag.exec(['add', null], (err) => { t.matchSnapshot(err, 'should exit usage error message') result = '' @@ -239,7 +229,7 @@ test('add missing pkg name', (t) => { }) test('set existing version', (t) => { - prefix = t.testdir({}) + npm.prefix = t.testdir({}) distTag.exec(['set', '@scoped/another@0.6.0', 'b'], (err) => { t.ifError(err, 'npm dist-tags set') t.matchSnapshot( @@ -256,7 +246,7 @@ test('remove existing tag', (t) => { npmRegistryFetchMock = async (url, opts) => { t.equal(opts.method, 'DELETE', 'should trigger request to remove tag') } - prefix = t.testdir({}) + npm.prefix = t.testdir({}) distTag.exec(['rm', '@scoped/another', 'c'], (err) => { t.ifError(err, 'npm dist-tags rm') t.matchSnapshot(log, 'should log remove info') @@ -269,7 +259,7 @@ test('remove existing tag', (t) => { }) test('remove non-existing tag', (t) => { - prefix = t.testdir({}) + npm.prefix = t.testdir({}) distTag.exec(['rm', '@scoped/another', 'nonexistent'], (err) => { t.match( err, @@ -284,7 +274,7 @@ test('remove non-existing tag', (t) => { }) test('remove missing pkg name', (t) => { - prefix = t.testdir({}) + npm.prefix = t.testdir({}) distTag.exec(['rm', null], (err) => { t.matchSnapshot(err, 'should exit usage error message') result = '' diff --git a/test/lib/exec.js b/test/lib/exec.js index eb9fef6a61da2..5e859a57a3129 100644 --- a/test/lib/exec.js +++ b/test/lib/exec.js @@ -1,5 +1,6 @@ const t = require('tap') const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') const { resolve, delimiter } = require('path') const OUTPUT = [] const output = (...msg) => OUTPUT.push(msg) @@ -25,25 +26,23 @@ class Arborist { let PROGRESS_ENABLED = true const LOG_WARN = [] let PROGRESS_IGNORED = false -const npm = { - flatOptions: { - yes: true, - call: '', - package: [], - legacyPeerDeps: false, - shell: 'shell-cmd', - }, +const flatOptions = { + legacyPeerDeps: false, + package: [], +} +const config = { + cache: 'cache-dir', + yes: true, + call: '', + package: [], + shell: 'shell-cmd', +} +const npm = mockNpm({ + flatOptions, + config, localPrefix: 'local-prefix', localBin: 'local-bin', globalBin: 'global-bin', - config: { - get: k => { - if (k !== 'cache') - throw new Error('unexpected config get') - - return 'cache-dir' - }, - }, log: { disableProgress: () => { PROGRESS_ENABLED = false @@ -56,7 +55,7 @@ const npm = { }, }, output, -} +}) const RUN_SCRIPTS = [] const runScript = async opt => { @@ -108,9 +107,11 @@ t.afterEach(cb => { READ_ERROR = null LOG_WARN.length = 0 PROGRESS_IGNORED = false - npm.flatOptions.legacyPeerDeps = false - npm.flatOptions.package = [] - npm.flatOptions.call = '' + flatOptions.legacyPeerDeps = false + config.package = [] + flatOptions.package = [] + config.call = '' + config.yes = true npm.localBin = 'local-bin' npm.globalBin = 'global-bin' cb() @@ -186,7 +187,7 @@ t.test('npm exec foo, already present locally', t => { if (er) throw er t.strictSame(MKDIRPS, [], 'no need to make any dirs') - t.match(ARB_CTOR, [{ package: ['foo'], path }]) + t.match(ARB_CTOR, [{ path }]) t.strictSame(ARB_REIFY, [], 'no need to reify anything') t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') t.match(RUN_SCRIPTS, [{ @@ -300,7 +301,7 @@ t.test('npm exec foo, not present locally or in central loc', t => { if (er) throw er t.strictSame(MKDIRPS, [installDir], 'need to make install dir') - t.match(ARB_CTOR, [{ package: ['foo'], path }]) + t.match(ARB_CTOR, [{ path }]) t.match(ARB_REIFY, [{add: ['foo@'], legacyPeerDeps: false}], 'need to install foo@') t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') const PATH = `${resolve(installDir, 'node_modules', '.bin')}${delimiter}${process.env.PATH}` @@ -340,7 +341,7 @@ t.test('npm exec foo, not present locally but in central loc', t => { if (er) throw er t.strictSame(MKDIRPS, [installDir], 'need to make install dir') - t.match(ARB_CTOR, [{ package: ['foo'], path }]) + t.match(ARB_CTOR, [{ path }]) t.match(ARB_REIFY, [], 'no need to install again, already there') t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') const PATH = `${resolve(installDir, 'node_modules', '.bin')}${delimiter}${process.env.PATH}` @@ -380,7 +381,7 @@ t.test('npm exec foo, present locally but wrong version', t => { if (er) throw er t.strictSame(MKDIRPS, [installDir], 'need to make install dir') - t.match(ARB_CTOR, [{ package: ['foo'], path }]) + t.match(ARB_CTOR, [{ path }]) t.match(ARB_REIFY, [{ add: ['foo@2.x'], legacyPeerDeps: false }], 'need to add foo@2.x') t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') const PATH = `${resolve(installDir, 'node_modules', '.bin')}${delimiter}${process.env.PATH}` @@ -412,7 +413,8 @@ t.test('npm exec --package=foo bar', t => { }, _from: 'foo@', } - npm.flatOptions.package = ['foo'] + config.package = ['foo'] + flatOptions.package = ['foo'] exec.exec(['bar', 'one arg', 'two arg'], er => { if (er) throw er @@ -459,7 +461,7 @@ t.test('npm exec @foo/bar -- --some=arg, locally installed', t => { if (er) throw er t.strictSame(MKDIRPS, [], 'no need to make any dirs') - t.match(ARB_CTOR, [{ package: ['@foo/bar'], path }]) + t.match(ARB_CTOR, [{ path }]) t.strictSame(ARB_REIFY, [], 'no need to reify anything') t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') t.match(RUN_SCRIPTS, [{ @@ -502,7 +504,7 @@ t.test('npm exec @foo/bar, with same bin alias and no unscoped named bin, locall if (er) throw er t.strictSame(MKDIRPS, [], 'no need to make any dirs') - t.match(ARB_CTOR, [{ package: ['@foo/bar'], path }]) + t.match(ARB_CTOR, [{ path }]) t.strictSame(ARB_REIFY, [], 'no need to reify anything') t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') t.match(RUN_SCRIPTS, [{ @@ -552,7 +554,7 @@ t.test('run command with 2 packages, need install, verify sort', t => { t.plan(cases.length) for (const packages of cases) { t.test(packages.join(', '), t => { - npm.flatOptions.package = packages + config.package = packages const add = packages.map(p => `${p}@`).sort((a, b) => a.localeCompare(b)) const path = t.testdir() const installDir = resolve('cache-dir/_npx/07de77790e5f40f2') @@ -583,7 +585,7 @@ t.test('run command with 2 packages, need install, verify sort', t => { if (er) throw er t.strictSame(MKDIRPS, [installDir], 'need to make install dir') - t.match(ARB_CTOR, [{ package: packages, path }]) + t.match(ARB_CTOR, [{ path }]) t.match(ARB_REIFY, [{add, legacyPeerDeps: false}], 'need to install both packages') t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') const PATH = `${resolve(installDir, 'node_modules', '.bin')}${delimiter}${process.env.PATH}` @@ -652,8 +654,8 @@ t.test('npm exec foo, many bins in package, none named foo', t => { t.test('npm exec -p foo -c "ls -laF"', t => { const path = t.testdir() npm.localPrefix = path - npm.flatOptions.package = ['foo'] - npm.flatOptions.call = 'ls -laF' + config.package = ['foo'] + config.call = 'ls -laF' ARB_ACTUAL_TREE[path] = { children: new Map([['foo', { name: 'foo', version: '1.2.3' }]]), } @@ -666,7 +668,7 @@ t.test('npm exec -p foo -c "ls -laF"', t => { if (er) throw er t.strictSame(MKDIRPS, [], 'no need to make any dirs') - t.match(ARB_CTOR, [{ package: ['foo'], path }]) + t.match(ARB_CTOR, [{ path }]) t.strictSame(ARB_REIFY, [], 'no need to reify anything') t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') t.match(RUN_SCRIPTS, [{ @@ -683,7 +685,7 @@ t.test('npm exec -p foo -c "ls -laF"', t => { }) t.test('positional args and --call together is an error', t => { - npm.flatOptions.call = 'true' + config.call = 'true' exec.exec(['foo'], er => { t.equal(er, exec.usage) t.end() @@ -705,8 +707,8 @@ t.test('prompt when installs are needed if not already present and shell is a TT const packages = ['foo', 'bar'] READ_RESULT = 'yolo' - npm.flatOptions.package = packages - npm.flatOptions.yes = undefined + config.package = packages + config.yes = undefined const add = packages.map(p => `${p}@`).sort((a, b) => a.localeCompare(b)) const path = t.testdir() @@ -738,7 +740,7 @@ t.test('prompt when installs are needed if not already present and shell is a TT if (er) throw er t.strictSame(MKDIRPS, [installDir], 'need to make install dir') - t.match(ARB_CTOR, [{ package: packages, path }]) + t.match(ARB_CTOR, [{ path }]) t.match(ARB_REIFY, [{add, legacyPeerDeps: false}], 'need to install both packages') t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') const PATH = `${resolve(installDir, 'node_modules', '.bin')}${delimiter}${process.env.PATH}` @@ -774,8 +776,8 @@ t.test('skip prompt when installs are needed if not already present and shell is const packages = ['foo', 'bar'] READ_RESULT = 'yolo' - npm.flatOptions.package = packages - npm.flatOptions.yes = undefined + config.package = packages + config.yes = undefined const add = packages.map(p => `${p}@`).sort((a, b) => a.localeCompare(b)) const path = t.testdir() @@ -807,7 +809,7 @@ t.test('skip prompt when installs are needed if not already present and shell is if (er) throw er t.strictSame(MKDIRPS, [installDir], 'need to make install dir') - t.match(ARB_CTOR, [{ package: packages, path }]) + t.match(ARB_CTOR, [{ path }]) t.match(ARB_REIFY, [{add, legacyPeerDeps: false}], 'need to install both packages') t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') const PATH = `${resolve(installDir, 'node_modules', '.bin')}${delimiter}${process.env.PATH}` @@ -841,8 +843,8 @@ t.test('skip prompt when installs are needed if not already present and shell is const packages = ['foo'] READ_RESULT = 'yolo' - npm.flatOptions.package = packages - npm.flatOptions.yes = undefined + config.package = packages + config.yes = undefined const add = packages.map(p => `${p}@`).sort((a, b) => a.localeCompare(b)) const path = t.testdir() @@ -866,7 +868,7 @@ t.test('skip prompt when installs are needed if not already present and shell is if (er) throw er t.strictSame(MKDIRPS, [installDir], 'need to make install dir') - t.match(ARB_CTOR, [{ package: packages, path }]) + t.match(ARB_CTOR, [{ path }]) t.match(ARB_REIFY, [{add, legacyPeerDeps: false}], 'need to install the package') t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') const PATH = `${resolve(installDir, 'node_modules', '.bin')}${delimiter}${process.env.PATH}` @@ -900,8 +902,8 @@ t.test('abort if prompt rejected', t => { const packages = ['foo', 'bar'] READ_RESULT = 'no, why would I want such a thing??' - npm.flatOptions.package = packages - npm.flatOptions.yes = undefined + config.package = packages + config.yes = undefined const path = t.testdir() const installDir = resolve('cache-dir/_npx/07de77790e5f40f2') @@ -931,7 +933,7 @@ t.test('abort if prompt rejected', t => { exec.exec(['foobar'], er => { t.equal(er, 'canceled', 'should be canceled') t.strictSame(MKDIRPS, [installDir], 'need to make install dir') - t.match(ARB_CTOR, [{ package: packages, path }]) + t.match(ARB_CTOR, [{ path }]) t.strictSame(ARB_REIFY, [], 'no install performed') t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') t.strictSame(RUN_SCRIPTS, []) @@ -958,8 +960,8 @@ t.test('abort if prompt false', t => { const packages = ['foo', 'bar'] READ_ERROR = 'canceled' - npm.flatOptions.package = packages - npm.flatOptions.yes = undefined + config.package = packages + config.yes = undefined const path = t.testdir() const installDir = resolve('cache-dir/_npx/07de77790e5f40f2') @@ -989,7 +991,7 @@ t.test('abort if prompt false', t => { exec.exec(['foobar'], er => { t.equal(er, 'canceled', 'should be canceled') t.strictSame(MKDIRPS, [installDir], 'need to make install dir') - t.match(ARB_CTOR, [{ package: packages, path }]) + t.match(ARB_CTOR, [{ path }]) t.strictSame(ARB_REIFY, [], 'no install performed') t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') t.strictSame(RUN_SCRIPTS, []) @@ -1015,8 +1017,8 @@ t.test('abort if -n provided', t => { const packages = ['foo', 'bar'] - npm.flatOptions.package = packages - npm.flatOptions.yes = false + config.package = packages + config.yes = false const path = t.testdir() const installDir = resolve('cache-dir/_npx/07de77790e5f40f2') @@ -1046,7 +1048,7 @@ t.test('abort if -n provided', t => { exec.exec(['foobar'], er => { t.equal(er, 'canceled', 'should be canceled') t.strictSame(MKDIRPS, [installDir], 'need to make install dir') - t.match(ARB_CTOR, [{ package: packages, path }]) + t.match(ARB_CTOR, [{ path }]) t.strictSame(ARB_REIFY, [], 'no install performed') t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') t.strictSame(RUN_SCRIPTS, []) @@ -1073,8 +1075,8 @@ t.test('forward legacyPeerDeps opt', t => { }, _from: 'foo@', } - npm.flatOptions.yes = true - npm.flatOptions.legacyPeerDeps = true + config.yes = true + flatOptions.legacyPeerDeps = true exec.exec(['foo'], er => { if (er) throw er diff --git a/test/lib/fund.js b/test/lib/fund.js index 2ae604a653632..bd600dbf75c01 100644 --- a/test/lib/fund.js +++ b/test/lib/fund.js @@ -1,5 +1,6 @@ const { test } = require('tap') const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') const version = '1.0.0' const funding = { @@ -180,11 +181,10 @@ const conflictingFundingPackages = { let result = '' let printUrl = '' -const _flatOptions = { +const config = { color: false, json: false, global: false, - prefix: undefined, unicode: false, which: undefined, } @@ -192,7 +192,7 @@ const openUrl = async (npm, url, msg) => { if (url === 'http://npmjs.org') throw new Error('ERROR') - if (_flatOptions.json) { + if (config.json) { printUrl = JSON.stringify({ title: msg, url: url, @@ -210,18 +210,16 @@ const Fund = requireInject('../../lib/fund.js', { : Promise.reject(new Error('ERROR')), }, }) -const fund = new Fund({ - flatOptions: _flatOptions, - get prefix () { - return _flatOptions.prefix - }, +const npm = mockNpm({ + config, output: msg => { result += msg + '\n' }, }) +const fund = new Fund(npm) test('fund with no package containing funding', t => { - _flatOptions.prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'no-funding-package', version: '0.0.0', @@ -237,7 +235,7 @@ test('fund with no package containing funding', t => { }) test('fund in which same maintainer owns all its deps', t => { - _flatOptions.prefix = t.testdir(maintainerOwnsAllDeps) + npm.prefix = t.testdir(maintainerOwnsAllDeps) fund.exec([], (err) => { t.ifError(err, 'should not error out') @@ -248,8 +246,8 @@ test('fund in which same maintainer owns all its deps', t => { }) test('fund in which same maintainer owns all its deps, using --json option', t => { - _flatOptions.json = true - _flatOptions.prefix = t.testdir(maintainerOwnsAllDeps) + config.json = true + npm.prefix = t.testdir(maintainerOwnsAllDeps) fund.exec([], (err) => { t.ifError(err, 'should not error out') @@ -281,13 +279,13 @@ test('fund in which same maintainer owns all its deps, using --json option', t = ) result = '' - _flatOptions.json = false + config.json = false t.end() }) }) test('fund containing multi-level nested deps with no funding', t => { - _flatOptions.prefix = t.testdir(nestedNoFundingPackages) + npm.prefix = t.testdir(nestedNoFundingPackages) fund.exec([], (err) => { t.ifError(err, 'should not error out') @@ -302,8 +300,8 @@ test('fund containing multi-level nested deps with no funding', t => { }) test('fund containing multi-level nested deps with no funding, using --json option', t => { - _flatOptions.prefix = t.testdir(nestedNoFundingPackages) - _flatOptions.json = true + npm.prefix = t.testdir(nestedNoFundingPackages) + config.json = true fund.exec([], (err) => { t.ifError(err, 'should not error out') @@ -328,14 +326,14 @@ test('fund containing multi-level nested deps with no funding, using --json opti ) result = '' - _flatOptions.json = false + config.json = false t.end() }) }) test('fund containing multi-level nested deps with no funding, using --json option', t => { - _flatOptions.prefix = t.testdir(nestedMultipleFundingPackages) - _flatOptions.json = true + npm.prefix = t.testdir(nestedMultipleFundingPackages) + config.json = true fund.exec([], (err) => { t.ifError(err, 'should not error out') @@ -385,26 +383,26 @@ test('fund containing multi-level nested deps with no funding, using --json opti ) result = '' - _flatOptions.json = false + config.json = false t.end() }) }) test('fund does not support global', t => { - _flatOptions.prefix = t.testdir({}) - _flatOptions.global = true + npm.prefix = t.testdir({}) + config.global = true fund.exec([], (err) => { t.match(err.code, 'EFUNDGLOBAL', 'should throw EFUNDGLOBAL error') result = '' - _flatOptions.global = false + config.global = false t.end() }) }) test('fund using package argument', t => { - _flatOptions.prefix = t.testdir(maintainerOwnsAllDeps) + npm.prefix = t.testdir(maintainerOwnsAllDeps) fund.exec(['.'], (err) => { t.ifError(err, 'should not error out') @@ -416,9 +414,9 @@ test('fund using package argument', t => { }) test('fund does not support global, using --json option', t => { - _flatOptions.prefix = t.testdir({}) - _flatOptions.global = true - _flatOptions.json = true + npm.prefix = t.testdir({}) + config.global = true + config.json = true fund.exec([], (err) => { t.equal(err.code, 'EFUNDGLOBAL', 'should use EFUNDGLOBAL error code') @@ -428,14 +426,14 @@ test('fund does not support global, using --json option', t => { 'should use expected error msg' ) - _flatOptions.global = false - _flatOptions.json = false + config.global = false + config.json = false t.end() }) }) test('fund using string shorthand', t => { - _flatOptions.prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'funding-string-shorthand', version: '0.0.0', @@ -453,7 +451,7 @@ test('fund using string shorthand', t => { }) test('fund using nested packages with multiple sources', t => { - _flatOptions.prefix = t.testdir(nestedMultipleFundingPackages) + npm.prefix = t.testdir(nestedMultipleFundingPackages) fund.exec(['.'], (err) => { t.ifError(err, 'should not error out') @@ -465,7 +463,7 @@ test('fund using nested packages with multiple sources', t => { }) test('fund using symlink ref', t => { - _flatOptions.prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'using-symlink-ref', version: '1.0.0', @@ -511,7 +509,7 @@ test('fund using symlink ref', t => { }) test('fund using data from actual tree', t => { - _flatOptions.prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'using-actual-tree', version: '1.0.0', @@ -558,22 +556,22 @@ test('fund using data from actual tree', t => { }) test('fund using nested packages with multiple sources, with a source number', t => { - _flatOptions.prefix = t.testdir(nestedMultipleFundingPackages) - _flatOptions.which = '1' + npm.prefix = t.testdir(nestedMultipleFundingPackages) + config.which = '1' fund.exec(['.'], (err) => { t.ifError(err, 'should not error out') t.matchSnapshot(printUrl, 'should open the numbered URL') - _flatOptions.which = undefined + config.which = undefined printUrl = '' t.end() }) }) test('fund using pkg name while having conflicting versions', t => { - _flatOptions.prefix = t.testdir(conflictingFundingPackages) - _flatOptions.which = '1' + npm.prefix = t.testdir(conflictingFundingPackages) + config.which = '1' fund.exec(['foo'], (err) => { t.ifError(err, 'should not error out') @@ -585,8 +583,8 @@ test('fund using pkg name while having conflicting versions', t => { }) test('fund using package argument with no browser, using --json option', t => { - _flatOptions.prefix = t.testdir(maintainerOwnsAllDeps) - _flatOptions.json = true + npm.prefix = t.testdir(maintainerOwnsAllDeps) + config.json = true fund.exec(['.'], (err) => { t.ifError(err, 'should not error out') @@ -599,14 +597,14 @@ test('fund using package argument with no browser, using --json option', t => { 'should open funding url using json output' ) - _flatOptions.json = false + config.json = false printUrl = '' t.end() }) }) test('fund using package info fetch from registry', t => { - _flatOptions.prefix = t.testdir({}) + npm.prefix = t.testdir({}) fund.exec(['ntl'], (err) => { t.ifError(err, 'should not error out') @@ -622,7 +620,7 @@ test('fund using package info fetch from registry', t => { }) test('fund tries to use package info fetch from registry but registry has nothing', t => { - _flatOptions.prefix = t.testdir({}) + npm.prefix = t.testdir({}) fund.exec(['foo'], (err) => { t.equal(err.code, 'ENOFUND', 'should have ENOFUND error code') @@ -638,7 +636,7 @@ test('fund tries to use package info fetch from registry but registry has nothin }) test('fund but target module has no funding info', t => { - _flatOptions.prefix = t.testdir(nestedNoFundingPackages) + npm.prefix = t.testdir(nestedNoFundingPackages) fund.exec(['foo'], (err) => { t.equal(err.code, 'ENOFUND', 'should have ENOFUND error code') @@ -654,8 +652,8 @@ test('fund but target module has no funding info', t => { }) test('fund using bad which value', t => { - _flatOptions.prefix = t.testdir(nestedMultipleFundingPackages) - _flatOptions.which = 3 + npm.prefix = t.testdir(nestedMultipleFundingPackages) + config.which = 3 fund.exec(['bar'], (err) => { t.equal(err.code, 'EFUNDNUMBER', 'should have EFUNDNUMBER error code') @@ -665,14 +663,14 @@ test('fund using bad which value', t => { 'should have bad which option error message' ) - _flatOptions.which = undefined + config.which = undefined result = '' t.end() }) }) test('fund pkg missing version number', t => { - _flatOptions.prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'foo', funding: 'http://example.com/foo', @@ -688,7 +686,7 @@ test('fund pkg missing version number', t => { }) test('fund a package throws on openUrl', t => { - _flatOptions.prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'foo', version: '1.0.0', @@ -704,7 +702,7 @@ test('fund a package throws on openUrl', t => { }) test('fund a package with type and multiple sources', t => { - _flatOptions.prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'foo', funding: [ @@ -730,7 +728,7 @@ test('fund a package with type and multiple sources', t => { }) test('fund colors', t => { - _flatOptions.prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-fund-colors', version: '1.0.0', @@ -782,20 +780,20 @@ test('fund colors', t => { }, }, }) - _flatOptions.color = true + npm.color = true fund.exec([], (err) => { t.ifError(err, 'should not error out') t.matchSnapshot(result, 'should print output with color info') result = '' - _flatOptions.color = false + npm.color = false t.end() }) }) test('sub dep with fund info and a parent with no funding info', t => { - _flatOptions.prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-multiple-funding-sources', version: '1.0.0', diff --git a/test/lib/help-search.js b/test/lib/help-search.js index 6228f5ca97b3b..cbb3947d3eb2d 100644 --- a/test/lib/help-search.js +++ b/test/lib/help-search.js @@ -2,6 +2,7 @@ const { test } = require('tap') const { join } = require('path') const requireInject = require('require-inject') const ansicolors = require('ansicolors') +const mockNpm = require('../fixtures/mock-npm') const OUTPUT = [] const output = (msg) => { @@ -10,11 +11,12 @@ const output = (msg) => { let npmHelpArgs = null let npmHelpErr = null -const npm = { +const config = { + long: false, +} +const npm = mockNpm({ color: false, - flatOptions: { - long: false, - }, + config, commands: { help: (args, cb) => { npmHelpArgs = args @@ -22,7 +24,7 @@ const npm = { }, }, output, -} +}) let npmUsageArg = null const npmUsage = (npm, arg) => { @@ -120,10 +122,10 @@ test('npm help-search single result propagates error', t => { test('npm help-search long output', t => { globRoot = t.testdir(globDir) - npm.flatOptions.long = true + config.long = true t.teardown(() => { OUTPUT.length = 0 - npm.flatOptions.long = false + config.long = false globRoot = null }) @@ -138,11 +140,11 @@ test('npm help-search long output', t => { test('npm help-search long output with color', t => { globRoot = t.testdir(globDir) - npm.flatOptions.long = true + config.long = true npm.color = true t.teardown(() => { OUTPUT.length = 0 - npm.flatOptions.long = false + config.long = false npm.color = false globRoot = null }) diff --git a/test/lib/init.js b/test/lib/init.js index 8b9f32e156e3d..2b212f4a159e8 100644 --- a/test/lib/init.js +++ b/test/lib/init.js @@ -1,5 +1,6 @@ const t = require('tap') const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') let result = '' const npmLog = { @@ -10,14 +11,17 @@ const npmLog = { resume: () => null, silly: () => null, } -const npm = { - config: { set () {} }, - flatOptions: {}, +const config = { + 'init-module': '~/.npm-init.js', +} +const npm = mockNpm({ + config, log: npmLog, + commands: {}, output: (...msg) => { result += msg.join('\n') }, -} +}) const mocks = { 'init-package-json': (dir, initFile, config, cb) => cb(null, 'data'), '../../lib/utils/usage.js': () => 'usage instructions', @@ -27,19 +31,13 @@ const init = new Init(npm) t.afterEach(cb => { result = '' - npm.config = { get: () => '', set () {} } + config.package = undefined npm.commands = {} - Object.defineProperty(npm, 'flatOptions', { value: {} }) npm.log = npmLog cb() }) t.test('classic npm init no args', t => { - npm.config = { - get () { - return '~/.npm-init.js' - }, - } init.exec([], err => { t.ifError(err, 'npm init no args') t.matchSnapshot(result, 'should print helper info') @@ -49,9 +47,7 @@ t.test('classic npm init no args', t => { t.test('classic npm init -y', t => { t.plan(7) - npm.config = { - get: () => '~/.npm-init.js', - } + config.yes = true Object.defineProperty(npm, 'flatOptions', { value: { yes: true} }) npm.log = { ...npm.log } npm.log.silly = (title, msg) => { @@ -72,14 +68,9 @@ t.test('classic npm init -y', t => { }) t.test('npm init ', t => { - t.plan(4) - npm.config = { - set (key, val) { - t.equal(key, 'package', 'should set package key') - t.deepEqual(val, [], 'should set empty array value') - }, - } + t.plan(3) npm.commands.exec = (arr, cb) => { + t.deepEqual(config.package, [], 'should set empty array value') t.deepEqual( arr, ['create-react-app'], @@ -178,20 +169,9 @@ t.test('npm init exec error', t => { }) t.test('should not rewrite flatOptions', t => { - t.plan(4) - Object.defineProperty(npm, 'flatOptions', { - get: () => ({}), - set () { - throw new Error('Should not set flatOptions') - }, - }) - npm.config = { - set (key, val) { - t.equal(key, 'package', 'should set package key') - t.deepEqual(val, [], 'should set empty array value') - }, - } + t.plan(3) npm.commands.exec = (arr, cb) => { + t.deepEqual(config.package, [], 'should set empty array value') t.deepEqual( arr, ['create-react-app', 'my-app'], diff --git a/test/lib/install.js b/test/lib/install.js index 8b7a968511136..b44452a69cc6f 100644 --- a/test/lib/install.js +++ b/test/lib/install.js @@ -2,6 +2,7 @@ const { test } = require('tap') const Install = require('../../lib/install.js') const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') test('should install using Arborist', (t) => { const SCRIPTS = [] @@ -28,16 +29,14 @@ test('should install using Arborist', (t) => { throw new Error('got wrong object passed to reify-finish') }, }) - const install = new Install({ + + const npm = mockNpm({ + config: { dev: true }, + flatOptions: { global: false }, globalDir: 'path/to/node_modules/', prefix: 'foo', - flatOptions: { - global: false, - }, - config: { - get: () => true, - }, }) + const install = new Install(npm) t.test('with args', t => { install.exec(['fizzbuzz'], er => { @@ -86,17 +85,16 @@ test('should ignore scripts with --ignore-scripts', (t) => { } }, }) - const install = new Install({ + const npm = mockNpm({ globalDir: 'path/to/node_modules/', prefix: 'foo', - flatOptions: { - global: false, - ignoreScripts: true, - }, + flatOptions: { global: false }, config: { - get: () => false, + global: false, + 'ignore-scripts': true, }, }) + const install = new Install(npm) install.exec([], er => { if (er) throw er @@ -113,16 +111,13 @@ test('should install globally using Arborist', (t) => { this.reify = () => {} }, }) - const install = new Install({ + const npm = mockNpm({ globalDir: 'path/to/node_modules/', prefix: 'foo', - flatOptions: { - global: true, - }, - config: { - get: () => false, - }, + config: { global: true }, + flatOptions: { global: true }, }) + const install = new Install(npm) install.exec([], er => { if (er) throw er diff --git a/test/lib/link.js b/test/lib/link.js index be7af3f524019..0d96ba0bcd684 100644 --- a/test/lib/link.js +++ b/test/lib/link.js @@ -3,6 +3,7 @@ const { resolve } = require('path') const Arborist = require('@npmcli/arborist') const t = require('tap') const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') const redactCwd = (path) => { const normalizePath = p => p @@ -15,17 +16,13 @@ const redactCwd = (path) => { t.cleanSnapshot = (str) => redactCwd(str) let reifyOutput -const npm = { +const config = {} +const npm = mockNpm({ globalDir: null, prefix: null, - flatOptions: {}, - config: { - get () { - return false - }, - find () {}, - }, -} + config, +}) + const printLinks = async (opts) => { let res = '' const arb = new Arborist(opts) diff --git a/test/lib/logout.js b/test/lib/logout.js index b00fa641d8c16..bae797f969321 100644 --- a/test/lib/logout.js +++ b/test/lib/logout.js @@ -1,12 +1,17 @@ const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') const { test } = require('tap') -const _flatOptions = { +const config = { registry: 'https://registry.npmjs.org/', scope: '', } +const flatOptions = { + registry: 'https://registry.npmjs.org/', + scope: '', +} +const npm = mockNpm({ config, flatOptions }) -const config = {} const npmlog = {} let result = null @@ -20,15 +25,12 @@ const mocks = { } const Logout = requireInject('../../lib/logout.js', mocks) -const logout = new Logout({ - flatOptions: _flatOptions, - config, -}) +const logout = new Logout(npm) test('token logout', async (t) => { t.plan(6) - _flatOptions.token = '@foo/' + flatOptions.token = '@foo/' npmlog.verbose = (title, msg) => { t.equal(title, 'logout', 'should have correcct log prefix') @@ -39,7 +41,7 @@ test('token logout', async (t) => { ) } - config.clearCredentialsByURI = (registry) => { + npm.config.clearCredentialsByURI = (registry) => { t.equal( registry, 'https://registry.npmjs.org/', @@ -47,7 +49,7 @@ test('token logout', async (t) => { ) } - config.save = (type) => { + npm.config.save = (type) => { t.equal(type, 'user', 'should save to user config') } @@ -70,7 +72,7 @@ test('token logout', async (t) => { 'should call npm-registry-fetch with expected values' ) - delete _flatOptions.token + delete flatOptions.token result = null mocks['npm-registry-fetch'] = null config.clearCredentialsByURI = null @@ -86,9 +88,11 @@ test('token logout', async (t) => { test('token scoped logout', async (t) => { t.plan(8) - _flatOptions.token = '@foo/' - _flatOptions.scope = '@myscope' - _flatOptions['@myscope:registry'] = 'https://diff-registry.npmjs.com/' + flatOptions.token = '@foo/' + config.scope = '@myscope' + config['@myscope:registry'] = 'https://diff-registry.npmjs.com/' + flatOptions.scope = '@myscope' + flatOptions['@myscope:registry'] = 'https://diff-registry.npmjs.com/' npmlog.verbose = (title, msg) => { t.equal(title, 'logout', 'should have correcct log prefix') @@ -99,7 +103,7 @@ test('token scoped logout', async (t) => { ) } - config.clearCredentialsByURI = (registry) => { + npm.config.clearCredentialsByURI = (registry) => { t.equal( registry, 'https://diff-registry.npmjs.com/', @@ -107,7 +111,7 @@ test('token scoped logout', async (t) => { ) } - config.delete = (ref, type) => { + npm.config.delete = (ref, type) => { t.equal( ref, '@myscope:registry', @@ -116,7 +120,7 @@ test('token scoped logout', async (t) => { t.equal(type, 'user', 'should delete from user config') } - config.save = (type) => { + npm.config.save = (type) => { t.equal(type, 'user', 'should save to user config') } @@ -140,9 +144,9 @@ test('token scoped logout', async (t) => { 'should call npm-registry-fetch with expected values' ) - _flatOptions.scope = '' - delete _flatOptions['@myscope:registry'] - delete _flatOptions.token + config.scope = '' + delete config['@myscope:registry'] + delete flatOptions.token result = null mocks['npm-registry-fetch'] = null config.clearCredentialsByURI = null @@ -158,8 +162,8 @@ test('token scoped logout', async (t) => { test('user/pass logout', async (t) => { t.plan(3) - _flatOptions.username = 'foo' - _flatOptions.password = 'bar' + flatOptions.username = 'foo' + flatOptions.password = 'bar' npmlog.verbose = (title, msg) => { t.equal(title, 'logout', 'should have correcct log prefix') @@ -170,17 +174,17 @@ test('user/pass logout', async (t) => { ) } - config.clearCredentialsByURI = () => null - config.save = () => null + npm.config.clearCredentialsByURI = () => null + npm.config.save = () => null await new Promise((res, rej) => { logout.exec([], (err) => { t.ifError(err, 'should not error out') - delete _flatOptions.username - delete _flatOptions.password - config.clearCredentialsByURI = null - config.save = null + delete flatOptions.username + delete flatOptions.password + npm.config.clearCredentialsByURI = null + npm.config.save = null npmlog.verbose = null res() @@ -203,9 +207,9 @@ test('missing credentials', (t) => { test('ignore invalid scoped registry config', async (t) => { t.plan(5) - _flatOptions.token = '@foo/' - _flatOptions.scope = '@myscope' - _flatOptions['@myscope:registry'] = '' + flatOptions.token = '@foo/' + config.scope = '@myscope' + flatOptions['@myscope:registry'] = '' npmlog.verbose = (title, msg) => { t.equal(title, 'logout', 'should have correcct log prefix') @@ -216,7 +220,7 @@ test('ignore invalid scoped registry config', async (t) => { ) } - config.clearCredentialsByURI = (registry) => { + npm.config.clearCredentialsByURI = (registry) => { t.equal( registry, 'https://registry.npmjs.org/', @@ -224,8 +228,8 @@ test('ignore invalid scoped registry config', async (t) => { ) } - config.delete = () => null - config.save = () => null + npm.config.delete = () => null + npm.config.save = () => null await new Promise((res, rej) => { logout.exec([], (err) => { @@ -247,7 +251,7 @@ test('ignore invalid scoped registry config', async (t) => { 'should call npm-registry-fetch with expected values' ) - delete _flatOptions.token + delete flatOptions.token result = null mocks['npm-registry-fetch'] = null config.clearCredentialsByURI = null diff --git a/test/lib/ls.js b/test/lib/ls.js index bcbd3413563dd..5367dec688658 100644 --- a/test/lib/ls.js +++ b/test/lib/ls.js @@ -1,4 +1,5 @@ const t = require('tap') +const mockNpm = require('../fixtures/mock-npm') const { resolve } = require('path') const { utimesSync } = require('fs') @@ -84,12 +85,9 @@ const diffDepTypesNmFixture = { }, } -let prefix -let globalDir = 'MISSING_GLOBAL_DIR' let result = '' -// note this _flatOptions representations is for tests-only and does not -// represent exactly the properties found in the actual flatOptions obj -const _flatOptions = { +const LS = require('../../lib/ls.js') +const config = { all: true, color: false, dev: false, @@ -99,32 +97,18 @@ const _flatOptions = { link: false, only: null, parseable: false, - get prefix () { - return prefix - }, production: false, } -const LS = require('../../lib/ls.js') -const ls = new LS({ - flatOptions: _flatOptions, - limit: { - fetch: 3, - }, - get prefix () { - return _flatOptions.prefix - }, - get globalDir () { - return globalDir - }, - config: { - get (key) { - return _flatOptions[key] - }, - }, +const flatOptions = { +} +const npm = mockNpm({ + config, + flatOptions, output: msg => { result = msg }, }) +const ls = new LS(npm) const redactCwd = res => res && res.replace(/\\+/g, '/').replace(new RegExp(__dirname.replace(/\\+/g, '/'), 'gi'), '{CWD}') @@ -138,10 +122,10 @@ const cleanUpResult = (done, t) => { t.test('ls', (t) => { t.beforeEach(cleanUpResult) - _flatOptions.json = false - _flatOptions.unicode = false + config.json = false + config.unicode = false t.test('no args', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -160,7 +144,7 @@ t.test('ls', (t) => { }) t.test('missing package.json', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ ...simpleNmFixture, }) ls.exec([], (err) => { @@ -175,7 +159,7 @@ t.test('ls', (t) => { }) t.test('extraneous deps', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -198,8 +182,8 @@ t.test('ls', (t) => { }) t.test('with filter arg', (t) => { - _flatOptions.color = true - prefix = t.testdir({ + npm.color = true + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -213,15 +197,15 @@ t.test('ls', (t) => { ls.exec(['lorem'], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output tree contaning only occurrences of filtered by package and colored output') - _flatOptions.color = false + npm.color = false t.end() }) }) t.test('with dot filter arg', (t) => { - _flatOptions.all = false - _flatOptions.depth = 0 - prefix = t.testdir({ + config.all = false + config.depth = 0 + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -235,14 +219,14 @@ t.test('ls', (t) => { ls.exec(['.'], (err) => { t.ifError(err, 'should not throw on missing dep above current level') t.matchSnapshot(redactCwd(result), 'should output tree contaning only occurrences of filtered by package and colored output') - _flatOptions.all = true - _flatOptions.depth = Infinity + config.all = true + config.depth = Infinity t.end() }) }) t.test('with filter arg nested dep', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -261,7 +245,7 @@ t.test('ls', (t) => { }) t.test('with multiple filter args', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -289,7 +273,7 @@ t.test('ls', (t) => { }) t.test('with missing filter arg', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -314,9 +298,9 @@ t.test('ls', (t) => { }) t.test('default --depth value should be 0', (t) => { - _flatOptions.all = false - _flatOptions.depth = undefined - prefix = t.testdir({ + config.all = false + config.depth = undefined + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -330,16 +314,16 @@ t.test('ls', (t) => { ls.exec([], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output tree containing only top-level dependencies') - _flatOptions.all = true - _flatOptions.depth = Infinity + config.all = true + config.depth = Infinity t.end() }) }) t.test('--depth=0', (t) => { - _flatOptions.all = false - _flatOptions.depth = 0 - prefix = t.testdir({ + config.all = false + config.depth = 0 + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -353,16 +337,16 @@ t.test('ls', (t) => { ls.exec([], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output tree containing only top-level dependencies') - _flatOptions.all = true - _flatOptions.depth = Infinity + config.all = true + config.depth = Infinity t.end() }) }) t.test('--depth=1', (t) => { - _flatOptions.all = false - _flatOptions.depth = 1 - prefix = t.testdir({ + config.all = false + config.depth = 1 + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -414,14 +398,14 @@ t.test('ls', (t) => { ls.exec([], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output tree containing top-level deps and their deps only') - _flatOptions.all = true - _flatOptions.depth = Infinity + config.all = true + config.depth = Infinity t.end() }) }) t.test('missing/invalid/extraneous', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -447,8 +431,8 @@ t.test('ls', (t) => { }) t.test('colored output', (t) => { - _flatOptions.color = true - prefix = t.testdir({ + npm.color = true + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -462,14 +446,14 @@ t.test('ls', (t) => { ls.exec([], (err) => { t.equal(err.code, 'ELSPROBLEMS', 'should have error code') t.matchSnapshot(redactCwd(result), 'should output tree containing color info') - _flatOptions.color = false + npm.color = false t.end() }) }) t.test('--dev', (t) => { - _flatOptions.dev = true - prefix = t.testdir({ + config.dev = true + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -491,14 +475,14 @@ t.test('ls', (t) => { }) ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing dev deps') - _flatOptions.dev = false + config.dev = false t.end() }) }) t.test('--only=development', (t) => { - _flatOptions.only = 'development' - prefix = t.testdir({ + config.only = 'development' + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -520,14 +504,14 @@ t.test('ls', (t) => { }) ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing only development deps') - _flatOptions.only = null + config.only = null t.end() }) }) t.test('--link', (t) => { - _flatOptions.link = true - prefix = t.testdir({ + config.link = true + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -559,13 +543,13 @@ t.test('ls', (t) => { }) ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing linked deps') - _flatOptions.link = false + config.link = false t.end() }) }) t.test('print deduped symlinks', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'print-deduped-symlinks', version: '1.0.0', @@ -595,14 +579,14 @@ t.test('ls', (t) => { }) ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing linked deps') - _flatOptions.link = false + config.link = false t.end() }) }) t.test('--production', (t) => { - _flatOptions.production = true - prefix = t.testdir({ + config.production = true + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -624,14 +608,14 @@ t.test('ls', (t) => { }) ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing production deps') - _flatOptions.production = false + config.production = false t.end() }) }) t.test('--only=prod', (t) => { - _flatOptions.only = 'prod' - prefix = t.testdir({ + config.only = 'prod' + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -653,14 +637,14 @@ t.test('ls', (t) => { }) ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing only prod deps') - _flatOptions.only = null + config.only = null t.end() }) }) t.test('--long', (t) => { - _flatOptions.long = true - prefix = t.testdir({ + config.long = true + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -682,16 +666,16 @@ t.test('ls', (t) => { }) ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree info with descriptions') - _flatOptions.long = true + config.long = true t.end() }) }) t.test('--long --depth=0', (t) => { - _flatOptions.all = false - _flatOptions.depth = 0 - _flatOptions.long = true - prefix = t.testdir({ + config.all = false + config.depth = 0 + config.long = true + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -713,15 +697,15 @@ t.test('ls', (t) => { }) ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing top-level deps with descriptions') - _flatOptions.all = true - _flatOptions.depth = Infinity - _flatOptions.long = false + config.all = true + config.depth = Infinity + config.long = false t.end() }) }) t.test('json read problems', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': '{broken json', }) ls.exec([], (err) => { @@ -732,7 +716,7 @@ t.test('ls', (t) => { }) t.test('empty location', (t) => { - prefix = t.testdir({}) + npm.prefix = t.testdir({}) ls.exec([], (err) => { t.ifError(err, 'should not error out on empty locations') t.matchSnapshot(redactCwd(result), 'should print empty result') @@ -741,7 +725,7 @@ t.test('ls', (t) => { }) t.test('invalid peer dep', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -768,8 +752,8 @@ t.test('ls', (t) => { }) t.test('invalid deduped dep', (t) => { - _flatOptions.color = true - prefix = t.testdir({ + npm.color = true + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'invalid-deduped-dep', version: '1.0.0', @@ -798,13 +782,13 @@ t.test('ls', (t) => { }) ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree signaling mismatching peer dep in problems') - _flatOptions.color = false + npm.color = false t.end() }) }) t.test('deduped missing dep', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -834,7 +818,7 @@ t.test('ls', (t) => { }) t.test('unmet peer dep', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -852,8 +836,8 @@ t.test('ls', (t) => { }) t.test('unmet optional dep', (t) => { - _flatOptions.color = true - prefix = t.testdir({ + npm.color = true + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -878,13 +862,13 @@ t.test('ls', (t) => { t.match(err.code, 'ELSPROBLEMS', 'should have ELSPROBLEMS error code') t.match(err.message, /invalid: optional-dep@1.0.0/, 'should have invalid dep error msg') t.matchSnapshot(redactCwd(result), 'should output tree with empty entry for missing optional deps') - _flatOptions.color = false + npm.color = false t.end() }) }) t.test('cycle deps', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -921,8 +905,8 @@ t.test('ls', (t) => { }) t.test('cycle deps with filter args', (t) => { - _flatOptions.color = true - prefix = t.testdir({ + npm.color = true + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -954,13 +938,13 @@ t.test('ls', (t) => { ls.exec(['a'], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should print tree output containing deduped ref') - _flatOptions.color = false + npm.color = false t.end() }) }) t.test('with no args dedupe entries', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'dedupe-entries', version: '1.0.0', @@ -1007,9 +991,9 @@ t.test('ls', (t) => { }) t.test('with no args dedupe entries and not displaying all', (t) => { - _flatOptions.all = false - _flatOptions.depth = 0 - prefix = t.testdir({ + config.all = false + config.depth = 0 + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'dedupe-entries', version: '1.0.0', @@ -1051,15 +1035,15 @@ t.test('ls', (t) => { ls.exec([], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should print tree output containing deduped ref') - _flatOptions.all = true - _flatOptions.depth = Infinity + config.all = true + config.depth = Infinity t.end() }) }) t.test('with args and dedupe entries', (t) => { - _flatOptions.color = true - prefix = t.testdir({ + npm.color = true + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'dedupe-entries', version: '1.0.0', @@ -1101,13 +1085,13 @@ t.test('ls', (t) => { ls.exec(['@npmcli/b'], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should print tree output containing deduped ref') - _flatOptions.color = false + npm.color = false t.end() }) }) t.test('with args and different order of items', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'dedupe-entries', version: '1.0.0', @@ -1154,7 +1138,7 @@ t.test('ls', (t) => { }) t.test('using aliases', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -1189,7 +1173,7 @@ t.test('ls', (t) => { }, }, }) - touchHiddenPackageLock(prefix) + touchHiddenPackageLock(npm.prefix) ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing aliases') t.end() @@ -1197,7 +1181,7 @@ t.test('ls', (t) => { }) t.test('resolved points to git ref', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -1235,7 +1219,7 @@ t.test('ls', (t) => { }, }, }) - touchHiddenPackageLock(prefix) + touchHiddenPackageLock(npm.prefix) ls.exec([], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output tree containing git refs') @@ -1244,7 +1228,7 @@ t.test('ls', (t) => { }) t.test('broken resolved field', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ node_modules: { a: { 'package.json': JSON.stringify({ @@ -1288,7 +1272,7 @@ t.test('ls', (t) => { }) t.test('from and resolved properties', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -1334,7 +1318,7 @@ t.test('ls', (t) => { }, }, }) - touchHiddenPackageLock(prefix) + touchHiddenPackageLock(npm.prefix) ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should not be printed in tree output') t.end() @@ -1342,7 +1326,7 @@ t.test('ls', (t) => { }) t.test('global', (t) => { - _flatOptions.global = true + config.global = true const fixtures = t.testdir({ node_modules: { a: { @@ -1369,18 +1353,18 @@ t.test('ls', (t) => { }) // mimics lib/npm.js globalDir getter but pointing to fixtures - globalDir = resolve(fixtures, 'node_modules') + npm.globalDir = resolve(fixtures, 'node_modules') ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should print tree and not mark top-level items extraneous') - globalDir = 'MISSING_GLOBAL_DIR' - _flatOptions.global = false + npm.globalDir = 'MISSING_GLOBAL_DIR' + config.global = false t.end() }) }) t.test('filtering by child of missing dep', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'filter-by-child-of-missing-dep', version: '1.0.0', @@ -1432,7 +1416,7 @@ t.test('ls', (t) => { }) t.test('loading a tree containing workspaces', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'filter-by-child-of-missing-dep', version: '1.0.0', @@ -1483,8 +1467,8 @@ t.test('ls', (t) => { }) t.test('filter pkg arg using depth option', (t) => { - _flatOptions.depth = 0 - prefix = t.testdir({ + config.depth = 0 + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-pkg-arg-filter-with-depth-opt', version: '1.0.0', @@ -1540,7 +1524,7 @@ t.test('ls', (t) => { t.matchSnapshot(redactCwd(result), 'should print empty results msg') // if no --depth config is defined, should print path to dep - _flatOptions.depth = null // default config value + config.depth = null // default config value ls.exec(['d'], (err) => { t.ifError(err, 'should NOT have ELSPROBLEMS error code when filter') t.matchSnapshot(redactCwd(result), 'should print expected result') @@ -1550,7 +1534,7 @@ t.test('ls', (t) => { }) t.teardown(() => { - _flatOptions.depth = Infinity + config.depth = Infinity }) t.end() @@ -1558,11 +1542,11 @@ t.test('ls', (t) => { t.test('ls --parseable', (t) => { t.beforeEach(cleanUpResult) - _flatOptions.json = false - _flatOptions.unicode = false - _flatOptions.parseable = true + config.json = false + config.unicode = false + config.parseable = true t.test('no args', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -1581,7 +1565,7 @@ t.test('ls --parseable', (t) => { }) t.test('missing package.json', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ ...simpleNmFixture, }) ls.exec([], (err) => { @@ -1596,7 +1580,7 @@ t.test('ls --parseable', (t) => { }) t.test('extraneous deps', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -1614,7 +1598,7 @@ t.test('ls --parseable', (t) => { }) t.test('with filter arg', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -1633,7 +1617,7 @@ t.test('ls --parseable', (t) => { }) t.test('with filter arg nested dep', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -1652,7 +1636,7 @@ t.test('ls --parseable', (t) => { }) t.test('with multiple filter args', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -1680,7 +1664,7 @@ t.test('ls --parseable', (t) => { }) t.test('with missing filter arg', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -1705,9 +1689,9 @@ t.test('ls --parseable', (t) => { }) t.test('default --depth value should be 0', (t) => { - _flatOptions.all = false - _flatOptions.depth = undefined - prefix = t.testdir({ + config.all = false + config.depth = undefined + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -1721,16 +1705,16 @@ t.test('ls --parseable', (t) => { ls.exec([], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output parseable output containing only top-level dependencies') - _flatOptions.all = true - _flatOptions.depth = Infinity + config.all = true + config.depth = Infinity t.end() }) }) t.test('--depth=0', (t) => { - _flatOptions.all = false - _flatOptions.depth = 0 - prefix = t.testdir({ + config.all = false + config.depth = 0 + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -1744,16 +1728,16 @@ t.test('ls --parseable', (t) => { ls.exec([], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output tree containing only top-level dependencies') - _flatOptions.all = true - _flatOptions.depth = Infinity + config.all = true + config.depth = Infinity t.end() }) }) t.test('--depth=1', (t) => { - _flatOptions.all = false - _flatOptions.depth = 1 - prefix = t.testdir({ + config.all = false + config.depth = 1 + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -1767,14 +1751,14 @@ t.test('ls --parseable', (t) => { ls.exec([], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output parseable containing top-level deps and their deps only') - _flatOptions.all = true - _flatOptions.depth = Infinity + config.all = true + config.depth = Infinity t.end() }) }) t.test('missing/invalid/extraneous', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -1793,8 +1777,8 @@ t.test('ls --parseable', (t) => { }) t.test('--dev', (t) => { - _flatOptions.dev = true - prefix = t.testdir({ + config.dev = true + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -1816,14 +1800,14 @@ t.test('ls --parseable', (t) => { }) ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing dev deps') - _flatOptions.dev = false + config.dev = false t.end() }) }) t.test('--only=development', (t) => { - _flatOptions.only = 'development' - prefix = t.testdir({ + config.only = 'development' + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -1845,14 +1829,14 @@ t.test('ls --parseable', (t) => { }) ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing only development deps') - _flatOptions.only = null + config.only = null t.end() }) }) t.test('--link', (t) => { - _flatOptions.link = true - prefix = t.testdir({ + config.link = true + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -1884,14 +1868,14 @@ t.test('ls --parseable', (t) => { }) ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing linked deps') - _flatOptions.link = false + config.link = false t.end() }) }) t.test('--production', (t) => { - _flatOptions.production = true - prefix = t.testdir({ + config.production = true + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -1913,14 +1897,14 @@ t.test('ls --parseable', (t) => { }) ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing production deps') - _flatOptions.production = false + config.production = false t.end() }) }) t.test('--only=prod', (t) => { - _flatOptions.only = 'prod' - prefix = t.testdir({ + config.only = 'prod' + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -1942,14 +1926,14 @@ t.test('ls --parseable', (t) => { }) ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing only prod deps') - _flatOptions.only = null + config.only = null t.end() }) }) t.test('--long', (t) => { - _flatOptions.long = true - prefix = t.testdir({ + config.long = true + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -1971,13 +1955,13 @@ t.test('ls --parseable', (t) => { }) ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree info with descriptions') - _flatOptions.long = true + config.long = true t.end() }) }) t.test('--long with extraneous deps', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -1996,8 +1980,8 @@ t.test('ls --parseable', (t) => { }) t.test('--long missing/invalid/extraneous', (t) => { - _flatOptions.long = true - prefix = t.testdir({ + config.long = true + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -2011,14 +1995,14 @@ t.test('ls --parseable', (t) => { ls.exec([], (err) => { t.match(err, { code: 'ELSPROBLEMS' }, 'should list dep problems') t.matchSnapshot(redactCwd(result), 'should output parseable result containing EXTRANEOUS/INVALID labels') - _flatOptions.long = false + config.long = false t.end() }) }) t.test('--long print symlink target location', (t) => { - _flatOptions.long = true - prefix = t.testdir({ + config.long = true + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -2051,16 +2035,16 @@ t.test('ls --parseable', (t) => { ls.exec([], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output parseable results with symlink targets') - _flatOptions.long = false + config.long = false t.end() }) }) t.test('--long --depth=0', (t) => { - _flatOptions.all = false - _flatOptions.depth = 0 - _flatOptions.long = true - prefix = t.testdir({ + config.all = false + config.depth = 0 + config.long = true + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -2082,15 +2066,15 @@ t.test('ls --parseable', (t) => { }) ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing top-level deps with descriptions') - _flatOptions.all = true - _flatOptions.depth = Infinity - _flatOptions.long = false + config.all = true + config.depth = Infinity + config.long = false t.end() }) }) t.test('json read problems', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': '{broken json', }) ls.exec([], (err) => { @@ -2101,7 +2085,7 @@ t.test('ls --parseable', (t) => { }) t.test('empty location', (t) => { - prefix = t.testdir({}) + npm.prefix = t.testdir({}) ls.exec([], (err) => { t.ifError(err, 'should not error out on empty locations') t.matchSnapshot(redactCwd(result), 'should print empty result') @@ -2110,7 +2094,7 @@ t.test('ls --parseable', (t) => { }) t.test('unmet peer dep', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -2137,7 +2121,7 @@ t.test('ls --parseable', (t) => { }) t.test('unmet optional dep', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -2167,7 +2151,7 @@ t.test('ls --parseable', (t) => { }) t.test('cycle deps', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -2203,7 +2187,7 @@ t.test('ls --parseable', (t) => { }) t.test('using aliases', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -2234,7 +2218,7 @@ t.test('ls --parseable', (t) => { }, }, }) - touchHiddenPackageLock(prefix) + touchHiddenPackageLock(npm.prefix) ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing aliases') t.end() @@ -2242,7 +2226,7 @@ t.test('ls --parseable', (t) => { }) t.test('resolved points to git ref', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -2279,7 +2263,7 @@ t.test('ls --parseable', (t) => { }, }, }) - touchHiddenPackageLock(prefix) + touchHiddenPackageLock(npm.prefix) ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing git refs') t.end() @@ -2287,7 +2271,7 @@ t.test('ls --parseable', (t) => { }) t.test('from and resolved properties', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -2333,7 +2317,7 @@ t.test('ls --parseable', (t) => { }, }, }) - touchHiddenPackageLock(prefix) + touchHiddenPackageLock(npm.prefix) ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should not be printed in tree output') t.end() @@ -2341,7 +2325,7 @@ t.test('ls --parseable', (t) => { }) t.test('global', (t) => { - _flatOptions.global = true + config.global = true const fixtures = t.testdir({ node_modules: { a: { @@ -2368,12 +2352,12 @@ t.test('ls --parseable', (t) => { }) // mimics lib/npm.js globalDir getter but pointing to fixtures - globalDir = resolve(fixtures, 'node_modules') + npm.globalDir = resolve(fixtures, 'node_modules') ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should print parseable output for global deps') - globalDir = 'MISSING_GLOBAL_DIR' - _flatOptions.global = false + npm.globalDir = 'MISSING_GLOBAL_DIR' + config.global = false t.end() }) }) @@ -2383,10 +2367,10 @@ t.test('ls --parseable', (t) => { t.test('ls --json', (t) => { t.beforeEach(cleanUpResult) - _flatOptions.json = true - _flatOptions.parseable = false + config.json = true + config.parseable = false t.test('no args', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -2425,7 +2409,7 @@ t.test('ls --json', (t) => { }) t.test('missing package.json', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ ...simpleNmFixture, }) ls.exec([], (err) => { @@ -2474,7 +2458,7 @@ t.test('ls --json', (t) => { }) t.test('extraneous deps', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -2528,7 +2512,7 @@ t.test('ls --json', (t) => { }) t.test('with filter arg', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -2564,7 +2548,7 @@ t.test('ls --json', (t) => { }) t.test('with filter arg nested dep', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -2600,7 +2584,7 @@ t.test('ls --json', (t) => { }) t.test('with multiple filter args', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -2648,7 +2632,7 @@ t.test('ls --json', (t) => { }) t.test('with missing filter arg', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -2680,9 +2664,9 @@ t.test('ls --json', (t) => { }) t.test('default --depth value should now be 0', (t) => { - _flatOptions.all = false - _flatOptions.depth = undefined - prefix = t.testdir({ + config.all = false + config.depth = undefined + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -2711,16 +2695,16 @@ t.test('ls --json', (t) => { }, 'should output json containing only top-level dependencies' ) - _flatOptions.all = true - _flatOptions.depth = Infinity + config.all = true + config.depth = Infinity t.end() }) }) t.test('--depth=0', (t) => { - _flatOptions.all = false - _flatOptions.depth = 0 - prefix = t.testdir({ + config.all = false + config.depth = 0 + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -2749,16 +2733,16 @@ t.test('ls --json', (t) => { }, 'should output json containing only top-level dependencies' ) - _flatOptions.all = true - _flatOptions.depth = Infinity + config.all = true + config.depth = Infinity t.end() }) }) t.test('--depth=1', (t) => { - _flatOptions.all = false - _flatOptions.depth = 1 - prefix = t.testdir({ + config.all = false + config.depth = 1 + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -2792,14 +2776,14 @@ t.test('ls --json', (t) => { }, 'should output json containing top-level deps and their deps only' ) - _flatOptions.all = true - _flatOptions.depth = Infinity + config.all = true + config.depth = Infinity t.end() }) }) t.test('missing/invalid/extraneous', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -2858,8 +2842,8 @@ t.test('ls --json', (t) => { }) t.test('--dev', (t) => { - _flatOptions.dev = true - prefix = t.testdir({ + config.dev = true + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -2899,14 +2883,14 @@ t.test('ls --json', (t) => { }, 'should output json containing dev deps' ) - _flatOptions.dev = false + config.dev = false t.end() }) }) t.test('--only=development', (t) => { - _flatOptions.only = 'development' - prefix = t.testdir({ + config.only = 'development' + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -2946,14 +2930,14 @@ t.test('ls --json', (t) => { }, 'should output json containing only development deps' ) - _flatOptions.only = null + config.only = null t.end() }) }) t.test('--link', (t) => { - _flatOptions.link = true - prefix = t.testdir({ + config.link = true + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -2998,14 +2982,14 @@ t.test('ls --json', (t) => { }, 'should output json containing linked deps' ) - _flatOptions.link = false + config.link = false t.end() }) }) t.test('--production', (t) => { - _flatOptions.production = true - prefix = t.testdir({ + config.production = true + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -3039,14 +3023,14 @@ t.test('ls --json', (t) => { }, 'should output json containing production deps' ) - _flatOptions.production = false + config.production = false t.end() }) }) t.test('--only=prod', (t) => { - _flatOptions.only = 'prod' - prefix = t.testdir({ + config.only = 'prod' + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -3080,13 +3064,13 @@ t.test('ls --json', (t) => { }, 'should output json containing only prod deps' ) - _flatOptions.only = null + config.only = null t.end() }) }) t.test('from lockfile', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ node_modules: { '@isaacs': { 'dedupe-tests-a': { @@ -3215,8 +3199,8 @@ t.test('ls --json', (t) => { }) t.test('--long', (t) => { - _flatOptions.long = true - prefix = t.testdir({ + config.long = true + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -3345,16 +3329,16 @@ t.test('ls --json', (t) => { }, 'should output long json info' ) - _flatOptions.long = true + config.long = true t.end() }) }) t.test('--long --depth=0', (t) => { - _flatOptions.all = false - _flatOptions.depth = 0 - _flatOptions.long = true - prefix = t.testdir({ + config.all = false + config.depth = 0 + config.long = true + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -3446,15 +3430,15 @@ t.test('ls --json', (t) => { }, 'should output json containing top-level deps in long format' ) - _flatOptions.all = true - _flatOptions.depth = Infinity - _flatOptions.long = false + config.all = true + config.depth = Infinity + config.long = false t.end() }) }) t.test('json read problems', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': '{broken json', }) ls.exec([], (err) => { @@ -3475,7 +3459,7 @@ t.test('ls --json', (t) => { }) t.test('empty location', (t) => { - prefix = t.testdir({}) + npm.prefix = t.testdir({}) ls.exec([], (err) => { t.ifError(err, 'should not error out on empty locations') t.deepEqual( @@ -3488,7 +3472,7 @@ t.test('ls --json', (t) => { }) t.test('unmet peer dep', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -3547,7 +3531,7 @@ t.test('ls --json', (t) => { }) t.test('unmet optional dep', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -3611,7 +3595,7 @@ t.test('ls --json', (t) => { }) t.test('cycle deps', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -3667,7 +3651,7 @@ t.test('ls --json', (t) => { }) t.test('using aliases', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -3697,7 +3681,7 @@ t.test('ls --json', (t) => { }, }, }) - touchHiddenPackageLock(prefix) + touchHiddenPackageLock(npm.prefix) ls.exec([], () => { t.deepEqual( jsonParse(result), @@ -3718,7 +3702,7 @@ t.test('ls --json', (t) => { }) t.test('resolved points to git ref', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -3757,7 +3741,7 @@ t.test('ls --json', (t) => { }, }, }) - touchHiddenPackageLock(prefix) + touchHiddenPackageLock(npm.prefix) ls.exec([], () => { t.deepEqual( jsonParse(result), @@ -3778,7 +3762,7 @@ t.test('ls --json', (t) => { }) t.test('from and resolved properties', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'test-npm-ls', version: '1.0.0', @@ -3841,7 +3825,7 @@ t.test('ls --json', (t) => { }, }, }) - touchHiddenPackageLock(prefix) + touchHiddenPackageLock(npm.prefix) ls.exec([], () => { t.deepEqual( jsonParse(result), @@ -3862,7 +3846,7 @@ t.test('ls --json', (t) => { }) t.test('node.name fallback if missing root package name', (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ version: '1.0.0', }), @@ -3881,7 +3865,7 @@ t.test('ls --json', (t) => { }) t.test('global', (t) => { - _flatOptions.global = true + config.global = true const fixtures = t.testdir({ node_modules: { a: { @@ -3908,7 +3892,7 @@ t.test('ls --json', (t) => { }) // mimics lib/npm.js globalDir getter but pointing to fixtures - globalDir = resolve(fixtures, 'node_modules') + npm.globalDir = resolve(fixtures, 'node_modules') ls.exec([], () => { t.deepEqual( @@ -3931,8 +3915,8 @@ t.test('ls --json', (t) => { }, 'should print json output for global deps' ) - globalDir = 'MISSING_GLOBAL_DIR' - _flatOptions.global = false + npm.globalDir = 'MISSING_GLOBAL_DIR' + config.global = false t.end() }) }) diff --git a/test/lib/outdated.js b/test/lib/outdated.js index 02952971b69f9..5aff7c37ac11a 100644 --- a/test/lib/outdated.js +++ b/test/lib/outdated.js @@ -1,5 +1,6 @@ const t = require('tap') const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') const packument = spec => { const mocks = { @@ -89,12 +90,13 @@ const outdated = (dir, opts) => { packument, }, }) - return new Outdated({ + const npm = mockNpm({ + ...opts, prefix: dir, globalDir: `${globalDir}/node_modules`, - flatOptions: opts, output, }) + return new Outdated(npm) } t.beforeEach((done) => { @@ -173,7 +175,7 @@ t.test('should display outdated deps', t => { t.test('outdated global', t => { outdated(null, { - global: true, + config: { global: true }, }).exec([], () => { t.matchSnapshot(logs) t.end() @@ -182,7 +184,9 @@ t.test('should display outdated deps', t => { t.test('outdated', t => { outdated(testDir, { - global: false, + config: { + global: false, + }, color: true, }).exec([], () => { t.matchSnapshot(logs) @@ -192,9 +196,11 @@ t.test('should display outdated deps', t => { t.test('outdated --omit=dev', t => { outdated(testDir, { - global: false, + config: { + global: false, + omit: ['dev'], + }, color: true, - omit: ['dev'], }).exec([], () => { t.matchSnapshot(logs) t.end() @@ -203,9 +209,11 @@ t.test('should display outdated deps', t => { t.test('outdated --omit=dev --omit=peer', t => { outdated(testDir, { - global: false, + config: { + global: false, + omit: ['dev', 'peer'], + }, color: true, - omit: ['dev', 'peer'], }).exec([], () => { t.matchSnapshot(logs) t.end() @@ -214,9 +222,11 @@ t.test('should display outdated deps', t => { t.test('outdated --omit=prod', t => { outdated(testDir, { - global: false, + config: { + global: false, + omit: ['prod'], + }, color: true, - omit: ['prod'], }).exec([], () => { t.matchSnapshot(logs) t.end() @@ -225,8 +235,10 @@ t.test('should display outdated deps', t => { t.test('outdated --long', t => { outdated(testDir, { - global: false, - long: true, + config: { + global: false, + long: true, + }, }).exec([], () => { t.matchSnapshot(logs) t.end() @@ -235,8 +247,10 @@ t.test('should display outdated deps', t => { t.test('outdated --json', t => { outdated(testDir, { - global: false, - json: true, + config: { + global: false, + json: true, + }, }).exec([], () => { t.matchSnapshot(logs) t.end() @@ -245,9 +259,11 @@ t.test('should display outdated deps', t => { t.test('outdated --json --long', t => { outdated(testDir, { - global: false, - json: true, - long: true, + config: { + global: false, + json: true, + long: true, + }, }).exec([], () => { t.matchSnapshot(logs) t.end() @@ -256,8 +272,10 @@ t.test('should display outdated deps', t => { t.test('outdated --parseable', t => { outdated(testDir, { - global: false, - parseable: true, + config: { + global: false, + parseable: true, + }, }).exec([], () => { t.matchSnapshot(logs) t.end() @@ -266,9 +284,11 @@ t.test('should display outdated deps', t => { t.test('outdated --parseable --long', t => { outdated(testDir, { - global: false, - parseable: true, - long: true, + config: { + global: false, + parseable: true, + long: true, + }, }).exec([], () => { t.matchSnapshot(logs) t.end() @@ -277,7 +297,9 @@ t.test('should display outdated deps', t => { t.test('outdated --all', t => { outdated(testDir, { - all: true, + config: { + all: true, + }, }).exec([], () => { t.matchSnapshot(logs) t.end() @@ -286,7 +308,9 @@ t.test('should display outdated deps', t => { t.test('outdated specific dep', t => { outdated(testDir, { - global: false, + config: { + global: false, + }, }).exec(['alpha'], () => { t.matchSnapshot(logs) t.end() diff --git a/test/lib/pack.js b/test/lib/pack.js index ce319be76ba3f..a5163acfdc49b 100644 --- a/test/lib/pack.js +++ b/test/lib/pack.js @@ -1,5 +1,6 @@ const t = require('tap') const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') const OUTPUT = [] const output = (...msg) => OUTPUT.push(msg) @@ -25,14 +26,15 @@ t.test('should pack current directory with no arguments', (t) => { clearProgress: () => {}, }, }) - const pack = new Pack({ - flatOptions: { + const npm = mockNpm({ + config: { unicode: false, json: false, - dryRun: false, + 'dry-run': false, }, output, }) + const pack = new Pack(npm) pack.exec([], er => { if (er) @@ -60,14 +62,15 @@ t.test('should pack given directory', (t) => { clearProgress: () => {}, }, }) - const pack = new Pack({ - flatOptions: { + const npm = mockNpm({ + config: { unicode: true, json: true, - dryRun: true, + 'dry-run': true, }, output, }) + const pack = new Pack(npm) pack.exec([testDir], er => { if (er) @@ -95,14 +98,15 @@ t.test('should pack given directory for scoped package', (t) => { clearProgress: () => {}, }, }) - const pack = new Pack({ - flatOptions: { + const npm = mockNpm({ + config: { unicode: true, json: true, - dryRun: true, + 'dry-run': true, }, output, }) + const pack = new Pack(npm) return pack.exec([testDir], er => { if (er) @@ -129,14 +133,15 @@ t.test('should log pack contents', (t) => { clearProgress: () => {}, }, }) - const pack = new Pack({ - flatOptions: { + const npm = mockNpm({ + config: { unicode: false, json: false, - dryRun: false, + 'dry-run': false, }, output, }) + const pack = new Pack(npm) pack.exec([], er => { if (er) diff --git a/test/lib/ping.js b/test/lib/ping.js index f3563036f71fd..95361035acb53 100644 --- a/test/lib/ping.js +++ b/test/lib/ping.js @@ -1,14 +1,15 @@ const { test } = require('tap') const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') test('pings', (t) => { t.plan(8) - const flatOptions = { registry: 'https://registry.npmjs.org' } + const registry = 'https://registry.npmjs.org' let noticeCalls = 0 const Ping = requireInject('../../lib/ping.js', { '../../lib/utils/ping.js': function (spec) { - t.equal(spec, flatOptions, 'passes flatOptions') + t.equal(spec.registry, registry, 'passes flatOptions') return {} }, npmlog: { @@ -16,7 +17,7 @@ test('pings', (t) => { ++noticeCalls if (noticeCalls === 1) { t.equal(type, 'PING', 'should log a PING') - t.equal(spec, flatOptions.registry, 'should log the registry url') + t.equal(spec, registry, 'should log the registry url') } else { t.equal(type, 'PONG', 'should log a PONG') t.match(spec, /\d+ms/, 'should log the elapsed milliseconds') @@ -24,7 +25,11 @@ test('pings', (t) => { }, }, }) - const ping = new Ping({ flatOptions }) + const npm = mockNpm({ + config: { registry }, + flatOptions: { registry }, + }) + const ping = new Ping(npm) ping.exec([], (err) => { t.equal(noticeCalls, 2, 'should have logged 2 lines') @@ -36,12 +41,12 @@ test('pings', (t) => { test('pings and logs details', (t) => { t.plan(10) - const flatOptions = { registry: 'https://registry.npmjs.org' } + const registry = 'https://registry.npmjs.org' const details = { extra: 'data' } let noticeCalls = 0 const Ping = requireInject('../../lib/ping.js', { '../../lib/utils/ping.js': function (spec) { - t.equal(spec, flatOptions, 'passes flatOptions') + t.equal(spec.registry, registry, 'passes flatOptions') return details }, npmlog: { @@ -49,7 +54,7 @@ test('pings and logs details', (t) => { ++noticeCalls if (noticeCalls === 1) { t.equal(type, 'PING', 'should log a PING') - t.equal(spec, flatOptions.registry, 'should log the registry url') + t.equal(spec, registry, 'should log the registry url') } else if (noticeCalls === 2) { t.equal(type, 'PONG', 'should log a PONG') t.match(spec, /\d+ms/, 'should log the elapsed milliseconds') @@ -61,7 +66,11 @@ test('pings and logs details', (t) => { }, }, }) - const ping = new Ping({ flatOptions }) + const npm = mockNpm({ + config: { registry }, + flatOptions: { registry }, + }) + const ping = new Ping(npm) ping.exec([], (err) => { t.equal(noticeCalls, 3, 'should have logged 3 lines') @@ -73,12 +82,12 @@ test('pings and logs details', (t) => { test('pings and returns json', (t) => { t.plan(11) - const flatOptions = { registry: 'https://registry.npmjs.org', json: true } + const registry = 'https://registry.npmjs.org' const details = { extra: 'data' } let noticeCalls = 0 const Ping = requireInject('../../lib/ping.js', { '../../lib/utils/ping.js': function (spec) { - t.equal(spec, flatOptions, 'passes flatOptions') + t.equal(spec.registry, registry, 'passes flatOptions') return details }, npmlog: { @@ -86,7 +95,7 @@ test('pings and returns json', (t) => { ++noticeCalls if (noticeCalls === 1) { t.equal(type, 'PING', 'should log a PING') - t.equal(spec, flatOptions.registry, 'should log the registry url') + t.equal(spec, registry, 'should log the registry url') } else { t.equal(type, 'PONG', 'should log a PONG') t.match(spec, /\d+ms/, 'should log the elapsed milliseconds') @@ -94,15 +103,17 @@ test('pings and returns json', (t) => { }, }, }) - const ping = new Ping({ - flatOptions, + const npm = mockNpm({ + config: { registry, json: true }, + flatOptions: { registry }, output: function (spec) { const parsed = JSON.parse(spec) - t.equal(parsed.registry, flatOptions.registry, 'returns the correct registry url') + t.equal(parsed.registry, registry, 'returns the correct registry url') t.match(parsed.details, details, 'prints returned details') t.type(parsed.time, 'number', 'returns time as a number') }, }) + const ping = new Ping(npm) ping.exec([], (err) => { t.equal(noticeCalls, 2, 'should have logged 2 lines') diff --git a/test/lib/profile.js b/test/lib/profile.js index d1be93b0cbb62..5b1210615aa2f 100644 --- a/test/lib/profile.js +++ b/test/lib/profile.js @@ -1,20 +1,24 @@ const t = require('tap') const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') let result = '' -const flatOptions = { +const config = { otp: '', json: false, parseable: false, registry: 'https://registry.npmjs.org/', } -const npm = { - config: {}, - flatOptions: { ...flatOptions }, +const flatOptions = { + registry: 'https://registry.npmjs.org/', +} +const npm = mockNpm({ + config, + flatOptions, output: (...msg) => { result = result ? `${result}\n${msg.join('\n')}` : msg.join('\n') }, -} +}) const mocks = { ansistyles: { bright: a => a }, npmlog: { @@ -65,8 +69,10 @@ const userProfile = { t.afterEach(cb => { result = '' - npm.config = {} - npm.flatOptions = { ...flatOptions } + flatOptions.otp = '' + config.json = false + config.parseable = false + config.registry = 'https://registry.npmjs.org/' cb() }) @@ -111,7 +117,7 @@ t.test('profile get no args', t => { }) t.test('--json', t => { - npm.flatOptions.json = true + config.json = true profile.exec(['get'], err => { if (err) @@ -127,7 +133,7 @@ t.test('profile get no args', t => { }) t.test('--parseable', t => { - npm.flatOptions.parseable = true + config.parseable = true profile.exec(['get'], err => { if (err) @@ -256,7 +262,7 @@ t.test('profile get ', t => { }) t.test('--json', t => { - npm.flatOptions.json = true + config.json = true profile.exec(['get', 'name'], err => { if (err) @@ -272,7 +278,7 @@ t.test('profile get ', t => { }) t.test('--parseable', t => { - npm.flatOptions.parseable = true + config.parseable = true profile.exec(['get', 'name'], err => { if (err) @@ -316,7 +322,7 @@ t.test('profile get multiple args', t => { }) t.test('--json', t => { - npm.flatOptions.json = true + config.json = true profile.exec(['get', 'name', 'email', 'github'], err => { if (err) @@ -332,7 +338,7 @@ t.test('profile get multiple args', t => { }) t.test('--parseable', t => { - npm.flatOptions.parseable = true + config.parseable = true profile.exec(['get', 'name', 'email', 'github'], err => { if (err) @@ -451,7 +457,7 @@ t.test('profile set ', t => { t.test('--json', t => { t.plan(2) - npm.flatOptions.json = true + config.json = true const Profile = requireInject('../../lib/profile.js', { ...mocks, @@ -476,7 +482,7 @@ t.test('profile set ', t => { t.test('--parseable', t => { t.plan(2) - npm.flatOptions.parseable = true + config.parseable = true const Profile = requireInject('../../lib/profile.js', { ...mocks, @@ -705,7 +711,7 @@ t.test('enable-2fa', t => { }) t.test('no support for --json output', t => { - npm.flatOptions.json = true + config.json = true profile.exec(['enable-2fa', 'auth-only'], err => { t.match( @@ -719,7 +725,7 @@ t.test('enable-2fa', t => { }) t.test('no support for --parseable output', t => { - npm.flatOptions.parseable = true + config.parseable = true profile.exec(['enable-2fa', 'auth-only'], err => { t.match( @@ -817,18 +823,16 @@ t.test('enable-2fa', t => { t.plan(10) // mock legacy basic auth style - npm.config = { - getCredentialsByURI (reg) { - t.equal(reg, flatOptions.registry, 'should use expected registry') - return { auth: Buffer.from('foo:bar').toString('base64') } - }, - setCredentialsByURI (registry, { token }) { - t.equal(registry, flatOptions.registry, 'should set expected registry') - t.equal(token, 'token', 'should set expected token') - }, - save (type) { - t.equal(type, 'user', 'should save to user config') - }, + npm.config.getCredentialsByURI = (reg) => { + t.equal(reg, flatOptions.registry, 'should use expected registry') + return { auth: Buffer.from('foo:bar').toString('base64') } + } + npm.config.setCredentialsByURI = (registry, { token }) => { + t.equal(registry, flatOptions.registry, 'should set expected registry') + t.equal(token, 'token', 'should set expected token') + } + npm.config.save = (type) => { + t.equal(type, 'user', 'should save to user config') } const npmProfile = { @@ -901,12 +905,10 @@ t.test('enable-2fa', t => { t.test('from token and set otp, retries on pending and verifies with qrcode', t => { t.plan(4) - npm.flatOptions.otp = '1234' + flatOptions.otp = '1234' - npm.config = { - getCredentialsByURI () { - return { token: 'token' } - }, + npm.config.getCredentialsByURI = () => { + return { token: 'token' } } let setCount = 0 @@ -1003,12 +1005,10 @@ t.test('enable-2fa', t => { }) t.test('from token and set otp, retrieves invalid otp', t => { - npm.flatOptions.otp = '1234' + flatOptions.otp = '1234' - npm.config = { - getCredentialsByURI () { - return { token: 'token' } - }, + npm.config.getCredentialsByURI = () => { + return { token: 'token' } } const npmProfile = { @@ -1055,12 +1055,11 @@ t.test('enable-2fa', t => { }) t.test('from token auth provides --otp config arg', t => { - npm.flatOptions.otp = '123456' + flatOptions.otp = '123456' + flatOptions.otp = '123456' - npm.config = { - getCredentialsByURI (reg) { - return { token: 'token' } - }, + npm.config.getCredentialsByURI = (reg) => { + return { token: 'token' } } const npmProfile = { @@ -1105,10 +1104,8 @@ t.test('enable-2fa', t => { }) t.test('missing tfa from user profile', t => { - npm.config = { - getCredentialsByURI (reg) { - return { token: 'token' } - }, + npm.config.getCredentialsByURI = (reg) => { + return { token: 'token' } } const npmProfile = { @@ -1156,10 +1153,8 @@ t.test('enable-2fa', t => { }) t.test('defaults to auth-and-writes permission if no mode specified', t => { - npm.config = { - getCredentialsByURI (reg) { - return { token: 'token' } - }, + npm.config.getCredentialsByURI = (reg) => { + return { token: 'token' } } const npmProfile = { @@ -1303,7 +1298,7 @@ t.test('disable-2fa', t => { }) t.test('--json', t => { - npm.flatOptions.json = true + config.json = true const Profile = requireInject('../../lib/profile.js', { ...mocks, @@ -1326,7 +1321,7 @@ t.test('disable-2fa', t => { }) t.test('--parseable', t => { - npm.flatOptions.parseable = true + config.parseable = true const Profile = requireInject('../../lib/profile.js', { ...mocks, @@ -1354,7 +1349,7 @@ t.test('disable-2fa', t => { t.test('--otp config already set', t => { t.plan(3) - npm.flatOptions.otp = '123456' + flatOptions.otp = '123456' const npmProfile = { async get () { diff --git a/test/lib/publish.js b/test/lib/publish.js index 6337a1fcf0b2a..bfc58a4c36a06 100644 --- a/test/lib/publish.js +++ b/test/lib/publish.js @@ -1,5 +1,6 @@ const t = require('tap') const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') const fs = require('fs') // The way we set loglevel is kind of convoluted, and there is no way to affect @@ -12,9 +13,7 @@ log.level = 'silent' // mock config const {defaults} = require('../../lib/utils/config.js') -const config = { - list: [defaults], -} +const config = defaults t.afterEach(cb => { log.level = 'silent' @@ -54,18 +53,17 @@ t.test('should publish with libnpmpublish, passing through flatOptions and respe }, }, }) - const publish = new Publish({ + const npm = mockNpm({ + config, flatOptions: { customValue: true, }, - config: { - ...config, - getCredentialsByURI: (uri) => { - t.same(uri, registry, 'gets credentials for expected registry') - return { token: 'some.registry.token' } - }, - }, }) + npm.config.getCredentialsByURI = (uri) => { + t.same(uri, registry, 'gets credentials for expected registry') + return { token: 'some.registry.token' } + } + const publish = new Publish(npm) publish.exec([testDir], (er) => { if (er) @@ -104,15 +102,12 @@ t.test('re-loads publishConfig.registry if added during script process', (t) => }, }, }) - const publish = new Publish({ - config: { - ...config, - getCredentialsByURI: (uri) => { - t.same(uri, registry, 'gets credentials for expected registry') - return { token: 'some.registry.token' } - }, - }, - }) + const npm = mockNpm({ config }) + npm.config.getCredentialsByURI = (uri) => { + t.same(uri, registry, 'gets credentials for expected registry') + return { token: 'some.registry.token' } + } + const publish = new Publish(npm) publish.exec([testDir], (er) => { if (er) @@ -148,21 +143,17 @@ t.test('if loglevel=info and json, should not output package contents', (t) => { }, }, }) - const publish = new Publish({ - flatOptions: { - json: true, - }, - config: { - ...config, - getCredentialsByURI: (uri) => { - t.same(uri, defaults.registry, 'gets credentials for expected registry') - return { token: 'some.registry.token' } - }, - }, + const npm = mockNpm({ + config: { ...config, json: true }, output: () => { t.pass('output is called') }, }) + npm.config.getCredentialsByURI = (uri) => { + t.same(uri, defaults.registry, 'gets credentials for expected registry') + return { token: 'some.registry.token' } + } + const publish = new Publish(npm) publish.exec([testDir], (er) => { if (er) @@ -198,20 +189,17 @@ t.test('if loglevel=silent and dry-run, should not output package contents or pu }, }, }) - const publish = new Publish({ - flatOptions: { - dryRun: true, - }, - config: { - ...config, - getCredentialsByURI: () => { - throw new Error('should not call getCredentialsByURI in dry run') - }, - }, + const npm = mockNpm({ + config: { ...config, 'dry-run': true }, output: () => { throw new Error('should not output in dry run mode') }, }) + npm.config.getCredentialsByURI = () => { + throw new Error('should not call getCredentialsByURI in dry run') + } + + const publish = new Publish(npm) publish.exec([testDir], (er) => { if (er) @@ -247,20 +235,16 @@ t.test('if loglevel=info and dry-run, should not publish, should log package con }, }, }) - const publish = new Publish({ - flatOptions: { - dryRun: true, - }, - config: { - ...config, - getCredentialsByURI: () => { - throw new Error('should not call getCredentialsByURI in dry run') - }, - }, + const npm = mockNpm({ + config: { ...config, 'dry-run': true }, output: () => { t.pass('output fn is called') }, }) + npm.config.getCredentialsByURI = () => { + throw new Error('should not call getCredentialsByURI in dry run') + } + const publish = new Publish(npm) publish.exec([testDir], (er) => { if (er) @@ -285,12 +269,10 @@ t.test('throws when invalid tag', (t) => { t.plan(1) const Publish = requireInject('../../lib/publish.js') - const publish = new Publish({ - flatOptions: { - defaultTag: '0.0.13', - }, - config, + const npm = mockNpm({ + config: { ...config, tag: '0.0.13' }, }) + const publish = new Publish(npm) publish.exec([], (err) => { t.match(err, { @@ -331,15 +313,12 @@ t.test('can publish a tarball', t => { }, }, }) - const publish = new Publish({ - config: { - ...config, - getCredentialsByURI: (uri) => { - t.same(uri, defaults.registry, 'gets credentials for expected registry') - return { token: 'some.registry.token' } - }, - }, - }) + const npm = mockNpm({ config }) + npm.config.getCredentialsByURI = (uri) => { + t.same(uri, defaults.registry, 'gets credentials for expected registry') + return { token: 'some.registry.token' } + } + const publish = new Publish(npm) publish.exec([`${testDir}/tarball/package.tgz`], (er) => { if (er) @@ -352,15 +331,12 @@ t.test('can publish a tarball', t => { t.test('should check auth for default registry', t => { t.plan(2) const Publish = requireInject('../../lib/publish.js') - const publish = new Publish({ - config: { - ...config, - getCredentialsByURI: (uri) => { - t.same(uri, defaults.registry, 'gets credentials for expected registry') - return {} - }, - }, - }) + const npm = mockNpm({ config }) + npm.config.getCredentialsByURI = (uri) => { + t.same(uri, defaults.registry, 'gets credentials for expected registry') + return {} + } + const publish = new Publish(npm) publish.exec([], (err) => { t.match(err, { @@ -375,18 +351,15 @@ t.test('should check auth for configured registry', t => { t.plan(2) const registry = 'https://some.registry' const Publish = requireInject('../../lib/publish.js') - const publish = new Publish({ - flatOptions: { - registry, - }, - config: { - ...config, - getCredentialsByURI: (uri) => { - t.same(uri, registry, 'gets credentials for expected registry') - return {} - }, - }, + const npm = mockNpm({ + config, + flatOptions: { registry }, }) + npm.config.getCredentialsByURI = (uri) => { + t.same(uri, registry, 'gets credentials for expected registry') + return {} + } + const publish = new Publish(npm) publish.exec([], (err) => { t.match(err, { @@ -408,18 +381,15 @@ t.test('should check auth for scope specific registry', t => { }) const Publish = requireInject('../../lib/publish.js') - const publish = new Publish({ - flatOptions: { - '@npm:registry': registry, - }, - config: { - ...config, - getCredentialsByURI: (uri) => { - t.same(uri, registry, 'gets credentials for expected registry') - return {} - }, - }, + const npm = mockNpm({ + config, + flatOptions: { '@npm:registry': registry }, }) + npm.config.getCredentialsByURI = (uri) => { + t.same(uri, registry, 'gets credentials for expected registry') + return {} + } + const publish = new Publish(npm) publish.exec([testDir], (err) => { t.match(err, { @@ -448,18 +418,16 @@ t.test('should use auth for scope specific registry', t => { }, }, }) - const publish = new Publish({ - flatOptions: { - '@npm:registry': registry, - }, - config: { - ...config, - getCredentialsByURI: (uri) => { - t.same(uri, registry, 'gets credentials for expected registry') - return { token: 'some.registry.token' } - }, - }, + const npm = mockNpm({ + config, + flatOptions: { '@npm:registry': registry }, }) + npm.config.getCredentialsByURI = (uri) => { + t.same(uri, registry, 'gets credentials for expected registry') + return { token: 'some.registry.token' } + } + const publish = new Publish(npm) + publish.exec([testDir], (er) => { if (er) throw er @@ -489,15 +457,14 @@ t.test('read registry only from publishConfig', t => { }, }, }) - const publish = new Publish({ - config: { - ...config, - getCredentialsByURI: (uri) => { - t.same(uri, registry, 'gets credentials for expected registry') - return { token: 'some.registry.token' } - }, - }, + const npm = mockNpm({ + config, }) + npm.config.getCredentialsByURI = (uri) => { + t.same(uri, registry, 'gets credentials for expected registry') + return { token: 'some.registry.token' } + } + const publish = new Publish(npm) publish.exec([testDir], (er) => { if (er) diff --git a/test/lib/rebuild.js b/test/lib/rebuild.js index 1eb45e0d1d7bd..73344dae68b3e 100644 --- a/test/lib/rebuild.js +++ b/test/lib/rebuild.js @@ -1,25 +1,27 @@ const fs = require('fs') const { resolve } = require('path') +const mockNpm = require('../fixtures/mock-npm') const t = require('tap') let result = '' -const npm = { +const config = { + global: false, +} +const npm = mockNpm({ globalDir: '', - flatOptions: { - global: false, - }, + config, prefix: '', output: (...msg) => { result += msg.join('\n') }, -} +}) const Rebuild = require('../../lib/rebuild.js') const rebuild = new Rebuild(npm) t.afterEach(cb => { npm.prefix = '' - npm.flatOptions.global = false + config.global = false npm.globalDir = '' result = '' cb() @@ -237,7 +239,7 @@ t.test('global prefix', t => { }, }) - npm.flatOptions.global = true + config.global = true npm.globalDir = resolve(globalPath, 'lib', 'node_modules') rebuild.exec([], err => { diff --git a/test/lib/run-script.js b/test/lib/run-script.js index 0566daf2341f4..c598ea707f946 100644 --- a/test/lib/run-script.js +++ b/test/lib/run-script.js @@ -1,33 +1,31 @@ const t = require('tap') const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') const RUN_SCRIPTS = [] -const npm = { +const flatOptions = { + scriptShell: undefined, +} +const config = { + json: false, + parseable: false, + 'if-present': false, +} + +const npm = mockNpm({ localPrefix: __dirname, - flatOptions: { - scriptShell: undefined, - json: false, - parseable: false, - }, - config: { - settings: { - 'if-present': false, - }, - get: k => npm.config.settings[k], - set: (k, v) => { - npm.config.settings[k] = v - }, - }, + flatOptions, + config, output: (...msg) => output.push(msg), -} +}) const output = [] t.afterEach(cb => { output.length = 0 RUN_SCRIPTS.length = 0 - npm.flatOptions.json = false - npm.flatOptions.parseable = false + config.json = false + config.parseable = false cb() }) @@ -331,7 +329,7 @@ t.test('run pre/post hooks', t => { }) t.test('skip pre/post hooks when using ignoreScripts', t => { - npm.flatOptions.ignoreScripts = true + config['ignore-scripts'] = true npm.localPrefix = t.testdir({ 'package.json': JSON.stringify({ @@ -368,7 +366,7 @@ t.test('skip pre/post hooks when using ignoreScripts', t => { }, ]) t.end() - delete npm.flatOptions.ignoreScripts + delete config['ignore-scripts'] }) }) @@ -466,7 +464,7 @@ t.test('list scripts', t => { }) t.test('warn json', t => { npmlog.level = 'warn' - npm.flatOptions.json = true + config.json = true runScript.exec([], er => { if (er) throw er @@ -476,7 +474,7 @@ t.test('list scripts', t => { }) t.test('parseable', t => { - npm.flatOptions.parseable = true + config.parseable = true runScript.exec([], er => { if (er) throw er diff --git a/test/lib/search.js b/test/lib/search.js index b1ba7775c73c8..b7b40844219fb 100644 --- a/test/lib/search.js +++ b/test/lib/search.js @@ -1,6 +1,7 @@ const Minipass = require('minipass') const t = require('tap') const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') const libnpmsearchResultFixture = require('../fixtures/libnpmsearch-stream-result.js') @@ -12,12 +13,17 @@ const flatOptions = { opts: '', }, } -const npm = { +const config = { + json: false, + parseable: false, +} +const npm = mockNpm({ + config, flatOptions: { ...flatOptions }, output: (...msg) => { result += msg.join('\n') }, -} +}) const npmlog = { silly () {}, clearProgress () {}, @@ -29,12 +35,13 @@ const mocks = { npmlog, libnpmsearch, '../../lib/utils/usage.js': () => 'usage instructions', - // '../../lib/search/format-package-stream.js': a => a, } t.afterEach(cb => { result = '' - npm.flatOptions = flatOptions + config.json = false + config.parseable = false + npm.flatOptions = { ...flatOptions } cb() }) @@ -86,7 +93,8 @@ t.test('search --json', (t) => { const src = new Minipass() src.objectMode = true - flatOptions.json = true + npm.flatOptions.json = true + config.json = true const libnpmsearch = { stream () { return src @@ -114,7 +122,7 @@ t.test('search --json', (t) => { 'should have expected search results as json' ) - flatOptions.json = false + config.json = false t.end() }) diff --git a/test/lib/shrinkwrap.js b/test/lib/shrinkwrap.js index dc4bc3b220ca2..b3de4f5fae28e 100644 --- a/test/lib/shrinkwrap.js +++ b/test/lib/shrinkwrap.js @@ -1,16 +1,21 @@ const t = require('tap') const fs = require('fs') const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') -const npm = { +const config = { + global: false, +} +const flatOptions = { + depth: 0, +} +const npm = mockNpm({ + config, + flatOptions, lockfileVersion: 2, globalDir: '', - flatOptions: { - depth: 0, - global: false, - }, prefix: '', -} +}) const tree = { meta: { hiddenLockfile: null, @@ -36,7 +41,7 @@ const mocks = { t.afterEach(cb => { npm.prefix = '' - npm.flatOptions.global = false + config.global = false npm.globalDir = '' cb() }) @@ -50,7 +55,7 @@ t.test('no args', t => { constructor (args) { t.deepEqual( args, - { ...npm.flatOptions, path: npm.prefix }, + { ...flatOptions, path: npm.prefix }, 'should call arborist constructor with expected args' ) } @@ -101,7 +106,7 @@ t.test('no virtual tree', t => { constructor (args) { t.deepEqual( args, - { ...npm.flatOptions, path: npm.prefix }, + { ...flatOptions, path: npm.prefix }, 'should call arborist constructor with expected args' ) } @@ -156,7 +161,7 @@ t.test('existing package-json file', t => { constructor (args) { t.deepEqual( args, - { ...npm.flatOptions, path: npm.prefix }, + { ...flatOptions, path: npm.prefix }, 'should call arborist constructor with expected args' ) } @@ -218,7 +223,7 @@ t.test('update shrinkwrap file version', t => { constructor (args) { t.deepEqual( args, - { ...npm.flatOptions, path: npm.prefix }, + { ...flatOptions, path: npm.prefix }, 'should call arborist constructor with expected args' ) } @@ -272,7 +277,7 @@ t.test('update to date shrinkwrap file', t => { constructor (args) { t.deepEqual( args, - { ...npm.flatOptions, path: npm.prefix }, + { ...flatOptions, path: npm.prefix }, 'should call arborist constructor with expected args' ) } @@ -320,7 +325,7 @@ t.test('update to date shrinkwrap file', t => { t.test('shrinkwrap --global', t => { const Shrinkwrap = requireInject('../../lib/shrinkwrap.js', mocks) - npm.flatOptions.global = true + config.global = true const shrinkwrap = new Shrinkwrap(npm) shrinkwrap.exec([], err => { diff --git a/test/lib/star.js b/test/lib/star.js index 774fabe3924c4..fa75d210577ac 100644 --- a/test/lib/star.js +++ b/test/lib/star.js @@ -1,16 +1,20 @@ const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') const t = require('tap') let result = '' const noop = () => null -const npm = { - config: { get () {} }, - flatOptions: { unicode: false }, +const config = { + unicode: false, + 'star.unstar': false, +} +const npm = mockNpm({ + config, output: (...msg) => { result += msg.join('\n') }, -} +}) const npmFetch = { json: noop } const npmlog = { error: noop, info: noop, verbose: noop } const mocks = { @@ -24,8 +28,8 @@ const Star = requireInject('../../lib/star.js', mocks) const star = new Star(npm) t.afterEach(cb => { - npm.config = { get () {} } - npm.flatOptions.unicode = false + config.unicode = false + config['star.unstar'] = false npmlog.info = noop result = '' cb() @@ -73,7 +77,7 @@ t.test('star a package', t => { t.test('unstar a package', t => { t.plan(4) const pkgName = '@npmcli/arborist' - npm.config.get = key => key === 'star.unstar' + config['star.unstar'] = true npmFetch.json = async (uri, opts) => ({ _id: pkgName, _rev: 'hash', @@ -100,7 +104,7 @@ t.test('unstar a package', t => { t.test('unicode', async t => { t.test('star a package', t => { - npm.flatOptions.unicode = true + config.unicode = true npmFetch.json = async (uri, opts) => ({}) star.exec(['pkg'], err => { if (err) @@ -115,8 +119,8 @@ t.test('unicode', async t => { }) t.test('unstar a package', t => { - npm.flatOptions.unicode = true - npm.config.get = key => key === 'star.unstar' + config.unicode = true + config['star.unstar'] = true npmFetch.json = async (uri, opts) => ({}) star.exec(['pkg'], err => { if (err) diff --git a/test/lib/uninstall.js b/test/lib/uninstall.js index c62b59950b894..5cb8a243ec19b 100644 --- a/test/lib/uninstall.js +++ b/test/lib/uninstall.js @@ -2,18 +2,18 @@ const fs = require('fs') const { resolve } = require('path') const t = require('tap') const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') -const npm = { +const npm = mockNpm({ globalDir: '', - flatOptions: { + config: { global: false, prefix: '', }, localPrefix: '', -} +}) const mocks = { '../../lib/utils/reify-finish.js': () => Promise.resolve(), - '../../lib/utils/usage.js': () => 'usage instructions', } const Uninstall = requireInject('../../lib/uninstall.js', mocks) @@ -85,13 +85,13 @@ t.test('remove single installed lib', t => { const b = resolve(path, 'node_modules/b') t.ok(() => fs.statSync(b)) - npm.flatOptions.prefix = path + npm.config.set('prefix', path) uninstall.exec(['b'], err => { if (err) throw err - t.throws(() => fs.statSync(b), 'should have removed package from nm') + t.throws(() => fs.statSync(b), 'should have removed package from npm') t.end() }) }) @@ -148,7 +148,7 @@ t.test('remove multiple installed libs', t => { t.ok(() => fs.statSync(a)) t.ok(() => fs.statSync(b)) - npm.flatOptions.prefix = path + npm.config.set('prefix', path) uninstall.exec(['b'], err => { if (err) @@ -195,8 +195,8 @@ t.test('no args global', t => { npm.localPrefix = resolve(path, 'projects', 'a') npm.globalDir = resolve(path, 'lib', 'node_modules') - npm.flatOptions.global = true - npm.flatOptions.prefix = path + npm.config.set('global', true) + npm.config.set('prefix', path) const a = resolve(path, 'lib/node_modules/a') t.ok(() => fs.statSync(a)) @@ -221,8 +221,7 @@ t.test('no args global but no package.json', t => { uninstall.exec([], err => { t.match( err, - 'usage instructions', - 'should throw usage instructions' + 'npm uninstall' ) t.end() diff --git a/test/lib/unpublish.js b/test/lib/unpublish.js index b1255b94a8fe4..ba99b53303073 100644 --- a/test/lib/unpublish.js +++ b/test/lib/unpublish.js @@ -1,19 +1,21 @@ const t = require('tap') const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') let result = '' const noop = () => null -const npm = { +const config = { + force: false, + silent: false, + loglevel: 'silly', +} +const npm = mockNpm({ localPrefix: '', - flatOptions: { - force: false, - silent: false, - loglevel: 'silly', - }, + config, output: (...msg) => { result += msg.join('\n') }, -} +}) const mocks = { npmlog: { silly () {}, verbose () {} }, libnpmaccess: { lsPackages: noop }, @@ -28,16 +30,16 @@ const mocks = { t.afterEach(cb => { result = '' - npm.flatOptions.force = false - npm.flatOptions.loglevel = 'silly' - npm.flatOptions.silent = false + config.force = false + config.loglevel = 'silly' + config.silent = false cb() }) t.test('no args --force', t => { t.plan(9) - npm.flatOptions.force = true + config.force = true const npmlog = { silly (title) { @@ -67,9 +69,6 @@ t.test('no args --force', t => { t.deepEqual( opts, { - force: true, - silent: false, - loglevel: 'silly', publishConfig: undefined, }, 'should unpublish with expected opts' @@ -102,7 +101,7 @@ t.test('no args --force', t => { }) t.test('no args --force missing package.json', t => { - npm.flatOptions.force = true + config.force = true const Unpublish = requireInject('../../lib/unpublish.js', { ...mocks, @@ -124,7 +123,7 @@ t.test('no args --force missing package.json', t => { }) t.test('no args --force unknown error reading package.json', t => { - npm.flatOptions.force = true + config.force = true const Unpublish = requireInject('../../lib/unpublish.js', { ...mocks, @@ -200,11 +199,7 @@ t.test('unpublish @version', t => { t.equal(spec, pa, 'should unpublish expected parsed spec') t.deepEqual( opts, - { - force: false, - silent: false, - loglevel: 'silly', - }, + {}, 'should unpublish with expected opts' ) }, @@ -231,7 +226,7 @@ t.test('unpublish @version', t => { }) t.test('no version found in package.json', t => { - npm.flatOptions.force = true + config.force = true const npa = () => ({ name: 'pkg', @@ -263,7 +258,7 @@ t.test('no version found in package.json', t => { }) t.test('unpublish --force no version set', t => { - npm.flatOptions.force = true + config.force = true const Unpublish = requireInject('../../lib/unpublish.js', { ...mocks, @@ -289,7 +284,7 @@ t.test('unpublish --force no version set', t => { }) t.test('silent', t => { - npm.flatOptions.loglevel = 'silent' + config.loglevel = 'silent' const npa = () => ({ name: 'pkg', diff --git a/test/lib/update.js b/test/lib/update.js index 15195573f5a24..695218a7f69cd 100644 --- a/test/lib/update.js +++ b/test/lib/update.js @@ -1,16 +1,18 @@ const { resolve } = require('path') const t = require('tap') const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') +const config = { + depth: 0, + global: false, +} const noop = () => null -const npm = { +const npm = mockNpm({ globalDir: '', - flatOptions: { - depth: 0, - global: false, - }, + config, prefix: '', -} +}) const mocks = { npmlog: { warn () {} }, '@npmcli/arborist': class { @@ -22,7 +24,7 @@ const mocks = { t.afterEach(cb => { npm.prefix = '' - npm.flatOptions.global = false + config.global = false npm.globalDir = '' cb() }) @@ -99,7 +101,7 @@ t.test('update --depth=', t => { t.plan(2) npm.prefix = '/project/a' - npm.flatOptions.depth = 1 + config.depth = 1 const Update = requireInject('../../lib/update.js', { ...mocks, @@ -131,7 +133,7 @@ t.test('update --global', t => { npm.prefix = '/project/a' npm.globalDir = resolve(process.cwd(), 'global/lib/node_modules') - npm.flatOptions.global = true + config.global = true class Arborist { constructor (args) { diff --git a/test/lib/utils/read-local-package.js b/test/lib/utils/read-local-package.js index 9ae21f7d62b4c..4b693afb48b8e 100644 --- a/test/lib/utils/read-local-package.js +++ b/test/lib/utils/read-local-package.js @@ -1,22 +1,17 @@ const requireInject = require('require-inject') const { test } = require('tap') +const mockNpm = require('../../fixtures/mock-npm') -let prefix -const _flatOptions = { +const config = { json: false, global: false, - get prefix () { - return prefix - }, } +const npm = mockNpm({ config }) const readLocalPackageName = requireInject('../../../lib/utils/read-local-package.js') -const npm = { - flatOptions: _flatOptions, -} test('read local package.json', async (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: 'my-local-package', version: '1.0.0', @@ -31,7 +26,7 @@ test('read local package.json', async (t) => { }) test('read local scoped-package.json', async (t) => { - prefix = t.testdir({ + npm.prefix = t.testdir({ 'package.json': JSON.stringify({ name: '@my-scope/my-local-package', version: '1.0.0', @@ -46,13 +41,13 @@ test('read local scoped-package.json', async (t) => { }) test('read using --global', async (t) => { - prefix = t.testdir({}) - _flatOptions.global = true + npm.prefix = t.testdir({}) + config.global = true const packageName = await readLocalPackageName(npm) t.equal( packageName, undefined, 'should not retrieve a package name' ) - _flatOptions.global = false + config.global = false }) diff --git a/test/lib/version.js b/test/lib/version.js index a8fcd831fb5c3..35d3d92cd2bee 100644 --- a/test/lib/version.js +++ b/test/lib/version.js @@ -1,21 +1,23 @@ const t = require('tap') const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') let result = [] const noop = () => null -const npm = { - flatOptions: { - tagVersionPrefix: 'v', - json: false, - }, +const config = { + 'tag-version-prefix': 'v', + json: false, +} +const npm = mockNpm({ + config, prefix: '', version: '1.0.0', output: (...msg) => { for (const m of msg) result.push(m) }, -} +}) const mocks = { libnpmversion: noop, } @@ -25,7 +27,7 @@ const version = new Version(npm) const _processVersions = process.versions t.afterEach(cb => { - npm.flatOptions.json = false + config.json = false npm.prefix = '' process.versions = _processVersions result = [] @@ -116,7 +118,7 @@ t.test('failure reading package.json', t => { t.test('--json option', t => { const prefix = t.testdir({}) - npm.flatOptions.json = true + config.json = true npm.prefix = prefix Object.defineProperty(process, 'versions', { value: {} }) @@ -140,8 +142,6 @@ t.test('with one arg', t => { t.deepEqual( opts, { - tagVersionPrefix: 'v', - json: false, path: '', }, 'should forward expected options' diff --git a/test/lib/view.js b/test/lib/view.js index 1363a5b9f9ac8..d136a1f418d10 100644 --- a/test/lib/view.js +++ b/test/lib/view.js @@ -1,5 +1,6 @@ const t = require('tap') const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') let logs const cleanLogs = (done) => { @@ -243,34 +244,33 @@ t.test('should log package info', t => { packument, }, }) - const view = new View({ - flatOptions: { - global: false, - }, + const npm = mockNpm({ + config: { global: false }, }) + const view = new View(npm) const ViewJson = requireInject('../../lib/view.js', { pacote: { packument, }, }) - const viewJson = new ViewJson({ - flatOptions: { - json: true, - }, + const jsonNpm = mockNpm({ + config: { json: true }, }) + const viewJson = new ViewJson(jsonNpm) const ViewUnicode = requireInject('../../lib/view.js', { pacote: { packument, }, }) - const viewUnicode = new ViewUnicode({ - flatOptions: { + const unicodeNpm = mockNpm({ + config: { global: false, unicode: true, }, }) + const viewUnicode = new ViewUnicode(unicodeNpm) t.test('package with license, bugs, repository and other fields', t => { view.exec(['green@1.0.0'], () => { @@ -351,13 +351,14 @@ t.test('should log info of package in current working dir', t => { packument, }, }) - const view = new View({ + const npm = mockNpm({ prefix: testDir, - flatOptions: { - defaultTag: '1.0.0', + config: { + tag: '1.0.0', global: false, }, }) + const view = new View(npm) t.test('specific version', t => { view.exec(['.@1.0.0'], () => { @@ -382,23 +383,24 @@ t.test('should log info by field name', t => { packument, }, }) - const viewJson = new ViewJson({ - flatOptions: { + const jsonNpm = mockNpm({ + config: { json: true, global: false, }, }) + const viewJson = new ViewJson(jsonNpm) + const View = requireInject('../../lib/view.js', { pacote: { packument, }, }) - const view = new View({ - flatOptions: { - global: false, - }, + const npm = mockNpm({ + config: { global: false }, }) + const view = new View(npm) t.test('readme', t => { view.exec(['yellow@1.0.0', 'readme'], () => { @@ -468,11 +470,10 @@ t.test('should log info by field name', t => { t.test('throw error if global mode', (t) => { const View = requireInject('../../lib/view.js') - const view = new View({ - flatOptions: { - global: true, - }, + const npm = mockNpm({ + config: { global: true }, }) + const view = new View(npm) view.exec([], (err) => { t.equals(err.message, 'Cannot use view command in global mode.') t.end() @@ -483,12 +484,11 @@ t.test('throw ENOENT error if package.json misisng', (t) => { const testDir = t.testdir({}) const View = requireInject('../../lib/view.js') - const view = new View({ + const npm = mockNpm({ prefix: testDir, - flatOptions: { - global: false, - }, + config: { global: false }, }) + const view = new View(npm) view.exec([], (err) => { t.match(err, { code: 'ENOENT' }) t.end() @@ -501,12 +501,11 @@ t.test('throw EJSONPARSE error if package.json not json', (t) => { }) const View = requireInject('../../lib/view.js') - const view = new View({ + const npm = mockNpm({ prefix: testDir, - flatOptions: { - global: false, - }, + config: { global: false }, }) + const view = new View(npm) view.exec([], (err) => { t.match(err, { code: 'EJSONPARSE' }) t.end() @@ -519,12 +518,11 @@ t.test('throw error if package.json has no name', (t) => { }) const View = requireInject('../../lib/view.js') - const view = new View({ + const npm = mockNpm({ prefix: testDir, - flatOptions: { - global: false, - }, + config: { global: false }, }) + const view = new View(npm) view.exec([], (err) => { t.equals(err.message, 'Invalid package.json, no "name" field') t.end() @@ -537,12 +535,13 @@ t.test('throws when unpublished', (t) => { packument, }, }) - const view = new View({ - flatOptions: { - defaultTag: '1.0.1', + const npm = mockNpm({ + config: { + tag: '1.0.1', global: false, }, }) + const view = new View(npm) view.exec(['red'], (err) => { t.equals(err.code, 'E404') t.end() @@ -555,12 +554,13 @@ t.test('completion', async t => { packument, }, }) - const view = new View({ - flatOptions: { - defaultTag: '1.0.1', + const npm = mockNpm({ + config: { + tag: '1.0.1', global: false, }, }) + const view = new View(npm) const res = await view.completion({ conf: { argv: { remain: ['npm', 'view', 'green@1.0.0'] } }, }) @@ -570,11 +570,13 @@ t.test('completion', async t => { t.test('no registry completion', async t => { const View = requireInject('../../lib/view.js') - const view = new View({ - flatOptions: { - defaultTag: '1.0.1', + const npm = mockNpm({ + config: { + tag: '1.0.1', + global: false, }, }) + const view = new View(npm) const res = await view.completion({conf: { argv: { remain: ['npm', 'view'] } } }) t.notOk(res, 'there is no package completion') t.end() diff --git a/test/lib/whoami.js b/test/lib/whoami.js index 1a1ecd25742e1..b242ea8941478 100644 --- a/test/lib/whoami.js +++ b/test/lib/whoami.js @@ -1,18 +1,21 @@ const { test } = require('tap') const requireInject = require('require-inject') +const mockNpm = require('../fixtures/mock-npm') test('whoami', (t) => { t.plan(3) const Whoami = requireInject('../../lib/whoami.js', { '../../lib/utils/get-identity.js': () => Promise.resolve('foo'), }) - const whoami = new Whoami({ - flatOptions: {}, + const npm = mockNpm({ + config: { json: false }, output: (output) => { t.equal(output, 'foo', 'should output the username') }, }) + const whoami = new Whoami(npm) + whoami.exec([], (err) => { t.ifError(err, 'npm whoami') t.ok('should successfully print username') @@ -24,12 +27,13 @@ test('whoami json', (t) => { const Whoami = requireInject('../../lib/whoami.js', { '../../lib/utils/get-identity.js': () => Promise.resolve('foo'), }) - const whoami = new Whoami({ - flatOptions: { json: true }, + const npm = mockNpm({ + config: { json: true }, output: (output) => { - t.equal(output, '"foo"', 'should output the username as json') + t.equal(output, '"foo"', 'should output the username') }, }) + const whoami = new Whoami(npm) whoami.exec([], (err) => { t.ifError(err, 'npm whoami') From 8cce4282f7bef11aeeb73cffd532b477b241985e Mon Sep 17 00:00:00 2001 From: Kenrick Date: Mon, 15 Mar 2021 16:09:29 +0800 Subject: [PATCH 09/42] fix(publish): handle case where multiple config list is present When not handled, when there are multiple entries in this.npm.config.list, it causes crash as described in #2834 The change here merge everything in this.npm.config.list, because as I observed, the default config is present only at the last entry. Fix: #2834 Co-authored-by: @wraithgar PR-URL: https://github.com/npm/cli/pull/2865 Credit: @kenrick95 Close: #2865 Reviewed-by: @isaacs, @wraithgar --- lib/publish.js | 2 +- test/lib/publish.js | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/lib/publish.js b/lib/publish.js index f46f70291bebc..bfea1e139dbfc 100644 --- a/lib/publish.js +++ b/lib/publish.js @@ -141,7 +141,7 @@ class Publish extends BaseCommand { publishConfigToOpts (publishConfig) { // create a new object that inherits from the config stack // then squash the css-case into camelCase opts, like we do - return flatten({...this.npm.config.list[0], ...publishConfig}) + return flatten({...flatten(this.npm.config.list[0]), ...publishConfig}) } } module.exports = Publish diff --git a/test/lib/publish.js b/test/lib/publish.js index bfc58a4c36a06..d1a1cd630c0e6 100644 --- a/test/lib/publish.js +++ b/test/lib/publish.js @@ -473,3 +473,48 @@ t.test('read registry only from publishConfig', t => { t.end() }) }) + +t.test('able to publish after if encountered multiple configs', t => { + t.plan(3) + + const registry = 'https://some.registry' + const tag = 'better-tag' + const publishConfig = { registry } + const testDir = t.testdir({ + 'package.json': JSON.stringify({ + name: 'my-cool-pkg', + version: '1.0.0', + publishConfig, + }, null, 2), + }) + + const configList = [ + { ...defaults, tag }, + { ...defaults, registry: `https://other.registry`, tag: 'some-tag' }, + defaults, + ] + + const Publish = requireInject('../../lib/publish.js', { + libnpmpublish: { + publish: (manifest, tarData, opts) => { + t.same(opts.defaultTag, tag, 'gets option for expected tag') + }, + }, + }) + const publish = new Publish({ + config: { + list: configList, + getCredentialsByURI: (uri) => { + t.same(uri, registry, 'gets credentials for expected registry') + return { token: 'some.registry.token' } + }, + }, + }) + + publish.exec([testDir], (er) => { + if (er) + throw er + t.pass('got to callback') + t.end() + }) +}) From 6598bfe8697439e827d84981f8504febca64a55a Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 15 Mar 2021 16:15:27 -0700 Subject: [PATCH 10/42] New consolidated config definitions This replaces the multiple separate sets of objects and documentation, some of which had defaults and/or types, some of which didn't, and cleans up a lot of configs that are no longer used. Deprecated configs are now marked, and the approach used to create these config definitions ensures that it is impossible to create a new config option that lacks the appropriate data for it. --- lib/utils/config/definition.js | 157 ++ lib/utils/config/definitions.js | 2014 +++++++++++++++++ lib/utils/config/describe-all.js | 16 + lib/utils/config/flatten.js | 32 + lib/utils/config/index.js | 52 + ...lib-utils-config-definition.js-TAP.test.js | 264 +++ ...ib-utils-config-definitions.js-TAP.test.js | 153 ++ ...b-utils-config-describe-all.js-TAP.test.js | 1349 +++++++++++ ...test-lib-utils-config-index.js-TAP.test.js | 127 ++ test/lib/utils/config/definition.js | 150 ++ test/lib/utils/config/definitions.js | 697 ++++++ test/lib/utils/config/describe-all.js | 6 + test/lib/utils/config/flatten.js | 34 + test/lib/utils/config/index.js | 24 + 14 files changed, 5075 insertions(+) create mode 100644 lib/utils/config/definition.js create mode 100644 lib/utils/config/definitions.js create mode 100644 lib/utils/config/describe-all.js create mode 100644 lib/utils/config/flatten.js create mode 100644 lib/utils/config/index.js create mode 100644 tap-snapshots/test-lib-utils-config-definition.js-TAP.test.js create mode 100644 tap-snapshots/test-lib-utils-config-definitions.js-TAP.test.js create mode 100644 tap-snapshots/test-lib-utils-config-describe-all.js-TAP.test.js create mode 100644 tap-snapshots/test-lib-utils-config-index.js-TAP.test.js create mode 100644 test/lib/utils/config/definition.js create mode 100644 test/lib/utils/config/definitions.js create mode 100644 test/lib/utils/config/describe-all.js create mode 100644 test/lib/utils/config/flatten.js create mode 100644 test/lib/utils/config/index.js diff --git a/lib/utils/config/definition.js b/lib/utils/config/definition.js new file mode 100644 index 0000000000000..b6323a51259dc --- /dev/null +++ b/lib/utils/config/definition.js @@ -0,0 +1,157 @@ +// class that describes a config key we know about +// this keeps us from defining a config key and not +// providing a default, description, etc. +// +// TODO: some kind of categorization system, so we can +// say "these are for registry access", "these are for +// version resolution" etc. + +const required = [ + 'type', + 'description', + 'default', + 'key', +] + +const allowed = [ + 'default', + 'type', + 'description', + 'flatten', + 'short', + 'typeDescription', + 'defaultDescription', + 'deprecated', + 'key', +] + +const { + typeDefs: { + semver: { type: semver }, + Umask: { type: Umask }, + url: { type: url }, + path: { type: path }, + }, +} = require('@npmcli/config') + +class Definition { + constructor (key, def) { + this.key = key + Object.assign(this, def) + this.validate() + if (!this.defaultDescription) + this.defaultDescription = describeValue(this.default) + if (!this.typeDescription) + this.typeDescription = describeType(this.type) + } + + validate () { + for (const req of required) { + if (!Object.prototype.hasOwnProperty.call(this, req)) + throw new Error(`config lacks ${req}: ${this.key}`) + } + if (!this.key) + throw new Error(`config lacks key: ${this.key}`) + for (const field of Object.keys(this)) { + if (!allowed.includes(field)) + throw new Error(`config defines unknown field ${field}: ${this.key}`) + } + } + + // a textual description of this config, suitable for help output + describe () { + const description = unindent(this.description) + const deprecated = !this.deprecated ? '' + : `* DEPRECATED: ${unindent(this.deprecated)}\n` + return wrapAll(`#### \`${this.key}\` + +* Default: ${unindent(this.defaultDescription)} +* Type: ${unindent(this.typeDescription)} +${deprecated} +${description} +`) + } +} + +const describeType = type => { + if (Array.isArray(type)) { + const descriptions = type + .filter(t => t !== Array) + .map(t => describeType(t)) + + // [a] => "a" + // [a, b] => "a or b" + // [a, b, c] => "a, b, or c" + // [a, Array] => "a (can be set multiple times)" + // [a, Array, b] => "a or b (can be set multiple times)" + const last = descriptions.length > 1 ? [descriptions.pop()] : [] + const oxford = descriptions.length > 1 ? ', or ' : ' or ' + const words = [descriptions.join(', ')].concat(last).join(oxford) + const multiple = type.includes(Array) ? ' (can be set multiple times)' + : '' + return `${words}${multiple}` + } + + // Note: these are not quite the same as the description printed + // when validation fails. In that case, we want to give the user + // a bit more information to help them figure out what's wrong. + switch (type) { + case String: + return 'String' + case Number: + return 'Number' + case Umask: + return 'Octal numeric string in range 0000..0777 (0..511)' + case Boolean: + return 'Boolean' + case Date: + return 'Date' + case path: + return 'Path' + case semver: + return 'SemVer string' + case url: + return 'URL' + default: + return describeValue(type) + } +} + +// if it's a string, quote it. otherwise, just cast to string. +const describeValue = val => + typeof val === 'string' ? JSON.stringify(val) : String(val) + +const unindent = s => { + // get the first \n followed by a bunch of spaces, and pluck off + // that many spaces from the start of every line. + const match = s.match(/\n +/) + return !match ? s.trim() : s.split(match[0]).join('\n').trim() +} + +const wrap = (s) => { + const cols = Math.min(Math.max(20, process.stdout.columns) || 80, 80) - 5 + return unindent(s).split(/[ \n]+/).reduce((left, right) => { + const last = left.split('\n').pop() + const join = last.length && last.length + right.length > cols ? '\n' : ' ' + return left + join + right + }) +} + +const wrapAll = s => { + let inCodeBlock = false + return s.split('\n\n').map(block => { + if (inCodeBlock || block.startsWith('```')) { + inCodeBlock = !block.endsWith('```') + return block + } + + if (block.charAt(0) === '*') { + return '* ' + block.substr(1).trim().split('\n* ').map(li => { + return wrap(li).replace(/\n/g, '\n ') + }).join('\n* ') + } else + return wrap(block) + }).join('\n\n') +} + +module.exports = Definition diff --git a/lib/utils/config/definitions.js b/lib/utils/config/definitions.js new file mode 100644 index 0000000000000..1ad3a166bdf81 --- /dev/null +++ b/lib/utils/config/definitions.js @@ -0,0 +1,2014 @@ +const definitions = {} +module.exports = definitions + +const Definition = require('./definition.js') + +const { version: npmVersion } = require('../../../package.json') +const ciDetect = require('@npmcli/ci-detect') +const ciName = ciDetect() +const querystring = require('querystring') +const isWindows = require('../is-windows.js') +const { join } = require('path') + +// used by cafile flattening to flatOptions.ca +const fs = require('fs') +const maybeReadFile = file => { + try { + return fs.readFileSync(file, 'utf8') + } catch (er) { + if (er.code !== 'ENOENT') + throw er + return null + } +} + +const editor = process.env.EDITOR || + process.env.VISUAL || + (isWindows ? 'notepad.exe' : 'vi') + +const shell = isWindows ? process.env.ComSpec || 'cmd' + : process.env.SHELL || 'sh' + +const { tmpdir, networkInterfaces } = require('os') +const getLocalAddresses = () => { + try { + return Object.values(networkInterfaces()).map( + int => int.map(({ address }) => address) + ).reduce((set, addrs) => set.concat(addrs), [null]) + } catch (e) { + return [null] + } +} + +const unicode = /UTF-?8$/i.test( + process.env.LC_ALL || + process.env.LC_CTYPE || + process.env.LANG +) + +// use LOCALAPPDATA on Windows, if set +// https://github.com/npm/cli/pull/899 +const cacheRoot = (isWindows && process.env.LOCALAPPDATA) || '~' +const cacheExtra = isWindows ? 'npm-cache' : '.npm' +const cache = `${cacheRoot}/${cacheExtra}` + +const Config = require('@npmcli/config') + +// TODO: refactor these type definitions so that they are less +// weird to pull out of the config module. +// TODO: use better type definition/validation API, nopt's is so weird. +const { + typeDefs: { + semver: { type: semver }, + Umask: { type: Umask }, + url: { type: url }, + path: { type: path }, + }, +} = Config + +const define = (key, def) => { + /* istanbul ignore if - this should never happen, prevents mistakes below */ + if (definitions[key]) + throw new Error(`defining key more than once: ${key}`) + definitions[key] = new Definition(key, def) +} + +// basic flattening function, just copy it over camelCase +const flatten = (key, obj, flatOptions) => { + const camel = key.replace(/-([a-z])/g, (_0, _1) => _1.toUpperCase()) + flatOptions[camel] = obj[key] +} + +// TODO: +// Instead of having each definition provide a flatten method, +// provide the (?list of?) flat option field(s?) that it impacts. +// When that config is set, we mark the relevant flatOption fields +// dirty. Then, a getter for that field defines how we actually +// set it. +// +// So, `save-dev`, `save-optional`, `save-prod`, et al would indicate +// that they affect the `saveType` flat option. Then the config.flat +// object has a `get saveType () { ... }` that looks at the "real" +// config settings from files etc and returns the appropriate value. +// +// Getters will also (maybe?) give us a hook to audit flat option +// usage, so we can document and group these more appropriately. +// +// This will be a problem with cases where we currently do: +// const opts = { ...npm.flatOptions, foo: 'bar' }, but we can maybe +// instead do `npm.config.set('foo', 'bar')` prior to passing the +// config object down where it needs to go. +// +// This way, when we go hunting for "where does saveType come from anyway!?" +// while fixing some Arborist bug, we won't have to hunt through too +// many places. + +// Define all config keys we know about + +define('_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\`. + `, +}) + +define('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\`. + `, + flatten, +}) + +define('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, +}) + +define('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, +}) + +define('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) + }, +}) + +define('always-auth', { + default: false, + type: Boolean, + description: ` + Force npm to always require authentication when accessing the registry, + even for \`GET\` requests. + `, + flatten, +}) + +define('audit', { + default: true, + type: Boolean, + description: ` + When "true" submit audit reports alongside \`npm install\` runs 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, +}) + +define('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, +}) + +define('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, +}) + +define('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, +}) + +define('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, +}) + +define('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, +}) + +define('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, +}) + +define('cache', { + default: cache, + 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') + }, +}) + +define('cache-max', { + default: Infinity, + 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 + }, +}) + +define('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 + }, +}) + +define('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) + }, +}) + +define('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, +}) + +define('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, +}) + +define('ci-name', { + default: ciName || 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, +}) + +define('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, +}) + +define('color', { + default: !process.env.NO_COLOR || process.env.NO_COLOR === '0', + 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 + }, +}) + +define('commit-hooks', { + default: true, + type: Boolean, + description: ` + Run git commit hooks when using the \`npm version\` command. + `, + flatten, +}) + +define('depth', { + default: null, + defaultDescription: ` + \`Infinity\` if \`--all\` is set, otherwise \`1\` + `, + 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, +}) + +define('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] + }, +}) + +define('diff', { + default: [], + type: [String, Array], + description: ` + Define arguments to compare in \`npm diff\`. + `, + flatten, +}) + +define('diff-ignore-all-space', { + default: false, + type: Boolean, + description: ` + Ignore whitespace when comparing lines in \`npm diff\`. + `, + flatten, +}) + +define('diff-name-only', { + default: false, + type: Boolean, + description: ` + Prints only filenames when using \`npm diff\`. + `, + flatten, +}) + +define('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, +}) + +define('diff-dst-prefix', { + default: 'b/', + type: String, + description: ` + Destination prefix to be used in \`npm diff\` output. + `, + flatten, +}) + +define('diff-src-prefix', { + default: 'a/', + type: String, + description: ` + Source prefix to be used in \`npm diff\` output. + `, + flatten, +}) + +define('diff-text', { + default: false, + type: Boolean, + description: ` + Treat all files as text in \`npm diff\`. + `, + flatten, +}) + +define('diff-unified', { + default: 3, + type: Number, + description: ` + The number of lines of context to print in \`npm diff\`. + `, + flatten, +}) + +define('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, +}) + +define('editor', { + default: editor, + 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, +}) + +define('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, +}) + +define('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] + }, +}) + +define('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] + }, +}) + +define('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] + }, +}) + +define('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] + }, +}) + +define('fetch-timeout', { + default: 5 * 60 * 1000, + defaultDescription: `${5 * 60 * 1000} (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] + }, +}) + +define('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. + + 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, +}) + +define('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, +}) + +define('format-package-lock', { + default: true, + type: Boolean, + description: ` + Format \`package-lock.json\` or \`npm-shrinkwrap.json\` as a human + readable file. + `, + flatten, +}) + +define('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, +}) + +define('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, +}) + +define('git-tag-version', { + default: true, + type: Boolean, + description: ` + Tag the commit when using the \`npm version\` command. + `, + flatten, +}) + +define('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, +}) + +define('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, +}) + +// the globalconfig has its default defined outside of this module +define('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, +}) + +define('heading', { + default: 'npm', + type: String, + description: ` + The string that starts all the debugging log output. + `, + flatten, +}) + +define('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, +}) + +define('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, +}) + +define('ignore-scripts', { + default: false, + type: Boolean, + description: ` + If true, npm does not run scripts specified in package.json files. + `, + flatten, +}) + +define('include', { + default: [], + type: [Array, 'prod', 'dev', 'optional', 'peer'], + description: ` + Option that allows for defining which types of dependencies to install. + + This is the inverse of \`--omit=\`. + + Dependency types specified in \`--include\` will not be omitted, + regardless of the order in which omit/include are specified on the + command-line. + `, + flatten (key, obj, flatOptions) { + // just call the omit flattener, it reads from obj.include + definitions.omit.flatten('omit', obj, flatOptions) + }, +}) + +define('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, +}) + +define('init-author-email', { + default: '', + type: String, + description: ` + The value \`npm init\` should use by default for the package author's + email. + `, +}) + +define('init-author-name', { + default: '', + type: String, + description: ` + The value \`npm init\` should use by default for the package author's name. + `, +}) + +define('init-author-url', { + default: '', + type: ['', url], + description: ` + The value \`npm init\` should use by default for the package author's homepage. + `, +}) + +define('init-license', { + default: 'ISC', + type: String, + description: ` + The value \`npm init\` should use by default for the package license. + `, +}) + +define('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). + `, +}) + +define('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. + `, +}) + +// these "aliases" are historically supported in .npmrc files, unfortunately +// They should be removed in a future npm version. +define('init.author.email', { + default: '', + type: String, + deprecated: ` + Use \`--init-author-email\` instead.`, + description: ` + Alias for \`--init-author-email\` + `, +}) + +define('init.author.name', { + default: '', + type: String, + deprecated: ` + Use \`--init-author-name\` instead. + `, + description: ` + Alias for \`--init-author-name\` + `, +}) + +define('init.author.url', { + default: '', + type: ['', url], + deprecated: ` + Use \`--init-author-url\` instead. + `, + description: ` + Alias for \`--init-author-url\` + `, +}) + +define('init.license', { + default: 'ISC', + type: String, + deprecated: ` + Use \`--init-license\` instead. + `, + description: ` + Alias for \`--init-license\` + `, +}) + +define('init.module', { + default: '~/.npm-init.js', + type: path, + deprecated: ` + Use \`--init-module\` instead. + `, + description: ` + Alias for \`--init-module\` + `, +}) + +define('init.version', { + default: '1.0.0', + type: semver, + deprecated: ` + Use \`--init-version\` instead. + `, + description: ` + Alias for \`--init-version\` + `, +}) + +define('json', { + default: false, + type: Boolean, + description: ` + Whether or not to output JSON data, rather than the normal output. + + This feature is currently experimental, and the output data structures + for many commands is either not implemented in JSON yet, or subject to + change. Only the output from \`npm ls --json\` and \`npm search --json\` + are currently valid. + `, + flatten, +}) + +define('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, +}) + +define('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, +}) + +define('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, +}) + +define('link', { + default: false, + type: Boolean, + description: ` + If true, then local installs will link if there is a suitable globally + installed package. + + Note that this means that local installs can cause things to be installed + into the global space at the same time. The link is only done if one of + the two conditions are met: + + * The package is not already installed globally, or + * the globally installed version is identical to the version that is + being installed locally. + `, +}) + +define('local-address', { + default: null, + type: getLocalAddresses(), + 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, +}) + +define('loglevel', { + default: 'notice', + type: [ + 'silent', + 'error', + 'warn', + 'notice', + 'http', + 'timing', + 'info', + 'verbose', + 'silly', + ], + description: ` + What level of logs to report. On failure, *all* logs are written to + \`npm-debug.log\` in the current working directory. + + Any logs of a higher level than the setting are shown. The default is + "notice". + `, +}) + +define('logs-max', { + default: 10, + type: Number, + description: ` + The maximum number of log files to store. + `, +}) + +define('long', { + default: false, + type: Boolean, + short: 'l', + description: ` + Show extended information in \`npm ls\` and \`npm search\`. + `, +}) + +define('maxsockets', { + default: Infinity, + type: Number, + description: ` + The maximum number of connections to use per origin (protocol/host/port + combination). + `, + flatten (key, obj, flatOptions) { + flatOptions.maxSockets = obj[key] + }, +}) + +define('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, +}) + +define('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. + `, +}) + +define('node-version', { + default: process.version, + defaultDescription: 'Node.js `process.version` value', + type: semver, + description: ` + The node version to use when checking a package's \`engines\` setting. + `, + flatten, +}) + +define('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(',') + }, +}) + +define('npm-version', { + default: npmVersion, + defaultDescription: 'Output of `npm --version`', + type: semver, + description: ` + The npm version to use when checking a package's \`engines\` setting. + `, + flatten, +}) + +define('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, +}) + +define('omit', { + default: process.env.NODE_ENV === 'production' ? ['dev'] : [], + 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)) + }, +}) + +define('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) + }, +}) + +define('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) + }, +}) + +define('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, +}) + +define('package', { + default: [], + type: [String, Array], + description: ` + The package to install for [\`npm exec\`](/commands/npm-exec) + `, + flatten, +}) + +define('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, +}) + +define('package-lock-only', { + default: false, + type: Boolean, + description: ` + If set to true, it will update only the \`package-lock.json\`, instead of + checking \`node_modules\` and downloading dependencies. + `, + flatten, +}) + +define('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, +}) + +define('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, +}) + +define('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, +}) + +// `prefix` has its default defined outside of this module +define('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. + `, +}) + +define('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, +}) + +define('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) + }, +}) + +define('progress', { + default: !ciName, + defaultDescription: ` + \`true\` unless running in a known CI system + `, + 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. + `, +}) + +define('proxy', { + default: null, + type: [null, false, url], // allow proxy to be disabled explicitly + 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, +}) + +define('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, +}) + +define('rebuild-bundle', { + default: true, + type: Boolean, + description: ` + Rebuild bundled dependencies after installation. + `, + flatten, +}) + +define('registry', { + default: 'https://registry.npmjs.org/', + type: url, + description: ` + The base URL of the npm registry. + `, + flatten, +}) + +define('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. + `, + flatten, +}) + +define('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'] + }, +}) + +// XXX: We should really deprecate all these `--save-blah` switches +// in favor of a single `--save-type` option. The unfortunate shortcut +// we took for `--save-peer --save-optional` being `--save-type=peerOptional` +// makes this tricky, and likely a breaking change. + +define('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' + }, +}) + +define('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, +}) + +define('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' + }, +}) + +define('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' + }, +}) + +define('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, +}) + +define('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' + }, +}) + +define('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 a private registry for the first time: + + \`\`\`bash + npm login --scope=@mycorp --registry=https://registry.mycorp.com + \`\`\` + + This will cause \`@mycorp\` to be mapped to the registry for future + installation of packages specified according to the pattern + \`@mycorp/package\`. + `, + flatten (key, obj, flatOptions) { + const value = obj[key] + flatOptions.projectScope = value && !/^@/.test(value) ? `@${value}` : value + }, +}) + +define('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 run\` command. + `, + flatten (key, obj, flatOptions) { + flatOptions.scriptShell = obj[key] || undefined + }, +}) + +define('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] + }, +}) + +define('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] + }, +}) + +define('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]) + }, +}) + +define('searchstaleness', { + default: 15 * 60, + 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] + }, +}) + +define('shell', { + default: shell, + 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, +}) + +define('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) + }, +}) + +define('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, +}) + +define('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, +}) + +define('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, +}) + +define('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, +}) + +define('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, +}) + +define('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] + }, +}) + +define('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. + `, + flatten (key, obj, flatOptions) { + flatOptions.defaultTag = obj[key] + }, +}) + +define('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, +}) + +define('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\`. + `, +}) + +define('tmp', { + default: tmpdir(), + defaultDescription: ` + The value returned by the Node.js \`os.tmpdir()\` method + + `, + type: path, + deprecated: ` + This setting is no longer used. npm stores temporary files in a special + location in the cache, and they are managed by + [\`cacache\`](http://npm.im/cacache). + `, + description: ` + Historically, the location where temporary files were stored. No longer + relevant. + `, +}) + +define('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, +}) + +define('unicode', { + default: unicode, + 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. + `, +}) + +define('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. + `, +}) + +define('usage', { + default: false, + type: Boolean, + short: ['?', 'H', 'h'], + description: ` + Show short usage output about the command specified. + `, +}) + +define('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\` + * \`{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() + }, +}) + +define('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. + `, +}) + +define('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. + `, +}) + +define('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. + `, +}) + +define('viewer', { + default: isWindows ? 'browser' : 'man', + defaultDescription: ` + "man" on Posix, "browser" on Windows + `, + type: String, + description: ` + The program to use to view help content. + + Set to \`"browser"\` to view html help content in the default web browser. + `, +}) + +define('yes', { + default: false, + type: Boolean, + short: 'y', + description: ` + Automatically answer "yes" to any prompts that npm might print on + the command line. + `, +}) diff --git a/lib/utils/config/describe-all.js b/lib/utils/config/describe-all.js new file mode 100644 index 0000000000000..ab3f3a63ea751 --- /dev/null +++ b/lib/utils/config/describe-all.js @@ -0,0 +1,16 @@ +const definitions = require('./definitions.js') +const describeAll = () => { + // sort not-deprecated ones to the top + /* istanbul ignore next - typically already sorted in the definitions file, + * but this is here so that our help doc will stay consistent if we decide + * to move them around. */ + const sort = ([keya, {deprecated: depa}], [keyb, {deprecated: depb}]) => { + return depa && !depb ? 1 + : !depa && depb ? -1 + : keya.localeCompare(keyb) + } + return Object.entries(definitions).sort(sort) + .map(([key, def]) => def.describe()) + .join('\n\n') +} +module.exports = describeAll diff --git a/lib/utils/config/flatten.js b/lib/utils/config/flatten.js new file mode 100644 index 0000000000000..f6d6124bddf7a --- /dev/null +++ b/lib/utils/config/flatten.js @@ -0,0 +1,32 @@ +// 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/lib/utils/config/index.js b/lib/utils/config/index.js new file mode 100644 index 0000000000000..a24f5865242bf --- /dev/null +++ b/lib/utils/config/index.js @@ -0,0 +1,52 @@ +const flatten = require('./flatten.js') +const definitions = require('./definitions.js') +const describeAll = require('./describe-all.js') + +// aliases where they get expanded into a completely different thing +// these are NOT supported in the environment or npmrc files, only +// expanded on the CLI. +// TODO: when we switch off of nopt, use an arg parser that supports +// more reasonable aliasing and short opts right in the definitions set. +const shorthands = { + 'enjoy-by': ['--before'], + d: ['--loglevel', 'info'], + dd: ['--loglevel', 'verbose'], + ddd: ['--loglevel', 'silly'], + quiet: ['--loglevel', 'warn'], + q: ['--loglevel', 'warn'], + s: ['--loglevel', 'silent'], + silent: ['--loglevel', 'silent'], + verbose: ['--loglevel', 'verbose'], + desc: ['--description'], + help: ['--usage'], + local: ['--no-global'], + n: ['--no-yes'], + no: ['--no-yes'], + porcelain: ['--parseable'], + readonly: ['--read-only'], + reg: ['--registry'], +} + +for (const [key, {short}] of Object.entries(definitions)) { + if (!short) + continue + // can be either an array or string + for (const s of [].concat(short)) + shorthands[s] = [`--${key}`] +} + +module.exports = { + get defaults () { + // NB: 'default' is a reserved word + return Object.entries(definitions).map(([key, { default: def }]) => { + return [key, def] + }).reduce((defaults, [key, def]) => { + defaults[key] = def + return defaults + }, {}) + }, + definitions, + flatten, + shorthands, + describeAll, +} diff --git a/tap-snapshots/test-lib-utils-config-definition.js-TAP.test.js b/tap-snapshots/test-lib-utils-config-definition.js-TAP.test.js new file mode 100644 index 0000000000000..ad506ae8e3585 --- /dev/null +++ b/tap-snapshots/test-lib-utils-config-definition.js-TAP.test.js @@ -0,0 +1,264 @@ +/* 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/lib/utils/config/definition.js TAP basic definition > description of deprecated thing 1`] = ` +#### \`deprecated\` + +* Default: A number bigger than 1 +* Type: An expression of a numeric quantity using numerals +* DEPRECATED: do not use this + +it should not be used ever + +not even once. +` + +exports[`test/lib/utils/config/definition.js TAP basic definition > human-readable description 1`] = ` +#### \`key\` + +* Default: "some default value" +* Type: Number or String + +just a test thingie +` + +exports[`test/lib/utils/config/definition.js TAP long description > cols=-1 1`] = ` +#### \`walden\` + +* Default: true +* Type: Boolean + +WHEN I WROTE the +following pages, +or rather the +bulk of them, I +lived alone, in +the woods, a +mile from any +neighbor, in a +house which I +had built +myself, on the +shore of Walden +Pond, in +Concord, +Massachusetts, +and earned my +living by the +labor of my +hands only. I +lived there two +years and two +months. At +present I am a +sojourner in +civilized life +again. + +I should not +obtrude my +affairs so much +on the notice of +my readers if +very particular +inquiries had +not been made by +my townsmen +concerning my +mode of life, +which some would +call +impertinent, +though they do +not appear to me +at all +impertinent, +but, considering +the +circumstances, +very natural and +pertinent. + +\`\`\` +this.is('a', { + code: 'sample', +}) + +with (multiple) { + blocks() +} +\`\`\` + +` + +exports[`test/lib/utils/config/definition.js TAP long description > cols=0 1`] = ` +#### \`walden\` + +* Default: true +* Type: Boolean + +WHEN I WROTE the +following pages, +or rather the +bulk of them, I +lived alone, in +the woods, a +mile from any +neighbor, in a +house which I +had built +myself, on the +shore of Walden +Pond, in +Concord, +Massachusetts, +and earned my +living by the +labor of my +hands only. I +lived there two +years and two +months. At +present I am a +sojourner in +civilized life +again. + +I should not +obtrude my +affairs so much +on the notice of +my readers if +very particular +inquiries had +not been made by +my townsmen +concerning my +mode of life, +which some would +call +impertinent, +though they do +not appear to me +at all +impertinent, +but, considering +the +circumstances, +very natural and +pertinent. + +\`\`\` +this.is('a', { + code: 'sample', +}) + +with (multiple) { + blocks() +} +\`\`\` + +` + +exports[`test/lib/utils/config/definition.js TAP long description > cols=40 1`] = ` +#### \`walden\` + +* Default: true +* Type: Boolean + +WHEN I WROTE the following pages, or +rather the bulk of them, I lived +alone, in the woods, a mile from any +neighbor, in a house which I had +built myself, on the shore of Walden +Pond, in Concord, Massachusetts, and +earned my living by the labor of my +hands only. I lived there two years +and two months. At present I am a +sojourner in civilized life again. + +I should not obtrude my affairs so +much on the notice of my readers if +very particular inquiries had not +been made by my townsmen concerning +my mode of life, which some would +call impertinent, though they do not +appear to me at all impertinent, +but, considering the circumstances, +very natural and pertinent. + +\`\`\` +this.is('a', { + code: 'sample', +}) + +with (multiple) { + blocks() +} +\`\`\` + +` + +exports[`test/lib/utils/config/definition.js TAP long description > cols=9000 1`] = ` +#### \`walden\` + +* Default: true +* Type: Boolean + +WHEN I WROTE the following pages, or rather the bulk of them, I lived alone, +in the woods, a mile from any neighbor, in a house which I had built myself, +on the shore of Walden Pond, in Concord, Massachusetts, and earned my living +by the labor of my hands only. I lived there two years and two months. At +present I am a sojourner in civilized life again. + +I should not obtrude my affairs so much on the notice of my readers if very +particular inquiries had not been made by my townsmen concerning my mode of +life, which some would call impertinent, though they do not appear to me at +all impertinent, but, considering the circumstances, very natural and +pertinent. + +\`\`\` +this.is('a', { + code: 'sample', +}) + +with (multiple) { + blocks() +} +\`\`\` + +` + +exports[`test/lib/utils/config/definition.js TAP long description > cols=NaN 1`] = ` +#### \`walden\` + +* Default: true +* Type: Boolean + +WHEN I WROTE the following pages, or rather the bulk of them, I lived alone, +in the woods, a mile from any neighbor, in a house which I had built myself, +on the shore of Walden Pond, in Concord, Massachusetts, and earned my living +by the labor of my hands only. I lived there two years and two months. At +present I am a sojourner in civilized life again. + +I should not obtrude my affairs so much on the notice of my readers if very +particular inquiries had not been made by my townsmen concerning my mode of +life, which some would call impertinent, though they do not appear to me at +all impertinent, but, considering the circumstances, very natural and +pertinent. + +\`\`\` +this.is('a', { + code: 'sample', +}) + +with (multiple) { + blocks() +} +\`\`\` + +` diff --git a/tap-snapshots/test-lib-utils-config-definitions.js-TAP.test.js b/tap-snapshots/test-lib-utils-config-definitions.js-TAP.test.js new file mode 100644 index 0000000000000..3e82da69913c9 --- /dev/null +++ b/tap-snapshots/test-lib-utils-config-definitions.js-TAP.test.js @@ -0,0 +1,153 @@ +/* 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/lib/utils/config/definitions.js TAP > all config keys 1`] = ` +Array [ + "_auth", + "access", + "all", + "allow-same-version", + "also", + "always-auth", + "audit", + "audit-level", + "auth-type", + "before", + "bin-links", + "browser", + "ca", + "cache", + "cache-max", + "cache-min", + "cafile", + "call", + "cert", + "ci-name", + "cidr", + "color", + "commit-hooks", + "depth", + "description", + "diff", + "diff-ignore-all-space", + "diff-name-only", + "diff-no-prefix", + "diff-dst-prefix", + "diff-src-prefix", + "diff-text", + "diff-unified", + "dry-run", + "editor", + "engine-strict", + "fetch-retries", + "fetch-retry-factor", + "fetch-retry-maxtimeout", + "fetch-retry-mintimeout", + "fetch-timeout", + "force", + "foreground-scripts", + "format-package-lock", + "fund", + "git", + "git-tag-version", + "global", + "global-style", + "globalconfig", + "heading", + "https-proxy", + "if-present", + "ignore-scripts", + "include", + "include-staged", + "init-author-email", + "init-author-name", + "init-author-url", + "init-license", + "init-module", + "init-version", + "init.author.email", + "init.author.name", + "init.author.url", + "init.license", + "init.module", + "init.version", + "json", + "key", + "legacy-bundling", + "legacy-peer-deps", + "link", + "local-address", + "loglevel", + "logs-max", + "long", + "maxsockets", + "message", + "node-options", + "node-version", + "noproxy", + "npm-version", + "offline", + "omit", + "only", + "optional", + "otp", + "package", + "package-lock", + "package-lock-only", + "parseable", + "prefer-offline", + "prefer-online", + "prefix", + "preid", + "production", + "progress", + "proxy", + "read-only", + "rebuild-bundle", + "registry", + "save", + "save-bundle", + "save-dev", + "save-exact", + "save-optional", + "save-peer", + "save-prefix", + "save-prod", + "scope", + "script-shell", + "searchexclude", + "searchlimit", + "searchopts", + "searchstaleness", + "shell", + "shrinkwrap", + "sign-git-commit", + "sign-git-tag", + "sso-poll-frequency", + "sso-type", + "strict-peer-deps", + "strict-ssl", + "tag", + "tag-version-prefix", + "timing", + "tmp", + "umask", + "unicode", + "update-notifier", + "usage", + "user-agent", + "userconfig", + "version", + "versions", + "viewer", + "yes", +] +` + +exports[`test/lib/utils/config/definitions.js TAP > all config keys that are shared to flatOptions 1`] = ` +Array [] +` diff --git a/tap-snapshots/test-lib-utils-config-describe-all.js-TAP.test.js b/tap-snapshots/test-lib-utils-config-describe-all.js-TAP.test.js new file mode 100644 index 0000000000000..ca758c701d174 --- /dev/null +++ b/tap-snapshots/test-lib-utils-config-describe-all.js-TAP.test.js @@ -0,0 +1,1349 @@ +/* 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/lib/utils/config/describe-all.js TAP > must match snapshot 1`] = ` +#### \`_auth\` + +* Default: null +* Type: null or String + +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\`. + +#### \`access\` + +* Default: 'restricted' for scoped packages, 'public' for unscoped packages +* Type: null, "restricted", or "public" + +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\`. + +#### \`all\` + +* Default: false +* Type: Boolean + +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. + +#### \`allow-same-version\` + +* Default: false +* Type: Boolean + +Prevents throwing an error when \`npm version\` is used to set the new version +to the same value as the current version. + +#### \`always-auth\` + +* Default: false +* Type: Boolean + +Force npm to always require authentication when accessing the registry, even +for \`GET\` requests. + +#### \`audit\` + +* Default: true +* Type: Boolean + +When "true" submit audit reports alongside \`npm install\` runs 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. + +#### \`audit-level\` + +* Default: null +* Type: "low", "moderate", "high", "critical", "none", or null + +The minimum level of vulnerability for \`npm audit\` to exit with a non-zero +exit code. + +#### \`before\` + +* Default: null +* Type: null or Date + +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\`. + +#### \`bin-links\` + +* Default: true +* Type: Boolean + +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. + +#### \`browser\` + +* Default: OS X: \`"open"\`, Windows: \`"start"\`, Others: \`"xdg-open"\` +* Type: null, Boolean, or String + +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. + +#### \`ca\` + +* Default: null +* Type: null or String (can be set multiple times) + +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. + +#### \`cache\` + +* Default: Windows: \`%LocalAppData%\\npm-cache\`, Posix: \`~/.npm\` +* Type: Path + +The location of npm's cache directory. See [\`npm +cache\`](/commands/npm-cache) + +#### \`cafile\` + +* Default: null +* Type: Path + +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. + +#### \`call\` + +* Default: "" +* Type: String + +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" +\`\`\` + + +#### \`cert\` + +* Default: null +* Type: null or String + +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). + +#### \`ci-name\` + +* Default: The name of the current CI system, or \`null\` when not on a known CI + platform. +* Type: null or String + +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. + +#### \`cidr\` + +* Default: null +* Type: null or String (can be set multiple times) + +This is a list of CIDR address to be used when configuring limited access +tokens with the \`npm token create\` command. + +#### \`color\` + +* Default: true unless the NO_COLOR environ is set to something other than '0' +* Type: "always" or Boolean + +If false, never shows colors. If \`"always"\` then always shows colors. If +true, then only prints color codes for tty file descriptors. + +#### \`commit-hooks\` + +* Default: true +* Type: Boolean + +Run git commit hooks when using the \`npm version\` command. + +#### \`depth\` + +* Default: \`Infinity\` if \`--all\` is set, otherwise \`1\` +* Type: null or Number + +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. + +#### \`description\` + +* Default: true +* Type: Boolean + +Show the description in \`npm search\` + +#### \`diff\` + +* Default: +* Type: String (can be set multiple times) + +Define arguments to compare in \`npm diff\`. + +#### \`diff-dst-prefix\` + +* Default: "b/" +* Type: String + +Destination prefix to be used in \`npm diff\` output. + +#### \`diff-ignore-all-space\` + +* Default: false +* Type: Boolean + +Ignore whitespace when comparing lines in \`npm diff\`. + +#### \`diff-name-only\` + +* Default: false +* Type: Boolean + +Prints only filenames when using \`npm diff\`. + +#### \`diff-no-prefix\` + +* Default: false +* Type: Boolean + +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. + +#### \`diff-src-prefix\` + +* Default: "a/" +* Type: String + +Source prefix to be used in \`npm diff\` output. + +#### \`diff-text\` + +* Default: false +* Type: Boolean + +Treat all files as text in \`npm diff\`. + +#### \`diff-unified\` + +* Default: 3 +* Type: Number + +The number of lines of context to print in \`npm diff\`. + +#### \`dry-run\` + +* Default: false +* Type: Boolean + +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. + +#### \`editor\` + +* Default: The EDITOR or VISUAL environment variables, or 'notepad.exe' on + Windows, or 'vim' on Unix systems +* Type: String + +The command to run for \`npm edit\` and \`npm config edit\`. + +#### \`engine-strict\` + +* Default: false +* Type: Boolean + +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. + +#### \`fetch-retries\` + +* Default: 2 +* Type: Number + +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. + +#### \`fetch-retry-factor\` + +* Default: 10 +* Type: Number + +The "factor" config for the \`retry\` module to use when fetching packages. + +#### \`fetch-retry-maxtimeout\` + +* Default: 60000 (1 minute) +* Type: Number + +The "maxTimeout" config for the \`retry\` module to use when fetching +packages. + +#### \`fetch-retry-mintimeout\` + +* Default: 10000 (10 seconds) +* Type: Number + +The "minTimeout" config for the \`retry\` module to use when fetching +packages. + +#### \`fetch-timeout\` + +* Default: 300000 (5 minutes) +* Type: Number + +The maximum amount of time to wait for HTTP requests to complete. + +#### \`force\` + +* Default: false +* Type: Boolean + +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. + +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! + +#### \`foreground-scripts\` + +* Default: false +* Type: Boolean + +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. + +#### \`format-package-lock\` + +* Default: true +* Type: Boolean + +Format \`package-lock.json\` or \`npm-shrinkwrap.json\` as a human readable +file. + +#### \`fund\` + +* Default: true +* Type: Boolean + +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. + +#### \`git\` + +* Default: "git" +* Type: String + +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. + +#### \`git-tag-version\` + +* Default: true +* Type: Boolean + +Tag the commit when using the \`npm version\` command. + +#### \`global\` + +* Default: false +* Type: Boolean + +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\` + +#### \`global-style\` + +* Default: false +* Type: Boolean + +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. + +#### \`globalconfig\` + +* Default: The global --prefix setting plus 'etc/npmrc'. For example, + '/usr/local/etc/npmrc' +* Type: Path + +The config file to read for global config options. + +#### \`heading\` + +* Default: "npm" +* Type: String + +The string that starts all the debugging log output. + +#### \`https-proxy\` + +* Default: null +* Type: null or URL + +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. + +#### \`if-present\` + +* Default: false +* Type: Boolean + +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. + +#### \`ignore-scripts\` + +* Default: false +* Type: Boolean + +If true, npm does not run scripts specified in package.json files. + +#### \`include\` + +* Default: +* Type: "prod", "dev", "optional", or "peer" (can be set multiple times) + +Option that allows for defining which types of dependencies to install. + +This is the inverse of \`--omit=\`. + +Dependency types specified in \`--include\` will not be omitted, regardless of +the order in which omit/include are specified on the command-line. + +#### \`include-staged\` + +* Default: false +* Type: Boolean + +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. + +#### \`init-author-email\` + +* Default: "" +* Type: String + +The value \`npm init\` should use by default for the package author's email. + +#### \`init-author-name\` + +* Default: "" +* Type: String + +The value \`npm init\` should use by default for the package author's name. + +#### \`init-author-url\` + +* Default: "" +* Type: "" or URL + +The value \`npm init\` should use by default for the package author's +homepage. + +#### \`init-license\` + +* Default: "ISC" +* Type: String + +The value \`npm init\` should use by default for the package license. + +#### \`init-module\` + +* Default: "~/.npm-init.js" +* Type: Path + +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). + +#### \`init-version\` + +* Default: "1.0.0" +* Type: SemVer string + +The value that \`npm init\` should use by default for the package version +number, if not already set in package.json. + +#### \`json\` + +* Default: false +* Type: Boolean + +Whether or not to output JSON data, rather than the normal output. + +This feature is currently experimental, and the output data structures for +many commands is either not implemented in JSON yet, or subject to change. +Only the output from \`npm ls --json\` and \`npm search --json\` are currently +valid. + +#### \`key\` + +* Default: null +* Type: null or String + +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). + +#### \`legacy-bundling\` + +* Default: false +* Type: Boolean + +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. + +#### \`legacy-peer-deps\` + +* Default: false +* Type: Boolean + +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. + +#### \`link\` + +* Default: false +* Type: Boolean + +If true, then local installs will link if there is a suitable globally +installed package. + +Note that this means that local installs can cause things to be installed +into the global space at the same time. The link is only done if one of the +two conditions are met: + +* The package is not already installed globally, or +* the globally installed version is identical to the version that is being + installed locally. + +#### \`local-address\` + +* Default: null +* Type: IP Address + +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. + +#### \`loglevel\` + +* Default: "notice" +* Type: "silent", "error", "warn", "notice", "http", "timing", "info", + "verbose", or "silly" + +What level of logs to report. On failure, *all* logs are written to +\`npm-debug.log\` in the current working directory. + +Any logs of a higher level than the setting are shown. The default is +"notice". + +#### \`logs-max\` + +* Default: 10 +* Type: Number + +The maximum number of log files to store. + +#### \`long\` + +* Default: false +* Type: Boolean + +Show extended information in \`npm ls\` and \`npm search\`. + +#### \`maxsockets\` + +* Default: Infinity +* Type: Number + +The maximum number of connections to use per origin (protocol/host/port +combination). + +#### \`message\` + +* Default: "%s" +* Type: String + +Commit message which is used by \`npm version\` when creating version commit. + +Any "%s" in the message will be replaced with the version number. + +#### \`node-options\` + +* Default: null +* Type: null or String + +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. + +#### \`node-version\` + +* Default: Node.js \`process.version\` value +* Type: SemVer string + +The node version to use when checking a package's \`engines\` setting. + +#### \`noproxy\` + +* Default: The value of the NO_PROXY environment variable +* Type: String (can be set multiple times) + +Domain extensions that should bypass any proxies. + +Also accepts a comma-delimited string. + +#### \`npm-version\` + +* Default: Output of \`npm --version\` +* Type: SemVer string + +The npm version to use when checking a package's \`engines\` setting. + +#### \`offline\` + +* Default: false +* Type: Boolean + +Force offline mode: no network requests will be done during install. To +allow the CLI to fill in missing cache data, see \`--prefer-offline\`. + +#### \`omit\` + +* Default: 'dev' if the NODE_ENV environment variable is set to 'production', + otherwise empty. +* Type: "dev", "optional", or "peer" (can be set multiple times) + +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. + +#### \`otp\` + +* Default: null +* Type: null or String + +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. + +#### \`package\` + +* Default: +* Type: String (can be set multiple times) + +The package to install for [\`npm exec\`](/commands/npm-exec) + +#### \`package-lock\` + +* Default: true +* Type: Boolean + +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\`. + +#### \`package-lock-only\` + +* Default: false +* Type: Boolean + +If set to true, it will update only the \`package-lock.json\`, instead of +checking \`node_modules\` and downloading dependencies. + +#### \`parseable\` + +* Default: false +* Type: Boolean + +Output parseable results from commands that write to standard output. For +\`npm search\`, this will be tab-separated table format. + +#### \`prefer-offline\` + +* Default: false +* Type: Boolean + +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\`. + +#### \`prefer-online\` + +* Default: false +* Type: Boolean + +If true, staleness checks for cached data will be forced, making the CLI +look for updates immediately even for fresh package data. + +#### \`prefix\` + +* Default: 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. +* Type: Path + +The location to install global items. If set on the command line, then it +forces non-global commands to run in the specified folder. + +#### \`preid\` + +* Default: "" +* Type: String + +The "prerelease identifier" to use as a prefix for the "prerelease" part of +a semver. Like the \`rc\` in \`1.2.0-rc.8\`. + +#### \`progress\` + +* Default: \`true\` unless running in a known CI system +* Type: Boolean + +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. + +#### \`proxy\` + +* Default: null +* Type: null, false, or URL + +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. + +#### \`read-only\` + +* Default: false +* Type: Boolean + +This is used to mark a token as unable to publish when configuring limited +access tokens with the \`npm token create\` command. + +#### \`rebuild-bundle\` + +* Default: true +* Type: Boolean + +Rebuild bundled dependencies after installation. + +#### \`registry\` + +* Default: "https://registry.npmjs.org/" +* Type: URL + +The base URL of the npm registry. + +#### \`save\` + +* Default: true +* Type: Boolean + +Save installed packages to a package.json file as dependencies. + +When used with the \`npm rm\` command, removes the dependency from +package.json. + +#### \`save-bundle\` + +* Default: false +* Type: Boolean + +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. + +#### \`save-dev\` + +* Default: false +* Type: Boolean + +Save installed packages to a package.json file as \`devDependencies\`. + +#### \`save-exact\` + +* Default: false +* Type: Boolean + +Dependencies saved to package.json will be configured with an exact version +rather than using npm's default semver range operator. + +#### \`save-optional\` + +* Default: false +* Type: Boolean + +Save installed packages to a package.json file as \`optionalDependencies\`. + +#### \`save-peer\` + +* Default: false +* Type: Boolean + +Save installed packages. to a package.json file as \`peerDependencies\` + +#### \`save-prefix\` + +* Default: "^" +* Type: String + +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. + +#### \`save-prod\` + +* Default: false +* Type: Boolean + +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. + +#### \`scope\` + +* Default: the scope of the current project, if any, or "" +* Type: String + +Associate an operation with a scope for a scoped registry. + +Useful when logging in to a private registry for the first time: + +\`\`\`bash +npm login --scope=@mycorp --registry=https://registry.mycorp.com +\`\`\` + +This will cause \`@mycorp\` to be mapped to the registry for future +installation of packages specified according to the pattern +\`@mycorp/package\`. + +#### \`script-shell\` + +* Default: '/bin/sh' on POSIX systems, 'cmd.exe' on Windows +* Type: null or String + +The shell to use for scripts run with the \`npm run\` command. + +#### \`searchexclude\` + +* Default: "" +* Type: String + +Space-separated options that limit the results from search. + +#### \`searchlimit\` + +* Default: 20 +* Type: Number + +Number of items to limit search results to. Will not apply at all to legacy +searches. + +#### \`searchopts\` + +* Default: "" +* Type: String + +Space-separated options that are always passed to search. + +#### \`searchstaleness\` + +* Default: 900 +* Type: Number + +The age of the cache, in seconds, before another registry request is made if +using legacy search endpoint. + +#### \`shell\` + +* Default: SHELL environment variable, or "bash" on Posix, or "cmd.exe" on + Windows +* Type: String + +The shell to run for the \`npm explore\` command. + +#### \`sign-git-commit\` + +* Default: false +* Type: Boolean + +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. + +#### \`sign-git-tag\` + +* Default: false +* Type: Boolean + +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. + +#### \`strict-peer-deps\` + +* Default: false +* Type: Boolean + +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. + +#### \`strict-ssl\` + +* Default: true +* Type: Boolean + +Whether or not to do SSL key validation when making requests to the registry +via https. + +See also the \`ca\` config. + +#### \`tag\` + +* Default: "latest" +* Type: String + +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. + +#### \`tag-version-prefix\` + +* Default: "v" +* Type: String + +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. + +#### \`timing\` + +* Default: false +* Type: Boolean + +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\`. + +#### \`umask\` + +* Default: 0 +* Type: Octal numeric string in range 0000..0777 (0..511) + +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. + +#### \`unicode\` + +* Default: 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 + +When set to true, npm uses unicode characters in the tree output. When +false, it uses ascii characters instead of unicode glyphs. + +#### \`update-notifier\` + +* Default: true +* Type: Boolean + +Set to false to suppress the update notification when using an older version +of npm than the latest. + +#### \`usage\` + +* Default: false +* Type: Boolean + +Show short usage output about the command specified. + +#### \`user-agent\` + +* Default: "npm/{npm-version} node/{node-version} {platform} {arch} {ci}" +* Type: String + +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\` +* \`{ci}\` - The value of the \`ci-name\` config, if set, prefixed with \`ci/\`, or + an empty string if \`ci-name\` is empty. + +#### \`userconfig\` + +* Default: "~/.npmrc" +* Type: Path + +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. + +#### \`version\` + +* Default: false +* Type: Boolean + +If true, output the npm version and exit successfully. + +Only relevant when specified explicitly on the command line. + +#### \`versions\` + +* Default: false +* Type: Boolean + +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. + +#### \`viewer\` + +* Default: "man" on Posix, "browser" on Windows +* Type: String + +The program to use to view help content. + +Set to \`"browser"\` to view html help content in the default web browser. + +#### \`yes\` + +* Default: false +* Type: Boolean + +Automatically answer "yes" to any prompts that npm might print on the +command line. + +#### \`also\` + +* Default: null +* Type: null, "dev", or "development" +* DEPRECATED: Please use --include=dev instead. + +When set to \`dev\` or \`development\`, this is an alias for \`--include=dev\`. + +#### \`auth-type\` + +* Default: "legacy" +* Type: "legacy", "sso", "saml", or "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. + +What authentication strategy to use with \`adduser\`/\`login\`. + +#### \`cache-max\` + +* Default: Infinity +* Type: Number +* DEPRECATED: This option has been deprecated in favor of \`--prefer-online\` + +\`--cache-max=0\` is an alias for \`--prefer-online\` + +#### \`cache-min\` + +* Default: 0 +* Type: Number +* DEPRECATED: This option has been deprecated in favor of \`--prefer-offline\`. + +\`--cache-min=9999 (or bigger)\` is an alias for \`--prefer-offline\`. + +#### \`init.author.email\` + +* Default: "" +* Type: String +* DEPRECATED: Use \`--init-author-email\` instead. + +Alias for \`--init-author-email\` + +#### \`init.author.name\` + +* Default: "" +* Type: String +* DEPRECATED: Use \`--init-author-name\` instead. + +Alias for \`--init-author-name\` + +#### \`init.author.url\` + +* Default: "" +* Type: "" or URL +* DEPRECATED: Use \`--init-author-url\` instead. + +Alias for \`--init-author-url\` + +#### \`init.license\` + +* Default: "ISC" +* Type: String +* DEPRECATED: Use \`--init-license\` instead. + +Alias for \`--init-license\` + +#### \`init.module\` + +* Default: "~/.npm-init.js" +* Type: Path +* DEPRECATED: Use \`--init-module\` instead. + +Alias for \`--init-module\` + +#### \`init.version\` + +* Default: "1.0.0" +* Type: SemVer string +* DEPRECATED: Use \`--init-version\` instead. + +Alias for \`--init-version\` + +#### \`only\` + +* Default: null +* Type: null, "prod", or "production" +* DEPRECATED: Use \`--omit=dev\` to omit dev dependencies from the install. + +When set to \`prod\` or \`production\`, this is an alias for \`--omit=dev\`. + +#### \`optional\` + +* Default: null +* Type: null or Boolean +* DEPRECATED: Use \`--omit=optional\` to exclude optional dependencies, or + \`--include=optional\` to include them. + +Default value does install optional deps unless otherwise omitted. + +Alias for --include=optional or --omit=optional + +#### \`production\` + +* Default: false +* Type: Boolean +* DEPRECATED: Use \`--omit=dev\` instead. + +Alias for \`--omit=dev\` + +#### \`shrinkwrap\` + +* Default: true +* Type: Boolean +* DEPRECATED: Use the --package-lock setting instead. + +Alias for --package-lock + +#### \`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. + +When used with SSO-enabled \`auth-type\`s, configures how regularly the +registry should be polled while the user is completing authentication. + +#### \`sso-type\` + +* Default: "oauth" +* Type: null, "oauth", or "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. + +If \`--auth-type=sso\`, the type of SSO type to use. + +#### \`tmp\` + +* Default: The value returned by the Node.js \`os.tmpdir()\` method + +* Type: Path +* DEPRECATED: This setting is no longer used. npm stores temporary files in a + special location in the cache, and they are managed by + [\`cacache\`](http://npm.im/cacache). + +Historically, the location where temporary files were stored. No longer +relevant. +` diff --git a/tap-snapshots/test-lib-utils-config-index.js-TAP.test.js b/tap-snapshots/test-lib-utils-config-index.js-TAP.test.js new file mode 100644 index 0000000000000..1044b3063c4bf --- /dev/null +++ b/tap-snapshots/test-lib-utils-config-index.js-TAP.test.js @@ -0,0 +1,127 @@ +/* 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/lib/utils/config/index.js TAP > shorthands 1`] = ` +Object { + "?": Array [ + "--usage", + ], + "a": Array [ + "--all", + ], + "B": Array [ + "--save-bundle", + ], + "c": Array [ + "--call", + ], + "C": Array [ + "--prefix", + ], + "d": Array [ + "--loglevel", + "info", + ], + "D": Array [ + "--save-dev", + ], + "dd": Array [ + "--loglevel", + "verbose", + ], + "ddd": Array [ + "--loglevel", + "silly", + ], + "desc": Array [ + "--description", + ], + "E": Array [ + "--save-exact", + ], + "enjoy-by": Array [ + "--before", + ], + "f": Array [ + "--force", + ], + "g": Array [ + "--global", + ], + "h": Array [ + "--usage", + ], + "H": Array [ + "--usage", + ], + "help": Array [ + "--usage", + ], + "l": Array [ + "--long", + ], + "local": Array [ + "--no-global", + ], + "m": Array [ + "--message", + ], + "n": Array [ + "--no-yes", + ], + "no": Array [ + "--no-yes", + ], + "O": Array [ + "--save-optional", + ], + "p": Array [ + "--parseable", + ], + "P": Array [ + "--save-prod", + ], + "porcelain": Array [ + "--parseable", + ], + "q": Array [ + "--loglevel", + "warn", + ], + "quiet": Array [ + "--loglevel", + "warn", + ], + "readonly": Array [ + "--read-only", + ], + "reg": Array [ + "--registry", + ], + "s": Array [ + "--loglevel", + "silent", + ], + "S": Array [ + "--save", + ], + "silent": Array [ + "--loglevel", + "silent", + ], + "v": Array [ + "--version", + ], + "verbose": Array [ + "--loglevel", + "verbose", + ], + "y": Array [ + "--yes", + ], +} +` diff --git a/test/lib/utils/config/definition.js b/test/lib/utils/config/definition.js new file mode 100644 index 0000000000000..25530f7237138 --- /dev/null +++ b/test/lib/utils/config/definition.js @@ -0,0 +1,150 @@ +const t = require('tap') +const Definition = require('../../../../lib/utils/config/definition.js') +const { + typeDefs: { + semver: { type: semver }, + Umask: { type: Umask }, + url: { type: url }, + path: { type: path }, + }, +} = require('@npmcli/config') + +t.test('basic definition', async t => { + const def = new Definition('key', { + default: 'some default value', + type: [Number, String], + description: 'just a test thingie', + }) + t.same(def, { + constructor: Definition, + key: 'key', + default: 'some default value', + defaultDescription: '"some default value"', + type: [Number, String], + typeDescription: 'Number or String', + description: 'just a test thingie', + }) + t.matchSnapshot(def.describe(), 'human-readable description') + + const deprecated = new Definition('deprecated', { + deprecated: 'do not use this', + default: 1234, + description: ' it should not be used\n ever\n\n not even once.\n\n', + type: Number, + defaultDescription: 'A number bigger than 1', + typeDescription: 'An expression of a numeric quantity using numerals', + }) + t.matchSnapshot(deprecated.describe(), 'description of deprecated thing') + + const nullOrUmask = new Definition('key', { + default: null, + type: [null, Umask], + description: 'asdf', + }) + t.equal(nullOrUmask.typeDescription, 'null or Octal numeric string in range 0000..0777 (0..511)') + const nullDateOrBool = new Definition('key', { + default: 7, + type: [null, Date, Boolean], + description: 'asdf', + }) + t.equal(nullDateOrBool.typeDescription, 'null, Date, or Boolean') + const manyPaths = new Definition('key', { + default: ['asdf'], + type: [path, Array], + description: 'asdf', + }) + t.equal(manyPaths.typeDescription, 'Path (can be set multiple times)') + const pathOrUrl = new Definition('key', { + default: ['https://example.com'], + type: [path, url], + description: 'asdf', + }) + t.equal(pathOrUrl.typeDescription, 'Path or URL') + const multi12 = new Definition('key', { + default: [], + type: [1, 2, Array], + description: 'asdf', + }) + t.equal(multi12.typeDescription, '1 or 2 (can be set multiple times)') + const multi123 = new Definition('key', { + default: [], + type: [1, 2, 3, Array], + description: 'asdf', + }) + t.equal(multi123.typeDescription, '1, 2, or 3 (can be set multiple times)') + const multi123Semver = new Definition('key', { + default: [], + type: [1, 2, 3, Array, semver], + description: 'asdf', + }) + t.equal(multi123Semver.typeDescription, '1, 2, 3, or SemVer string (can be set multiple times)') +}) + +t.test('missing fields', async t => { + t.throws(() => new Definition('lacks-default', { + description: 'no default', + type: String, + }), { message: 'config lacks default: lacks-default' }) + t.throws(() => new Definition('lacks-type', { + description: 'no type', + default: 1234, + }), { message: 'config lacks type: lacks-type' }) + t.throws(() => new Definition(null, { + description: 'falsey key', + default: 1234, + type: Number, + }), { message: 'config lacks key: null' }) + t.throws(() => new Definition('extra-field', { + type: String, + default: 'extra', + extra: 'more than is wanted', + description: 'this is not ok', + }), { message: 'config defines unknown field extra: extra-field' }) +}) + +t.test('long description', async t => { + const { stdout: { columns } } = process + t.teardown(() => process.stdout.columns = columns) + + const long = new Definition('walden', { + description: ` + WHEN I WROTE the following pages, or rather the bulk of them, I lived + alone, in the woods, a mile from any neighbor, in a house which I had + built myself, on the shore of Walden Pond, in Concord, Massachusetts, and + earned my living by the labor of my hands only. I lived there two years + and two months. At present I am a sojourner in civilized life again. + + I should not obtrude my affairs so much on the notice of my readers if + very particular inquiries had not been made by my townsmen concerning my + mode of life, which some would call impertinent, though they do not + appear to me at all impertinent, but, considering the circumstances, very + natural and pertinent. + + \`\`\` + this.is('a', { + code: 'sample', + }) + + with (multiple) { + blocks() + } + \`\`\` + `, + default: true, + type: Boolean, + }) + process.stdout.columns = 40 + t.matchSnapshot(long.describe(), 'cols=40') + + process.stdout.columns = 9000 + t.matchSnapshot(long.describe(), 'cols=9000') + + process.stdout.columns = 0 + t.matchSnapshot(long.describe(), 'cols=0') + + process.stdout.columns = -1 + t.matchSnapshot(long.describe(), 'cols=-1') + + process.stdout.columns = NaN + t.matchSnapshot(long.describe(), 'cols=NaN') +}) diff --git a/test/lib/utils/config/definitions.js b/test/lib/utils/config/definitions.js new file mode 100644 index 0000000000000..3169feefb8f91 --- /dev/null +++ b/test/lib/utils/config/definitions.js @@ -0,0 +1,697 @@ +const t = require('tap') + +const requireInject = require('require-inject') +const { resolve } = require('path') + +// have to fake the node version, or else it'll only pass on this one +Object.defineProperty(process, 'version', { + value: 'v14.8.0', +}) + +// also fake the npm version, so that it doesn't get reset every time +const pkg = require('../../../../package.json') + +// this is a pain to keep typing +const defpath = '../../../../lib/utils/config/definitions.js' + +// set this in the test when we need it +delete process.env.NODE_ENV +const definitions = require(defpath) + +const isWin = '../../../../lib/utils/is-windows.js' + +// snapshot these just so we note when they change +t.matchSnapshot(Object.keys(definitions), 'all config keys') +t.matchSnapshot(Object.keys(definitions).filter(d => d.flatten), + 'all config keys that are shared to flatOptions') + +t.equal(definitions['npm-version'].default, pkg.version, 'npm-version default') +t.equal(definitions['node-version'].default, process.version, 'node-version default') + +t.test('basic flattening function camelCases from css-case', t => { + const flat = {} + const obj = { 'always-auth': true } + definitions['always-auth'].flatten('always-auth', obj, flat) + t.strictSame(flat, { alwaysAuth: true }) + t.end() +}) + +t.test('editor', t => { + t.test('has EDITOR and VISUAL, use EDITOR', t => { + process.env.EDITOR = 'vim' + process.env.VISUAL = 'mate' + const defs = requireInject(defpath) + t.equal(defs.editor.default, 'vim') + t.end() + }) + t.test('has VISUAL but no EDITOR, use VISUAL', t => { + delete process.env.EDITOR + process.env.VISUAL = 'mate' + const defs = requireInject(defpath) + t.equal(defs.editor.default, 'mate') + t.end() + }) + t.test('has neither EDITOR nor VISUAL, system specific', t => { + delete process.env.EDITOR + delete process.env.VISUAL + const defsWin = requireInject(defpath, { + [isWin]: true, + }) + t.equal(defsWin.editor.default, 'notepad.exe') + const defsNix = requireInject(defpath, { + [isWin]: false, + }) + t.equal(defsNix.editor.default, 'vi') + t.end() + }) + t.end() +}) + +t.test('shell', t => { + t.test('windows, env.ComSpec then cmd.exe', t => { + process.env.ComSpec = 'command.com' + const defsComSpec = requireInject(defpath, { + [isWin]: true, + }) + t.equal(defsComSpec.shell.default, 'command.com') + delete process.env.ComSpec + const defsNoComSpec = requireInject(defpath, { + [isWin]: true, + }) + t.equal(defsNoComSpec.shell.default, 'cmd') + t.end() + }) + + t.test('nix, SHELL then sh', t => { + process.env.SHELL = '/usr/local/bin/bash' + const defsShell = requireInject(defpath, { + [isWin]: false, + }) + t.equal(defsShell.shell.default, '/usr/local/bin/bash') + delete process.env.SHELL + const defsNoShell = requireInject(defpath, { + [isWin]: false, + }) + t.equal(defsNoShell.shell.default, 'sh') + t.end() + }) + + t.end() +}) + +t.test('local-address allowed types', t => { + t.test('get list from os.networkInterfaces', t => { + const os = { + tmpdir: () => '/tmp', + networkInterfaces: () => ({ + eth420: [{ address: '127.0.0.1' }], + eth69: [{ address: 'no place like home' }], + }), + } + const defs = requireInject(defpath, { os }) + t.same(defs['local-address'].type, [ + null, + '127.0.0.1', + 'no place like home', + ]) + t.end() + }) + t.test('handle os.networkInterfaces throwing', t => { + const os = { + tmpdir: () => '/tmp', + networkInterfaces: () => { + throw new Error('no network interfaces for some reason') + }, + } + const defs = requireInject(defpath, { os }) + t.same(defs['local-address'].type, [null]) + t.end() + }) + t.end() +}) + +t.test('unicode allowed?', t => { + const { LC_ALL, LC_CTYPE, LANG } = process.env + t.teardown(() => Object.assign(process.env, { LC_ALL, LC_CTYPE, LANG })) + + process.env.LC_ALL = 'utf8' + process.env.LC_CTYPE = 'UTF-8' + process.env.LANG = 'Unicode utf-8' + + const lcAll = requireInject(defpath) + t.equal(lcAll.unicode.default, true) + process.env.LC_ALL = 'no unicode for youUUUU!' + const noLcAll = requireInject(defpath) + t.equal(noLcAll.unicode.default, false) + + delete process.env.LC_ALL + const lcCtype = requireInject(defpath) + t.equal(lcCtype.unicode.default, true) + process.env.LC_CTYPE = 'something other than unicode version 8' + const noLcCtype = requireInject(defpath) + t.equal(noLcCtype.unicode.default, false) + + delete process.env.LC_CTYPE + const lang = requireInject(defpath) + t.equal(lang.unicode.default, true) + process.env.LANG = 'ISO-8859-1' + const noLang = requireInject(defpath) + t.equal(noLang.unicode.default, false) + t.end() +}) + +t.test('cache', t => { + process.env.LOCALAPPDATA = 'app/data/local' + const defsWinLocalAppData = requireInject(defpath, { + [isWin]: true, + }) + t.equal(defsWinLocalAppData.cache.default, 'app/data/local/npm-cache') + + delete process.env.LOCALAPPDATA + const defsWinNoLocalAppData = requireInject(defpath, { + [isWin]: true, + }) + t.equal(defsWinNoLocalAppData.cache.default, '~/npm-cache') + + const defsNix = requireInject(defpath, { + [isWin]: false, + }) + t.equal(defsNix.cache.default, '~/.npm') + + const flat = {} + defsNix.cache.flatten('cache', { cache: '/some/cache/value' }, flat) + const {join} = require('path') + t.equal(flat.cache, join('/some/cache/value', '_cacache')) + + t.end() +}) + +t.test('flatteners that populate flat.omit array', t => { + t.test('also', t => { + const flat = {} + const obj = {} + + // ignored if setting is not dev or development + obj.also = 'ignored' + definitions.also.flatten('also', obj, flat) + t.strictSame(obj, {also: 'ignored'}, 'nothing done') + t.strictSame(flat, {}, 'nothing done') + + obj.also = 'development' + definitions.also.flatten('also', obj, flat) + t.strictSame(obj, { also: 'development', include: ['dev'] }, 'marked dev as included') + t.strictSame(flat, { omit: [] }, 'nothing omitted, so nothing changed') + + obj.omit = ['dev', 'optional'] + obj.include = [] + definitions.also.flatten('also', obj, flat) + t.strictSame(obj, { also: 'development', omit: ['dev', 'optional'], include: ['dev'] }, 'marked dev as included') + t.strictSame(flat, { omit: ['optional'] }, 'removed dev from omit') + t.end() + }) + + t.test('include', t => { + const flat = {} + const obj = { include: ['dev'] } + definitions.include.flatten('include', obj, flat) + t.strictSame(flat, {omit: []}, 'not omitting anything') + obj.omit = ['optional', 'dev'] + definitions.include.flatten('include', obj, flat) + t.strictSame(flat, {omit: ['optional']}, 'only omitting optional') + t.end() + }) + + t.test('omit', t => { + const flat = {} + const obj = { include: ['dev'], omit: ['dev', 'optional'] } + definitions.omit.flatten('omit', obj, flat) + t.strictSame(flat, { omit: ['optional'] }, 'do not omit what is included') + + process.env.NODE_ENV = 'production' + const defProdEnv = requireInject(defpath) + t.strictSame(defProdEnv.omit.default, ['dev'], 'omit dev in production') + t.end() + }) + + t.test('only', t => { + const flat = {} + const obj = { only: 'asdf' } + definitions.only.flatten('only', obj, flat) + t.strictSame(flat, {}, 'ignored if value is not production') + + obj.only = 'prod' + definitions.only.flatten('only', obj, flat) + t.strictSame(flat, {omit: ['dev']}, 'omit dev when --only=prod') + + obj.include = ['dev'] + flat.omit = [] + definitions.only.flatten('only', obj, flat) + t.strictSame(flat, {omit: []}, 'do not omit when included') + + t.end() + }) + + t.test('optional', t => { + const flat = {} + const obj = { optional: null } + + definitions.optional.flatten('optional', obj, flat) + t.strictSame(obj, { optional: null }, 'do nothing by default') + t.strictSame(flat, {}, 'do nothing by default') + + obj.optional = true + definitions.optional.flatten('optional', obj, flat) + t.strictSame(obj, {include: ['optional'], optional: true}, 'include optional when set') + t.strictSame(flat, {omit: []}, 'nothing to omit in flatOptions') + + delete obj.include + obj.optional = false + definitions.optional.flatten('optional', obj, flat) + t.strictSame(obj, {omit: ['optional'], optional: false}, 'omit optional when set false') + t.strictSame(flat, {omit: ['optional']}, 'omit optional when set false') + + t.end() + }) + + t.test('production', t => { + const flat = {} + const obj = {production: true} + definitions.production.flatten('production', obj, flat) + t.strictSame(obj, {production: true, omit: ['dev']}, '--production sets --omit=dev') + t.strictSame(flat, {omit: ['dev']}, '--production sets --omit=dev') + + delete obj.omit + obj.production = false + delete flat.omit + definitions.production.flatten('production', obj, flat) + t.strictSame(obj, {production: false}, '--no-production has no effect') + t.strictSame(flat, {}, '--no-production has no effect') + + obj.production = true + obj.include = ['dev'] + definitions.production.flatten('production', obj, flat) + t.strictSame(obj, {production: true, include: ['dev'], omit: ['dev']}, 'omit and include dev') + t.strictSame(flat, {omit: []}, 'do not omit dev when included') + + t.end() + }) + + t.end() +}) + +t.test('cache-max', t => { + const flat = {} + const obj = { 'cache-max': 10342 } + definitions['cache-max'].flatten('cache-max', obj, flat) + t.strictSame(flat, {}, 'no effect if not <= 0') + obj['cache-max'] = 0 + definitions['cache-max'].flatten('cache-max', obj, flat) + t.strictSame(flat, {preferOnline: true}, 'preferOnline if <= 0') + t.end() +}) + +t.test('cache-min', t => { + const flat = {} + const obj = { 'cache-min': 123 } + definitions['cache-min'].flatten('cache-min', obj, flat) + t.strictSame(flat, {}, 'no effect if not >= 9999') + obj['cache-min'] = 9999 + definitions['cache-min'].flatten('cache-min', obj, flat) + t.strictSame(flat, {preferOffline: true}, 'preferOffline if >=9999') + t.end() +}) + +t.test('color', t => { + const { isTTY } = process.stdout + t.teardown(() => process.stdout.isTTY = isTTY) + + const flat = {} + const obj = { color: 'always' } + + definitions.color.flatten('color', obj, flat) + t.strictSame(flat, {color: true}, 'true when --color=always') + + obj.color = false + definitions.color.flatten('color', obj, flat) + t.strictSame(flat, {color: false}, 'true when --no-color') + + process.stdout.isTTY = false + obj.color = true + definitions.color.flatten('color', obj, flat) + t.strictSame(flat, {color: false}, 'no color when stdout not tty') + process.stdout.isTTY = true + definitions.color.flatten('color', obj, flat) + t.strictSame(flat, {color: true}, '--color turns on color when stdout is tty') + + delete process.env.NO_COLOR + const defsAllowColor = requireInject(defpath) + t.equal(defsAllowColor.color.default, true, 'default true when no NO_COLOR env') + + process.env.NO_COLOR = '0' + const defsNoColor0 = requireInject(defpath) + t.equal(defsNoColor0.color.default, true, 'default true when no NO_COLOR=0') + + process.env.NO_COLOR = '1' + const defsNoColor1 = requireInject(defpath) + t.equal(defsNoColor1.color.default, false, 'default false when no NO_COLOR=1') + + t.end() +}) + +t.test('retry options', t => { + const obj = {} + // : flat.retry[