diff options
Diffstat (limited to 'deps')
191 files changed, 10075 insertions, 7710 deletions
diff --git a/deps/npm/AUTHORS b/deps/npm/AUTHORS index e58d1dffae..10189f9c4b 100644 --- a/deps/npm/AUTHORS +++ b/deps/npm/AUTHORS @@ -755,3 +755,5 @@ Ikko Ashimine <eltociear@gmail.com> MrBrain295 <66077254+MrBrain295@users.noreply.github.com> kumavis <aaron@kumavis.me> Christof Lemke <christoflemke@github.com> +Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> +Bjørn Johansen <bjjohans@microsoft.com> diff --git a/deps/npm/CHANGELOG.md b/deps/npm/CHANGELOG.md index 22e6018f0a..fc9f3ff1e4 100644 --- a/deps/npm/CHANGELOG.md +++ b/deps/npm/CHANGELOG.md @@ -1,3 +1,52 @@ +## v7.6.1 (2021-03-04) + +### BUG FIXES + +* [`3c9a589b0`](https://github.com/npm/cli/commit/3c9a589b004fa828a304abaf52d1d781710e1143) + [#2807](https://github.com/npm/cli/issues/2807) + `npm explain` show when an edge is a bundled edge + ([@kumavis](https://github.com/kumavis)) +* [`b33c760ce`](https://github.com/npm/cli/commit/b33c760cea7fe2696d35b5530abc1b455980fef1) + [#2766](https://github.com/npm/cli/issues/2766) + unused arguments cleanup + ([@sandersn](https://github.com/sandersn)) +* [`4a5dd3a5a`](https://github.com/npm/cli/commit/4a5dd3a5a200b3f4f7b47168497d8e03dca3a2ca) + [#2772](https://github.com/npm/cli/issues/2772) + fix(npm) pass npm context everywhere + ([@wraithgar](https://github.com/wraithgar)) +* [`e69be2ac5`](https://github.com/npm/cli/commit/e69be2ac5c35e985732e2baa00b70d39332e4b9f) + [#2789](https://github.com/npm/cli/issues/2789) + fix npm prefix on all Windows unix shells + ([@isaacs](https://github.com/isaacs)) +* [`2d682e4ca`](https://github.com/npm/cli/commit/2d682e4cab0cf109a16332f3222f1e9a4027db69) + [#2803](https://github.com/npm/cli/issues/2803) + fix(search): don't pass unused args + ([@wraithgar](https://github.com/wraithgar)) +* [`b3e7dd19b`](https://github.com/npm/cli/commit/b3e7dd19bb4888dad2bfb6702aed6560a7f91bf8) + [#2822](https://github.com/npm/cli/issues/2822) + fix(diff): set option "where" for pacote + ([@ruyadorno](https://github.com/ruyadorno)) +* [`96006640b`](https://github.com/npm/cli/commit/96006640b902d31415260df5ce3ad8d066a64623) + [#2824](https://github.com/npm/cli/issues/2824) + fix(repo, auth.sso): don't promisify open-url + ([@wraithgar](https://github.com/wraithgar)) + +### DOCUMENTATION + +* [`c8b73db82`](https://github.com/npm/cli/commit/c8b73db82f0f2445c20a0a64110586253accd66b) + [#2690](https://github.com/npm/cli/issues/2690) + fix(docs): update scripts docs + ([@wraithgar](https://github.com/wraithgar)) +* [`5d922394b`](https://github.com/npm/cli/commit/5d922394b7874b2b38d34f03f2decbe0eb3e8583) + [#2809](https://github.com/npm/cli/issues/2809) + update republish timeout after unpublish + ([@BAJ-](https://github.com/BAJ-)) + +### DEPENDENCIES + +* [`2d4ae598f`](https://github.com/npm/cli/commit/2d4ae598f30049680797685f76154b16a7e15a66) + `@npmcli/arborist@2.2.6` + ## v7.6.0 (2021-02-25) ### FEATURES diff --git a/deps/npm/bin/npm b/deps/npm/bin/npm index 4183703a78..a131a53543 100755 --- a/deps/npm/bin/npm +++ b/deps/npm/bin/npm @@ -1,10 +1,10 @@ -#!/bin/sh +#!/usr/bin/env bash (set -o igncr) 2>/dev/null && set -o igncr; # cygwin encoding fix basedir=`dirname "$0"` case `uname` in - *CYGWIN*) basedir=`cygpath -w "$basedir"`;; + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac NODE_EXE="$basedir/node.exe" @@ -15,23 +15,30 @@ if ! [ -x "$NODE_EXE" ]; then NODE_EXE=node fi -NPM_CLI_JS="$basedir/node_modules/npm/bin/npm-cli.js" +# this path is passed to node.exe, so it needs to match whatever +# kind of paths Node.js thinks it's using, typically win32 paths. +CLI_BASEDIR="$("$NODE_EXE" -p 'require("path").dirname(process.execPath)')" +NPM_CLI_JS="$CLI_BASEDIR/node_modules/npm/bin/npm-cli.js" -case `uname` in - *MINGW*) - NPM_PREFIX=`"$NODE_EXE" "$NPM_CLI_JS" prefix -g` - NPM_PREFIX_NPM_CLI_JS="$NPM_PREFIX/node_modules/npm/bin/npm-cli.js" - if [ -f "$NPM_PREFIX_NPM_CLI_JS" ]; then - NPM_CLI_JS="$NPM_PREFIX_NPM_CLI_JS" - fi - ;; - *CYGWIN*) - NPM_PREFIX=`"$NODE_EXE" "$NPM_CLI_JS" prefix -g` - NPM_PREFIX_NPM_CLI_JS="$NPM_PREFIX/node_modules/npm/bin/npm-cli.js" - if [ -f "$NPM_PREFIX_NPM_CLI_JS" ]; then - NPM_CLI_JS="$NPM_PREFIX_NPM_CLI_JS" - fi - ;; -esac +NPM_PREFIX=`"$NODE_EXE" "$NPM_CLI_JS" prefix -g` +if [ $? -ne 0 ]; then + # if this didn't work, then everything else below will fail + echo "Could not determine Node.js install directory" >&2 + exit 1 +fi +NPM_PREFIX_NPM_CLI_JS="$NPM_PREFIX/node_modules/npm/bin/npm-cli.js" + +# a path that will fail -f test on any posix bash +NPM_WSL_PATH="/.." + +# WSL can run Windows binaries, so we have to give it the win32 path +# however, WSL bash tests against posix paths, so we need to construct that +# to know if npm is installed globally. +if [ `uname` = 'Linux' ] && type wslpath &>/dev/null ; then + NPM_WSL_PATH=`wslpath "$NPM_PREFIX_NPM_CLI_JS"` +fi +if [ -f "$NPM_PREFIX_NPM_CLI_JS" ] || [ -f "$NPM_WSL_PATH" ]; then + NPM_CLI_JS="$NPM_PREFIX_NPM_CLI_JS" +fi "$NODE_EXE" "$NPM_CLI_JS" "$@" diff --git a/deps/npm/bin/npx b/deps/npm/bin/npx index f43754d620..4b58a104b9 100755 --- a/deps/npm/bin/npx +++ b/deps/npm/bin/npx @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash # This is used by the Node.js installer, which expects the cygwin/mingw # shell script to already be present in the npm dependency folder. @@ -8,7 +8,7 @@ basedir=`dirname "$0"` case `uname` in - *CYGWIN*) basedir=`cygpath -w "$basedir"`;; + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; esac NODE_EXE="$basedir/node.exe" @@ -16,24 +16,30 @@ if ! [ -x "$NODE_EXE" ]; then NODE_EXE=node fi -NPM_CLI_JS="$basedir/node_modules/npm/bin/npm-cli.js" -NPX_CLI_JS="$basedir/node_modules/npm/bin/npx-cli.js" - -case `uname` in - *MINGW*) - NPM_PREFIX=`"$NODE_EXE" "$NPM_CLI_JS" prefix -g` - NPM_PREFIX_NPX_CLI_JS="$NPM_PREFIX/node_modules/npm/bin/npx-cli.js" - if [ -f "$NPM_PREFIX_NPX_CLI_JS" ]; then - NPX_CLI_JS="$NPM_PREFIX_NPX_CLI_JS" - fi - ;; - *CYGWIN*) - NPM_PREFIX=`"$NODE_EXE" "$NPM_CLI_JS" prefix -g` - NPM_PREFIX_NPX_CLI_JS="$NPM_PREFIX/node_modules/npm/bin/npx-cli.js" - if [ -f "$NPM_PREFIX_NPX_CLI_JS" ]; then - NPX_CLI_JS="$NPM_PREFIX_NPX_CLI_JS" - fi - ;; -esac +# these paths are passed to node.exe, so they need to match whatever +# kind of paths Node.js thinks it's using, typically win32 paths. +CLI_BASEDIR="$("$NODE_EXE" -p 'require("path").dirname(process.execPath)')" +if [ $? -ne 0 ]; then + # if this didn't work, then everything else below will fail + echo "Could not determine Node.js install directory" >&2 + exit 1 +fi +NPM_CLI_JS="$CLI_BASEDIR/node_modules/npm/bin/npm-cli.js" +NPX_CLI_JS="$CLI_BASEDIR/node_modules/npm/bin/npx-cli.js" +NPM_PREFIX=`"$NODE_EXE" "$NPM_CLI_JS" prefix -g` +NPM_PREFIX_NPX_CLI_JS="$NPM_PREFIX/node_modules/npm/bin/npx-cli.js" + +# a path that will fail -f test on any posix bash +NPX_WSL_PATH="/.." + +# WSL can run Windows binaries, so we have to give it the win32 path +# however, WSL bash tests against posix paths, so we need to construct that +# to know if npm is installed globally. +if [ `uname` = 'Linux' ] && type wslpath &>/dev/null ; then + NPX_WSL_PATH=`wslpath "$NPM_PREFIX_NPX_CLI_JS"` +fi +if [ -f "$NPM_PREFIX_NPX_CLI_JS" ] || [ -f "$NPX_WSL_PATH" ]; then + NPX_CLI_JS="$NPM_PREFIX_NPX_CLI_JS" +fi "$NODE_EXE" "$NPX_CLI_JS" "$@" diff --git a/deps/npm/docs/content/commands/npm-unpublish.md b/deps/npm/docs/content/commands/npm-unpublish.md index 14813e9434..e9d6e9045c 100644 --- a/deps/npm/docs/content/commands/npm-unpublish.md +++ b/deps/npm/docs/content/commands/npm-unpublish.md @@ -42,7 +42,7 @@ versions then the registry will remove the root package entry entirely. Even if you unpublish a package version, that specific name and version combination can never be reused. In order to publish the package again, you must use a new version number. If you unpublish the entire package, -you may not publish any new versions of that package until 28 days have +you may not publish any new versions of that package until 24 hours have passed. ### See Also diff --git a/deps/npm/docs/content/using-npm/scripts.md b/deps/npm/docs/content/using-npm/scripts.md index db7bd56b51..9312a21546 100644 --- a/deps/npm/docs/content/using-npm/scripts.md +++ b/deps/npm/docs/content/using-npm/scripts.md @@ -6,11 +6,19 @@ description: How npm handles the "scripts" field ### Description -The `"scripts"` property of your `package.json` file supports a number of built-in scripts and their preset life cycle events as well as arbitrary scripts. These all can be executed by running `npm run-script <stage>` or `npm run <stage>` for short. *Pre* and *post* commands with matching names will be run for those as well (e.g. `premyscript`, `myscript`, `postmyscript`). Scripts from dependencies can be run with `npm explore <pkg> -- npm run <stage>`. +The `"scripts"` property of your `package.json` file supports a number +of built-in scripts and their preset life cycle events as well as +arbitrary scripts. These all can be executed by running +`npm run-script <stage>` or `npm run <stage>` for short. *Pre* and *post* +commands with matching names will be run for those as well (e.g. `premyscript`, +`myscript`, `postmyscript`). Scripts from dependencies can be run with +`npm explore <pkg> -- npm run <stage>`. ### Pre & Post Scripts -To create "pre" or "post" scripts for any scripts defined in the `"scripts"` section of the `package.json`, simply create another script *with a matching name* and add "pre" or "post" to the beginning of them. +To create "pre" or "post" scripts for any scripts defined in the +`"scripts"` section of the `package.json`, simply create another script +*with a matching name* and add "pre" or "post" to the beginning of them. ```json { @@ -22,20 +30,35 @@ To create "pre" or "post" scripts for any scripts defined in the `"scripts"` sec } ``` +In this example `npm run compress` would execute these scripts as +described. + ### Life Cycle Scripts -There are some special life cycle scripts that happen only in certain situations. These scripts happen in addition to the "pre" and "post" script. +There are some special life cycle scripts that happen only in certain +situations. These scripts happen in addition to the `pre<event>`, `post<event>`, and +`<event>` scripts. + * `prepare`, `prepublish`, `prepublishOnly`, `prepack`, `postpack` **prepare** (since `npm@4.0.0`) +* Runs any time before the package is packed, i.e. during `npm publish` + and `npm pack` * Runs BEFORE the package is packed * Runs BEFORE the package is published * Runs on local `npm install` without any arguments * Run AFTER `prepublish`, but BEFORE `prepublishOnly` -* NOTE: If a package being installed through git contains a `prepare` script, its `dependencies` and `devDependencies` will be installed, and the prepare script will be run, before the package is packaged and installed. + +* NOTE: If a package being installed through git contains a `prepare` + script, its `dependencies` and `devDependencies` will be installed, and + the prepare script will be run, before the package is packaged and + installed. + +* As of `npm@7` these scripts run in the background **prepublish** (DEPRECATED) -* Same as `prepare` +* Does not run during `npm publish`, but does run during `npm ci` + and `npm install`. See below for more info. **prepublishOnly** * Runs BEFORE the package is prepared and packed, ONLY on `npm publish`. @@ -45,7 +68,7 @@ There are some special life cycle scripts that happen only in certain situations * NOTE: "`npm run pack`" is NOT the same as "`npm pack`". "`npm run pack`" is an arbitrary user defined script name, where as, "`npm pack`" is a CLI defined command. **postpack** -* Runs AFTER the tarball has been generated and moved to its final destination. +* Runs AFTER the tarball has been generated but before it is moved to its final destination (if at all, publish does not save the tarball locally) #### Prepare and Prepublish @@ -74,51 +97,116 @@ The advantage of doing these things at `prepublish` time is that they can be don ### Life Cycle Operation Order -#### [`npm publish`](/commands/npm-publish) +#### [`npm cache add`](/commands/npm-cache) -* `prepublishOnly` * `prepare` + +#### [`npm ci`](/commands/npm-ci) + +* `preinstall` +* `install` +* `postinstall` * `prepublish` -* `publish` -* `postpublish` +* `preprepare` +* `prepare` +* `postprepare` + + These all run after the actual installation of modules into + `node_modules`, in order, with no internal actions happening in between + +#### [`npm diff`](/commands/npm-diff) + +* `prepare` + +#### [`npm env`](/commands/npm-env) + +* `env` (You can override the default behavior of `npm env` by defining + a custom `env` entry in your `scripts` object) + +#### [`npm install`](/commands/npm-install) + +These also run when you run `npm install -g <pkg-name>` + +* `preinstall` +* `install` +* `postinstall` +* `prepublish` +* `preprepare` +* `prepare` +* `postprepare` + +If there is a `binding.gyp` file in the root of your package and you +haven't defined your own `install` or `preinstall` scripts, npm will +default the `install` command to compile using node-gyp via `node-gyp +rebuild` + +These are run from the scripts of `<pkg-name>` #### [`npm pack`](/commands/npm-pack) * `prepack` +* `prepare` * `postpack` -#### [`npm install`](/commands/npm-install) +#### [`npm publish`](/commands/npm-publish) + +* `prepublishOnly` +* `prepack` +* `prepare` +* `postpack` +* `publish` +* `postpublish` + +`prepare` will not run during `--dry-run` + +#### [`npm rebuild`](/commands/npm-rebuild) * `preinstall` * `install` * `postinstall` +* `prepare` -Also triggers +`prepare` is only run if the current directory is a symlink (e.g. with +linked packages) -* `prepublish` (when on local) -* `prepare` (when on local or workspaces) +#### [`npm restart`](/commands/npm-restart) -#### [`npm start`](/commands/npm-start) +If there is a `restart` script defined, these events are run, otherwise +`stop` and `start` are both run if present, including their `pre` and +`post` iterations) + +* `prerestart` +* `restart` +* `postrestart` -`npm run start` has an `npm start` shorthand. +#### [`npm run <user defined>`](/commands/npm-run) + +* `pre<user-defined>` +* `<user-defined>` +* `post<user-defined>` + +#### [`npm start`](/commands/npm-start) * `prestart` * `start` * `poststart` -### Default Values -npm will default some script values based on package contents. +If there is a `server.js` file in the root of your package, then npm +will default the `start` command to `node server.js`. `prestart` and +`poststart` will still run in this case. + +#### [`npm stop`](/commands/npm-stop) -* `"start": "node server.js"`: +* `prestop` +* `stop` +* `poststop` - If there is a `server.js` file in the root of your package, then npm - will default the `start` command to `node server.js`. +#### [`npm test`](/commands/npm-test) -* `"install": "node-gyp rebuild"`: +* `pretest` +* `test` +* `posttest` - If there is a `binding.gyp` file in the root of your package and you - haven't defined your own `install` or `preinstall` scripts, npm will - default the `install` command to compile using node-gyp. ### User @@ -131,7 +219,6 @@ Package scripts run in an environment where many pieces of information are made available regarding the setup of npm and the current state of the process. - #### path If you depend on modules that define executable scripts, like test @@ -139,14 +226,14 @@ suites, then those executables will be added to the `PATH` for executing the scripts. So, if your package.json has this: ```json -{ - "name" : "foo", - "dependencies" : { - "bar" : "0.1.x" - }, - "scripts": { - "start" : "bar ./test" - } +{ + "name" : "foo", + "dependencies" : { + "bar" : "0.1.x" + }, + "scripts": { + "start" : "bar ./test" + } } ``` @@ -159,8 +246,8 @@ The package.json fields are tacked onto the `npm_package_` prefix. So, for instance, if you had `{"name":"foo", "version":"1.2.5"}` in your package.json file, then your package scripts would have the `npm_package_name` environment variable set to "foo", and the -`npm_package_version` set to "1.2.5". You can access these variables -in your code with `process.env.npm_package_name` and +`npm_package_version` set to "1.2.5". You can access these variables +in your code with `process.env.npm_package_name` and `process.env.npm_package_version`, and so on for other fields. #### configuration @@ -176,14 +263,14 @@ there is a config param of `<name>[@<version>]:<key>`. For example, if the package.json has this: ```json -{ - "name" : "foo", - "config" : { - "port" : "8080" - }, - "scripts" : { - "start" : "node server.js" - } +{ + "name" : "foo", + "config" : { + "port" : "8080" + }, + "scripts" : { + "start" : "node server.js" + } } ``` @@ -219,10 +306,10 @@ process.env.npm_package_scripts_install === "foo.js" For example, if your package.json contains this: ```json -{ - "scripts" : { - "install" : "scripts/install.js", - "postinstall" : "scripts/postinstall.js", +{ + "scripts" : { + "install" : "scripts/install.js", + "postinstall" : "scripts/postinstall.js", "uninstall" : "scripts/uninstall.js" } } @@ -239,10 +326,10 @@ If you want to run a make command, you can do so. This works just fine: ```json -{ - "scripts" : { - "preinstall" : "./configure", - "install" : "make && make install", +{ + "scripts" : { + "preinstall" : "./configure", + "install" : "make && make install", "test" : "make test" } } diff --git a/deps/npm/docs/output/commands/npm-ls.html b/deps/npm/docs/output/commands/npm-ls.html index aaa6f3ea28..4d0119abc6 100644 --- a/deps/npm/docs/output/commands/npm-ls.html +++ b/deps/npm/docs/output/commands/npm-ls.html @@ -159,7 +159,7 @@ tree at all, use <a href="../commands/npm-explain.html"><code>npm explain</code> the results to only the paths to the packages named. Note that nested packages will <em>also</em> show the paths to the specified packages. For example, running <code>npm ls promzard</code> in npm’s source tree will show:</p> -<pre lang="bash"><code>npm@7.6.0 /path/to/npm +<pre lang="bash"><code>npm@7.6.1 /path/to/npm └─┬ init-package-json@0.0.4 └── promzard@0.1.5 </code></pre> diff --git a/deps/npm/docs/output/commands/npm-unpublish.html b/deps/npm/docs/output/commands/npm-unpublish.html index 3ec254a109..75751d3408 100644 --- a/deps/npm/docs/output/commands/npm-unpublish.html +++ b/deps/npm/docs/output/commands/npm-unpublish.html @@ -166,7 +166,7 @@ versions then the registry will remove the root package entry entirely.</p> <p>Even if you unpublish a package version, that specific name and version combination can never be reused. In order to publish the package again, you must use a new version number. If you unpublish the entire package, -you may not publish any new versions of that package until 28 days have +you may not publish any new versions of that package until 24 hours have passed.</p> <h3 id="see-also">See Also</h3> <ul> diff --git a/deps/npm/docs/output/commands/npm.html b/deps/npm/docs/output/commands/npm.html index 610396038c..21f5e8dd81 100644 --- a/deps/npm/docs/output/commands/npm.html +++ b/deps/npm/docs/output/commands/npm.html @@ -148,7 +148,7 @@ npm command-line interface <pre lang="bash"><code>npm <command> [args] </code></pre> <h3 id="version">Version</h3> -<p>7.6.0</p> +<p>7.6.1</p> <h3 id="description">Description</h3> <p>npm is the package manager for the Node JavaScript platform. It puts modules in place so that node can find them, and manages dependency diff --git a/deps/npm/docs/output/using-npm/scripts.html b/deps/npm/docs/output/using-npm/scripts.html index bb02d9b1f8..e231cfec2a 100644 --- a/deps/npm/docs/output/using-npm/scripts.html +++ b/deps/npm/docs/output/using-npm/scripts.html @@ -141,13 +141,21 @@ npm command-line interface <section id="table_of_contents"> <h2 id="table-of-contents">Table of contents</h2> -<div id="_table_of_contents"><ul><li><a href="#description">Description</a></li><li><a href="#pre--post-scripts">Pre & Post Scripts</a></li><li><a href="#life-cycle-scripts">Life Cycle Scripts</a></li><ul><li><a href="#prepare-and-prepublish">Prepare and Prepublish</a></li></ul><li><a href="#life-cycle-operation-order">Life Cycle Operation Order</a></li><ul><li><a href="#npm-publish"><a href="../commands/npm-publish.html"><code>npm publish</code></a></a></li><li><a href="#npm-pack"><a href="../commands/npm-pack.html"><code>npm pack</code></a></a></li><li><a href="#npm-install"><a href="../commands/npm-install.html"><code>npm install</code></a></a></li><li><a href="#npm-start"><a href="../commands/npm-start.html"><code>npm start</code></a></a></li></ul><li><a href="#default-values">Default Values</a></li><li><a href="#user">User</a></li><li><a href="#environment">Environment</a></li><ul><li><a href="#path">path</a></li><li><a href="#packagejson-vars">package.json vars</a></li><li><a href="#configuration">configuration</a></li><li><a href="#special-packagejson-config-object">Special: package.json “config” object</a></li><li><a href="#current-lifecycle-event">current lifecycle event</a></li></ul><li><a href="#examples">Examples</a></li><li><a href="#exiting">Exiting</a></li><li><a href="#hook-scripts">Hook Scripts</a></li><li><a href="#best-practices">Best Practices</a></li><li><a href="#see-also">See Also</a></li></ul></div> +<div id="_table_of_contents"><ul><li><a href="#description">Description</a></li><li><a href="#pre--post-scripts">Pre & Post Scripts</a></li><li><a href="#life-cycle-scripts">Life Cycle Scripts</a></li><ul><li><a href="#prepare-and-prepublish">Prepare and Prepublish</a></li></ul><li><a href="#life-cycle-operation-order">Life Cycle Operation Order</a></li><ul><li><a href="#npm-cache-add"><a href="../commands/npm-cache.html"><code>npm cache add</code></a></a></li><li><a href="#npm-ci"><a href="../commands/npm-ci.html"><code>npm ci</code></a></a></li><li><a href="#npm-diff"><a href="../commands/npm-diff.html"><code>npm diff</code></a></a></li><li><a href="#npm-env"><a href="../commands/npm-env.html"><code>npm env</code></a></a></li><li><a href="#npm-install"><a href="../commands/npm-install.html"><code>npm install</code></a></a></li><li><a href="#npm-pack"><a href="../commands/npm-pack.html"><code>npm pack</code></a></a></li><li><a href="#npm-publish"><a href="../commands/npm-publish.html"><code>npm publish</code></a></a></li><li><a href="#npm-rebuild"><a href="../commands/npm-rebuild.html"><code>npm rebuild</code></a></a></li><li><a href="#npm-restart"><a href="../commands/npm-restart.html"><code>npm restart</code></a></a></li><li><a href="#npm-run-user-defined"><a href="../commands/npm-run.html"><code>npm run <user defined></code></a></a></li><li><a href="#npm-start"><a href="../commands/npm-start.html"><code>npm start</code></a></a></li><li><a href="#npm-stop"><a href="../commands/npm-stop.html"><code>npm stop</code></a></a></li><li><a href="#npm-test"><a href="../commands/npm-test.html"><code>npm test</code></a></a></li></ul><li><a href="#user">User</a></li><li><a href="#environment">Environment</a></li><ul><li><a href="#path">path</a></li><li><a href="#packagejson-vars">package.json vars</a></li><li><a href="#configuration">configuration</a></li><li><a href="#special-packagejson-config-object">Special: package.json “config” object</a></li><li><a href="#current-lifecycle-event">current lifecycle event</a></li></ul><li><a href="#examples">Examples</a></li><li><a href="#exiting">Exiting</a></li><li><a href="#hook-scripts">Hook Scripts</a></li><li><a href="#best-practices">Best Practices</a></li><li><a href="#see-also">See Also</a></li></ul></div> </section> <div id="_content"><h3 id="description">Description</h3> -<p>The <code>"scripts"</code> property of your <code>package.json</code> file supports a number of built-in scripts and their preset life cycle events as well as arbitrary scripts. These all can be executed by running <code>npm run-script <stage></code> or <code>npm run <stage></code> for short. <em>Pre</em> and <em>post</em> commands with matching names will be run for those as well (e.g. <code>premyscript</code>, <code>myscript</code>, <code>postmyscript</code>). Scripts from dependencies can be run with <code>npm explore <pkg> -- npm run <stage></code>.</p> +<p>The <code>"scripts"</code> property of your <code>package.json</code> file supports a number +of built-in scripts and their preset life cycle events as well as +arbitrary scripts. These all can be executed by running +<code>npm run-script <stage></code> or <code>npm run <stage></code> for short. <em>Pre</em> and <em>post</em> +commands with matching names will be run for those as well (e.g. <code>premyscript</code>, +<code>myscript</code>, <code>postmyscript</code>). Scripts from dependencies can be run with +<code>npm explore <pkg> -- npm run <stage></code>.</p> <h3 id="pre--post-scripts">Pre & Post Scripts</h3> -<p>To create “pre” or “post” scripts for any scripts defined in the <code>"scripts"</code> section of the <code>package.json</code>, simply create another script <em>with a matching name</em> and add “pre” or “post” to the beginning of them.</p> +<p>To create “pre” or “post” scripts for any scripts defined in the +<code>"scripts"</code> section of the <code>package.json</code>, simply create another script +<em>with a matching name</em> and add “pre” or “post” to the beginning of them.</p> <pre lang="json"><code>{ "scripts": { "precompress": "{{ executes BEFORE the `compress` script }}", @@ -156,22 +164,47 @@ npm command-line interface } } </code></pre> +<p>In this example <code>npm run compress</code> would execute these scripts as +described.</p> <h3 id="life-cycle-scripts">Life Cycle Scripts</h3> -<p>There are some special life cycle scripts that happen only in certain situations. These scripts happen in addition to the “pre” and “post” script.</p> +<p>There are some special life cycle scripts that happen only in certain +situations. These scripts happen in addition to the <code>pre<event></code>, <code>post<event></code>, and +<code><event></code> scripts.</p> <ul> <li><code>prepare</code>, <code>prepublish</code>, <code>prepublishOnly</code>, <code>prepack</code>, <code>postpack</code></li> </ul> <p><strong>prepare</strong> (since <code>npm@4.0.0</code>)</p> <ul> -<li>Runs BEFORE the package is packed</li> -<li>Runs BEFORE the package is published</li> -<li>Runs on local <code>npm install</code> without any arguments</li> -<li>Run AFTER <code>prepublish</code>, but BEFORE <code>prepublishOnly</code></li> -<li>NOTE: If a package being installed through git contains a <code>prepare</code> script, its <code>dependencies</code> and <code>devDependencies</code> will be installed, and the prepare script will be run, before the package is packaged and installed.</li> +<li> +<p>Runs any time before the package is packed, i.e. during <code>npm publish</code> +and <code>npm pack</code></p> +</li> +<li> +<p>Runs BEFORE the package is packed</p> +</li> +<li> +<p>Runs BEFORE the package is published</p> +</li> +<li> +<p>Runs on local <code>npm install</code> without any arguments</p> +</li> +<li> +<p>Run AFTER <code>prepublish</code>, but BEFORE <code>prepublishOnly</code></p> +</li> +<li> +<p>NOTE: If a package being installed through git contains a <code>prepare</code> +script, its <code>dependencies</code> and <code>devDependencies</code> will be installed, and +the prepare script will be run, before the package is packaged and +installed.</p> +</li> +<li> +<p>As of <code>npm@7</code> these scripts run in the background</p> +</li> </ul> <p><strong>prepublish</strong> (DEPRECATED)</p> <ul> -<li>Same as <code>prepare</code></li> +<li>Does not run during <code>npm publish</code>, but does run during <code>npm ci</code> +and <code>npm install</code>. See below for more info.</li> </ul> <p><strong>prepublishOnly</strong></p> <ul> @@ -184,7 +217,7 @@ npm command-line interface </ul> <p><strong>postpack</strong></p> <ul> -<li>Runs AFTER the tarball has been generated and moved to its final destination.</li> +<li>Runs AFTER the tarball has been generated but before it is moved to its final destination (if at all, publish does not save the tarball locally)</li> </ul> <h4 id="prepare-and-prepublish">Prepare and Prepublish</h4> <p><strong>Deprecation Note: prepublish</strong></p> @@ -207,51 +240,106 @@ the size for your users.</li> other system tools on the target machines.</li> </ul> <h3 id="life-cycle-operation-order">Life Cycle Operation Order</h3> -<h4 id="npm-publish"><a href="../commands/npm-publish.html"><code>npm publish</code></a></h4> +<h4 id="npm-cache-add"><a href="../commands/npm-cache.html"><code>npm cache add</code></a></h4> <ul> -<li><code>prepublishOnly</code></li> <li><code>prepare</code></li> +</ul> +<h4 id="npm-ci"><a href="../commands/npm-ci.html"><code>npm ci</code></a></h4> +<ul> +<li><code>preinstall</code></li> +<li><code>install</code></li> +<li><code>postinstall</code></li> <li><code>prepublish</code></li> -<li><code>publish</code></li> -<li><code>postpublish</code></li> +<li><code>preprepare</code></li> +<li><code>prepare</code></li> +<li><code>postprepare</code></li> +</ul> +<p>These all run after the actual installation of modules into +<code>node_modules</code>, in order, with no internal actions happening in between</p> +<h4 id="npm-diff"><a href="../commands/npm-diff.html"><code>npm diff</code></a></h4> +<ul> +<li><code>prepare</code></li> +</ul> +<h4 id="npm-env"><a href="../commands/npm-env.html"><code>npm env</code></a></h4> +<ul> +<li><code>env</code> (You can override the default behavior of <code>npm env</code> by defining +a custom <code>env</code> entry in your <code>scripts</code> object)</li> +</ul> +<h4 id="npm-install"><a href="../commands/npm-install.html"><code>npm install</code></a></h4> +<p>These also run when you run <code>npm install -g <pkg-name></code></p> +<ul> +<li><code>preinstall</code></li> +<li><code>install</code></li> +<li><code>postinstall</code></li> +<li><code>prepublish</code></li> +<li><code>preprepare</code></li> +<li><code>prepare</code></li> +<li><code>postprepare</code></li> </ul> +<p>If there is a <code>binding.gyp</code> file in the root of your package and you +haven’t defined your own <code>install</code> or <code>preinstall</code> scripts, npm will +default the <code>install</code> command to compile using node-gyp via <code>node-gyp rebuild</code></p> +<p>These are run from the scripts of <code><pkg-name></code></p> <h4 id="npm-pack"><a href="../commands/npm-pack.html"><code>npm pack</code></a></h4> <ul> <li><code>prepack</code></li> +<li><code>prepare</code></li> <li><code>postpack</code></li> </ul> -<h4 id="npm-install"><a href="../commands/npm-install.html"><code>npm install</code></a></h4> +<h4 id="npm-publish"><a href="../commands/npm-publish.html"><code>npm publish</code></a></h4> +<ul> +<li><code>prepublishOnly</code></li> +<li><code>prepack</code></li> +<li><code>prepare</code></li> +<li><code>postpack</code></li> +<li><code>publish</code></li> +<li><code>postpublish</code></li> +</ul> +<p><code>prepare</code> will not run during <code>--dry-run</code></p> +<h4 id="npm-rebuild"><a href="../commands/npm-rebuild.html"><code>npm rebuild</code></a></h4> <ul> <li><code>preinstall</code></li> <li><code>install</code></li> <li><code>postinstall</code></li> +<li><code>prepare</code></li> +</ul> +<p><code>prepare</code> is only run if the current directory is a symlink (e.g. with +linked packages)</p> +<h4 id="npm-restart"><a href="../commands/npm-restart.html"><code>npm restart</code></a></h4> +<p>If there is a <code>restart</code> script defined, these events are run, otherwise +<code>stop</code> and <code>start</code> are both run if present, including their <code>pre</code> and +<code>post</code> iterations)</p> +<ul> +<li><code>prerestart</code></li> +<li><code>restart</code></li> +<li><code>postrestart</code></li> </ul> -<p>Also triggers</p> +<h4 id="npm-run-user-defined"><a href="../commands/npm-run.html"><code>npm run <user defined></code></a></h4> <ul> -<li><code>prepublish</code> (when on local)</li> -<li><code>prepare</code> (when on local or workspaces)</li> +<li><code>pre<user-defined></code></li> +<li><code><user-defined></code></li> +<li><code>post<user-defined></code></li> </ul> <h4 id="npm-start"><a href="../commands/npm-start.html"><code>npm start</code></a></h4> -<p><code>npm run start</code> has an <code>npm start</code> shorthand.</p> <ul> <li><code>prestart</code></li> <li><code>start</code></li> <li><code>poststart</code></li> </ul> -<h3 id="default-values">Default Values</h3> -<p>npm will default some script values based on package contents.</p> -<ul> -<li> -<p><code>"start": "node server.js"</code>:</p> <p>If there is a <code>server.js</code> file in the root of your package, then npm -will default the <code>start</code> command to <code>node server.js</code>.</p> -</li> -<li> -<p><code>"install": "node-gyp rebuild"</code>:</p> -<p>If there is a <code>binding.gyp</code> file in the root of your package and you -haven’t defined your own <code>install</code> or <code>preinstall</code> scripts, npm will -default the <code>install</code> command to compile using node-gyp.</p> -</li> +will default the <code>start</code> command to <code>node server.js</code>. <code>prestart</code> and +<code>poststart</code> will still run in this case.</p> +<h4 id="npm-stop"><a href="../commands/npm-stop.html"><code>npm stop</code></a></h4> +<ul> +<li><code>prestop</code></li> +<li><code>stop</code></li> +<li><code>poststop</code></li> +</ul> +<h4 id="npm-test"><a href="../commands/npm-test.html"><code>npm test</code></a></h4> +<ul> +<li><code>pretest</code></li> +<li><code>test</code></li> +<li><code>posttest</code></li> </ul> <h3 id="user">User</h3> <p>When npm is run as root, scripts are always run with the effective uid @@ -264,14 +352,14 @@ the process.</p> <p>If you depend on modules that define executable scripts, like test suites, then those executables will be added to the <code>PATH</code> for executing the scripts. So, if your package.json has this:</p> -<pre lang="json"><code>{ - "name" : "foo", - "dependencies" : { - "bar" : "0.1.x" - }, - "scripts": { - "start" : "bar ./test" - } +<pre lang="json"><code>{ + "name" : "foo", + "dependencies" : { + "bar" : "0.1.x" + }, + "scripts": { + "start" : "bar ./test" + } } </code></pre> <p>then you could run <code>npm start</code> to execute the <code>bar</code> script, which is @@ -292,14 +380,14 @@ config by checking the <code>npm_config_root</code> environment variable.</p> <p>The package.json “config” keys are overwritten in the environment if there is a config param of <code><name>[@<version>]:<key></code>. For example, if the package.json has this:</p> -<pre lang="json"><code>{ - "name" : "foo", - "config" : { - "port" : "8080" - }, - "scripts" : { - "start" : "node server.js" - } +<pre lang="json"><code>{ + "name" : "foo", + "config" : { + "port" : "8080" + }, + "scripts" : { + "start" : "node server.js" + } } </code></pre> <p>and the server.js is this:</p> @@ -320,10 +408,10 @@ see this in the script:</p> </code></pre> <h3 id="examples">Examples</h3> <p>For example, if your package.json contains this:</p> -<pre lang="json"><code>{ - "scripts" : { - "install" : "scripts/install.js", - "postinstall" : "scripts/postinstall.js", +<pre lang="json"><code>{ + "scripts" : { + "install" : "scripts/install.js", + "postinstall" : "scripts/postinstall.js", "uninstall" : "scripts/uninstall.js" } } @@ -336,10 +424,10 @@ be wise in this case to look at the <code>npm_lifecycle_event</code> environment variable.</p> <p>If you want to run a make command, you can do so. This works just fine:</p> -<pre lang="json"><code>{ - "scripts" : { - "preinstall" : "./configure", - "install" : "make && make install", +<pre lang="json"><code>{ + "scripts" : { + "preinstall" : "./configure", + "install" : "make && make install", "test" : "make test" } } diff --git a/deps/npm/lib/access.js b/deps/npm/lib/access.js index 10b1e21e0c..e11934af43 100644 --- a/deps/npm/lib/access.js +++ b/deps/npm/lib/access.js @@ -3,25 +3,11 @@ const path = require('path') const libaccess = require('libnpmaccess') const readPackageJson = require('read-package-json-fast') -const npm = require('./npm.js') const output = require('./utils/output.js') const otplease = require('./utils/otplease.js') const usageUtil = require('./utils/usage.js') const getIdentity = require('./utils/get-identity.js') -const usage = usageUtil( - 'npm access', - 'npm access public [<package>]\n' + - 'npm access restricted [<package>]\n' + - 'npm access grant <read-only|read-write> <scope:team> [<package>]\n' + - 'npm access revoke <scope:team> [<package>]\n' + - 'npm access 2fa-required [<package>]\n' + - 'npm access 2fa-not-required [<package>]\n' + - 'npm access ls-packages [<user>|<scope>|<scope:team>]\n' + - 'npm access ls-collaborators [<package> [<user>]]\n' + - 'npm access edit [<package>]' -) - const subcommands = [ 'public', 'restricted', @@ -34,152 +20,195 @@ const subcommands = [ '2fa-not-required', ] -const UsageError = (msg) => - Object.assign(new Error(`\nUsage: ${msg}\n\n` + usage), { - code: 'EUSAGE', - }) - -const cmd = (args, cb) => - access(args) - .then(x => cb(null, x)) - .catch(err => err.code === 'EUSAGE' - ? cb(err.message) - : cb(err) +class Access { + constructor (npm) { + this.npm = npm + } + + get usage () { + return usageUtil( + 'access', + 'npm access public [<package>]\n' + + 'npm access restricted [<package>]\n' + + 'npm access grant <read-only|read-write> <scope:team> [<package>]\n' + + 'npm access revoke <scope:team> [<package>]\n' + + 'npm access 2fa-required [<package>]\n' + + 'npm access 2fa-not-required [<package>]\n' + + 'npm access ls-packages [<user>|<scope>|<scope:team>]\n' + + 'npm access ls-collaborators [<package> [<user>]]\n' + + 'npm access edit [<package>]' ) + } -const access = async ([cmd, ...args], cb) => { - const fn = subcommands.includes(cmd) && access[cmd] + async completion (opts) { + const argv = opts.conf.argv.remain + if (argv.length === 2) + return subcommands + + switch (argv[2]) { + case 'grant': + if (argv.length === 3) + return ['read-only', 'read-write'] + else + return [] + + case 'public': + case 'restricted': + case 'ls-packages': + case 'ls-collaborators': + case 'edit': + case '2fa-required': + case '2fa-not-required': + case 'revoke': + return [] + default: + throw new Error(argv[2] + ' not recognized') + } + } - if (!cmd) - throw UsageError('Subcommand is required.') + exec (args, cb) { + this.access(args) + .then(x => cb(null, x)) + .catch(err => err.code === 'EUSAGE' + ? cb(err.message) + : cb(err) + ) + } - if (!fn) - throw UsageError(`${cmd} is not a recognized subcommand.`) + async access ([cmd, ...args]) { + if (!cmd) + throw this.usageError('Subcommand is required.') - return fn(args, { ...npm.flatOptions }) -} + if (!subcommands.includes(cmd) || !this[cmd]) + throw this.usageError(`${cmd} is not a recognized subcommand.`) -const completion = async (opts) => { - const argv = opts.conf.argv.remain - if (argv.length === 2) - return subcommands + return this[cmd](args, { ...this.npm.flatOptions }) + } - switch (argv[2]) { - case 'grant': - if (argv.length === 3) - return ['read-only', 'read-write'] - else - return [] + public ([pkg], opts) { + return this.modifyPackage(pkg, opts, libaccess.public) + } - case 'public': - case 'restricted': - case 'ls-packages': - case 'ls-collaborators': - case 'edit': - case '2fa-required': - case '2fa-not-required': - case 'revoke': - return [] - default: - throw new Error(argv[2] + ' not recognized') + restricted ([pkg], opts) { + return this.modifyPackage(pkg, opts, libaccess.restricted) } -} -access.public = ([pkg], opts) => - modifyPackage(pkg, opts, libaccess.public) + async grant ([perms, scopeteam, pkg], opts) { + if (!perms || (perms !== 'read-only' && perms !== 'read-write')) + throw this.usageError('First argument must be either `read-only` or `read-write`.') -access.restricted = ([pkg], opts) => - modifyPackage(pkg, opts, libaccess.restricted) + if (!scopeteam) + throw this.usageError('`<scope:team>` argument is required.') -access.grant = async ([perms, scopeteam, pkg], opts) => { - if (!perms || (perms !== 'read-only' && perms !== 'read-write')) - throw UsageError('First argument must be either `read-only` or `read-write`.') + const [, scope, team] = scopeteam.match(/^@?([^:]+):(.*)$/) || [] - if (!scopeteam) - throw UsageError('`<scope:team>` argument is required.') + if (!scope && !team) { + throw this.usageError( + 'Second argument used incorrect format.\n' + + 'Example: @example:developers' + ) + } - const [, scope, team] = scopeteam.match(/^@?([^:]+):(.*)$/) || [] + return this.modifyPackage(pkg, opts, (pkgName, opts) => + libaccess.grant(pkgName, scopeteam, perms, opts), false) + } - if (!scope && !team) { - throw UsageError( - 'Second argument used incorrect format.\n' + - 'Example: @example:developers' - ) + async revoke ([scopeteam, pkg], opts) { + if (!scopeteam) + throw this.usageError('`<scope:team>` argument is required.') + + const [, scope, team] = scopeteam.match(/^@?([^:]+):(.*)$/) || [] + + if (!scope || !team) { + throw this.usageError( + 'First argument used incorrect format.\n' + + 'Example: @example:developers' + ) + } + + return this.modifyPackage(pkg, opts, (pkgName, opts) => + libaccess.revoke(pkgName, scopeteam, opts)) } - return modifyPackage(pkg, opts, (pkgName, opts) => - libaccess.grant(pkgName, scopeteam, perms, opts), false) -} + get ['2fa-required'] () { + return this.tfaRequired + } -access.revoke = async ([scopeteam, pkg], opts) => { - if (!scopeteam) - throw UsageError('`<scope:team>` argument is required.') + tfaRequired ([pkg], opts) { + return this.modifyPackage(pkg, opts, libaccess.tfaRequired, false) + } - const [, scope, team] = scopeteam.match(/^@?([^:]+):(.*)$/) || [] + get ['2fa-not-required'] () { + return this.tfaNotRequired + } - if (!scope || !team) { - throw UsageError( - 'First argument used incorrect format.\n' + - 'Example: @example:developers' - ) + tfaNotRequired ([pkg], opts) { + return this.modifyPackage(pkg, opts, libaccess.tfaNotRequired, false) } - return modifyPackage(pkg, opts, (pkgName, opts) => - libaccess.revoke(pkgName, scopeteam, opts)) -} + get ['ls-packages'] () { + return this.lsPackages + } -access['2fa-required'] = access.tfaRequired = ([pkg], opts) => - modifyPackage(pkg, opts, libaccess.tfaRequired, false) + async lsPackages ([owner], opts) { + if (!owner) + owner = await getIdentity(this.npm, opts) -access['2fa-not-required'] = access.tfaNotRequired = ([pkg], opts) => - modifyPackage(pkg, opts, libaccess.tfaNotRequired, false) + const pkgs = await libaccess.lsPackages(owner, opts) -access['ls-packages'] = access.lsPackages = async ([owner], opts) => { - if (!owner) - owner = await getIdentity(opts) + // TODO - print these out nicely (breaking change) + output(JSON.stringify(pkgs, null, 2)) + } - const pkgs = await libaccess.lsPackages(owner, opts) + get ['ls-collaborators'] () { + return this.lsCollaborators + } - // TODO - print these out nicely (breaking change) - output(JSON.stringify(pkgs, null, 2)) -} + async lsCollaborators ([pkg, usr], opts) { + const pkgName = await this.getPackage(pkg, false) + const collabs = await libaccess.lsCollaborators(pkgName, usr, opts) -access['ls-collaborators'] = access.lsCollaborators = async ([pkg, usr], opts) => { - const pkgName = await getPackage(pkg, false) - const collabs = await libaccess.lsCollaborators(pkgName, usr, opts) + // TODO - print these out nicely (breaking change) + output(JSON.stringify(collabs, null, 2)) + } - // TODO - print these out nicely (breaking change) - output(JSON.stringify(collabs, null, 2)) -} + async edit () { + throw new Error('edit subcommand is not implemented yet') + } -access.edit = () => - Promise.reject(new Error('edit subcommand is not implemented yet')) - -const modifyPackage = (pkg, opts, fn, requireScope = true) => - getPackage(pkg, requireScope) - .then(pkgName => otplease(opts, opts => fn(pkgName, opts))) - -const getPackage = async (name, requireScope) => { - if (name && name.trim()) - return name.trim() - else { - try { - const pkg = await readPackageJson(path.resolve(npm.prefix, 'package.json')) - name = pkg.name - } catch (err) { - if (err.code === 'ENOENT') { - throw new Error( - 'no package name passed to command and no package.json found' - ) - } else - throw err + modifyPackage (pkg, opts, fn, requireScope = true) { + return this.getPackage(pkg, requireScope) + .then(pkgName => otplease(opts, opts => fn(pkgName, opts))) + } + + async getPackage (name, requireScope) { + if (name && name.trim()) + return name.trim() + else { + try { + const pkg = await readPackageJson(path.resolve(this.npm.prefix, 'package.json')) + name = pkg.name + } catch (err) { + if (err.code === 'ENOENT') { + throw new Error( + 'no package name passed to command and no package.json found' + ) + } else + throw err + } + + if (requireScope && !name.match(/^@[^/]+\/.*$/)) + throw this.usageError('This command is only available for scoped packages.') + else + return name } + } - if (requireScope && !name.match(/^@[^/]+\/.*$/)) - throw UsageError('This command is only available for scoped packages.') - else - return name + usageError (msg) { + return Object.assign(new Error(`\nUsage: ${msg}\n\n` + this.usage), { + code: 'EUSAGE', + }) } } -module.exports = Object.assign(cmd, { usage, completion, subcommands }) +module.exports = Access diff --git a/deps/npm/lib/adduser.js b/deps/npm/lib/adduser.js index c68c2b80f8..dac0f5a468 100644 --- a/deps/npm/lib/adduser.js +++ b/deps/npm/lib/adduser.js @@ -1,5 +1,4 @@ const log = require('npmlog') -const npm = require('./npm.js') const output = require('./utils/output.js') const usageUtil = require('./utils/usage.js') const replaceInfo = require('./utils/replace-info.js') @@ -10,66 +9,76 @@ const authTypes = { sso: require('./auth/sso.js'), } -const usage = usageUtil( - 'adduser', - 'npm adduser [--registry=url] [--scope=@orgname] [--always-auth]' -) +class AddUser { + constructor (npm) { + this.npm = npm + } -const cmd = (args, cb) => adduser(args).then(() => cb()).catch(cb) + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil( + 'adduser', + 'npm adduser [--registry=url] [--scope=@orgname] [--always-auth]' + ) + } -const getRegistry = ({ scope, registry }) => { - if (scope) { - const scopedRegistry = npm.config.get(`${scope}:registry`) - const cliRegistry = npm.config.get('registry', 'cli') - if (scopedRegistry && !cliRegistry) - return scopedRegistry + exec (args, cb) { + this.adduser(args).then(() => cb()).catch(cb) } - return registry -} -const getAuthType = ({ authType }) => { - const type = authTypes[authType] + async adduser (args) { + const { scope } = this.npm.flatOptions + const registry = this.getRegistry(this.npm.flatOptions) + const auth = this.getAuthType(this.npm.flatOptions) + const creds = this.npm.config.getCredentialsByURI(registry) - if (!type) - throw new Error('no such auth module') + log.disableProgress() - return type -} + log.notice('', `Log in on ${replaceInfo(registry)}`) -const updateConfig = async ({ newCreds, registry, scope }) => { - npm.config.delete('_token', 'user') // prevent legacy pollution + const { message, newCreds } = await auth(this.npm, { + ...this.npm.flatOptions, + creds, + registry, + scope, + }) - if (scope) - npm.config.set(scope + ':registry', registry, 'user') + await this.updateConfig({ + newCreds, + registry, + scope, + }) - npm.config.setCredentialsByURI(registry, newCreds) - await npm.config.save('user') -} + output(message) + } -const adduser = async (args) => { - const { scope } = npm.flatOptions - const registry = getRegistry(npm.flatOptions) - const auth = getAuthType(npm.flatOptions) - const creds = npm.config.getCredentialsByURI(registry) + getRegistry ({ scope, registry }) { + if (scope) { + const scopedRegistry = this.npm.config.get(`${scope}:registry`) + const cliRegistry = this.npm.config.get('registry', 'cli') + if (scopedRegistry && !cliRegistry) + return scopedRegistry + } + return registry + } - log.disableProgress() + getAuthType ({ authType }) { + const type = authTypes[authType] - log.notice('', `Log in on ${replaceInfo(registry)}`) + if (!type) + throw new Error('no such auth module') - const { message, newCreds } = await auth({ - ...npm.flatOptions, - creds, - registry, - scope, - }) + return type + } - await updateConfig({ - newCreds, - registry, - scope, - }) + async updateConfig ({ newCreds, registry, scope }) { + this.npm.config.delete('_token', 'user') // prevent legacy pollution - output(message) -} + if (scope) + this.npm.config.set(scope + ':registry', registry, 'user') -module.exports = Object.assign(cmd, { usage }) + this.npm.config.setCredentialsByURI(registry, newCreds) + await this.npm.config.save('user') + } +} +module.exports = AddUser diff --git a/deps/npm/lib/audit.js b/deps/npm/lib/audit.js index 1b31401b1a..dfa01cb270 100644 --- a/deps/npm/lib/audit.js +++ b/deps/npm/lib/audit.js @@ -1,55 +1,65 @@ const Arborist = require('@npmcli/arborist') const auditReport = require('npm-audit-report') -const npm = require('./npm.js') const output = require('./utils/output.js') const reifyFinish = require('./utils/reify-finish.js') const auditError = require('./utils/audit-error.js') +const usageUtil = require('./utils/usage.js') -const audit = async args => { - const arb = new Arborist({ - ...npm.flatOptions, - audit: true, - path: npm.prefix, - }) - const fix = args[0] === 'fix' - await arb.audit({ fix }) - if (fix) - await reifyFinish(arb) - else { - // will throw if there's an error, because this is an audit command - auditError(arb.auditReport) - const reporter = npm.flatOptions.json ? 'json' : 'detail' - const result = auditReport(arb.auditReport, { - ...npm.flatOptions, - reporter, - }) - process.exitCode = process.exitCode || result.exitCode - output(result.report) +class Audit { + constructor (npm) { + this.npm = npm } -} -const cmd = (args, cb) => audit(args).then(() => cb()).catch(cb) + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil( + 'audit', + 'npm audit [--json] [--production]' + + '\nnpm audit fix ' + + '[--force|--package-lock-only|--dry-run|--production|--only=(dev|prod)]' + ) + } -const usageUtil = require('./utils/usage') -const usage = usageUtil( - 'audit', - 'npm audit [--json] [--production]' + - '\nnpm audit fix ' + - '[--force|--package-lock-only|--dry-run|--production|--only=(dev|prod)]' -) + async completion (opts) { + const argv = opts.conf.argv.remain -const completion = async (opts) => { - const argv = opts.conf.argv.remain + if (argv.length === 2) + return ['fix'] - if (argv.length === 2) - return ['fix'] + switch (argv[2]) { + case 'fix': + return [] + default: + throw new Error(argv[2] + ' not recognized') + } + } - switch (argv[2]) { - case 'fix': - return [] - default: - throw new Error(argv[2] + ' not recognized') + exec (args, cb) { + this.audit(args).then(() => cb()).catch(cb) + } + + async audit (args) { + const arb = new Arborist({ + ...this.npm.flatOptions, + audit: true, + path: this.npm.prefix, + }) + const fix = args[0] === 'fix' + await arb.audit({ fix }) + if (fix) + await reifyFinish(this.npm, arb) + 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, + }) + process.exitCode = process.exitCode || result.exitCode + output(result.report) + } } } -module.exports = Object.assign(cmd, { usage, completion }) +module.exports = Audit diff --git a/deps/npm/lib/auth/legacy.js b/deps/npm/lib/auth/legacy.js index f291ca794e..8659446dc4 100644 --- a/deps/npm/lib/auth/legacy.js +++ b/deps/npm/lib/auth/legacy.js @@ -4,11 +4,6 @@ const profile = require('npm-profile') const openUrl = require('../utils/open-url.js') const read = require('../utils/read-user-info.js') -// TODO: refactor lib/utils/open-url and its usages -const openerPromise = (url) => new Promise((resolve, reject) => { - openUrl(url, 'to complete your login please visit', (er) => er ? reject(er) : resolve()) -}) - const loginPrompter = async (creds) => { const opts = { log: log } @@ -19,7 +14,7 @@ const loginPrompter = async (creds) => { return creds } -const login = async (opts) => { +const login = async (npm, opts) => { let res const requestOTP = async () => { @@ -54,6 +49,7 @@ const login = async (opts) => { return newUser } + const openerPromise = (url) => openUrl(npm, url, 'to complete your login please visit') try { res = await profile.login(openerPromise, loginPrompter, opts) } catch (err) { diff --git a/deps/npm/lib/auth/oauth.js b/deps/npm/lib/auth/oauth.js index ee45317113..99c2ca0ca0 100644 --- a/deps/npm/lib/auth/oauth.js +++ b/deps/npm/lib/auth/oauth.js @@ -1,9 +1,8 @@ const sso = require('./sso.js') -const npm = require('../npm.js') -const login = (opts) => { +const login = (npm, opts) => { npm.config.set('sso-type', 'oauth') - return sso(opts) + return sso(npm, opts) } module.exports = login diff --git a/deps/npm/lib/auth/saml.js b/deps/npm/lib/auth/saml.js index f30d82849d..3dd31ca013 100644 --- a/deps/npm/lib/auth/saml.js +++ b/deps/npm/lib/auth/saml.js @@ -1,9 +1,8 @@ const sso = require('./sso.js') -const npm = require('../npm.js') -const login = (opts) => { +const login = (npm, opts) => { npm.config.set('sso-type', 'saml') - return sso(opts) + return sso(npm, opts) } module.exports = login diff --git a/deps/npm/lib/auth/sso.js b/deps/npm/lib/auth/sso.js index 378295f5f6..56cff3c06e 100644 --- a/deps/npm/lib/auth/sso.js +++ b/deps/npm/lib/auth/sso.js @@ -7,14 +7,11 @@ // CLI, we can remove this, and fold the lib/auth/legacy.js back into // lib/adduser.js -const { promisify } = require('util') - const log = require('npmlog') const profile = require('npm-profile') const npmFetch = require('npm-registry-fetch') -const npm = require('../npm.js') -const openUrl = promisify(require('../utils/open-url.js')) +const openUrl = require('../utils/open-url.js') const otplease = require('../utils/otplease.js') const pollForSession = ({ registry, token, opts }) => { @@ -38,7 +35,7 @@ function sleep (time) { return new Promise((resolve) => setTimeout(resolve, time)) } -const login = async ({ creds, registry, scope }) => { +const login = async (npm, { creds, registry, scope }) => { log.warn('deprecated', 'SSO --auth-type is deprecated') const opts = { ...npm.flatOptions, creds, registry, scope } @@ -65,7 +62,7 @@ const login = async ({ creds, registry, scope }) => { if (!sso) throw new Error('no SSO URL returned by services') - await openUrl(sso, 'to complete your login please visit') + await openUrl(npm, sso, 'to complete your login please visit') const username = await pollForSession({ registry, token, opts }) diff --git a/deps/npm/lib/bin.js b/deps/npm/lib/bin.js index e627ce22f1..11490c41cb 100644 --- a/deps/npm/lib/bin.js +++ b/deps/npm/lib/bin.js @@ -1,13 +1,26 @@ -const npm = require('./npm.js') const output = require('./utils/output.js') +const envPath = require('./utils/path.js') const usageUtil = require('./utils/usage.js') -const PATH = require('./utils/path.js') -const cmd = (args, cb) => bin(args).then(() => cb()).catch(cb) -const usage = usageUtil('bin', 'npm bin [-g]') -const bin = async (args, cb) => { - const b = npm.bin - output(b) - if (npm.flatOptions.global && !PATH.includes(b)) - console.error('(not in PATH env variable)') + +class Bin { + constructor (npm) { + this.npm = npm + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil('bin', 'npm bin [-g]') + } + + exec (args, cb) { + this.bin(args).then(() => cb()).catch(cb) + } + + async bin (args) { + const b = this.npm.bin + output(b) + if (this.npm.flatOptions.global && !envPath.includes(b)) + console.error('(not in PATH env variable)') + } } -module.exports = Object.assign(cmd, { usage }) +module.exports = Bin diff --git a/deps/npm/lib/birthday.js b/deps/npm/lib/birthday.js index 6c71a9e715..5ea855512f 100644 --- a/deps/npm/lib/birthday.js +++ b/deps/npm/lib/birthday.js @@ -1,11 +1,18 @@ -const npm = require('./npm.js') -module.exports = (_, cb) => { - Object.defineProperty(npm, 'flatOptions', { - value: { - ...npm.flatOptions, - package: ['@npmcli/npm-birthday'], - yes: true, - }, - }) - return npm.commands.exec(['npm-birthday'], cb) +class Birthday { + constructor (npm) { + this.npm = npm + Object.defineProperty(this.npm, 'flatOptions', { + value: { + ...npm.flatOptions, + package: ['@npmcli/npm-birthday'], + yes: true, + }, + }) + } + + exec (args, cb) { + return this.npm.commands.exec(['npm-birthday'], cb) + } } + +module.exports = Birthday diff --git a/deps/npm/lib/bugs.js b/deps/npm/lib/bugs.js index 09856313ce..fb0d7c9277 100644 --- a/deps/npm/lib/bugs.js +++ b/deps/npm/lib/bugs.js @@ -1,46 +1,55 @@ const log = require('npmlog') const pacote = require('pacote') -const { promisify } = require('util') -const openUrl = promisify(require('./utils/open-url.js')) +const openUrl = require('./utils/open-url.js') const usageUtil = require('./utils/usage.js') -const npm = require('./npm.js') const hostedFromMani = require('./utils/hosted-git-info-from-manifest.js') -const usage = usageUtil('bugs', 'npm bugs [<pkgname>]') +class Bugs { + constructor (npm) { + this.npm = npm + } -const cmd = (args, cb) => bugs(args).then(() => cb()).catch(cb) + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil('bugs', 'npm bugs [<pkgname>]') + } -const bugs = async args => { - if (!args || !args.length) - args = ['.'] + exec (args, cb) { + this.bugs(args).then(() => cb()).catch(cb) + } - await Promise.all(args.map(pkg => getBugs(pkg))) -} + async bugs (args) { + if (!args || !args.length) + args = ['.'] -const getBugsUrl = mani => { - if (mani.bugs) { - if (typeof mani.bugs === 'string') - return mani.bugs + await Promise.all(args.map(pkg => this.getBugs(pkg))) + } - if (typeof mani.bugs === 'object' && mani.bugs.url) - return mani.bugs.url + async getBugs (pkg) { + const opts = { ...this.npm.flatOptions, fullMetadata: true } + const mani = await pacote.manifest(pkg, opts) + const url = this.getBugsUrl(mani) + log.silly('bugs', 'url', url) + await openUrl(this.npm, url, `${mani.name} bug list available at the following URL`) } - // try to get it from the repo, if possible - const info = hostedFromMani(mani) - if (info) - return info.bugs() + getBugsUrl (mani) { + if (mani.bugs) { + if (typeof mani.bugs === 'string') + return mani.bugs - // just send them to the website, hopefully that has some info! - return `https://www.npmjs.com/package/${mani.name}` -} + if (typeof mani.bugs === 'object' && mani.bugs.url) + return mani.bugs.url + } -const getBugs = async pkg => { - const opts = { ...npm.flatOptions, fullMetadata: true } - const mani = await pacote.manifest(pkg, opts) - const url = getBugsUrl(mani) - log.silly('bugs', 'url', url) - await openUrl(url, `${mani.name} bug list available at the following URL`) + // try to get it from the repo, if possible + const info = hostedFromMani(mani) + if (info) + return info.bugs() + + // just send them to the website, hopefully that has some info! + return `https://www.npmjs.com/package/${mani.name}` + } } -module.exports = Object.assign(cmd, { usage }) +module.exports = Bugs diff --git a/deps/npm/lib/cache.js b/deps/npm/lib/cache.js index 7b84353b4a..8469559764 100644 --- a/deps/npm/lib/cache.js +++ b/deps/npm/lib/cache.js @@ -1,62 +1,69 @@ const cacache = require('cacache') const { promisify } = require('util') const log = require('npmlog') -const npm = require('./npm.js') const output = require('./utils/output.js') const pacote = require('pacote') const path = require('path') const rimraf = promisify(require('rimraf')) const usageUtil = require('./utils/usage.js') +class Cache { + constructor (npm) { + this.npm = npm + } -const usage = usageUtil('cache', - 'npm cache add <tarball file>' + - '\nnpm cache add <folder>' + - '\nnpm cache add <tarball url>' + - '\nnpm cache add <git url>' + - '\nnpm cache add <name>@<version>' + - '\nnpm cache clean' + - '\nnpm cache verify' -) - -const completion = async (opts) => { - const argv = opts.conf.argv.remain - if (argv.length === 2) - return ['add', 'clean', 'verify'] - - // TODO - eventually... - switch (argv[2]) { - case 'verify': - case 'clean': - case 'add': - return [] + get usage () { + return usageUtil('cache', + 'npm cache add <tarball file>' + + '\nnpm cache add <folder>' + + '\nnpm cache add <tarball url>' + + '\nnpm cache add <git url>' + + '\nnpm cache add <name>@<version>' + + '\nnpm cache clean' + + '\nnpm cache verify' + ) } -} -const cmd = (args, cb) => cache(args).then(() => cb()).catch(cb) - -const cache = async (args) => { - const cmd = args.shift() - switch (cmd) { - case 'rm': case 'clear': case 'clean': - return await clean(args) - case 'add': - return await add(args) - case 'verify': case 'check': - return await verify() - default: - throw Object.assign(new Error(usage), { code: 'EUSAGE' }) + async completion (opts) { + const argv = opts.conf.argv.remain + if (argv.length === 2) + return ['add', 'clean', 'verify'] + + // TODO - eventually... + switch (argv[2]) { + case 'verify': + case 'clean': + case 'add': + return [] + } + } + + exec (args, cb) { + this.cache(args).then(() => cb()).catch(cb) + } + + async cache (args) { + const cmd = args.shift() + switch (cmd) { + case 'rm': case 'clear': case 'clean': + return await this.clean(args) + case 'add': + return await this.add(args) + case 'verify': case 'check': + return await this.verify() + default: + throw Object.assign(new Error(this.usage), { code: 'EUSAGE' }) + } } -} -// npm cache clean [pkg]* -const clean = async (args) => { - if (args.length) - throw new Error('npm cache clear does not accept arguments') + // npm cache clean [pkg]* + async clean (args) { + if (args.length) + throw new Error('npm cache clear does not accept arguments') - const cachePath = path.join(npm.cache, '_cacache') - if (!npm.flatOptions.force) { - throw new Error(`As of npm@5, the npm cache self-heals from corruption issues + const cachePath = path.join(this.npm.cache, '_cacache') + if (!this.npm.flatOptions.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 want to make sure everything is consistent, use \`npm cache verify\` @@ -70,52 +77,53 @@ temporary cache instead of nuking the actual one. If you're sure you want to delete the entire cache, rerun this command with --force.`) + } + return rimraf(cachePath) } - return rimraf(cachePath) -} -// npm cache add <tarball-url> -// npm cache add <pkg> <ver> -// npm cache add <tarball> -// npm cache add <folder> -const add = async (args) => { - const usage = 'Usage:\n' + - ' npm cache add <tarball-url>\n' + - ' npm cache add <pkg>@<ver>\n' + - ' npm cache add <tarball>\n' + - ' npm cache add <folder>\n' - log.silly('cache add', 'args', args) - const spec = args[0] && args[0] + - (args[1] === undefined || args[1] === null ? '' : `@${args[1]}`) - - if (!spec) - throw Object.assign(new Error(usage), { code: 'EUSAGE' }) - - log.silly('cache add', 'spec', spec) - const opts = { ...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 - // for a normal request. - await pacote.tarball.stream(spec, stream => { - stream.resume() - return stream.promise() - }, opts) -} + // npm cache add <tarball-url> + // npm cache add <pkg> <ver> + // npm cache add <tarball> + // npm cache add <folder> + async add (args) { + const usage = 'Usage:\n' + + ' npm cache add <tarball-url>\n' + + ' npm cache add <pkg>@<ver>\n' + + ' npm cache add <tarball>\n' + + ' npm cache add <folder>\n' + log.silly('cache add', 'args', args) + const spec = args[0] && args[0] + + (args[1] === undefined || args[1] === null ? '' : `@${args[1]}`) + + if (!spec) + throw Object.assign(new Error(usage), { code: 'EUSAGE' }) + + log.silly('cache add', 'spec', spec) + const opts = { ...this.npm.flatOptions } -const verify = async () => { - const cache = path.join(npm.cache, '_cacache') - const prefix = cache.indexOf(process.env.HOME) === 0 - ? `~${cache.substr(process.env.HOME.length)}` - : cache - const stats = await cacache.verify(cache) - output(`Cache verified and compressed (${prefix})`) - output(`Content verified: ${stats.verifiedContent} (${stats.keptSize} bytes)`) - stats.badContentCount && output(`Corrupted content removed: ${stats.badContentCount}`) - stats.reclaimedCount && output(`Content garbage-collected: ${stats.reclaimedCount} (${stats.reclaimedSize} bytes)`) - stats.missingContent && output(`Missing content: ${stats.missingContent}`) - output(`Index entries: ${stats.totalEntries}`) - output(`Finished in ${stats.runTime.total / 1000}s`) + // 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 + // for a normal request. + await pacote.tarball.stream(spec, stream => { + stream.resume() + return stream.promise() + }, opts) + } + + async verify () { + const cache = path.join(this.npm.cache, '_cacache') + const prefix = cache.indexOf(process.env.HOME) === 0 + ? `~${cache.substr(process.env.HOME.length)}` + : cache + const stats = await cacache.verify(cache) + output(`Cache verified and compressed (${prefix})`) + output(`Content verified: ${stats.verifiedContent} (${stats.keptSize} bytes)`) + stats.badContentCount && output(`Corrupted content removed: ${stats.badContentCount}`) + stats.reclaimedCount && output(`Content garbage-collected: ${stats.reclaimedCount} (${stats.reclaimedSize} bytes)`) + stats.missingContent && output(`Missing content: ${stats.missingContent}`) + output(`Index entries: ${stats.totalEntries}`) + output(`Finished in ${stats.runTime.total / 1000}s`) + } } -module.exports = Object.assign(cmd, { completion, usage }) +module.exports = Cache diff --git a/deps/npm/lib/ci.js b/deps/npm/lib/ci.js index 51c165acce..03a91a6046 100644 --- a/deps/npm/lib/ci.js +++ b/deps/npm/lib/ci.js @@ -7,13 +7,8 @@ const fs = require('fs') const readdir = util.promisify(fs.readdir) const log = require('npmlog') -const npm = require('./npm.js') const usageUtil = require('./utils/usage.js') -const usage = usageUtil('ci', 'npm ci') - -const cmd = (args, cb) => ci().then(() => cb()).catch(cb) - const removeNodeModules = async where => { const rimrafOpts = { glob: false } process.emit('time', 'npm-ci:rm') @@ -24,55 +19,70 @@ const removeNodeModules = async where => { process.emit('timeEnd', 'npm-ci:rm') } -const ci = async () => { - if (npm.flatOptions.global) { - const err = new Error('`npm ci` does not work for global packages') - err.code = 'ECIGLOBAL' - throw err +class CI { + constructor (npm) { + this.npm = npm + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil('ci', 'npm ci') } - const where = npm.prefix - const { scriptShell, ignoreScripts } = npm.flatOptions - const arb = new Arborist({ ...npm.flatOptions, path: where }) + exec (args, cb) { + this.ci().then(() => cb()).catch(cb) + } + + async ci () { + if (this.npm.flatOptions.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([ - arb.loadVirtual().catch(er => { - log.verbose('loadVirtual', er.stack) - const msg = - 'The `npm ci` command can only install with an existing package-lock.json or\n' + - 'npm-shrinkwrap.json with lockfileVersion >= 1. Run an install with npm@5 or\n' + - 'later to generate a package-lock.json file, then try again.' - throw new Error(msg) - }), - removeNodeModules(where), - ]) - // npm ci should never modify the lockfile or package.json - await arb.reify({ ...npm.flatOptions, save: false }) + await Promise.all([ + arb.loadVirtual().catch(er => { + log.verbose('loadVirtual', er.stack) + const msg = + 'The `npm ci` command can only install with an existing package-lock.json or\n' + + 'npm-shrinkwrap.json with lockfileVersion >= 1. Run an install with npm@5 or\n' + + 'later to generate a package-lock.json file, then try again.' + throw new Error(msg) + }), + removeNodeModules(where), + ]) + // npm ci should never modify the lockfile or package.json + await arb.reify({ ...this.npm.flatOptions, save: false }) - // run the same set of scripts that `npm install` runs. - if (!ignoreScripts) { - const scripts = [ - 'preinstall', - 'install', - 'postinstall', - 'prepublish', // XXX should we remove this finally?? - 'preprepare', - 'prepare', - 'postprepare', - ] - for (const event of scripts) { - await runScript({ - path: where, - args: [], - scriptShell, - stdio: 'inherit', - stdioString: true, - banner: log.level !== 'silent', - event, - }) + // run the same set of scripts that `npm install` runs. + if (!ignoreScripts) { + const scripts = [ + 'preinstall', + 'install', + 'postinstall', + 'prepublish', // XXX should we remove this finally?? + 'preprepare', + 'prepare', + 'postprepare', + ] + for (const event of scripts) { + await runScript({ + path: where, + args: [], + scriptShell, + stdio: 'inherit', + stdioString: true, + banner: log.level !== 'silent', + event, + }) + } } + await reifyFinish(this.npm, arb) } - await reifyFinish(arb) } -module.exports = Object.assign(cmd, {usage}) +module.exports = CI diff --git a/deps/npm/lib/completion.js b/deps/npm/lib/completion.js index b31867d988..4c37e6ef35 100644 --- a/deps/npm/lib/completion.js +++ b/deps/npm/lib/completion.js @@ -29,7 +29,6 @@ // as an array. // -const npm = require('./npm.js') const { types, shorthands } = require('./utils/config.js') const deref = require('./utils/deref-command.js') const { aliases, cmdList, plumbing } = require('./utils/cmd-list.js') @@ -44,115 +43,127 @@ const output = require('./utils/output.js') const fileExists = require('./utils/file-exists.js') const usageUtil = require('./utils/usage.js') -const usage = usageUtil('completion', 'source <(npm completion)') const { promisify } = require('util') -const cmd = (args, cb) => compl(args).then(() => cb()).catch(cb) +class Completion { + constructor (npm) { + this.npm = npm + } -// completion for the completion command -const completion = async (opts) => { - if (opts.w > 2) - return + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil('completion', 'source <(npm completion)') + } - const { resolve } = require('path') - const [bashExists, zshExists] = await Promise.all([ - fileExists(resolve(process.env.HOME, '.bashrc')), - fileExists(resolve(process.env.HOME, '.zshrc')), - ]) - const out = [] - if (zshExists) - out.push(['>>', '~/.zshrc']) - - if (bashExists) - out.push(['>>', '~/.bashrc']) - - return out -} + // completion for the completion command + async completion (opts) { + if (opts.w > 2) + return -const compl = async args => { - if (isWindowsShell) { - const msg = 'npm completion supported only in MINGW / Git bash on Windows' - throw Object.assign(new Error(msg), { - code: 'ENOTSUP', - }) + const { resolve } = require('path') + const [bashExists, zshExists] = await Promise.all([ + fileExists(resolve(process.env.HOME, '.bashrc')), + fileExists(resolve(process.env.HOME, '.zshrc')), + ]) + const out = [] + if (zshExists) + out.push(['>>', '~/.zshrc']) + + if (bashExists) + out.push(['>>', '~/.bashrc']) + + return out } - const { COMP_CWORD, COMP_LINE, COMP_POINT } = process.env + exec (args, cb) { + this.compl(args).then(() => cb()).catch(cb) + } - // if the COMP_* isn't in the env, then just dump the script. - if (COMP_CWORD === undefined || + async compl (args) { + if (isWindowsShell) { + const msg = 'npm completion supported only in MINGW / Git bash on Windows' + throw Object.assign(new Error(msg), { + code: 'ENOTSUP', + }) + } + + const { COMP_CWORD, COMP_LINE, COMP_POINT } = process.env + + // if the COMP_* isn't in the env, then just dump the script. + if (COMP_CWORD === undefined || COMP_LINE === undefined || COMP_POINT === undefined) - return dumpScript() - - // ok we're actually looking at the envs and outputting the suggestions - // get the partial line and partial word, - // if the point isn't at the end. - // ie, tabbing at: npm foo b|ar - const w = +COMP_CWORD - const words = args.map(unescape) - const word = words[w] - const line = COMP_LINE - const point = +COMP_POINT - const partialLine = line.substr(0, point) - const partialWords = words.slice(0, w) - - // figure out where in that last word the point is. - const partialWordRaw = args[w] - let i = partialWordRaw.length - while (partialWordRaw.substr(0, i) !== partialLine.substr(-1 * i) && i > 0) - i-- - - const partialWord = unescape(partialWordRaw.substr(0, i)) - partialWords.push(partialWord) - - const opts = { - words, - w, - word, - line, - lineLength: line.length, - point, - partialLine, - partialWords, - partialWord, - raw: args, - } + return dumpScript() + + // ok we're actually looking at the envs and outputting the suggestions + // get the partial line and partial word, + // if the point isn't at the end. + // ie, tabbing at: npm foo b|ar + const w = +COMP_CWORD + const words = args.map(unescape) + const word = words[w] + const line = COMP_LINE + const point = +COMP_POINT + const partialLine = line.substr(0, point) + const partialWords = words.slice(0, w) + + // figure out where in that last word the point is. + const partialWordRaw = args[w] + let i = partialWordRaw.length + while (partialWordRaw.substr(0, i) !== partialLine.substr(-1 * i) && i > 0) + i-- + + const partialWord = unescape(partialWordRaw.substr(0, i)) + partialWords.push(partialWord) + + const opts = { + words, + w, + word, + line, + lineLength: line.length, + point, + partialLine, + partialWords, + partialWord, + raw: args, + } - if (partialWords.slice(0, -1).indexOf('--') === -1) { - if (word.charAt(0) === '-') - return wrap(opts, configCompl(opts)) + if (partialWords.slice(0, -1).indexOf('--') === -1) { + if (word.charAt(0) === '-') + return wrap(opts, configCompl(opts)) - if (words[w - 1] && + if (words[w - 1] && words[w - 1].charAt(0) === '-' && !isFlag(words[w - 1])) { - // awaiting a value for a non-bool config. - // don't even try to do this for now - return wrap(opts, configValueCompl(opts)) + // awaiting a value for a non-bool config. + // don't even try to do this for now + return wrap(opts, configValueCompl(opts)) + } } - } - // try to find the npm command. - // it's the first thing after all the configs. - // take a little shortcut and use npm's arg parsing logic. - // don't have to worry about the last arg being implicitly - // boolean'ed, since the last block will catch that. - const parsed = opts.conf = - nopt(types, shorthands, partialWords.slice(0, -1), 0) - // check if there's a command already. - const cmd = parsed.argv.remain[1] - if (!cmd) - return wrap(opts, cmdCompl(opts)) - - Object.keys(parsed).forEach(k => npm.config.set(k, parsed[k])) - - // at this point, if words[1] is some kind of npm command, - // then complete on it. - // otherwise, do nothing - const impl = npm.commands[cmd] - if (impl && impl.completion) { - const comps = await impl.completion(opts) - return wrap(opts, comps) + // try to find the npm command. + // it's the first thing after all the configs. + // take a little shortcut and use npm's arg parsing logic. + // don't have to worry about the last arg being implicitly + // boolean'ed, since the last block will catch that. + const parsed = opts.conf = + nopt(types, shorthands, partialWords.slice(0, -1), 0) + // check if there's a command already. + const cmd = parsed.argv.remain[1] + if (!cmd) + return wrap(opts, cmdCompl(opts)) + + Object.keys(parsed).forEach(k => this.npm.config.set(k, parsed[k])) + + // at this point, if words[1] is some kind of npm command, + // then complete on it. + // otherwise, do nothing + const impl = this.npm.commands[cmd] + if (impl && impl.completion) { + const comps = await impl.completion(opts) + return wrap(opts, comps) + } } } @@ -266,4 +277,4 @@ const cmdCompl = opts => { return fullList } -module.exports = Object.assign(cmd, { completion, usage }) +module.exports = Completion diff --git a/deps/npm/lib/config.js b/deps/npm/lib/config.js index e4da296de8..2805db9b80 100644 --- a/deps/npm/lib/config.js +++ b/deps/npm/lib/config.js @@ -1,4 +1,3 @@ -const npm = require('./npm.js') const { defaults, types } = require('./utils/config.js') const usageUtil = require('./utils/usage.js') const output = require('./utils/output.js') @@ -13,165 +12,173 @@ const { spawn } = require('child_process') const { EOL } = require('os') const ini = require('ini') -const usage = usageUtil( - 'config', - 'npm config set <key>=<value> [<key>=<value> ...]' + - '\nnpm config get [<key> [<key> ...]]' + - '\nnpm config delete <key> [<key> ...]' + - '\nnpm config list [--json]' + - '\nnpm config edit' + - '\nnpm set <key>=<value> [<key>=<value> ...]' + - '\nnpm get [<key> [<key> ...]]' -) - -const cmd = (args, cb) => config(args).then(() => cb()).catch(cb) - -const completion = async (opts) => { - const argv = opts.conf.argv.remain - if (argv[1] !== 'config') - argv.unshift('config') - - if (argv.length === 2) { - const cmds = ['get', 'set', 'delete', 'ls', 'rm', 'edit'] - if (opts.partialWord !== 'l') - cmds.push('list') - - return cmds +// take an array of `[key, value, k2=v2, k3, v3, ...]` and turn into +// { key: value, k2: v2, k3: v3 } +const keyValues = args => { + const kv = {} + for (let i = 0; i < args.length; i++) { + const arg = args[i].split('=') + const key = arg.shift() + const val = arg.length ? arg.join('=') + : i < args.length - 1 ? args[++i] + : '' + kv[key.trim()] = val.trim() } + return kv +} - const action = argv[2] - switch (action) { - case 'set': - // todo: complete with valid values, if possible. - if (argv.length > 3) - return [] +const publicVar = k => !/^(\/\/[^:]+:)?_/.test(k) - // fallthrough - /* eslint no-fallthrough:0 */ - case 'get': - case 'delete': - case 'rm': - return Object.keys(types) - case 'edit': - case 'list': - case 'ls': - default: - return [] +class Config { + constructor (npm) { + this.npm = npm } -} -const UsageError = () => - Object.assign(new Error(usage), { code: 'EUSAGE' }) + get usage () { + return usageUtil( + 'config', + 'npm config set <key>=<value> [<key>=<value> ...]' + + '\nnpm config get [<key> [<key> ...]]' + + '\nnpm config delete <key> [<key> ...]' + + '\nnpm config list [--json]' + + '\nnpm config edit' + + '\nnpm set <key>=<value> [<key>=<value> ...]' + + '\nnpm get [<key> [<key> ...]]' + ) + } + + async completion (opts) { + const argv = opts.conf.argv.remain + if (argv[1] !== 'config') + argv.unshift('config') + + if (argv.length === 2) { + const cmds = ['get', 'set', 'delete', 'ls', 'rm', 'edit'] + if (opts.partialWord !== 'l') + cmds.push('list') -const config = async ([action, ...args]) => { - npm.log.disableProgress() - try { + return cmds + } + + const action = argv[2] switch (action) { case 'set': - await set(args) - break + // todo: complete with valid values, if possible. + if (argv.length > 3) + return [] + + // fallthrough + /* eslint no-fallthrough:0 */ case 'get': - await get(args) - break case 'delete': case 'rm': - case 'del': - await del(args) - break + return Object.keys(types) + case 'edit': case 'list': case 'ls': - await (npm.flatOptions.json ? listJson() : list()) - break - case 'edit': - await edit() - break default: - throw UsageError() + return [] } - } finally { - npm.log.enableProgress() } -} -// take an array of `[key, value, k2=v2, k3, v3, ...]` and turn into -// { key: value, k2: v2, k3: v3 } -const keyValues = args => { - const kv = {} - for (let i = 0; i < args.length; i++) { - const arg = args[i].split('=') - const key = arg.shift() - const val = arg.length ? arg.join('=') - : i < args.length - 1 ? args[++i] - : '' - kv[key.trim()] = val.trim() + exec (args, cb) { + this.config(args).then(() => cb()).catch(cb) } - return kv -} -const set = async (args) => { - if (!args.length) - throw UsageError() - - const where = npm.flatOptions.global ? 'global' : 'user' - for (const [key, val] of Object.entries(keyValues(args))) { - npm.log.info('config', 'set %j %j', key, val) - npm.config.set(key, val || '', where) - if (!npm.config.validate(where)) - npm.log.warn('config', 'omitting invalid config values') + async config ([action, ...args]) { + this.npm.log.disableProgress() + try { + switch (action) { + case 'set': + await this.set(args) + break + case 'get': + await this.get(args) + break + case 'delete': + case 'rm': + case 'del': + await this.del(args) + break + case 'list': + case 'ls': + await (this.npm.flatOptions.json ? this.listJson() : this.list()) + break + case 'edit': + await this.edit() + break + default: + throw this.usageError() + } + } finally { + this.npm.log.enableProgress() + } } - await npm.config.save(where) -} + async set (args) { + if (!args.length) + throw this.usageError() + + const where = this.npm.flatOptions.global ? 'global' : 'user' + for (const [key, val] of Object.entries(keyValues(args))) { + this.npm.log.info('config', 'set %j %j', key, val) + this.npm.config.set(key, val || '', where) + if (!this.npm.config.validate(where)) + this.npm.log.warn('config', 'omitting invalid config values') + } + + await this.npm.config.save(where) + } -const get = async keys => { - if (!keys.length) - return list() + async get (keys) { + if (!keys.length) + return this.list() - const out = [] - for (const key of keys) { - if (!publicVar(key)) - throw `The ${key} option is protected, and cannot be retrieved in this way` + const out = [] + for (const key of keys) { + if (!publicVar(key)) + throw `The ${key} option is protected, and cannot be retrieved in this way` - const pref = keys.length > 1 ? `${key}=` : '' - out.push(pref + npm.config.get(key)) + const pref = keys.length > 1 ? `${key}=` : '' + out.push(pref + this.npm.config.get(key)) + } + output(out.join('\n')) } - output(out.join('\n')) -} -const del = async keys => { - if (!keys.length) - throw UsageError() + async del (keys) { + if (!keys.length) + throw this.usageError() - const where = npm.flatOptions.global ? 'global' : 'user' - for (const key of keys) - npm.config.delete(key, where) - await npm.config.save(where) -} + const where = this.npm.flatOptions.global ? 'global' : 'user' + for (const key of keys) + this.npm.config.delete(key, where) + await this.npm.config.save(where) + } -const edit = async () => { - const { editor: e, global } = npm.flatOptions - const where = global ? 'global' : 'user' - const file = npm.config.data.get(where).source - - // save first, just to make sure it's synced up - // this also removes all the comments from the last time we edited it. - await npm.config.save(where) - - const data = ( - await readFile(file, 'utf8').catch(() => '') - ).replace(/\r\n/g, '\n') - const defData = Object.entries(defaults).reduce((str, [key, val]) => { - const obj = { [key]: val } - const i = ini.stringify(obj) - .replace(/\r\n/g, '\n') // normalizes output from ini.stringify - .replace(/\n$/m, '') - .replace(/^/g, '; ') - .replace(/\n/g, '\n; ') - .split('\n') - return str + '\n' + i - }, '') - - const tmpData = `;;;; + async edit () { + const { editor: e, global } = this.npm.flatOptions + const where = global ? 'global' : 'user' + const file = this.npm.config.data.get(where).source + + // save first, just to make sure it's synced up + // this also removes all the comments from the last time we edited it. + await this.npm.config.save(where) + + const data = ( + await readFile(file, 'utf8').catch(() => '') + ).replace(/\r\n/g, '\n') + const defData = Object.entries(defaults).reduce((str, [key, val]) => { + const obj = { [key]: val } + const i = ini.stringify(obj) + .replace(/\r\n/g, '\n') // normalizes output from ini.stringify + .replace(/\n$/m, '') + .replace(/^/g, '; ') + .replace(/\n/g, '\n; ') + .split('\n') + return str + '\n' + i + }, '') + + const tmpData = `;;;; ; npm ${where}config file: ${file} ; this is a simple ini-formatted file ; lines that start with semi-colons are comments @@ -190,64 +197,67 @@ ${data.split('\n').sort((a, b) => a.localeCompare(b)).join('\n').trim()} ${defData} `.split('\n').join(EOL) - await mkdirp(dirname(file)) - await writeFile(file, tmpData, 'utf8') - await new Promise((resolve, reject) => { - const [bin, ...args] = e.split(/\s+/) - const editor = spawn(bin, [...args, file], { stdio: 'inherit' }) - editor.on('exit', (code) => { - if (code) - return reject(new Error(`editor process exited with code: ${code}`)) - return resolve() + await mkdirp(dirname(file)) + await writeFile(file, tmpData, 'utf8') + await new Promise((resolve, reject) => { + const [bin, ...args] = e.split(/\s+/) + const editor = spawn(bin, [...args, file], { stdio: 'inherit' }) + editor.on('exit', (code) => { + if (code) + return reject(new Error(`editor process exited with code: ${code}`)) + return resolve() + }) }) - }) -} - -const publicVar = k => !/^(\/\/[^:]+:)?_/.test(k) + } -const list = async () => { - const msg = [] - const { long } = npm.flatOptions - for (const [where, { data, source }] of npm.config.data.entries()) { - if (where === 'default' && !long) - continue + async list () { + const msg = [] + const { long } = this.npm.flatOptions + for (const [where, { data, source }] of this.npm.config.data.entries()) { + if (where === 'default' && !long) + continue + + const keys = Object.keys(data).sort((a, b) => a.localeCompare(b)) + if (!keys.length) + continue + + msg.push(`; "${where}" config from ${source}`, '') + for (const k of keys) { + const v = publicVar(k) ? JSON.stringify(data[k]) : '(protected)' + const src = this.npm.config.find(k) + const overridden = src !== where + msg.push((overridden ? '; ' : '') + + `${k} = ${v} ${overridden ? `; overridden by ${src}` : ''}`) + } + msg.push('') + } - const keys = Object.keys(data).sort((a, b) => a.localeCompare(b)) - if (!keys.length) - continue - - msg.push(`; "${where}" config from ${source}`, '') - for (const k of keys) { - const v = publicVar(k) ? JSON.stringify(data[k]) : '(protected)' - const src = npm.config.find(k) - const overridden = src !== where - msg.push((overridden ? '; ' : '') + - `${k} = ${v} ${overridden ? `; overridden by ${src}` : ''}`) + if (!long) { + msg.push( + `; node bin location = ${process.execPath}`, + `; cwd = ${process.cwd()}`, + `; HOME = ${process.env.HOME}`, + '; Run `npm config ls -l` to show all defaults.' + ) } - msg.push('') - } - if (!long) { - msg.push( - `; node bin location = ${process.execPath}`, - `; cwd = ${process.cwd()}`, - `; HOME = ${process.env.HOME}`, - '; Run `npm config ls -l` to show all defaults.' - ) + output(msg.join('\n').trim()) } - output(msg.join('\n').trim()) -} + async listJson () { + const publicConf = {} + for (const key in this.npm.config.list[0]) { + if (!publicVar(key)) + continue -const listJson = async () => { - const publicConf = {} - for (const key in npm.config.list[0]) { - if (!publicVar(key)) - continue + publicConf[key] = this.npm.config.get(key) + } + output(JSON.stringify(publicConf, null, 2)) + } - publicConf[key] = npm.config.get(key) + usageError () { + return Object.assign(new Error(this.usage), { code: 'EUSAGE' }) } - output(JSON.stringify(publicConf, null, 2)) } -module.exports = Object.assign(cmd, { usage, completion }) +module.exports = Config diff --git a/deps/npm/lib/dedupe.js b/deps/npm/lib/dedupe.js index 2211fcac8b..59978895ef 100644 --- a/deps/npm/lib/dedupe.js +++ b/deps/npm/lib/dedupe.js @@ -1,29 +1,39 @@ // dedupe duplicated packages, or find them in the tree -const npm = require('./npm.js') const Arborist = require('@npmcli/arborist') const usageUtil = require('./utils/usage.js') const reifyFinish = require('./utils/reify-finish.js') -const usage = usageUtil('dedupe', 'npm dedupe') +class Dedupe { + constructor (npm) { + this.npm = npm + } -const cmd = (args, cb) => dedupe(args).then(() => cb()).catch(cb) + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil('dedupe', 'npm dedupe') + } -const dedupe = async (args) => { - if (npm.flatOptions.global) { - const er = new Error('`npm dedupe` does not work in global mode.') - er.code = 'EDEDUPEGLOBAL' - throw er + exec (args, cb) { + this.dedupe(args).then(() => cb()).catch(cb) } - const dryRun = (args && args.dryRun) || npm.flatOptions.dryRun - const where = npm.prefix - const arb = new Arborist({ - ...npm.flatOptions, - path: where, - dryRun, - }) - await arb.dedupe(npm.flatOptions) - await reifyFinish(arb) + async dedupe (args) { + if (this.npm.config.get('global')) { + const er = new Error('`npm dedupe` does not work in global mode.') + er.code = 'EDEDUPEGLOBAL' + throw er + } + + const dryRun = this.npm.config.get('dry-run') + const where = this.npm.prefix + const arb = new Arborist({ + ...this.npm.flatOptions, + path: where, + dryRun, + }) + await arb.dedupe(this.npm.flatOptions) + await reifyFinish(this.npm, arb) + } } -module.exports = Object.assign(cmd, { usage }) +module.exports = Dedupe diff --git a/deps/npm/lib/deprecate.js b/deps/npm/lib/deprecate.js index 42d099b544..48f27ab6c3 100644 --- a/deps/npm/lib/deprecate.js +++ b/deps/npm/lib/deprecate.js @@ -1,4 +1,3 @@ -const npm = require('./npm.js') const fetch = require('npm-registry-fetch') const otplease = require('./utils/otplease.js') const npa = require('npm-package-arg') @@ -7,67 +6,77 @@ const getIdentity = require('./utils/get-identity.js') const libaccess = require('libnpmaccess') const usageUtil = require('./utils/usage.js') -const UsageError = () => - Object.assign(new Error(`\nUsage: ${usage}`), { - code: 'EUSAGE', - }) +class Deprecate { + constructor (npm) { + this.npm = npm + } -const usage = usageUtil( - 'deprecate', - 'npm deprecate <pkg>[@<version>] <message>' -) + get usage () { + return usageUtil( + 'deprecate', + 'npm deprecate <pkg>[@<version>] <message>' + ) + } -const completion = async (opts) => { - if (opts.conf.argv.remain.length > 1) - return [] + async completion (opts) { + if (opts.conf.argv.remain.length > 1) + return [] - const username = await getIdentity(npm.flatOptions) - const packages = await libaccess.lsPackages(username, npm.flatOptions) - return Object.keys(packages) - .filter((name) => - packages[name] === 'write' && - (opts.conf.argv.remain.length === 0 || - name.startsWith(opts.conf.argv.remain[0]))) -} - -const cmd = (args, cb) => - deprecate(args) - .then(() => cb()) - .catch(err => cb(err.code === 'EUSAGE' ? err.message : err)) + const username = await getIdentity(this.npm, this.npm.flatOptions) + const packages = await libaccess.lsPackages(username, this.npm.flatOptions) + return Object.keys(packages) + .filter((name) => + packages[name] === 'write' && + (opts.conf.argv.remain.length === 0 || + name.startsWith(opts.conf.argv.remain[0]))) + } -const deprecate = async ([pkg, msg]) => { - if (!pkg || !msg) - throw UsageError() + exec (args, cb) { + this.deprecate(args) + .then(() => cb()) + .catch(err => cb(err.code === 'EUSAGE' ? err.message : err)) + } - // fetch the data and make sure it exists. - const p = npa(pkg) - // npa makes the default spec "latest", but for deprecation - // "*" is the appropriate default. - const spec = p.rawSpec === '' ? '*' : p.fetchSpec + async deprecate ([pkg, msg]) { + if (!pkg || !msg) + throw this.usageError() - if (semver.validRange(spec, true) === null) - throw new Error(`invalid version range: ${spec}`) + // fetch the data and make sure it exists. + const p = npa(pkg) + // npa makes the default spec "latest", but for deprecation + // "*" is the appropriate default. + const spec = p.rawSpec === '' ? '*' : p.fetchSpec - const uri = '/' + p.escapedName - const packument = await fetch.json(uri, { - ...npm.flatOptions, - spec: p, - query: { write: true }, - }) + if (semver.validRange(spec, true) === null) + throw new Error(`invalid version range: ${spec}`) - Object.keys(packument.versions) - .filter(v => semver.satisfies(v, spec, { includePrerelease: true })) - .forEach(v => { - packument.versions[v].deprecated = msg + const uri = '/' + p.escapedName + const packument = await fetch.json(uri, { + ...this.npm.flatOptions, + spec: p, + query: { write: true }, }) - return otplease(npm.flatOptions, opts => fetch(uri, { - ...opts, - spec: p, - method: 'PUT', - body: packument, - ignoreBody: true, - })) + Object.keys(packument.versions) + .filter(v => semver.satisfies(v, spec, { includePrerelease: true })) + .forEach(v => { + packument.versions[v].deprecated = msg + }) + + return otplease(this.npm.flatOptions, opts => fetch(uri, { + ...opts, + spec: p, + method: 'PUT', + body: packument, + ignoreBody: true, + })) + } + + usageError () { + return Object.assign(new Error(`\nUsage: ${this.usage}`), { + code: 'EUSAGE', + }) + } } -module.exports = Object.assign(cmd, { completion, usage }) +module.exports = Deprecate diff --git a/deps/npm/lib/diff.js b/deps/npm/lib/diff.js index 9ef5a78a20..859e6f76fe 100644 --- a/deps/npm/lib/diff.js +++ b/deps/npm/lib/diff.js @@ -8,258 +8,271 @@ const npmlog = require('npmlog') const pacote = require('pacote') const pickManifest = require('npm-pick-manifest') -const npm = require('./npm.js') const usageUtil = require('./utils/usage.js') const output = require('./utils/output.js') const readLocalPkg = require('./utils/read-local-package.js') -const usage = usageUtil( - 'diff', - 'npm diff [...<paths>]' + - '\nnpm diff --diff=<pkg-name> [...<paths>]' + - '\nnpm diff --diff=<version-a> [--diff=<version-b>] [...<paths>]' + - '\nnpm diff --diff=<spec-a> [--diff=<spec-b>] [...<paths>]' + - '\nnpm diff [--diff-ignore-all-space] [--diff-name-only] [...<paths>] [...<paths>]' -) - -const cmd = (args, cb) => diff(args).then(() => cb()).catch(cb) - -const where = () => { - const globalTop = resolve(npm.globalDir, '..') - const { global } = npm.flatOptions - return global ? globalTop : npm.prefix -} +class Diff { + constructor (npm) { + this.npm = npm + } -const diff = async (args) => { - const specs = npm.flatOptions.diff.filter(d => d) - if (specs.length > 2) { - throw new TypeError( - 'Can\'t use more than two --diff arguments.\n\n' + - `Usage:\n${usage}` + get usage () { + return usageUtil( + 'diff', + 'npm diff [...<paths>]' + + '\nnpm diff --diff=<pkg-name> [...<paths>]' + + '\nnpm diff --diff=<version-a> [--diff=<version-b>] [...<paths>]' + + '\nnpm diff --diff=<spec-a> [--diff=<spec-b>] [...<paths>]' + + '\nnpm diff [--diff-ignore-all-space] [--diff-name-only] [...<paths>] [...<paths>]' ) } - const [a, b] = await retrieveSpecs(specs) - npmlog.info('diff', { src: a, dst: b }) - - const res = await libdiff([a, b], { ...npm.flatOptions, diffFiles: args }) - return output(res) -} + get where () { + const globalTop = resolve(this.npm.globalDir, '..') + const { global } = this.npm.flatOptions + return global ? globalTop : this.npm.prefix + } -const retrieveSpecs = ([a, b]) => { - // no arguments, defaults to comparing cwd - // to its latest published registry version - if (!a) - return defaultSpec() + exec (args, cb) { + this.diff(args).then(() => cb()).catch(cb) + } - // single argument, used to compare wanted versions of an - // installed dependency or to compare the cwd to a published version - if (!b) - return transformSingleSpec(a) + async diff (args) { + const specs = this.npm.flatOptions.diff.filter(d => d) + if (specs.length > 2) { + throw new TypeError( + 'Can\'t use more than two --diff arguments.\n\n' + + `Usage:\n${this.usage}` + ) + } - return convertVersionsToSpecs([a, b]) - .then(findVersionsByPackageName) -} + const [a, b] = await this.retrieveSpecs(specs) + npmlog.info('diff', { src: a, dst: b }) -const defaultSpec = async () => { - let noPackageJson - let pkgName - try { - pkgName = await readLocalPkg() - } catch (e) { - npmlog.verbose('diff', 'could not read project dir package.json') - noPackageJson = true + const res = await libdiff([a, b], { + ...this.npm.flatOptions, + diffFiles: args, + where: this.where, + }) + return output(res) } - if (!pkgName || noPackageJson) { - throw new Error( - 'Needs multiple arguments to compare or run from a project dir.\n\n' + - `Usage:\n${usage}` - ) - } + async retrieveSpecs ([a, b]) { + // no arguments, defaults to comparing cwd + // to its latest published registry version + if (!a) + return this.defaultSpec() - return [ - `${pkgName}@${npm.flatOptions.defaultTag}`, - `file:${npm.prefix}`, - ] -} + // single argument, used to compare wanted versions of an + // installed dependency or to compare the cwd to a published version + if (!b) + return this.transformSingleSpec(a) -const transformSingleSpec = async (a) => { - let noPackageJson - let pkgName - try { - pkgName = await readLocalPkg() - } catch (e) { - npmlog.verbose('diff', 'could not read project dir package.json') - noPackageJson = true + const specs = await this.convertVersionsToSpecs([a, b]) + return this.findVersionsByPackageName(specs) } - const missingPackageJson = new Error( - 'Needs multiple arguments to compare or run from a project dir.\n\n' + - `Usage:\n${usage}` - ) - const specSelf = () => { - if (noPackageJson) - throw missingPackageJson - - return `file:${npm.prefix}` - } + async defaultSpec () { + let noPackageJson + let pkgName + try { + pkgName = await readLocalPkg(this.npm) + } catch (e) { + npmlog.verbose('diff', 'could not read project dir package.json') + noPackageJson = true + } - // using a valid semver range, that means it should just diff - // the cwd against a published version to the registry using the - // same project name and the provided semver range - if (semver.validRange(a)) { - if (!pkgName) - throw missingPackageJson + if (!pkgName || noPackageJson) { + throw new Error( + 'Needs multiple arguments to compare or run from a project dir.\n\n' + + `Usage:\n${this.usage}` + ) + } return [ - `${pkgName}@${a}`, - specSelf(), + `${pkgName}@${this.npm.flatOptions.defaultTag}`, + `file:${this.npm.prefix}`, ] } - // when using a single package name as arg and it's part of the current - // install tree, then retrieve the current installed version and compare - // it against the same value `npm outdated` would suggest you to update to - const spec = npa(a) - if (spec.registry) { - let actualTree - let node + async transformSingleSpec (a) { + let noPackageJson + let pkgName try { - const opts = { - ...npm.flatOptions, - path: where(), - } - const arb = new Arborist(opts) - actualTree = await arb.loadActual(opts) - node = actualTree && - actualTree.inventory.query('name', spec.name) - .values().next().value + pkgName = await readLocalPkg(this.npm) } catch (e) { - npmlog.verbose('diff', 'failed to load actual install tree') + npmlog.verbose('diff', 'could not read project dir package.json') + noPackageJson = true + } + const missingPackageJson = new Error( + 'Needs multiple arguments to compare or run from a project dir.\n\n' + + `Usage:\n${this.usage}` + ) + + const specSelf = () => { + if (noPackageJson) + throw missingPackageJson + + return `file:${this.npm.prefix}` } - if (!node || !node.name || !node.package || !node.package.version) { + // using a valid semver range, that means it should just diff + // the cwd against a published version to the registry using the + // same project name and the provided semver range + if (semver.validRange(a)) { + if (!pkgName) + throw missingPackageJson + return [ - `${spec.name}@${spec.fetchSpec}`, + `${pkgName}@${a}`, specSelf(), ] } - const tryRootNodeSpec = () => - (actualTree && actualTree.edgesOut.get(spec.name) || {}).spec - - const tryAnySpec = () => { - for (const edge of node.edgesIn) - return edge.spec - } + // when using a single package name as arg and it's part of the current + // install tree, then retrieve the current installed version and compare + // it against the same value `npm outdated` would suggest you to update to + const spec = npa(a) + if (spec.registry) { + let actualTree + let node + try { + const opts = { + ...this.npm.flatOptions, + path: this.where, + } + const arb = new Arborist(opts) + actualTree = await arb.loadActual(opts) + node = actualTree && + actualTree.inventory.query('name', spec.name) + .values().next().value + } catch (e) { + npmlog.verbose('diff', 'failed to load actual install tree') + } - const aSpec = `file:${node.realpath}` - - // finds what version of the package to compare against, if a exact - // version or tag was passed than it should use that, otherwise - // work from the top of the arborist tree to find the original semver - // range declared in the package that depends on the package. - let bSpec - if (spec.rawSpec) - bSpec = spec.rawSpec - else { - const bTargetVersion = - tryRootNodeSpec() - || tryAnySpec() - - // figure out what to compare against, - // follows same logic to npm outdated "Wanted" results - const packument = await pacote.packument(spec, { - ...npm.flatOptions, - preferOnline: true, - }) - bSpec = pickManifest( - packument, - bTargetVersion, - { ...npm.flatOptions } - ).version - } + if (!node || !node.name || !node.package || !node.package.version) { + return [ + `${spec.name}@${spec.fetchSpec}`, + specSelf(), + ] + } - return [ - `${spec.name}@${aSpec}`, - `${spec.name}@${bSpec}`, - ] - } else if (spec.type === 'directory') { - return [ - `file:${spec.fetchSpec}`, - specSelf(), - ] - } else { - throw new Error( - 'Spec type not supported.\n\n' + - `Usage:\n${usage}` - ) - } -} + const tryRootNodeSpec = () => + (actualTree && actualTree.edgesOut.get(spec.name) || {}).spec -const convertVersionsToSpecs = async ([a, b]) => { - const semverA = semver.validRange(a) - const semverB = semver.validRange(b) + const tryAnySpec = () => { + for (const edge of node.edgesIn) + return edge.spec + } - // both specs are semver versions, assume current project dir name - if (semverA && semverB) { - let pkgName - try { - pkgName = await readLocalPkg() - } catch (e) { - npmlog.verbose('diff', 'could not read project dir package.json') - } + const aSpec = `file:${node.realpath}` + + // finds what version of the package to compare against, if a exact + // version or tag was passed than it should use that, otherwise + // work from the top of the arborist tree to find the original semver + // range declared in the package that depends on the package. + let bSpec + if (spec.rawSpec) + bSpec = spec.rawSpec + else { + const bTargetVersion = + tryRootNodeSpec() + || tryAnySpec() + + // figure out what to compare against, + // follows same logic to npm outdated "Wanted" results + const packument = await pacote.packument(spec, { + ...this.npm.flatOptions, + preferOnline: true, + }) + bSpec = pickManifest( + packument, + bTargetVersion, + { ...this.npm.flatOptions } + ).version + } - if (!pkgName) { + return [ + `${spec.name}@${aSpec}`, + `${spec.name}@${bSpec}`, + ] + } else if (spec.type === 'directory') { + return [ + `file:${spec.fetchSpec}`, + specSelf(), + ] + } else { throw new Error( - 'Needs to be run from a project dir in order to diff two versions.\n\n' + - `Usage:\n${usage}` + 'Spec type not supported.\n\n' + + `Usage:\n${this.usage}` ) } - return [`${pkgName}@${a}`, `${pkgName}@${b}`] } - // otherwise uses the name from the other arg to - // figure out the spec.name of what to compare - if (!semverA && semverB) - return [a, `${npa(a).name}@${b}`] + async convertVersionsToSpecs ([a, b]) { + const semverA = semver.validRange(a) + const semverB = semver.validRange(b) + + // both specs are semver versions, assume current project dir name + if (semverA && semverB) { + let pkgName + try { + pkgName = await readLocalPkg(this.npm) + } catch (e) { + npmlog.verbose('diff', 'could not read project dir package.json') + } + + if (!pkgName) { + throw new Error( + 'Needs to be run from a project dir in order to diff two versions.\n\n' + + `Usage:\n${this.usage}` + ) + } + return [`${pkgName}@${a}`, `${pkgName}@${b}`] + } - if (semverA && !semverB) - return [`${npa(b).name}@${a}`, b] + // otherwise uses the name from the other arg to + // figure out the spec.name of what to compare + if (!semverA && semverB) + return [a, `${npa(a).name}@${b}`] - // no valid semver ranges used - return [a, b] -} + if (semverA && !semverB) + return [`${npa(b).name}@${a}`, b] -const findVersionsByPackageName = async (specs) => { - let actualTree - try { - const opts = { - ...npm.flatOptions, - path: where(), - } - const arb = new Arborist(opts) - actualTree = await arb.loadActual(opts) - } catch (e) { - npmlog.verbose('diff', 'failed to load actual install tree') + // no valid semver ranges used + return [a, b] } - return specs.map(i => { - const spec = npa(i) - if (spec.rawSpec) - return i + async findVersionsByPackageName (specs) { + let actualTree + try { + const opts = { + ...this.npm.flatOptions, + path: this.where, + } + const arb = new Arborist(opts) + actualTree = await arb.loadActual(opts) + } catch (e) { + npmlog.verbose('diff', 'failed to load actual install tree') + } + + return specs.map(i => { + const spec = npa(i) + if (spec.rawSpec) + return i - const node = actualTree - && actualTree.inventory.query('name', spec.name) - .values().next().value + const node = actualTree + && actualTree.inventory.query('name', spec.name) + .values().next().value - const res = !node || !node.package || !node.package.version - ? spec.fetchSpec - : `file:${node.realpath}` + const res = !node || !node.package || !node.package.version + ? spec.fetchSpec + : `file:${node.realpath}` - return `${spec.name}@${res}` - }) + return `${spec.name}@${res}` + }) + } } -module.exports = Object.assign(cmd, { usage }) +module.exports = Diff diff --git a/deps/npm/lib/dist-tag.js b/deps/npm/lib/dist-tag.js index e958bb7544..171a88c527 100644 --- a/deps/npm/lib/dist-tag.js +++ b/deps/npm/lib/dist-tag.js @@ -3,69 +3,77 @@ const npa = require('npm-package-arg') const regFetch = require('npm-registry-fetch') const semver = require('semver') -const npm = require('./npm.js') const output = require('./utils/output.js') const otplease = require('./utils/otplease.js') const readLocalPkgName = require('./utils/read-local-package.js') const usageUtil = require('./utils/usage.js') -const usage = usageUtil( - 'dist-tag', - 'npm dist-tag add <pkg>@<version> [<tag>]' + - '\nnpm dist-tag rm <pkg> <tag>' + - '\nnpm dist-tag ls [<pkg>]' -) - -const completion = async (opts) => { - const argv = opts.conf.argv.remain - if (argv.length === 2) - return ['add', 'rm', 'ls'] - - switch (argv[2]) { - default: - return [] +class DistTag { + constructor (npm) { + this.npm = npm } -} -const cmd = (args, cb) => distTag(args).then(() => cb()).catch(cb) + get usage () { + return usageUtil( + 'dist-tag', + 'npm dist-tag add <pkg>@<version> [<tag>]' + + '\nnpm dist-tag rm <pkg> <tag>' + + '\nnpm dist-tag ls [<pkg>]' + ) + } -const distTag = async ([cmdName, pkg, tag]) => { - const opts = npm.flatOptions - const has = (items) => new Set(items).has(cmdName) + async completion (opts) { + const argv = opts.conf.argv.remain + if (argv.length === 2) + return ['add', 'rm', 'ls'] - if (has(['add', 'a', 'set', 's'])) - return add(pkg, tag, opts) + switch (argv[2]) { + default: + return [] + } + } - if (has(['rm', 'r', 'del', 'd', 'remove'])) - return remove(pkg, tag, opts) + exec (args, cb) { + this.distTag(args).then(() => cb()).catch(cb) + } - if (has(['ls', 'l', 'sl', 'list'])) - return list(pkg, opts) + async distTag ([cmdName, pkg, tag]) { + const opts = this.npm.flatOptions + const has = (items) => new Set(items).has(cmdName) - if (!pkg) { - // when only using the pkg name the default behavior - // should be listing the existing tags - return list(cmdName, opts) - } else - throw usage -} + if (has(['add', 'a', 'set', 's'])) + return this.add(pkg, tag, opts) -function add (spec, tag, opts) { - spec = npa(spec || '') - const version = spec.rawSpec - const defaultTag = tag || opts.defaultTag + if (has(['rm', 'r', 'del', 'd', 'remove'])) + return this.remove(pkg, tag, opts) - log.verbose('dist-tag add', defaultTag, 'to', spec.name + '@' + version) + if (has(['ls', 'l', 'sl', 'list'])) + return this.list(pkg, opts) - if (!spec.name || !version || !defaultTag) - throw usage + if (!pkg) { + // when only using the pkg name the default behavior + // should be listing the existing tags + return this.list(cmdName, opts) + } else + throw this.usage + } + + async add (spec, tag, opts) { + spec = npa(spec || '') + const version = spec.rawSpec + const defaultTag = tag || opts.defaultTag + + log.verbose('dist-tag add', defaultTag, 'to', spec.name + '@' + version) - const t = defaultTag.trim() + if (!spec.name || !version || !defaultTag) + throw this.usage - if (semver.validRange(t)) - throw new Error('Tag name must not be a valid SemVer range: ' + t) + const t = defaultTag.trim() - return fetchTags(spec, opts).then(tags => { + if (semver.validRange(t)) + throw new Error('Tag name must not be a valid SemVer range: ' + t) + + const tags = await this.fetchTags(spec, opts) if (tags[t] === version) { log.warn('dist-tag add', t, 'is already set to version', version) return @@ -82,20 +90,18 @@ function add (spec, tag, opts) { }, spec, } - return otplease(reqOpts, reqOpts => regFetch(url, reqOpts)).then(() => { - output(`+${t}: ${spec.name}@${version}`) - }) - }) -} + await otplease(reqOpts, reqOpts => regFetch(url, reqOpts)) + output(`+${t}: ${spec.name}@${version}`) + } -function remove (spec, tag, opts) { - spec = npa(spec || '') - log.verbose('dist-tag del', tag, 'from', spec.name) + async remove (spec, tag, opts) { + spec = npa(spec || '') + log.verbose('dist-tag del', tag, 'from', spec.name) - if (!spec.name) - throw usage + if (!spec.name) + throw this.usage - return fetchTags(spec, opts).then(tags => { + const tags = await this.fetchTags(spec, opts) if (!tags[tag]) { log.info('dist-tag del', tag, 'is not a dist-tag on', spec.name) throw new Error(tag + ' is not a dist-tag on ' + spec.name) @@ -109,50 +115,43 @@ function remove (spec, tag, opts) { method: 'DELETE', spec, } - return otplease(reqOpts, reqOpts => regFetch(url, reqOpts)).then(() => { - output(`-${tag}: ${spec.name}@${version}`) - }) - }) -} + await otplease(reqOpts, reqOpts => regFetch(url, reqOpts)) + output(`-${tag}: ${spec.name}@${version}`) + } -function list (spec, opts) { - if (!spec) { - return readLocalPkgName().then(pkg => { + async list (spec, opts) { + if (!spec) { + const pkg = await readLocalPkgName(this.npm) if (!pkg) - throw usage + throw this.usage - return list(pkg, opts) - }) + return this.list(pkg, opts) + } + spec = npa(spec) + + try { + const tags = await this.fetchTags(spec, opts) + const msg = + Object.keys(tags).map(k => `${k}: ${tags[k]}`).sort().join('\n') + output(msg) + return tags + } catch (err) { + log.error('dist-tag ls', "Couldn't get dist-tag data for", spec) + throw err + } } - spec = npa(spec) - - return fetchTags(spec, opts).then(tags => { - const msg = - Object.keys(tags).map(k => `${k}: ${tags[k]}`).sort().join('\n') - output(msg) - return tags - }, err => { - log.error('dist-tag ls', "Couldn't get dist-tag data for", spec) - throw err - }) -} -function fetchTags (spec, opts) { - return regFetch.json( - `/-/package/${spec.escapedName}/dist-tags`, - { - ...opts, - 'prefer-online': true, - spec, - } - ).then(data => { + async fetchTags (spec, opts) { + const data = await regFetch.json( + `/-/package/${spec.escapedName}/dist-tags`, + { ...opts, 'prefer-online': true, spec } + ) if (data && typeof data === 'object') delete data._etag if (!data || !Object.keys(data).length) throw new Error('No dist-tags found for ' + spec.name) return data - }) + } } - -module.exports = Object.assign(cmd, { usage, completion }) +module.exports = DistTag diff --git a/deps/npm/lib/docs.js b/deps/npm/lib/docs.js index fa0adb3d37..2dad7a26db 100644 --- a/deps/npm/lib/docs.js +++ b/deps/npm/lib/docs.js @@ -1,39 +1,47 @@ const log = require('npmlog') const pacote = require('pacote') -const { promisify } = require('util') -const openUrl = promisify(require('./utils/open-url.js')) +const openUrl = require('./utils/open-url.js') const usageUtil = require('./utils/usage.js') -const npm = require('./npm.js') const hostedFromMani = require('./utils/hosted-git-info-from-manifest.js') -const usage = usageUtil('docs', 'npm docs [<pkgname> [<pkgname> ...]]') - -const cmd = (args, cb) => docs(args).then(() => cb()).catch(cb) - -const docs = async args => { - if (!args || !args.length) - args = ['.'] - - await Promise.all(args.map(pkg => getDocs(pkg))) -} - -const getDocsUrl = mani => { - if (mani.homepage) - return mani.homepage - - const info = hostedFromMani(mani) - if (info) - return info.docs() - - return 'https://www.npmjs.com/package/' + mani.name +class Docs { + constructor (npm) { + this.npm = npm + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil('docs', 'npm docs [<pkgname> [<pkgname> ...]]') + } + + exec (args, cb) { + this.docs(args).then(() => cb()).catch(cb) + } + + async docs (args) { + if (!args || !args.length) + args = ['.'] + + await Promise.all(args.map(pkg => this.getDocs(pkg))) + } + + async getDocs (pkg) { + const opts = { ...this.npm.flatOptions, fullMetadata: true } + const mani = await pacote.manifest(pkg, opts) + const url = this.getDocsUrl(mani) + log.silly('docs', 'url', url) + await openUrl(this.npm, url, `${mani.name} docs available at the following URL`) + } + + getDocsUrl (mani) { + if (mani.homepage) + return mani.homepage + + const info = hostedFromMani(mani) + if (info) + return info.docs() + + return 'https://www.npmjs.com/package/' + mani.name + } } - -const getDocs = async pkg => { - const opts = { ...npm.flatOptions, fullMetadata: true } - const mani = await pacote.manifest(pkg, opts) - const url = getDocsUrl(mani) - log.silly('docs', 'url', url) - await openUrl(url, `${mani.name} docs available at the following URL`) -} - -module.exports = Object.assign(cmd, { usage }) +module.exports = Docs diff --git a/deps/npm/lib/doctor.js b/deps/npm/lib/doctor.js index e149aec128..81860004e3 100644 --- a/deps/npm/lib/doctor.js +++ b/deps/npm/lib/doctor.js @@ -1,79 +1,22 @@ -const npm = require('./npm.js') - +const cacache = require('cacache') const chalk = require('chalk') -const ansiTrim = require('./utils/ansi-trim.js') +const fs = require('fs') +const fetch = require('make-fetch-happen') const table = require('text-table') -const output = require('./utils/output.js') -const usageUtil = require('./utils/usage.js') -const usage = usageUtil('doctor', 'npm doctor') -const { resolve } = require('path') - -const ping = require('./utils/ping.js') -const checkPing = async () => { - const tracker = npm.log.newItem('checkPing', 1) - tracker.info('checkPing', 'Pinging registry') - try { - await ping(npm.flatOptions) - return '' - } catch (er) { - if (/^E\d{3}$/.test(er.code || '')) - throw er.code.substr(1) + ' ' + er.message - else - throw er.message - } finally { - tracker.finish() - } -} - +const which = require('which') const pacote = require('pacote') -const getLatestNpmVersion = async () => { - const tracker = npm.log.newItem('getLatestNpmVersion', 1) - tracker.info('getLatestNpmVersion', 'Getting npm package information') - try { - const latest = (await pacote.manifest('npm@latest', npm.flatOptions)).version - if (semver.gte(npm.version, latest)) - return `current: v${npm.version}, latest: v${latest}` - else - throw `Use npm v${latest}` - } finally { - tracker.finish() - } -} - +const { resolve } = require('path') const semver = require('semver') -const fetch = require('make-fetch-happen') -const getLatestNodejsVersion = async () => { - // XXX get the latest in the current major as well - const current = process.version - const currentRange = `^${current}` - const url = 'https://nodejs.org/dist/index.json' - const tracker = npm.log.newItem('getLatestNodejsVersion', 1) - tracker.info('getLatestNodejsVersion', 'Getting Node.js release information') - try { - const res = await fetch(url, { method: 'GET', ...npm.flatOptions }) - const data = await res.json() - let maxCurrent = '0.0.0' - let maxLTS = '0.0.0' - for (const { lts, version } of data) { - if (lts && semver.gt(version, maxLTS)) - maxLTS = version - - if (semver.satisfies(version, currentRange) && - semver.gt(version, maxCurrent)) - maxCurrent = version - } - const recommended = semver.gt(maxCurrent, maxLTS) ? maxCurrent : maxLTS - if (semver.gte(process.version, recommended)) - return `current: ${current}, recommended: ${recommended}` - else - throw `Use node ${recommended} (current: ${current})` - } finally { - tracker.finish() - } -} - const { promisify } = require('util') -const fs = require('fs') +const ansiTrim = require('./utils/ansi-trim.js') +const isWindows = require('./utils/is-windows.js') +const output = require('./utils/output.js') +const ping = require('./utils/ping.js') +const usageUtil = require('./utils/usage.js') +const { defaults: { registry: defaultRegistry } } = require('./utils/config.js') +const lstat = promisify(fs.lstat) +const readdir = promisify(fs.readdir) +const access = promisify(fs.access) const { R_OK, W_OK, X_OK } = fs.constants const maskLabel = mask => { const label = [] @@ -88,200 +31,268 @@ const maskLabel = mask => { return label.join(', ') } -const lstat = promisify(fs.lstat) -const readdir = promisify(fs.readdir) -const access = promisify(fs.access) -const isWindows = require('./utils/is-windows.js') -const checkFilesPermission = async (root, shouldOwn, mask = null) => { - if (mask === null) - mask = shouldOwn ? R_OK | W_OK : R_OK - - let ok = true - - const tracker = npm.log.newItem(root, 1) - - try { - const uid = process.getuid() - const gid = process.getgid() - const files = new Set([root]) - for (const f of files) { - tracker.silly('checkFilesPermission', f.substr(root.length + 1)) - const st = await lstat(f) - .catch(er => { - ok = false - tracker.warn('checkFilesPermission', 'error getting info for ' + f) - }) - tracker.completeWork(1) - - if (!st) - continue +class Doctor { + constructor (npm) { + this.npm = npm + } - if (shouldOwn && (uid !== st.uid || gid !== st.gid)) { - tracker.warn('checkFilesPermission', 'should be owner of ' + f) - ok = false - } + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil('doctor', 'npm doctor') + } - if (!st.isDirectory() && !st.isFile()) - continue + exec (args, cb) { + this.doctor(args).then(() => cb()).catch(cb) + } + async doctor (args) { + this.npm.log.info('Running checkup') + + // each message is [title, ok, message] + const messages = [] + + const actions = [ + ['npm ping', 'checkPing', []], + ['npm -v', 'getLatestNpmVersion', []], + ['node -v', 'getLatestNodejsVersion', []], + ['npm config get registry', 'checkNpmRegistry', []], + ['which git', 'getGitPath', []], + ...(isWindows ? [] : [ + ['Perms check on cached files', 'checkFilesPermission', [this.npm.cache, true, R_OK]], + ['Perms check on local node_modules', 'checkFilesPermission', [this.npm.localDir, true]], + ['Perms check on global node_modules', 'checkFilesPermission', [this.npm.globalDir, false]], + ['Perms check on local bin folder', 'checkFilesPermission', [this.npm.localBin, false, R_OK | W_OK | X_OK]], + ['Perms check on global bin folder', 'checkFilesPermission', [this.npm.globalBin, false, X_OK]], + ]), + ['Verify cache contents', 'verifyCachedFiles', [this.npm.flatOptions.cache]], + // TODO: + // - ensure arborist.loadActual() runs without errors and no invalid edges + // - ensure package-lock.json matches loadActual() + // - verify loadActual without hidden lock file matches hidden lockfile + // - verify all local packages have bins linked + ] + + // Do the actual work + for (const [msg, fn, args] of actions) { + const line = [msg] try { - await access(f, mask) + line.push(true, await this[fn](...args)) } catch (er) { - ok = false - const msg = `Missing permissions on ${f} (expect: ${maskLabel(mask)})` - tracker.error('checkFilesPermission', msg) - continue + line.push(false, er) } + messages.push(line) + } - if (st.isDirectory()) { - const entries = await readdir(f) - .catch(er => { - ok = false - tracker.warn('checkFilesPermission', 'error reading directory ' + f) - return [] - }) - for (const entry of entries) - files.add(resolve(f, entry)) + const outHead = ['Check', 'Value', 'Recommendation/Notes'] + .map(!this.npm.color ? h => h : h => chalk.underline(h)) + let allOk = true + const outBody = messages.map(!this.npm.color + ? item => { + allOk = allOk && item[1] + item[1] = item[1] ? 'ok' : 'not ok' + item[2] = String(item[2]) + return item } + : item => { + allOk = allOk && item[1] + if (!item[1]) { + item[0] = chalk.red(item[0]) + item[2] = chalk.magenta(String(item[2])) + } + item[1] = item[1] ? chalk.green('ok') : chalk.red('not ok') + return item + }) + const outTable = [outHead, ...outBody] + const tableOpts = { + stringLength: s => ansiTrim(s).length, } - } finally { - tracker.finish() - if (!ok) { - throw `Check the permissions of files in ${root}` + - (shouldOwn ? ' (should be owned by current user)' : '') - } else + + const silent = this.npm.log.levels[this.npm.log.level] > + this.npm.log.levels.error + if (!silent) { + output(table(outTable, tableOpts)) + if (!allOk) + console.error('') + } + if (!allOk) + throw 'Some problems found. See above for recommendations.' + } + + async checkPing () { + const tracker = this.npm.log.newItem('checkPing', 1) + tracker.info('checkPing', 'Pinging registry') + try { + await ping(this.npm.flatOptions) return '' + } catch (er) { + if (/^E\d{3}$/.test(er.code || '')) + throw er.code.substr(1) + ' ' + er.message + else + throw er.message + } finally { + tracker.finish() + } } -} -const which = require('which') -const getGitPath = async () => { - const tracker = npm.log.newItem('getGitPath', 1) - tracker.info('getGitPath', 'Finding git in your PATH') - try { - return await which('git').catch(er => { - tracker.warn(er) - throw "Install git and ensure it's in your PATH." - }) - } finally { - tracker.finish() + async getLatestNpmVersion () { + const tracker = this.npm.log.newItem('getLatestNpmVersion', 1) + tracker.info('getLatestNpmVersion', 'Getting npm package information') + try { + const latest = (await pacote.manifest('npm@latest', this.npm.flatOptions)).version + if (semver.gte(this.npm.version, latest)) + return `current: v${this.npm.version}, latest: v${latest}` + else + throw `Use npm v${latest}` + } finally { + tracker.finish() + } } -} -const cacache = require('cacache') -const verifyCachedFiles = async () => { - const tracker = npm.log.newItem('verifyCachedFiles', 1) - tracker.info('verifyCachedFiles', 'Verifying the npm cache') - try { - const stats = await cacache.verify(npm.flatOptions.cache) - const { - badContentCount, - reclaimedCount, - missingContent, - reclaimedSize, - } = stats - if (badContentCount || reclaimedCount || missingContent) { - if (badContentCount) - tracker.warn('verifyCachedFiles', `Corrupted content removed: ${badContentCount}`) - - if (reclaimedCount) - tracker.warn('verifyCachedFiles', `Content garbage-collected: ${reclaimedCount} (${reclaimedSize} bytes)`) - - if (missingContent) - tracker.warn('verifyCachedFiles', `Missing content: ${missingContent}`) - - tracker.warn('verifyCachedFiles', 'Cache issues have been fixed') + async getLatestNodejsVersion () { + // XXX get the latest in the current major as well + const current = process.version + const currentRange = `^${current}` + const url = 'https://nodejs.org/dist/index.json' + const tracker = this.npm.log.newItem('getLatestNodejsVersion', 1) + tracker.info('getLatestNodejsVersion', 'Getting Node.js release information') + try { + const res = await fetch(url, { method: 'GET', ...this.npm.flatOptions }) + const data = await res.json() + let maxCurrent = '0.0.0' + let maxLTS = '0.0.0' + for (const { lts, version } of data) { + if (lts && semver.gt(version, maxLTS)) + maxLTS = version + + if (semver.satisfies(version, currentRange) && + semver.gt(version, maxCurrent)) + maxCurrent = version + } + const recommended = semver.gt(maxCurrent, maxLTS) ? maxCurrent : maxLTS + if (semver.gte(process.version, recommended)) + return `current: ${current}, recommended: ${recommended}` + else + throw `Use node ${recommended} (current: ${current})` + } finally { + tracker.finish() } - tracker.info('verifyCachedFiles', `Verification complete. Stats: ${ - JSON.stringify(stats, null, 2) - }`) - return `verified ${stats.verifiedContent} tarballs` - } finally { - tracker.finish() } -} -const { defaults: { registry: defaultRegistry } } = require('./utils/config.js') -const checkNpmRegistry = async () => { - if (npm.flatOptions.registry !== defaultRegistry) - throw `Try \`npm config set registry=${defaultRegistry}\`` - else - return `using default registry (${defaultRegistry})` -} + async checkFilesPermission (root, shouldOwn, mask = null) { + if (mask === null) + mask = shouldOwn ? R_OK | W_OK : R_OK + + let ok = true + + const tracker = this.npm.log.newItem(root, 1) -const cmd = (args, cb) => doctor(args).then(() => cb()).catch(cb) - -const doctor = async args => { - npm.log.info('Running checkup') - - // each message is [title, ok, message] - const messages = [] - - const actions = [ - ['npm ping', checkPing, []], - ['npm -v', getLatestNpmVersion, []], - ['node -v', getLatestNodejsVersion, []], - ['npm config get registry', checkNpmRegistry, []], - ['which git', getGitPath, []], - ...(isWindows ? [] : [ - ['Perms check on cached files', checkFilesPermission, [npm.cache, true, R_OK]], - ['Perms check on local node_modules', checkFilesPermission, [npm.localDir, true]], - ['Perms check on global node_modules', checkFilesPermission, [npm.globalDir, false]], - ['Perms check on local bin folder', checkFilesPermission, [npm.localBin, false, R_OK | W_OK | X_OK]], - ['Perms check on global bin folder', checkFilesPermission, [npm.globalBin, false, X_OK]], - ]), - ['Verify cache contents', verifyCachedFiles, [npm.flatOptions.cache]], - // TODO: - // - ensure arborist.loadActual() runs without errors and no invalid edges - // - ensure package-lock.json matches loadActual() - // - verify loadActual without hidden lock file matches hidden lockfile - // - verify all local packages have bins linked - ] - - for (const [msg, fn, args] of actions) { - const line = [msg] try { - line.push(true, await fn(...args)) - } catch (er) { - line.push(false, er) + const uid = process.getuid() + const gid = process.getgid() + const files = new Set([root]) + for (const f of files) { + tracker.silly('checkFilesPermission', f.substr(root.length + 1)) + const st = await lstat(f) + .catch(er => { + ok = false + tracker.warn('checkFilesPermission', 'error getting info for ' + f) + }) + + tracker.completeWork(1) + + if (!st) + continue + + if (shouldOwn && (uid !== st.uid || gid !== st.gid)) { + tracker.warn('checkFilesPermission', 'should be owner of ' + f) + ok = false + } + + if (!st.isDirectory() && !st.isFile()) + continue + + try { + await access(f, mask) + } catch (er) { + ok = false + const msg = `Missing permissions on ${f} (expect: ${maskLabel(mask)})` + tracker.error('checkFilesPermission', msg) + continue + } + + if (st.isDirectory()) { + const entries = await readdir(f) + .catch(er => { + ok = false + tracker.warn('checkFilesPermission', 'error reading directory ' + f) + return [] + }) + for (const entry of entries) + files.add(resolve(f, entry)) + } + } + } finally { + tracker.finish() + if (!ok) { + throw `Check the permissions of files in ${root}` + + (shouldOwn ? ' (should be owned by current user)' : '') + } else + return '' } - messages.push(line) } - const silent = npm.log.levels[npm.log.level] > npm.log.levels.error - - const outHead = ['Check', 'Value', 'Recommendation/Notes'] - .map(!npm.color ? h => h : h => chalk.underline(h)) - let allOk = true - const outBody = messages.map(!npm.color - ? item => { - allOk = allOk && item[1] - item[1] = item[1] ? 'ok' : 'not ok' - item[2] = String(item[2]) - return item + async getGitPath () { + const tracker = this.npm.log.newItem('getGitPath', 1) + tracker.info('getGitPath', 'Finding git in your PATH') + try { + return await which('git').catch(er => { + tracker.warn(er) + throw "Install git and ensure it's in your PATH." + }) + } finally { + tracker.finish() } - : item => { - allOk = allOk && item[1] - if (!item[1]) { - item[0] = chalk.red(item[0]) - item[2] = chalk.magenta(String(item[2])) + } + + async verifyCachedFiles () { + const tracker = this.npm.log.newItem('verifyCachedFiles', 1) + tracker.info('verifyCachedFiles', 'Verifying the npm cache') + try { + const stats = await cacache.verify(this.npm.flatOptions.cache) + const { + badContentCount, + reclaimedCount, + missingContent, + reclaimedSize, + } = stats + if (badContentCount || reclaimedCount || missingContent) { + if (badContentCount) + tracker.warn('verifyCachedFiles', `Corrupted content removed: ${badContentCount}`) + + if (reclaimedCount) + tracker.warn('verifyCachedFiles', `Content garbage-collected: ${reclaimedCount} (${reclaimedSize} bytes)`) + + if (missingContent) + tracker.warn('verifyCachedFiles', `Missing content: ${missingContent}`) + + tracker.warn('verifyCachedFiles', 'Cache issues have been fixed') } - item[1] = item[1] ? chalk.green('ok') : chalk.red('not ok') - return item - }) - const outTable = [outHead, ...outBody] - const tableOpts = { - stringLength: s => ansiTrim(s).length, + tracker.info('verifyCachedFiles', `Verification complete. Stats: ${ + JSON.stringify(stats, null, 2) + }`) + return `verified ${stats.verifiedContent} tarballs` + } finally { + tracker.finish() + } } - if (!silent) { - output(table(outTable, tableOpts)) - if (!allOk) - console.error('') + async checkNpmRegistry () { + if (this.npm.flatOptions.registry !== defaultRegistry) + throw `Try \`npm config set registry=${defaultRegistry}\`` + else + return `using default registry (${defaultRegistry})` } - if (!allOk) - throw 'Some problems found. See above for recommendations.' } -module.exports = Object.assign(cmd, { usage }) +module.exports = Doctor diff --git a/deps/npm/lib/edit.js b/deps/npm/lib/edit.js index 9ae6349262..a7dbb38205 100644 --- a/deps/npm/lib/edit.js +++ b/deps/npm/lib/edit.js @@ -4,33 +4,55 @@ const { resolve } = require('path') const fs = require('graceful-fs') const { spawn } = require('child_process') -const npm = require('./npm.js') const usageUtil = require('./utils/usage.js') const splitPackageNames = require('./utils/split-package-names.js') - -const usage = usageUtil('edit', 'npm edit <pkg>[/<subpkg>...]') const completion = require('./utils/completion/installed-shallow.js') -function edit (args, cb) { - if (args.length !== 1) - return cb(usage) - - const path = splitPackageNames(args[0]) - const dir = resolve(npm.dir, path) - - fs.lstat(dir, (err) => { - if (err) - return cb(err) - - const [bin, ...args] = npm.config.get('editor').split(/\s+/) - const editor = spawn(bin, [...args, dir], { stdio: 'inherit' }) - editor.on('exit', (code) => { - if (code) - return cb(new Error(`editor process exited with code: ${code}`)) - - npm.commands.rebuild([dir], cb) +class Edit { + constructor (npm) { + this.npm = npm + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil('edit', 'npm edit <pkg>[/<subpkg>...]') + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ + async completion (opts) { + return completion(this.npm, opts) + } + + exec (args, cb) { + this.edit(args).then(() => cb()).catch(cb) + } + + async edit (args) { + if (args.length !== 1) + throw new Error(this.usage) + + const path = splitPackageNames(args[0]) + const dir = resolve(this.npm.dir, path) + + // graceful-fs does not promisify + await new Promise((resolve, reject) => { + fs.lstat(dir, (err) => { + if (err) + return reject(err) + const [bin, ...args] = this.npm.config.get('editor').split(/\s+/) + const editor = spawn(bin, [...args, dir], { stdio: 'inherit' }) + editor.on('exit', (code) => { + if (code) + return reject(new Error(`editor process exited with code: ${code}`)) + this.npm.commands.rebuild([dir], (err) => { + if (err) + return reject(err) + + resolve() + }) + }) + }) }) - }) + } } - -module.exports = Object.assign(edit, { completion, usage }) +module.exports = Edit diff --git a/deps/npm/lib/exec.js b/deps/npm/lib/exec.js index dab65c23a3..d1db491285 100644 --- a/deps/npm/lib/exec.js +++ b/deps/npm/lib/exec.js @@ -1,28 +1,18 @@ -const npm = require('./npm.js') const output = require('./utils/output.js') const usageUtil = require('./utils/usage.js') -const usage = usageUtil('exec', - 'Run a command from a local or remote npm package.\n\n' + - - 'npm exec -- <pkg>[@<version>] [args...]\n' + - 'npm exec --package=<pkg>[@<version>] -- <cmd> [args...]\n' + - 'npm exec -c \'<cmd> [args...]\'\n' + - 'npm exec --package=foo -c \'<cmd> [args...]\'\n' + - '\n' + - 'npx <pkg>[@<specifier>] [args...]\n' + - 'npx -p <pkg>[@<specifier>] <cmd> [args...]\n' + - 'npx -c \'<cmd> [args...]\'\n' + - 'npx -p <pkg>[@<specifier>] -c \'<cmd> [args...]\'' + - '\n' + - 'Run without --call or positional args to open interactive subshell\n', - - '\n--package=<pkg> (may be specified multiple times)\n' + - '-p is a shorthand for --package only when using npx executable\n' + - '-c <cmd> --call=<cmd> (may not be mixed with positional arguments)' -) - const { promisify } = require('util') const read = promisify(require('read')) +const mkdirp = require('mkdirp-infer-owner') +const readPackageJson = require('read-package-json-fast') +const Arborist = require('@npmcli/arborist') +const runScript = require('@npmcli/run-script') +const { resolve, delimiter } = require('path') +const ciDetect = require('@npmcli/ci-detect') +const crypto = require('crypto') +const pacote = require('pacote') +const npa = require('npm-package-arg') +const fileExists = require('./utils/file-exists.js') +const PATH = require('./utils/path.js') // it's like this: // @@ -49,237 +39,258 @@ const read = promisify(require('read')) // runScript({ pkg, event: 'npx', ... }) // process.env.npm_lifecycle_event = 'npx' -const mkdirp = require('mkdirp-infer-owner') -const readPackageJson = require('read-package-json-fast') -const Arborist = require('@npmcli/arborist') -const runScript = require('@npmcli/run-script') -const { resolve, delimiter } = require('path') -const ciDetect = require('@npmcli/ci-detect') -const crypto = require('crypto') -const pacote = require('pacote') -const npa = require('npm-package-arg') -const fileExists = require('./utils/file-exists.js') -const PATH = require('./utils/path.js') - -const cmd = (args, cb) => exec(args).then(() => cb()).catch(cb) - -const run = async ({ args, call, pathArr, shell }) => { - // turn list of args into command string - const script = call || args.shift() || shell - - // do the fakey runScript dance - // still should work if no package.json in cwd - const realPkg = await readPackageJson(`${npm.localPrefix}/package.json`) - .catch(() => ({})) - const pkg = { - ...realPkg, - scripts: { - ...(realPkg.scripts || {}), - npx: script, - }, +class Exec { + constructor (npm) { + this.npm = npm } - npm.log.disableProgress() - try { - if (script === shell) { - if (process.stdin.isTTY) { - if (ciDetect()) - return npm.log.warn('exec', 'Interactive mode disabled in CI environment') - output(`\nEntering npm script environment\nType 'exit' or ^D when finished\n`) - } - } - return await runScript({ - ...npm.flatOptions, - pkg, - banner: false, - // we always run in cwd, not --prefix - path: process.cwd(), - stdioString: true, - event: 'npx', - args, - env: { - PATH: pathArr.join(delimiter), - }, - stdio: 'inherit', - }) - } finally { - npm.log.enableProgress() + get usage () { + return usageUtil('exec', + 'Run a command from a local or remote npm package.\n\n' + + + 'npm exec -- <pkg>[@<version>] [args...]\n' + + 'npm exec --package=<pkg>[@<version>] -- <cmd> [args...]\n' + + 'npm exec -c \'<cmd> [args...]\'\n' + + 'npm exec --package=foo -c \'<cmd> [args...]\'\n' + + '\n' + + 'npx <pkg>[@<specifier>] [args...]\n' + + 'npx -p <pkg>[@<specifier>] <cmd> [args...]\n' + + 'npx -c \'<cmd> [args...]\'\n' + + 'npx -p <pkg>[@<specifier>] -c \'<cmd> [args...]\'' + + '\n' + + 'Run without --call or positional args to open interactive subshell\n', + + '\n--package=<pkg> (may be specified multiple times)\n' + + '-p is a shorthand for --package only when using npx executable\n' + + '-c <cmd> --call=<cmd> (may not be mixed with positional arguments)' + ) } -} - -const exec = async args => { - const { package: packages, call, shell } = npm.flatOptions - if (call && args.length) - throw usage + exec (args, cb) { + this._exec(args).then(() => cb()).catch(cb) + } - const pathArr = [...PATH] + // 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 - // nothing to maybe install, skip the arborist dance - if (!call && !args.length && !packages.length) { - return await run({ - args, - call, - shell, - pathArr, - }) - } + if (call && args.length) + throw this.usage - const needPackageCommandSwap = args.length && !packages.length - // if there's an argument and no package has been explicitly asked for - // check the local and global bin paths for a binary named the same as - // the argument and run it if it exists, otherwise fall through to - // the behavior of treating the single argument as a package name - if (needPackageCommandSwap) { - let binExists = false - if (await fileExists(`${npm.localBin}/${args[0]}`)) { - pathArr.unshift(npm.localBin) - binExists = true - } else if (await fileExists(`${npm.globalBin}/${args[0]}`)) { - pathArr.unshift(npm.globalBin) - binExists = true - } + const pathArr = [...PATH] - if (binExists) { - return await run({ + // nothing to maybe install, skip the arborist dance + if (!call && !args.length && !packages.length) { + return await this.run({ args, call, - pathArr, shell, + pathArr, }) } - packages.push(args[0]) - } + const needPackageCommandSwap = args.length && !packages.length + // if there's an argument and no package has been explicitly asked for + // check the local and global bin paths for a binary named the same as + // the argument and run it if it exists, otherwise fall through to + // the behavior of treating the single argument as a package name + if (needPackageCommandSwap) { + let binExists = false + if (await fileExists(`${this.npm.localBin}/${args[0]}`)) { + pathArr.unshift(this.npm.localBin) + binExists = true + } else if (await fileExists(`${this.npm.globalBin}/${args[0]}`)) { + pathArr.unshift(this.npm.globalBin) + binExists = true + } - // If we do `npm exec foo`, and have a `foo` locally, then we'll - // always use that, so we don't really need to fetch the manifest. - // So: run npa on each packages entry, and if it is a name with a - // rawSpec==='', then try to readPackageJson at - // node_modules/${name}/package.json, and only pacote fetch if - // that fails. - const manis = await Promise.all(packages.map(async p => { - const spec = npa(p, npm.localPrefix) - if (spec.type === 'tag' && spec.rawSpec === '') { - // fall through to the pacote.manifest() approach - try { - const pj = resolve(npm.localPrefix, 'node_modules', spec.name) - return await readPackageJson(pj) - } catch (er) {} + if (binExists) { + return await this.run({ + args, + call, + pathArr, + shell, + }) + } + + packages.push(args[0]) } - // Force preferOnline to true so we are making sure to pull in the latest - // This is especially useful if the user didn't give us a version, and - // they expect to be running @latest - return await pacote.manifest(p, { - ...npm.flatOptions, - preferOnline: true, - }) - })) - - if (needPackageCommandSwap) - args[0] = getBinFromManifest(manis[0]) - - // figure out whether we need to install stuff, or if local is fine - const localArb = new Arborist({ - ...npm.flatOptions, - path: npm.localPrefix, - }) - const tree = await localArb.loadActual() - - // do we have all the packages in manifest list? - const needInstall = manis.some(mani => manifestMissing(tree, mani)) - - if (needInstall) { - const installDir = cacheInstallDir(packages) - await mkdirp(installDir) - const arb = new Arborist({ ...npm.flatOptions, path: installDir }) - const tree = await arb.loadActual() - - // at this point, we have to ensure that we get the exact same - // version, because it's something that has only ever been installed - // by npm exec in the cache install directory - const add = manis.filter(mani => manifestMissing(tree, { - ...mani, - _from: `${mani.name}@${mani.version}`, + + // If we do `npm exec foo`, and have a `foo` locally, then we'll + // always use that, so we don't really need to fetch the manifest. + // So: run npa on each packages entry, and if it is a name with a + // rawSpec==='', then try to readPackageJson at + // node_modules/${name}/package.json, and only pacote fetch if + // that fails. + const manis = await Promise.all(packages.map(async p => { + const spec = npa(p, this.npm.localPrefix) + if (spec.type === 'tag' && spec.rawSpec === '') { + // fall through to the pacote.manifest() approach + try { + const pj = resolve(this.npm.localPrefix, 'node_modules', spec.name) + return await readPackageJson(pj) + } catch (er) {} + } + // Force preferOnline to true so we are making sure to pull in the latest + // This is especially useful if the user didn't give us a version, and + // they expect to be running @latest + return await pacote.manifest(p, { + ...this.npm.flatOptions, + preferOnline: true, + }) })) - .map(mani => mani._from) - .sort((a, b) => a.localeCompare(b)) - - // no need to install if already present - if (add.length) { - if (!npm.flatOptions.yes) { - // set -n to always say no - if (npm.flatOptions.yes === false) - throw 'canceled' - - if (!process.stdin.isTTY || ciDetect()) { - npm.log.warn('exec', `The following package${ + + if (needPackageCommandSwap) + args[0] = this.getBinFromManifest(manis[0]) + + // figure out whether we need to install stuff, or if local is fine + const localArb = new Arborist({ + ...this.npm.flatOptions, + path: this.npm.localPrefix, + }) + const tree = await localArb.loadActual() + + // do we have all the packages in manifest list? + const needInstall = manis.some(mani => this.manifestMissing(tree, mani)) + + if (needInstall) { + const installDir = this.cacheInstallDir(packages) + await mkdirp(installDir) + const arb = new Arborist({ ...this.npm.flatOptions, path: installDir }) + const tree = await arb.loadActual() + + // at this point, we have to ensure that we get the exact same + // version, because it's something that has only ever been installed + // by npm exec in the cache install directory + const add = manis.filter(mani => this.manifestMissing(tree, { + ...mani, + _from: `${mani.name}@${mani.version}`, + })) + .map(mani => mani._from) + .sort((a, b) => a.localeCompare(b)) + + // no need to install if already present + if (add.length) { + if (!this.npm.flatOptions.yes) { + // set -n to always say no + if (this.npm.flatOptions.yes === false) + throw 'canceled' + + if (!process.stdin.isTTY || ciDetect()) { + this.npm.log.warn('exec', `The following package${ add.length === 1 ? ' was' : 's were' } not found and will be installed: ${ add.map((pkg) => pkg.replace(/@$/, '')).join(', ') }`) - } else { - const addList = add.map(a => ` ${a.replace(/@$/, '')}`) - .join('\n') + '\n' - const prompt = `Need to install the following packages:\n${ + } else { + const addList = add.map(a => ` ${a.replace(/@$/, '')}`) + .join('\n') + '\n' + const prompt = `Need to install the following packages:\n${ addList }Ok to proceed? ` - const confirm = await read({ prompt, default: 'y' }) - if (confirm.trim().toLowerCase().charAt(0) !== 'y') - throw 'canceled' + const confirm = await read({ prompt, default: 'y' }) + if (confirm.trim().toLowerCase().charAt(0) !== 'y') + throw 'canceled' + } } + await arb.reify({ ...this.npm.flatOptions, add }) } - await arb.reify({ ...npm.flatOptions, add }) + pathArr.unshift(resolve(installDir, 'node_modules/.bin')) } - pathArr.unshift(resolve(installDir, 'node_modules/.bin')) + + return await this.run({ args, call, pathArr, shell }) } - return await run({ args, call, pathArr, shell }) -} + async run ({ args, call, pathArr, shell }) { + // turn list of args into command string + const script = call || args.shift() || shell + + // do the fakey runScript dance + // still should work if no package.json in cwd + const realPkg = await readPackageJson(`${this.npm.localPrefix}/package.json`) + .catch(() => ({})) + const pkg = { + ...realPkg, + scripts: { + ...(realPkg.scripts || {}), + npx: script, + }, + } -const manifestMissing = (tree, mani) => { - // if the tree doesn't have a child by that name/version, return true - // true means we need to install it - const child = tree.children.get(mani.name) - // if no child, we have to load it - if (!child) - return true + this.npm.log.disableProgress() + try { + if (script === shell) { + if (process.stdin.isTTY) { + if (ciDetect()) + return this.npm.log.warn('exec', 'Interactive mode disabled in CI environment') + output(`\nEntering npm script environment\nType 'exit' or ^D when finished\n`) + } + } + return await runScript({ + ...this.npm.flatOptions, + pkg, + banner: false, + // we always run in cwd, not --prefix + path: process.cwd(), + stdioString: true, + event: 'npx', + args, + env: { + PATH: pathArr.join(delimiter), + }, + stdio: 'inherit', + }) + } finally { + this.npm.log.enableProgress() + } + } - // if no version/tag specified, allow whatever's there - if (mani._from === `${mani.name}@`) - return false + manifestMissing (tree, mani) { + // if the tree doesn't have a child by that name/version, return true + // true means we need to install it + const child = tree.children.get(mani.name) + // if no child, we have to load it + if (!child) + return true - // otherwise the version has to match what we WOULD get - return child.version !== mani.version -} + // if no version/tag specified, allow whatever's there + if (mani._from === `${mani.name}@`) + return false -const getBinFromManifest = mani => { - // if we have a bin matching (unscoped portion of) packagename, use that - // otherwise if there's 1 bin or all bin value is the same (alias), use that, - // otherwise fail - const bin = mani.bin || {} - if (new Set(Object.values(bin)).size === 1) - return Object.keys(bin)[0] - - // XXX probably a util to parse this better? - const name = mani.name.replace(/^@[^/]+\//, '') - if (bin[name]) - return name - - // XXX need better error message - throw Object.assign(new Error('could not determine executable to run'), { - pkgid: mani._id, - }) -} + // otherwise the version has to match what we WOULD get + return child.version !== mani.version + } -// only packages not found in ${prefix}/node_modules -const cacheInstallDir = packages => - resolve(npm.config.get('cache'), '_npx', getHash(packages)) + getBinFromManifest (mani) { + // if we have a bin matching (unscoped portion of) packagename, use that + // otherwise if there's 1 bin or all bin value is the same (alias), use + // that, otherwise fail + const bin = mani.bin || {} + if (new Set(Object.values(bin)).size === 1) + return Object.keys(bin)[0] + + // XXX probably a util to parse this better? + const name = mani.name.replace(/^@[^/]+\//, '') + if (bin[name]) + return name + + // XXX need better error message + throw Object.assign(new Error('could not determine executable to run'), { + pkgid: mani._id, + }) + } -const getHash = packages => - crypto.createHash('sha512') - .update(packages.sort((a, b) => a.localeCompare(b)).join('\n')) - .digest('hex') - .slice(0, 16) + cacheInstallDir (packages) { + // only packages not found in ${prefix}/node_modules + return resolve(this.npm.config.get('cache'), '_npx', this.getHash(packages)) + } -module.exports = Object.assign(cmd, { usage }) + getHash (packages) { + return crypto.createHash('sha512') + .update(packages.sort((a, b) => a.localeCompare(b)).join('\n')) + .digest('hex') + .slice(0, 16) + } +} +module.exports = Exec diff --git a/deps/npm/lib/explain.js b/deps/npm/lib/explain.js index a0a4427bcc..01541040ef 100644 --- a/deps/npm/lib/explain.js +++ b/deps/npm/lib/explain.js @@ -1,5 +1,4 @@ const usageUtil = require('./utils/usage.js') -const npm = require('./npm.js') const { explainNode } = require('./utils/explain-dep.js') const completion = require('./utils/completion/installed-deep.js') const output = require('./utils/output.js') @@ -9,86 +8,101 @@ const semver = require('semver') const { relative, resolve } = require('path') const validName = require('validate-npm-package-name') -const usage = usageUtil('explain', 'npm explain <folder | specifier>') - -const cmd = (args, cb) => explain(args).then(() => cb()).catch(cb) +class Explain { + constructor (npm) { + this.npm = npm + } -const explain = async (args) => { - if (!args.length) - throw usage + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil('explain', 'npm explain <folder | specifier>') + } - const arb = new Arborist({ path: npm.prefix, ...npm.flatOptions }) - const tree = await arb.loadActual() + /* istanbul ignore next - see test/lib/load-all-commands.js */ + async completion (opts) { + return completion(this.npm, opts) + } - const nodes = new Set() - for (const arg of args) { - for (const node of getNodes(tree, arg)) - nodes.add(node) + exec (args, cb) { + this.explain(args).then(() => cb()).catch(cb) } - if (nodes.size === 0) - throw `No dependencies found matching ${args.join(', ')}` - const expls = [] - for (const node of nodes) { - const { extraneous, dev, optional, devOptional, peer, inBundle } = node - const expl = node.explain() - if (extraneous) - expl.extraneous = true - else { - expl.dev = dev - expl.optional = optional - expl.devOptional = devOptional - expl.peer = peer - expl.bundled = inBundle + async explain (args) { + if (!args.length) + throw this.usage + + const arb = new Arborist({ path: this.npm.prefix, ...this.npm.flatOptions }) + const tree = await arb.loadActual() + + const nodes = new Set() + for (const arg of args) { + for (const node of this.getNodes(tree, arg)) + nodes.add(node) } - expls.push(expl) - } + if (nodes.size === 0) + throw `No dependencies found matching ${args.join(', ')}` - if (npm.flatOptions.json) - output(JSON.stringify(expls, null, 2)) - else { - output(expls.map(expl => { - return explainNode(expl, Infinity, npm.color) - }).join('\n\n')) + const expls = [] + for (const node of nodes) { + const { extraneous, dev, optional, devOptional, peer, inBundle } = node + const expl = node.explain() + if (extraneous) + expl.extraneous = true + else { + expl.dev = dev + expl.optional = optional + expl.devOptional = devOptional + expl.peer = peer + expl.bundled = inBundle + } + expls.push(expl) + } + + if (this.npm.flatOptions.json) + output(JSON.stringify(expls, null, 2)) + else { + output(expls.map(expl => { + return explainNode(expl, Infinity, this.npm.color) + }).join('\n\n')) + } } -} -const getNodes = (tree, arg) => { - // if it's just a name, return packages by that name - const { validForOldPackages: valid } = validName(arg) - if (valid) - return tree.inventory.query('name', arg) + getNodes (tree, arg) { + // if it's just a name, return packages by that name + const { validForOldPackages: valid } = validName(arg) + if (valid) + return tree.inventory.query('name', arg) - // if it's a location, get that node - const maybeLoc = arg.replace(/\\/g, '/').replace(/\/+$/, '') - const nodeByLoc = tree.inventory.get(maybeLoc) - if (nodeByLoc) - return [nodeByLoc] + // if it's a location, get that node + const maybeLoc = arg.replace(/\\/g, '/').replace(/\/+$/, '') + const nodeByLoc = tree.inventory.get(maybeLoc) + if (nodeByLoc) + return [nodeByLoc] - // maybe a path to a node_modules folder - const maybePath = relative(npm.prefix, resolve(maybeLoc)) - .replace(/\\/g, '/').replace(/\/+$/, '') - const nodeByPath = tree.inventory.get(maybePath) - if (nodeByPath) - return [nodeByPath] + // maybe a path to a node_modules folder + const maybePath = relative(this.npm.prefix, resolve(maybeLoc)) + .replace(/\\/g, '/').replace(/\/+$/, '') + const nodeByPath = tree.inventory.get(maybePath) + if (nodeByPath) + return [nodeByPath] - // otherwise, try to select all matching nodes - try { - return getNodesByVersion(tree, arg) - } catch (er) { - return [] + // otherwise, try to select all matching nodes + try { + return this.getNodesByVersion(tree, arg) + } catch (er) { + return [] + } } -} -const getNodesByVersion = (tree, arg) => { - const spec = npa(arg, npm.prefix) - if (spec.type !== 'version' && spec.type !== 'range') - return [] + getNodesByVersion (tree, arg) { + const spec = npa(arg, this.npm.prefix) + if (spec.type !== 'version' && spec.type !== 'range') + return [] - return tree.inventory.filter(node => { - return node.package.name === spec.name && - semver.satisfies(node.package.version, spec.rawSpec) - }) + return tree.inventory.filter(node => { + return node.package.name === spec.name && + semver.satisfies(node.package.version, spec.rawSpec) + }) + } } - -module.exports = Object.assign(cmd, { usage, completion }) +module.exports = Explain diff --git a/deps/npm/lib/explore.js b/deps/npm/lib/explore.js index e9b09707ec..fdfe6e1bcf 100644 --- a/deps/npm/lib/explore.js +++ b/deps/npm/lib/explore.js @@ -1,69 +1,82 @@ // npm explore <pkg>[@<version>] // open a subshell to the package folder. -const usageUtil = require('./utils/usage.js') -const completion = require('./utils/completion/installed-shallow.js') -const usage = usageUtil('explore', 'npm explore <pkg> [ -- <command>]') const rpj = require('read-package-json-fast') - -const cmd = (args, cb) => explore(args).then(() => cb()).catch(cb) - -const output = require('./utils/output.js') -const npm = require('./npm.js') - const runScript = require('@npmcli/run-script') const { join, resolve, relative } = require('path') +const completion = require('./utils/completion/installed-shallow.js') +const output = require('./utils/output.js') +const usageUtil = require('./utils/usage.js') -const explore = async args => { - if (args.length < 1 || !args[0]) - throw usage +class Explore { + constructor (npm) { + this.npm = npm + } - const pkgname = args.shift() + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil('explore', 'npm explore <pkg> [ -- <command>]') + } - // detect and prevent any .. shenanigans - const path = join(npm.dir, join('/', pkgname)) - if (relative(path, npm.dir) === '') - throw usage + /* istanbul ignore next - see test/lib/load-all-commands.js */ + async completion (opts) { + return completion(this.npm, opts) + } - // run as if running a script named '_explore', which we set to either - // the set of arguments, or the shell config, and let @npmcli/run-script - // handle all the escaping and PATH setup stuff. + exec (args, cb) { + this.explore(args).then(() => cb()).catch(cb) + } - const pkg = await rpj(resolve(path, 'package.json')).catch(er => { - npm.log.error('explore', `It doesn't look like ${pkgname} is installed.`) - throw er - }) + async explore (args) { + if (args.length < 1 || !args[0]) + throw this.usage - const { shell } = npm.flatOptions - pkg.scripts = { - ...(pkg.scripts || {}), - _explore: args.join(' ').trim() || shell, - } + const pkgname = args.shift() + + // detect and prevent any .. shenanigans + const path = join(this.npm.dir, join('/', pkgname)) + if (relative(path, this.npm.dir) === '') + throw this.usage - if (!args.length) - output(`\nExploring ${path}\nType 'exit' or ^D when finished\n`) - npm.log.disableProgress() - try { - return await runScript({ - ...npm.flatOptions, - pkg, - banner: false, - path, - stdioString: true, - event: '_explore', - stdio: 'inherit', - }).catch(er => { - process.exitCode = typeof er.code === 'number' && er.code !== 0 ? er.code - : 1 - // if it's not an exit error, or non-interactive, throw it - const isProcExit = er.message === 'command failed' && - (typeof er.code === 'number' || /^SIG/.test(er.signal || '')) - if (args.length || !isProcExit) - throw er + // run as if running a script named '_explore', which we set to either + // the set of arguments, or the shell config, and let @npmcli/run-script + // handle all the escaping and PATH setup stuff. + + const pkg = await rpj(resolve(path, 'package.json')).catch(er => { + this.npm.log.error('explore', `It doesn't look like ${pkgname} is installed.`) + throw er }) - } finally { - npm.log.enableProgress() + + const { shell } = this.npm.flatOptions + pkg.scripts = { + ...(pkg.scripts || {}), + _explore: args.join(' ').trim() || shell, + } + + if (!args.length) + output(`\nExploring ${path}\nType 'exit' or ^D when finished\n`) + this.npm.log.disableProgress() + try { + return await runScript({ + ...this.npm.flatOptions, + pkg, + banner: false, + path, + stdioString: true, + event: '_explore', + stdio: 'inherit', + }).catch(er => { + process.exitCode = typeof er.code === 'number' && er.code !== 0 ? er.code + : 1 + // if it's not an exit error, or non-interactive, throw it + const isProcExit = er.message === 'command failed' && + (typeof er.code === 'number' || /^SIG/.test(er.signal || '')) + if (args.length || !isProcExit) + throw er + }) + } finally { + this.npm.log.enableProgress() + } } } - -module.exports = Object.assign(cmd, { completion, usage }) +module.exports = Explore diff --git a/deps/npm/lib/find-dupes.js b/deps/npm/lib/find-dupes.js index 19e7ea6a7c..5061be9cc3 100644 --- a/deps/npm/lib/find-dupes.js +++ b/deps/npm/lib/find-dupes.js @@ -1,8 +1,19 @@ // dedupe duplicated packages, or find them in the tree -const dedupe = require('./dedupe.js') const usageUtil = require('./utils/usage.js') -const usage = usageUtil('find-dupes', 'npm find-dupes') -const cmd = (args, cb) => dedupe({ dryRun: true }, cb) +class FindDupes { + constructor (npm) { + this.npm = npm + } -module.exports = Object.assign(cmd, { usage }) + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil('find-dupes', 'npm find-dupes') + } + + exec (args, cb) { + this.npm.config.set('dry-run', true) + this.npm.commands.dedupe([], cb) + } +} +module.exports = FindDupes diff --git a/deps/npm/lib/fund.js b/deps/npm/lib/fund.js index 41dd48c465..1e97242664 100644 --- a/deps/npm/lib/fund.js +++ b/deps/npm/lib/fund.js @@ -11,200 +11,210 @@ const { isValidFunding, } = require('libnpmfund') -const npm = require('./npm.js') const completion = require('./utils/completion/installed-deep.js') const output = require('./utils/output.js') const openUrl = require('./utils/open-url.js') const usageUtil = require('./utils/usage.js') -const usage = usageUtil( - 'fund', - 'npm fund', - 'npm fund [--json] [--browser] [--unicode] [[<@scope>/]<pkg> [--which=<fundingSourceNumber>]' -) - -const cmd = (args, cb) => fund(args).then(() => cb()).catch(cb) - -function printJSON (fundingInfo) { - return JSON.stringify(fundingInfo, null, 2) -} - const getPrintableName = ({ name, version }) => { const printableVersion = version ? `@${version}` : '' return `${name}${printableVersion}` } -function printHuman (fundingInfo, { color, unicode }) { - const seenUrls = new Map() - - const tree = obj => - archy(obj, '', { unicode }) - - const result = depth({ - tree: fundingInfo, - - // composes human readable package name - // and creates a new archy item for readable output - visit: ({ name, version, funding }) => { - const [fundingSource] = [] - .concat(normalizeFunding(funding)) - .filter(isValidFunding) - const { url } = fundingSource || {} - const pkgRef = getPrintableName({ name, version }) - let item = { - label: pkgRef, - } - - if (url) { - item.label = tree({ - label: color ? chalk.bgBlack.white(url) : url, - nodes: [pkgRef], - }).trim() - - // stacks all packages together under the same item - if (seenUrls.has(url)) { - item = seenUrls.get(url) - item.label += `, ${pkgRef}` - return null - } else - seenUrls.set(url, item) - } - - return item - }, - - // puts child nodes back into returned archy - // output while also filtering out missing items - leave: (item, children) => { - if (item) - item.nodes = children.filter(Boolean) - - return item - }, - - // turns tree-like object return by libnpmfund - // into children to be properly read by treeverse - getChildren: (node) => - Object.keys(node.dependencies || {}) - .map(key => ({ - name: key, - ...node.dependencies[key], - })), - }) - - const res = tree(result) - return color ? chalk.reset(res) : res -} +class Fund { + constructor (npm) { + this.npm = npm + } -async function openFundingUrl ({ path, tree, spec, fundingSourceNumber }) { - const arg = npa(spec, path) - const retrievePackageMetadata = () => { - if (arg.type === 'directory') { - if (tree.path === arg.fetchSpec) { - // matches cwd, e.g: npm fund . - return tree.package - } else { - // matches any file path within current arborist inventory - for (const item of tree.inventory.values()) { - if (item.path === arg.fetchSpec) - return item.package - } - } - } else { - // tries to retrieve a package from arborist inventory - // by matching resulted package name from the provided spec - const [item] = [...tree.inventory.query('name', arg.name)] - .filter(i => semver.valid(i.package.version)) - .sort((a, b) => semver.rcompare(a.package.version, b.package.version)) - - if (item) - return item.package - } + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil( + 'fund', + 'npm fund', + 'npm fund [--json] [--browser] [--unicode] [[<@scope>/]<pkg> [--which=<fundingSourceNumber>]' + ) } - const { funding } = retrievePackageMetadata() || - await pacote.manifest(arg, npm.flatOptions).catch(() => ({})) - - const validSources = [] - .concat(normalizeFunding(funding)) - .filter(isValidFunding) - - const matchesValidSource = - validSources.length === 1 || - (fundingSourceNumber > 0 && fundingSourceNumber <= validSources.length) - - if (matchesValidSource) { - const index = fundingSourceNumber ? fundingSourceNumber - 1 : 0 - const { type, url } = validSources[index] - const typePrefix = type ? `${type} funding` : 'Funding' - const msg = `${typePrefix} available at the following URL` - return new Promise((resolve, reject) => - openUrl(url, msg, err => err - ? reject(err) - : resolve() - )) - } else if (validSources.length && !(fundingSourceNumber >= 1)) { - validSources.forEach(({ type, url }, i) => { - const typePrefix = type ? `${type} funding` : 'Funding' - const msg = `${typePrefix} available at the following URL` - output(`${i + 1}: ${msg}: ${url}`) - }) - output('Run `npm fund [<@scope>/]<pkg> --which=1`, for example, to open the first funding URL listed in that package') - } else { - const noFundingError = new Error(`No valid funding method available for: ${spec}`) - noFundingError.code = 'ENOFUND' + /* istanbul ignore next - see test/lib/load-all-commands.js */ + async completion (opts) { + return completion(this.npm, opts) + } - throw noFundingError + exec (args, cb) { + this.fund(args).then(() => cb()).catch(cb) } -} -const fund = async (args) => { - const opts = npm.flatOptions - const spec = args[0] - const numberArg = opts.which + async fund (args) { + const opts = this.npm.flatOptions + const spec = args[0] + const numberArg = opts.which - const fundingSourceNumber = numberArg && parseInt(numberArg, 10) + const fundingSourceNumber = numberArg && parseInt(numberArg, 10) - const badFundingSourceNumber = - numberArg !== undefined && + const badFundingSourceNumber = + numberArg !== undefined && (String(fundingSourceNumber) !== numberArg || fundingSourceNumber < 1) - if (badFundingSourceNumber) { - const err = new Error('`npm fund [<@scope>/]<pkg> [--which=fundingSourceNumber]` must be given a positive integer') - err.code = 'EFUNDNUMBER' - throw err + if (badFundingSourceNumber) { + const err = new Error('`npm fund [<@scope>/]<pkg> [--which=fundingSourceNumber]` must be given a positive integer') + err.code = 'EFUNDNUMBER' + throw err + } + + if (opts.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 tree = await arb.loadActual() + + if (spec) { + await this.openFundingUrl({ + path: where, + tree, + spec, + fundingSourceNumber, + }) + return + } + + const print = opts.json + ? this.printJSON + : this.printHuman + + output( + print( + getFundingInfo(tree), + opts + ) + ) } - if (opts.global) { - const err = new Error('`npm fund` does not support global packages') - err.code = 'EFUNDGLOBAL' - throw err + printJSON (fundingInfo) { + return JSON.stringify(fundingInfo, null, 2) } - const where = npm.prefix - const arb = new Arborist({ ...opts, path: where }) - const tree = await arb.loadActual() + printHuman (fundingInfo, { color, unicode }) { + const seenUrls = new Map() + + const tree = obj => + archy(obj, '', { unicode }) + + const result = depth({ + tree: fundingInfo, + + // composes human readable package name + // and creates a new archy item for readable output + visit: ({ name, version, funding }) => { + const [fundingSource] = [] + .concat(normalizeFunding(funding)) + .filter(isValidFunding) + const { url } = fundingSource || {} + const pkgRef = getPrintableName({ name, version }) + let item = { + label: pkgRef, + } + + if (url) { + item.label = tree({ + label: color ? chalk.bgBlack.white(url) : url, + nodes: [pkgRef], + }).trim() + + // stacks all packages together under the same item + if (seenUrls.has(url)) { + item = seenUrls.get(url) + item.label += `, ${pkgRef}` + return null + } else + seenUrls.set(url, item) + } - if (spec) { - await openFundingUrl({ - path: where, - tree, - spec, - fundingSourceNumber, + return item + }, + + // puts child nodes back into returned archy + // output while also filtering out missing items + leave: (item, children) => { + if (item) + item.nodes = children.filter(Boolean) + + return item + }, + + // turns tree-like object return by libnpmfund + // into children to be properly read by treeverse + getChildren: (node) => + Object.keys(node.dependencies || {}) + .map(key => ({ + name: key, + ...node.dependencies[key], + })), }) - return + + const res = tree(result) + return color ? chalk.reset(res) : res } - const print = opts.json - ? printJSON - : printHuman + async openFundingUrl ({ path, tree, spec, fundingSourceNumber }) { + const arg = npa(spec, path) + const retrievePackageMetadata = () => { + if (arg.type === 'directory') { + if (tree.path === arg.fetchSpec) { + // matches cwd, e.g: npm fund . + return tree.package + } else { + // matches any file path within current arborist inventory + for (const item of tree.inventory.values()) { + if (item.path === arg.fetchSpec) + return item.package + } + } + } else { + // tries to retrieve a package from arborist inventory + // by matching resulted package name from the provided spec + const [item] = [...tree.inventory.query('name', arg.name)] + .filter(i => semver.valid(i.package.version)) + .sort((a, b) => semver.rcompare(a.package.version, b.package.version)) + + if (item) + return item.package + } + } + + const { funding } = retrievePackageMetadata() || + await pacote.manifest(arg, this.npm.flatOptions).catch(() => ({})) - output( - print( - getFundingInfo(tree), - opts - ) - ) -} + const validSources = [] + .concat(normalizeFunding(funding)) + .filter(isValidFunding) + + const matchesValidSource = + validSources.length === 1 || + (fundingSourceNumber > 0 && fundingSourceNumber <= validSources.length) + + if (matchesValidSource) { + const index = fundingSourceNumber ? fundingSourceNumber - 1 : 0 + const { type, url } = validSources[index] + const typePrefix = type ? `${type} funding` : 'Funding' + const msg = `${typePrefix} available at the following URL` + return openUrl(this.npm, url, msg) + } else if (validSources.length && !(fundingSourceNumber >= 1)) { + validSources.forEach(({ type, url }, i) => { + const typePrefix = type ? `${type} funding` : 'Funding' + const msg = `${typePrefix} available at the following URL` + output(`${i + 1}: ${msg}: ${url}`) + }) + output('Run `npm fund [<@scope>/]<pkg> --which=1`, for example, to open the first funding URL listed in that package') + } else { + const noFundingError = new Error(`No valid funding method available for: ${spec}`) + noFundingError.code = 'ENOFUND' -module.exports = Object.assign(cmd, { usage, completion }) + throw noFundingError + } + } +} +module.exports = Fund diff --git a/deps/npm/lib/get.js b/deps/npm/lib/get.js index 8a416027d7..a5b2f55144 100644 --- a/deps/npm/lib/get.js +++ b/deps/npm/lib/get.js @@ -1,15 +1,25 @@ -const npm = require('./npm.js') -const config = require('./config.js') const usageUtil = require('./utils/usage.js') -const usage = usageUtil( - 'get', - 'npm get [<key> ...] (See `npm config`)' -) +class Get { + constructor (npm) { + this.npm = npm + } -const completion = config.completion + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil( + 'get', + 'npm get [<key> ...] (See `npm config`)' + ) + } -const cmd = (args, cb) => - npm.commands.config(['get'].concat(args), cb) + /* istanbul ignore next - see test/lib/load-all-commands.js */ + async completion (opts) { + return this.npm.commands.config.completion(opts) + } -module.exports = Object.assign(cmd, { usage, completion }) + exec (args, cb) { + this.npm.commands.config(['get'].concat(args), cb) + } +} +module.exports = Get diff --git a/deps/npm/lib/help-search.js b/deps/npm/lib/help-search.js index b184735048..ed2bc23b91 100644 --- a/deps/npm/lib/help-search.js +++ b/deps/npm/lib/help-search.js @@ -1,203 +1,211 @@ const fs = require('fs') const path = require('path') -const npm = require('./npm.js') const color = require('ansicolors') const output = require('./utils/output.js') const usageUtil = require('./utils/usage.js') +const npmUsage = require('./utils/npm-usage.js') const { promisify } = require('util') const glob = promisify(require('glob')) const readFile = promisify(fs.readFile) const didYouMean = require('./utils/did-you-mean.js') const { cmdList } = require('./utils/cmd-list.js') -const usage = usageUtil('help-search', 'npm help-search <text>') - -const npmUsage = require('./utils/npm-usage.js') +class HelpSearch { + constructor (npm) { + this.npm = npm + } -const cmd = (args, cb) => helpSearch(args).then(() => cb()).catch(cb) + get usage () { + return usageUtil('help-search', 'npm help-search <text>') + } -const helpSearch = async args => { - if (!args.length) - throw usage + exec (args, cb) { + this.helpSearch(args).then(() => cb()).catch(cb) + } - const docPath = path.resolve(__dirname, '..', 'docs/content') + async helpSearch (args) { + if (!args.length) + throw this.usage + + const docPath = path.resolve(__dirname, '..', 'docs/content') + + const files = await glob(`${docPath}/*/*.md`) + const data = await this.readFiles(files) + const results = await this.searchFiles(args, data, files) + // if only one result, then just show that help section. + if (results.length === 1) { + return this.npm.commands.help([path.basename(results[0].file, '.md')], er => { + if (er) + throw er + }) + } - const files = await glob(`${docPath}/*/*.md`) - const data = await readFiles(files) - const results = await searchFiles(args, data, files) - // if only one result, then just show that help section. - if (results.length === 1) { - return npm.commands.help([path.basename(results[0].file, '.md')], er => { - if (er) - throw er - }) + const formatted = this.formatResults(args, results) + if (!formatted.trim()) + npmUsage(this.npm, false) + else { + output(formatted) + output(didYouMean(args[0], cmdList)) + } } - const formatted = formatResults(args, results) - if (!formatted.trim()) - npmUsage(false) - else { - output(formatted) - output(didYouMean(args[0], cmdList)) + async readFiles (files) { + const res = {} + await Promise.all(files.map(async file => { + res[file] = (await readFile(file, 'utf8')) + .replace(/^---\n(.*\n)*?---\n/, '').trim() + })) + return res } -} -const readFiles = async files => { - const res = {} - await Promise.all(files.map(async file => { - res[file] = (await readFile(file, 'utf8')) - .replace(/^---\n(.*\n)*?---\n/, '').trim() - })) - return res -} + async searchFiles (args, data, files) { + const results = [] + for (const [file, content] of Object.entries(data)) { + const lowerCase = content.toLowerCase() + // skip if no matches at all + if (!args.some(a => lowerCase.includes(a.toLowerCase()))) + continue + + const lines = content.split(/\n+/) + + // if a line has a search term, then skip it and the next line. + // if the next line has a search term, then skip all 3 + // otherwise, set the line to null. then remove the nulls. + for (let i = 0; i < lines.length; i++) { + const line = lines[i] + const nextLine = lines[i + 1] + let match = false + if (nextLine) { + match = args.some(a => + nextLine.toLowerCase().includes(a.toLowerCase())) + if (match) { + // skip over the next line, and the line after it. + i += 2 + continue + } + } + + match = args.some(a => line.toLowerCase().includes(a.toLowerCase())) -const searchFiles = async (args, data, files) => { - const results = [] - for (const [file, content] of Object.entries(data)) { - const lowerCase = content.toLowerCase() - // skip if no matches at all - if (!args.some(a => lowerCase.includes(a.toLowerCase()))) - continue - - const lines = content.split(/\n+/) - - // if a line has a search term, then skip it and the next line. - // if the next line has a search term, then skip all 3 - // otherwise, set the line to null. then remove the nulls. - for (let i = 0; i < lines.length; i++) { - const line = lines[i] - const nextLine = lines[i + 1] - let match = false - if (nextLine) { - match = args.some(a => nextLine.toLowerCase().includes(a.toLowerCase())) if (match) { - // skip over the next line, and the line after it. - i += 2 + // skip over the next line + i++ continue } - } - match = args.some(a => line.toLowerCase().includes(a.toLowerCase())) - - if (match) { - // skip over the next line - i++ - continue + lines[i] = null } - lines[i] = null - } - - // now squish any string of nulls into a single null - const pruned = lines.reduce((l, r) => { - if (!(r === null && l[l.length - 1] === null)) - l.push(r) + // now squish any string of nulls into a single null + const pruned = lines.reduce((l, r) => { + if (!(r === null && l[l.length - 1] === null)) + l.push(r) - return l - }, []) + return l + }, []) - if (pruned[pruned.length - 1] === null) - pruned.pop() + if (pruned[pruned.length - 1] === null) + pruned.pop() - if (pruned[0] === null) - pruned.shift() + if (pruned[0] === null) + pruned.shift() - // now count how many args were found - const found = {} - let totalHits = 0 - for (const line of pruned) { - for (const arg of args) { - const hit = (line || '').toLowerCase() - .split(arg.toLowerCase()).length - 1 + // now count how many args were found + const found = {} + let totalHits = 0 + for (const line of pruned) { + for (const arg of args) { + const hit = (line || '').toLowerCase() + .split(arg.toLowerCase()).length - 1 - if (hit > 0) { - found[arg] = (found[arg] || 0) + hit - totalHits += hit + if (hit > 0) { + found[arg] = (found[arg] || 0) + hit + totalHits += hit + } } } + + const cmd = 'npm help ' + + path.basename(file, '.md').replace(/^npm-/, '') + results.push({ + file, + cmd, + lines: pruned, + found: Object.keys(found), + hits: found, + totalHits, + }) } - const cmd = 'npm help ' + - path.basename(file, '.md').replace(/^npm-/, '') - results.push({ - file, - cmd, - lines: pruned, - found: Object.keys(found), - hits: found, - totalHits, - }) + // sort results by number of results found, then by number of hits + // then by number of matching lines + + // coverage is ignored here because the contents of results are + // nondeterministic due to either glob or readFiles or Object.entries + return results.sort(/* istanbul ignore next */ (a, b) => + a.found.length > b.found.length ? -1 + : a.found.length < b.found.length ? 1 + : a.totalHits > b.totalHits ? -1 + : a.totalHits < b.totalHits ? 1 + : a.lines.length > b.lines.length ? -1 + : a.lines.length < b.lines.length ? 1 + : 0).slice(0, 10) } - // sort results by number of results found, then by number of hits - // then by number of matching lines - - // coverage is ignored here because the contents of results are - // nondeterministic due to either glob or readFiles or Object.entries - return results.sort(/* istanbul ignore next */ (a, b) => - a.found.length > b.found.length ? -1 - : a.found.length < b.found.length ? 1 - : a.totalHits > b.totalHits ? -1 - : a.totalHits < b.totalHits ? 1 - : a.lines.length > b.lines.length ? -1 - : a.lines.length < b.lines.length ? 1 - : 0).slice(0, 10) -} - -const formatResults = (args, results) => { - const cols = Math.min(process.stdout.columns || Infinity, 80) + 1 + formatResults (args, results) { + const cols = Math.min(process.stdout.columns || Infinity, 80) + 1 - const out = results.map(res => { - const out = [res.cmd] - const r = Object.keys(res.hits) - .map(k => `${k}:${res.hits[k]}`) - .sort((a, b) => a > b ? 1 : -1) - .join(' ') + const out = results.map(res => { + const out = [res.cmd] + const r = Object.keys(res.hits) + .map(k => `${k}:${res.hits[k]}`) + .sort((a, b) => a > b ? 1 : -1) + .join(' ') - out.push(' '.repeat((Math.max(1, cols - out.join(' ').length - r.length - 1)))) - out.push(r) + out.push(' '.repeat((Math.max(1, cols - out.join(' ').length - r.length - 1)))) + out.push(r) - if (!npm.flatOptions.long) - return out.join('') + if (!this.npm.flatOptions.long) + return out.join('') - out.unshift('\n\n') - out.push('\n') - out.push('-'.repeat(cols - 1) + '\n') - res.lines.forEach((line, i) => { - if (line === null || i > 3) - return + out.unshift('\n\n') + out.push('\n') + out.push('-'.repeat(cols - 1) + '\n') + res.lines.forEach((line, i) => { + if (line === null || i > 3) + return - if (!npm.color) { - out.push(line + '\n') - return - } - const hilitLine = [] - for (const arg of args) { - const finder = line.toLowerCase().split(arg.toLowerCase()) - let p = 0 - for (const f of finder) { - hilitLine.push(line.substr(p, f.length)) - const word = line.substr(p + f.length, arg.length) - const hilit = color.bgBlack(color.red(word)) - hilitLine.push(hilit) - p += f.length + arg.length + if (!this.npm.color) { + out.push(line + '\n') + return } - } - out.push(hilitLine.join('') + '\n') - }) + const hilitLine = [] + for (const arg of args) { + const finder = line.toLowerCase().split(arg.toLowerCase()) + let p = 0 + for (const f of finder) { + hilitLine.push(line.substr(p, f.length)) + const word = line.substr(p + f.length, arg.length) + const hilit = color.bgBlack(color.red(word)) + hilitLine.push(hilit) + p += f.length + arg.length + } + } + out.push(hilitLine.join('') + '\n') + }) - return out.join('') - }).join('\n') + return out.join('') + }).join('\n') - const finalOut = results.length && !npm.flatOptions.long - ? 'Top hits for ' + (args.map(JSON.stringify).join(' ')) + '\n' + + const finalOut = results.length && !this.npm.flatOptions.long + ? 'Top hits for ' + (args.map(JSON.stringify).join(' ')) + '\n' + '—'.repeat(cols - 1) + '\n' + out + '\n' + '—'.repeat(cols - 1) + '\n' + '(run with -l or --long to see more context)' - : out + : out - return finalOut.trim() + return finalOut.trim() + } } - -module.exports = Object.assign(cmd, { usage }) +module.exports = HelpSearch diff --git a/deps/npm/lib/help.js b/deps/npm/lib/help.js index 6f215c76c1..d7897326f3 100644 --- a/deps/npm/lib/help.js +++ b/deps/npm/lib/help.js @@ -1,191 +1,224 @@ - -module.exports = help - -help.completion = async (opts) => { - if (opts.conf.argv.remain.length > 2) - return [] - const g = path.resolve(__dirname, '../man/man[0-9]/*.[0-9]') - const files = await new Promise((resolve, reject) => { - glob(g, function (er, files) { - if (er) - return reject(er) - resolve(files) - }) - }) - - return Object.keys(files.reduce(function (acc, file) { - file = path.basename(file).replace(/\.[0-9]+$/, '') - file = file.replace(/^npm-/, '') - acc[file] = true - return acc - }, { help: true })) -} - const npmUsage = require('./utils/npm-usage.js') const { spawn } = require('child_process') const path = require('path') -const npm = require('./npm.js') const log = require('npmlog') -const openUrl = require('./utils/open-url') +const openUrl = require('./utils/open-url.js') const glob = require('glob') const output = require('./utils/output.js') const usage = require('./utils/usage.js') -help.usage = usage('help', 'npm help <term> [<terms..>]') - -function help (args, cb) { - const argv = npm.config.parsedArgv.cooked - - let argnum = 0 - if (args.length === 2 && ~~args[0]) - argnum = ~~args.shift() - - // npm help foo bar baz: search topics - if (args.length > 1 && args[0]) - return npm.commands['help-search'](args, cb) +class Help { + constructor (npm) { + this.npm = npm + } - const affordances = { - 'find-dupes': 'dedupe', + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usage('help', 'npm help <term> [<terms..>]') } - let section = affordances[args[0]] || npm.deref(args[0]) || args[0] - // npm help <noargs>: show basic usage - if (!section) { - npmUsage(argv[0] === 'help') - return cb() + async completion (opts) { + if (opts.conf.argv.remain.length > 2) + return [] + const g = path.resolve(__dirname, '../man/man[0-9]/*.[0-9]') + const files = await new Promise((resolve, reject) => { + glob(g, function (er, files) { + if (er) + return reject(er) + resolve(files) + }) + }) + + return Object.keys(files.reduce(function (acc, file) { + file = path.basename(file).replace(/\.[0-9]+$/, '') + file = file.replace(/^npm-/, '') + acc[file] = true + return acc + }, { help: true })) } - // npm <command> -h: show command usage - if (npm.config.get('usage') && - npm.commands[section] && - npm.commands[section].usage) { - npm.config.set('loglevel', 'silent') - log.level = 'silent' - output(npm.commands[section].usage) - return cb() + exec (args, cb) { + this.help(args).then(() => cb()).catch(cb) } - let pref = [1, 5, 7] - if (argnum) - pref = [argnum].concat(pref.filter(n => n !== argnum)) - - // npm help <section>: Try to find the path - const manroot = path.resolve(__dirname, '..', 'man') - - // legacy - if (section === 'global') - section = 'folders' - else if (section.match(/.*json/)) - section = section.replace('.json', '-json') - - // find either /section.n or /npm-section.n - // The glob is used in the glob. The regexp is used much - // further down. Globs and regexps are different - const compextglob = '.+(gz|bz2|lzma|[FYzZ]|xz)' - const compextre = '\\.(gz|bz2|lzma|[FYzZ]|xz)$' - const f = '+(npm-' + section + '|' + section + ').[0-9]?(' + compextglob + ')' - return glob(manroot + '/*/' + f, (er, mans) => { - if (er) - return cb(er) - - if (!mans.length) - return npm.commands['help-search'](args, cb) - - mans = mans.map((man) => { - const ext = path.extname(man) - if (man.match(new RegExp(compextre))) - man = path.basename(man, ext) - - return man + async help (args) { + const argv = this.npm.config.parsedArgv.cooked + + let argnum = 0 + if (args.length === 2 && ~~args[0]) + argnum = ~~args.shift() + + // npm help foo bar baz: search topics + if (args.length > 1 && args[0]) + return this.helpSearch(args) + + const affordances = { + 'find-dupes': 'dedupe', + } + let section = affordances[args[0]] || this.npm.deref(args[0]) || args[0] + + // npm help <noargs>: show basic usage + if (!section) { + npmUsage(this.npm, argv[0] === 'help') + return + } + + // npm <command> -h: show command usage + if (this.npm.config.get('usage') && + this.npm.commands[section] && + this.npm.commands[section].usage) { + this.npm.config.set('loglevel', 'silent') + log.level = 'silent' + output(this.npm.commands[section].usage) + return + } + + let pref = [1, 5, 7] + if (argnum) + pref = [argnum].concat(pref.filter(n => n !== argnum)) + + // npm help <section>: Try to find the path + const manroot = path.resolve(__dirname, '..', 'man') + + // legacy + if (section === 'global') + section = 'folders' + else if (section.match(/.*json/)) + section = section.replace('.json', '-json') + + // find either /section.n or /npm-section.n + // The glob is used in the glob. The regexp is used much + // further down. Globs and regexps are different + const compextglob = '.+(gz|bz2|lzma|[FYzZ]|xz)' + const compextre = '\\.(gz|bz2|lzma|[FYzZ]|xz)$' + const f = '+(npm-' + section + '|' + section + ').[0-9]?(' + compextglob + ')' + return new Promise((resolve, reject) => { + glob(manroot + '/*/' + f, async (er, mans) => { + if (er) + return reject(er) + + if (!mans.length) { + this.helpSearch(args).then(resolve).catch(reject) + return + } + + mans = mans.map((man) => { + const ext = path.extname(man) + if (man.match(new RegExp(compextre))) + man = path.basename(man, ext) + + return man + }) + + this.viewMan(this.pickMan(mans, pref), (err) => { + if (err) + return reject(err) + return resolve() + }) + }) }) + } - viewMan(pickMan(mans, pref), cb) - }) -} - -function pickMan (mans, pref_) { - const nre = /([0-9]+)$/ - const pref = {} - pref_.forEach((sect, i) => pref[sect] = i) - mans = mans.sort((a, b) => { - const an = a.match(nre)[1] - const bn = b.match(nre)[1] - return an === bn ? (a > b ? -1 : 1) - : pref[an] < pref[bn] ? -1 - : 1 - }) - return mans[0] -} + helpSearch (args) { + return new Promise((resolve, reject) => { + this.npm.commands['help-search'](args, (err) => { + // This would only error if args was empty, which it never is + /* istanbul ignore next */ + if (err) + return reject(err) -function viewMan (man, cb) { - const nre = /([0-9]+)$/ - const num = man.match(nre)[1] - const section = path.basename(man, '.' + num) - - // at this point, we know that the specified man page exists - const manpath = path.join(__dirname, '..', 'man') - const env = {} - Object.keys(process.env).forEach(function (i) { - env[i] = process.env[i] - }) - env.MANPATH = manpath - const viewer = npm.config.get('viewer') - - const opts = { - env, - stdio: 'inherit', + resolve() + }) + }) } - let bin = 'man' - const args = [] - switch (viewer) { - case 'woman': - bin = 'emacsclient' - args.push('-e', `(woman-find-file '${man}')`) - break - - case 'browser': - bin = false - try { - const url = htmlMan(man) - openUrl(url, 'help available at the following URL', cb) - } catch (err) { - return cb(err) - } - break - - default: - args.push(num, section) - break + pickMan (mans, pref_) { + const nre = /([0-9]+)$/ + const pref = {} + pref_.forEach((sect, i) => pref[sect] = i) + mans = mans.sort((a, b) => { + const an = a.match(nre)[1] + const bn = b.match(nre)[1] + return an === bn ? (a > b ? -1 : 1) + : pref[an] < pref[bn] ? -1 + : 1 + }) + return mans[0] } - if (bin) { - const proc = spawn(bin, args, opts) - proc.on('exit', (code) => { - if (code) - return cb(new Error(`help process exited with code: ${code}`)) + viewMan (man, cb) { + const nre = /([0-9]+)$/ + const num = man.match(nre)[1] + const section = path.basename(man, '.' + num) - return cb() + // at this point, we know that the specified man page exists + const manpath = path.join(__dirname, '..', 'man') + const env = {} + Object.keys(process.env).forEach(function (i) { + env[i] = process.env[i] }) + env.MANPATH = manpath + const viewer = this.npm.config.get('viewer') + + const opts = { + env, + stdio: 'inherit', + } + + let bin = 'man' + const args = [] + switch (viewer) { + case 'woman': + bin = 'emacsclient' + args.push('-e', `(woman-find-file '${man}')`) + break + + case 'browser': + bin = false + try { + const url = this.htmlMan(man) + openUrl(this.npm, url, 'help available at the following URL').then( + () => cb() + ).catch(cb) + } catch (err) { + cb(err) + } + break + + default: + args.push(num, section) + break + } + + if (bin) { + const proc = spawn(bin, args, opts) + proc.on('exit', (code) => { + if (code) + return cb(new Error(`help process exited with code: ${code}`)) + + return cb() + }) + } } -} -function htmlMan (man) { - let sect = +man.match(/([0-9]+)$/)[1] - const f = path.basename(man).replace(/[.]([0-9]+)$/, '') - switch (sect) { - case 1: - sect = 'commands' - break - case 5: - sect = 'configuring-npm' - break - case 7: - sect = 'using-npm' - break - default: - throw new Error('invalid man section: ' + sect) + htmlMan (man) { + let sect = +man.match(/([0-9]+)$/)[1] + const f = path.basename(man).replace(/[.]([0-9]+)$/, '') + switch (sect) { + case 1: + sect = 'commands' + break + case 5: + sect = 'configuring-npm' + break + case 7: + sect = 'using-npm' + break + default: + throw new Error('invalid man section: ' + sect) + } + return 'file://' + path.resolve(__dirname, '..', 'docs', 'output', sect, f + '.html') } - return 'file://' + path.resolve(__dirname, '..', 'docs', 'output', sect, f + '.html') } +module.exports = Help diff --git a/deps/npm/lib/hook.js b/deps/npm/lib/hook.js index 7d69ccbf2a..312f542d7c 100644 --- a/deps/npm/lib/hook.js +++ b/deps/npm/lib/hook.js @@ -1,53 +1,62 @@ const hookApi = require('libnpmhook') -const npm = require('./npm.js') const output = require('./utils/output.js') const otplease = require('./utils/otplease.js') const relativeDate = require('tiny-relative-date') const Table = require('cli-table3') - const usageUtil = require('./utils/usage.js') -const usage = usageUtil('hook', [ - 'npm hook add <pkg> <url> <secret> [--type=<type>]', - 'npm hook ls [pkg]', - 'npm hook rm <id>', - 'npm hook update <id> <url> <secret>', -].join('\n')) -const cmd = (args, cb) => hook(args).then(() => cb()).catch(cb) +class Hook { + constructor (npm) { + this.npm = npm + } + + get usage () { + return usageUtil('hook', [ + 'npm hook add <pkg> <url> <secret> [--type=<type>]', + 'npm hook ls [pkg]', + 'npm hook rm <id>', + 'npm hook update <id> <url> <secret>', + ].join('\n')) + } + + exec (args, cb) { + this.hook(args).then(() => cb()).catch(cb) + } -const hook = async (args) => otplease(npm.flatOptions, opts => { - switch (args[0]) { - case 'add': - return add(args[1], args[2], args[3], opts) - case 'ls': - return ls(args[1], opts) - case 'rm': - return rm(args[1], opts) - case 'update': - case 'up': - return update(args[1], args[2], args[3], opts) - default: - throw usage + async hook (args) { + return otplease(this.npm.flatOptions, (opts) => { + switch (args[0]) { + case 'add': + return this.add(args[1], args[2], args[3], opts) + case 'ls': + return this.ls(args[1], opts) + case 'rm': + return this.rm(args[1], opts) + case 'update': + case 'up': + return this.update(args[1], args[2], args[3], opts) + default: + throw this.usage + } + }) } -}) -const add = (pkg, uri, secret, opts) => { - hookApi.add(pkg, uri, secret, opts).then(hook => { + async add (pkg, uri, secret, opts) { + const hook = await hookApi.add(pkg, uri, secret, opts) if (opts.json) output(JSON.stringify(hook, null, 2)) else if (opts.parseable) { output(Object.keys(hook).join('\t')) output(Object.keys(hook).map(k => hook[k]).join('\t')) } else if (!opts.silent && opts.loglevel !== 'silent') { - output(`+ ${hookName(hook)} ${ + output(`+ ${this.hookName(hook)} ${ opts.unicode ? ' ➜ ' : ' -> ' } ${hook.endpoint}`) } - }) -} + } -const ls = (pkg, opts) => { - return hookApi.ls({ ...opts, package: pkg }).then(hooks => { + async ls (pkg, opts) { + const hooks = await hookApi.ls({ ...opts, package: pkg }) if (opts.json) output(JSON.stringify(hooks, null, 2)) else if (opts.parseable) { @@ -67,7 +76,7 @@ const ls = (pkg, opts) => { hooks.forEach((hook) => { table.push([ { rowSpan: 2, content: hook.id }, - hookName(hook), + this.hookName(hook), hook.endpoint, ]) if (hook.last_delivery) { @@ -83,46 +92,43 @@ const ls = (pkg, opts) => { }) output(table.toString()) } - }) -} + } -const rm = (id, opts) => { - return hookApi.rm(id, opts).then(hook => { + async rm (id, opts) { + const hook = await hookApi.rm(id, opts) if (opts.json) output(JSON.stringify(hook, null, 2)) else if (opts.parseable) { output(Object.keys(hook).join('\t')) output(Object.keys(hook).map(k => hook[k]).join('\t')) } else if (!opts.silent && opts.loglevel !== 'silent') { - output(`- ${hookName(hook)} ${ + output(`- ${this.hookName(hook)} ${ opts.unicode ? ' ✘ ' : ' X ' } ${hook.endpoint}`) } - }) -} + } -const update = (id, uri, secret, opts) => { - return hookApi.update(id, uri, secret, opts).then(hook => { + async update (id, uri, secret, opts) { + const hook = await hookApi.update(id, uri, secret, opts) if (opts.json) output(JSON.stringify(hook, null, 2)) else if (opts.parseable) { output(Object.keys(hook).join('\t')) output(Object.keys(hook).map(k => hook[k]).join('\t')) } else if (!opts.silent && opts.loglevel !== 'silent') { - output(`+ ${hookName(hook)} ${ + output(`+ ${this.hookName(hook)} ${ opts.unicode ? ' ➜ ' : ' -> ' } ${hook.endpoint}`) } - }) -} + } -const hookName = (hook) => { - let target = hook.name - if (hook.type === 'scope') - target = '@' + target - if (hook.type === 'owner') - target = '~' + target - return target + hookName (hook) { + let target = hook.name + if (hook.type === 'scope') + target = '@' + target + if (hook.type === 'owner') + target = '~' + target + return target + } } - -module.exports = Object.assign(cmd, { usage }) +module.exports = Hook diff --git a/deps/npm/lib/init.js b/deps/npm/lib/init.js index a029779f89..af97a9614e 100644 --- a/deps/npm/lib/init.js +++ b/deps/npm/lib/init.js @@ -1,88 +1,97 @@ const initJson = require('init-package-json') const npa = require('npm-package-arg') -const npm = require('./npm.js') const usageUtil = require('./utils/usage.js') const output = require('./utils/output.js') -const usage = usageUtil( - 'init', - '\nnpm init [--force|-f|--yes|-y|--scope]' + - '\nnpm init <@scope> (same as `npx <@scope>/create`)' + - '\nnpm init [<@scope>/]<name> (same as `npx [<@scope>/]create-<name>`)' -) - -const cmd = (args, cb) => init(args).then(() => cb()).catch(cb) +class Init { + constructor (npm) { + this.npm = npm + } -const init = async args => { - // the new npx style way - if (args.length) { - const initerName = args[0] - let packageName = initerName - if (/^@[^/]+$/.test(initerName)) - packageName = initerName + '/create' - else { - const req = npa(initerName) - if (req.type === 'git' && req.hosted) { - const { user, project } = req.hosted - packageName = initerName - .replace(user + '/' + project, user + '/create-' + project) - } else if (req.registry) { - packageName = req.name.replace(/^(@[^/]+\/)?/, '$1create-') - if (req.rawSpec) - packageName += '@' + req.rawSpec - } else { - throw Object.assign(new Error( - 'Unrecognized initializer: ' + initerName + - '\nFor more package binary executing power check out `npx`:' + - '\nhttps://www.npmjs.com/package/npx' - ), { code: 'EUNSUPPORTED' }) - } - } - npm.config.set('package', []) - const newArgs = [packageName, ...args.slice(1)] - return new Promise((res, rej) => { - npm.commands.exec(newArgs, er => er ? rej(er) : res()) - }) + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil( + 'init', + '\nnpm init [--force|-f|--yes|-y|--scope]' + + '\nnpm init <@scope> (same as `npx <@scope>/create`)' + + '\nnpm init [<@scope>/]<name> (same as `npx [<@scope>/]create-<name>`)' + ) } - // the old way - const dir = process.cwd() - npm.log.pause() - npm.log.disableProgress() - const initFile = npm.config.get('init-module') - if (!npm.flatOptions.yes && !npm.flatOptions.force) { - 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.', - '', - 'See `npm help init` for definitive documentation on these fields', - 'and exactly what they do.', - '', - 'Use `npm install <pkg>` afterwards to install a package and', - 'save it as a dependency in the package.json file.', - '', - 'Press ^C at any time to quit.', - ].join('\n')) + exec (args, cb) { + this.init(args).then(() => cb()).catch(cb) } - // XXX promisify init-package-json - await new Promise((res, rej) => { - initJson(dir, initFile, npm.config, (er, data) => { - npm.log.resume() - npm.log.enableProgress() - npm.log.silly('package data', data) - if (er && er.message === 'canceled') { - npm.log.warn('init', 'canceled') - return res() - } - if (er) - rej(er) + + async init (args) { + // the new npx style way + if (args.length) { + const initerName = args[0] + let packageName = initerName + if (/^@[^/]+$/.test(initerName)) + packageName = initerName + '/create' else { - npm.log.info('init', 'written successfully') - res(data) + const req = npa(initerName) + if (req.type === 'git' && req.hosted) { + const { user, project } = req.hosted + packageName = initerName + .replace(user + '/' + project, user + '/create-' + project) + } else if (req.registry) { + packageName = req.name.replace(/^(@[^/]+\/)?/, '$1create-') + if (req.rawSpec) + packageName += '@' + req.rawSpec + } else { + throw Object.assign(new Error( + 'Unrecognized initializer: ' + initerName + + '\nFor more package binary executing power check out `npx`:' + + '\nhttps://www.npmjs.com/package/npx' + ), { code: 'EUNSUPPORTED' }) + } } + this.npm.config.set('package', []) + const newArgs = [packageName, ...args.slice(1)] + return new Promise((res, rej) => { + this.npm.commands.exec(newArgs, er => er ? rej(er) : res()) + }) + } + + // the old way + const dir = process.cwd() + 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) { + 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.', + '', + 'See `npm help init` for definitive documentation on these fields', + 'and exactly what they do.', + '', + 'Use `npm install <pkg>` afterwards to install a package and', + 'save it as a dependency in the package.json file.', + '', + 'Press ^C at any time to quit.', + ].join('\n')) + } + // XXX promisify init-package-json + await new Promise((res, rej) => { + initJson(dir, initFile, this.npm.config, (er, data) => { + this.npm.log.resume() + this.npm.log.enableProgress() + this.npm.log.silly('package data', data) + if (er && er.message === 'canceled') { + this.npm.log.warn('init', 'canceled') + return res() + } + if (er) + rej(er) + else { + this.npm.log.info('init', 'written successfully') + res(data) + } + }) }) - }) + } } - -module.exports = Object.assign(cmd, { usage }) +module.exports = Init diff --git a/deps/npm/lib/install-ci-test.js b/deps/npm/lib/install-ci-test.js index 52c41c413a..d1740999d4 100644 --- a/deps/npm/lib/install-ci-test.js +++ b/deps/npm/lib/install-ci-test.js @@ -1,19 +1,27 @@ // npm install-ci-test // Runs `npm ci` and then runs `npm test` -const ci = require('./ci.js') -const test = require('./test.js') const usageUtil = require('./utils/usage.js') -const usage = usageUtil( - 'install-ci-test', - 'npm install-ci-test [args]' + - '\nSame args as `npm ci`' -) +class InstallCITest { + constructor (npm) { + this.npm = npm + } -const completion = ci.completion + get usage () { + return usageUtil( + 'install-ci-test', + 'npm install-ci-test [args]' + + '\nSame args as `npm ci`' + ) + } -const ciTest = (args, cb) => - ci(args, er => er ? cb(er) : test([], cb)) - -module.exports = Object.assign(ciTest, { usage, completion }) + exec (args, cb) { + this.npm.commands.ci(args, (er) => { + if (er) + return cb(er) + this.npm.commands.test([], cb) + }) + } +} +module.exports = InstallCITest diff --git a/deps/npm/lib/install-test.js b/deps/npm/lib/install-test.js index 9593361e32..487f8da00b 100644 --- a/deps/npm/lib/install-test.js +++ b/deps/npm/lib/install-test.js @@ -1,19 +1,31 @@ // npm install-test // Runs `npm install` and then runs `npm test` -const install = require('./install.js') -const test = require('./test.js') const usageUtil = require('./utils/usage.js') -const usage = usageUtil( - 'install-test', - 'npm install-test [args]' + - '\nSame args as `npm install`' -) +class InstallTest { + constructor (npm) { + this.npm = npm + } -const completion = install.completion + get usage () { + return usageUtil( + 'install-test', + 'npm install-test [args]' + + '\nSame args as `npm install`' + ) + } -const installTest = (args, cb) => - install(args, er => er ? cb(er) : test([], cb)) + async completion (opts) { + return this.npm.commands.install.completion(opts) + } -module.exports = Object.assign(installTest, { usage, completion }) + exec (args, cb) { + this.npm.commands.install(args, (er) => { + if (er) + return cb(er) + this.npm.commands.test([], cb) + }) + } +} +module.exports = InstallTest diff --git a/deps/npm/lib/install.js b/deps/npm/lib/install.js index 5f0137db1c..d7fd384d5b 100644 --- a/deps/npm/lib/install.js +++ b/deps/npm/lib/install.js @@ -3,7 +3,6 @@ const fs = require('fs') const util = require('util') const readdir = util.promisify(fs.readdir) -const npm = require('./npm.js') const usageUtil = require('./utils/usage.js') const reifyFinish = require('./utils/reify-finish.js') const log = require('npmlog') @@ -11,133 +10,143 @@ const { resolve, join } = require('path') const Arborist = require('@npmcli/arborist') const runScript = require('@npmcli/run-script') -const cmd = async (args, cb) => install(args).then(() => cb()).catch(cb) - -const install = async args => { - // the /path/to/node_modules/.. - const globalTop = resolve(npm.globalDir, '..') - const { ignoreScripts, global: isGlobalInstall } = npm.flatOptions - const where = isGlobalInstall ? globalTop : npm.prefix - - // don't try to install the prefix into itself - args = args.filter(a => resolve(a) !== npm.prefix) - - // `npm i -g` => "install this package globally" - if (where === globalTop && !args.length) - args = ['.'] - - // TODO: Add warnings for other deprecated flags? or remove this one? - if (npm.config.get('dev')) - log.warn('install', 'Usage of the `--dev` option is deprecated. Use `--include=dev` instead.') - - const arb = new Arborist({ - ...npm.flatOptions, - path: where, - }) +class Install { + constructor (npm) { + this.npm = npm + } - await arb.reify({ - ...npm.flatOptions, - add: args, - }) - if (!args.length && !isGlobalInstall && !ignoreScripts) { - const { scriptShell } = npm.flatOptions - const scripts = [ - 'preinstall', + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil( 'install', - 'postinstall', - 'prepublish', // XXX should we remove this finally?? - 'preprepare', - 'prepare', - 'postprepare', - ] - for (const event of scripts) { - await runScript({ - path: where, - args: [], - scriptShell, - stdio: 'inherit', - stdioString: true, - banner: log.level !== 'silent', - event, - }) - } + 'npm install (with no args, in package dir)' + + '\nnpm install [<@scope>/]<pkg>' + + '\nnpm install [<@scope>/]<pkg>@<tag>' + + '\nnpm install [<@scope>/]<pkg>@<version>' + + '\nnpm install [<@scope>/]<pkg>@<version range>' + + '\nnpm install <alias>@npm:<name>' + + '\nnpm install <folder>' + + '\nnpm install <tarball file>' + + '\nnpm install <tarball url>' + + '\nnpm install <git:// url>' + + '\nnpm install <github username>/<github project>', + '[--save-prod|--save-dev|--save-optional|--save-peer] [--save-exact] [--no-save]' + ) } - await reifyFinish(arb) -} -const usage = usageUtil( - 'install', - 'npm install (with no args, in package dir)' + - '\nnpm install [<@scope>/]<pkg>' + - '\nnpm install [<@scope>/]<pkg>@<tag>' + - '\nnpm install [<@scope>/]<pkg>@<version>' + - '\nnpm install [<@scope>/]<pkg>@<version range>' + - '\nnpm install <alias>@npm:<name>' + - '\nnpm install <folder>' + - '\nnpm install <tarball file>' + - '\nnpm install <tarball url>' + - '\nnpm install <git:// url>' + - '\nnpm install <github username>/<github project>', - '[--save-prod|--save-dev|--save-optional|--save-peer] [--save-exact] [--no-save]' -) + async completion (opts) { + const { partialWord } = opts + // install can complete to a folder with a package.json, or any package. + // if it has a slash, then it's gotta be a folder + // if it starts with https?://, then just give up, because it's a url + if (/^https?:\/\//.test(partialWord)) { + // do not complete to URLs + return [] + } -const completion = async (opts) => { - const { partialWord } = opts - // install can complete to a folder with a package.json, or any package. - // if it has a slash, then it's gotta be a folder - // if it starts with https?://, then just give up, because it's a url - if (/^https?:\/\//.test(partialWord)) { - // do not complete to URLs - return [] - } + if (/\//.test(partialWord)) { + // Complete fully to folder if there is exactly one match and it + // is a folder containing a package.json file. If that is not the + // case we return 0 matches, which will trigger the default bash + // complete. + const lastSlashIdx = partialWord.lastIndexOf('/') + const partialName = partialWord.slice(lastSlashIdx + 1) + const partialPath = partialWord.slice(0, lastSlashIdx) || '/' - if (/\//.test(partialWord)) { - // Complete fully to folder if there is exactly one match and it - // is a folder containing a package.json file. If that is not the - // case we return 0 matches, which will trigger the default bash - // complete. - const lastSlashIdx = partialWord.lastIndexOf('/') - const partialName = partialWord.slice(lastSlashIdx + 1) - const partialPath = partialWord.slice(0, lastSlashIdx) || '/' + const annotatePackageDirMatch = async (sibling) => { + const fullPath = join(partialPath, sibling) + if (sibling.slice(0, partialName.length) !== partialName) + return null // not name match - const annotatePackageDirMatch = async (sibling) => { - const fullPath = join(partialPath, sibling) - if (sibling.slice(0, partialName.length) !== partialName) - return null // not name match + try { + const contents = await readdir(fullPath) + return { + fullPath, + isPackage: contents.indexOf('package.json') !== -1, + } + } catch (er) { + return { isPackage: false } + } + } try { - const contents = await readdir(fullPath) - return { - fullPath, - isPackage: contents.indexOf('package.json') !== -1, + const siblings = await readdir(partialPath) + const matches = await Promise.all( + siblings.map(async sibling => { + return await annotatePackageDirMatch(sibling) + }) + ) + const match = matches.filter(el => !el || el.isPackage).pop() + if (match) { + // Success - only one match and it is a package dir + return [match.fullPath] + } else { + // no matches + return [] } } catch (er) { - return { isPackage: false } + return [] // invalid dir: no matching } } + // Note: there used to be registry completion here, + // but it stopped making sense somewhere around + // 50,000 packages on the registry + } + + exec (args, cb) { + this.install(args).then(() => cb()).catch(cb) + } + + async install (args) { + // the /path/to/node_modules/.. + const globalTop = resolve(this.npm.globalDir, '..') + const { ignoreScripts, global: isGlobalInstall } = this.npm.flatOptions + const where = isGlobalInstall ? globalTop : this.npm.prefix - try { - const siblings = await readdir(partialPath) - const matches = await Promise.all( - siblings.map(async sibling => { - return await annotatePackageDirMatch(sibling) + // don't try to install the prefix into itself + args = args.filter(a => resolve(a) !== this.npm.prefix) + + // `npm i -g` => "install this package globally" + if (where === globalTop && !args.length) + args = ['.'] + + // TODO: Add warnings for other deprecated flags? or remove this one? + if (this.npm.config.get('dev')) + log.warn('install', 'Usage of the `--dev` option is deprecated. Use `--include=dev` instead.') + + const arb = new Arborist({ + ...this.npm.flatOptions, + path: where, + }) + + await arb.reify({ + ...this.npm.flatOptions, + add: args, + }) + if (!args.length && !isGlobalInstall && !ignoreScripts) { + const { scriptShell } = this.npm.flatOptions + const scripts = [ + 'preinstall', + 'install', + 'postinstall', + 'prepublish', // XXX should we remove this finally?? + 'preprepare', + 'prepare', + 'postprepare', + ] + for (const event of scripts) { + await runScript({ + path: where, + args: [], + scriptShell, + stdio: 'inherit', + stdioString: true, + banner: log.level !== 'silent', + event, }) - ) - const match = matches.filter(el => !el || el.isPackage).pop() - if (match) { - // Success - only one match and it is a package dir - return [match.fullPath] - } else { - // no matches - return [] } - } catch (er) { - return [] // invalid dir: no matching } + await reifyFinish(this.npm, arb) } - // Note: there used to be registry completion here, - // but it stopped making sense somewhere around - // 50,000 packages on the registry } - -module.exports = Object.assign(cmd, { usage, completion }) +module.exports = Install diff --git a/deps/npm/lib/link.js b/deps/npm/lib/link.js index 0bb3d87b5e..6d5e207105 100644 --- a/deps/npm/lib/link.js +++ b/deps/npm/lib/link.js @@ -8,145 +8,154 @@ const npa = require('npm-package-arg') const rpj = require('read-package-json-fast') const semver = require('semver') -const npm = require('./npm.js') const usageUtil = require('./utils/usage.js') const reifyFinish = require('./utils/reify-finish.js') -const completion = async (opts) => { - const dir = npm.globalDir - const files = await readdir(dir) - return files.filter(f => !/^[._-]/.test(f)) -} +class Link { + constructor (npm) { + this.npm = npm + } -const usage = usageUtil( - 'link', - 'npm link (in package dir)' + - '\nnpm link [<@scope>/]<pkg>[@<version>]' -) - -const cmd = (args, cb) => link(args).then(() => cb()).catch(cb) - -const link = async args => { - if (npm.config.get('global')) { - throw Object.assign( - new Error( - 'link should never be --global.\n' + - 'Please re-run this command with --local' - ), - { code: 'ELINKGLOBAL' } + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil( + 'link', + 'npm link (in package dir)' + + '\nnpm link [<@scope>/]<pkg>[@<version>]' ) } - // link with no args: symlink the folder to the global location - // link with package arg: symlink the global to the local - args = args.filter(a => resolve(a) !== npm.prefix) - return args.length - ? linkInstall(args) - : linkPkg() -} + async completion (opts) { + const dir = this.npm.globalDir + const files = await readdir(dir) + return files.filter(f => !/^[._-]/.test(f)) + } -// Returns a list of items that can't be fulfilled by -// things found in the current arborist inventory -const missingArgsFromTree = (tree, args) => { - if (tree.isLink) - return missingArgsFromTree(tree.target, args) - - const foundNodes = [] - const missing = args.filter(a => { - const arg = npa(a) - const nodes = tree.children.values() - const argFound = [...nodes].every(node => { - // TODO: write tests for unmatching version specs, this is hard to test - // atm but should be simple once we have a mocked registry again - if (arg.name !== node.name /* istanbul ignore next */ || ( - arg.version && - !semver.satisfies(node.version, arg.version) - )) { - foundNodes.push(node) - return true - } - }) - return argFound - }) + exec (args, cb) { + this.link(args).then(() => cb()).catch(cb) + } - // remote nodes from the loaded tree in order - // to avoid dropping them later when reifying - for (const node of foundNodes) - node.parent = null + async link (args) { + if (this.npm.config.get('global')) { + throw Object.assign( + new Error( + 'link should never be --global.\n' + + 'Please re-run this command with --local' + ), + { code: 'ELINKGLOBAL' } + ) + } + + // link with no args: symlink the folder to the global location + // link with package arg: symlink the global to the local + args = args.filter(a => resolve(a) !== this.npm.prefix) + return args.length + ? this.linkInstall(args) + : this.linkPkg() + } - return missing -} + async linkInstall (args) { + // load current packages from the global space, + // and then add symlinks installs locally + const globalTop = resolve(this.npm.globalDir, '..') + const globalOpts = { + ...this.npm.flatOptions, + path: globalTop, + global: true, + prune: false, + } + const globalArb = new Arborist(globalOpts) + + // get only current top-level packages from the global space + const globals = await globalArb.loadActual({ + filter: (node, kid) => + !node.isRoot || args.some(a => npa(a).name === kid), + }) -const linkInstall = async args => { - // load current packages from the global space, - // and then add symlinks installs locally - const globalTop = resolve(npm.globalDir, '..') - const globalOpts = { - ...npm.flatOptions, - path: globalTop, - global: true, - prune: false, - } - const globalArb = new Arborist(globalOpts) - - // get only current top-level packages from the global space - const globals = await globalArb.loadActual({ - filter: (node, kid) => - !node.isRoot || args.some(a => npa(a).name === kid), - }) - - // any extra arg that is missing from the current - // global space should be reified there first - const missing = missingArgsFromTree(globals, args) - if (missing.length) { - await globalArb.reify({ - ...globalOpts, - add: missing, + // any extra arg that is missing from the current + // global space should be reified there first + const missing = this.missingArgsFromTree(globals, args) + if (missing.length) { + await globalArb.reify({ + ...globalOpts, + add: missing, + }) + } + + // get a list of module names that should be linked in the local prefix + const names = [] + for (const a of args) { + const arg = npa(a) + names.push( + arg.type === 'directory' + ? (await rpj(resolve(arg.fetchSpec, 'package.json'))).name + : arg.name + ) + } + + // 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) + + // create a new arborist instance for the local prefix and + // reify all the pending names as symlinks there + const localArb = new Arborist({ + ...this.npm.flatOptions, + path: this.npm.prefix, + save, }) + await localArb.reify({ + ...this.npm.flatOptions, + path: this.npm.prefix, + add: names.map(l => `file:${resolve(globalTop, 'node_modules', l)}`), + save, + }) + + await reifyFinish(this.npm, localArb) } - // get a list of module names that should be linked in the local prefix - const names = [] - for (const a of args) { - const arg = npa(a) - names.push( - arg.type === 'directory' - ? (await rpj(resolve(arg.fetchSpec, 'package.json'))).name - : arg.name - ) + async linkPkg () { + const globalTop = resolve(this.npm.globalDir, '..') + const arb = new Arborist({ + ...this.npm.flatOptions, + path: globalTop, + global: true, + }) + await arb.reify({ add: [`file:${this.npm.prefix}`] }) + await reifyFinish(this.npm, arb) } - // npm link should not save=true by default unless you're - // using any of --save-dev or other types - const save = - Boolean(npm.config.find('save') !== 'default' || npm.flatOptions.saveType) - - // create a new arborist instance for the local prefix and - // reify all the pending names as symlinks there - const localArb = new Arborist({ - ...npm.flatOptions, - path: npm.prefix, - save, - }) - await localArb.reify({ - ...npm.flatOptions, - path: npm.prefix, - add: names.map(l => `file:${resolve(globalTop, 'node_modules', l)}`), - save, - }) - - await reifyFinish(localArb) -} + // Returns a list of items that can't be fulfilled by + // things found in the current arborist inventory + missingArgsFromTree (tree, args) { + if (tree.isLink) + return this.missingArgsFromTree(tree.target, args) + + const foundNodes = [] + const missing = args.filter(a => { + const arg = npa(a) + const nodes = tree.children.values() + const argFound = [...nodes].every(node => { + // TODO: write tests for unmatching version specs, this is hard to test + // atm but should be simple once we have a mocked registry again + if (arg.name !== node.name /* istanbul ignore next */ || ( + arg.version && + !semver.satisfies(node.version, arg.version) + )) { + foundNodes.push(node) + return true + } + }) + return argFound + }) -const linkPkg = async () => { - const globalTop = resolve(npm.globalDir, '..') - const arb = new Arborist({ - ...npm.flatOptions, - path: globalTop, - global: true, - }) - await arb.reify({ add: [`file:${npm.prefix}`] }) - await reifyFinish(arb) -} + // remote nodes from the loaded tree in order + // to avoid dropping them later when reifying + for (const node of foundNodes) + node.parent = null -module.exports = Object.assign(cmd, { completion, usage }) + return missing + } +} +module.exports = Link diff --git a/deps/npm/lib/ll.js b/deps/npm/lib/ll.js index 1d5a6217da..7915f5d27c 100644 --- a/deps/npm/lib/ll.js +++ b/deps/npm/lib/ll.js @@ -1,9 +1,19 @@ -const { usage, completion } = require('./ls.js') -const npm = require('./npm.js') +const LS = require('./ls.js') +const usageUtil = require('./utils/usage.js') -const cmd = (args, cb) => { - npm.config.set('long', true) - return npm.commands.ls(args, cb) +class LL extends LS { + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil( + 'll', + 'npm ll [[<@scope>/]<pkg> ...]' + ) + } + + exec (args, cb) { + this.npm.config.set('long', true) + super.exec(args, cb) + } } -module.exports = Object.assign(cmd, { usage, completion }) +module.exports = LL diff --git a/deps/npm/lib/logout.js b/deps/npm/lib/logout.js index d2762c1ba3..9fb1eab21a 100644 --- a/deps/npm/lib/logout.js +++ b/deps/npm/lib/logout.js @@ -1,44 +1,52 @@ -const eu = encodeURIComponent const log = require('npmlog') const getAuth = require('npm-registry-fetch/auth.js') const npmFetch = require('npm-registry-fetch') -const npm = require('./npm.js') const usageUtil = require('./utils/usage.js') -const usage = usageUtil( - 'logout', - 'npm logout [--registry=<url>] [--scope=<@scope>]' -) - -const cmd = (args, cb) => logout(args).then(() => cb()).catch(cb) - -const logout = async (args) => { - const { registry, scope } = npm.flatOptions - const regRef = scope ? `${scope}:registry` : 'registry' - const reg = npm.flatOptions[regRef] || registry - - const auth = getAuth(reg, npm.flatOptions) - - if (auth.token) { - log.verbose('logout', `clearing token for ${reg}`) - await npmFetch(`/-/user/token/${eu(auth.token)}`, { - ...npm.flatOptions, - method: 'DELETE', - ignoreBody: true, - }) - } else if (auth.username || auth.password) - log.verbose('logout', `clearing user credentials for ${reg}`) - else { - const msg = `not logged in to ${reg}, so can't log out!` - throw Object.assign(new Error(msg), { code: 'ENEEDAUTH' }) +class Logout { + constructor (npm) { + this.npm = npm } - if (scope) - npm.config.delete(regRef, 'user') + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil( + 'logout', + 'npm logout [--registry=<url>] [--scope=<@scope>]' + ) + } - npm.config.clearCredentialsByURI(reg) + exec (args, cb) { + this.logout(args).then(() => cb()).catch(cb) + } - await npm.config.save('user') + async logout (args) { + const { registry, scope } = this.npm.flatOptions + const regRef = scope ? `${scope}:registry` : 'registry' + const reg = this.npm.flatOptions[regRef] || registry + + const auth = getAuth(reg, this.npm.flatOptions) + + if (auth.token) { + log.verbose('logout', `clearing token for ${reg}`) + await npmFetch(`/-/user/token/${encodeURIComponent(auth.token)}`, { + ...this.npm.flatOptions, + method: 'DELETE', + ignoreBody: true, + }) + } else if (auth.username || auth.password) + log.verbose('logout', `clearing user credentials for ${reg}`) + else { + const msg = `not logged in to ${reg}, so can't log out!` + throw Object.assign(new Error(msg), { code: 'ENEEDAUTH' }) + } + + if (scope) + this.npm.config.delete(regRef, 'user') + + this.npm.config.clearCredentialsByURI(reg) + + await this.npm.config.save('user') + } } - -module.exports = Object.assign(cmd, { usage }) +module.exports = Logout diff --git a/deps/npm/lib/ls.js b/deps/npm/lib/ls.js index 603c3b412d..359fe21e6f 100644 --- a/deps/npm/lib/ls.js +++ b/deps/npm/lib/ls.js @@ -7,7 +7,6 @@ const Arborist = require('@npmcli/arborist') const { breadth } = require('treeverse') const npa = require('npm-package-arg') -const npm = require('./npm.js') const usageUtil = require('./utils/usage.js') const completion = require('./utils/completion/installed-deep.js') const output = require('./utils/output.js') @@ -24,20 +23,166 @@ const _problems = Symbol('problems') const _required = Symbol('required') const _type = Symbol('type') -const usage = usageUtil( - 'ls', - 'npm ls [[<@scope>/]<pkg> ...]' -) +class LS { + constructor (npm) { + this.npm = npm + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil( + 'ls', + 'npm ls [[<@scope>/]<pkg> ...]' + ) + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ + async completion (opts) { + return completion(this.npm, opts) + } + + exec (args, cb) { + this.ls(args).then(() => cb()).catch(cb) + } -const cmd = (args, cb) => ls(args).then(() => cb()).catch(cb) + 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 dev = this.npm.config.get('dev') + const development = this.npm.config.get('development') + const link = this.npm.config.get('link') + const only = this.npm.config.get('only') + const prod = this.npm.config.get('prod') + const production = this.npm.config.get('production') + + const arb = new Arborist({ + global, + ...this.npm.flatOptions, + legacyPeerDeps: false, + path, + }) + const tree = await this.initTree({arb, args }) + + const seenItems = new Set() + const seenNodes = new Map() + const problems = new Set() + + // defines special handling of printed depth when filtering with args + const filterDefaultDepth = depth === null ? Infinity : depth + const depthToPrint = (all || args.length) + ? filterDefaultDepth + : (depth || 0) + + // add root node of tree to list of seenNodes + seenNodes.set(tree.path, tree) + + // tree traversal happens here, using treeverse.breadth + const result = await breadth({ + tree, + // recursive method, `node` is going to be the current elem (starting from + // the `tree` obj) that was just visited in the `visit` method below + // `nodeResult` is going to be the returned `item` from `visit` + getChildren (node, nodeResult) { + const seenPaths = new Set() + const shouldSkipChildren = + !(node instanceof Arborist.Node) || (node[_depth] > depthToPrint) + return (shouldSkipChildren) + ? [] + : [...(node.target || node).edgesOut.values()] + .filter(filterByEdgesTypes({ + dev, + development, + link, + node, + prod, + production, + only, + tree, + })) + .map(mapEdgesToNodes({ seenPaths })) + .concat(appendExtraneousChildren({ node, seenPaths })) + .sort(sortAlphabetically) + .map(augmentNodesWithMetadata({ + args, + currentDepth: node[_depth], + nodeResult, + seenNodes, + })) + }, + // visit each `node` of the `tree`, returning an `item` - these are + // the elements that will be used to build the final output + visit (node) { + node[_problems] = getProblems(node, { global }) + + const item = json + ? getJsonOutputItem(node, { global, long }) + : parseable + ? null + : getHumanOutputItem(node, { args, color, global, long }) + + // loop through list of node problems to add them to global list + if (node[_include]) { + for (const problem of node[_problems]) + problems.add(problem) + } + + seenItems.add(item) + + // return a promise so we don't blow the stack + return Promise.resolve(item) + }, + }) + + // handle the special case of a broken package.json in the root folder + const [rootError] = tree.errors.filter(e => + e.code === 'EJSONPARSE' && e.path === resolve(path, 'package.json')) + + output( + json + ? jsonOutput({ path, problems, result, rootError, seenItems }) + : parseable + ? parseableOutput({ seenNodes, global, long }) + : humanOutput({ color, result, seenItems, unicode }) + ) -const initTree = async ({ arb, args, json }) => { - const tree = await arb.loadActual() - tree[_include] = args.length === 0 - tree[_depth] = 0 + // if filtering items, should exit with error code on no results + if (result && !result[_include] && args.length) + process.exitCode = 1 - return tree + if (rootError) { + throw Object.assign( + new Error('Failed to parse root package.json'), + { code: 'EJSONPARSE' } + ) + } + + if (problems.size) { + throw Object.assign( + new Error([...problems].join(EOL)), + { code: 'ELSPROBLEMS' } + ) + } + } + + async initTree ({ arb, args }) { + const tree = await arb.loadActual() + tree[_include] = args.length === 0 + tree[_depth] = 0 + + return tree + } } +module.exports = LS const isGitNode = (node) => { if (!node.resolved) @@ -252,7 +397,6 @@ const augmentNodesWithMetadata = ({ args, currentDepth, nodeResult, - parseable, seenNodes, }) => (node) => { // if the original edge was a deduped dep, treeverse will fail to @@ -285,7 +429,7 @@ const augmentNodesWithMetadata = ({ // _filteredBy is used to apply extra color info to the item that // was used in args in order to filter node[_filteredBy] = node[_include] = - filterByPositionalArgs(args, { node: seenNodes.get(node.path), seenNodes }) + filterByPositionalArgs(args, { node: seenNodes.get(node.path) }) // _depth keeps track of how many levels deep tree traversal currently is // so that we can `npm ls --depth=1` node[_depth] = currentDepth + 1 @@ -359,140 +503,3 @@ const parseableOutput = ({ global, long, seenNodes }) => { } return out.trim() } - -const ls = async (args) => { - const { - all, - color, - depth, - json, - long, - global, - parseable, - prefix, - unicode, - } = npm.flatOptions - const path = global ? resolve(npm.globalDir, '..') : prefix - const dev = npm.config.get('dev') - const development = npm.config.get('development') - const link = npm.config.get('link') - const only = npm.config.get('only') - const prod = npm.config.get('prod') - const production = npm.config.get('production') - - const arb = new Arborist({ - global, - ...npm.flatOptions, - legacyPeerDeps: false, - path, - }) - const tree = await initTree({ - arb, - args, - global, - json, - }) - - const seenItems = new Set() - const seenNodes = new Map() - const problems = new Set() - - // defines special handling of printed depth when filtering with args - const filterDefaultDepth = depth === null ? Infinity : depth - const depthToPrint = (all || args.length) - ? filterDefaultDepth - : (depth || 0) - - // add root node of tree to list of seenNodes - seenNodes.set(tree.path, tree) - - // tree traversal happens here, using treeverse.breadth - const result = await breadth({ - tree, - // recursive method, `node` is going to be the current elem (starting from - // the `tree` obj) that was just visited in the `visit` method below - // `nodeResult` is going to be the returned `item` from `visit` - getChildren (node, nodeResult) { - const seenPaths = new Set() - const shouldSkipChildren = - !(node instanceof Arborist.Node) || (node[_depth] > depthToPrint) - return (shouldSkipChildren) - ? [] - : [...(node.target || node).edgesOut.values()] - .filter(filterByEdgesTypes({ - dev, - development, - link, - node, - prod, - production, - only, - tree, - })) - .map(mapEdgesToNodes({ seenPaths })) - .concat(appendExtraneousChildren({ node, seenPaths })) - .sort(sortAlphabetically) - .map(augmentNodesWithMetadata({ - args, - currentDepth: node[_depth], - nodeResult, - parseable, - seenNodes, - })) - }, - // visit each `node` of the `tree`, returning an `item` - these are - // the elements that will be used to build the final output - visit (node) { - node[_problems] = getProblems(node, { global }) - - const item = json - ? getJsonOutputItem(node, { global, long }) - : parseable - ? null - : getHumanOutputItem(node, { args, color, global, long }) - - // loop through list of node problems to add them to global list - if (node[_include]) { - for (const problem of node[_problems]) - problems.add(problem) - } - - seenItems.add(item) - - // return a promise so we don't blow the stack - return Promise.resolve(item) - }, - }) - - // handle the special case of a broken package.json in the root folder - const [rootError] = tree.errors.filter(e => - e.code === 'EJSONPARSE' && e.path === resolve(path, 'package.json')) - - output( - json - ? jsonOutput({ path, problems, result, rootError, seenItems }) - : parseable - ? parseableOutput({ seenNodes, global, long }) - : humanOutput({ color, result, seenItems, unicode }) - ) - - // if filtering items, should exit with error code on no results - if (result && !result[_include] && args.length) - process.exitCode = 1 - - if (rootError) { - throw Object.assign( - new Error('Failed to parse root package.json'), - { code: 'EJSONPARSE' } - ) - } - - if (problems.size) { - throw Object.assign( - new Error([...problems].join(EOL)), - { code: 'ELSPROBLEMS' } - ) - } -} - -module.exports = Object.assign(cmd, { usage, completion }) diff --git a/deps/npm/lib/npm.js b/deps/npm/lib/npm.js index 85dc67a78a..1f8c785e75 100644 --- a/deps/npm/lib/npm.js +++ b/deps/npm/lib/npm.js @@ -13,40 +13,28 @@ require('graceful-fs').gracefulify(require('fs')) const procLogListener = require('./utils/proc-log-listener.js') -const hasOwnProperty = (obj, key) => - Object.prototype.hasOwnProperty.call(obj, key) - -// the first time `npm.commands.xyz` is loaded, it gets added -// to the cmds object, so we don't have to load it again. -const proxyCmds = (npm) => { - const cmds = {} - return new Proxy(cmds, { - get: (prop, cmd) => { - if (hasOwnProperty(cmds, cmd)) - return cmds[cmd] - - const actual = deref(cmd) - if (!actual) { - cmds[cmd] = undefined - return cmds[cmd] - } - if (cmds[actual]) { - cmds[cmd] = cmds[actual] - return cmds[cmd] - } - cmds[actual] = makeCmd(actual) - cmds[cmd] = cmds[actual] - return cmds[cmd] - }, - }) -} - -const makeCmd = cmd => { - const impl = require(`./${cmd}.js`) - const fn = (args, cb) => npm[_runCmd](cmd, impl, args, cb) - Object.assign(fn, impl) - return fn -} +const proxyCmds = new Proxy({}, { + get: (target, cmd) => { + const actual = deref(cmd) + if (actual && !Reflect.has(target, actual)) { + const Impl = require(`./${actual}.js`) + const impl = new Impl(npm) + // Our existing npm.commands[x] act like a function with attributes, but + // our modules have non-inumerable attributes so we can't just assign + // them to an anonymous function like we used to. This acts like that + // old way of doing things, until we can make breaking changes to the + // npm.commands[x] api + target[actual] = new Proxy( + (args, cb) => npm[_runCmd](cmd, impl, args, cb), + { + get: (target, attr, receiver) => { + return Reflect.get(impl, attr, receiver) + }, + }) + } + return target[actual] + }, +}) const { types, defaults, shorthands } = require('./utils/config.js') const { shellouts } = require('./utils/cmd-list.js') @@ -68,7 +56,7 @@ const npm = module.exports = new class extends EventEmitter { } this.started = Date.now() this.command = null - this.commands = proxyCmds(this) + this.commands = proxyCmds procLogListener() process.emit('time', 'npm') this.version = require('../package.json').version @@ -121,7 +109,7 @@ const npm = module.exports = new class extends EventEmitter { console.log(impl.usage) cb() } else { - impl(args, er => { + impl.exec(args, er => { process.emit('timeEnd', `command:${cmd}`) cb(er) }) @@ -193,7 +181,7 @@ const npm = module.exports = new class extends EventEmitter { this.title = tokrev ? 'npm token revoke' + (this.argv[2] ? ' ***' : '') : ['npm', ...this.argv].join(' ') - this.color = setupLog(this.config, this) + this.color = setupLog(this.config) process.env.COLOR = this.color ? '1' : '0' cleanUpLogFiles(this.cache, this.config.get('logs-max'), log.warn) diff --git a/deps/npm/lib/org.js b/deps/npm/lib/org.js index aa9c97d497..054e1833db 100644 --- a/deps/npm/lib/org.js +++ b/deps/npm/lib/org.js @@ -1,139 +1,148 @@ const liborg = require('libnpmorg') -const npm = require('./npm.js') +const usageUtil = require('./utils/usage.js') const output = require('./utils/output.js') const otplease = require('./utils/otplease.js') const Table = require('cli-table3') -module.exports = org - -org.subcommands = ['set', 'rm', 'ls'] - -org.usage = - 'npm org set orgname username [developer | admin | owner]\n' + - 'npm org rm orgname username\n' + - 'npm org ls orgname [<username>]' - -org.completion = async (opts) => { - var argv = opts.conf.argv.remain - if (argv.length === 2) - return org.subcommands +class Org { + constructor (npm) { + this.npm = npm + } - switch (argv[2]) { - case 'ls': - case 'add': - case 'rm': - case 'set': - return [] - default: - throw new Error(argv[2] + ' not recognized') + get usage () { + return usageUtil( + 'org', + 'npm org set orgname username [developer | admin | owner]\n' + + 'npm org rm orgname username\n' + + 'npm org ls orgname [<username>]' + ) } -} -function UsageError () { - throw Object.assign(new Error(org.usage), { code: 'EUSAGE' }) -} + async completion (opts) { + const argv = opts.conf.argv.remain + if (argv.length === 2) + return ['set', 'rm', 'ls'] -function org ([cmd, orgname, username, role], cb) { - return otplease(npm.flatOptions, opts => { - switch (cmd) { + switch (argv[2]) { + case 'ls': case 'add': - case 'set': - return orgSet(orgname, username, role, opts) case 'rm': - return orgRm(orgname, username, opts) - case 'ls': - return orgList(orgname, username, opts) + case 'set': + return [] default: - UsageError() + throw new Error(argv[2] + ' not recognized') } - }).then( - x => cb(null, x), - err => cb(err.code === 'EUSAGE' ? err.message : err) - ) -} + } -function orgSet (org, user, role, opts) { - role = role || 'developer' - if (!org) - throw new Error('First argument `orgname` is required.') - - if (!user) - throw new Error('Second argument `username` is required.') - - if (!['owner', 'admin', 'developer'].find(x => x === role)) - throw new Error('Third argument `role` must be one of `owner`, `admin`, or `developer`, with `developer` being the default value if omitted.') - - return liborg.set(org, user, role, opts).then(memDeets => { - if (opts.json) - output(JSON.stringify(memDeets, null, 2)) - else if (opts.parseable) { - output(['org', 'orgsize', 'user', 'role'].join('\t')) - output([ - memDeets.org.name, - memDeets.org.size, - memDeets.user, - memDeets.role, - ].join('\t')) - } else if (!opts.silent && opts.loglevel !== 'silent') - output(`Added ${memDeets.user} as ${memDeets.role} to ${memDeets.org.name}. You now have ${memDeets.org.size} member${memDeets.org.size === 1 ? '' : 's'} in this org.`) - - return memDeets - }) -} + exec (args, cb) { + this.org(args) + .then(x => cb(null, x)) + .catch(err => err.code === 'EUSAGE' + ? cb(err.message) + : cb(err) + ) + } -function orgRm (org, user, opts) { - if (!org) - throw new Error('First argument `orgname` is required.') - - if (!user) - throw new Error('Second argument `username` is required.') - - return liborg.rm(org, user, opts).then(() => { - return liborg.ls(org, opts) - }).then(roster => { - user = user.replace(/^[~@]?/, '') - org = org.replace(/^[~@]?/, '') - const userCount = Object.keys(roster).length - if (opts.json) { - output(JSON.stringify({ - user, - org, - userCount, - deleted: true, - })) - } else if (opts.parseable) { - output(['user', 'org', 'userCount', 'deleted'].join('\t')) - output([user, org, userCount, true].join('\t')) - } else if (!opts.silent && opts.loglevel !== 'silent') - output(`Successfully removed ${user} from ${org}. You now have ${userCount} member${userCount === 1 ? '' : 's'} in this org.`) - }) -} + async org ([cmd, orgname, username, role], cb) { + return otplease(this.npm.flatOptions, opts => { + switch (cmd) { + case 'add': + case 'set': + return this.set(orgname, username, role, opts) + case 'rm': + return this.rm(orgname, username, opts) + case 'ls': + return this.ls(orgname, username, opts) + default: + throw Object.assign(new Error(this.usage), { code: 'EUSAGE' }) + } + }) + } -function orgList (org, user, opts) { - if (!org) - throw new Error('First argument `orgname` is required.') + set (org, user, role, opts) { + role = role || 'developer' + if (!org) + throw new Error('First argument `orgname` is required.') + + if (!user) + throw new Error('Second argument `username` is required.') + + if (!['owner', 'admin', 'developer'].find(x => x === role)) + throw new Error('Third argument `role` must be one of `owner`, `admin`, or `developer`, with `developer` being the default value if omitted.') + + return liborg.set(org, user, role, opts).then(memDeets => { + if (opts.json) + output(JSON.stringify(memDeets, null, 2)) + else if (opts.parseable) { + output(['org', 'orgsize', 'user', 'role'].join('\t')) + output([ + memDeets.org.name, + memDeets.org.size, + memDeets.user, + memDeets.role, + ].join('\t')) + } else if (!opts.silent && opts.loglevel !== 'silent') + output(`Added ${memDeets.user} as ${memDeets.role} to ${memDeets.org.name}. You now have ${memDeets.org.size} member${memDeets.org.size === 1 ? '' : 's'} in this org.`) + + return memDeets + }) + } - return liborg.ls(org, opts).then(roster => { - if (user) { - const newRoster = {} - if (roster[user]) - newRoster[user] = roster[user] + rm (org, user, opts) { + if (!org) + throw new Error('First argument `orgname` is required.') + + if (!user) + throw new Error('Second argument `username` is required.') + + return liborg.rm(org, user, opts).then(() => { + return liborg.ls(org, opts) + }).then(roster => { + user = user.replace(/^[~@]?/, '') + org = org.replace(/^[~@]?/, '') + const userCount = Object.keys(roster).length + if (opts.json) { + output(JSON.stringify({ + user, + org, + userCount, + deleted: true, + })) + } else if (opts.parseable) { + output(['user', 'org', 'userCount', 'deleted'].join('\t')) + output([user, org, userCount, true].join('\t')) + } else if (!opts.silent && opts.loglevel !== 'silent') + output(`Successfully removed ${user} from ${org}. You now have ${userCount} member${userCount === 1 ? '' : 's'} in this org.`) + }) + } - roster = newRoster - } - if (opts.json) - output(JSON.stringify(roster, null, 2)) - else if (opts.parseable) { - output(['user', 'role'].join('\t')) - Object.keys(roster).forEach(user => { - output([user, roster[user]].join('\t')) - }) - } else if (!opts.silent && opts.loglevel !== 'silent') { - const table = new Table({ head: ['user', 'role'] }) - Object.keys(roster).sort().forEach(user => { - table.push([user, roster[user]]) - }) - output(table.toString()) - } - }) + ls (org, user, opts) { + if (!org) + throw new Error('First argument `orgname` is required.') + + return liborg.ls(org, opts).then(roster => { + if (user) { + const newRoster = {} + if (roster[user]) + newRoster[user] = roster[user] + + roster = newRoster + } + if (opts.json) + output(JSON.stringify(roster, null, 2)) + else if (opts.parseable) { + output(['user', 'role'].join('\t')) + Object.keys(roster).forEach(user => { + output([user, roster[user]].join('\t')) + }) + } else if (!opts.silent && opts.loglevel !== 'silent') { + const table = new Table({ head: ['user', 'role'] }) + Object.keys(roster).sort().forEach(user => { + table.push([user, roster[user]]) + }) + output(table.toString()) + } + }) + } } +module.exports = Org diff --git a/deps/npm/lib/outdated.js b/deps/npm/lib/outdated.js index c10f63a12e..fc6967faf6 100644 --- a/deps/npm/lib/outdated.js +++ b/deps/npm/lib/outdated.js @@ -9,112 +9,135 @@ const pickManifest = require('npm-pick-manifest') const Arborist = require('@npmcli/arborist') -const npm = require('./npm.js') const output = require('./utils/output.js') const usageUtil = require('./utils/usage.js') const ansiTrim = require('./utils/ansi-trim.js') -const usage = usageUtil('outdated', - 'npm outdated [[<@scope>/]<pkg> ...]' -) +class Outdated { + constructor (npm) { + this.npm = npm + } -function cmd (args, cb) { - outdated(args) - .then(() => cb()) - .catch(cb) -} + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil('outdated', + 'npm outdated [[<@scope>/]<pkg> ...]' + ) + } -async function outdated (args) { - const opts = npm.flatOptions - const global = path.resolve(npm.globalDir, '..') - const where = opts.global - ? global - : npm.prefix - - const arb = new Arborist({ - ...opts, - path: where, - }) - - const tree = await arb.loadActual() - const list = await outdated_(tree, args, opts) - - // sorts list alphabetically - const outdated = list.sort((a, b) => a.name.localeCompare(b.name)) - - // return if no outdated packages - if (outdated.length === 0 && !opts.json) - return - - // display results - if (opts.json) - output(makeJSON(outdated, opts)) - else if (opts.parseable) - output(makeParseable(outdated, opts)) - else { - const outList = outdated.map(x => makePretty(x, opts)) - const outHead = ['Package', - 'Current', - 'Wanted', - 'Latest', - 'Location', - 'Depended by', - ] - - if (opts.long) - outHead.push('Package Type', 'Homepage') - const outTable = [outHead].concat(outList) - - if (opts.color) - outTable[0] = outTable[0].map(heading => styles.underline(heading)) - - const tableOpts = { - align: ['l', 'r', 'r', 'r', 'l'], - stringLength: s => ansiTrim(s).length, - } - output(table(outTable, tableOpts)) + exec (args, cb) { + this.outdated(args).then(() => cb()).catch(cb) } -} -async function outdated_ (tree, deps, opts) { - const list = [] + async outdated (args) { + this.opts = this.npm.flatOptions + + const global = path.resolve(this.npm.globalDir, '..') + const where = this.opts.global + ? global + : this.npm.prefix + + const arb = new Arborist({ + ...this.opts, + path: where, + }) - const edges = new Set() - function getEdges (nodes, type) { - const getEdgesIn = (node) => { - for (const edge of node.edgesIn) - edges.add(edge) + this.edges = new Set() + this.list = [] + this.tree = await arb.loadActual() + + if (args.length !== 0) { + // specific deps + for (let i = 0; i < args.length; i++) { + const nodes = this.tree.inventory.query('name', args[i]) + this.getEdges(nodes, 'edgesIn') + } + } else { + if (this.opts.all) { + // all deps in tree + const nodes = this.tree.inventory.values() + this.getEdges(nodes, 'edgesOut') + } + // top-level deps + this.getEdges() } - const getEdgesOut = (node) => { - if (opts.global) { - for (const child of node.children.values()) - edges.add(child) - } else { - for (const edge of node.edgesOut.values()) - edges.add(edge) + await Promise.all(Array.from(this.edges).map((edge) => { + return this.getOutdatedInfo(edge) + })) + + // sorts list alphabetically + const outdated = this.list.sort((a, b) => a.name.localeCompare(b.name)) + + // return if no outdated packages + if (outdated.length === 0 && !this.opts.json) + return + + // display results + if (this.opts.json) + output(this.makeJSON(outdated)) + else if (this.opts.parseable) + output(this.makeParseable(outdated)) + else { + const outList = outdated.map(x => this.makePretty(x)) + const outHead = ['Package', + 'Current', + 'Wanted', + 'Latest', + 'Location', + 'Depended by', + ] + + if (this.opts.long) + outHead.push('Package Type', 'Homepage') + const outTable = [outHead].concat(outList) + + if (this.opts.color) + outTable[0] = outTable[0].map(heading => styles.underline(heading)) + + const tableOpts = { + align: ['l', 'r', 'r', 'r', 'l'], + stringLength: s => ansiTrim(s).length, } + output(table(outTable, tableOpts)) } + } + getEdges (nodes, type) { if (!nodes) - return getEdgesOut(tree) + return this.getEdgesOut(this.tree) for (const node of nodes) { type === 'edgesOut' - ? getEdgesOut(node) - : getEdgesIn(node) + ? this.getEdgesOut(node) + : this.getEdgesIn(node) + } + } + + getEdgesIn (node) { + for (const edge of node.edgesIn) + this.edges.add(edge) + } + + getEdgesOut (node) { + if (this.opts.global) { + for (const child of node.children.values()) + this.edges.add(child) + } else { + for (const edge of node.edgesOut.values()) + this.edges.add(edge) } } - async function getPackument (spec) { + async getPackument (spec) { const packument = await pacote.packument(spec, { - ...npm.flatOptions, - fullMetadata: npm.flatOptions.long, + ...this.npm.flatOptions, + fullMetadata: this.npm.flatOptions.long, preferOnline: true, }) return packument } - async function getOutdatedInfo (edge) { + async getOutdatedInfo (edge) { const spec = npa(edge.name) const node = edge.to || edge const { path, location } = node @@ -125,7 +148,7 @@ async function outdated_ (tree, deps, opts) { : edge.dev ? 'devDependencies' : 'dependencies' - for (const omitType of opts.omit || []) { + for (const omitType of this.opts.omit || []) { if (node[omitType]) return } @@ -136,7 +159,7 @@ async function outdated_ (tree, deps, opts) { return try { - const packument = await getPackument(spec) + const packument = await this.getPackument(spec) const expected = edge.spec // if it's not a range, version, or tag, skip it try { @@ -145,15 +168,15 @@ async function outdated_ (tree, deps, opts) { } catch (err) { return null } - const wanted = pickManifest(packument, expected, npm.flatOptions) - const latest = pickManifest(packument, '*', npm.flatOptions) + const wanted = pickManifest(packument, expected, this.npm.flatOptions) + const latest = pickManifest(packument, '*', this.npm.flatOptions) if ( !current || current !== wanted.version || wanted.version !== latest.version ) { - list.push({ + this.list.push({ name: edge.name, path, type, @@ -167,7 +190,7 @@ async function outdated_ (tree, deps, opts) { } } catch (err) { // silently catch and ignore ETARGET, E403 & - // E404 errors, deps are just skipped { + // E404 errors, deps are just skipped if (!( err.code === 'ETARGET' || err.code === 'E403' || @@ -177,113 +200,89 @@ async function outdated_ (tree, deps, opts) { } } - const p = [] - if (deps.length !== 0) { - // specific deps - for (let i = 0; i < deps.length; i++) { - const nodes = tree.inventory.query('name', deps[i]) - getEdges(nodes, 'edgesIn') - } - } else { - if (opts.all) { - // all deps in tree - const nodes = tree.inventory.values() - getEdges(nodes, 'edgesOut') - } - // top-level deps - getEdges() - } - - for (const edge of edges) - p.push(getOutdatedInfo(edge)) - - await Promise.all(p) - return list -} - -// formatting functions -function makePretty (dep, opts) { - const { - current = 'MISSING', - location = '-', - homepage = '', - name, - wanted, - latest, - type, - dependent, - } = dep - - const columns = [name, current, wanted, latest, location, dependent] - - if (opts.long) { - columns[6] = type - columns[7] = homepage - } - - if (opts.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 - } - - return columns -} - -// --parseable creates output like this: -// <fullpath>:<name@wanted>:<name@installed>:<name@latest>:<dependedby> -function makeParseable (list, opts) { - return list.map(dep => { + // formatting functions + makePretty (dep) { const { + current = 'MISSING', + location = '-', + homepage = '', name, - current, wanted, latest, - path, - dependent, type, - homepage, - } = dep - const out = [ - path, - name + '@' + wanted, - current ? (name + '@' + current) : 'MISSING', - name + '@' + latest, dependent, - ] - if (opts.long) - out.push(type, homepage) + } = dep - return out.join(':') - }).join(os.EOL) -} + const columns = [name, current, wanted, latest, location, dependent] -function makeJSON (list, opts) { - const out = {} - list.forEach(dep => { - const { - name, - current, - wanted, - latest, - path, - type, - dependent, - homepage, - } = dep - out[name] = { - current, - wanted, - latest, - dependent, - location: path, + if (this.opts.long) { + columns[6] = type + columns[7] = homepage } - if (opts.long) { - out[name].type = type - out[name].homepage = homepage + + if (this.opts.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 } - }) - return JSON.stringify(out, null, 2) -} -module.exports = Object.assign(cmd, { usage }) + return columns + } + + // --parseable creates output like this: + // <fullpath>:<name@wanted>:<name@installed>:<name@latest>:<dependedby> + makeParseable (list) { + return list.map(dep => { + const { + name, + current, + wanted, + latest, + path, + dependent, + type, + homepage, + } = dep + const out = [ + path, + name + '@' + wanted, + current ? (name + '@' + current) : 'MISSING', + name + '@' + latest, + dependent, + ] + if (this.opts.long) + out.push(type, homepage) + + return out.join(':') + }).join(os.EOL) + } + + makeJSON (list) { + const out = {} + list.forEach(dep => { + const { + name, + current, + wanted, + latest, + path, + type, + dependent, + homepage, + } = dep + out[name] = { + current, + wanted, + latest, + dependent, + location: path, + } + if (this.opts.long) { + out[name].type = type + out[name].homepage = homepage + } + }) + return JSON.stringify(out, null, 2) + } +} +module.exports = Outdated diff --git a/deps/npm/lib/owner.js b/deps/npm/lib/owner.js index 6dce3ec70f..6cb9904880 100644 --- a/deps/npm/lib/owner.js +++ b/deps/npm/lib/owner.js @@ -3,94 +3,138 @@ const npa = require('npm-package-arg') const npmFetch = require('npm-registry-fetch') const pacote = require('pacote') -const npm = require('./npm.js') const output = require('./utils/output.js') const otplease = require('./utils/otplease.js') const readLocalPkg = require('./utils/read-local-package.js') const usageUtil = require('./utils/usage.js') -const usage = usageUtil( - 'owner', - 'npm owner add <user> [<@scope>/]<pkg>' + - '\nnpm owner rm <user> [<@scope>/]<pkg>' + - '\nnpm owner ls [<@scope>/]<pkg>' -) +class Owner { + constructor (npm) { + this.npm = npm + } -const completion = async (opts) => { - const argv = opts.conf.argv.remain - if (argv.length > 3) - return [] + get usage () { + return usageUtil( + 'owner', + 'npm owner add <user> [<@scope>/]<pkg>' + + '\nnpm owner rm <user> [<@scope>/]<pkg>' + + '\nnpm owner ls [<@scope>/]<pkg>' + ) + } - if (argv[1] !== 'owner') - argv.unshift('owner') + get usageError () { + return Object.assign(new Error(this.usage), { code: 'EUSAGE' }) + } - if (argv.length === 2) - return ['add', 'rm', 'ls'] + async completion (opts) { + const argv = opts.conf.argv.remain + if (argv.length > 3) + return [] - // reaches registry in order to autocomplete rm - if (argv[2] === 'rm') { - const opts = { - ...npm.flatOptions, - fullMetadata: true, + if (argv[1] !== 'owner') + argv.unshift('owner') + + if (argv.length === 2) + return ['add', 'rm', 'ls'] + + // reaches registry in order to autocomplete rm + if (argv[2] === 'rm') { + const pkgName = await readLocalPkg(this.npm) + if (!pkgName) + return [] + + const spec = npa(pkgName) + const data = await pacote.packument(spec, { + ...this.npm.flatOptions, + fullMetadata: true, + }) + if (data && data.maintainers && data.maintainers.length) + return data.maintainers.map(m => m.name) } - const pkgName = await readLocalPkg() - if (!pkgName) - return [] + return [] + } - const spec = npa(pkgName) - const data = await pacote.packument(spec, opts) - if (data && data.maintainers && data.maintainers.length) - return data.maintainers.map(m => m.name) + exec (args, cb) { + this.owner(args).then(() => cb()).catch(cb) } - return [] -} -const UsageError = () => - Object.assign(new Error(usage), { code: 'EUSAGE' }) - -const cmd = (args, cb) => owner(args).then(() => cb()).catch(cb) - -const owner = async ([action, ...args]) => { - const opts = npm.flatOptions - switch (action) { - case 'ls': - case 'list': - return ls(args[0], opts) - case 'add': - return add(args[0], args[1], opts) - case 'rm': - case 'remove': - return rm(args[0], args[1], opts) - default: - throw UsageError() + async owner ([action, ...args]) { + const opts = this.npm.flatOptions + switch (action) { + case 'ls': + case 'list': + return this.ls(args[0], opts) + case 'add': + return this.add(args[0], args[1], opts) + case 'rm': + case 'remove': + return this.rm(args[0], args[1], opts) + default: + throw this.usageError + } } -} -const ls = async (pkg, opts) => { - if (!pkg) { - const pkgName = await readLocalPkg() - if (!pkgName) - throw UsageError() + async ls (pkg, opts) { + if (!pkg) { + const pkgName = await readLocalPkg(this.npm) + if (!pkgName) + throw this.usageError + + pkg = pkgName + } + + const spec = npa(pkg) + + try { + const packumentOpts = { ...opts, fullMetadata: true } + const { maintainers } = await pacote.packument(spec, packumentOpts) + if (!maintainers || !maintainers.length) + output('no admin found') + else + output(maintainers.map(o => `${o.name} <${o.email}>`).join('\n')) - pkg = pkgName + return maintainers + } catch (err) { + log.error('owner ls', "Couldn't get owner data", pkg) + throw err + } } - const spec = npa(pkg) + async add (user, pkg, opts) { + if (!user) + throw this.usageError - try { - const packumentOpts = { ...opts, fullMetadata: true } - const { maintainers } = await pacote.packument(spec, packumentOpts) - if (!maintainers || !maintainers.length) - output('no admin found') - else - output(maintainers.map(o => `${o.name} <${o.email}>`).join('\n')) + if (!pkg) { + const pkgName = await readLocalPkg(this.npm) + if (!pkgName) + throw this.usageError - return maintainers - } catch (err) { - log.error('owner ls', "Couldn't get owner data", pkg) - throw err + pkg = pkgName + } + log.verbose('owner add', '%s to %s', user, pkg) + + const spec = npa(pkg) + return putOwners(spec, user, opts, validateAddOwner) + } + + async rm (user, pkg, opts) { + if (!user) + throw this.usageError + + if (!pkg) { + const pkgName = await readLocalPkg(this.npm) + if (!pkgName) + throw this.usageError + + pkg = pkgName + } + log.verbose('owner rm', '%s from %s', user, pkg) + + const spec = npa(pkg) + return putOwners(spec, user, opts, validateRmOwner) } } +module.exports = Owner const validateAddOwner = (newOwner, owners) => { owners = owners || [] @@ -109,23 +153,6 @@ const validateAddOwner = (newOwner, owners) => { ] } -const add = async (user, pkg, opts) => { - if (!user) - throw UsageError() - - if (!pkg) { - const pkgName = await readLocalPkg() - if (!pkgName) - throw UsageError() - - pkg = pkgName - } - log.verbose('owner add', '%s to %s', user, pkg) - - const spec = npa(pkg) - return putOwners(spec, user, opts, validateAddOwner) -} - const validateRmOwner = (rmOwner, owners) => { let found = false const m = owners.filter(function (o) { @@ -151,23 +178,6 @@ const validateRmOwner = (rmOwner, owners) => { return m } -const rm = async (user, pkg, opts) => { - if (!user) - throw UsageError() - - if (!pkg) { - const pkgName = await readLocalPkg() - if (!pkgName) - throw UsageError() - - pkg = pkgName - } - log.verbose('owner rm', '%s from %s', user, pkg) - - const spec = npa(pkg) - return putOwners(spec, user, opts, validateRmOwner) -} - const putOwners = async (spec, user, opts, validation) => { const uri = `/-/user/org.couchdb.user:${encodeURIComponent(user)}` let u = '' @@ -227,5 +237,3 @@ const putOwners = async (spec, user, opts, validation) => { } return res } - -module.exports = Object.assign(cmd, { usage, completion }) diff --git a/deps/npm/lib/pack.js b/deps/npm/lib/pack.js index ff906cc2bd..cf1e77f48e 100644 --- a/deps/npm/lib/pack.js +++ b/deps/npm/lib/pack.js @@ -4,46 +4,53 @@ const pacote = require('pacote') const libpack = require('libnpmpack') const npa = require('npm-package-arg') -const npm = require('./npm.js') const { getContents, logTar } = require('./utils/tar.js') const writeFile = util.promisify(require('fs').writeFile) const output = require('./utils/output.js') const usageUtil = require('./utils/usage.js') -const usage = usageUtil('pack', 'npm pack [[<@scope>/]<pkg>...] [--dry-run]') -const cmd = (args, cb) => pack(args).then(() => cb()).catch(cb) +class Pack { + constructor (npm) { + this.npm = npm + } -const pack = async (args) => { - if (args.length === 0) - args = ['.'] + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil('pack', 'npm pack [[<@scope>/]<pkg>...] [--dry-run]') + } - const { unicode } = npm.flatOptions + exec (args, cb) { + this.pack(args).then(() => cb()).catch(cb) + } - // clone the opts because pacote mutates it with resolved/integrity - const tarballs = await Promise.all(args.map((arg) => - pack_(arg, { ...npm.flatOptions }))) + async pack (args) { + if (args.length === 0) + args = ['.'] - for (const tar of tarballs) { - logTar(tar, { log, unicode }) - output(tar.filename.replace(/^@/, '').replace(/\//, '-')) - } -} + const { unicode } = this.npm.flatOptions -const pack_ = async (arg, opts) => { - const spec = npa(arg) - const { dryRun } = opts - const manifest = await pacote.manifest(spec, opts) - const filename = `${manifest.name}-${manifest.version}.tgz` - .replace(/^@/, '').replace(/\//, '-') - const tarballData = await libpack(arg, opts) - const pkgContents = await getContents(manifest, tarballData) + // 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 manifest = await pacote.manifest(spec, this.npm.flatOptions) + const filename = `${manifest.name}-${manifest.version}.tgz` + .replace(/^@/, '').replace(/\//, '-') + const tarballData = await libpack(arg, this.npm.flatOptions) + const pkgContents = await getContents(manifest, tarballData) - if (!dryRun) - await writeFile(filename, tarballData) + if (!dryRun) + await writeFile(filename, tarballData) - return pkgContents -} + return pkgContents + })) -module.exports = Object.assign(cmd, { usage }) + for (const tar of tarballs) { + logTar(tar, { log, unicode }) + output(tar.filename.replace(/^@/, '').replace(/\//, '-')) + } + } +} +module.exports = Pack diff --git a/deps/npm/lib/ping.js b/deps/npm/lib/ping.js index efa2263103..e43f0640f2 100644 --- a/deps/npm/lib/ping.js +++ b/deps/npm/lib/ping.js @@ -1,27 +1,36 @@ const log = require('npmlog') -const npm = require('./npm.js') const output = require('./utils/output.js') const usageUtil = require('./utils/usage.js') +const pingUtil = require('./utils/ping.js') -const usage = usageUtil('ping', 'npm ping\nping registry') +class Ping { + constructor (npm) { + this.npm = npm + } -const cmd = (args, cb) => ping(args).then(() => cb()).catch(cb) -const pingUtil = require('./utils/ping.js') + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil('ping', 'npm ping\nping registry') + } -const ping = async args => { - log.notice('PING', npm.flatOptions.registry) - const start = Date.now() - const details = await pingUtil(npm.flatOptions) - const time = Date.now() - start - log.notice('PONG', `${time / 1000}ms`) - if (npm.flatOptions.json) { - output(JSON.stringify({ - registry: npm.flatOptions.registry, - time, - details, - }, null, 2)) - } else if (Object.keys(details).length) - log.notice('PONG', `${JSON.stringify(details, null, 2)}`) -} + exec (args, cb) { + this.ping(args).then(() => cb()).catch(cb) + } -module.exports = Object.assign(cmd, { usage }) + async ping (args) { + log.notice('PING', this.npm.flatOptions.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) { + output(JSON.stringify({ + registry: this.npm.flatOptions.registry, + time, + details, + }, null, 2)) + } else if (Object.keys(details).length) + log.notice('PONG', `${JSON.stringify(details, null, 2)}`) + } +} +module.exports = Ping diff --git a/deps/npm/lib/prefix.js b/deps/npm/lib/prefix.js index d108b9d423..e46f9c4cdd 100644 --- a/deps/npm/lib/prefix.js +++ b/deps/npm/lib/prefix.js @@ -1,7 +1,22 @@ -const npm = require('./npm.js') const output = require('./utils/output.js') const usageUtil = require('./utils/usage.js') -const cmd = (args, cb) => prefix(args).then(() => cb()).catch(cb) -const usage = usageUtil('prefix', 'npm prefix [-g]') -const prefix = async (args, cb) => output(npm.prefix) -module.exports = Object.assign(cmd, { usage }) + +class Prefix { + constructor (npm) { + this.npm = npm + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil('prefix', 'npm prefix [-g]') + } + + exec (args, cb) { + this.prefix(args).then(() => cb()).catch(cb) + } + + async prefix (args) { + return output(this.npm.prefix) + } +} +module.exports = Prefix diff --git a/deps/npm/lib/profile.js b/deps/npm/lib/profile.js index 3727ac0c8b..dab99092b0 100644 --- a/deps/npm/lib/profile.js +++ b/deps/npm/lib/profile.js @@ -6,71 +6,14 @@ const npmProfile = require('npm-profile') const qrcodeTerminal = require('qrcode-terminal') const Table = require('cli-table3') -const npm = require('./npm.js') const otplease = require('./utils/otplease.js') const output = require('./utils/output.js') const pulseTillDone = require('./utils/pulse-till-done.js') const readUserInfo = require('./utils/read-user-info.js') const usageUtil = require('./utils/usage.js') -const usage = usageUtil( - 'npm profile enable-2fa [auth-only|auth-and-writes]\n', - 'npm profile disable-2fa\n', - 'npm profile get [<key>]\n', - 'npm profile set <key> <value>' -) - -const completion = async (opts) => { - var argv = opts.conf.argv.remain - const subcommands = ['enable-2fa', 'disable-2fa', 'get', 'set'] - - if (!argv[2]) - return subcommands - - switch (argv[2]) { - case 'enable-2fa': - case 'enable-tfa': - return ['auth-and-writes', 'auth-only'] - - case 'disable-2fa': - case 'disable-tfa': - case 'get': - case 'set': - return [] - default: - throw new Error(argv[2] + ' not recognized') - } -} - -const cmd = (args, cb) => profile(args).then(() => cb()).catch(cb) - -const profile = async (args) => { - if (args.length === 0) - throw new Error(usage) - - log.gauge.show('profile') - - const [subcmd, ...opts] = args - - switch (subcmd) { - case 'enable-2fa': - case 'enable-tfa': - case 'enable2fa': - case 'enabletfa': - return enable2fa(opts) - case 'disable-2fa': - case 'disable-tfa': - case 'disable2fa': - case 'disabletfa': - return disable2fa() - case 'get': - return get(opts) - case 'set': - return set(opts) - default: - throw new Error('Unknown profile command: ' + subcmd) - } -} +const qrcode = url => + new Promise((resolve) => qrcodeTerminal.generate(url, resolve)) const knownProfileKeys = [ 'name', @@ -85,64 +28,6 @@ const knownProfileKeys = [ 'updated', ] -const get = async args => { - const tfa = 'two-factor auth' - const conf = { ...npm.flatOptions } - - const info = await pulseTillDone.withPromise(npmProfile.get(conf)) - - if (!info.cidr_whitelist) - delete info.cidr_whitelist - - if (conf.json) { - output(JSON.stringify(info, null, 2)) - return - } - - // clean up and format key/values for output - const cleaned = {} - for (const key of knownProfileKeys) - cleaned[key] = info[key] || '' - - const unknownProfileKeys = Object.keys(info).filter((k) => !(k in cleaned)) - for (const key of unknownProfileKeys) - cleaned[key] = info[key] || '' - - delete cleaned.tfa - delete cleaned.email_verified - cleaned.email += info.email_verified ? ' (verified)' : '(unverified)' - - if (info.tfa && !info.tfa.pending) - cleaned[tfa] = info.tfa.mode - else - cleaned[tfa] = 'disabled' - - if (args.length) { - const values = args // comma or space separated - .join(',') - .split(/,/) - .filter((arg) => arg.trim() !== '') - .map((arg) => cleaned[arg]) - .join('\t') - output(values) - } else { - if (conf.parseable) { - for (const key of Object.keys(info)) { - if (key === 'tfa') - output(`${key}\t${cleaned[tfa]}`) - else - output(`${key}\t${info[key]}`) - } - } else { - const table = new Table() - for (const key of Object.keys(cleaned)) - table.push({ [ansistyles.bright(key)]: cleaned[key] }) - - output(table.toString()) - } - } -} - const writableProfileKeys = [ 'email', 'password', @@ -153,242 +38,364 @@ const writableProfileKeys = [ 'github', ] -const set = async (args) => { - const conf = { ...npm.flatOptions } - const prop = (args[0] || '').toLowerCase().trim() +class Profile { + constructor (npm) { + this.npm = npm + } - let value = args.length > 1 ? args.slice(1).join(' ') : null + get usage () { + return usageUtil( + 'profile', + 'npm profile enable-2fa [auth-only|auth-and-writes]\n', + 'npm profile disable-2fa\n', + 'npm profile get [<key>]\n', + 'npm profile set <key> <value>' + ) + } - const readPasswords = async () => { - const newpassword = await readUserInfo.password('New password: ') - const confirmedpassword = await readUserInfo.password(' Again: ') + async completion (opts) { + var argv = opts.conf.argv.remain - if (newpassword !== confirmedpassword) { - log.warn('profile', 'Passwords do not match, please try again.') - return readPasswords() - } + if (!argv[2]) + return ['enable-2fa', 'disable-2fa', 'get', 'set'] - return newpassword - } + switch (argv[2]) { + case 'enable-2fa': + case 'enable-tfa': + return ['auth-and-writes', 'auth-only'] - if (prop !== 'password' && value === null) - throw new Error('npm profile set <prop> <value>') + case 'disable-2fa': + case 'disable-tfa': + case 'get': + case 'set': + return [] + default: + throw new Error(argv[2] + ' not recognized') + } + } - if (prop === 'password' && value !== null) { - throw new Error( - 'npm profile set password\n' + - 'Do not include your current or new passwords on the command line.') + exec (args, cb) { + this.profile(args).then(() => cb()).catch(cb) } - if (writableProfileKeys.indexOf(prop) === -1) { - throw new Error(`"${prop}" is not a property we can set. ` + - `Valid properties are: ` + writableProfileKeys.join(', ')) + async profile (args) { + if (args.length === 0) + throw new Error(this.usage) + + log.gauge.show('profile') + + const [subcmd, ...opts] = args + + switch (subcmd) { + case 'enable-2fa': + case 'enable-tfa': + case 'enable2fa': + case 'enabletfa': + return this.enable2fa(opts) + case 'disable-2fa': + case 'disable-tfa': + case 'disable2fa': + case 'disabletfa': + return this.disable2fa() + case 'get': + return this.get(opts) + case 'set': + return this.set(opts) + default: + throw new Error('Unknown profile command: ' + subcmd) + } } - if (prop === 'password') { - const current = await readUserInfo.password('Current password: ') - const newpassword = await readPasswords() + async get (args) { + const tfa = 'two-factor auth' + const conf = { ...this.npm.flatOptions } + + const info = await pulseTillDone.withPromise(npmProfile.get(conf)) + + if (!info.cidr_whitelist) + delete info.cidr_whitelist + + if (conf.json) { + output(JSON.stringify(info, null, 2)) + return + } - value = { old: current, new: newpassword } + // clean up and format key/values for output + const cleaned = {} + for (const key of knownProfileKeys) + cleaned[key] = info[key] || '' + + const unknownProfileKeys = Object.keys(info).filter((k) => !(k in cleaned)) + for (const key of unknownProfileKeys) + cleaned[key] = info[key] || '' + + delete cleaned.tfa + delete cleaned.email_verified + cleaned.email += info.email_verified ? ' (verified)' : '(unverified)' + + if (info.tfa && !info.tfa.pending) + cleaned[tfa] = info.tfa.mode + else + cleaned[tfa] = 'disabled' + + if (args.length) { + const values = args // comma or space separated + .join(',') + .split(/,/) + .filter((arg) => arg.trim() !== '') + .map((arg) => cleaned[arg]) + .join('\t') + output(values) + } else { + if (conf.parseable) { + for (const key of Object.keys(info)) { + if (key === 'tfa') + output(`${key}\t${cleaned[tfa]}`) + else + output(`${key}\t${info[key]}`) + } + } else { + const table = new Table() + for (const key of Object.keys(cleaned)) + table.push({ [ansistyles.bright(key)]: cleaned[key] }) + + output(table.toString()) + } + } } - // FIXME: Work around to not clear everything other than what we're setting - const user = await pulseTillDone.withPromise(npmProfile.get(conf)) - const newUser = {} + async set (args) { + const conf = { ...this.npm.flatOptions } + const prop = (args[0] || '').toLowerCase().trim() - for (const key of writableProfileKeys) - newUser[key] = user[key] + let value = args.length > 1 ? args.slice(1).join(' ') : null - newUser[prop] = value + const readPasswords = async () => { + const newpassword = await readUserInfo.password('New password: ') + const confirmedpassword = await readUserInfo.password(' Again: ') - const result = await otplease(conf, conf => npmProfile.set(newUser, conf)) + if (newpassword !== confirmedpassword) { + log.warn('profile', 'Passwords do not match, please try again.') + return readPasswords() + } - if (conf.json) - output(JSON.stringify({ [prop]: result[prop] }, null, 2)) - else if (conf.parseable) - output(prop + '\t' + result[prop]) - else if (result[prop] != null) - output('Set', prop, 'to', result[prop]) - else - output('Set', prop) -} + return newpassword + } -const enable2fa = async (args) => { - if (args.length > 1) - throw new Error('npm profile enable-2fa [auth-and-writes|auth-only]') - - const mode = args[0] || 'auth-and-writes' - if (mode !== 'auth-only' && mode !== 'auth-and-writes') { - throw new Error( - `Invalid two-factor authentication mode "${mode}".\n` + - 'Valid modes are:\n' + - ' auth-only - Require two-factor authentication only when logging in\n' + - ' auth-and-writes - Require two-factor authentication when logging in ' + - 'AND when publishing' - ) - } + if (prop !== 'password' && value === null) + throw new Error('npm profile set <prop> <value>') - const conf = { ...npm.flatOptions } - if (conf.json || conf.parseable) { - throw new Error( - 'Enabling two-factor authentication is an interactive operation and ' + - (conf.json ? 'JSON' : 'parseable') + ' output mode is not available' - ) - } + if (prop === 'password' && value !== null) { + throw new Error( + 'npm profile set password\n' + + 'Do not include your current or new passwords on the command line.') + } - const info = { - tfa: { - mode: mode, - }, - } + if (writableProfileKeys.indexOf(prop) === -1) { + throw new Error(`"${prop}" is not a property we can set. ` + + `Valid properties are: ` + writableProfileKeys.join(', ')) + } - // if they're using legacy auth currently then we have to - // update them to a bearer token before continuing. - const auth = getAuth(conf) + if (prop === 'password') { + const current = await readUserInfo.password('Current password: ') + const newpassword = await readPasswords() - if (!auth.basic && !auth.token) { - throw new Error( - 'You need to be logged in to registry ' + - `${conf.registry} in order to enable 2fa` - ) + value = { old: current, new: newpassword } + } + + // FIXME: Work around to not clear everything other than what we're setting + const user = await pulseTillDone.withPromise(npmProfile.get(conf)) + const newUser = {} + + for (const key of writableProfileKeys) + newUser[key] = user[key] + + newUser[prop] = value + + const result = await otplease(conf, conf => npmProfile.set(newUser, conf)) + + if (conf.json) + output(JSON.stringify({ [prop]: result[prop] }, null, 2)) + else if (conf.parseable) + output(prop + '\t' + result[prop]) + else if (result[prop] != null) + output('Set', prop, 'to', result[prop]) + else + output('Set', prop) } - if (auth.basic) { - log.info('profile', 'Updating authentication to bearer token') - const result = await npmProfile.createToken( - auth.basic.password, false, [], conf - ) + async enable2fa (args) { + if (args.length > 1) + throw new Error('npm profile enable-2fa [auth-and-writes|auth-only]') - if (!result.token) { + const mode = args[0] || 'auth-and-writes' + if (mode !== 'auth-only' && mode !== 'auth-and-writes') { throw new Error( - `Your registry ${conf.registry} does not seem to ` + - 'support bearer tokens. Bearer tokens are required for ' + - 'two-factor authentication' + `Invalid two-factor authentication mode "${mode}".\n` + + 'Valid modes are:\n' + + ' auth-only - Require two-factor authentication only when logging in\n' + + ' auth-and-writes - Require two-factor authentication when logging in ' + + 'AND when publishing' ) } - npm.config.setCredentialsByURI(conf.registry, { token: result.token }) - await npm.config.save('user') - } + const conf = { ...this.npm.flatOptions } + if (conf.json || conf.parseable) { + throw new Error( + 'Enabling two-factor authentication is an interactive operation and ' + + (conf.json ? 'JSON' : 'parseable') + ' output mode is not available' + ) + } - log.notice('profile', 'Enabling two factor authentication for ' + mode) - const password = await readUserInfo.password() - info.tfa.password = password + const info = { + tfa: { + mode: mode, + }, + } - log.info('profile', 'Determine if tfa is pending') - const userInfo = await pulseTillDone.withPromise(npmProfile.get(conf)) + // 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 auth = {} + + if (creds.token) + auth.token = creds.token + else if (creds.username) + auth.basic = { username: creds.username, password: creds.password } + else if (creds.auth) { + const basic = Buffer.from(creds.auth, 'base64').toString().split(':', 2) + auth.basic = { username: basic[0], password: basic[1] } + } - 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( - 'Enter one-time password from your authenticator app: ' + 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` ) - conf.otp = otp } - } - log.info('profile', 'Setting two-factor authentication to ' + mode) - const challenge = await pulseTillDone.withPromise(npmProfile.set(info, conf)) + if (auth.basic) { + log.info('profile', 'Updating authentication to bearer token') + const result = await npmProfile.createToken( + auth.basic.password, false, [], conf + ) - if (challenge.tfa === null) { - output('Two factor authentication mode changed to: ' + mode) - return - } + if (!result.token) { + throw new Error( + `Your registry ${conf.registry} does not seem to ` + + 'support bearer tokens. Bearer tokens are required for ' + + 'two-factor authentication' + ) + } - const badResponse = typeof challenge.tfa !== 'string' - || !/^otpauth:[/][/]/.test(challenge.tfa) - if (badResponse) { - throw new Error( - 'Unknown error enabling two-factor authentication. Expected otpauth URL' + - ', got: ' + inspect(challenge.tfa) - ) - } + this.npm.config.setCredentialsByURI( + conf.registry, + { token: result.token } + ) + await this.npm.config.save('user') + } - const otpauth = new URL(challenge.tfa) - const secret = otpauth.searchParams.get('secret') - const code = await qrcode(challenge.tfa) + log.notice('profile', 'Enabling two factor authentication for ' + mode) + const password = await readUserInfo.password() + info.tfa.password = password - output( - 'Scan into your authenticator app:\n' + code + '\n Or enter code:', secret - ) + log.info('profile', 'Determine if tfa is pending') + const userInfo = await pulseTillDone.withPromise(npmProfile.get(conf)) - const interactiveOTP = - await readUserInfo.otp('And an OTP code from your authenticator: ') + 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( + 'Enter one-time password from your authenticator app: ' + ) + conf.otp = otp + } + } - log.info('profile', 'Finalizing two-factor authentication') + log.info('profile', 'Setting two-factor authentication to ' + mode) + const challenge = await pulseTillDone.withPromise( + npmProfile.set(info, conf) + ) - const result = await npmProfile.set({ tfa: [interactiveOTP] }, conf) + if (challenge.tfa === null) { + output('Two factor authentication mode changed to: ' + mode) + return + } - output( - '2FA successfully enabled. Below are your recovery codes, ' + - 'please print these out.' - ) - output( - 'You will need these to recover access to your account ' + - 'if you lose your authentication device.' - ) + const badResponse = typeof challenge.tfa !== 'string' + || !/^otpauth:[/][/]/.test(challenge.tfa) + if (badResponse) { + throw new Error( + 'Unknown error enabling two-factor authentication. Expected otpauth URL' + + ', got: ' + inspect(challenge.tfa) + ) + } - for (const tfaCode of result.tfa) - output('\t' + tfaCode) -} + const otpauth = new URL(challenge.tfa) + const secret = otpauth.searchParams.get('secret') + const code = await qrcode(challenge.tfa) -const getAuth = conf => { - const creds = npm.config.getCredentialsByURI(conf.registry) - const auth = {} - - if (creds.token) - auth.token = creds.token - else if (creds.username) - auth.basic = { username: creds.username, password: creds.password } - else if (creds.auth) { - const basic = Buffer.from(creds.auth, 'base64').toString().split(':', 2) - auth.basic = { username: basic[0], password: basic[1] } - } + output( + 'Scan into your authenticator app:\n' + code + '\n Or enter code:', secret + ) - if (conf.otp) - auth.otp = conf.otp + const interactiveOTP = + await readUserInfo.otp('And an OTP code from your authenticator: ') - return auth -} + log.info('profile', 'Finalizing two-factor authentication') -const disable2fa = async args => { - const conf = { ...npm.flatOptions } - const info = await pulseTillDone.withPromise(npmProfile.get(conf)) + const result = await npmProfile.set({ tfa: [interactiveOTP] }, conf) - if (!info.tfa || info.tfa.pending) { - output('Two factor authentication not enabled.') - return + output( + '2FA successfully enabled. Below are your recovery codes, ' + + 'please print these out.' + ) + output( + 'You will need these to recover access to your account ' + + 'if you lose your authentication device.' + ) + + for (const tfaCode of result.tfa) + output('\t' + tfaCode) } - const password = await readUserInfo.password() + async disable2fa (args) { + const conf = { ...this.npm.flatOptions } + const info = await pulseTillDone.withPromise(npmProfile.get(conf)) - if (!conf.otp) { - const msg = 'Enter one-time password from your authenticator app: ' - conf.otp = await readUserInfo.otp(msg) - } + if (!info.tfa || info.tfa.pending) { + output('Two factor authentication not enabled.') + return + } - log.info('profile', 'disabling tfa') + const password = await readUserInfo.password() - await pulseTillDone.withPromise(npmProfile.set({ - tfa: { password: password, mode: 'disable' }, - }, conf)) + if (!conf.otp) { + const msg = 'Enter one-time password from your authenticator app: ' + conf.otp = await readUserInfo.otp(msg) + } - if (conf.json) - output(JSON.stringify({ tfa: false }, null, 2)) - else if (conf.parseable) - output('tfa\tfalse') - else - output('Two factor authentication disabled.') -} + log.info('profile', 'disabling tfa') -const qrcode = url => - new Promise((resolve) => qrcodeTerminal.generate(url, resolve)) + await pulseTillDone.withPromise(npmProfile.set({ + tfa: { password: password, mode: 'disable' }, + }, conf)) -module.exports = Object.assign(cmd, { usage, completion }) + if (conf.json) + output(JSON.stringify({ tfa: false }, null, 2)) + else if (conf.parseable) + output('tfa\tfalse') + else + output('Two factor authentication disabled.') + } +} +module.exports = Profile diff --git a/deps/npm/lib/prune.js b/deps/npm/lib/prune.js index 228fd3eebb..b839301d51 100644 --- a/deps/npm/lib/prune.js +++ b/deps/npm/lib/prune.js @@ -1,24 +1,32 @@ // prune extraneous packages -const npm = require('./npm.js') const Arborist = require('@npmcli/arborist') const usageUtil = require('./utils/usage.js') - const reifyFinish = require('./utils/reify-finish.js') -const usage = usageUtil('prune', - 'npm prune [[<@scope>/]<pkg>...] [--production]' -) +class Prune { + constructor (npm) { + this.npm = npm + } -const cmd = (args, cb) => prune().then(() => cb()).catch(cb) + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil('prune', + 'npm prune [[<@scope>/]<pkg>...] [--production]' + ) + } -const prune = async () => { - const where = npm.prefix - const arb = new Arborist({ - ...npm.flatOptions, - path: where, - }) - await arb.prune(npm.flatOptions) - await reifyFinish(arb) -} + exec (args, cb) { + this.prune().then(() => cb()).catch(cb) + } -module.exports = Object.assign(cmd, { usage }) + async prune () { + const where = this.npm.prefix + const arb = new Arborist({ + ...this.npm.flatOptions, + path: where, + }) + await arb.prune(this.npm.flatOptions) + await reifyFinish(this.npm, arb) + } +} +module.exports = Prune diff --git a/deps/npm/lib/publish.js b/deps/npm/lib/publish.js index 5ec66d42fa..c8e82c44c5 100644 --- a/deps/npm/lib/publish.js +++ b/deps/npm/lib/publish.js @@ -8,9 +8,10 @@ const pacote = require('pacote') const npa = require('npm-package-arg') const npmFetch = require('npm-registry-fetch') -const npm = require('./npm.js') +const { flatten } = require('./utils/flat-options.js') const output = require('./utils/output.js') const otplease = require('./utils/otplease.js') +const usageUtil = require('./utils/usage.js') const { getContents, logTar } = require('./utils/tar.js') // this is the only case in the CLI where we use the old full slow @@ -18,122 +19,125 @@ const { getContents, logTar } = require('./utils/tar.js') // defaults and metadata, like git sha's and default scripts and all that. const readJson = util.promisify(require('read-package-json')) -const usageUtil = require('./utils/usage.js') -const usage = usageUtil('publish', - 'npm publish [<folder>] [--tag <tag>] [--access <public|restricted>] [--dry-run]' + - '\n\nPublishes \'.\' if no argument supplied' + - '\nSets tag `latest` if no --tag specified') +class Publish { + constructor (npm) { + this.npm = npm + } -const cmd = (args, cb) => publish(args).then(() => cb()).catch(cb) + get usage () { + return usageUtil('publish', + 'npm publish [<folder>] [--tag <tag>] [--access <public|restricted>] [--dry-run]' + + '\n\nPublishes \'.\' if no argument supplied' + + '\nSets tag `latest` if no --tag specified') + } -const publish = async args => { - if (args.length === 0) - args = ['.'] - if (args.length !== 1) - throw usage + exec (args, cb) { + this.publish(args).then(() => cb()).catch(cb) + } - log.verbose('publish', args) + async publish (args) { + if (args.length === 0) + args = ['.'] + if (args.length !== 1) + throw this.usage - const opts = { ...npm.flatOptions } - const { json, defaultTag } = opts + log.verbose('publish', args) - if (semver.validRange(defaultTag)) - throw new Error('Tag name must not be a valid SemVer range: ' + defaultTag.trim()) + const opts = { ...this.npm.flatOptions } + const { unicode, dryRun, json, defaultTag } = opts - const tarball = await publish_(args[0], opts) - const silent = log.level === 'silent' - if (!silent && json) - output(JSON.stringify(tarball, null, 2)) - else if (!silent) - output(`+ ${tarball.id}`) + if (semver.validRange(defaultTag)) + throw new Error('Tag name must not be a valid SemVer range: ' + defaultTag.trim()) - return tarball -} + // you can publish name@version, ./foo.tgz, etc. + // even though the default is the 'file:.' cwd. + const spec = npa(args[0]) + let manifest = await this.getManifest(spec, opts) -// if it's a directory, read it from the file system -// otherwise, get the full metadata from whatever it is -const getManifest = (spec, opts) => - spec.type === 'directory' ? readJson(`${spec.fetchSpec}/package.json`) - : pacote.manifest(spec, { ...opts, fullMetadata: true }) + if (manifest.publishConfig) + Object.assign(opts, this.publishConfigToOpts(manifest.publishConfig)) -// for historical reasons, publishConfig in package.json can contain -// ANY config keys that npm supports in .npmrc files and elsewhere. -// We *may* want to revisit this at some point, and have a minimal set -// that's a SemVer-major change that ought to get a RFC written on it. -const { flatten } = require('./utils/flat-options.js') -const publishConfigToOpts = publishConfig => - // create a new object that inherits from the config stack - // then squash the css-case into camelCase opts, like we do - flatten(Object.assign(Object.create(npm.config.list[0]), publishConfig)) - -const publish_ = async (arg, opts) => { - const { unicode, dryRun, json } = opts - // you can publish name@version, ./foo.tgz, etc. - // even though the default is the 'file:.' cwd. - const spec = npa(arg) - - let manifest = await getManifest(spec, opts) - - if (manifest.publishConfig) - Object.assign(opts, publishConfigToOpts(manifest.publishConfig)) - - // only run scripts for directory type publishes - if (spec.type === 'directory') { - await runScript({ - event: 'prepublishOnly', - path: spec.fetchSpec, - stdio: 'inherit', - pkg: manifest, - banner: log.level !== 'silent', - }) - } + // only run scripts for directory type publishes + if (spec.type === 'directory') { + await runScript({ + event: 'prepublishOnly', + path: spec.fetchSpec, + stdio: 'inherit', + pkg: manifest, + banner: log.level !== 'silent', + }) + } + + const tarballData = await pack(spec, opts) + const pkgContents = await getContents(manifest, tarballData) + + // The purpose of re-reading the manifest is in case it changed, + // so that we send the latest and greatest thing to the registry + // note that publishConfig might have changed as well! + manifest = await this.getManifest(spec, opts) + if (manifest.publishConfig) + Object.assign(opts, this.publishConfigToOpts(manifest.publishConfig)) + + // note that logTar calls npmlog.notice(), so if we ARE in silent mode, + // this will do nothing, but we still want it in the debuglog if it fails. + if (!json) + logTar(pkgContents, { log, unicode }) + + if (!dryRun) { + const resolved = npa.resolve(manifest.name, manifest.version) + const registry = npmFetch.pickRegistry(resolved, opts) + const creds = this.npm.config.getCredentialsByURI(registry) + if (!creds.token && !creds.username) { + throw Object.assign(new Error('This command requires you to be logged in.'), { + code: 'ENEEDAUTH', + }) + } + await otplease(opts, opts => libpub(manifest, tarballData, opts)) + } + + if (spec.type === 'directory') { + await runScript({ + event: 'publish', + path: spec.fetchSpec, + stdio: 'inherit', + pkg: manifest, + banner: log.level !== 'silent', + }) - const tarballData = await pack(spec, opts) - const pkgContents = await getContents(manifest, tarballData) - - // The purpose of re-reading the manifest is in case it changed, - // so that we send the latest and greatest thing to the registry - // note that publishConfig might have changed as well! - manifest = await getManifest(spec, opts) - if (manifest.publishConfig) - Object.assign(opts, publishConfigToOpts(manifest.publishConfig)) - - // note that logTar calls npmlog.notice(), so if we ARE in silent mode, - // this will do nothing, but we still want it in the debuglog if it fails. - if (!json) - logTar(pkgContents, { log, unicode }) - - if (!dryRun) { - const resolved = npa.resolve(manifest.name, manifest.version) - const registry = npmFetch.pickRegistry(resolved, opts) - const creds = npm.config.getCredentialsByURI(registry) - if (!creds.token && !creds.username) { - throw Object.assign(new Error('This command requires you to be logged in.'), { - code: 'ENEEDAUTH', + await runScript({ + event: 'postpublish', + path: spec.fetchSpec, + stdio: 'inherit', + pkg: manifest, + banner: log.level !== 'silent', }) } - await otplease(opts, opts => libpub(manifest, tarballData, opts)) + + const silent = log.level === 'silent' + if (!silent && json) + output(JSON.stringify(pkgContents, null, 2)) + else if (!silent) + output(`+ ${pkgContents.id}`) + + return pkgContents } - if (spec.type === 'directory') { - await runScript({ - event: 'publish', - path: spec.fetchSpec, - stdio: 'inherit', - pkg: manifest, - banner: log.level !== 'silent', - }) - - await runScript({ - event: 'postpublish', - path: spec.fetchSpec, - stdio: 'inherit', - pkg: manifest, - banner: log.level !== 'silent', - }) + // if it's a directory, read it from the file system + // otherwise, get the full metadata from whatever it is + getManifest (spec, opts) { + if (spec.type === 'directory') + return readJson(`${spec.fetchSpec}/package.json`) + return pacote.manifest(spec, { ...opts, fullMetadata: true }) } - return pkgContents + // for historical reasons, publishConfig in package.json can contain + // ANY config keys that npm supports in .npmrc files and elsewhere. + // We *may* want to revisit this at some point, and have a minimal set + // that's a SemVer-major change that ought to get a RFC written on it. + 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}) + } } - -module.exports = Object.assign(cmd, { usage }) +module.exports = Publish diff --git a/deps/npm/lib/rebuild.js b/deps/npm/lib/rebuild.js index ab34b7f3df..1091b01589 100644 --- a/deps/npm/lib/rebuild.js +++ b/deps/npm/lib/rebuild.js @@ -2,64 +2,74 @@ const { resolve } = require('path') const Arborist = require('@npmcli/arborist') const npa = require('npm-package-arg') const semver = require('semver') - -const npm = require('./npm.js') const usageUtil = require('./utils/usage.js') const output = require('./utils/output.js') +const completion = require('./utils/completion/installed-deep.js') -const cmd = (args, cb) => rebuild(args).then(() => cb()).catch(cb) +class Rebuild { + constructor (npm) { + this.npm = npm + } -const usage = usageUtil('rebuild', 'npm rebuild [[<@scope>/]<name>[@<version>] ...]') + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil('rebuild', 'npm rebuild [[<@scope>/]<name>[@<version>] ...]') + } -const completion = require('./utils/completion/installed-deep.js') + /* istanbul ignore next - see test/lib/load-all-commands.js */ + async completion (opts) { + return completion(this.npm, opts) + } -const rebuild = async args => { - const globalTop = resolve(npm.globalDir, '..') - const where = npm.flatOptions.global ? globalTop : npm.prefix - const arb = new Arborist({ - ...npm.flatOptions, - path: where, - }) + exec (args, cb) { + this.rebuild(args).then(() => cb()).catch(cb) + } - if (args.length) { - // get the set of nodes matching the name that we want rebuilt - const tree = await arb.loadActual() - const filter = getFilterFn(args) - await arb.rebuild({ - nodes: tree.inventory.filter(filter), + async rebuild (args) { + const globalTop = resolve(this.npm.globalDir, '..') + const where = this.npm.flatOptions.global ? globalTop : this.npm.prefix + const arb = new Arborist({ + ...this.npm.flatOptions, + path: where, }) - } else - await arb.rebuild() - output('rebuilt dependencies successfully') -} + if (args.length) { + // get the set of nodes matching the name that we want rebuilt + const tree = await arb.loadActual() + const specs = args.map(arg => { + const spec = npa(arg) + if (spec.type === 'tag' && spec.rawSpec === '') + return spec -const getFilterFn = args => { - const specs = args.map(arg => { - const spec = npa(arg) - if (spec.type === 'tag' && spec.rawSpec === '') - return spec + if (spec.type !== 'range' && spec.type !== 'version' && spec.type !== 'directory') + throw new Error('`npm rebuild` only supports SemVer version/range specifiers') - if (spec.type !== 'range' && spec.type !== 'version' && spec.type !== 'directory') - throw new Error('`npm rebuild` only supports SemVer version/range specifiers') + return spec + }) + const nodes = tree.inventory.filter(node => this.isNode(specs, node)) - return spec - }) + await arb.rebuild({ nodes }) + } else + await arb.rebuild() - return node => specs.some(spec => { - if (spec.type === 'directory') - return node.path === spec.fetchSpec + output('rebuilt dependencies successfully') + } - if (spec.name !== node.name) - return false + isNode (specs, node) { + return specs.some(spec => { + if (spec.type === 'directory') + return node.path === spec.fetchSpec - if (spec.rawSpec === '' || spec.rawSpec === '*') - return true + if (spec.name !== node.name) + return false - const { version } = node.package - // TODO: add tests for a package with missing version - return semver.satisfies(version, spec.fetchSpec) - }) -} + if (spec.rawSpec === '' || spec.rawSpec === '*') + return true -module.exports = Object.assign(cmd, { usage, completion }) + const { version } = node.package + // TODO: add tests for a package with missing version + return semver.satisfies(version, spec.fetchSpec) + }) + } +} +module.exports = Rebuild diff --git a/deps/npm/lib/repo.js b/deps/npm/lib/repo.js index e9074dca68..f0be99d4d6 100644 --- a/deps/npm/lib/repo.js +++ b/deps/npm/lib/repo.js @@ -1,52 +1,63 @@ const log = require('npmlog') const pacote = require('pacote') -const { promisify } = require('util') -const openUrl = promisify(require('./utils/open-url.js')) -const usageUtil = require('./utils/usage.js') -const npm = require('./npm.js') -const hostedFromMani = require('./utils/hosted-git-info-from-manifest.js') const { URL } = require('url') -const usage = usageUtil('repo', 'npm repo [<pkgname> [<pkgname> ...]]') - -const cmd = (args, cb) => repo(args).then(() => cb()).catch(cb) +const hostedFromMani = require('./utils/hosted-git-info-from-manifest.js') +const openUrl = require('./utils/open-url.js') +const usageUtil = require('./utils/usage.js') -const repo = async args => { - if (!args || !args.length) - args = ['.'] +class Repo { + constructor (npm) { + this.npm = npm + } - await Promise.all(args.map(pkg => getRepo(pkg))) -} + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil('repo', 'npm repo [<pkgname> [<pkgname> ...]]') + } -const getRepo = async pkg => { - const opts = { ...npm.flatOptions, fullMetadata: true } - const mani = await pacote.manifest(pkg, opts) + exec (args, cb) { + this.repo(args).then(() => cb()).catch(cb) + } - const r = mani.repository - const rurl = !r ? null - : typeof r === 'string' ? r - : typeof r === 'object' && typeof r.url === 'string' ? r.url - : null + async repo (args) { + if (!args || !args.length) + args = ['.'] - if (!rurl) { - throw Object.assign(new Error('no repository'), { - pkgid: pkg, - }) + await Promise.all(args.map(pkg => this.get(pkg))) } - const info = hostedFromMani(mani) - const url = info ? - info.browse(mani.repository.directory) : unknownHostedUrl(rurl) + async get (pkg) { + const opts = { ...this.npm.flatOptions, fullMetadata: true } + const mani = await pacote.manifest(pkg, opts) - if (!url) { - throw Object.assign(new Error('no repository: could not get url'), { - pkgid: pkg, - }) - } + const r = mani.repository + const rurl = !r ? null + : typeof r === 'string' ? r + : typeof r === 'object' && typeof r.url === 'string' ? r.url + : null + + if (!rurl) { + throw Object.assign(new Error('no repository'), { + pkgid: pkg, + }) + } + + const info = hostedFromMani(mani) + const url = info ? + info.browse(mani.repository.directory) : unknownHostedUrl(rurl) - log.silly('docs', 'url', url) - await openUrl(url, `${mani.name} repo available at the following URL`) + if (!url) { + throw Object.assign(new Error('no repository: could not get url'), { + pkgid: pkg, + }) + } + + log.silly('docs', 'url', url) + await openUrl(this.npm, url, `${mani.name} repo available at the following URL`) + } } +module.exports = Repo const unknownHostedUrl = url => { try { @@ -67,5 +78,3 @@ const unknownHostedUrl = url => { return null } } - -module.exports = Object.assign(cmd, { usage }) diff --git a/deps/npm/lib/restart.js b/deps/npm/lib/restart.js index 1462cf6051..d5a7789ca9 100644 --- a/deps/npm/lib/restart.js +++ b/deps/npm/lib/restart.js @@ -1,2 +1,9 @@ -const npm = require('./npm.js') -module.exports = require('./utils/lifecycle-cmd.js')(npm, 'restart') +const LifecycleCmd = require('./utils/lifecycle-cmd.js') + +// This ends up calling run-script(['restart', ...args]) +class Restart extends LifecycleCmd { + constructor (npm) { + super(npm, 'restart') + } +} +module.exports = Restart diff --git a/deps/npm/lib/root.js b/deps/npm/lib/root.js index 631aef8386..8e5ac63d7b 100644 --- a/deps/npm/lib/root.js +++ b/deps/npm/lib/root.js @@ -1,7 +1,22 @@ -const npm = require('./npm.js') const output = require('./utils/output.js') const usageUtil = require('./utils/usage.js') -const cmd = (args, cb) => root(args).then(() => cb()).catch(cb) -const usage = usageUtil('root', 'npm root [-g]') -const root = async (args, cb) => output(npm.dir) -module.exports = Object.assign(cmd, { usage }) + +class Root { + constructor (npm) { + this.npm = npm + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil('root', 'npm root [-g]') + } + + exec (args, cb) { + this.root(args).then(() => cb()).catch(cb) + } + + async root () { + output(this.npm.dir) + } +} +module.exports = Root diff --git a/deps/npm/lib/run-script.js b/deps/npm/lib/run-script.js index 4dfb854cad..cdfd88f10f 100644 --- a/deps/npm/lib/run-script.js +++ b/deps/npm/lib/run-script.js @@ -1,144 +1,157 @@ const runScript = require('@npmcli/run-script') const { isServerPackage } = runScript -const npm = require('./npm.js') const readJson = require('read-package-json-fast') const { resolve } = require('path') const output = require('./utils/output.js') const log = require('npmlog') -const usageUtil = require('./utils/usage') -const didYouMean = require('./utils/did-you-mean') +const usageUtil = require('./utils/usage.js') +const didYouMean = require('./utils/did-you-mean.js') const isWindowsShell = require('./utils/is-windows-shell.js') -const usage = usageUtil( - 'run-script', - 'npm run-script <command> [-- <args>]' -) - -const completion = async (opts) => { - const argv = opts.conf.argv.remain - if (argv.length === 2) { - // find the script name - const json = resolve(npm.localPrefix, 'package.json') - const { scripts = {} } = await readJson(json).catch(er => ({})) - return Object.keys(scripts) +const cmdList = [ + 'publish', + 'install', + 'uninstall', + 'test', + 'stop', + 'start', + 'restart', + 'version', +].reduce((l, p) => l.concat(['pre' + p, p, 'post' + p]), []) + +class RunScript { + constructor (npm) { + this.npm = npm } -} -const cmd = (args, cb) => { - const fn = args.length ? doRun : list - return fn(args).then(() => cb()).catch(cb) -} + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil( + 'run-script', + 'npm run-script <command> [-- <args>]' + ) + } -const doRun = async (args) => { - const path = npm.localPrefix - const event = args.shift() - const { scriptShell } = npm.flatOptions + async completion (opts) { + const argv = opts.conf.argv.remain + if (argv.length === 2) { + // find the script name + const json = resolve(this.npm.localPrefix, 'package.json') + const { scripts = {} } = await readJson(json).catch(er => ({})) + return Object.keys(scripts) + } + } - const pkg = await readJson(`${path}/package.json`) - const { scripts = {} } = pkg + exec (args, cb) { + if (args.length) + this.run(args).then(() => cb()).catch(cb) + else + this.list(args).then(() => cb()).catch(cb) + } - if (event === 'restart' && !scripts.restart) - scripts.restart = 'npm stop --if-present && npm start' - else if (event === 'env' && !scripts.env) - scripts.env = isWindowsShell ? 'SET' : 'env' + async run (args) { + const path = this.npm.localPrefix + const event = args.shift() + const { scriptShell } = this.npm.flatOptions - pkg.scripts = scripts + const pkg = await readJson(`${path}/package.json`) + const { scripts = {} } = pkg - if (!Object.prototype.hasOwnProperty.call(scripts, event) && !(event === 'start' && await isServerPackage(path))) { - if (npm.config.get('if-present')) - return + if (event === 'restart' && !scripts.restart) + scripts.restart = 'npm stop --if-present && npm start' + else if (event === 'env' && !scripts.env) + scripts.env = isWindowsShell ? 'SET' : 'env' - const suggestions = didYouMean(event, Object.keys(scripts)) - throw new Error(`missing script: ${event}${ - suggestions ? `\n${suggestions}` : ''}`) - } + pkg.scripts = scripts - // positional args only added to the main event, not pre/post - const events = [[event, args]] - if (!npm.flatOptions.ignoreScripts) { - if (scripts[`pre${event}`]) - events.unshift([`pre${event}`, []]) + if ( + !Object.prototype.hasOwnProperty.call(scripts, event) && + !(event === 'start' && await isServerPackage(path)) + ) { + if (this.npm.config.get('if-present')) + return - if (scripts[`post${event}`]) - events.push([`post${event}`, []]) - } + const suggestions = didYouMean(event, Object.keys(scripts)) + throw new Error(`missing script: ${event}${ + suggestions ? `\n${suggestions}` : ''}`) + } - const opts = { - path, - args, - scriptShell, - stdio: 'inherit', - stdioString: true, - pkg, - banner: log.level !== 'silent', - } + // positional args only added to the main event, not pre/post + const events = [[event, args]] + if (!this.npm.flatOptions.ignoreScripts) { + if (scripts[`pre${event}`]) + events.unshift([`pre${event}`, []]) + + if (scripts[`post${event}`]) + events.push([`post${event}`, []]) + } - for (const [event, args] of events) { - await runScript({ - ...opts, - event, + const opts = { + path, args, - }) + scriptShell, + stdio: 'inherit', + stdioString: true, + pkg, + banner: log.level !== 'silent', + } + + for (const [event, args] of events) { + await runScript({ + ...opts, + event, + args, + }) + } } -} -const list = async () => { - const path = npm.localPrefix - const { scripts, name } = await readJson(`${path}/package.json`) - const cmdList = [ - 'publish', - 'install', - 'uninstall', - 'test', - 'stop', - 'start', - 'restart', - 'version', - ].reduce((l, p) => l.concat(['pre' + p, p, 'post' + p]), []) - - if (!scripts) - return [] - - const allScripts = Object.keys(scripts) - if (log.level === 'silent') - return allScripts + async list () { + const path = this.npm.localPrefix + const { scripts, name } = await readJson(`${path}/package.json`) - if (npm.flatOptions.json) { - output(JSON.stringify(scripts, null, 2)) - return allScripts - } + if (!scripts) + return [] - if (npm.flatOptions.parseable) { - for (const [script, cmd] of Object.entries(scripts)) - output(`${script}:${cmd}`) + const allScripts = Object.keys(scripts) + if (log.level === 'silent') + return allScripts - return allScripts - } + if (this.npm.flatOptions.json) { + output(JSON.stringify(scripts, null, 2)) + return allScripts + } - const indent = '\n ' - const prefix = ' ' - const cmds = [] - const runScripts = [] - for (const script of allScripts) { - const list = cmdList.includes(script) ? cmds : runScripts - list.push(script) - } + if (this.npm.flatOptions.parseable) { + for (const [script, cmd] of Object.entries(scripts)) + output(`${script}:${cmd}`) - if (cmds.length) - output(`Lifecycle scripts included in ${name}:`) + return allScripts + } - for (const script of cmds) - output(prefix + script + indent + scripts[script]) + const indent = '\n ' + const prefix = ' ' + const cmds = [] + const runScripts = [] + for (const script of allScripts) { + const list = cmdList.includes(script) ? cmds : runScripts + list.push(script) + } - if (!cmds.length && runScripts.length) - output(`Scripts available in ${name} via \`npm run-script\`:`) - else if (runScripts.length) - output('\navailable via `npm run-script`:') + if (cmds.length) + output(`Lifecycle scripts included in ${name}:`) - for (const script of runScripts) - output(prefix + script + indent + scripts[script]) + for (const script of cmds) + output(prefix + script + indent + scripts[script]) - return allScripts -} + if (!cmds.length && runScripts.length) + output(`Scripts available in ${name} via \`npm run-script\`:`) + else if (runScripts.length) + output('\navailable via `npm run-script`:') + + for (const script of runScripts) + output(prefix + script + indent + scripts[script]) -module.exports = Object.assign(cmd, { completion, usage }) + return allScripts + } +} +module.exports = RunScript diff --git a/deps/npm/lib/search.js b/deps/npm/lib/search.js index 3f8fd99fb8..e0922b9846 100644 --- a/deps/npm/lib/search.js +++ b/deps/npm/lib/search.js @@ -5,69 +5,10 @@ const log = require('npmlog') const formatPackageStream = require('./search/format-package-stream.js') const packageFilter = require('./search/package-filter.js') -const npm = require('./npm.js') const output = require('./utils/output.js') const usageUtil = require('./utils/usage.js') -const usage = usageUtil( - 'search', - 'npm search [-l|--long] [--json] [--parseable] [--no-description] [search terms ...]' -) - -const cmd = (args, cb) => search(args).then(() => cb()).catch(cb) - -const search = async (args) => { - const opts = { - ...npm.flatOptions, - ...npm.flatOptions.search, - include: prepareIncludes(args, npm.flatOptions.search.opts), - exclude: prepareExcludes(npm.flatOptions.search.exclude), - } - - if (opts.include.length === 0) - throw new Error('search must be called with arguments') - - // Used later to figure out whether we had any packages go out - let anyOutput = false - - class FilterStream extends Minipass { - write (pkg) { - if (packageFilter(pkg, opts.include, opts.exclude)) - super.write(pkg) - } - } - - const filterStream = new FilterStream() - - // Grab a configured output stream that will spit out packages in the - // desired format. - const outputStream = formatPackageStream({ - args, // --searchinclude options are not highlighted - ...opts, - }) - - log.silly('search', 'searching packages') - const p = new Pipeline( - libSearch.stream(opts.include, opts), - filterStream, - outputStream - ) - - p.on('data', chunk => { - if (!anyOutput) - anyOutput = true - output(chunk.toString('utf8')) - }) - - await p.promise() - if (!anyOutput && !opts.json && !opts.parseable) - output('No matches found for ' + (args.map(JSON.stringify).join(' '))) - - log.silly('search', 'search completed') - log.clearProgress() -} - -function prepareIncludes (args, searchopts) { +function prepareIncludes (args) { return args .map(s => s.toLowerCase()) .filter(s => s) @@ -85,4 +26,72 @@ function prepareExcludes (searchexclude) { .filter(s => s) } -module.exports = Object.assign(cmd, { usage }) +class Search { + constructor (npm) { + this.npm = npm + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil( + 'search', + 'npm search [-l|--long] [--json] [--parseable] [--no-description] [search terms ...]' + ) + } + + exec (args, cb) { + this.search(args).then(() => cb()).catch(cb) + } + + async search (args) { + const opts = { + ...this.npm.flatOptions, + ...this.npm.flatOptions.search, + include: prepareIncludes(args), + exclude: prepareExcludes(this.npm.flatOptions.search.exclude), + } + + if (opts.include.length === 0) + throw new Error('search must be called with arguments') + + // Used later to figure out whether we had any packages go out + let anyOutput = false + + class FilterStream extends Minipass { + write (pkg) { + if (packageFilter(pkg, opts.include, opts.exclude)) + super.write(pkg) + } + } + + const filterStream = new FilterStream() + + // Grab a configured output stream that will spit out packages in the + // desired format. + const outputStream = formatPackageStream({ + args, // --searchinclude options are not highlighted + ...opts, + }) + + log.silly('search', 'searching packages') + const p = new Pipeline( + libSearch.stream(opts.include, opts), + filterStream, + outputStream + ) + + p.on('data', chunk => { + if (!anyOutput) + anyOutput = true + output(chunk.toString('utf8')) + }) + + await p.promise() + if (!anyOutput && !opts.json && !opts.parseable) + output('No matches found for ' + (args.map(JSON.stringify).join(' '))) + + log.silly('search', 'search completed') + log.clearProgress() + } +} +module.exports = Search diff --git a/deps/npm/lib/set-script.js b/deps/npm/lib/set-script.js index 7bac6eca50..25545898e1 100644 --- a/deps/npm/lib/set-script.js +++ b/deps/npm/lib/set-script.js @@ -1,52 +1,62 @@ const log = require('npmlog') const usageUtil = require('./utils/usage.js') -const { localPrefix } = require('./npm.js') const fs = require('fs') -const usage = usageUtil('set-script', 'npm set-script [<script>] [<command>]') const parseJSON = require('json-parse-even-better-errors') const rpj = require('read-package-json-fast') -const cmd = (args, cb) => set(args).then(() => cb()).catch(cb) - -const set = async function (args) { - if (process.env.npm_lifecycle_event === 'postinstall') - throw new Error('Scripts can’t set from the postinstall script') +class SetScript { + constructor (npm) { + this.npm = npm + } - // Parse arguments - if (args.length !== 2) - throw new Error(`Expected 2 arguments: got ${args.length}`) + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil('set-script', 'npm set-script [<script>] [<command>]') + } - // Set the script - let manifest - let warn = false - try { - manifest = fs.readFileSync(localPrefix + '/package.json', 'utf-8') - } catch (error) { - throw new Error('package.json not found') + exec (args, cb) { + this.set(args).then(() => cb()).catch(cb) } - try { - manifest = parseJSON(manifest) - } catch (error) { - throw new Error(`Invalid package.json: ${error}`) + + async set (args) { + if (process.env.npm_lifecycle_event === 'postinstall') + throw new Error('Scripts can’t set from the postinstall script') + + // Parse arguments + if (args.length !== 2) + throw new Error(`Expected 2 arguments: got ${args.length}`) + + // Set the script + let manifest + let warn = false + try { + manifest = fs.readFileSync(this.npm.localPrefix + '/package.json', 'utf-8') + } catch (error) { + throw new Error('package.json not found') + } + try { + manifest = parseJSON(manifest) + } catch (error) { + throw new Error(`Invalid package.json: ${error}`) + } + if (!manifest.scripts) + manifest.scripts = {} + if (manifest.scripts[args[0]] && manifest.scripts[args[0]] !== args[1]) + warn = true + manifest.scripts[args[0]] = args[1] + // format content + const packageJsonInfo = await rpj(this.npm.localPrefix + '/package.json') + const { + [Symbol.for('indent')]: indent, + [Symbol.for('newline')]: newline, + } = packageJsonInfo + const format = indent === undefined ? ' ' : indent + const eol = newline === undefined ? '\n' : newline + const content = (JSON.stringify(manifest, null, format) + '\n') + .replace(/\n/g, eol) + fs.writeFileSync(this.npm.localPrefix + '/package.json', content) + if (warn) + log.warn('set-script', `Script "${args[0]}" was overwritten`) } - if (!manifest.scripts) - manifest.scripts = {} - if (manifest.scripts[args[0]] && manifest.scripts[args[0]] !== args[1]) - warn = true - manifest.scripts[args[0]] = args[1] - // format content - const packageJsonInfo = await rpj(localPrefix + '/package.json') - const { - [Symbol.for('indent')]: indent, - [Symbol.for('newline')]: newline, - } = packageJsonInfo - const format = indent === undefined ? ' ' : indent - const eol = newline === undefined ? '\n' : newline - const content = (JSON.stringify(manifest, null, format) + '\n') - .replace(/\n/g, eol) - fs.writeFileSync(localPrefix + '/package.json', content) - if (warn) - log.warn('set-script', `Script "${args[0]}" was overwritten`) } - -module.exports = Object.assign(cmd, { usage }) +module.exports = SetScript diff --git a/deps/npm/lib/set.js b/deps/npm/lib/set.js index 3d61c1e93e..cbce1547e8 100644 --- a/deps/npm/lib/set.js +++ b/deps/npm/lib/set.js @@ -1,14 +1,26 @@ -const npm = require('./npm.js') -const config = require('./config.js') +const usageUtil = require('./utils/usage.js') -const usage = 'npm set <key>=<value> [<key>=<value> ...] (See `npm config`)' +class Set { + constructor (npm) { + this.npm = npm + } -const completion = config.completion + get usage () { + return usageUtil( + 'set', + 'npm set <key>=<value> [<key>=<value> ...] (See `npm config`)' + ) + } -const cmd = (args, cb) => { - if (!args.length) - return cb(usage) - npm.commands.config(['set'].concat(args), cb) -} + /* istanbul ignore next - see test/lib/load-all-commands.js */ + async completion (opts) { + return this.npm.commands.config.completion(opts) + } -module.exports = Object.assign(cmd, { usage, completion }) + exec (args, cb) { + if (!args.length) + return cb(this.usage) + this.npm.commands.config(['set'].concat(args), cb) + } +} +module.exports = Set diff --git a/deps/npm/lib/shrinkwrap.js b/deps/npm/lib/shrinkwrap.js index 8768f35b5f..a7516131d2 100644 --- a/deps/npm/lib/shrinkwrap.js +++ b/deps/npm/lib/shrinkwrap.js @@ -5,48 +5,58 @@ const { unlink } = fs.promises || { unlink: util.promisify(fs.unlink) } const Arborist = require('@npmcli/arborist') const log = require('npmlog') -const npm = require('./npm.js') const usageUtil = require('./utils/usage.js') -const usage = usageUtil('shrinkwrap', 'npm shrinkwrap') - -const cmd = (args, cb) => shrinkwrap().then(() => cb()).catch(cb) - -const shrinkwrap = async () => { - // if has a npm-shrinkwrap.json, nothing to do - // if has a package-lock.json, rename to npm-shrinkwrap.json - // if has neither, load the actual tree and save that as npm-shrinkwrap.json - // in all cases, re-cast to current lockfile version - // - // loadVirtual, fall back to loadActual - // rename shrinkwrap file type, and tree.meta.save() - if (npm.flatOptions.global) { - const er = new Error('`npm shrinkwrap` does not work for global packages') - er.code = 'ESHRINKWRAPGLOBAL' - throw er + +class Shrinkwrap { + constructor (npm) { + this.npm = npm } - const path = npm.prefix - const sw = resolve(path, 'npm-shrinkwrap.json') - const arb = new Arborist({ ...npm.flatOptions, path }) - const tree = await arb.loadVirtual().catch(() => arb.loadActual()) - const { meta } = tree - const newFile = meta.hiddenLockfile || !meta.loadedFromDisk - const oldFilename = meta.filename - const notSW = !newFile && basename(oldFilename) !== 'npm-shrinkwrap.json' - - meta.hiddenLockfile = false - meta.filename = sw - await meta.save() - - if (newFile) - log.notice('', 'created a lockfile as npm-shrinkwrap.json') - else if (notSW) { - await unlink(oldFilename) - log.notice('', 'package-lock.json has been renamed to npm-shrinkwrap.json') - } else if (meta.originalLockfileVersion !== npm.lockfileVersion) - log.notice('', `npm-shrinkwrap.json updated to version ${npm.lockfileVersion}`) - else - log.notice('', 'npm-shrinkwrap.json up to date') -} + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil('shrinkwrap', 'npm shrinkwrap') + } + + exec (args, cb) { + this.shrinkwrap().then(() => cb()).catch(cb) + } -module.exports = Object.assign(cmd, { usage }) + async shrinkwrap () { + // if has a npm-shrinkwrap.json, nothing to do + // if has a package-lock.json, rename to npm-shrinkwrap.json + // if has neither, load the actual tree and save that as npm-shrinkwrap.json + // in all cases, re-cast to current lockfile version + // + // loadVirtual, fall back to loadActual + // rename shrinkwrap file type, and tree.meta.save() + if (this.npm.flatOptions.global) { + const er = new Error('`npm shrinkwrap` does not work for global packages') + er.code = 'ESHRINKWRAPGLOBAL' + throw er + } + + const path = this.npm.prefix + const sw = resolve(path, 'npm-shrinkwrap.json') + const arb = new Arborist({ ...this.npm.flatOptions, path }) + const tree = await arb.loadVirtual().catch(() => arb.loadActual()) + const { meta } = tree + const newFile = meta.hiddenLockfile || !meta.loadedFromDisk + const oldFilename = meta.filename + const notSW = !newFile && basename(oldFilename) !== 'npm-shrinkwrap.json' + + meta.hiddenLockfile = false + meta.filename = sw + await meta.save() + + if (newFile) + log.notice('', 'created a lockfile as npm-shrinkwrap.json') + else if (notSW) { + await unlink(oldFilename) + log.notice('', 'package-lock.json has been renamed to npm-shrinkwrap.json') + } else if (meta.originalLockfileVersion !== this.npm.lockfileVersion) + log.notice('', `npm-shrinkwrap.json updated to version ${this.npm.lockfileVersion}`) + else + log.notice('', 'npm-shrinkwrap.json up to date') + } +} +module.exports = Shrinkwrap diff --git a/deps/npm/lib/star.js b/deps/npm/lib/star.js index c02fdeed89..b39d23b2c1 100644 --- a/deps/npm/lib/star.js +++ b/deps/npm/lib/star.js @@ -2,73 +2,81 @@ const fetch = require('npm-registry-fetch') const log = require('npmlog') const npa = require('npm-package-arg') -const npm = require('./npm.js') const output = require('./utils/output.js') const usageUtil = require('./utils/usage.js') const getIdentity = require('./utils/get-identity') -const usage = usageUtil( - 'star', - 'npm star [<pkg>...]\n' + - 'npm unstar [<pkg>...]' -) +class Star { + constructor (npm) { + this.npm = npm + } -const cmd = (args, cb) => star(args).then(() => cb()).catch(cb) + get usage () { + return usageUtil( + 'star', + 'npm star [<pkg>...]\n' + + 'npm unstar [<pkg>...]' + ) + } -const star = async args => { - if (!args.length) - throw new Error(usage) + exec (args, cb) { + this.star(args).then(() => cb()).catch(cb) + } - // if we're unstarring, then show an empty star image - // otherwise, show the full star image - const { unicode } = npm.flatOptions - const unstar = npm.config.get('star.unstar') - const full = unicode ? '\u2605 ' : '(*)' - const empty = unicode ? '\u2606 ' : '( )' - const show = unstar ? empty : full + async star (args) { + if (!args.length) + throw new Error(this.usage) - const pkgs = args.map(npa) - for (const pkg of pkgs) { - const [username, fullData] = await Promise.all([ - getIdentity(npm.flatOptions), - fetch.json(pkg.escapedName, { - ...npm.flatOptions, - spec: pkg, - query: { write: true }, - preferOnline: true, - }), - ]) + // if we're unstarring, then show an empty star image + // otherwise, show the full star image + const { unicode } = this.npm.flatOptions + const unstar = this.npm.config.get('star.unstar') + const full = unicode ? '\u2605 ' : '(*)' + const empty = unicode ? '\u2606 ' : '( )' + const show = unstar ? empty : full - if (!username) - throw new Error('You need to be logged in!') + const pkgs = args.map(npa) + for (const pkg of pkgs) { + const [username, fullData] = await Promise.all([ + getIdentity(this.npm, this.npm.flatOptions), + fetch.json(pkg.escapedName, { + ...this.npm.flatOptions, + spec: pkg, + query: { write: true }, + preferOnline: true, + }), + ]) - const body = { - _id: fullData._id, - _rev: fullData._rev, - users: fullData.users || {}, - } + if (!username) + throw new Error('You need to be logged in!') - if (!unstar) { - log.info('star', 'starring', body._id) - body.users[username] = true - log.verbose('star', 'starring', body) - } else { - delete body.users[username] - log.info('unstar', 'unstarring', body._id) - log.verbose('unstar', 'unstarring', body) - } + const body = { + _id: fullData._id, + _rev: fullData._rev, + users: fullData.users || {}, + } + + if (!unstar) { + log.info('star', 'starring', body._id) + body.users[username] = true + log.verbose('star', 'starring', body) + } else { + delete body.users[username] + log.info('unstar', 'unstarring', body._id) + log.verbose('unstar', 'unstarring', body) + } - const data = await fetch.json(pkg.escapedName, { - ...npm.flatOptions, - spec: pkg, - method: 'PUT', - body, - }) + const data = await fetch.json(pkg.escapedName, { + ...this.npm.flatOptions, + spec: pkg, + method: 'PUT', + body, + }) - output(show + ' ' + pkg.name) - log.verbose('star', data) - return data + output(show + ' ' + pkg.name) + log.verbose('star', data) + return data + } } } - -module.exports = Object.assign(cmd, { usage }) +module.exports = Star diff --git a/deps/npm/lib/stars.js b/deps/npm/lib/stars.js index d1c2b73fb1..fe280705b4 100644 --- a/deps/npm/lib/stars.js +++ b/deps/npm/lib/stars.js @@ -1,35 +1,42 @@ const log = require('npmlog') const fetch = require('npm-registry-fetch') -const npm = require('./npm.js') const output = require('./utils/output.js') const getIdentity = require('./utils/get-identity.js') const usageUtil = require('./utils/usage.js') -const usage = usageUtil('stars', 'npm stars [<user>]') - -const cmd = (args, cb) => stars(args).then(() => cb()).catch(cb) - -const stars = (args) => { - return stars_(args).catch(er => { - if (er.code === 'ENEEDAUTH') - log.warn('stars', 'auth is required to look up your username') - - throw er - }) -} - -const stars_ = async ([user = getIdentity(npm.flatOptions)]) => { - const { rows } = await fetch.json('/-/_view/starredByUser', { - ...npm.flatOptions, - query: { key: `"${await user}"` }, - }) - - if (rows.length === 0) - log.warn('stars', 'user has not starred any packages') - - for (const row of rows) - output(row.value) +class Stars { + constructor (npm) { + this.npm = npm + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil('stars', 'npm stars [<user>]') + } + + exec (args, cb) { + this.stars(args).then(() => cb()).catch(er => { + if (er.code === 'ENEEDAUTH') + log.warn('stars', 'auth is required to look up your username') + cb(er) + }) + } + + async stars ([user]) { + if (!user) + user = await getIdentity(this.npm, this.npm.flatOptions) + + const { rows } = await fetch.json('/-/_view/starredByUser', { + ...this.npm.flatOptions, + query: { key: `"${user}"` }, + }) + + if (rows.length === 0) + log.warn('stars', 'user has not starred any packages') + + for (const row of rows) + output(row.value) + } } - -module.exports = Object.assign(cmd, { usage }) +module.exports = Stars diff --git a/deps/npm/lib/start.js b/deps/npm/lib/start.js index 9fa076d5e3..3d46a3a7ba 100644 --- a/deps/npm/lib/start.js +++ b/deps/npm/lib/start.js @@ -1,2 +1,9 @@ -const npm = require('./npm.js') -module.exports = require('./utils/lifecycle-cmd.js')(npm, 'start') +const LifecycleCmd = require('./utils/lifecycle-cmd.js') + +// This ends up calling run-script(['start', ...args]) +class Start extends LifecycleCmd { + constructor (npm) { + super(npm, 'start') + } +} +module.exports = Start diff --git a/deps/npm/lib/stop.js b/deps/npm/lib/stop.js index 827d414d13..d7df5887e7 100644 --- a/deps/npm/lib/stop.js +++ b/deps/npm/lib/stop.js @@ -1,2 +1,9 @@ -const npm = require('./npm.js') -module.exports = require('./utils/lifecycle-cmd.js')(npm, 'stop') +const LifecycleCmd = require('./utils/lifecycle-cmd.js') + +// This ends up calling run-script(['stop', ...args]) +class Stop extends LifecycleCmd { + constructor (npm) { + super(npm, 'stop') + } +} +module.exports = Stop diff --git a/deps/npm/lib/team.js b/deps/npm/lib/team.js index 24e8360b64..4947739a04 100644 --- a/deps/npm/lib/team.js +++ b/deps/npm/lib/team.js @@ -1,146 +1,148 @@ const columns = require('cli-columns') const libteam = require('libnpmteam') -const npm = require('./npm.js') const output = require('./utils/output.js') const otplease = require('./utils/otplease.js') -const usageUtil = require('./utils/usage') +const usageUtil = require('./utils/usage.js') -const subcommands = ['create', 'destroy', 'add', 'rm', 'ls'] +class Team { + constructor (npm) { + this.npm = npm + } + + get usage () { + return usageUtil( + 'team', + 'npm team create <scope:team> [--otp <otpcode>]\n' + + 'npm team destroy <scope:team> [--otp <otpcode>]\n' + + 'npm team add <scope:team> <user> [--otp <otpcode>]\n' + + 'npm team rm <scope:team> <user> [--otp <otpcode>]\n' + + 'npm team ls <scope>|<scope:team>\n' + ) + } -const usage = usageUtil( - 'team', - 'npm team create <scope:team> [--otp <otpcode>]\n' + - 'npm team destroy <scope:team> [--otp <otpcode>]\n' + - 'npm team add <scope:team> <user> [--otp <otpcode>]\n' + - 'npm team rm <scope:team> <user> [--otp <otpcode>]\n' + - 'npm team ls <scope>|<scope:team>\n' -) + async completion (opts) { + const { conf: { argv: { remain: argv } } } = opts + const subcommands = ['create', 'destroy', 'add', 'rm', 'ls'] -const completion = async (opts) => { - const { conf: { argv: { remain: argv } } } = opts - if (argv.length === 2) - return subcommands + if (argv.length === 2) + return subcommands - switch (argv[2]) { - case 'ls': - case 'create': - case 'destroy': - case 'add': - case 'rm': + if (subcommands.includes(argv[2])) return [] - default: - throw new Error(argv[2] + ' not recognized') + + throw new Error(argv[2] + ' not recognized') } -} -const cmd = (args, cb) => team(args).then(() => cb()).catch(cb) + exec (args, cb) { + this.team(args).then(() => cb()).catch(cb) + } -const team = async ([cmd, entity = '', user = '']) => { - // Entities are in the format <scope>:<team> - // XXX: "description" option to libnpmteam is used as a description of the - // team, but in npm's options, this is a boolean meaning "show the - // description in npm search output". Hence its being set to null here. - await otplease(npm.flatOptions, opts => { - entity = entity.replace(/^@/, '') - switch (cmd) { - case 'create': return teamCreate(entity, opts) - case 'destroy': return teamDestroy(entity, opts) - case 'add': return teamAdd(entity, user, opts) - case 'rm': return teamRm(entity, user, opts) - case 'ls': { - const match = entity.match(/[^:]+:.+/) - if (match) - return teamListUsers(entity, opts) - else - return teamListTeams(entity, opts) + async team ([cmd, entity = '', user = '']) { + // Entities are in the format <scope>:<team> + // XXX: "description" option to libnpmteam is used as a description of the + // team, but in npm's options, this is a boolean meaning "show the + // description in npm search output". Hence its being set to null here. + await otplease(this.npm.flatOptions, opts => { + entity = entity.replace(/^@/, '') + switch (cmd) { + case 'create': return this.create(entity, opts) + case 'destroy': return this.destroy(entity, opts) + case 'add': return this.add(entity, user, opts) + case 'rm': return this.rm(entity, user, opts) + case 'ls': { + const match = entity.match(/[^:]+:.+/) + if (match) + return this.listUsers(entity, opts) + else + return this.listTeams(entity, opts) + } + default: + throw this.usage } - default: - throw usage - } - }) -} + }) + } -const teamCreate = async (entity, opts) => { - await libteam.create(entity, opts) - if (opts.json) { - output(JSON.stringify({ - created: true, - team: entity, - })) - } else if (opts.parseable) - output(`${entity}\tcreated`) - else if (!opts.silent && opts.loglevel !== 'silent') - output(`+@${entity}`) -} + async create (entity, opts) { + await libteam.create(entity, opts) + if (opts.json) { + output(JSON.stringify({ + created: true, + team: entity, + })) + } else if (opts.parseable) + output(`${entity}\tcreated`) + else if (!opts.silent && opts.loglevel !== 'silent') + output(`+@${entity}`) + } -const teamDestroy = async (entity, opts) => { - await libteam.destroy(entity, opts) - if (opts.json) { - output(JSON.stringify({ - deleted: true, - team: entity, - })) - } else if (opts.parseable) - output(`${entity}\tdeleted`) - else if (!opts.silent && opts.loglevel !== 'silent') - output(`-@${entity}`) -} + async destroy (entity, opts) { + await libteam.destroy(entity, opts) + if (opts.json) { + output(JSON.stringify({ + deleted: true, + team: entity, + })) + } else if (opts.parseable) + output(`${entity}\tdeleted`) + else if (!opts.silent && opts.loglevel !== 'silent') + output(`-@${entity}`) + } -const teamAdd = async (entity, user, opts) => { - await libteam.add(user, entity, opts) - if (opts.json) { - output(JSON.stringify({ - added: true, - team: entity, - user, - })) - } else if (opts.parseable) - output(`${user}\t${entity}\tadded`) - else if (!opts.silent && opts.loglevel !== 'silent') - output(`${user} added to @${entity}`) -} + async add (entity, user, opts) { + await libteam.add(user, entity, opts) + if (opts.json) { + output(JSON.stringify({ + added: true, + team: entity, + user, + })) + } else if (opts.parseable) + output(`${user}\t${entity}\tadded`) + else if (!opts.silent && opts.loglevel !== 'silent') + output(`${user} added to @${entity}`) + } -const teamRm = async (entity, user, opts) => { - await libteam.rm(user, entity, opts) - if (opts.json) { - output(JSON.stringify({ - removed: true, - team: entity, - user, - })) - } else if (opts.parseable) - output(`${user}\t${entity}\tremoved`) - else if (!opts.silent && opts.loglevel !== 'silent') - output(`${user} removed from @${entity}`) -} + async rm (entity, user, opts) { + await libteam.rm(user, entity, opts) + if (opts.json) { + output(JSON.stringify({ + removed: true, + team: entity, + user, + })) + } else if (opts.parseable) + output(`${user}\t${entity}\tremoved`) + else if (!opts.silent && opts.loglevel !== 'silent') + output(`${user} removed from @${entity}`) + } -const teamListUsers = async (entity, opts) => { - const users = (await libteam.lsUsers(entity, opts)).sort() - if (opts.json) - output(JSON.stringify(users, null, 2)) - else if (opts.parseable) - output(users.join('\n')) - else if (!opts.silent && opts.loglevel !== 'silent') { - const plural = users.length === 1 ? '' : 's' - const more = users.length === 0 ? '' : ':\n' - output(`\n@${entity} has ${users.length} user${plural}${more}`) - output(columns(users, { padding: 1 })) + async listUsers (entity, opts) { + const users = (await libteam.lsUsers(entity, opts)).sort() + if (opts.json) + output(JSON.stringify(users, null, 2)) + else if (opts.parseable) + output(users.join('\n')) + else if (!opts.silent && opts.loglevel !== 'silent') { + const plural = users.length === 1 ? '' : 's' + const more = users.length === 0 ? '' : ':\n' + output(`\n@${entity} has ${users.length} user${plural}${more}`) + output(columns(users, { padding: 1 })) + } } -} -const teamListTeams = async (entity, opts) => { - const teams = (await libteam.lsTeams(entity, opts)).sort() - if (opts.json) - output(JSON.stringify(teams, null, 2)) - else if (opts.parseable) - output(teams.join('\n')) - else if (!opts.silent && opts.loglevel !== 'silent') { - const plural = teams.length === 1 ? '' : 's' - const more = teams.length === 0 ? '' : ':\n' - output(`\n@${entity} has ${teams.length} team${plural}${more}`) - output(columns(teams.map(t => `@${t}`), { padding: 1 })) + async listTeams (entity, opts) { + const teams = (await libteam.lsTeams(entity, opts)).sort() + if (opts.json) + output(JSON.stringify(teams, null, 2)) + else if (opts.parseable) + output(teams.join('\n')) + else if (!opts.silent && opts.loglevel !== 'silent') { + const plural = teams.length === 1 ? '' : 's' + const more = teams.length === 0 ? '' : ':\n' + output(`\n@${entity} has ${teams.length} team${plural}${more}`) + output(columns(teams.map(t => `@${t}`), { padding: 1 })) + } } } - -module.exports = Object.assign(cmd, { completion, usage }) +module.exports = Team diff --git a/deps/npm/lib/test.js b/deps/npm/lib/test.js index ea5914ea38..12292d2924 100644 --- a/deps/npm/lib/test.js +++ b/deps/npm/lib/test.js @@ -1,12 +1,19 @@ -const npm = require('./npm.js') -const testCmd = require('./utils/lifecycle-cmd.js')(npm, 'test') -const { completion, usage } = testCmd -const cmd = (args, cb) => testCmd(args, er => { - if (er && er.code === 'ELIFECYCLE') { - /* eslint-disable standard/no-callback-literal */ - cb('Test failed. See above for more details.') - } else - cb(er) -}) +const LifecycleCmd = require('./utils/lifecycle-cmd.js') -module.exports = Object.assign(cmd, { completion, usage }) +// This ends up calling run-script(['test', ...args]) +class Test extends LifecycleCmd { + constructor (npm) { + super(npm, 'test') + } + + exec (args, cb) { + super.exec(args, er => { + if (er && er.code === 'ELIFECYCLE') { + /* eslint-disable standard/no-callback-literal */ + cb('Test failed. See above for more details.') + } else + cb(er) + }) + } +} +module.exports = Test diff --git a/deps/npm/lib/token.js b/deps/npm/lib/token.js index 1e78584c77..ad6d5c6fcb 100644 --- a/deps/npm/lib/token.js +++ b/deps/npm/lib/token.js @@ -1,127 +1,68 @@ -const profile = require('npm-profile') -const npm = require('./npm.js') -const output = require('./utils/output.js') -const otplease = require('./utils/otplease.js') const Table = require('cli-table3') -const isCidrV4 = require('is-cidr').v4 -const isCidrV6 = require('is-cidr').v6 -const readUserInfo = require('./utils/read-user-info.js') const ansistyles = require('ansistyles') +const { v4: isCidrV4, v6: isCidrV6 } = require('is-cidr') const log = require('npmlog') -const pulseTillDone = require('./utils/pulse-till-done.js') +const profile = require('npm-profile') -module.exports = token +const otplease = require('./utils/otplease.js') +const output = require('./utils/output.js') +const pulseTillDone = require('./utils/pulse-till-done.js') +const readUserInfo = require('./utils/read-user-info.js') +const usageUtil = require('./utils/usage.js') -token._validateCIDRList = validateCIDRList +class Token { + constructor (npm) { + this.npm = npm + } -const usageUtil = require('./utils/usage.js') -token.usage = usageUtil('token', - 'npm token list\n' + - 'npm token revoke <id|token>\n' + - 'npm token create [--read-only] [--cidr=list]') - -const UsageError = (msg) => - Object.assign(new Error(`\nUsage: ${msg}\n\n` + token.usage), { - code: 'EUSAGE', - }) - -const InvalidCIDRError = (msg) => - Object.assign(new Error(msg), { code: 'EINVALIDCIDR' }) - -token.subcommands = ['list', 'revoke', 'create'] - -token.completion = async (opts) => { - var argv = opts.conf.argv.remain - if (argv.length === 2) - return token.subcommands - - switch (argv[2]) { - case 'list': - case 'revoke': - case 'create': - return [] - default: - throw new Error(argv[2] + ' not recognized') + get usage () { + return usageUtil('token', + 'npm token list\n' + + 'npm token revoke <id|token>\n' + + 'npm token create [--read-only] [--cidr=list]' + ) } -} -function withCb (prom, cb) { - prom.then((value) => cb(null, value), cb) -} + async completion (opts) { + const argv = opts.conf.argv.remain + const subcommands = ['list', 'revoke', 'create'] + if (argv.length === 2) + return subcommands + + if (subcommands.includes(argv[2])) + return [] -function token (args, cb) { - log.gauge.show('token') - if (args.length === 0) - return withCb(list([]), cb) - switch (args[0]) { - case 'list': - case 'ls': - withCb(list(), cb) - break - case 'delete': - case 'revoke': - case 'remove': - case 'rm': - withCb(rm(args.slice(1)), cb) - break - case 'create': - withCb(create(args.slice(1)), cb) - break - default: - cb(UsageError(`${args[0]} is not a recognized subcommand.`)) + throw new Error(argv[2] + ' not recognized') } -} -function generateTokenIds (tokens, minLength) { - const byId = {} - for (const token of tokens) { - token.id = token.key - for (let ii = minLength; ii < token.key.length; ++ii) { - const match = tokens.some(ot => - ot !== token && - ot.key.slice(0, ii) === token.key.slice(0, ii)) - if (!match) { - token.id = token.key.slice(0, ii) - break - } - } - byId[token.id] = token + exec (args, cb) { + this.token(args).then(() => cb()).catch(cb) } - return byId -} -function config () { - const conf = { ...npm.flatOptions } - const creds = npm.config.getCredentialsByURI(conf.registry) - if (creds.token) - conf.auth = { token: creds.token } - else if (creds.username) { - conf.auth = { - basic: { - username: creds.username, - password: creds.password, - }, - } - } else if (creds.auth) { - const auth = Buffer.from(creds.auth, 'base64').toString().split(':', 2) - conf.auth = { - basic: { - username: auth[0], - password: auth[1], - }, + async token (args, cb) { + log.gauge.show('token') + if (args.length === 0) + return this.list() + switch (args[0]) { + case 'list': + case 'ls': + return this.list() + case 'delete': + case 'revoke': + case 'remove': + case 'rm': + return this.rm(args.slice(1)) + case 'create': + return this.create(args.slice(1)) + default: + throw this.usageError(`${args[0]} is not a recognized subcommand.`) } - } else - conf.auth = {} - - if (conf.otp) - conf.auth.otp = conf.otp - return conf -} + } -function list (args) { - const conf = config() - log.info('token', 'getting list') - return pulseTillDone.withPromise(profile.listTokens(conf)).then((tokens) => { + async list () { + const conf = this.config() + log.info('token', 'getting list') + const tokens = await pulseTillDone.withPromise(profile.listTokens(conf)) if (conf.json) { output(JSON.stringify(tokens, null, 2)) return @@ -138,7 +79,7 @@ function list (args) { }) return } - generateTokenIds(tokens, 6) + this.generateTokenIds(tokens, 6) const idWidth = tokens.reduce((acc, token) => Math.max(acc, token.id.length), 0) const table = new Table({ @@ -155,18 +96,17 @@ function list (args) { ]) }) output(table.toString()) - }) -} + } -function rm (args) { - if (args.length === 0) - return Promise.reject(UsageError('`<tokenKey>` argument is required.')) + async rm (args) { + if (args.length === 0) + throw this.usageError('`<tokenKey>` argument is required.') - const conf = config() - const toRemove = [] - const progress = log.newItem('removing tokens', toRemove.length) - progress.info('token', 'getting existing list') - return pulseTillDone.withPromise(profile.listTokens(conf).then((tokens) => { + const conf = this.config() + const toRemove = [] + const progress = log.newItem('removing tokens', toRemove.length) + progress.info('token', 'getting existing list') + const tokens = await pulseTillDone.withPromise(profile.listTokens(conf)) args.forEach((id) => { const matches = tokens.filter((token) => token.key.indexOf(id) === 0) if (matches.length === 1) @@ -181,59 +121,113 @@ function rm (args) { toRemove.push(id) } }) - return Promise.all(toRemove.map(key => { + await Promise.all(toRemove.map(key => { return otplease(conf, conf => { return profile.removeToken(key, conf) }) })) - })).then(() => { if (conf.json) output(JSON.stringify(toRemove)) else if (conf.parseable) output(toRemove.join('\t')) else output('Removed ' + toRemove.length + ' token' + (toRemove.length !== 1 ? 's' : '')) - }) -} + } -function create (args) { - const conf = config() - const cidr = conf.cidr - const readonly = conf.readOnly + async create (args) { + const conf = this.config() + const cidr = conf.cidr + const readonly = conf.readOnly + + return readUserInfo.password().then((password) => { + const validCIDR = this.validateCIDRList(cidr) + log.info('token', 'creating') + return pulseTillDone.withPromise(otplease(conf, conf => { + return profile.createToken(password, readonly, validCIDR, conf) + })) + }).then((result) => { + delete result.key + delete result.updated + if (conf.json) + output(JSON.stringify(result)) + else if (conf.parseable) + Object.keys(result).forEach((k) => output(k + '\t' + result[k])) + else { + const table = new Table() + for (const k of Object.keys(result)) + table.push({ [ansistyles.bright(k)]: String(result[k]) }) + output(table.toString()) + } + }) + } - return readUserInfo.password().then((password) => { - const validCIDR = validateCIDRList(cidr) - log.info('token', 'creating') - return pulseTillDone.withPromise(otplease(conf, conf => { - return profile.createToken(password, readonly, validCIDR, conf) - })) - }).then((result) => { - delete result.key - delete result.updated - if (conf.json) - output(JSON.stringify(result)) - else if (conf.parseable) - Object.keys(result).forEach((k) => output(k + '\t' + result[k])) - else { - const table = new Table() - for (const k of Object.keys(result)) - table.push({ [ansistyles.bright(k)]: String(result[k]) }) - output(table.toString()) - } - }) -} + config () { + const conf = { ...this.npm.flatOptions } + const creds = this.npm.config.getCredentialsByURI(conf.registry) + if (creds.token) + conf.auth = { token: creds.token } + else if (creds.username) { + conf.auth = { + basic: { + username: creds.username, + password: creds.password, + }, + } + } else if (creds.auth) { + const auth = Buffer.from(creds.auth, 'base64').toString().split(':', 2) + conf.auth = { + basic: { + username: auth[0], + password: auth[1], + }, + } + } else + conf.auth = {} -function validateCIDR (cidr) { - if (isCidrV6(cidr)) - throw InvalidCIDRError('CIDR whitelist can only contain IPv4 addresses, ' + cidr + ' is IPv6') + if (conf.otp) + conf.auth.otp = conf.otp + return conf + } - if (!isCidrV4(cidr)) - throw InvalidCIDRError('CIDR whitelist contains invalid CIDR entry: ' + cidr) -} + usageError (msg) { + return Object.assign(new Error(`\nUsage: ${msg}\n\n` + this.usage), { + code: 'EUSAGE', + }) + } -function validateCIDRList (cidrs) { - const maybeList = cidrs ? (Array.isArray(cidrs) ? cidrs : [cidrs]) : [] - const list = maybeList.length === 1 ? maybeList[0].split(/,\s*/) : maybeList - list.forEach(validateCIDR) - return list + invalidCIDRError (msg) { + return Object.assign(new Error(msg), { code: 'EINVALIDCIDR' }) + } + + generateTokenIds (tokens, minLength) { + const byId = {} + for (const token of tokens) { + token.id = token.key + for (let ii = minLength; ii < token.key.length; ++ii) { + const match = tokens.some(ot => + ot !== token && + ot.key.slice(0, ii) === token.key.slice(0, ii)) + if (!match) { + token.id = token.key.slice(0, ii) + break + } + } + byId[token.id] = token + } + return byId + } + + validateCIDRList (cidrs) { + const maybeList = cidrs ? (Array.isArray(cidrs) ? cidrs : [cidrs]) : [] + const list = maybeList.length === 1 ? maybeList[0].split(/,\s*/) : maybeList + for (const cidr of list) { + if (isCidrV6(cidr)) + throw this.invalidCIDRError('CIDR whitelist can only contain IPv4 addresses, ' + cidr + ' is IPv6') + + if (!isCidrV4(cidr)) + throw this.invalidCIDRError('CIDR whitelist contains invalid CIDR entry: ' + cidr) + } + return list + } } +module.exports = Token diff --git a/deps/npm/lib/uninstall.js b/deps/npm/lib/uninstall.js index 15995c0b3c..d7116e4c2d 100644 --- a/deps/npm/lib/uninstall.js +++ b/deps/npm/lib/uninstall.js @@ -2,49 +2,62 @@ const { resolve } = require('path') const Arborist = require('@npmcli/arborist') const rpj = require('read-package-json-fast') -const npm = require('./npm.js') const usageUtil = require('./utils/usage.js') const reifyFinish = require('./utils/reify-finish.js') const completion = require('./utils/completion/installed-shallow.js') -const usage = usageUtil( - 'uninstall', - 'npm uninstall [<@scope>/]<pkg>[@<version>]... [-S|--save|--no-save]' -) - -const cmd = (args, cb) => rm(args).then(() => cb()).catch(cb) - -const rm = async args => { - // the /path/to/node_modules/.. - const { global, prefix } = npm.flatOptions - const path = global ? resolve(npm.globalDir, '..') : prefix - - if (!args.length) { - if (!global) - throw new Error('Must provide a package name to remove') - else { - let pkg - - try { - pkg = await rpj(resolve(npm.localPrefix, 'package.json')) - } catch (er) { - if (er.code !== 'ENOENT' && er.code !== 'ENOTDIR') - throw er - else - throw usage - } +class Uninstall { + constructor (npm) { + this.npm = npm + } - args.push(pkg.name) - } + get usage () { + return usageUtil( + 'uninstall', + 'npm uninstall [<@scope>/]<pkg>[@<version>]... [-S|--save|--no-save]' + ) } - const arb = new Arborist({ ...npm.flatOptions, path }) + /* istanbul ignore next - see test/lib/load-all-commands.js */ + async completion (opts) { + return completion(this.npm, opts) + } - await arb.reify({ - ...npm.flatOptions, - rm: args, - }) - await reifyFinish(arb) -} + exec (args, cb) { + this.uninstall(args).then(() => cb()).catch(cb) + } -module.exports = Object.assign(cmd, { usage, completion }) + async uninstall (args) { + // the /path/to/node_modules/.. + const { global, prefix } = this.npm.flatOptions + const path = global ? resolve(this.npm.globalDir, '..') : prefix + + if (!args.length) { + if (!global) + throw new Error('Must provide a package name to remove') + else { + let pkg + + try { + pkg = await rpj(resolve(this.npm.localPrefix, 'package.json')) + } catch (er) { + if (er.code !== 'ENOENT' && er.code !== 'ENOTDIR') + throw er + else + throw this.usage + } + + args.push(pkg.name) + } + } + + const arb = new Arborist({ ...this.npm.flatOptions, path }) + + await arb.reify({ + ...this.npm.flatOptions, + rm: args, + }) + await reifyFinish(this.npm, arb) + } +} +module.exports = Uninstall diff --git a/deps/npm/lib/unpublish.js b/deps/npm/lib/unpublish.js index bb931682b4..34751da4a5 100644 --- a/deps/npm/lib/unpublish.js +++ b/deps/npm/lib/unpublish.js @@ -7,102 +7,107 @@ const npmFetch = require('npm-registry-fetch') const libunpub = require('libnpmpublish').unpublish const readJson = util.promisify(require('read-package-json')) -const npm = require('./npm.js') const usageUtil = require('./utils/usage.js') const output = require('./utils/output.js') const otplease = require('./utils/otplease.js') const getIdentity = require('./utils/get-identity.js') -const usage = usageUtil('unpublish', 'npm unpublish [<@scope>/]<pkg>[@<version>]') - -const cmd = (args, cb) => unpublish(args).then(() => cb()).catch(cb) - -const completion = async (args) => { - const { partialWord, conf } = args - - if (conf.argv.remain.length >= 3) - return [] - - const opts = npm.flatOptions - const username = await getIdentity({ ...opts }).catch(() => null) - if (!username) - return [] - - const access = await libaccess.lsPackages(username, opts) - // do a bit of filtering at this point, so that we don't need - // to fetch versions for more than one thing, but also don't - // accidentally unpublish a whole project - let pkgs = Object.keys(access || {}) - if (!partialWord || !pkgs.length) - return pkgs +class Unpublish { + constructor (npm) { + this.npm = npm + } - const pp = npa(partialWord).name - pkgs = pkgs.filter(p => !p.indexOf(pp)) - if (pkgs.length > 1) - return pkgs + get usage () { + return usageUtil('unpublish', 'npm unpublish [<@scope>/]<pkg>[@<version>]') + } - const json = await npmFetch.json(npa(pkgs[0]).escapedName, opts) - const versions = Object.keys(json.versions) - if (!versions.length) - return pkgs - else - return versions.map(v => `${pkgs[0]}@${v}`) -} + async completion (args) { + const { partialWord, conf } = args + + if (conf.argv.remain.length >= 3) + return [] + + const opts = this.npm.flatOptions + const username = await getIdentity(this.npm, { ...opts }).catch(() => null) + if (!username) + return [] + + const access = await libaccess.lsPackages(username, opts) + // do a bit of filtering at this point, so that we don't need + // to fetch versions for more than one thing, but also don't + // accidentally unpublish a whole project + let pkgs = Object.keys(access || {}) + if (!partialWord || !pkgs.length) + return pkgs + + const pp = npa(partialWord).name + pkgs = pkgs.filter(p => !p.indexOf(pp)) + if (pkgs.length > 1) + return pkgs + + const json = await npmFetch.json(npa(pkgs[0]).escapedName, opts) + const versions = Object.keys(json.versions) + if (!versions.length) + return pkgs + else + return versions.map(v => `${pkgs[0]}@${v}`) + } -async function unpublish (args) { - if (args.length > 1) - throw new Error(usage) - - const spec = args.length && npa(args[0]) - const opts = npm.flatOptions - const { force, silent, loglevel } = opts - let res - let pkgName - let pkgVersion - - log.silly('unpublish', 'args[0]', args[0]) - log.silly('unpublish', 'spec', spec) - - if (!spec.rawSpec && !force) { - throw new Error( - 'Refusing to delete entire project.\n' + - 'Run with --force to do this.\n' + - usage - ) + exec (args, cb) { + this.unpublish(args).then(() => cb()).catch(cb) } - if (!spec || path.resolve(spec.name) === npm.localPrefix) { - // if there's a package.json in the current folder, then - // read the package name and version out of that. - const pkgJson = path.join(npm.localPrefix, 'package.json') - let manifest - try { - manifest = await readJson(pkgJson) - } catch (err) { - if (err && err.code !== 'ENOENT' && err.code !== 'ENOTDIR') - throw err - else - throw new Error(`Usage: ${usage}`) + async unpublish (args) { + if (args.length > 1) + throw new Error(this.usage) + + const spec = args.length && npa(args[0]) + const opts = this.npm.flatOptions + const { force, silent, loglevel } = opts + let pkgName + let pkgVersion + + log.silly('unpublish', 'args[0]', args[0]) + log.silly('unpublish', 'spec', spec) + + if (!spec.rawSpec && !force) { + throw new Error( + 'Refusing to delete entire project.\n' + + 'Run with --force to do this.\n' + + this.usage + ) } - log.verbose('unpublish', manifest) - - const { name, version, publishConfig } = manifest - const pkgJsonSpec = npa.resolve(name, version) - const optsWithPub = { ...opts, publishConfig } - res = await otplease(opts, opts => libunpub(pkgJsonSpec, optsWithPub)) - pkgName = name - pkgVersion = version ? `@${version}` : '' - } else { - res = await otplease(opts, opts => libunpub(spec, opts)) - pkgName = spec.name - pkgVersion = spec.type === 'version' ? `@${spec.rawSpec}` : '' - } - - if (!silent && loglevel !== 'silent') - output(`- ${pkgName}${pkgVersion}`) + 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. + const pkgJson = path.join(this.npm.localPrefix, 'package.json') + let manifest + try { + manifest = await readJson(pkgJson) + } catch (err) { + if (err && err.code !== 'ENOENT' && err.code !== 'ENOTDIR') + throw err + else + throw new Error(`Usage: ${this.usage}`) + } + + log.verbose('unpublish', manifest) + + const { name, version, publishConfig } = manifest + const pkgJsonSpec = npa.resolve(name, version) + const optsWithPub = { ...opts, publishConfig } + await otplease(opts, opts => libunpub(pkgJsonSpec, optsWithPub)) + pkgName = name + pkgVersion = version ? `@${version}` : '' + } else { + await otplease(opts, opts => libunpub(spec, opts)) + pkgName = spec.name + pkgVersion = spec.type === 'version' ? `@${spec.rawSpec}` : '' + } - return res + if (!silent && loglevel !== 'silent') + output(`- ${pkgName}${pkgVersion}`) + } } - -module.exports = Object.assign(cmd, { completion, usage }) +module.exports = Unpublish diff --git a/deps/npm/lib/unstar.js b/deps/npm/lib/unstar.js index 554bd60bc9..c814ef2b66 100644 --- a/deps/npm/lib/unstar.js +++ b/deps/npm/lib/unstar.js @@ -1,9 +1,9 @@ -const { usage } = require('./star.js') -const npm = require('./npm.js') +const Star = require('./star.js') -const unstar = (args, cb) => { - npm.config.set('star.unstar', true) - return npm.commands.star(args, cb) +class Unstar extends Star { + exec (args, cb) { + this.npm.config.set('star.unstar', true) + super.exec(args, cb) + } } - -module.exports = Object.assign(unstar, { usage }) +module.exports = Unstar diff --git a/deps/npm/lib/update.js b/deps/npm/lib/update.js index 6eaa085b87..98043e09c7 100644 --- a/deps/npm/lib/update.js +++ b/deps/npm/lib/update.js @@ -3,37 +3,51 @@ const path = require('path') const Arborist = require('@npmcli/arborist') const log = require('npmlog') -const npm = require('./npm.js') const usageUtil = require('./utils/usage.js') const reifyFinish = require('./utils/reify-finish.js') const completion = require('./utils/completion/installed-deep.js') -const usage = usageUtil( - 'update', - 'npm update [-g] [<pkg>...]' -) - -const cmd = (args, cb) => update(args).then(() => cb()).catch(cb) +class Update { + constructor (npm) { + this.npm = npm + } -const update = async args => { - const update = args.length === 0 ? true : args - const global = path.resolve(npm.globalDir, '..') - const where = npm.flatOptions.global - ? global - : npm.prefix + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil( + 'update', + 'npm update [-g] [<pkg>...]' + ) + } - if (npm.flatOptions.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') + /* istanbul ignore next - see test/lib/load-all-commands.js */ + async completion (opts) { + return completion(this.npm, opts) } - const arb = new Arborist({ - ...npm.flatOptions, - path: where, - }) + exec (args, cb) { + this.update(args).then(() => cb()).catch(cb) + } - await arb.reify({ update }) - await reifyFinish(arb) + async update (args) { + const update = args.length === 0 ? true : args + const global = path.resolve(this.npm.globalDir, '..') + const where = this.npm.flatOptions.global + ? global + : this.npm.prefix + + if (this.npm.flatOptions.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') + } + + const arb = new Arborist({ + ...this.npm.flatOptions, + path: where, + }) + + await arb.reify({ update }) + await reifyFinish(this.npm, arb) + } } - -module.exports = Object.assign(cmd, { usage, completion }) +module.exports = Update diff --git a/deps/npm/lib/utils/audit-error.js b/deps/npm/lib/utils/audit-error.js index c7423c4472..ae0749ff6f 100644 --- a/deps/npm/lib/utils/audit-error.js +++ b/deps/npm/lib/utils/audit-error.js @@ -4,8 +4,7 @@ // returns 'true' if there was an error, false otherwise const output = require('./output.js') -const npm = require('../npm.js') -const auditError = (report) => { +const auditError = (npm, report) => { if (!report || !report.error) return false diff --git a/deps/npm/lib/utils/completion/installed-deep.js b/deps/npm/lib/utils/completion/installed-deep.js index f464bb9a9d..b65c17e41d 100644 --- a/deps/npm/lib/utils/completion/installed-deep.js +++ b/deps/npm/lib/utils/completion/installed-deep.js @@ -1,8 +1,7 @@ const { resolve } = require('path') const Arborist = require('@npmcli/arborist') -const npm = require('../../npm.js') -const installedDeep = async () => { +const installedDeep = async (npm) => { const { depth, global, diff --git a/deps/npm/lib/utils/completion/installed-shallow.js b/deps/npm/lib/utils/completion/installed-shallow.js index c9c680e7dd..1c9b8ef5ac 100644 --- a/deps/npm/lib/utils/completion/installed-shallow.js +++ b/deps/npm/lib/utils/completion/installed-shallow.js @@ -1,10 +1,8 @@ -const npm = require('../../npm.js') const { promisify } = require('util') const readdir = promisify(require('readdir-scoped-modules')) -const names = global => readdir(global ? npm.globalDir : npm.localDir) - -const installedShallow = async (opts) => { +const installedShallow = async (npm, opts) => { + const names = global => readdir(global ? npm.globalDir : npm.localDir) const { conf: { argv: { remain } } } = opts if (remain.length > 3) return null diff --git a/deps/npm/lib/utils/explain-dep.js b/deps/npm/lib/utils/explain-dep.js index 213493c654..c01bc780bf 100644 --- a/deps/npm/lib/utils/explain-dep.js +++ b/deps/npm/lib/utils/explain-dep.js @@ -86,9 +86,10 @@ const explainDependents = ({ name, dependents }, depth, color) => { return str.split('\n').join('\n ') } -const explainEdge = ({ name, type, from, spec }, depth, color) => { +const explainEdge = ({ name, type, bundled, from, spec }, depth, color) => { const { bold } = color ? chalk : nocolor return (type === 'prod' ? '' : `${colorType(type, color)} `) + + (bundled ? `${colorType('bundled', color)} ` : '') + `${bold(name)}@"${bold(spec)}" from ` + explainFrom(from, depth, color) } diff --git a/deps/npm/lib/utils/get-identity.js b/deps/npm/lib/utils/get-identity.js index d5c560161d..e92a2c524e 100644 --- a/deps/npm/lib/utils/get-identity.js +++ b/deps/npm/lib/utils/get-identity.js @@ -1,10 +1,9 @@ const npmFetch = require('npm-registry-fetch') -const npm = require('../npm') const needsAuthError = (msg) => Object.assign(new Error(msg), { code: 'ENEEDAUTH' }) -module.exports = async (opts = {}) => { +module.exports = async (npm, opts = {}) => { const { registry } = opts if (!registry) throw Object.assign(new Error('No registry specified.'), { code: 'ENOREGISTRY' }) diff --git a/deps/npm/lib/utils/lifecycle-cmd.js b/deps/npm/lib/utils/lifecycle-cmd.js index 94b109942a..8be9b5a12f 100644 --- a/deps/npm/lib/utils/lifecycle-cmd.js +++ b/deps/npm/lib/utils/lifecycle-cmd.js @@ -1,10 +1,19 @@ // The implementation of commands that are just "run a script" -// test, start, stop, restart - +// restart, start, stop, test const usageUtil = require('./usage.js') -module.exports = (npm, stage) => { - const cmd = (args, cb) => npm.commands['run-script']([stage, ...args], cb) - const usage = usageUtil(stage, `npm ${stage} [-- <args>]`) - return Object.assign(cmd, { usage }) +class LifecycleCmd { + constructor (npm, stage) { + this.npm = npm + this.stage = stage + } + + get usage () { + return usageUtil(this.stage, `npm ${this.stage} [-- <args>]`) + } + + exec (args, cb) { + this.npm.commands['run-script']([this.stage, ...args], cb) + } } +module.exports = LifecycleCmd diff --git a/deps/npm/lib/utils/npm-usage.js b/deps/npm/lib/utils/npm-usage.js index d4261f79dc..220f8037f1 100644 --- a/deps/npm/lib/utils/npm-usage.js +++ b/deps/npm/lib/utils/npm-usage.js @@ -1,10 +1,9 @@ -const npm = require('../npm.js') const didYouMean = require('./did-you-mean.js') const { dirname } = require('path') const output = require('./output.js') const { cmdList } = require('./cmd-list') -module.exports = (valid = true) => { +module.exports = (npm, valid = true) => { npm.config.set('loglevel', 'silent') const usesBrowser = npm.config.get('viewer') === 'browser' ? ' (in a browser)' : '' @@ -22,7 +21,7 @@ npm help <term> search for help on <term>${usesBrowser} npm help npm more involved overview${usesBrowser} All commands: -${npm.config.get('long') ? usages() : ('\n ' + wrap(cmdList))} +${npm.config.get('long') ? usages(npm) : ('\n ' + wrap(cmdList))} Specify configs in the ini-formatted file: ${npm.config.get('userconfig')} @@ -59,12 +58,11 @@ const wrap = (arr) => { return out.join('\n ').substr(2) } -const usages = () => { +const usages = (npm) => { // return a string of <command>: <usage> let maxLen = 0 return cmdList.reduce((set, c) => { - set.push([c, require(`../${npm.deref(c)}.js`).usage || - /* istanbul ignore next - all commands should have usage */ '']) + set.push([c, npm.commands[c].usage]) maxLen = Math.max(maxLen, c.length) return set }, []) diff --git a/deps/npm/lib/utils/open-url.js b/deps/npm/lib/utils/open-url.js index 28c2d038a4..1fe456bd05 100644 --- a/deps/npm/lib/utils/open-url.js +++ b/deps/npm/lib/utils/open-url.js @@ -1,19 +1,12 @@ -const npm = require('../npm.js') const output = require('./output.js') const opener = require('opener') const { URL } = require('url') -const isUrlValid = url => { - try { - return /^(https?|file):$/.test(new URL(url).protocol) - } catch (_) { - return false - } -} - // attempt to open URL in web-browser, print address otherwise: -module.exports = function open (url, errMsg, cb, browser = npm.config.get('browser')) { +const open = async (npm, url, errMsg) => { + const browser = npm.config.get('browser') + function printAlternateMsg () { const json = npm.config.get('json') const alternateMsg = json @@ -28,18 +21,28 @@ module.exports = function open (url, errMsg, cb, browser = npm.config.get('brows if (browser === false) { printAlternateMsg() - return cb() + return } - if (!isUrlValid(url)) - return cb(new Error('Invalid URL: ' + url)) + try { + if (!/^(https?|file):$/.test(new URL(url).protocol)) + throw new Error() + } catch (_) { + throw new Error('Invalid URL: ' + url) + } const command = browser === true ? null : browser - opener(url, { command }, (er) => { - if (er && er.code === 'ENOENT') { - printAlternateMsg() - return cb() - } else - return cb(er) + await new Promise((resolve, reject) => { + opener(url, { command }, (err) => { + if (err) { + if (err.code === 'ENOENT') + printAlternateMsg() + else + return reject(err) + } + return resolve() + }) }) } + +module.exports = open diff --git a/deps/npm/lib/utils/read-local-package.js b/deps/npm/lib/utils/read-local-package.js index 7ab130c1f3..c31bca9947 100644 --- a/deps/npm/lib/utils/read-local-package.js +++ b/deps/npm/lib/utils/read-local-package.js @@ -1,8 +1,6 @@ const { resolve } = require('path') const readJson = require('read-package-json-fast') -const npm = require('../npm.js') - -async function readLocalPackageName (cb) { +async function readLocalPackageName (npm) { if (npm.flatOptions.global) return diff --git a/deps/npm/lib/utils/reify-finish.js b/deps/npm/lib/utils/reify-finish.js index 9c95e9fcff..1c02b93a41 100644 --- a/deps/npm/lib/utils/reify-finish.js +++ b/deps/npm/lib/utils/reify-finish.js @@ -1,17 +1,16 @@ const reifyOutput = require('./reify-output.js') -const npm = require('../npm.js') const ini = require('ini') const util = require('util') const fs = require('fs') const { writeFile } = fs.promises || { writeFile: util.promisify(fs.writeFile) } const {resolve} = require('path') -const reifyFinish = async arb => { - await saveBuiltinConfig(arb) - reifyOutput(arb) +const reifyFinish = async (npm, arb) => { + await saveBuiltinConfig(npm, arb) + reifyOutput(npm, arb) } -const saveBuiltinConfig = async arb => { +const saveBuiltinConfig = async (npm, arb) => { const { options: { global }, actualTree } = arb if (!global) return diff --git a/deps/npm/lib/utils/reify-output.js b/deps/npm/lib/utils/reify-output.js index 4abaadc2ec..216f0e902e 100644 --- a/deps/npm/lib/utils/reify-output.js +++ b/deps/npm/lib/utils/reify-output.js @@ -9,7 +9,6 @@ // found 37 vulnerabilities (5 low, 7 moderate, 25 high) // run `npm audit fix` to fix them, or `npm audit` for details -const npm = require('../npm.js') const log = require('npmlog') const output = require('./output.js') const { depth } = require('treeverse') @@ -19,7 +18,7 @@ const { readTree: getFundingInfo } = require('libnpmfund') const auditError = require('./audit-error.js') // TODO: output JSON if flatOptions.json is true -const reifyOutput = arb => { +const reifyOutput = (npm, arb) => { // don't print any info in --silent mode if (log.levels[log.level] > log.levels.error) return @@ -29,7 +28,7 @@ const reifyOutput = arb => { // note: fails and crashes if we're running audit fix and there was an error // which is a good thing, because there's no point printing all this other // stuff in that case! - const auditReport = auditError(arb.auditReport) ? null : arb.auditReport + const auditReport = auditError(npm, arb.auditReport) ? null : arb.auditReport const summary = { added: 0, @@ -75,9 +74,9 @@ const reifyOutput = arb => { } output(JSON.stringify(summary, 0, 2)) } else { - packagesChangedMessage(summary) + packagesChangedMessage(npm, summary) packagesFundingMessage(summary) - printAuditReport(auditReport) + printAuditReport(npm, auditReport) } } @@ -85,7 +84,7 @@ const reifyOutput = arb => { // at the end if there's still stuff, because it's silly for `npm audit` // to tell you to run `npm audit` for details. otherwise, use the summary // report. if we get here, we know it's not quiet or json. -const printAuditReport = report => { +const printAuditReport = (npm, report) => { if (!report) return @@ -102,7 +101,7 @@ const printAuditReport = report => { output('\n' + res.report) } -const packagesChangedMessage = ({ added, removed, changed, audited }) => { +const packagesChangedMessage = (npm, { added, removed, changed, audited }) => { const msg = ['\n'] if (added === 0 && removed === 0 && changed === 0) { msg.push('up to date') diff --git a/deps/npm/lib/utils/usage.js b/deps/npm/lib/utils/usage.js index ddcbd708b7..5f4eca73ea 100644 --- a/deps/npm/lib/utils/usage.js +++ b/deps/npm/lib/utils/usage.js @@ -1,7 +1,7 @@ -var aliases = require('../utils/cmd-list').aliases +const aliases = require('../utils/cmd-list').aliases module.exports = function usage (cmd, txt, opt) { - var post = Object.keys(aliases).reduce(function (p, c) { + const post = Object.keys(aliases).reduce(function (p, c) { var val = aliases[c] if (val !== cmd) return p diff --git a/deps/npm/lib/version.js b/deps/npm/lib/version.js index 9d87b2f2c9..1ba834f5d7 100644 --- a/deps/npm/lib/version.js +++ b/deps/npm/lib/version.js @@ -1,75 +1,86 @@ const libversion = require('libnpmversion') -const npm = require('./npm.js') const output = require('./utils/output.js') const usageUtil = require('./utils/usage.js') -const completion = async (opts) => { - const { conf: { argv: { remain } } } = opts - if (remain.length > 2) - return [] +class Version { + constructor (npm) { + this.npm = npm + } - return [ - 'major', - 'minor', - 'patch', - 'premajor', - 'preminor', - 'prepatch', - 'prerelease', - 'from-git', - ] -} + get usage () { + return usageUtil( + 'version', + 'npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease [--preid=<prerelease-id>] | from-git]\n' + + '(run in package dir)\n\n' + + `'npm -v' or 'npm --version' to print npm version (${this.npm.version})\n` + + `'npm view <pkg> version' to view a package's published version\n` + + `'npm ls' to inspect current package/dependency versions\n` + ) + } -const usage = usageUtil('version', -`npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease [--preid=<prerelease-id>] | from-git] -(run in package dir) + async completion (opts) { + const { conf: { argv: { remain } } } = opts + if (remain.length > 2) + return [] -'npm -v' or 'npm --version' to print npm version (${npm.version}) -'npm view <pkg> version' to view a package's published version -'npm ls' to inspect current package/dependency versions` -) + return [ + 'major', + 'minor', + 'patch', + 'premajor', + 'preminor', + 'prepatch', + 'prerelease', + 'from-git', + ] + } -const cmd = (args, cb) => version(args).then(() => cb()).catch(cb) + exec (args, cb) { + return this.version(args).then(() => cb()).catch(cb) + } -const version = async args => { - switch (args.length) { - case 0: - return list() - case 1: - return version_(args) - default: - throw usage + async version (args) { + switch (args.length) { + case 0: + return this.list() + case 1: + return this.change(args) + default: + throw this.usage + } } -} -const version_ = async (args) => { - const prefix = npm.flatOptions.tagVersionPrefix - const version = await libversion(args[0], { - ...npm.flatOptions, - path: npm.prefix, - }) - return output(`${prefix}${version}`) -} + async change (args) { + const prefix = this.npm.flatOptions.tagVersionPrefix + const version = await libversion(args[0], { + ...this.npm.flatOptions, + path: this.npm.prefix, + }) + return output(`${prefix}${version}`) + } -const list = async () => { - const results = {} - const { promisify } = require('util') - const { resolve } = require('path') - const readFile = promisify(require('fs').readFile) - const pj = resolve(npm.prefix, 'package.json') + async list () { + const results = {} + const { promisify } = require('util') + const { resolve } = require('path') + const readFile = promisify(require('fs').readFile) + const pj = resolve(this.npm.prefix, 'package.json') - const pkg = await readFile(pj, 'utf8') - .then(data => JSON.parse(data)) - .catch(() => ({})) + const pkg = await readFile(pj, 'utf8') + .then(data => JSON.parse(data)) + .catch(() => ({})) - if (pkg.name && pkg.version) - results[pkg.name] = pkg.version + if (pkg.name && pkg.version) + results[pkg.name] = pkg.version - results.npm = npm.version - for (const [key, version] of Object.entries(process.versions)) - results[key] = version + results.npm = this.npm.version + for (const [key, version] of Object.entries(process.versions)) + results[key] = version - output(npm.flatOptions.json ? JSON.stringify(results, null, 2) : results) + if (this.npm.flatOptions.json) + output(JSON.stringify(results, null, 2)) + else + output(results) + } } - -module.exports = Object.assign(cmd, { usage, completion }) +module.exports = Version diff --git a/deps/npm/lib/view.js b/deps/npm/lib/view.js index d6d79d35c5..d0d5fa59d4 100644 --- a/deps/npm/lib/view.js +++ b/deps/npm/lib/view.js @@ -3,293 +3,370 @@ const byteSize = require('byte-size') const color = require('ansicolors') const columns = require('cli-columns') +const fs = require('fs') +const jsonParse = require('json-parse-even-better-errors') const log = require('npmlog') const npa = require('npm-package-arg') -const npm = require('./npm.js') -const { packument } = require('pacote') const path = require('path') -const { inspect, promisify } = require('util') const relativeDate = require('tiny-relative-date') const semver = require('semver') const style = require('ansistyles') -const usageUtil = require('./utils/usage') +const { inspect, promisify } = require('util') +const { packument } = require('pacote') + +const usageUtil = require('./utils/usage.js') -const fs = require('fs') const readFile = promisify(fs.readFile) -const jsonParse = require('json-parse-even-better-errors') const readJson = async file => jsonParse(await readFile(file, 'utf8')) -const usage = usageUtil( - 'view', - 'npm view [<@scope>/]<pkg>[@<version>] [<field>[.subfield]...]' -) - -const cmd = (args, cb) => view(args).then(() => cb()).catch(cb) +class View { + constructor (npm) { + this.npm = npm + } -const completion = async (opts) => { - if (opts.conf.argv.remain.length <= 2) { - // There used to be registry completion here, but it stopped - // making sense somewhere around 50,000 packages on the registry - return + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil( + 'view', + 'npm view [<@scope>/]<pkg>[@<version>] [<field>[.subfield]...]' + ) } - // have the package, get the fields - const config = { ...npm.flatOptions, fullMetadata: true, preferOnline: true } - const { defaultTag } = config - const spec = npa(opts.conf.argv.remain[2]) - const pckmnt = await packument(spec, config) - const dv = pckmnt.versions[pckmnt['dist-tags'][defaultTag]] - pckmnt.versions = Object.keys(pckmnt.versions).sort(semver.compareLoose) - - return getFields(pckmnt).concat(getFields(dv)) - - function getFields (d, f, pref) { - f = f || [] - if (!d) + + async completion (opts) { + if (opts.conf.argv.remain.length <= 2) { + // There used to be registry completion here, but it stopped + // making sense somewhere around 50,000 packages on the registry + return + } + // have the package, get the fields + const config = { + ...this.npm.flatOptions, + fullMetadata: true, + preferOnline: true, + } + const { defaultTag } = config + const spec = npa(opts.conf.argv.remain[2]) + const pckmnt = await packument(spec, config) + const dv = pckmnt.versions[pckmnt['dist-tags'][defaultTag]] + pckmnt.versions = Object.keys(pckmnt.versions).sort(semver.compareLoose) + + return getFields(pckmnt).concat(getFields(dv)) + + function getFields (d, f, pref) { + f = f || [] + if (!d) + return f + pref = pref || [] + Object.keys(d).forEach((k) => { + if (k.charAt(0) === '_' || k.indexOf('.') !== -1) + return + const p = pref.concat(k).join('.') + f.push(p) + if (Array.isArray(d[k])) { + d[k].forEach((val, i) => { + const pi = p + '[' + i + ']' + if (val && typeof val === 'object') + getFields(val, f, [p]) + else + f.push(pi) + }) + return + } + if (typeof d[k] === 'object') + getFields(d[k], f, [p]) + }) return f - pref = pref || [] - Object.keys(d).forEach(function (k) { - if (k.charAt(0) === '_' || k.indexOf('.') !== -1) - return - const p = pref.concat(k).join('.') - f.push(p) - if (Array.isArray(d[k])) { - d[k].forEach(function (val, i) { - const pi = p + '[' + i + ']' - if (val && typeof val === 'object') - getFields(val, f, [p]) - else - f.push(pi) - }) - return - } - if (typeof d[k] === 'object') - getFields(d[k], f, [p]) - }) - return f + } } -} -const view = async args => { - if (!args.length) - args = ['.'] - - const opts = { ...npm.flatOptions, preferOnline: true, fullMetadata: true } - const pkg = args.shift() - let nv - if (/^[.]@/.test(pkg)) - nv = npa.resolve(null, pkg.slice(2)) - else - nv = npa(pkg) - - const name = nv.name - const local = (name === '.' || !name) - - if (opts.global && local) - throw new Error('Cannot use view command in global mode.') - - if (local) { - const dir = npm.prefix - const manifest = await readJson(path.resolve(dir, 'package.json')) - if (!manifest.name) - throw new Error('Invalid package.json, no "name" field') - const p = manifest.name - nv = npa(p) - if (pkg && ~pkg.indexOf('@')) - nv.rawSpec = pkg.split('@')[pkg.indexOf('@')] - - await fetchAndRead(nv, args, opts) - } else - await fetchAndRead(nv, args, opts) -} + exec (args, cb) { + this.view(args).then(() => cb()).catch(cb) + } -const fetchAndRead = async (nv, args, opts) => { - // get the data about this package - let version = nv.rawSpec || npm.flatOptions.defaultTag + async view (args) { + if (!args.length) + args = ['.'] - const pckmnt = await packument(nv, opts) + const opts = { + ...this.npm.flatOptions, + preferOnline: true, + fullMetadata: true, + } + const pkg = args.shift() + let nv + if (/^[.]@/.test(pkg)) + nv = npa.resolve(null, pkg.slice(2)) + else + nv = npa(pkg) + + const name = nv.name + const local = (name === '.' || !name) + + if (opts.global && local) + throw new Error('Cannot use view command in global mode.') + + if (local) { + const dir = this.npm.prefix + const manifest = await readJson(path.resolve(dir, 'package.json')) + if (!manifest.name) + throw new Error('Invalid package.json, no "name" field') + const p = manifest.name + nv = npa(p) + if (pkg && ~pkg.indexOf('@')) + nv.rawSpec = pkg.split('@')[pkg.indexOf('@')] + } - if (pckmnt['dist-tags'] && pckmnt['dist-tags'][version]) - version = pckmnt['dist-tags'][version] + // get the data about this package + let version = nv.rawSpec || this.npm.flatOptions.defaultTag - if (pckmnt.time && pckmnt.time.unpublished) { - const u = pckmnt.time.unpublished - const er = new Error('Unpublished by ' + u.name + ' on ' + u.time) - er.statusCode = 404 - er.code = 'E404' - er.pkgid = pckmnt._id - throw er - } + const pckmnt = await packument(nv, opts) - const results = [] - const versions = pckmnt.versions || {} - pckmnt.versions = Object.keys(versions).sort(semver.compareLoose) - if (!args.length) - args = [''] + if (pckmnt['dist-tags'] && pckmnt['dist-tags'][version]) + version = pckmnt['dist-tags'][version] - // remove readme unless we asked for it - if (args.indexOf('readme') === -1) - delete pckmnt.readme + if (pckmnt.time && pckmnt.time.unpublished) { + const u = pckmnt.time.unpublished + const er = new Error('Unpublished by ' + u.name + ' on ' + u.time) + er.statusCode = 404 + er.code = 'E404' + er.pkgid = pckmnt._id + throw er + } - Object.keys(versions).forEach(function (v) { - if (semver.satisfies(v, version, true)) { - args.forEach(arg => { - // remove readme unless we asked for it - if (args.indexOf('readme') !== -1) - delete versions[v].readme + const results = [] + const versions = pckmnt.versions || {} + pckmnt.versions = Object.keys(versions).sort(semver.compareLoose) + if (!args.length) + args = [''] - results.push(showFields(pckmnt, versions[v], arg)) - }) + // remove readme unless we asked for it + if (args.indexOf('readme') === -1) + delete pckmnt.readme + + Object.keys(versions).forEach((v) => { + if (semver.satisfies(v, version, true)) { + args.forEach(arg => { + // remove readme unless we asked for it + if (args.indexOf('readme') !== -1) + delete versions[v].readme + + results.push(showFields(pckmnt, versions[v], arg)) + }) + } + }) + let retval = results.reduce(reducer, {}) + + if (args.length === 1 && args[0] === '') { + retval = cleanBlanks(retval) + log.silly('view', retval) } - }) - let retval = results.reduce(reducer, {}) - if (args.length === 1 && args[0] === '') { - retval = cleanBlanks(retval) - log.silly('view', retval) + if ( + !opts.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)) + ) + return retval + } else { + // view by field name + await this.printData(retval, pckmnt._id, opts) + return retval + } } - if ( - !opts.json && - args.length === 1 && - args[0] === '' - ) { - // general view - pckmnt.version = version - await Promise.all( - results.map((v) => prettyView(pckmnt, v[Object.keys(v)[0]][''], opts)) - ) - return retval - } else { - // view by field name - await printData(retval, pckmnt._id, opts) - return retval - } -} + async printData (data, name, opts) { + const versions = Object.keys(data) + let msg = '' + let msgJson = [] + const includeVersions = versions.length > 1 + let includeFields + + versions.forEach((v) => { + const fields = Object.keys(data[v]) + includeFields = includeFields || (fields.length > 1) + if (opts.json) + msgJson.push({}) + fields.forEach((f) => { + let d = cleanup(data[v][f]) + if (fields.length === 1 && opts.json) + msgJson[msgJson.length - 1][f] = d -const prettyView = async (packument, manifest, opts) => { - // More modern, pretty printing of default view - const unicode = opts.unicode - const tags = [] + if (includeVersions || includeFields || typeof d !== 'string') { + if (opts.json) + msgJson[msgJson.length - 1][f] = d + else { + d = inspect(d, { + showHidden: false, + depth: 5, + colors: this.npm.color, + maxArrayLength: null, + }) + } + } else if (typeof d === 'string' && opts.json) + d = JSON.stringify(d) + + if (!opts.json) { + if (f && includeFields) + f += ' = ' + msg += (includeVersions ? name + '@' + v + ' ' : '') + + (includeFields ? f : '') + d + '\n' + } + }) + }) - Object.keys(packument['dist-tags']).forEach((t) => { - const version = packument['dist-tags'][t] - tags.push(`${style.bright(color.green(t))}: ${version}`) - }) - const unpackedSize = manifest.dist.unpackedSize && - byteSize(manifest.dist.unpackedSize) - const licenseField = manifest.license || 'Proprietary' - const info = { - name: color.green(manifest.name), - version: color.green(manifest.version), - bins: Object.keys(manifest.bin || {}).map(color.yellow), - versions: color.yellow(packument.versions.length + ''), - description: manifest.description, - deprecated: manifest.deprecated, - keywords: (packument.keywords || []).map(color.yellow), - license: typeof licenseField === 'string' - ? licenseField - : (licenseField.type || 'Proprietary'), - deps: Object.keys(manifest.dependencies || {}).map((dep) => { - return `${color.yellow(dep)}: ${manifest.dependencies[dep]}` - }), - publisher: manifest._npmUser && unparsePerson({ - name: color.yellow(manifest._npmUser.name), - email: color.cyan(manifest._npmUser.email), - }), - modified: !packument.time ? undefined - : color.yellow(relativeDate(packument.time[packument.version])), - maintainers: (packument.maintainers || []).map((u) => unparsePerson({ - name: color.yellow(u.name), - email: color.cyan(u.email), - })), - repo: ( - manifest.bugs && (manifest.bugs.url || manifest.bugs) - ) || ( - manifest.repository && (manifest.repository.url || manifest.repository) - ), - site: ( - manifest.homepage && (manifest.homepage.url || manifest.homepage) - ), - tags, - tarball: color.cyan(manifest.dist.tarball), - shasum: color.yellow(manifest.dist.shasum), - integrity: manifest.dist.integrity && color.yellow(manifest.dist.integrity), - fileCount: manifest.dist.fileCount && color.yellow(manifest.dist.fileCount), - unpackedSize: unpackedSize && color.yellow(unpackedSize.value) + ' ' + unpackedSize.unit, - } - if (info.license.toLowerCase().trim() === 'proprietary') - info.license = style.bright(color.red(info.license)) - else - info.license = color.green(info.license) - - console.log('') - console.log( - style.underline(style.bright(`${info.name}@${info.version}`)) + - ' | ' + info.license + - ' | deps: ' + (info.deps.length ? color.cyan(info.deps.length) : color.green('none')) + - ' | versions: ' + info.versions - ) - info.description && console.log(info.description) - if (info.repo || info.site) - info.site && console.log(color.cyan(info.site)) - - const warningSign = unicode ? ' ⚠️ ' : '!!' - info.deprecated && console.log( - `\n${style.bright(color.red('DEPRECATED'))}${ - warningSign - } - ${info.deprecated}` - ) + if (opts.json) { + if (msgJson.length && Object.keys(msgJson[0]).length === 1) { + const k = Object.keys(msgJson[0])[0] + msgJson = msgJson.map(m => m[k]) + } + if (msgJson.length === 1) + msg = JSON.stringify(msgJson[0], null, 2) + '\n' + else if (msgJson.length > 1) + msg = JSON.stringify(msgJson, null, 2) + '\n' + } - if (info.keywords.length) { - console.log('') - console.log('keywords:', info.keywords.join(', ')) - } + // disable the progress bar entirely, as we can't meaningfully update it if + // we may have partial lines printed. + log.disableProgress() - if (info.bins.length) { - console.log('') - console.log('bin:', info.bins.join(', ')) + // only log if there is something to log + if (msg !== '') + console.log(msg.trim()) } - console.log('') - console.log('dist') - console.log('.tarball:', info.tarball) - console.log('.shasum:', info.shasum) - info.integrity && console.log('.integrity:', info.integrity) - info.unpackedSize && console.log('.unpackedSize:', info.unpackedSize) + async prettyView (packument, manifest, opts) { + // More modern, pretty printing of default view + const unicode = opts.unicode + const tags = [] + + Object.keys(packument['dist-tags']).forEach((t) => { + const version = packument['dist-tags'][t] + tags.push(`${style.bright(color.green(t))}: ${version}`) + }) + const unpackedSize = manifest.dist.unpackedSize && + byteSize(manifest.dist.unpackedSize) + const licenseField = manifest.license || 'Proprietary' + const info = { + name: color.green(manifest.name), + version: color.green(manifest.version), + bins: Object.keys(manifest.bin || {}).map(color.yellow), + versions: color.yellow(packument.versions.length + ''), + description: manifest.description, + deprecated: manifest.deprecated, + keywords: (packument.keywords || []).map(color.yellow), + license: typeof licenseField === 'string' + ? licenseField + : (licenseField.type || 'Proprietary'), + deps: Object.keys(manifest.dependencies || {}).map((dep) => { + return `${color.yellow(dep)}: ${manifest.dependencies[dep]}` + }), + publisher: manifest._npmUser && unparsePerson({ + name: color.yellow(manifest._npmUser.name), + email: color.cyan(manifest._npmUser.email), + }), + modified: !packument.time ? undefined + : color.yellow(relativeDate(packument.time[packument.version])), + maintainers: (packument.maintainers || []).map((u) => unparsePerson({ + name: color.yellow(u.name), + email: color.cyan(u.email), + })), + repo: ( + manifest.bugs && (manifest.bugs.url || manifest.bugs) + ) || ( + manifest.repository && (manifest.repository.url || manifest.repository) + ), + site: ( + manifest.homepage && (manifest.homepage.url || manifest.homepage) + ), + tags, + tarball: color.cyan(manifest.dist.tarball), + shasum: color.yellow(manifest.dist.shasum), + integrity: + manifest.dist.integrity && color.yellow(manifest.dist.integrity), + fileCount: + manifest.dist.fileCount && color.yellow(manifest.dist.fileCount), + unpackedSize: unpackedSize && color.yellow(unpackedSize.value) + ' ' + unpackedSize.unit, + } + if (info.license.toLowerCase().trim() === 'proprietary') + info.license = style.bright(color.red(info.license)) + else + info.license = color.green(info.license) - const maxDeps = 24 - if (info.deps.length) { console.log('') - console.log('dependencies:') - console.log(columns(info.deps.slice(0, maxDeps), { padding: 1 })) - if (info.deps.length > maxDeps) - console.log(`(...and ${info.deps.length - maxDeps} more.)`) - } + console.log( + style.underline(style.bright(`${info.name}@${info.version}`)) + + ' | ' + info.license + + ' | deps: ' + (info.deps.length ? color.cyan(info.deps.length) : color.green('none')) + + ' | versions: ' + info.versions + ) + info.description && console.log(info.description) + if (info.repo || info.site) + info.site && console.log(color.cyan(info.site)) + + const warningSign = unicode ? ' ⚠️ ' : '!!' + info.deprecated && console.log( + `\n${style.bright(color.red('DEPRECATED'))}${ + warningSign + } - ${info.deprecated}` + ) + + if (info.keywords.length) { + console.log('') + console.log('keywords:', info.keywords.join(', ')) + } + + if (info.bins.length) { + console.log('') + console.log('bin:', info.bins.join(', ')) + } - if (info.maintainers && info.maintainers.length) { console.log('') - console.log('maintainers:') - info.maintainers.forEach((u) => console.log('-', u)) - } + console.log('dist') + console.log('.tarball:', info.tarball) + console.log('.shasum:', info.shasum) + info.integrity && console.log('.integrity:', info.integrity) + info.unpackedSize && console.log('.unpackedSize:', info.unpackedSize) + + const maxDeps = 24 + if (info.deps.length) { + console.log('') + console.log('dependencies:') + console.log(columns(info.deps.slice(0, maxDeps), { padding: 1 })) + if (info.deps.length > maxDeps) + console.log(`(...and ${info.deps.length - maxDeps} more.)`) + } - console.log('') - console.log('dist-tags:') - console.log(columns(info.tags)) + if (info.maintainers && info.maintainers.length) { + console.log('') + console.log('maintainers:') + info.maintainers.forEach((u) => console.log('-', u)) + } - if (info.publisher || info.modified) { - let publishInfo = 'published' - if (info.modified) - publishInfo += ` ${info.modified}` - if (info.publisher) - publishInfo += ` by ${info.publisher}` console.log('') - console.log(publishInfo) + console.log('dist-tags:') + console.log(columns(info.tags)) + + if (info.publisher || info.modified) { + let publishInfo = 'published' + if (info.modified) + publishInfo += ` ${info.modified}` + if (info.publisher) + publishInfo += ` by ${info.publisher}` + console.log('') + console.log(publishInfo) + } } } +module.exports = View function cleanBlanks (obj) { const clean = {} - Object.keys(obj).forEach(function (version) { + Object.keys(obj).forEach((version) => { clean[version] = obj[version][''] }) return clean @@ -297,9 +374,9 @@ function cleanBlanks (obj) { function reducer (l, r) { if (r) { - Object.keys(r).forEach(function (v) { + Object.keys(r).forEach((v) => { l[v] = l[v] || {} - Object.keys(r[v]).forEach(function (t) { + Object.keys(r[v]).forEach((t) => { l[v][t] = r[v][t] }) }) @@ -311,8 +388,8 @@ function reducer (l, r) { // return whatever was printed function showFields (data, version, fields) { const o = {} - ;[data, version].forEach(function (s) { - Object.keys(s).forEach(function (k) { + ;[data, version].forEach((s) => { + Object.keys(s).forEach((k) => { o[k] = s[k] }) }) @@ -346,7 +423,7 @@ function search (data, fields, version, title) { return search(data[0], fields, version, title) let results = [] - data.forEach(function (data, i) { + data.forEach((data, i) => { const tl = title.length const newt = title.substr(0, tl - fields.join('.').length - 1) + '[' + i + ']' + [''].concat(fields).join('.') @@ -368,68 +445,6 @@ function search (data, fields, version, title) { return o } -async function printData (data, name, opts) { - const versions = Object.keys(data) - let msg = '' - let msgJson = [] - const includeVersions = versions.length > 1 - let includeFields - - versions.forEach(function (v) { - const fields = Object.keys(data[v]) - includeFields = includeFields || (fields.length > 1) - if (opts.json) - msgJson.push({}) - fields.forEach(function (f) { - let d = cleanup(data[v][f]) - if (fields.length === 1 && opts.json) - msgJson[msgJson.length - 1][f] = d - - if (includeVersions || includeFields || typeof d !== 'string') { - if (opts.json) - msgJson[msgJson.length - 1][f] = d - else { - d = inspect(d, { - showHidden: false, - depth: 5, - colors: npm.color, - maxArrayLength: null, - }) - } - } else if (typeof d === 'string' && opts.json) - d = JSON.stringify(d) - - if (!opts.json) { - if (f && includeFields) - f += ' = ' - msg += (includeVersions ? name + '@' + v + ' ' : '') + - (includeFields ? f : '') + d + '\n' - } - }) - }) - - if (opts.json) { - if (msgJson.length && Object.keys(msgJson[0]).length === 1) { - const k = Object.keys(msgJson[0])[0] - msgJson = msgJson.map(function (m) { - return m[k] - }) - } - if (msgJson.length === 1) - msg = JSON.stringify(msgJson[0], null, 2) + '\n' - else if (msgJson.length > 1) - msg = JSON.stringify(msgJson, null, 2) + '\n' - } - - // disable the progress bar entirely, as we can't meaningfully update it if - // we may have partial lines printed. - log.disableProgress() - - // only log if there is something to log - if (msg !== '') - console.log(msg.trim()) -} - function cleanup (data) { if (Array.isArray(data)) return data.map(cleanup) @@ -447,10 +462,9 @@ function cleanup (data) { return data } + function unparsePerson (d) { return d.name + (d.email ? ' <' + d.email + '>' : '') + (d.url ? ' (' + d.url + ')' : '') } - -module.exports = Object.assign(cmd, { completion, usage }) diff --git a/deps/npm/lib/whoami.js b/deps/npm/lib/whoami.js index bbbc39ccec..39184ed9c5 100644 --- a/deps/npm/lib/whoami.js +++ b/deps/npm/lib/whoami.js @@ -1,16 +1,29 @@ -const npm = require('./npm.js') const output = require('./utils/output.js') const getIdentity = require('./utils/get-identity.js') const usageUtil = require('./utils/usage.js') -const cmd = (args, cb) => whoami(args).then(() => cb()).catch(cb) +class Whoami { + constructor (npm) { + this.npm = npm + } -const usage = usageUtil('whoami', 'npm whoami [--registry <registry>]\n(just prints username according to given registry)') + /* istanbul ignore next - see test/lib/load-all-commands.js */ + get usage () { + return usageUtil( + 'whoami', + 'npm whoami [--registry <registry>]\n' + + '(just prints username according to given registry)' + ) + } -const whoami = async ([spec]) => { - const opts = npm.flatOptions - const username = await getIdentity(opts, spec) - output(opts.json ? JSON.stringify(username) : username) -} + exec (args, cb) { + this.whoami(args).then(() => cb()).catch(cb) + } -module.exports = Object.assign(cmd, { usage }) + async whoami (args) { + const opts = this.npm.flatOptions + const username = await getIdentity(this.npm, opts) + output(opts.json ? JSON.stringify(username) : username) + } +} +module.exports = Whoami diff --git a/deps/npm/man/man1/npm-ls.1 b/deps/npm/man/man1/npm-ls.1 index f5d5e81130..7c139ecb65 100644 --- a/deps/npm/man/man1/npm-ls.1 +++ b/deps/npm/man/man1/npm-ls.1 @@ -26,7 +26,7 @@ example, running \fBnpm ls promzard\fP in npm's source tree will show: .P .RS 2 .nf -npm@7\.6\.0 /path/to/npm +npm@7\.6\.1 /path/to/npm └─┬ init\-package\-json@0\.0\.4 └── promzard@0\.1\.5 .fi diff --git a/deps/npm/man/man1/npm-unpublish.1 b/deps/npm/man/man1/npm-unpublish.1 index 44d0bd45d1..4a9a3bc8cb 100644 --- a/deps/npm/man/man1/npm-unpublish.1 +++ b/deps/npm/man/man1/npm-unpublish.1 @@ -39,7 +39,7 @@ versions then the registry will remove the root package entry entirely\. Even if you unpublish a package version, that specific name and version combination can never be reused\. In order to publish the package again, you must use a new version number\. If you unpublish the entire package, -you may not publish any new versions of that package until 28 days have +you may not publish any new versions of that package until 24 hours have passed\. .SS See Also .RS 0 diff --git a/deps/npm/man/man1/npm.1 b/deps/npm/man/man1/npm.1 index 2848e6332e..77353a6d6a 100644 --- a/deps/npm/man/man1/npm.1 +++ b/deps/npm/man/man1/npm.1 @@ -10,7 +10,7 @@ npm <command> [args] .RE .SS Version .P -7\.6\.0 +7\.6\.1 .SS Description .P npm is the package manager for the Node JavaScript platform\. It puts diff --git a/deps/npm/man/man5/package-locks.5 b/deps/npm/man/man5/package-locks.5 new file mode 100644 index 0000000000..0fc35cfb5a --- /dev/null +++ b/deps/npm/man/man5/package-locks.5 @@ -0,0 +1,200 @@ +.TH "PACKAGE\-LOCKS" "5" "December 2020" "" "" +.SH "NAME" +\fBpackage-locks\fR \- An explanation of npm lockfiles +.SS Description +.P +Conceptually, the "input" to npm help \fBinstall\fP is a npm help package\.json, while its +"output" is a fully\-formed \fBnode_modules\fP tree: a representation of the +dependencies you declared\. In an ideal world, npm would work like a pure +function: the same \fBpackage\.json\fP should produce the exact same \fBnode_modules\fP +tree, any time\. In some cases, this is indeed true\. But in many others, npm is +unable to do this\. There are multiple reasons for this: +.RS 0 +.IP \(bu 2 +different versions of npm (or other package managers) may have been used to install a package, each using slightly different installation algorithms\. +.IP \(bu 2 +a new version of a direct semver\-range package may have been published since the last time your packages were installed, and thus a newer version will be used\. +.IP \(bu 2 +A dependency of one of your dependencies may have published a new version, which will update even if you used pinned dependency specifiers (\fB1\.2\.3\fP instead of \fB^1\.2\.3\fP) +.IP \(bu 2 +The registry you installed from is no longer available, or allows mutation of versions (unlike the primary npm registry), and a different version of a package exists under the same version number now\. + +.RE +.P +As an example, consider package A: +.P +.RS 2 +.nf +{ + "name": "A", + "version": "0\.1\.0", + "dependencies": { + "B": "<0\.1\.0" + } +} +.fi +.RE +.P +package B: +.P +.RS 2 +.nf +{ + "name": "B", + "version": "0\.0\.1", + "dependencies": { + "C": "<0\.1\.0" + } +} +.fi +.RE +.P +and package C: +.P +.RS 2 +.nf +{ + "name": "C", + "version": "0\.0\.1" +} +.fi +.RE +.P +If these are the only versions of A, B, and C available in the +registry, then a normal \fBnpm install A\fP will install: +.P +.RS 2 +.nf +A@0\.1\.0 +`\-\- B@0\.0\.1 + `\-\- C@0\.0\.1 +.fi +.RE +.P +However, if B@0\.0\.2 is published, then a fresh \fBnpm install A\fP will +install: +.P +.RS 2 +.nf +A@0\.1\.0 +`\-\- B@0\.0\.2 + `\-\- C@0\.0\.1 +.fi +.RE +.P +assuming the new version did not modify B's dependencies\. Of course, +the new version of B could include a new version of C and any number +of new dependencies\. If such changes are undesirable, the author of A +could specify a dependency on B@0\.0\.1\|\. However, if A's author and B's +author are not the same person, there's no way for A's author to say +that he or she does not want to pull in newly published versions of C +when B hasn't changed at all\. +.P +To prevent this potential issue, npm uses npm help package\-lock\.json or, if present, npm help npm\-shrinkwrap\.json\. These files are called package locks, or lockfiles\. +.P +Whenever you run \fBnpm install\fP, npm generates or updates your package lock, +which will look something like this: +.P +.RS 2 +.nf +{ + "name": "A", + "version": "0\.1\.0", + \.\.\.metadata fields\.\.\. + "dependencies": { + "B": { + "version": "0\.0\.1", + "resolved": "https://registry\.npmjs\.org/B/\-/B\-0\.0\.1\.tgz", + "integrity": "sha512\-DeAdb33F+" + "dependencies": { + "C": { + "version": "git://github\.com/org/C\.git#5c380ae319fc4efe9e7f2d9c78b0faa588fd99b4" + } + } + } + } +} +.fi +.RE +.P +This file describes an \fIexact\fR, and more importantly \fIreproducible\fR +\fBnode_modules\fP tree\. Once it's present, any future installation will base its +work off this file, instead of recalculating dependency versions off +npm help package\.json\. +.P +The presence of a package lock changes the installation behavior such that: +.RS 0 +.IP 1. 3 +The module tree described by the package lock is reproduced\. This means +reproducing the structure described in the file, using the specific files +referenced in "resolved" if available, falling back to normal package resolution +using "version" if one isn't\. +.IP 2. 3 +The tree is walked and any missing dependencies are installed in the usual +fashion\. + +.RE +.P +If \fBpreshrinkwrap\fP, \fBshrinkwrap\fP or \fBpostshrinkwrap\fP are in the \fBscripts\fP +property of the \fBpackage\.json\fP, they will be executed in order\. \fBpreshrinkwrap\fP +and \fBshrinkwrap\fP are executed before the shrinkwrap, \fBpostshrinkwrap\fP is +executed afterwards\. These scripts run for both \fBpackage\-lock\.json\fP and +\fBnpm\-shrinkwrap\.json\fP\|\. For example to run some postprocessing on the generated +file: +.P +.RS 2 +.nf + "scripts": { + "postshrinkwrap": "json \-I \-e \\"this\.myMetadata = $MY_APP_METADATA\\"" + } +.fi +.RE +.SS Using locked packages +.P +Using a locked package is no different than using any package without a package +lock: any commands that update \fBnode_modules\fP and/or \fBpackage\.json\fP\|'s +dependencies will automatically sync the existing lockfile\. This includes \fBnpm +install\fP, \fBnpm rm\fP, \fBnpm update\fP, etc\. To prevent this update from happening, +you can use the \fB\-\-no\-save\fP option to prevent saving altogether, or +\fB\-\-no\-shrinkwrap\fP to allow \fBpackage\.json\fP to be updated while leaving +\fBpackage\-lock\.json\fP or \fBnpm\-shrinkwrap\.json\fP intact\. +.P +It is highly recommended you commit the generated package lock to source +control: this will allow anyone else on your team, your deployments, your +CI/continuous integration, and anyone else who runs \fBnpm install\fP in your +package source to get the exact same dependency tree that you were developing +on\. Additionally, the diffs from these changes are human\-readable and will +inform you of any changes npm has made to your \fBnode_modules\fP, so you can notice +if any transitive dependencies were updated, hoisted, etc\. +.SS Resolving lockfile conflicts +.P +Occasionally, two separate npm install will create package locks that cause +merge conflicts in source control systems\. As of \fBnpm@5\.7\.0\fP, these conflicts +can be resolved by manually fixing any \fBpackage\.json\fP conflicts, and then +running \fBnpm install [\-\-package\-lock\-only]\fP again\. npm will automatically +resolve any conflicts for you and write a merged package lock that includes all +the dependencies from both branches in a reasonable tree\. If +\fB\-\-package\-lock\-only\fP is provided, it will do this without also modifying your +local \fBnode_modules/\fP\|\. +.P +To make this process seamless on git, consider installing +\fBnpm\-merge\-driver\fP \fIhttps://npm\.im/npm\-merge\-driver\fR, which will teach git how +to do this itself without any user interaction\. In short: \fB$ npx +npm\-merge\-driver install \-g\fP will let you do this, and even works with +pre\-\fBnpm@5\.7\.0\fP versions of npm 5, albeit a bit more noisily\. Note that if +\fBpackage\.json\fP itself conflicts, you will have to resolve that by hand and run +\fBnpm install\fP manually, even with the merge driver\. +.SS See Also +.RS 0 +.IP \(bu 2 +https://medium\.com/@sdboyer/so\-you\-want\-to\-write\-a\-package\-manager\-4ae9c17d9527 +.IP \(bu 2 +npm help package\.json +.IP \(bu 2 +npm help package\-lock\.json +.IP \(bu 2 +npm help shrinkwrap\.json +.IP \(bu 2 +npm help shrinkwrap + +.RE diff --git a/deps/npm/man/man5/shrinkwrap-json.5 b/deps/npm/man/man5/shrinkwrap-json.5 new file mode 100644 index 0000000000..7457df20e4 --- /dev/null +++ b/deps/npm/man/man5/shrinkwrap-json.5 @@ -0,0 +1,32 @@ +.TH "SHRINKWRAP\.JSON" "5" "December 2020" "" "" +.SH "NAME" +\fBshrinkwrap.json\fR \- A publishable lockfile +.SS Description +.P +\fBnpm\-shrinkwrap\.json\fP is a file created by npm help \fBshrinkwrap\fP\|\. It is identical to +\fBpackage\-lock\.json\fP, with one major caveat: Unlike \fBpackage\-lock\.json\fP, +\fBnpm\-shrinkwrap\.json\fP may be included when publishing a package\. +.P +The recommended use\-case for \fBnpm\-shrinkwrap\.json\fP is applications deployed +through the publishing process on the registry: for example, daemons and +command\-line tools intended as global installs or \fBdevDependencies\fP\|\. It's +strongly discouraged for library authors to publish this file, since that would +prevent end users from having control over transitive dependency updates\. +.P +Additionally, if both \fBpackage\-lock\.json\fP and \fBnpm\-shrinkwrap\.json\fP are present +in a package root, \fBpackage\-lock\.json\fP will be ignored in favor of this file\. +.P +For full details and description of the \fBnpm\-shrinkwrap\.json\fP file format, refer +to the manual page for npm help package\-lock\.json\. +.SS See also +.RS 0 +.IP \(bu 2 +npm help shrinkwrap +.IP \(bu 2 +npm help package\-lock\.json +.IP \(bu 2 +npm help package\.json +.IP \(bu 2 +npm help install + +.RE diff --git a/deps/npm/man/man7/disputes.7 b/deps/npm/man/man7/disputes.7 new file mode 100644 index 0000000000..8e95505991 --- /dev/null +++ b/deps/npm/man/man7/disputes.7 @@ -0,0 +1,149 @@ +.TH "DISPUTES" "7" "December 2020" "" "" +.SH "NAME" +\fBdisputes\fR \- Handling Module Name Disputes +.P +This document describes the steps that you should take to resolve module name +disputes with other npm publishers\. It also describes special steps you should +take about names you think infringe your trademarks\. +.P +This document is a clarification of the acceptable behavior outlined in the +npm Code of Conduct \fIhttps://www\.npmjs\.com/policies/conduct\fR, and nothing in +this document should be interpreted to contradict any aspect of the npm Code of +Conduct\. +.SS TL;DR +.RS 0 +.IP 1. 3 +Get the author email with \fBnpm owner ls <pkgname>\fP +.IP 2. 3 +Email the author, CC support@npmjs\.com +.IP 3. 3 +After a few weeks, if there's no resolution, we'll sort it out\. + +.RE +.P +Don't squat on package names\. Publish code or move out of the way\. +.SS Description +.P +There sometimes arise cases where a user publishes a module, and then later, +some other user wants to use that name\. Here are some common ways that happens +(each of these is based on actual events\.) +.RS 0 +.IP 1. 3 +Alice writes a JavaScript module \fBfoo\fP, which is not node\-specific\. Alice +doesn't use node at all\. Yusuf wants to use \fBfoo\fP in node, so he wraps it in +an npm module\. Some time later, Alice starts using node, and wants to take +over management of her program\. +.IP 2. 3 +Yusuf writes an npm module \fBfoo\fP, and publishes it\. Perhaps much later, Alice +finds a bug in \fBfoo\fP, and fixes it\. She sends a pull request to Yusuf, but +Yusuf doesn't have the time to deal with it, because he has a new job and a +new baby and is focused on his new Erlang project, and kind of not involved +with node any more\. Alice would like to publish a new \fBfoo\fP, but can't, +because the name is taken\. +.IP 3. 3 +Yusuf writes a 10\-line flow\-control library, and calls it \fBfoo\fP, and +publishes it to the npm registry\. Being a simple little thing, it never +really has to be updated\. Alice works for Foo Inc, the makers of the +critically acclaimed and widely\-marketed \fBfoo\fP JavaScript toolkit framework\. +They publish it to npm as \fBfoojs\fP, but people are routinely confused when +\fBnpm install foo\fP is some different thing\. +.IP 4. 3 +Yusuf writes a parser for the widely\-known \fBfoo\fP file format, because he +needs it for work\. Then, he gets a new job, and never updates the prototype\. +Later on, Alice writes a much more complete \fBfoo\fP parser, but can't publish, +because Yusuf's \fBfoo\fP is in the way\. +.IP 5. 3 +\fBnpm owner ls foo\fP\|\. This will tell Alice the email address of the owner +(Yusuf)\. +.IP 6. 3 +Alice emails Yusuf, explaining the situation \fBas respectfully as possible\fR, +and what she would like to do with the module name\. She adds the npm support +staff support@npmjs\.com to the CC list of the email\. Mention in the email +that Yusuf can run npm owner \fBadd alice foo\fP to add Alice as an owner of the +foo package\. +.IP 7. 3 +After a reasonable amount of time, if Yusuf has not responded, or if Yusuf +and Alice can't come to any sort of resolution, email support +support@npmjs\.com and we'll sort it out\. ("Reasonable" is usually at least +4 weeks\.) + +.RE +.SS Reasoning +.P +In almost every case so far, the parties involved have been able to reach an +amicable resolution without any major intervention\. Most people really do want +to be reasonable, and are probably not even aware that they're in your way\. +.P +Module ecosystems are most vibrant and powerful when they are as self\-directed +as possible\. If an admin one day deletes something you had worked on, then that +is going to make most people quite upset, regardless of the justification\. When +humans solve their problems by talking to other humans with respect, everyone +has the chance to end up feeling good about the interaction\. +.SS Exceptions +.P +Some things are not allowed, and will be removed without discussion if they are +brought to the attention of the npm registry admins, including but not limited +to: +.RS 0 +.IP 1. 3 +Malware (that is, a package designed to exploit or harm the machine on which +it is installed)\. +.IP 2. 3 +Violations of copyright or licenses (for example, cloning an MIT\-licensed +program, and then removing or changing the copyright and license statement)\. +.IP 3. 3 +Illegal content\. +.IP 4. 3 +"Squatting" on a package name that you plan to use, but aren't actually +using\. Sorry, I don't care how great the name is, or how perfect a fit it is +for the thing that someday might happen\. If someone wants to use it today, +and you're just taking up space with an empty tarball, you're going to be +evicted\. +.IP 5. 3 +Putting empty packages in the registry\. Packages must have SOME +functionality\. It can be silly, but it can't be nothing\. (See also: +squatting\.) +.IP 6. 3 +Doing weird things with the registry, like using it as your own personal +application database or otherwise putting non\-packagey things into it\. +.IP 7. 3 +Other things forbidden by the npm +Code of Conduct \fIhttps://www\.npmjs\.com/policies/conduct\fR such as hateful +language, pornographic content, or harassment\. + +.RE +.P +If you see bad behavior like this, please report it to abuse@npmjs\.com right +away\. \fBYou are never expected to resolve abusive behavior on your own\. We are +here to help\.\fR +.SS Trademarks +.P +If you think another npm publisher is infringing your trademark, such as by +using a confusingly similar package name, email abuse@npmjs\.com with a link to +the package or user account on https://www\.npmjs\.com/ \fIhttps://www\.npmjs\.com/\fR\|\. +Attach a copy of your trademark registration certificate\. +.P +If we see that the package's publisher is intentionally misleading others by +misusing your registered mark without permission, we will transfer the package +name to you\. Otherwise, we will contact the package publisher and ask them to +clear up any confusion with changes to their package's \fBREADME\fP file or +metadata\. +.SS Changes +.P +This is a living document and may be updated from time to time\. Please refer to +the git history for this document \fIhttps://github\.com/npm/cli/commits/latest/doc/misc/npm\-disputes\.md\fR +to view the changes\. +.SS License +.P +Copyright (C) npm, Inc\., All rights reserved +.P +This document may be reused under a Creative Commons Attribution\-ShareAlike +License\. +.SS See also +.RS 0 +.IP \(bu 2 +npm help registry +.IP \(bu 2 +npm help owner + +.RE diff --git a/deps/npm/man/man7/scripts.7 b/deps/npm/man/man7/scripts.7 index c160a0ea5b..78d9ad4b9a 100644 --- a/deps/npm/man/man7/scripts.7 +++ b/deps/npm/man/man7/scripts.7 @@ -3,10 +3,18 @@ \fBscripts\fR \- How npm handles the "scripts" field .SS Description .P -The \fB"scripts"\fP property of your \fBpackage\.json\fP file supports a number of built\-in scripts and their preset life cycle events as well as arbitrary scripts\. These all can be executed by running \fBnpm run\-script <stage>\fP or \fBnpm run <stage>\fP for short\. \fIPre\fR and \fIpost\fR commands with matching names will be run for those as well (e\.g\. \fBpremyscript\fP, \fBmyscript\fP, \fBpostmyscript\fP)\. Scripts from dependencies can be run with \fBnpm explore <pkg> \-\- npm run <stage>\fP\|\. +The \fB"scripts"\fP property of your \fBpackage\.json\fP file supports a number +of built\-in scripts and their preset life cycle events as well as +arbitrary scripts\. These all can be executed by running +\fBnpm run\-script <stage>\fP or \fBnpm run <stage>\fP for short\. \fIPre\fR and \fIpost\fR +commands with matching names will be run for those as well (e\.g\. \fBpremyscript\fP, +\fBmyscript\fP, \fBpostmyscript\fP)\. Scripts from dependencies can be run with +\fBnpm explore <pkg> \-\- npm run <stage>\fP\|\. .SS Pre & Post Scripts .P -To create "pre" or "post" scripts for any scripts defined in the \fB"scripts"\fP section of the \fBpackage\.json\fP, simply create another script \fIwith a matching name\fR and add "pre" or "post" to the beginning of them\. +To create "pre" or "post" scripts for any scripts defined in the +\fB"scripts"\fP section of the \fBpackage\.json\fP, simply create another script +\fIwith a matching name\fR and add "pre" or "post" to the beginning of them\. .P .RS 2 .nf @@ -19,9 +27,14 @@ To create "pre" or "post" scripts for any scripts defined in the \fB"scripts"\fP } .fi .RE +.P +In this example \fBnpm run compress\fP would execute these scripts as +described\. .SS Life Cycle Scripts .P -There are some special life cycle scripts that happen only in certain situations\. These scripts happen in addition to the "pre" and "post" script\. +There are some special life cycle scripts that happen only in certain +situations\. These scripts happen in addition to the \fBpre<event>\fP, \fBpost<event>\fP, and +\fB<event>\fP scripts\. .RS 0 .IP \(bu 2 \fBprepare\fP, \fBprepublish\fP, \fBprepublishOnly\fP, \fBprepack\fP, \fBpostpack\fP @@ -31,6 +44,9 @@ There are some special life cycle scripts that happen only in certain situations \fBprepare\fR (since \fBnpm@4\.0\.0\fP) .RS 0 .IP \(bu 2 +Runs any time before the package is packed, i\.e\. during \fBnpm publish\fP + and \fBnpm pack\fP +.IP \(bu 2 Runs BEFORE the package is packed .IP \(bu 2 Runs BEFORE the package is published @@ -39,14 +55,20 @@ Runs on local \fBnpm install\fP without any arguments .IP \(bu 2 Run AFTER \fBprepublish\fP, but BEFORE \fBprepublishOnly\fP .IP \(bu 2 -NOTE: If a package being installed through git contains a \fBprepare\fP script, its \fBdependencies\fP and \fBdevDependencies\fP will be installed, and the prepare script will be run, before the package is packaged and installed\. +NOTE: If a package being installed through git contains a \fBprepare\fP +script, its \fBdependencies\fP and \fBdevDependencies\fP will be installed, and +the prepare script will be run, before the package is packaged and +installed\. +.IP \(bu 2 +As of \fBnpm@7\fP these scripts run in the background .RE .P \fBprepublish\fR (DEPRECATED) .RS 0 .IP \(bu 2 -Same as \fBprepare\fP +Does not run during \fBnpm publish\fP, but does run during \fBnpm ci\fP +and \fBnpm install\fP\|\. See below for more info\. .RE .P @@ -69,7 +91,7 @@ NOTE: "\fBnpm run pack\fP" is NOT the same as "\fBnpm pack\fP"\. "\fBnpm run pac \fBpostpack\fR .RS 0 .IP \(bu 2 -Runs AFTER the tarball has been generated and moved to its final destination\. +Runs AFTER the tarball has been generated but before it is moved to its final destination (if at all, publish does not save the tarball locally) .RE .SS Prepare and Prepublish @@ -107,29 +129,101 @@ other system tools on the target machines\. .RE .SS Life Cycle Operation Order -.SS npm help \fBpublish\fP +.SS npm help \fBcache add\fP .RS 0 .IP \(bu 2 -\fBprepublishOnly\fP +\fBprepare\fP + +.RE +.SS npm help \fBci\fP +.RS 0 +.IP \(bu 2 +\fBpreinstall\fP +.IP \(bu 2 +\fBinstall\fP +.IP \(bu 2 +\fBpostinstall\fP +.IP \(bu 2 +\fBprepublish\fP +.IP \(bu 2 +\fBpreprepare\fP +.IP \(bu 2 +\fBprepare\fP +.IP \(bu 2 +\fBpostprepare\fP +These all run after the actual installation of modules into +\fBnode_modules\fP, in order, with no internal actions happening in between + +.RE +.SS npm help \fBdiff\fP +.RS 0 .IP \(bu 2 \fBprepare\fP + +.RE +.SS npm help \fBenv\fP +.RS 0 +.IP \(bu 2 +\fBenv\fP (You can override the default behavior of \fBnpm env\fP by defining + a custom \fBenv\fP entry in your \fBscripts\fP object) + +.RE +.SS npm help \fBinstall\fP +.P +These also run when you run \fBnpm install \-g <pkg\-name>\fP +.RS 0 +.IP \(bu 2 +\fBpreinstall\fP +.IP \(bu 2 +\fBinstall\fP +.IP \(bu 2 +\fBpostinstall\fP .IP \(bu 2 \fBprepublish\fP .IP \(bu 2 -\fBpublish\fP +\fBpreprepare\fP .IP \(bu 2 -\fBpostpublish\fP +\fBprepare\fP +.IP \(bu 2 +\fBpostprepare\fP .RE +.P +If there is a \fBbinding\.gyp\fP file in the root of your package and you +haven't defined your own \fBinstall\fP or \fBpreinstall\fP scripts, npm will +default the \fBinstall\fP command to compile using node\-gyp via \fBnode\-gyp +rebuild\fP +.P +These are run from the scripts of \fB<pkg\-name>\fP .SS npm help \fBpack\fP .RS 0 .IP \(bu 2 \fBprepack\fP .IP \(bu 2 +\fBprepare\fP +.IP \(bu 2 +\fBpostpack\fP + +.RE +.SS npm help \fBpublish\fP +.RS 0 +.IP \(bu 2 +\fBprepublishOnly\fP +.IP \(bu 2 +\fBprepack\fP +.IP \(bu 2 +\fBprepare\fP +.IP \(bu 2 \fBpostpack\fP +.IP \(bu 2 +\fBpublish\fP +.IP \(bu 2 +\fBpostpublish\fP .RE -.SS npm help \fBinstall\fP +.P +\fBprepare\fP will not run during \fB\-\-dry\-run\fP +.SS npm help \fBrebuild\fP .RS 0 .IP \(bu 2 \fBpreinstall\fP @@ -137,20 +231,38 @@ other system tools on the target machines\. \fBinstall\fP .IP \(bu 2 \fBpostinstall\fP +.IP \(bu 2 +\fBprepare\fP .RE .P -Also triggers +\fBprepare\fP is only run if the current directory is a symlink (e\.g\. with +linked packages) +.SS npm help \fBrestart\fP +.P +If there is a \fBrestart\fP script defined, these events are run, otherwise +\fBstop\fP and \fBstart\fP are both run if present, including their \fBpre\fP and +\fBpost\fP iterations) +.RS 0 +.IP \(bu 2 +\fBprerestart\fP +.IP \(bu 2 +\fBrestart\fP +.IP \(bu 2 +\fBpostrestart\fP + +.RE +.SS npm help \fBrun <user defined>\fP .RS 0 .IP \(bu 2 -\fBprepublish\fP (when on local) +\fBpre<user\-defined>\fP +.IP \(bu 2 +\fB<user\-defined>\fP .IP \(bu 2 -\fBprepare\fP (when on local or workspaces) +\fBpost<user\-defined>\fP .RE .SS npm help \fBstart\fP -.P -\fBnpm run start\fP has an \fBnpm start\fP shorthand\. .RS 0 .IP \(bu 2 \fBprestart\fP @@ -160,19 +272,28 @@ Also triggers \fBpoststart\fP .RE -.SS Default Values .P -npm will default some script values based on package contents\. +If there is a \fBserver\.js\fP file in the root of your package, then npm +will default the \fBstart\fP command to \fBnode server\.js\fP\|\. \fBprestart\fP and +\fBpoststart\fP will still run in this case\. +.SS npm help \fBstop\fP .RS 0 .IP \(bu 2 -\fB"start": "node server\.js"\fP: -If there is a \fBserver\.js\fP file in the root of your package, then npm -will default the \fBstart\fP command to \fBnode server\.js\fP\|\. +\fBprestop\fP .IP \(bu 2 -\fB"install": "node\-gyp rebuild"\fP: -If there is a \fBbinding\.gyp\fP file in the root of your package and you -haven't defined your own \fBinstall\fP or \fBpreinstall\fP scripts, npm will -default the \fBinstall\fP command to compile using node\-gyp\. +\fBstop\fP +.IP \(bu 2 +\fBpoststop\fP + +.RE +.SS npm help \fBtest\fP +.RS 0 +.IP \(bu 2 +\fBpretest\fP +.IP \(bu 2 +\fBtest\fP +.IP \(bu 2 +\fBposttest\fP .RE .SS User @@ -192,14 +313,14 @@ executing the scripts\. So, if your package\.json has this: .P .RS 2 .nf -{ - "name" : "foo", - "dependencies" : { - "bar" : "0\.1\.x" - }, - "scripts": { - "start" : "bar \./test" - } +{ + "name" : "foo", + "dependencies" : { + "bar" : "0\.1\.x" + }, + "scripts": { + "start" : "bar \./test" + } } .fi .RE @@ -212,8 +333,8 @@ The package\.json fields are tacked onto the \fBnpm_package_\fP prefix\. So, for instance, if you had \fB{"name":"foo", "version":"1\.2\.5"}\fP in your package\.json file, then your package scripts would have the \fBnpm_package_name\fP environment variable set to "foo", and the -\fBnpm_package_version\fP set to "1\.2\.5"\. You can access these variables -in your code with \fBprocess\.env\.npm_package_name\fP and +\fBnpm_package_version\fP set to "1\.2\.5"\. You can access these variables +in your code with \fBprocess\.env\.npm_package_name\fP and \fBprocess\.env\.npm_package_version\fP, and so on for other fields\. .SS configuration .P @@ -228,14 +349,14 @@ if the package\.json has this: .P .RS 2 .nf -{ - "name" : "foo", - "config" : { - "port" : "8080" - }, - "scripts" : { - "start" : "node server\.js" - } +{ + "name" : "foo", + "config" : { + "port" : "8080" + }, + "scripts" : { + "start" : "node server\.js" + } } .fi .RE @@ -277,10 +398,10 @@ For example, if your package\.json contains this: .P .RS 2 .nf -{ - "scripts" : { - "install" : "scripts/install\.js", - "postinstall" : "scripts/postinstall\.js", +{ + "scripts" : { + "install" : "scripts/install\.js", + "postinstall" : "scripts/postinstall\.js", "uninstall" : "scripts/uninstall\.js" } } @@ -299,10 +420,10 @@ fine: .P .RS 2 .nf -{ - "scripts" : { - "preinstall" : "\./configure", - "install" : "make && make install", +{ + "scripts" : { + "preinstall" : "\./configure", + "install" : "make && make install", "test" : "make test" } } diff --git a/deps/npm/man/man7/semver.7 b/deps/npm/man/man7/semver.7 new file mode 100644 index 0000000000..0a08bf9abb --- /dev/null +++ b/deps/npm/man/man7/semver.7 @@ -0,0 +1,510 @@ +.TH "SEMVER" "7" "December 2020" "" "" +.SH "NAME" +\fBsemver\fR \- The semantic versioner for npm +.SH Install +.P +.RS 2 +.nf +npm install \-\-save semver +.fi +.RE +.SH Usage +.P +As a node module: +.P +.RS 2 +.nf +const semver = require('semver') + +semver\.valid('1\.2\.3') // '1\.2\.3' +semver\.valid('a\.b\.c') // null +semver\.clean(' =v1\.2\.3 ') // '1\.2\.3' +semver\.satisfies('1\.2\.3', '1\.x || >=2\.5\.0 || 5\.0\.0 \- 7\.2\.3') // true +semver\.gt('1\.2\.3', '9\.8\.7') // false +semver\.lt('1\.2\.3', '9\.8\.7') // true +semver\.minVersion('>=1\.0\.0') // '1\.0\.0' +semver\.valid(semver\.coerce('v2')) // '2\.0\.0' +semver\.valid(semver\.coerce('42\.6\.7\.9\.3\-alpha')) // '42\.6\.7' +.fi +.RE +.P +As a command\-line utility: +.P +.RS 2 +.nf +$ semver \-h + +A JavaScript implementation of the https://semver\.org/ specification +Copyright Isaac Z\. Schlueter + +Usage: semver [options] <version> [<version> [\.\.\.]] +Prints valid versions sorted by SemVer precedence + +Options: +\-r \-\-range <range> + Print versions that match the specified range\. + +\-i \-\-increment [<level>] + Increment a version by the specified level\. Level can + be one of: major, minor, patch, premajor, preminor, + prepatch, or prerelease\. Default level is 'patch'\. + Only one version may be specified\. + +\-\-preid <identifier> + Identifier to be used to prefix premajor, preminor, + prepatch or prerelease version increments\. + +\-l \-\-loose + Interpret versions and ranges loosely + +\-p \-\-include\-prerelease + Always include prerelease versions in range matching + +\-c \-\-coerce + Coerce a string into SemVer if possible + (does not imply \-\-loose) + +Program exits successfully if any valid version satisfies +all supplied ranges, and prints all satisfying versions\. + +If no satisfying versions are found, then exits failure\. + +Versions are printed in ascending order, so supplying +multiple versions to the utility will just sort them\. +.fi +.RE +.SH Versions +.P +A "version" is described by the \fBv2\.0\.0\fP specification found at +https://semver\.org/\|\. +.P +A leading \fB"="\fP or \fB"v"\fP character is stripped off and ignored\. +.SH Ranges +.P +A \fBversion range\fP is a set of \fBcomparators\fP which specify versions +that satisfy the range\. +.P +A \fBcomparator\fP is composed of an \fBoperator\fP and a \fBversion\fP\|\. The set +of primitive \fBoperators\fP is: +.RS 0 +.IP \(bu 2 +\fB<\fP Less than +.IP \(bu 2 +\fB<=\fP Less than or equal to +.IP \(bu 2 +\fB>\fP Greater than +.IP \(bu 2 +\fB>=\fP Greater than or equal to +.IP \(bu 2 +\fB=\fP Equal\. If no operator is specified, then equality is assumed, +so this operator is optional, but MAY be included\. + +.RE +.P +For example, the comparator \fB>=1\.2\.7\fP would match the versions +\fB1\.2\.7\fP, \fB1\.2\.8\fP, \fB2\.5\.3\fP, and \fB1\.3\.9\fP, but not the versions \fB1\.2\.6\fP +or \fB1\.1\.0\fP\|\. +.P +Comparators can be joined by whitespace to form a \fBcomparator set\fP, +which is satisfied by the \fBintersection\fR of all of the comparators +it includes\. +.P +A range is composed of one or more comparator sets, joined by \fB||\fP\|\. A +version matches a range if and only if every comparator in at least +one of the \fB||\fP\-separated comparator sets is satisfied by the version\. +.P +For example, the range \fB>=1\.2\.7 <1\.3\.0\fP would match the versions +\fB1\.2\.7\fP, \fB1\.2\.8\fP, and \fB1\.2\.99\fP, but not the versions \fB1\.2\.6\fP, \fB1\.3\.0\fP, +or \fB1\.1\.0\fP\|\. +.P +The range \fB1\.2\.7 || >=1\.2\.9 <2\.0\.0\fP would match the versions \fB1\.2\.7\fP, +\fB1\.2\.9\fP, and \fB1\.4\.6\fP, but not the versions \fB1\.2\.8\fP or \fB2\.0\.0\fP\|\. +.SS Prerelease Tags +.P +If a version has a prerelease tag (for example, \fB1\.2\.3\-alpha\.3\fP) then +it will only be allowed to satisfy comparator sets if at least one +comparator with the same \fB[major, minor, patch]\fP tuple also has a +prerelease tag\. +.P +For example, the range \fB>1\.2\.3\-alpha\.3\fP would be allowed to match the +version \fB1\.2\.3\-alpha\.7\fP, but it would \fInot\fR be satisfied by +\fB3\.4\.5\-alpha\.9\fP, even though \fB3\.4\.5\-alpha\.9\fP is technically "greater +than" \fB1\.2\.3\-alpha\.3\fP according to the SemVer sort rules\. The version +range only accepts prerelease tags on the \fB1\.2\.3\fP version\. The +version \fB3\.4\.5\fP \fIwould\fR satisfy the range, because it does not have a +prerelease flag, and \fB3\.4\.5\fP is greater than \fB1\.2\.3\-alpha\.7\fP\|\. +.P +The purpose for this behavior is twofold\. First, prerelease versions +frequently are updated very quickly, and contain many breaking changes +that are (by the author's design) not yet fit for public consumption\. +Therefore, by default, they are excluded from range matching +semantics\. +.P +Second, a user who has opted into using a prerelease version has +clearly indicated the intent to use \fIthat specific\fR set of +alpha/beta/rc versions\. By including a prerelease tag in the range, +the user is indicating that they are aware of the risk\. However, it +is still not appropriate to assume that they have opted into taking a +similar risk on the \fInext\fR set of prerelease versions\. +.P +Note that this behavior can be suppressed (treating all prerelease +versions as if they were normal versions, for the purpose of range +matching) by setting the \fBincludePrerelease\fP flag on the options +object to any +functions \fIhttps://github\.com/npm/node\-semver#functions\fR that do +range matching\. +.SS Prerelease Identifiers +.P +The method \fB\|\.inc\fP takes an additional \fBidentifier\fP string argument that +will append the value of the string as a prerelease identifier: +.P +.RS 2 +.nf +semver\.inc('1\.2\.3', 'prerelease', 'beta') +// '1\.2\.4\-beta\.0' +.fi +.RE +.P +command\-line example: +.P +.RS 2 +.nf +$ semver 1\.2\.3 \-i prerelease \-\-preid beta +1\.2\.4\-beta\.0 +.fi +.RE +.P +Which then can be used to increment further: +.P +.RS 2 +.nf +$ semver 1\.2\.4\-beta\.0 \-i prerelease +1\.2\.4\-beta\.1 +.fi +.RE +.SS Advanced Range Syntax +.P +Advanced range syntax desugars to primitive comparators in +deterministic ways\. +.P +Advanced ranges may be combined in the same way as primitive +comparators using white space or \fB||\fP\|\. +.SS Hyphen Ranges \fBX\.Y\.Z \- A\.B\.C\fP +.P +Specifies an inclusive set\. +.RS 0 +.IP \(bu 2 +\fB1\.2\.3 \- 2\.3\.4\fP := \fB>=1\.2\.3 <=2\.3\.4\fP + +.RE +.P +If a partial version is provided as the first version in the inclusive +range, then the missing pieces are replaced with zeroes\. +.RS 0 +.IP \(bu 2 +\fB1\.2 \- 2\.3\.4\fP := \fB>=1\.2\.0 <=2\.3\.4\fP + +.RE +.P +If a partial version is provided as the second version in the +inclusive range, then all versions that start with the supplied parts +of the tuple are accepted, but nothing that would be greater than the +provided tuple parts\. +.RS 0 +.IP \(bu 2 +\fB1\.2\.3 \- 2\.3\fP := \fB>=1\.2\.3 <2\.4\.0\fP +.IP \(bu 2 +\fB1\.2\.3 \- 2\fP := \fB>=1\.2\.3 <3\.0\.0\fP + +.RE +.SS X\-Ranges \fB1\.2\.x\fP \fB1\.X\fP \fB1\.2\.*\fP \fB*\fP +.P +Any of \fBX\fP, \fBx\fP, or \fB*\fP may be used to "stand in" for one of the +numeric values in the \fB[major, minor, patch]\fP tuple\. +.RS 0 +.IP \(bu 2 +\fB*\fP := \fB>=0\.0\.0\fP (Any version satisfies) +.IP \(bu 2 +\fB1\.x\fP := \fB>=1\.0\.0 <2\.0\.0\fP (Matching major version) +.IP \(bu 2 +\fB1\.2\.x\fP := \fB>=1\.2\.0 <1\.3\.0\fP (Matching major and minor versions) + +.RE +.P +A partial version range is treated as an X\-Range, so the special +character is in fact optional\. +.RS 0 +.IP \(bu 2 +\fB""\fP (empty string) := \fB*\fP := \fB>=0\.0\.0\fP +.IP \(bu 2 +\fB1\fP := \fB1\.x\.x\fP := \fB>=1\.0\.0 <2\.0\.0\fP +.IP \(bu 2 +\fB1\.2\fP := \fB1\.2\.x\fP := \fB>=1\.2\.0 <1\.3\.0\fP + +.RE +.SS Tilde Ranges \fB~1\.2\.3\fP \fB~1\.2\fP \fB~1\fP +.P +Allows patch\-level changes if a minor version is specified on the +comparator\. Allows minor\-level changes if not\. +.RS 0 +.IP \(bu 2 +\fB~1\.2\.3\fP := \fB>=1\.2\.3 <1\.(2+1)\.0\fP := \fB>=1\.2\.3 <1\.3\.0\fP +.IP \(bu 2 +\fB~1\.2\fP := \fB>=1\.2\.0 <1\.(2+1)\.0\fP := \fB>=1\.2\.0 <1\.3\.0\fP (Same as \fB1\.2\.x\fP) +.IP \(bu 2 +\fB~1\fP := \fB>=1\.0\.0 <(1+1)\.0\.0\fP := \fB>=1\.0\.0 <2\.0\.0\fP (Same as \fB1\.x\fP) +.IP \(bu 2 +\fB~0\.2\.3\fP := \fB>=0\.2\.3 <0\.(2+1)\.0\fP := \fB>=0\.2\.3 <0\.3\.0\fP +.IP \(bu 2 +\fB~0\.2\fP := \fB>=0\.2\.0 <0\.(2+1)\.0\fP := \fB>=0\.2\.0 <0\.3\.0\fP (Same as \fB0\.2\.x\fP) +.IP \(bu 2 +\fB~0\fP := \fB>=0\.0\.0 <(0+1)\.0\.0\fP := \fB>=0\.0\.0 <1\.0\.0\fP (Same as \fB0\.x\fP) +.IP \(bu 2 +\fB~1\.2\.3\-beta\.2\fP := \fB>=1\.2\.3\-beta\.2 <1\.3\.0\fP Note that prereleases in +the \fB1\.2\.3\fP version will be allowed, if they are greater than or +equal to \fBbeta\.2\fP\|\. So, \fB1\.2\.3\-beta\.4\fP would be allowed, but +\fB1\.2\.4\-beta\.2\fP would not, because it is a prerelease of a +different \fB[major, minor, patch]\fP tuple\. + +.RE +.SS Caret Ranges \fB^1\.2\.3\fP \fB^0\.2\.5\fP \fB^0\.0\.4\fP +.P +Allows changes that do not modify the left\-most non\-zero digit in the +\fB[major, minor, patch]\fP tuple\. In other words, this allows patch and +minor updates for versions \fB1\.0\.0\fP and above, patch updates for +versions \fB0\.X >=0\.1\.0\fP, and \fIno\fR updates for versions \fB0\.0\.X\fP\|\. +.P +Many authors treat a \fB0\.x\fP version as if the \fBx\fP were the major +"breaking\-change" indicator\. +.P +Caret ranges are ideal when an author may make breaking changes +between \fB0\.2\.4\fP and \fB0\.3\.0\fP releases, which is a common practice\. +However, it presumes that there will \fInot\fR be breaking changes between +\fB0\.2\.4\fP and \fB0\.2\.5\fP\|\. It allows for changes that are presumed to be +additive (but non\-breaking), according to commonly observed practices\. +.RS 0 +.IP \(bu 2 +\fB^1\.2\.3\fP := \fB>=1\.2\.3 <2\.0\.0\fP +.IP \(bu 2 +\fB^0\.2\.3\fP := \fB>=0\.2\.3 <0\.3\.0\fP +.IP \(bu 2 +\fB^0\.0\.3\fP := \fB>=0\.0\.3 <0\.0\.4\fP +.IP \(bu 2 +\fB^1\.2\.3\-beta\.2\fP := \fB>=1\.2\.3\-beta\.2 <2\.0\.0\fP Note that prereleases in +the \fB1\.2\.3\fP version will be allowed, if they are greater than or +equal to \fBbeta\.2\fP\|\. So, \fB1\.2\.3\-beta\.4\fP would be allowed, but +\fB1\.2\.4\-beta\.2\fP would not, because it is a prerelease of a +different \fB[major, minor, patch]\fP tuple\. +.IP \(bu 2 +\fB^0\.0\.3\-beta\fP := \fB>=0\.0\.3\-beta <0\.0\.4\fP Note that prereleases in the +\fB0\.0\.3\fP version \fIonly\fR will be allowed, if they are greater than or +equal to \fBbeta\fP\|\. So, \fB0\.0\.3\-pr\.2\fP would be allowed\. + +.RE +.P +When parsing caret ranges, a missing \fBpatch\fP value desugars to the +number \fB0\fP, but will allow flexibility within that value, even if the +major and minor versions are both \fB0\fP\|\. +.RS 0 +.IP \(bu 2 +\fB^1\.2\.x\fP := \fB>=1\.2\.0 <2\.0\.0\fP +.IP \(bu 2 +\fB^0\.0\.x\fP := \fB>=0\.0\.0 <0\.1\.0\fP +.IP \(bu 2 +\fB^0\.0\fP := \fB>=0\.0\.0 <0\.1\.0\fP + +.RE +.P +A missing \fBminor\fP and \fBpatch\fP values will desugar to zero, but also +allow flexibility within those values, even if the major version is +zero\. +.RS 0 +.IP \(bu 2 +\fB^1\.x\fP := \fB>=1\.0\.0 <2\.0\.0\fP +.IP \(bu 2 +\fB^0\.x\fP := \fB>=0\.0\.0 <1\.0\.0\fP + +.RE +.SS Range Grammar +.P +Putting all this together, here is a Backus\-Naur grammar for ranges, +for the benefit of parser authors: +.P +.RS 2 +.nf +range\-set ::= range ( logical\-or range ) * +logical\-or ::= ( ' ' ) * '||' ( ' ' ) * +range ::= hyphen | simple ( ' ' simple ) * | '' +hyphen ::= partial ' \- ' partial +simple ::= primitive | partial | tilde | caret +primitive ::= ( '<' | '>' | '>=' | '<=' | '=' ) partial +partial ::= xr ( '\.' xr ( '\.' xr qualifier ? )? )? +xr ::= 'x' | 'X' | '*' | nr +nr ::= '0' | ['1'\-'9'] ( ['0'\-'9'] ) * +tilde ::= '~' partial +caret ::= '^' partial +qualifier ::= ( '\-' pre )? ( '+' build )? +pre ::= parts +build ::= parts +parts ::= part ( '\.' part ) * +part ::= nr | [\-0\-9A\-Za\-z]+ +.fi +.RE +.SH Functions +.P +All methods and classes take a final \fBoptions\fP object argument\. All +options in this object are \fBfalse\fP by default\. The options supported +are: +.RS 0 +.IP \(bu 2 +\fBloose\fP Be more forgiving about not\-quite\-valid semver strings\. +(Any resulting output will always be 100% strict compliant, of +course\.) For backwards compatibility reasons, if the \fBoptions\fP +argument is a boolean value instead of an object, it is interpreted +to be the \fBloose\fP param\. +.IP \(bu 2 +\fBincludePrerelease\fP Set to suppress the default +behavior \fIhttps://github\.com/npm/node\-semver#prerelease\-tags\fR of +excluding prerelease tagged versions from ranges unless they are +explicitly opted into\. + +.RE +.P +Strict\-mode Comparators and Ranges will be strict about the SemVer +strings that they parse\. +.RS 0 +.IP \(bu 2 +\fBvalid(v)\fP: Return the parsed version, or null if it's not valid\. +.IP \(bu 2 +\fBinc(v, release)\fP: Return the version incremented by the release +type (\fBmajor\fP, \fBpremajor\fP, \fBminor\fP, \fBpreminor\fP, \fBpatch\fP, +\fBprepatch\fP, or \fBprerelease\fP), or null if it's not valid +.RS +.IP \(bu 2 +\fBpremajor\fP in one call will bump the version up to the next major +version and down to a prerelease of that major version\. +\fBpreminor\fP, and \fBprepatch\fP work the same way\. +.IP \(bu 2 +If called from a non\-prerelease version, the \fBprerelease\fP will work the +same as \fBprepatch\fP\|\. It increments the patch version, then makes a +prerelease\. If the input version is already a prerelease it simply +increments it\. + +.RE +.IP \(bu 2 +\fBprerelease(v)\fP: Returns an array of prerelease components, or null +if none exist\. Example: \fBprerelease('1\.2\.3\-alpha\.1') \-> ['alpha', 1]\fP +.IP \(bu 2 +\fBmajor(v)\fP: Return the major version number\. +.IP \(bu 2 +\fBminor(v)\fP: Return the minor version number\. +.IP \(bu 2 +\fBpatch(v)\fP: Return the patch version number\. +.IP \(bu 2 +\fBintersects(r1, r2, loose)\fP: Return true if the two supplied ranges +or comparators intersect\. +.IP \(bu 2 +\fBparse(v)\fP: Attempt to parse a string as a semantic version, returning either +a \fBSemVer\fP object or \fBnull\fP\|\. + +.RE +.SS Comparison +.RS 0 +.IP \(bu 2 +\fBgt(v1, v2)\fP: \fBv1 > v2\fP +.IP \(bu 2 +\fBgte(v1, v2)\fP: \fBv1 >= v2\fP +.IP \(bu 2 +\fBlt(v1, v2)\fP: \fBv1 < v2\fP +.IP \(bu 2 +\fBlte(v1, v2)\fP: \fBv1 <= v2\fP +.IP \(bu 2 +\fBeq(v1, v2)\fP: \fBv1 == v2\fP This is true if they're logically equivalent, +even if they're not the exact same string\. You already know how to +compare strings\. +.IP \(bu 2 +\fBneq(v1, v2)\fP: \fBv1 != v2\fP The opposite of \fBeq\fP\|\. +.IP \(bu 2 +\fBcmp(v1, comparator, v2)\fP: Pass in a comparison string, and it'll call +the corresponding function above\. \fB"==="\fP and \fB"!=="\fP do simple +string comparison, but are included for completeness\. Throws if an +invalid comparison string is provided\. +.IP \(bu 2 +\fBcompare(v1, v2)\fP: Return \fB0\fP if \fBv1 == v2\fP, or \fB1\fP if \fBv1\fP is greater, or \fB\-1\fP if +\fBv2\fP is greater\. Sorts in ascending order if passed to \fBArray\.sort()\fP\|\. +.IP \(bu 2 +\fBrcompare(v1, v2)\fP: The reverse of compare\. Sorts an array of versions +in descending order when passed to \fBArray\.sort()\fP\|\. +.IP \(bu 2 +\fBdiff(v1, v2)\fP: Returns difference between two versions by the release type +(\fBmajor\fP, \fBpremajor\fP, \fBminor\fP, \fBpreminor\fP, \fBpatch\fP, \fBprepatch\fP, or \fBprerelease\fP), +or null if the versions are the same\. + +.RE +.SS Comparators +.RS 0 +.IP \(bu 2 +\fBintersects(comparator)\fP: Return true if the comparators intersect + +.RE +.SS Ranges +.RS 0 +.IP \(bu 2 +\fBvalidRange(range)\fP: Return the valid range or null if it's not valid +.IP \(bu 2 +\fBsatisfies(version, range)\fP: Return true if the version satisfies the +range\. +.IP \(bu 2 +\fBmaxSatisfying(versions, range)\fP: Return the highest version in the list +that satisfies the range, or \fBnull\fP if none of them do\. +.IP \(bu 2 +\fBminSatisfying(versions, range)\fP: Return the lowest version in the list +that satisfies the range, or \fBnull\fP if none of them do\. +.IP \(bu 2 +\fBminVersion(range)\fP: Return the lowest version that can possibly match +the given range\. +.IP \(bu 2 +\fBgtr(version, range)\fP: Return \fBtrue\fP if version is greater than all the +versions possible in the range\. +.IP \(bu 2 +\fBltr(version, range)\fP: Return \fBtrue\fP if version is less than all the +versions possible in the range\. +.IP \(bu 2 +\fBoutside(version, range, hilo)\fP: Return true if the version is outside +the bounds of the range in either the high or low direction\. The +\fBhilo\fP argument must be either the string \fB\|'>'\fP or \fB\|'<'\fP\|\. (This is +the function called by \fBgtr\fP and \fBltr\fP\|\.) +.IP \(bu 2 +\fBintersects(range)\fP: Return true if any of the ranges comparators intersect + +.RE +.P +Note that, since ranges may be non\-contiguous, a version might not be +greater than a range, less than a range, \fIor\fR satisfy a range! For +example, the range \fB1\.2 <1\.2\.9 || >2\.0\.0\fP would have a hole from \fB1\.2\.9\fP +until \fB2\.0\.0\fP, so the version \fB1\.2\.10\fP would not be greater than the +range (because \fB2\.0\.1\fP satisfies, which is higher), nor less than the +range (since \fB1\.2\.8\fP satisfies, which is lower), and it also does not +satisfy the range\. +.P +If you want to know if a version satisfies or does not satisfy a +range, use the \fBsatisfies(version, range)\fP function\. +.SS Coercion +.RS 0 +.IP \(bu 2 +\fBcoerce(version)\fP: Coerces a string to semver if possible + +.RE +.P +This aims to provide a very forgiving translation of a non\-semver string to +semver\. It looks for the first digit in a string, and consumes all +remaining characters which satisfy at least a partial semver (e\.g\., \fB1\fP, +\fB1\.2\fP, \fB1\.2\.3\fP) up to the max permitted length (256 characters)\. Longer +versions are simply truncated (\fB4\.6\.3\.9\.2\-alpha2\fP becomes \fB4\.6\.3\fP)\. All +surrounding text is simply ignored (\fBv3\.4 replaces v3\.3\.1\fP becomes +\fB3\.4\.0\fP)\. Only text which lacks digits will fail coercion (\fBversion one\fP +is not valid)\. The maximum length for any semver component considered for +coercion is 16 characters; longer components will be ignored +(\fB10000000000000000\.4\.7\.4\fP becomes \fB4\.7\.4\fP)\. The maximum value for any +semver component is \fBNumber\.MAX_SAFE_INTEGER || (2**53 \- 1)\fP; higher value +components are invalid (\fB9999999999999999\.4\.7\.4\fP is likely invalid)\. diff --git a/deps/npm/node_modules/@npmcli/arborist/lib/edge.js b/deps/npm/node_modules/@npmcli/arborist/lib/edge.js index c5f00faff2..79510d509f 100644 --- a/deps/npm/node_modules/@npmcli/arborist/lib/edge.js +++ b/deps/npm/node_modules/@npmcli/arborist/lib/edge.js @@ -87,16 +87,24 @@ class Edge { // return the edge data, and an explanation of how that edge came to be here [_explain] (seen) { - const { error, from } = this + const { error, from, bundled } = this return { type: this.type, name: this.name, spec: this.spec, + ...(bundled ? { bundled } : {}), ...(error ? { error } : {}), ...(from ? { from: from.explain(null, seen) } : {}), } } + get bundled () { + if (!this.from) + return false + const { package: { bundleDependencies = [] } } = this.from + return bundleDependencies.includes(this.name) + } + get workspace () { return this[_type] === 'workspace' } diff --git a/deps/npm/node_modules/@npmcli/arborist/package.json b/deps/npm/node_modules/@npmcli/arborist/package.json index cf4224234c..268f0e72a9 100644 --- a/deps/npm/node_modules/@npmcli/arborist/package.json +++ b/deps/npm/node_modules/@npmcli/arborist/package.json @@ -1,6 +1,6 @@ { "name": "@npmcli/arborist", - "version": "2.2.5", + "version": "2.2.6", "description": "Manage node_modules trees", "dependencies": { "@npmcli/installed-package-contents": "^1.0.7", diff --git a/deps/npm/package.json b/deps/npm/package.json index 8ed3c32b88..f4d34f98ab 100644 --- a/deps/npm/package.json +++ b/deps/npm/package.json @@ -1,5 +1,5 @@ { - "version": "7.6.0", + "version": "7.6.1", "name": "npm", "description": "a package manager for JavaScript", "keywords": [ @@ -42,7 +42,7 @@ "./package.json": "./package.json" }, "dependencies": { - "@npmcli/arborist": "^2.2.5", + "@npmcli/arborist": "^2.2.6", "@npmcli/ci-detect": "^1.2.0", "@npmcli/config": "^1.2.9", "@npmcli/run-script": "^1.8.3", @@ -180,7 +180,7 @@ "devDependencies": { "@mdx-js/mdx": "^1.6.22", "cmark-gfm": "^0.8.5", - "eslint": "^7.19.0", + "eslint": "^7.21.0", "eslint-plugin-import": "^2.22.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^4.3.1", diff --git a/deps/npm/tap-snapshots/test-lib-utils-explain-dep.js-TAP.test.js b/deps/npm/tap-snapshots/test-lib-utils-explain-dep.js-TAP.test.js index 6d169bca69..7e77081f9d 100644 --- a/deps/npm/tap-snapshots/test-lib-utils-explain-dep.js-TAP.test.js +++ b/deps/npm/tap-snapshots/test-lib-utils-explain-dep.js-TAP.test.js @@ -24,13 +24,13 @@ manydep@1.0.0 exports[`test/lib/utils/explain-dep.js TAP bundled > explain color deep 1`] = ` [1mbundle-of-joy[22m@[1m1.0.0[22m [1m[34mbundled[39m[22m[2m[22m [2mnode_modules/bundle-of-joy[22m - [1mprod-dep[22m@"[1m1.x[22m" from the root project + [34mbundled[39m [1mprod-dep[22m@"[1m1.x[22m" from the root project ` exports[`test/lib/utils/explain-dep.js TAP bundled > explain nocolor shallow 1`] = ` bundle-of-joy@1.0.0 bundled node_modules/bundle-of-joy - prod-dep@"1.x" from the root project + bundled prod-dep@"1.x" from the root project ` exports[`test/lib/utils/explain-dep.js TAP bundled > print color 1`] = ` diff --git a/deps/npm/tap-snapshots/test-lib-utils-npm-usage.js-TAP.test.js b/deps/npm/tap-snapshots/test-lib-utils-npm-usage.js-TAP.test.js index 5fb74fd5ef..5fbb548330 100644 --- a/deps/npm/tap-snapshots/test-lib-utils-npm-usage.js-TAP.test.js +++ b/deps/npm/tap-snapshots/test-lib-utils-npm-usage.js-TAP.test.js @@ -5,7 +5,7 @@ * Make sure to inspect the output below. Do not ignore changes! */ 'use strict' -exports[`test/lib/utils/npm-usage.js TAP basic usage > must match snapshot 1`] = ` +exports[`test/lib/utils/npm-usage.js TAP usage basic usage > must match snapshot 1`] = ` Usage: npm <command> @@ -41,7 +41,7 @@ npm@{VERSION} {BASEDIR} ` -exports[`test/lib/utils/npm-usage.js TAP did you mean? > must match snapshot 1`] = ` +exports[`test/lib/utils/npm-usage.js TAP usage did you mean? > must match snapshot 1`] = ` Usage: npm <command> @@ -77,14 +77,14 @@ npm@{VERSION} {BASEDIR} ` -exports[`test/lib/utils/npm-usage.js TAP did you mean? > must match snapshot 2`] = ` +exports[`test/lib/utils/npm-usage.js TAP usage did you mean? > must match snapshot 2`] = ` Did you mean one of these? install uninstall ` -exports[`test/lib/utils/npm-usage.js TAP set process.stdout.columns columns=0 > must match snapshot 1`] = ` +exports[`test/lib/utils/npm-usage.js TAP usage set process.stdout.columns columns=0 > must match snapshot 1`] = ` Usage: npm <command> @@ -120,7 +120,7 @@ npm@{VERSION} {BASEDIR} ` -exports[`test/lib/utils/npm-usage.js TAP set process.stdout.columns columns=90 > must match snapshot 1`] = ` +exports[`test/lib/utils/npm-usage.js TAP usage set process.stdout.columns columns=90 > must match snapshot 1`] = ` Usage: npm <command> @@ -156,7 +156,7 @@ npm@{VERSION} {BASEDIR} ` -exports[`test/lib/utils/npm-usage.js TAP with browser > must match snapshot 1`] = ` +exports[`test/lib/utils/npm-usage.js TAP usage with browser > must match snapshot 1`] = ` Usage: npm <command> @@ -192,7 +192,7 @@ npm@{VERSION} {BASEDIR} ` -exports[`test/lib/utils/npm-usage.js TAP with long > must match snapshot 1`] = ` +exports[`test/lib/utils/npm-usage.js TAP usage with long > must match snapshot 1`] = ` Usage: npm <command> @@ -360,9 +360,9 @@ All commands: alias: ln - ll npm ls [[<@scope>/]<pkg> ...] + ll npm ll [[<@scope>/]<pkg> ...] - alias: list + alias: la login npm adduser [--registry=url] [--scope=@orgname] [--always-auth] @@ -378,6 +378,8 @@ All commands: npm org rm orgname username npm org ls orgname [<username>] + alias: ogr + outdated npm outdated [[<@scope>/]<pkg> ...] owner npm owner add <user> [<@scope>/]<pkg> @@ -393,10 +395,10 @@ All commands: prefix npm prefix [-g] - profile npm profile disable-2fa + profile npm profile enable-2fa [auth-only|auth-and-writes] - common options: npm profile get [<key>] + common options: npm profile disable-2fa prune npm prune [[<@scope>/]<pkg>...] [--production] @@ -474,6 +476,7 @@ All commands: 'npm view <pkg> version' to view a package's published version 'npm ls' to inspect current package/dependency versions + alias: verison view npm view [<@scope>/]<pkg>[@<version>] [<field>[.subfield]...] diff --git a/deps/npm/test/bin/windows-shims.js b/deps/npm/test/bin/windows-shims.js new file mode 100644 index 0000000000..8d73e39f2c --- /dev/null +++ b/deps/npm/test/bin/windows-shims.js @@ -0,0 +1,135 @@ +const t = require('tap') + +if (process.platform !== 'win32') { + t.plan(0, 'test only relevant on windows') + process.exit(0) +} + +const has = path => { + try { + // If WSL is installed, it *has* a bash.exe, but it fails if + // there is no distro installed, so we need to detect that. + const result = spawnSync(path, ['-l', '-c', 'exit 0']) + if (result.status === 0) + return true + else { + // print whatever error we got + throw result.error || Object.assign(new Error(String(result.stderr)), { + code: result.status, + }) + } + } catch (er) { + t.comment(`not installed: ${path}`, er) + return false + } +} + +const { version } = require('../../package.json') +const spawn = require('@npmcli/promise-spawn') +const { spawnSync } = require('child_process') +const { resolve } = require('path') +const { ProgramFiles, SystemRoot } = process.env +const { readFileSync, chmodSync } = require('fs') +const gitBash = resolve(ProgramFiles, 'Git', 'bin', 'bash.exe') +const gitUsrBinBash = resolve(ProgramFiles, 'Git', 'usr', 'bin', 'bash.exe') +const wslBash = resolve(SystemRoot, 'System32', 'bash.exe') +const cygwinBash = resolve(SystemRoot, '/', 'cygwin64', 'bin', 'bash.exe') + +const bashes = Object.entries({ + 'wsl bash': wslBash, + 'git bash': gitBash, + 'git internal bash': gitUsrBinBash, + 'cygwin bash': cygwinBash, +}) + +const npmShim = resolve(__dirname, '../../bin/npm') +const npxShim = resolve(__dirname, '../../bin/npx') + +const path = t.testdir({ + 'node.exe': readFileSync(process.execPath), + npm: readFileSync(npmShim), + npx: readFileSync(npxShim), + // simulate the state where one version of npm is installed + // with node, but we should load the globally installed one + 'global-prefix': { + node_modules: { + npm: t.fixture('symlink', resolve(__dirname, '../..')), + }, + }, + // put in a shim that ONLY prints the intended global prefix, + // and should not be used for anything else. + node_modules: { + npm: { + bin: { + 'npx-cli.js': ` + throw new Error('this should not be called') + `, + 'npm-cli.js': ` + const assert = require('assert') + const args = process.argv.slice(2) + assert.equal(args[0], 'prefix') + assert.equal(args[1], '-g') + const { resolve } = require('path') + console.log(resolve(__dirname, '../../../global-prefix')) + `, + }, + }, + }, +}) +chmodSync(resolve(path, 'npm'), 0o755) +chmodSync(resolve(path, 'npx'), 0o755) + +for (const [name, bash] of bashes) { + if (!has(bash)) { + t.skip(`${name} not installed`, { bin: bash, diagnostic: true }) + continue + } + + if (bash === cygwinBash && process.env.NYC_CONFIG) { + t.skip('Cygwin does not play nicely with NYC, run without coverage') + continue + } + + t.test(name, async t => { + t.plan(2) + t.test('npm', async t => { + // only cygwin *requires* the -l, but the others are ok with it + // don't hit the registry for the update check + const args = ['-l', 'npm', 'help'] + + const result = await spawn(bash, args, { + env: { PATH: path, npm_config_update_notifier: 'false' }, + cwd: path, + stdioString: true, + }) + t.match(result, { + cmd: bash, + args: ['-l', 'npm', 'help'], + code: 0, + signal: null, + stderr: String, + // should have loaded this instance of npm we symlinked in + stdout: `npm@${version} ${resolve(__dirname, '../..')}`, + }) + }) + + t.test('npx', async t => { + const args = ['-l', 'npx', '--version'] + + const result = await spawn(bash, args, { + env: { PATH: path, npm_config_update_notifier: 'false' }, + cwd: path, + stdioString: true, + }) + t.match(result, { + cmd: bash, + args: ['-l', 'npx', '--version'], + code: 0, + signal: null, + stderr: String, + // should have loaded this instance of npm we symlinked in + stdout: version, + }) + }) + }) +} diff --git a/deps/npm/test/coverage-map.js b/deps/npm/test/coverage-map.js index f247c051f0..63f2a608e0 100644 --- a/deps/npm/test/coverage-map.js +++ b/deps/npm/test/coverage-map.js @@ -7,6 +7,10 @@ const coverageMap = (filename) => { return glob.sync(`${dir}/**/*.js`) .map(f => relative(process.cwd(), f)) } + if (/windows-shims\.js$/.test(filename)) { + // this one doesn't provide any coverage nyc can track + return [] + } if (/^test\/(lib|bin)\//.test(filename)) return filename.replace(/^test\//, '') return [] diff --git a/deps/npm/test/lib/access.js b/deps/npm/test/lib/access.js index fb799f2df2..3a732ad0aa 100644 --- a/deps/npm/test/lib/access.js +++ b/deps/npm/test/lib/access.js @@ -1,17 +1,12 @@ const { test } = require('tap') const requireInject = require('require-inject') -const access = requireInject('../../lib/access.js', { - '../../lib/npm.js': { - flatOptions: {}, - }, -}) +const Access = require('../../lib/access.js') test('completion', t => { - const { completion } = access - + const access = new Access({ flatOptions: {} }) const testComp = (argv, expect) => { - const res = completion({conf: {argv: {remain: argv}}}) + const res = access.completion({conf: {argv: {remain: argv}}}) t.resolves(res, expect, argv.join(' ')) } @@ -32,7 +27,7 @@ test('completion', t => { testComp(['npm', 'access', 'revoke'], []) t.rejects( - completion({conf: {argv: {remain: ['npm', 'access', 'foobar']}}}), + access.completion({conf: {argv: {remain: ['npm', 'access', 'foobar']}}}), { message: 'foobar not recognized' } ) @@ -40,14 +35,16 @@ test('completion', t => { }) test('subcommand required', t => { - access([], (err) => { + const access = new Access({ flatOptions: {} }) + access.exec([], (err) => { t.equal(err, '\nUsage: Subcommand is required.\n\n' + access.usage) t.end() }) }) test('unrecognized subcommand', (t) => { - access(['blerg'], (err) => { + const access = new Access({ flatOptions: {} }) + access.exec(['blerg'], (err) => { t.match( err, /Usage: blerg is not a recognized subcommand/, @@ -58,7 +55,8 @@ test('unrecognized subcommand', (t) => { }) test('edit', (t) => { - access([ + const access = new Access({ flatOptions: {} }) + access.exec([ 'edit', '@scoped/another', ], (err) => { @@ -77,10 +75,8 @@ test('access public on unscoped package', (t) => { name: 'npm-access-public-pkg', }), }) - const access = requireInject('../../lib/access.js', { - '../../lib/npm.js': { prefix }, - }) - access([ + const access = new Access({ prefix }) + access.exec([ 'public', ], (err) => { t.match( @@ -98,7 +94,7 @@ test('access public on scoped package', (t) => { const prefix = t.testdir({ 'package.json': JSON.stringify({ name }), }) - const access = requireInject('../../lib/access.js', { + const Access = requireInject('../../lib/access.js', { libnpmaccess: { public: (pkg, { registry }) => { t.equal(pkg, name, 'should use pkg name ref') @@ -110,14 +106,12 @@ test('access public on scoped package', (t) => { return true }, }, - '../../lib/npm.js': { - flatOptions: { - registry: 'https://registry.npmjs.org', - }, - prefix, - }, }) - access([ + const access = new Access({ + flatOptions: { registry: 'https://registry.npmjs.org' }, + prefix, + }) + access.exec([ 'public', ], (err) => { t.ifError(err, 'npm access') @@ -129,10 +123,8 @@ test('access public on missing package.json', (t) => { const prefix = t.testdir({ node_modules: {}, }) - const access = requireInject('../../lib/access.js', { - '../../lib/npm.js': { prefix }, - }) - access([ + const access = new Access({ prefix }) + access.exec([ 'public', ], (err) => { t.match( @@ -149,10 +141,8 @@ test('access public on invalid package.json', (t) => { 'package.json': '{\n', node_modules: {}, }) - const access = requireInject('../../lib/access.js', { - '../../lib/npm.js': { prefix }, - }) - access([ + const access = new Access({ prefix }) + access.exec([ 'public', ], (err) => { t.match( @@ -170,10 +160,8 @@ test('access restricted on unscoped package', (t) => { name: 'npm-access-restricted-pkg', }), }) - const access = requireInject('../../lib/access.js', { - '../../lib/npm.js': { prefix }, - }) - access([ + const access = new Access({ prefix }) + access.exec([ 'restricted', ], (err) => { t.match( @@ -191,7 +179,7 @@ test('access restricted on scoped package', (t) => { const prefix = t.testdir({ 'package.json': JSON.stringify({ name }), }) - const access = requireInject('../../lib/access.js', { + const Access = requireInject('../../lib/access.js', { libnpmaccess: { restricted: (pkg, { registry }) => { t.equal(pkg, name, 'should use pkg name ref') @@ -203,14 +191,12 @@ test('access restricted on scoped package', (t) => { return true }, }, - '../../lib/npm.js': { - flatOptions: { - registry: 'https://registry.npmjs.org', - }, - prefix, - }, }) - access([ + const access = new Access({ + flatOptions: { registry: 'https://registry.npmjs.org' }, + prefix, + }) + access.exec([ 'restricted', ], (err) => { t.ifError(err, 'npm access') @@ -222,10 +208,8 @@ test('access restricted on missing package.json', (t) => { const prefix = t.testdir({ node_modules: {}, }) - const access = requireInject('../../lib/access.js', { - '../../lib/npm.js': { prefix }, - }) - access([ + const access = new Access({ prefix }) + access.exec([ 'restricted', ], (err) => { t.match( @@ -242,10 +226,8 @@ test('access restricted on invalid package.json', (t) => { 'package.json': '{\n', node_modules: {}, }) - const access = requireInject('../../lib/access.js', { - '../../lib/npm.js': { prefix }, - }) - access([ + const access = new Access({ prefix }) + access.exec([ 'restricted', ], (err) => { t.match( @@ -259,7 +241,7 @@ test('access restricted on invalid package.json', (t) => { test('access grant read-only', (t) => { t.plan(5) - const access = requireInject('../../lib/access.js', { + const Access = requireInject('../../lib/access.js', { libnpmaccess: { grant: (spec, team, permissions) => { t.equal(spec, '@scoped/another', 'should use expected spec') @@ -268,9 +250,9 @@ test('access grant read-only', (t) => { return true }, }, - '../../lib/npm.js': {}, }) - access([ + const access = new Access({}) + access.exec([ 'grant', 'read-only', 'myorg:myteam', @@ -283,7 +265,7 @@ test('access grant read-only', (t) => { test('access grant read-write', (t) => { t.plan(5) - const access = requireInject('../../lib/access.js', { + const Access = requireInject('../../lib/access.js', { libnpmaccess: { grant: (spec, team, permissions) => { t.equal(spec, '@scoped/another', 'should use expected spec') @@ -292,9 +274,9 @@ test('access grant read-write', (t) => { return true }, }, - '../../lib/npm.js': {}, }) - access([ + const access = new Access({}) + access.exec([ 'grant', 'read-write', 'myorg:myteam', @@ -312,7 +294,7 @@ test('access grant current cwd', (t) => { name: 'yargs', }), }) - const access = requireInject('../../lib/access.js', { + const Access = requireInject('../../lib/access.js', { libnpmaccess: { grant: (spec, team, permissions) => { t.equal(spec, 'yargs', 'should use expected spec') @@ -321,9 +303,9 @@ test('access grant current cwd', (t) => { return true }, }, - '../../lib/npm.js': { prefix }, }) - access([ + const access = new Access({ prefix }) + access.exec([ 'grant', 'read-write', 'myorg:myteam', @@ -334,7 +316,8 @@ test('access grant current cwd', (t) => { }) test('access grant others', (t) => { - access([ + const access = new Access({ flatOptions: {} }) + access.exec([ 'grant', 'rerere', 'myorg:myteam', @@ -350,7 +333,8 @@ test('access grant others', (t) => { }) test('access grant missing team args', (t) => { - access([ + const access = new Access({ flatOptions: {} }) + access.exec([ 'grant', 'read-only', undefined, @@ -366,7 +350,8 @@ test('access grant missing team args', (t) => { }) test('access grant malformed team arg', (t) => { - access([ + const access = new Access({ flatOptions: {} }) + access.exec([ 'grant', 'read-only', 'foo', @@ -383,7 +368,7 @@ test('access grant malformed team arg', (t) => { test('access 2fa-required/2fa-not-required', t => { t.plan(2) - const access = requireInject('../../lib/access.js', { + const Access = requireInject('../../lib/access.js', { libnpmaccess: { tfaRequired: (spec) => { t.equal(spec, '@scope/pkg', 'should use expected spec') @@ -394,15 +379,15 @@ test('access 2fa-required/2fa-not-required', t => { return true }, }, - '../../lib/npm.js': {}, }) + const access = new Access({}) - access(['2fa-required', '@scope/pkg'], er => { + access.exec(['2fa-required', '@scope/pkg'], er => { if (er) throw er }) - access(['2fa-not-required', 'unscoped-pkg'], er => { + access.exec(['2fa-not-required', 'unscoped-pkg'], er => { if (er) throw er }) @@ -410,7 +395,7 @@ test('access 2fa-required/2fa-not-required', t => { test('access revoke', (t) => { t.plan(4) - const access = requireInject('../../lib/access.js', { + const Access = requireInject('../../lib/access.js', { libnpmaccess: { revoke: (spec, team) => { t.equal(spec, '@scoped/another', 'should use expected spec') @@ -418,9 +403,9 @@ test('access revoke', (t) => { return true }, }, - '../../lib/npm.js': {}, }) - access([ + const access = new Access({}) + access.exec([ 'revoke', 'myorg:myteam', '@scoped/another', @@ -431,7 +416,8 @@ test('access revoke', (t) => { }) test('access revoke missing team args', (t) => { - access([ + const access = new Access({ flatOptions: {} }) + access.exec([ 'revoke', undefined, '@scoped/another', @@ -446,7 +432,8 @@ test('access revoke missing team args', (t) => { }) test('access revoke malformed team arg', (t) => { - access([ + const access = new Access({ flatOptions: {} }) + access.exec([ 'revoke', 'foo', '@scoped/another', @@ -462,7 +449,7 @@ test('access revoke malformed team arg', (t) => { test('npm access ls-packages with no team', (t) => { t.plan(3) - const access = requireInject('../../lib/access.js', { + const Access = requireInject('../../lib/access.js', { libnpmaccess: { lsPackages: (entity) => { t.equal(entity, 'foo', 'should use expected entity') @@ -471,9 +458,9 @@ test('npm access ls-packages with no team', (t) => { }, '../../lib/utils/get-identity.js': () => Promise.resolve('foo'), '../../lib/utils/output.js': () => null, - '../../lib/npm.js': {}, }) - access([ + const access = new Access({}) + access.exec([ 'ls-packages', ], (err) => { t.ifError(err, 'npm access') @@ -483,7 +470,7 @@ test('npm access ls-packages with no team', (t) => { test('access ls-packages on team', (t) => { t.plan(3) - const access = requireInject('../../lib/access.js', { + const Access = requireInject('../../lib/access.js', { libnpmaccess: { lsPackages: (entity) => { t.equal(entity, 'myorg:myteam', 'should use expected entity') @@ -491,9 +478,9 @@ test('access ls-packages on team', (t) => { }, }, '../../lib/utils/output.js': () => null, - '../../lib/npm.js': {}, }) - access([ + const access = new Access({}) + access.exec([ 'ls-packages', 'myorg:myteam', ], (err) => { @@ -509,7 +496,7 @@ test('access ls-collaborators on current', (t) => { name: 'yargs', }), }) - const access = requireInject('../../lib/access.js', { + const Access = requireInject('../../lib/access.js', { libnpmaccess: { lsCollaborators: (spec) => { t.equal(spec, 'yargs', 'should use expected spec') @@ -517,9 +504,9 @@ test('access ls-collaborators on current', (t) => { }, }, '../../lib/utils/output.js': () => null, - '../../lib/npm.js': { prefix }, }) - access([ + const access = new Access({ prefix }) + access.exec([ 'ls-collaborators', ], (err) => { t.ifError(err, 'npm access') @@ -529,7 +516,7 @@ test('access ls-collaborators on current', (t) => { test('access ls-collaborators on spec', (t) => { t.plan(3) - const access = requireInject('../../lib/access.js', { + const Access = requireInject('../../lib/access.js', { libnpmaccess: { lsCollaborators: (spec) => { t.equal(spec, 'yargs', 'should use expected spec') @@ -537,9 +524,9 @@ test('access ls-collaborators on spec', (t) => { }, }, '../../lib/utils/output.js': () => null, - '../../lib/npm.js': {}, }) - access([ + const access = new Access({}) + access.exec([ 'ls-collaborators', 'yargs', ], (err) => { diff --git a/deps/npm/test/lib/adduser.js b/deps/npm/test/lib/adduser.js index 36f59e0857..32fd97c1bd 100644 --- a/deps/npm/test/lib/adduser.js +++ b/deps/npm/test/lib/adduser.js @@ -16,7 +16,7 @@ let failSave = false let deletedConfig = {} let registryOutput = '' let setConfig = {} -const authDummy = (options) => { +const authDummy = (npm, options) => { if (!options.fromFlatOptions) throw new Error('did not pass full flatOptions to auth function') @@ -37,46 +37,49 @@ const deleteMock = (key, where) => { [key]: where, } } -const adduser = requireInject('../../lib/adduser.js', { +const npm = { + flatOptions: _flatOptions, + config: { + delete: deleteMock, + get (key, where) { + if (!where || where === 'user') + return _flatOptions[key] + }, + getCredentialsByURI, + async save () { + if (failSave) + throw new Error('error saving user config') + }, + set (key, value, where) { + setConfig = { + ...setConfig, + [key]: { + value, + where, + }, + } + }, + setCredentialsByURI, + }, +} + +const AddUser = requireInject('../../lib/adduser.js', { npmlog: { disableProgress: () => null, notice: (_, msg) => { registryOutput = msg }, }, - '../../lib/npm.js': { - flatOptions: _flatOptions, - config: { - delete: deleteMock, - get (key, where) { - if (!where || where === 'user') - return _flatOptions[key] - }, - getCredentialsByURI, - async save () { - if (failSave) - throw new Error('error saving user config') - }, - set (key, value, where) { - setConfig = { - ...setConfig, - [key]: { - value, - where, - }, - } - }, - setCredentialsByURI, - }, - }, '../../lib/utils/output.js': msg => { result = msg }, '../../lib/auth/legacy.js': authDummy, }) +const adduser = new AddUser(npm) + test('simple login', (t) => { - adduser([], (err) => { + adduser.exec([], (err) => { t.ifError(err, 'npm adduser') t.equal( @@ -129,7 +132,7 @@ test('simple login', (t) => { test('bad auth type', (t) => { _flatOptions.authType = 'foo' - adduser([], (err) => { + adduser.exec([], (err) => { t.match( err, /Error: no such auth module/, @@ -147,7 +150,7 @@ test('bad auth type', (t) => { test('scoped login', (t) => { _flatOptions.scope = '@myscope' - adduser([], (err) => { + adduser.exec([], (err) => { t.ifError(err, 'npm adduser') t.deepEqual( @@ -168,7 +171,7 @@ test('scoped login with valid scoped registry config', (t) => { _flatOptions['@myscope:registry'] = 'https://diff-registry.npmjs.com/' _flatOptions.scope = '@myscope' - adduser([], (err) => { + adduser.exec([], (err) => { t.ifError(err, 'npm adduser') t.deepEqual( @@ -189,7 +192,7 @@ test('scoped login with valid scoped registry config', (t) => { test('save config failure', (t) => { failSave = true - adduser([], (err) => { + adduser.exec([], (err) => { t.match( err, /error saving user config/, diff --git a/deps/npm/test/lib/audit.js b/deps/npm/test/lib/audit.js index 3d6296bac6..6fd9c8a2c9 100644 --- a/deps/npm/test/lib/audit.js +++ b/deps/npm/test/lib/audit.js @@ -1,6 +1,5 @@ const t = require('tap') const requireInject = require('require-inject') -const audit = require('../../lib/audit.js') t.test('should audit using Arborist', t => { let ARB_ARGS = null @@ -10,13 +9,13 @@ t.test('should audit using Arborist', t => { let OUTPUT_CALLED = false let ARB_OBJ = null - const audit = requireInject('../../lib/audit.js', { - '../../lib/npm.js': { - prefix: 'foo', - flatOptions: { - json: false, - }, + const npm = { + prefix: 'foo', + flatOptions: { + json: false, }, + } + const Audit = requireInject('../../lib/audit.js', { 'npm-audit-report': () => { AUDIT_REPORT_CALLED = true return { @@ -32,7 +31,7 @@ t.test('should audit using Arborist', t => { this.auditReport = {} } }, - '../../lib/utils/reify-finish.js': arb => { + '../../lib/utils/reify-finish.js': (npm, arb) => { if (arb !== ARB_OBJ) throw new Error('got wrong object passed to reify-output') @@ -43,8 +42,10 @@ t.test('should audit using Arborist', t => { }, }) + const audit = new Audit(npm) + t.test('audit', t => { - audit([], () => { + audit.exec([], () => { t.match(ARB_ARGS, { audit: true, path: 'foo' }) t.equal(AUDIT_CALLED, true, 'called audit') t.equal(AUDIT_REPORT_CALLED, true, 'called audit report') @@ -54,7 +55,7 @@ t.test('should audit using Arborist', t => { }) t.test('audit fix', t => { - audit(['fix'], () => { + audit.exec(['fix'], () => { t.equal(REIFY_FINISH_CALLED, true, 'called reify output') t.end() }) @@ -64,13 +65,14 @@ t.test('should audit using Arborist', t => { }) t.test('should audit - json', t => { - const audit = requireInject('../../lib/audit.js', { - '../../lib/npm.js': { - prefix: 'foo', - flatOptions: { - json: true, - }, + const npm = { + prefix: 'foo', + flatOptions: { + json: true, }, + } + + const Audit = requireInject('../../lib/audit.js', { 'npm-audit-report': () => ({ report: 'there are vulnerabilities', exitCode: 0, @@ -83,8 +85,9 @@ t.test('should audit - json', t => { '../../lib/utils/reify-output.js': () => {}, '../../lib/utils/output.js': () => {}, }) + const audit = new Audit(npm) - audit([], (err) => { + audit.exec([], (err) => { t.notOk(err, 'no errors') t.end() }) @@ -95,17 +98,17 @@ t.test('report endpoint error', t => { t.test(`json=${json}`, t => { const OUTPUT = [] const LOGS = [] - const mocks = { - '../../lib/npm.js': { - prefix: 'foo', - command: 'audit', - flatOptions: { - json, - }, - log: { - warn: (...warning) => LOGS.push(warning), - }, + const npm = { + prefix: 'foo', + command: 'audit', + flatOptions: { + json, }, + log: { + warn: (...warning) => LOGS.push(warning), + }, + } + const Audit = requireInject('../../lib/audit.js', { 'npm-audit-report': () => { throw new Error('should not call audit report when there are errors') }, @@ -130,15 +133,10 @@ t.test('report endpoint error', t => { '../../lib/utils/output.js': (...msg) => { OUTPUT.push(msg) }, - } - // have to pass mocks to both to get the npm and output set right - const auditError = requireInject('../../lib/utils/audit-error.js', mocks) - const audit = requireInject('../../lib/audit.js', { - ...mocks, - '../../lib/utils/audit-error.js': auditError, }) + const audit = new Audit(npm) - audit([], (err) => { + audit.exec([], (err) => { t.equal(err, 'audit endpoint returned an error') t.strictSame(OUTPUT, [ [ @@ -168,6 +166,8 @@ t.test('report endpoint error', t => { }) t.test('completion', t => { + const Audit = require('../../lib/audit.js') + const audit = new Audit({}) t.test('fix', async t => { t.resolveMatch(audit.completion({ conf: { argv: { remain: ['npm', 'audit'] } } }), ['fix'], 'completes to fix') t.end() diff --git a/deps/npm/test/lib/auth/legacy.js b/deps/npm/test/lib/auth/legacy.js index f926ae1306..f5297c5817 100644 --- a/deps/npm/test/lib/auth/legacy.js +++ b/deps/npm/test/lib/auth/legacy.js @@ -13,19 +13,19 @@ const legacy = requireInject('../../../lib/auth/legacy.js', { }, }, 'npm-profile': profile, - '../../../lib/utils/open-url.js': (url, msg, cb) => { - if (url) - cb() - else { - cb(Object.assign( - new Error('failed open url'), - { code: 'ERROR' } - )) - } + '../../../lib/utils/open-url.js': (npm, url, msg) => { + if (!url) + throw Object.assign(new Error('failed open url'), { code: 'ERROR' }) }, '../../../lib/utils/read-user-info.js': read, }) +const npm = { + config: { + get: () => null, + }, +} + test('login using username/password with token result', async (t) => { profile.login = () => { return { token } @@ -34,7 +34,7 @@ test('login using username/password with token result', async (t) => { const { message, newCreds, - } = await legacy({ + } = await legacy(npm, { creds: { username: 'u', password: 'p', @@ -75,7 +75,7 @@ test('login using username/password with user info result', async (t) => { const { message, newCreds, - } = await legacy({ + } = await legacy(npm, { creds: { username: 'u', password: 'p', @@ -126,7 +126,7 @@ test('login otp requested', async (t) => { const { message, newCreds, - } = await legacy({ + } = await legacy(npm, { creds: { username: 'u', password: 'p', @@ -162,7 +162,7 @@ test('login missing basic credential info', async (t) => { )) await t.rejects( - legacy({ + legacy(npm, { creds: { username: 'u', password: 'p', @@ -196,7 +196,7 @@ test('create new user when user not found', async (t) => { const { message, newCreds, - } = await legacy({ + } = await legacy(npm, { creds: { username: 'u', password: 'p', @@ -246,7 +246,7 @@ test('prompts for user info if required', async (t) => { const { message, newCreds, - } = await legacy({ + } = await legacy(npm, { creds: { alwaysAuth: true, }, @@ -304,7 +304,7 @@ test('request otp when creating new user', async (t) => { } read.otp = () => Promise.resolve('1234') - await legacy({ + await legacy(npm, { creds: { username: 'u', password: 'p', @@ -333,7 +333,7 @@ test('unknown error during user creation', async (t) => { )) await t.rejects( - legacy({ + legacy(npm, { creds: { username: 'u', password: 'p', @@ -358,7 +358,7 @@ test('open url error', async (t) => { } await t.rejects( - legacy({ + legacy(npm, { creds: { username: 'u', password: 'p', @@ -377,7 +377,7 @@ test('open url error', async (t) => { test('login no credentials provided', async (t) => { profile.login = () => ({ token }) - await legacy({ + await legacy(npm, { creds: { username: undefined, password: undefined, @@ -401,7 +401,7 @@ test('login no credentials provided', async (t) => { test('scoped login', async (t) => { profile.login = () => ({ token }) - const { message } = await legacy({ + const { message } = await legacy(npm, { creds: { username: 'u', password: 'p', diff --git a/deps/npm/test/lib/auth/oauth.js b/deps/npm/test/lib/auth/oauth.js index 82d478b52c..c2f4c3443a 100644 --- a/deps/npm/test/lib/auth/oauth.js +++ b/deps/npm/test/lib/auth/oauth.js @@ -9,21 +9,21 @@ test('oauth login', (t) => { scope: 'myscope', } + const npm = { + config: { + set: (key, value) => { + t.equal(key, 'sso-type', 'should define sso-type') + t.equal(value, 'oauth', 'should set sso-type to oauth') + }, + }, + } const oauth = requireInject('../../../lib/auth/oauth.js', { - '../../../lib/auth/sso.js': (opts) => { + '../../../lib/auth/sso.js': (npm, opts) => { t.equal(opts, oauthOpts, 'should forward opts') }, - '../../../lib/npm.js': { - config: { - set: (key, value) => { - t.equal(key, 'sso-type', 'should define sso-type') - t.equal(value, 'oauth', 'should set sso-type to oauth') - }, - }, - }, }) - oauth(oauthOpts) + oauth(npm, oauthOpts) t.end() }) diff --git a/deps/npm/test/lib/auth/saml.js b/deps/npm/test/lib/auth/saml.js index 87fa6688b5..b8c21f649e 100644 --- a/deps/npm/test/lib/auth/saml.js +++ b/deps/npm/test/lib/auth/saml.js @@ -9,21 +9,21 @@ test('saml login', (t) => { scope: 'myscope', } + const npm = { + config: { + set: (key, value) => { + t.equal(key, 'sso-type', 'should define sso-type') + t.equal(value, 'saml', 'should set sso-type to saml') + }, + }, + } const saml = requireInject('../../../lib/auth/saml.js', { - '../../../lib/auth/sso.js': (opts) => { + '../../../lib/auth/sso.js': (npm, opts) => { t.equal(opts, samlOpts, 'should forward opts') }, - '../../../lib/npm.js': { - config: { - set: (key, value) => { - t.equal(key, 'sso-type', 'should define sso-type') - t.equal(value, 'saml', 'should set sso-type to saml') - }, - }, - }, }) - saml(samlOpts) + saml(npm, samlOpts) t.end() }) diff --git a/deps/npm/test/lib/auth/sso.js b/deps/npm/test/lib/auth/sso.js index 1fc04c64cd..6f3d981a6f 100644 --- a/deps/npm/test/lib/auth/sso.js +++ b/deps/npm/test/lib/auth/sso.js @@ -22,17 +22,12 @@ const sso = requireInject('../../../lib/auth/sso.js', { }, 'npm-profile': profile, 'npm-registry-fetch': npmFetch, - '../../../lib/npm.js': { - flatOptions: _flatOptions, - }, - '../../../lib/utils/open-url.js': (url, msg, cb) => { - if (url) - cb() - else { - cb(Object.assign( + '../../../lib/utils/open-url.js': async (npm, url, msg) => { + if (!url) { + throw Object.assign( new Error('failed open url'), { code: 'ERROR' } - )) + ) } }, '../../../lib/utils/otplease.js': (opts, fn) => { @@ -47,11 +42,15 @@ const sso = requireInject('../../../lib/auth/sso.js', { }, }) +const npm = { + flatOptions: _flatOptions, +} + test('empty login', async (t) => { _flatOptions.ssoType = false await t.rejects( - sso({}), + sso(npm, {}), { message: 'Missing option: sso-type' }, 'should throw if no sso-type defined in flatOptions' ) @@ -92,7 +91,7 @@ test('simple login', async (t) => { const { message, newCreds, - } = await sso({ + } = await sso(npm, { creds: {}, registry: 'https://registry.npmjs.org/', scope: '', @@ -157,7 +156,7 @@ test('polling retry', async (t) => { )) } - await sso({ + await sso(npm, { creds: {}, registry: 'https://registry.npmjs.org/', scope: '', @@ -177,7 +176,7 @@ test('polling error', async (t) => { )) await t.rejects( - sso({ + sso(npm, { creds: {}, registry: 'https://registry.npmjs.org/', scope: '', @@ -196,7 +195,7 @@ test('no token retrieved from loginCouch', async (t) => { profile.loginCouch = () => ({}) await t.rejects( - sso({ + sso(npm, { creds: {}, registry: 'https://registry.npmjs.org/', scope: '', @@ -214,7 +213,7 @@ test('no sso url retrieved from loginCouch', async (t) => { profile.loginCouch = () => Promise.resolve({ token }) await t.rejects( - sso({ + sso(npm, { creds: {}, registry: 'https://registry.npmjs.org/', scope: '', @@ -235,7 +234,7 @@ test('scoped login', async (t) => { const { message, newCreds, - } = await sso({ + } = await sso(npm, { creds: {}, registry: 'https://diff-registry.npmjs.org/', scope: 'myscope', diff --git a/deps/npm/test/lib/bin.js b/deps/npm/test/lib/bin.js index c5ed2a91b9..e96eb91af9 100644 --- a/deps/npm/test/lib/bin.js +++ b/deps/npm/test/lib/bin.js @@ -5,14 +5,15 @@ test('bin', (t) => { t.plan(3) const dir = '/bin/dir' - const bin = requireInject('../../lib/bin.js', { - '../../lib/npm.js': { bin: dir, flatOptions: { global: false } }, + const Bin = requireInject('../../lib/bin.js', { '../../lib/utils/output.js': (output) => { t.equal(output, dir, 'prints the correct directory') }, }) - bin([], (err) => { + const bin = new Bin({ bin: dir, flatOptions: { global: false } }) + + bin.exec([], (err) => { t.ifError(err, 'npm bin') t.ok('should have printed directory') }) @@ -30,15 +31,16 @@ test('bin -g', (t) => { } const dir = '/bin/dir' - const bin = requireInject('../../lib/bin.js', { - '../../lib/npm.js': { bin: dir, flatOptions: { global: true } }, + const Bin = requireInject('../../lib/bin.js', { '../../lib/utils/path.js': [dir], '../../lib/utils/output.js': (output) => { t.equal(output, dir, 'prints the correct directory') }, }) - bin([], (err) => { + const bin = new Bin({ bin: dir, flatOptions: { global: true } }) + + bin.exec([], (err) => { t.ifError(err, 'npm bin') t.ok('should have printed directory') }) @@ -56,15 +58,15 @@ test('bin -g (not in path)', (t) => { } const dir = '/bin/dir' - const bin = requireInject('../../lib/bin.js', { - '../../lib/npm.js': { bin: dir, flatOptions: { global: true } }, + const Bin = requireInject('../../lib/bin.js', { '../../lib/utils/path.js': ['/not/my/dir'], '../../lib/utils/output.js': (output) => { t.equal(output, dir, 'prints the correct directory') }, }) + const bin = new Bin({ bin: dir, flatOptions: { global: true } }) - bin([], (err) => { + bin.exec([], (err) => { t.ifError(err, 'npm bin') t.ok('should have printed directory') }) diff --git a/deps/npm/test/lib/birthday.js b/deps/npm/test/lib/birthday.js index 3b8110fc8f..c818223fb5 100644 --- a/deps/npm/test/lib/birthday.js +++ b/deps/npm/test/lib/birthday.js @@ -1,5 +1,4 @@ const t = require('tap') -const requireInject = require('require-inject') const npm = { flatOptions: { yes: false, @@ -17,10 +16,9 @@ const npm = { }, } -const birthday = requireInject('../../lib/birthday.js', { - '../../lib/npm.js': npm, -}) +const Birthday = require('../../lib/birthday.js') +const birthday = new Birthday(npm) let calledCb = false -birthday([], () => calledCb = true) +birthday.exec([], () => calledCb = true) t.equal(calledCb, true, 'called the callback') diff --git a/deps/npm/test/lib/bugs.js b/deps/npm/test/lib/bugs.js index 992bd9f614..e98131f113 100644 --- a/deps/npm/test/lib/bugs.js +++ b/deps/npm/test/lib/bugs.js @@ -43,17 +43,18 @@ const pacote = { // keep a tally of which urls got opened const opened = {} -const openUrl = (url, errMsg, cb) => { +const openUrl = async (npm, url, errMsg) => { opened[url] = opened[url] || 0 opened[url]++ - process.nextTick(cb) } -const bugs = requireInject('../../lib/bugs.js', { +const Bugs = requireInject('../../lib/bugs.js', { pacote, '../../lib/utils/open-url.js': openUrl, }) +const bugs = new Bugs({ flatOptions: {} }) + t.test('open bugs urls', t => { const expect = { nobugs: 'https://www.npmjs.com/package/nobugs', @@ -68,7 +69,7 @@ t.test('open bugs urls', t => { t.plan(keys.length) keys.forEach(pkg => { t.test(pkg, t => { - bugs([pkg], (er) => { + bugs.exec([pkg], (er) => { if (er) throw er t.equal(opened[expect[pkg]], 1, 'opened expected url', {opened}) @@ -79,7 +80,7 @@ t.test('open bugs urls', t => { }) t.test('open default package if none specified', t => { - bugs([], (er) => { + bugs.exec([], (er) => { if (er) throw er t.equal(opened['https://example.com'], 2, 'opened expected url', {opened}) diff --git a/deps/npm/test/lib/cache.js b/deps/npm/test/lib/cache.js index 05d269dd4d..67499f37e9 100644 --- a/deps/npm/test/lib/cache.js +++ b/deps/npm/test/lib/cache.js @@ -58,27 +58,26 @@ const cacache = { }, } -const mocks = { +const Cache = requireInject('../../lib/cache.js', { cacache, npmlog, pacote, rimraf, - '../../lib/npm.js': npm, '../../lib/utils/output.js': output, '../../lib/utils/usage.js': usageUtil, -} +}) -const cache = requireInject('../../lib/cache.js', mocks) +const cache = new Cache(npm) t.test('cache no args', t => { - cache([], err => { + cache.exec([], err => { t.equal(err.message, 'usage instructions', 'should throw usage instructions') t.end() }) }) t.test('cache clean', t => { - cache(['clean'], err => { + cache.exec(['clean'], err => { t.match(err.message, 'the npm cache self-heals', 'should throw warning') t.end() }) @@ -91,7 +90,7 @@ t.test('cache clean (force)', t => { flatOptions.force = false }) - cache(['clear'], err => { + cache.exec(['clear'], err => { t.ifError(err) t.equal(rimrafPath, path.join(npm.cache, '_cacache')) t.end() @@ -99,7 +98,7 @@ t.test('cache clean (force)', t => { }) t.test('cache clean with arg', t => { - cache(['rm', 'pkg'], err => { + cache.exec(['rm', 'pkg'], err => { t.match(err.message, 'does not accept arguments', 'should throw error') t.end() }) @@ -110,7 +109,7 @@ t.test('cache add no arg', t => { logOutput = [] }) - cache(['add'], err => { + cache.exec(['add'], err => { t.strictSame(logOutput, [ ['silly', 'cache add', 'args', []], ], 'logs correctly') @@ -126,7 +125,7 @@ t.test('cache add pkg only', t => { tarballStreamOpts = {} }) - cache(['add', 'mypkg'], err => { + cache.exec(['add', 'mypkg'], err => { t.ifError(err) t.strictSame(logOutput, [ ['silly', 'cache add', 'args', ['mypkg']], @@ -145,7 +144,7 @@ t.test('cache add pkg w/ spec modifier', t => { tarballStreamOpts = {} }) - cache(['add', 'mypkg', 'latest'], err => { + cache.exec(['add', 'mypkg', 'latest'], err => { t.ifError(err) t.strictSame(logOutput, [ ['silly', 'cache add', 'args', ['mypkg', 'latest']], @@ -162,7 +161,7 @@ t.test('cache verify', t => { outputOutput = [] }) - cache(['verify'], err => { + cache.exec(['verify'], err => { t.ifError(err) t.match(outputOutput, [ `Cache verified and compressed (${path.join(npm.cache, '_cacache')})`, @@ -189,7 +188,7 @@ t.test('cache verify w/ extra output', t => { delete cacacheVerifyStats.missingContent }) - cache(['check'], err => { + cache.exec(['check'], err => { t.ifError(err) t.match(outputOutput, [ `Cache verified and compressed (~${path.join('/fake/path', '_cacache')})`, diff --git a/deps/npm/test/lib/ci.js b/deps/npm/test/lib/ci.js index 28c66b056c..3419218ef9 100644 --- a/deps/npm/test/lib/ci.js +++ b/deps/npm/test/lib/ci.js @@ -9,19 +9,8 @@ const requireInject = require('require-inject') test('should ignore scripts with --ignore-scripts', (t) => { const SCRIPTS = [] let REIFY_CALLED = false - const ci = requireInject('../../lib/ci.js', { + const CI = requireInject('../../lib/ci.js', { '../../lib/utils/reify-finish.js': async () => {}, - '../../lib/npm.js': { - globalDir: 'path/to/node_modules/', - prefix: 'foo', - flatOptions: { - global: false, - ignoreScripts: true, - }, - config: { - get: () => false, - }, - }, '@npmcli/run-script': ({ event }) => { SCRIPTS.push(event) }, @@ -32,7 +21,20 @@ test('should ignore scripts with --ignore-scripts', (t) => { } }, }) - ci([], er => { + + const ci = new CI({ + globalDir: 'path/to/node_modules/', + prefix: 'foo', + flatOptions: { + global: false, + ignoreScripts: true, + }, + config: { + get: () => false, + }, + }) + + ci.exec([], er => { if (er) throw er t.equal(REIFY_CALLED, true, 'called reify') @@ -87,13 +89,7 @@ test('should use Arborist and run-script', (t) => { const expectRimrafs = 3 let actualRimrafs = 0 - const ci = requireInject('../../lib/ci.js', { - '../../lib/npm.js': { - prefix: path, - flatOptions: { - global: false, - }, - }, + const CI = requireInject('../../lib/ci.js', { '../../lib/utils/reify-finish.js': async () => {}, '@npmcli/run-script': opts => { t.match(opts, { event: scripts.shift() }) @@ -118,7 +114,15 @@ test('should use Arborist and run-script', (t) => { t.ok(arb, 'gets arborist tree') }, }) - ci(null, er => { + + const ci = new CI({ + prefix: path, + flatOptions: { + global: false, + }, + }) + + ci.exec(null, er => { if (er) throw er for (const [msg, result] of Object.entries(timers)) @@ -131,13 +135,7 @@ test('should use Arborist and run-script', (t) => { }) test('should pass flatOptions to Arborist.reify', (t) => { - const ci = requireInject('../../lib/ci.js', { - '../../lib/npm.js': { - prefix: 'foo', - flatOptions: { - production: true, - }, - }, + const CI = requireInject('../../lib/ci.js', { '../../lib/utils/reify-finish.js': async () => {}, '@npmcli/run-script': opts => {}, '@npmcli/arborist': function () { @@ -148,7 +146,13 @@ test('should pass flatOptions to Arborist.reify', (t) => { } }, }) - ci(null, er => { + const ci = new CI({ + prefix: 'foo', + flatOptions: { + production: true, + }, + }) + ci.exec(null, er => { if (er) throw er }) @@ -160,13 +164,7 @@ test('should throw if package-lock.json or npm-shrinkwrap missing', (t) => { 'package.json': 'some info', }) - const ci = requireInject('../../lib/ci.js', { - '../../lib/npm.js': { - prefix: testDir, - flatOptions: { - global: false, - }, - }, + const CI = requireInject('../../lib/ci.js', { '@npmcli/run-script': opts => {}, '../../lib/utils/reify-finish.js': async () => {}, npmlog: { @@ -175,7 +173,13 @@ test('should throw if package-lock.json or npm-shrinkwrap missing', (t) => { }, }, }) - ci(null, (err, res) => { + const ci = new CI({ + prefix: testDir, + flatOptions: { + global: false, + }, + }) + ci.exec(null, (err, res) => { t.ok(err, 'throws error when there is no package-lock') t.notOk(res) t.end() @@ -183,17 +187,17 @@ test('should throw if package-lock.json or npm-shrinkwrap missing', (t) => { }) test('should throw ECIGLOBAL', (t) => { - const ci = requireInject('../../lib/ci.js', { - '../../lib/npm.js': { - prefix: 'foo', - flatOptions: { - global: true, - }, - }, + const CI = requireInject('../../lib/ci.js', { '@npmcli/run-script': opts => {}, '../../lib/utils/reify-finish.js': async () => {}, }) - ci(null, (err, res) => { + const ci = new CI({ + prefix: 'foo', + flatOptions: { + global: true, + }, + }) + ci.exec(null, (err, res) => { t.equals(err.code, 'ECIGLOBAL', 'throws error with global packages') t.notOk(res) t.end() @@ -207,13 +211,7 @@ test('should remove existing node_modules before installing', (t) => { }, }) - const ci = requireInject('../../lib/ci.js', { - '../../lib/npm.js': { - prefix: testDir, - flatOptions: { - global: false, - }, - }, + const CI = requireInject('../../lib/ci.js', { '@npmcli/run-script': opts => {}, '../../lib/utils/reify-finish.js': async () => {}, '@npmcli/arborist': function () { @@ -229,7 +227,14 @@ test('should remove existing node_modules before installing', (t) => { }, }) - ci(null, er => { + const ci = new CI({ + prefix: testDir, + flatOptions: { + global: false, + }, + }) + + ci.exec(null, er => { if (er) throw er }) diff --git a/deps/npm/test/lib/completion.js b/deps/npm/test/lib/completion.js index 19f70df20e..89e8134ebb 100644 --- a/deps/npm/test/lib/completion.js +++ b/deps/npm/test/lib/completion.js @@ -75,8 +75,7 @@ const deref = (cmd) => { return cmd } -const completion = requireInject('../../lib/completion.js', { - '../../lib/npm.js': npm, +const Completion = requireInject('../../lib/completion.js', { '../../lib/utils/cmd-list.js': cmdList, '../../lib/utils/config.js': config, '../../lib/utils/deref-command.js': deref, @@ -85,6 +84,7 @@ const completion = requireInject('../../lib/completion.js', { output.push(line) }, }) +const completion = new Completion(npm) test('completion completion', async t => { const home = process.env.HOME @@ -125,11 +125,13 @@ test('completion completion wrong word count', async t => { }) test('completion errors in windows without bash', t => { - const compl = requireInject('../../lib/completion.js', { + const Compl = requireInject('../../lib/completion.js', { '../../lib/utils/is-windows-shell.js': true, }) - compl({}, (err) => { + const compl = new Compl() + + compl.exec({}, (err) => { t.match(err, { code: 'ENOTSUP', message: /completion supported only in MINGW/, @@ -162,7 +164,7 @@ test('dump script when completion is not being attempted', t => { }) } - completion({}, (err) => { + completion.exec({}, (err) => { if (err) throw err @@ -195,7 +197,7 @@ test('dump script exits correctly when EPIPE is emitted on stdout', t => { }) } - completion({}, (err) => { + completion.exec({}, (err) => { if (err) throw err @@ -228,7 +230,7 @@ test('non EPIPE errors cause failures', t => { }) } - completion({}, (err) => { + completion.exec({}, (err) => { t.equal(err.errno, 'ESOMETHINGELSE', 'propagated error') t.equal(data, completionScript, 'wrote the completion script') t.end() @@ -248,7 +250,7 @@ test('completion completes single command name', t => { output.length = 0 }) - completion(['npm', 'c'], (err, res) => { + completion.exec(['npm', 'c'], (err, res) => { if (err) throw err @@ -270,7 +272,7 @@ test('completion completes command names', t => { output.length = 0 }) - completion(['npm', 'a'], (err, res) => { + completion.exec(['npm', 'a'], (err, res) => { if (err) throw err @@ -292,7 +294,7 @@ test('completion of invalid command name does nothing', t => { output.length = 0 }) - completion(['npm', 'compute'], (err, res) => { + completion.exec(['npm', 'compute'], (err, res) => { if (err) throw err @@ -314,7 +316,7 @@ test('handles async completion function', t => { output.length = 0 }) - completion(['npm', 'promise', ''], (err, res) => { + completion.exec(['npm', 'promise', ''], (err, res) => { if (err) throw err @@ -343,7 +345,7 @@ test('completion triggers command completions', t => { output.length = 0 }) - completion(['npm', 'access', ''], (err, res) => { + completion.exec(['npm', 'access', ''], (err, res) => { if (err) throw err @@ -372,7 +374,7 @@ test('completion triggers filtered command completions', t => { output.length = 0 }) - completion(['npm', 'access', 'p'], (err, res) => { + completion.exec(['npm', 'access', 'p'], (err, res) => { if (err) throw err @@ -401,7 +403,7 @@ test('completions for commands that return nested arrays are joined', t => { output.length = 0 }) - completion(['npm', 'completion', ''], (err, res) => { + completion.exec(['npm', 'completion', ''], (err, res) => { if (err) throw err @@ -430,7 +432,7 @@ test('completions for commands that return nothing work correctly', t => { output.length = 0 }) - completion(['npm', 'donothing', ''], (err, res) => { + completion.exec(['npm', 'donothing', ''], (err, res) => { if (err) throw err @@ -459,7 +461,7 @@ test('completions for commands that return a single item work correctly', t => { output.length = 0 }) - completion(['npm', 'driveaboat', ''], (err, res) => { + completion.exec(['npm', 'driveaboat', ''], (err, res) => { if (err) throw err @@ -489,7 +491,7 @@ test('command completion for commands with no completion return no results', t = }) // quotes around adduser are to ensure coverage when unescaping commands - completion(['npm', '\'adduser\'', ''], (err, res) => { + completion.exec(['npm', '\'adduser\'', ''], (err, res) => { if (err) throw err @@ -520,7 +522,7 @@ test('command completion errors propagate', t => { accessCompletionError = false }) - completion(['npm', 'access', ''], (err, res) => { + completion.exec(['npm', 'access', ''], (err, res) => { t.match(err, /access completion failed/, 'catches the appropriate error') t.strictSame(npmConfig, { argv: { @@ -547,7 +549,7 @@ test('completion can complete flags', t => { output.length = 0 }) - completion(['npm', 'install', '--'], (err, res) => { + completion.exec(['npm', 'install', '--'], (err, res) => { if (err) throw err @@ -570,7 +572,7 @@ test('double dashes escape from flag completion', t => { output.length = 0 }) - completion(['npm', '--', 'install', '--'], (err, res) => { + completion.exec(['npm', '--', 'install', '--'], (err, res) => { if (err) throw err @@ -593,7 +595,7 @@ test('completion cannot complete options that take a value in mid-command', t => output.length = 0 }) - completion(['npm', '--registry', 'install'], (err, res) => { + completion.exec(['npm', '--registry', 'install'], (err, res) => { if (err) throw err diff --git a/deps/npm/test/lib/config.js b/deps/npm/test/lib/config.js index edaa6486cd..c2420aefb4 100644 --- a/deps/npm/test/lib/config.js +++ b/deps/npm/test/lib/config.js @@ -68,17 +68,17 @@ const usageUtil = () => 'usage instructions' const mocks = { '../../lib/utils/config.js': { defaults, types }, - '../../lib/npm.js': npm, '../../lib/utils/output.js': msg => { result = msg }, '../../lib/utils/usage.js': usageUtil, } -const config = requireInject('../../lib/config.js', mocks) +const Config = requireInject('../../lib/config.js', mocks) +const config = new Config(npm) t.test('config no args', t => { - config([], (err) => { + config.exec([], (err) => { t.match(err, /usage instructions/, 'should not error out on empty locations') t.end() }) @@ -94,7 +94,7 @@ t.test('config list', t => { delete npm.config.find }) - config(['list'], (err) => { + config.exec(['list'], (err) => { t.ifError(err, 'npm config list') t.matchSnapshot(result, 'should list configs') }) @@ -120,7 +120,7 @@ t.test('config list overrides', t => { delete npm.config.find }) - config(['list'], (err) => { + config.exec(['list'], (err) => { t.ifError(err, 'npm config list') t.matchSnapshot(result, 'should list overridden configs') }) @@ -138,7 +138,7 @@ t.test('config list --long', t => { result = '' }) - config(['list'], (err) => { + config.exec(['list'], (err) => { t.ifError(err, 'npm config list --long') t.matchSnapshot(result, 'should list all configs') }) @@ -163,7 +163,7 @@ t.test('config list --json', t => { result = '' }) - config(['list'], (err) => { + config.exec(['list'], (err) => { t.ifError(err, 'npm config list --json') t.deepEqual( JSON.parse(result), @@ -179,7 +179,7 @@ t.test('config list --json', t => { }) t.test('config delete no args', t => { - config(['delete'], (err) => { + config.exec(['delete'], (err) => { t.equal( err.message, 'usage instructions', @@ -202,7 +202,7 @@ t.test('config delete key', t => { t.equal(where, 'user', 'should save user config post-delete') } - config(['delete', 'foo'], (err) => { + config.exec(['delete', 'foo'], (err) => { t.ifError(err, 'npm config delete key') }) @@ -229,7 +229,7 @@ t.test('config delete multiple key', t => { t.equal(where, 'user', 'should save user config post-delete') } - config(['delete', 'foo', 'bar'], (err) => { + config.exec(['delete', 'foo', 'bar'], (err) => { t.ifError(err, 'npm config delete keys') }) @@ -252,7 +252,7 @@ t.test('config delete key --global', t => { } flatOptions.global = true - config(['delete', 'foo'], (err) => { + config.exec(['delete', 'foo'], (err) => { t.ifError(err, 'npm config delete key --global') }) @@ -264,7 +264,7 @@ t.test('config delete key --global', t => { }) t.test('config set no args', t => { - config(['set'], (err) => { + config.exec(['set'], (err) => { t.equal( err.message, 'usage instructions', @@ -287,7 +287,7 @@ t.test('config set key', t => { t.equal(where, 'user', 'should save user config') } - config(['set', 'foo', 'bar'], (err) => { + config.exec(['set', 'foo', 'bar'], (err) => { t.ifError(err, 'npm config set key') }) @@ -310,7 +310,7 @@ t.test('config set key=val', t => { t.equal(where, 'user', 'should save user config') } - config(['set', 'foo=bar'], (err) => { + config.exec(['set', 'foo=bar'], (err) => { t.ifError(err, 'npm config set key') }) @@ -341,7 +341,7 @@ t.test('config set multiple keys', t => { t.equal(where, 'user', 'should save user config') } - config(['set', ...args], (err) => { + config.exec(['set', ...args], (err) => { t.ifError(err, 'npm config set key') }) @@ -364,7 +364,7 @@ t.test('config set key to empty value', t => { t.equal(where, 'user', 'should save user config') } - config(['set', 'foo'], (err) => { + config.exec(['set', 'foo'], (err) => { t.ifError(err, 'npm config set key to empty value') }) @@ -392,7 +392,7 @@ t.test('config set invalid key', t => { delete npm.log.warn }) - config(['set', 'foo', 'bar'], (err) => { + config.exec(['set', 'foo', 'bar'], (err) => { t.ifError(err, 'npm config set invalid key') }) }) @@ -411,7 +411,7 @@ t.test('config set key --global', t => { } flatOptions.global = true - config(['set', 'foo', 'bar'], (err) => { + config.exec(['set', 'foo', 'bar'], (err) => { t.ifError(err, 'npm config set key --global') }) @@ -432,7 +432,7 @@ t.test('config get no args', t => { delete npm.config.find }) - config(['get'], (err) => { + config.exec(['get'], (err) => { t.ifError(err, 'npm config get no args') t.matchSnapshot(result, 'should list configs on config get no args') }) @@ -451,7 +451,7 @@ t.test('config get key', t => { throw new Error('should not save') } - config(['get', 'foo'], (err) => { + config.exec(['get', 'foo'], (err) => { t.ifError(err, 'npm config get key') }) @@ -479,7 +479,7 @@ t.test('config get multiple keys', t => { throw new Error('should not save') } - config(['get', 'foo', 'bar'], (err) => { + config.exec(['get', 'foo', 'bar'], (err) => { t.ifError(err, 'npm config get multiple keys') t.equal(result, 'foo=asdf\nbar=asdf') }) @@ -492,7 +492,7 @@ t.test('config get multiple keys', t => { }) t.test('config get private key', t => { - config(['get', '//private-reg.npmjs.org/:_authThoken'], (err) => { + config.exec(['get', '//private-reg.npmjs.org/:_authThoken'], (err) => { t.match( err, /The \/\/private-reg.npmjs.org\/:_authThoken option is protected, and cannot be retrieved in this way/, @@ -538,16 +538,19 @@ sign-git-commit=true` }, }, } - const config = requireInject('../../lib/config.js', editMocks) - config(['edit'], (err) => { + const Config = requireInject('../../lib/config.js', editMocks) + const config = new Config(npm) + + config.exec(['edit'], (err) => { t.ifError(err, 'npm config edit') // test no config file result editMocks.fs.readFile = (p, e, cb) => { cb(new Error('ERR')) } - const config = requireInject('../../lib/config.js', editMocks) - config(['edit'], (err) => { + const Config = requireInject('../../lib/config.js', editMocks) + const config = new Config(npm) + config.exec(['edit'], (err) => { t.ifError(err, 'npm config edit') }) }) @@ -594,8 +597,9 @@ t.test('config edit --global', t => { }, }, } - const config = requireInject('../../lib/config.js', editMocks) - config(['edit'], (err) => { + const Config = requireInject('../../lib/config.js', editMocks) + const config = new Config(npm) + config.exec(['edit'], (err) => { t.match(err, /exited with code: 137/, 'propagated exit code from editor') }) diff --git a/deps/npm/test/lib/dedupe.js b/deps/npm/test/lib/dedupe.js index b14185525b..3e8b2f4c01 100644 --- a/deps/npm/test/lib/dedupe.js +++ b/deps/npm/test/lib/dedupe.js @@ -1,29 +1,29 @@ const { test } = require('tap') const requireInject = require('require-inject') -test('should throw in global mode', (t) => { - const dedupe = requireInject('../../lib/dedupe.js', { - '../../lib/npm.js': { - flatOptions: { - global: true, - }, +const npm = (base) => { + const config = base.config + return { + ...base, + flatOptions: { dryRun: false }, + config: { + get: (k) => config[k], }, - }) + } +} + +test('should throw in global mode', (t) => { + const Dedupe = requireInject('../../lib/dedupe.js') + const dedupe = new Dedupe(npm({ config: { global: true }})) - dedupe([], er => { + dedupe.exec([], er => { t.match(er, { code: 'EDEDUPEGLOBAL' }, 'throws EDEDUPEGLOBAL') t.end() }) }) test('should remove dupes using Arborist', (t) => { - const dedupe = requireInject('../../lib/dedupe.js', { - '../../lib/npm.js': { - prefix: 'foo', - flatOptions: { - dryRun: 'false', - }, - }, + const Dedupe = requireInject('../../lib/dedupe.js', { '@npmcli/arborist': function (args) { t.ok(args, 'gets options object') t.ok(args.path, 'gets path option') @@ -32,11 +32,17 @@ test('should remove dupes using Arborist', (t) => { t.ok(true, 'dedupe is called') } }, - '../../lib/utils/reify-finish.js': (arb) => { + '../../lib/utils/reify-finish.js': (npm, arb) => { t.ok(arb, 'gets arborist tree') }, }) - dedupe({ dryRun: true }, er => { + const dedupe = new Dedupe(npm({ + prefix: 'foo', + config: { + 'dry-run': 'true', + }, + })) + dedupe.exec([], er => { if (er) throw er t.ok(true, 'callback is called') @@ -45,20 +51,20 @@ test('should remove dupes using Arborist', (t) => { }) test('should remove dupes using Arborist - no arguments', (t) => { - const dedupe = requireInject('../../lib/dedupe.js', { - '../../lib/npm.js': { - prefix: 'foo', - flatOptions: { - dryRun: 'true', - }, - }, + const Dedupe = requireInject('../../lib/dedupe.js', { '@npmcli/arborist': function (args) { t.ok(args.dryRun, 'gets dryRun from flatOptions') this.dedupe = () => {} }, '../../lib/utils/reify-output.js': () => {}, }) - dedupe(null, () => { + const dedupe = new Dedupe(npm({ + prefix: 'foo', + config: { + 'dry-run': true, + }, + })) + dedupe.exec(null, () => { t.end() }) }) diff --git a/deps/npm/test/lib/deprecate.js b/deps/npm/test/lib/deprecate.js index fd563de120..03100166a0 100644 --- a/deps/npm/test/lib/deprecate.js +++ b/deps/npm/test/lib/deprecate.js @@ -18,10 +18,7 @@ npmFetch.json = async (uri, opts) => { } } -const deprecate = requireInject('../../lib/deprecate.js', { - '../../lib/npm.js': { - flatOptions: { registry: 'https://registry.npmjs.org' }, - }, +const Deprecate = requireInject('../../lib/deprecate.js', { '../../lib/utils/get-identity.js': async () => getIdentityImpl(), '../../lib/utils/otplease.js': async (opts, fn) => fn(opts), libnpmaccess: { @@ -30,16 +27,19 @@ const deprecate = requireInject('../../lib/deprecate.js', { 'npm-registry-fetch': npmFetch, }) +const deprecate = new Deprecate({ + flatOptions: { registry: 'https://registry.npmjs.org' }, +}) + test('completion', async t => { const defaultIdentityImpl = getIdentityImpl t.teardown(() => { getIdentityImpl = defaultIdentityImpl }) - const { completion } = deprecate - const testComp = async (argv, expect) => { - const res = await completion({ conf: { argv: { remain: argv } } }) + const res = + await deprecate.completion({ conf: { argv: { remain: argv } } }) t.strictSame(res, expect, `completion: ${argv}`) } @@ -59,21 +59,21 @@ test('completion', async t => { }) test('no args', t => { - deprecate([], (err) => { + deprecate.exec([], (err) => { t.match(err, /Usage: npm deprecate/, 'logs usage') t.end() }) }) test('only one arg', t => { - deprecate(['foo'], (err) => { + deprecate.exec(['foo'], (err) => { t.match(err, /Usage: npm deprecate/, 'logs usage') t.end() }) }) test('invalid semver range', t => { - deprecate(['foo@notaversion', 'this will fail'], (err) => { + deprecate.exec(['foo@notaversion', 'this will fail'], (err) => { t.match(err, /invalid version range/, 'logs semver error') t.end() }) @@ -84,7 +84,7 @@ test('deprecates given range', t => { npmFetchBody = null }) - deprecate(['foo@1.0.0', 'this version is deprecated'], (err) => { + deprecate.exec(['foo@1.0.0', 'this version is deprecated'], (err) => { if (err) throw err @@ -110,7 +110,7 @@ test('deprecates all versions when no range is specified', t => { npmFetchBody = null }) - deprecate(['foo', 'this version is deprecated'], (err) => { + deprecate.exec(['foo', 'this version is deprecated'], (err) => { if (err) throw err diff --git a/deps/npm/test/lib/diff.js b/deps/npm/test/lib/diff.js index 926c54fdf1..2af3bd69bc 100644 --- a/deps/npm/test/lib/diff.js +++ b/deps/npm/test/lib/diff.js @@ -28,7 +28,6 @@ const mocks = { npmlog: { info: noop, verbose: noop }, libnpmdiff: (...args) => libnpmdiff(...args), 'npm-registry-fetch': async () => ({}), - '../../lib/npm.js': npm, '../../lib/utils/output.js': noop, '../../lib/utils/read-local-package.js': async () => rlp(), '../../lib/utils/usage.js': () => 'usage instructions', @@ -42,7 +41,8 @@ t.afterEach(cb => { cb() }) -const diff = requireInject('../../lib/diff.js', mocks) +const Diff = requireInject('../../lib/diff.js', mocks) +const diff = new Diff(npm) t.test('no args', t => { t.test('in a project dir', t => { @@ -56,7 +56,7 @@ t.test('no args', t => { } npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -65,7 +65,7 @@ t.test('no args', t => { t.test('no args, missing package.json name in cwd', t => { rlp = () => undefined - diff([], err => { + diff.exec([], err => { t.match( err, /Needs multiple arguments to compare or run from a project dir./, @@ -80,7 +80,7 @@ t.test('no args', t => { throw new Error('ERR') } - diff([], err => { + diff.exec([], err => { t.match( err, /Needs multiple arguments to compare or run from a project dir./, @@ -106,7 +106,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['foo@1.0.0'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -120,7 +120,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['foo@1.0.0'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { t.match( err, /Needs multiple arguments to compare or run from a project dir./, @@ -142,7 +142,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['foo@~1.0.0'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -160,7 +160,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['2.1.4'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -172,7 +172,7 @@ t.test('single arg', t => { } npm.flatOptions.diff = ['2.1.4'] - diff([], err => { + diff.exec([], err => { t.match( err, /Needs multiple arguments to compare or run from a project dir./, @@ -200,7 +200,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['2.1.4'] npm.flatOptions.prefix = path - diff(['./foo.js', './bar.js'], err => { + diff.exec(['./foo.js', './bar.js'], err => { if (err) throw err }) @@ -224,7 +224,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['bar@1.0.0'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -250,7 +250,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['simple-output'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -264,7 +264,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['bar'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { t.match( err, /Needs multiple arguments to compare or run from a project dir./, @@ -297,7 +297,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['bar'] npm.flatOptions.prefix = path - const diff = requireInject('../../lib/diff.js', { + const Diff = requireInject('../../lib/diff.js', { ...mocks, pacote: { packument: (spec) => { @@ -313,8 +313,9 @@ t.test('single arg', t => { t.equal(b, 'bar@1.8.10', 'should have possible semver range spec') }, }) + const diff = new Diff(npm) - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -359,7 +360,7 @@ t.test('single arg', t => { npm.flatOptions.prefix = resolve(path, 'project') npm.globalDir = resolve(path, 'globalDir/lib/node_modules') - const diff = requireInject('../../lib/diff.js', { + const Diff = requireInject('../../lib/diff.js', { ...mocks, pacote: { packument: (spec) => { @@ -375,8 +376,9 @@ t.test('single arg', t => { t.equal(b, 'lorem@2.1.0', 'should have possible semver range spec') }, }) + const diff = new Diff(npm) - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -410,7 +412,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['bar@2.0.0'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -445,7 +447,7 @@ t.test('single arg', t => { }), }) - const diff = requireInject('../../lib/diff.js', { + const Diff = requireInject('../../lib/diff.js', { ...mocks, '../../lib/utils/read-local-package.js': async () => 'my-project', pacote: { @@ -462,11 +464,12 @@ t.test('single arg', t => { t.equal(b, 'lorem@2.2.2', 'should have expected target spec') }, }) + const diff = new Diff(npm) npm.flatOptions.diff = ['lorem'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -481,7 +484,7 @@ t.test('single arg', t => { }), }) - const diff = requireInject('../../lib/diff.js', { + const Diff = requireInject('../../lib/diff.js', { ...mocks, '../../lib/utils/read-local-package.js': async () => 'my-project', '@npmcli/arborist': class { @@ -494,11 +497,12 @@ t.test('single arg', t => { t.equal(b, `file:${path}`, 'should target current cwd') }, }) + const diff = new Diff(npm) npm.flatOptions.diff = ['lorem'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -517,7 +521,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['bar'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -535,7 +539,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['my-project'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -553,7 +557,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['/path/to/other-dir'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -564,7 +568,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['git+https://github.com/user/foo'] - diff([], err => { + diff.exec([], err => { t.match( err, /Spec type not supported./, @@ -588,7 +592,7 @@ t.test('first arg is a qualified spec', t => { } npm.flatOptions.diff = ['bar@1.0.0', 'bar@^2.0.0'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -622,7 +626,7 @@ t.test('first arg is a qualified spec', t => { npm.flatOptions.prefix = path npm.flatOptions.diff = ['bar@2.0.0', 'bar'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -638,7 +642,7 @@ t.test('first arg is a qualified spec', t => { t.equal(b, 'bar@2.0.0', 'should use name from first arg') } - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -653,7 +657,7 @@ t.test('first arg is a qualified spec', t => { } npm.flatOptions.diff = ['bar@1.0.0', 'bar-fork'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -691,7 +695,7 @@ t.test('first arg is a known dependency name', t => { npm.flatOptions.prefix = path npm.flatOptions.diff = ['bar', 'bar@2.0.0'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -731,7 +735,7 @@ t.test('first arg is a known dependency name', t => { npm.flatOptions.prefix = path npm.flatOptions.diff = ['bar', 'bar-fork'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -765,7 +769,7 @@ t.test('first arg is a known dependency name', t => { npm.flatOptions.prefix = path npm.flatOptions.diff = ['bar', '2.0.0'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -799,7 +803,7 @@ t.test('first arg is a known dependency name', t => { npm.flatOptions.prefix = path npm.flatOptions.diff = ['bar', 'bar-fork'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -819,7 +823,7 @@ t.test('first arg is a valid semver range', t => { t.equal(b, 'bar@2.0.0', 'should use expected spec') } - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -853,7 +857,7 @@ t.test('first arg is a valid semver range', t => { npm.flatOptions.prefix = path npm.flatOptions.diff = ['1.0.0', 'bar'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -869,7 +873,7 @@ t.test('first arg is a valid semver range', t => { } npm.flatOptions.diff = ['1.0.0', '2.0.0'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -883,7 +887,7 @@ t.test('first arg is a valid semver range', t => { npm.flatOptions.diff = ['1.0.0', '2.0.0'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { t.match( err, /Needs to be run from a project dir in order to diff two versions./, @@ -903,7 +907,7 @@ t.test('first arg is a valid semver range', t => { } npm.flatOptions.diff = ['1.0.0', 'bar'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -918,7 +922,7 @@ t.test('first arg is a valid semver range', t => { }), }) - const diff = requireInject('../../lib/diff.js', { + const Diff = requireInject('../../lib/diff.js', { ...mocks, '../../lib/utils/read-local-package.js': async () => 'my-project', '@npmcli/arborist': class { @@ -931,11 +935,12 @@ t.test('first arg is a valid semver range', t => { t.equal(b, 'lorem@2.0.0', 'should target expected spec') }, }) + const diff = new Diff(npm) npm.flatOptions.diff = ['1.0.0', 'lorem@2.0.0'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -946,16 +951,17 @@ t.test('first arg is a valid semver range', t => { t.test('first arg is an unknown dependency name', t => { t.test('second arg is a qualified spec', t => { - t.plan(3) + t.plan(4) libnpmdiff = async ([a, b], opts) => { t.equal(a, 'bar@latest', 'should set expected first spec') t.equal(b, 'bar@2.0.0', 'should set expected second spec') t.match(opts, npm.flatOptions, 'should forward flat options') + t.match(opts, { where: '.' }, 'should forward pacote options') } npm.flatOptions.diff = ['bar', 'bar@2.0.0'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -989,7 +995,7 @@ t.test('first arg is an unknown dependency name', t => { npm.flatOptions.prefix = path npm.flatOptions.diff = ['bar-fork', 'bar'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -1004,7 +1010,7 @@ t.test('first arg is an unknown dependency name', t => { } npm.flatOptions.diff = ['bar', '^1.0.0'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -1019,7 +1025,7 @@ t.test('first arg is an unknown dependency name', t => { } npm.flatOptions.diff = ['bar', 'bar-fork'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -1040,7 +1046,7 @@ t.test('first arg is an unknown dependency name', t => { npm.flatOptions.diff = ['bar', 'bar-fork'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -1062,7 +1068,7 @@ t.test('various options', t => { }, 'should forward nameOnly=true option') } - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -1085,7 +1091,7 @@ t.test('various options', t => { }, 'should forward diffFiles values') } - diff(['./foo.js', './bar.js'], err => { + diff.exec(['./foo.js', './bar.js'], err => { if (err) throw err }) @@ -1109,7 +1115,7 @@ t.test('various options', t => { } npm.flatOptions.prefix = path - diff(['./foo.js', './bar.js'], err => { + diff.exec(['./foo.js', './bar.js'], err => { if (err) throw err }) @@ -1137,7 +1143,7 @@ t.test('various options', t => { }, 'should forward diff options') } - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -1148,7 +1154,7 @@ t.test('various options', t => { t.test('too many args', t => { npm.flatOptions.diff = ['a', 'b', 'c'] - diff([], err => { + diff.exec([], err => { t.match( err, /Can't use more than two --diff arguments./, diff --git a/deps/npm/test/lib/dist-tag.js b/deps/npm/test/lib/dist-tag.js index c189352302..b761fb103c 100644 --- a/deps/npm/test/lib/dist-tag.js +++ b/deps/npm/test/lib/dist-tag.js @@ -48,7 +48,7 @@ const logger = (...msgs) => { log += '\n' } -const distTag = requireInject('../../lib/dist-tag.js', { +const DistTag = requireInject('../../lib/dist-tag.js', { npmlog: { error: logger, info: logger, @@ -58,26 +58,27 @@ const distTag = requireInject('../../lib/dist-tag.js', { get 'npm-registry-fetch' () { return npmRegistryFetchMock }, - '../../lib/npm.js': { - flatOptions: _flatOptions, - config: { - get (key) { - return _flatOptions[key] - }, - }, - }, '../../lib/utils/output.js': msg => { result = msg }, }) +const distTag = new DistTag({ + flatOptions: _flatOptions, + config: { + get (key) { + return _flatOptions[key] + }, + }, +}) + test('ls in current package', (t) => { prefix = t.testdir({ 'package.json': JSON.stringify({ name: '@scoped/pkg', }), }) - distTag(['ls'], (err) => { + distTag.exec(['ls'], (err) => { t.ifError(err, 'npm dist-tags ls') t.matchSnapshot( result, @@ -95,7 +96,7 @@ test('no args in current package', (t) => { name: '@scoped/pkg', }), }) - distTag([], (err) => { + distTag.exec([], (err) => { t.ifError(err, 'npm dist-tags ls') t.matchSnapshot( result, @@ -109,7 +110,7 @@ test('no args in current package', (t) => { test('borked cmd usage', (t) => { prefix = t.testdir({}) - distTag(['borked', '@scoped/pkg'], (err) => { + distTag.exec(['borked', '@scoped/pkg'], (err) => { t.matchSnapshot(err, 'should show usage error') result = '' log = '' @@ -119,7 +120,7 @@ test('borked cmd usage', (t) => { test('ls on named package', (t) => { prefix = t.testdir({}) - distTag(['ls', '@scoped/another'], (err) => { + distTag.exec(['ls', '@scoped/another'], (err) => { t.ifError(err, 'npm dist-tags ls') t.matchSnapshot( result, @@ -133,7 +134,7 @@ test('ls on named package', (t) => { test('ls on missing package', (t) => { prefix = t.testdir({}) - distTag(['ls', 'foo'], (err) => { + distTag.exec(['ls', 'foo'], (err) => { t.matchSnapshot( log, 'should log no dist-tag found msg' @@ -154,7 +155,7 @@ test('ls on missing name in current package', (t) => { version: '1.0.0', }), }) - distTag(['ls'], (err) => { + distTag.exec(['ls'], (err) => { t.matchSnapshot( err, 'should throw usage error message' @@ -167,7 +168,7 @@ test('ls on missing name in current package', (t) => { test('only named package arg', (t) => { prefix = t.testdir({}) - distTag(['@scoped/another'], (err) => { + distTag.exec(['@scoped/another'], (err) => { t.ifError(err, 'npm dist-tags ls') t.matchSnapshot( result, @@ -186,7 +187,7 @@ test('add new tag', (t) => { t.equal(opts.body, '7.7.7', 'should point to expected version') } prefix = t.testdir({}) - distTag(['add', '@scoped/another@7.7.7', 'c'], (err) => { + distTag.exec(['add', '@scoped/another@7.7.7', 'c'], (err) => { t.ifError(err, 'npm dist-tags add') t.matchSnapshot( result, @@ -201,7 +202,7 @@ test('add new tag', (t) => { test('add using valid semver range as name', (t) => { prefix = t.testdir({}) - distTag(['add', '@scoped/another@7.7.7', '1.0.0'], (err) => { + distTag.exec(['add', '@scoped/another@7.7.7', '1.0.0'], (err) => { t.match( err, /Error: Tag name must not be a valid SemVer range: 1.0.0/, @@ -219,7 +220,7 @@ test('add using valid semver range as name', (t) => { test('add missing args', (t) => { prefix = t.testdir({}) - distTag(['add', '@scoped/another@7.7.7'], (err) => { + distTag.exec(['add', '@scoped/another@7.7.7'], (err) => { t.matchSnapshot(err, 'should exit usage error message') result = '' log = '' @@ -229,7 +230,7 @@ test('add missing args', (t) => { test('add missing pkg name', (t) => { prefix = t.testdir({}) - distTag(['add', null], (err) => { + distTag.exec(['add', null], (err) => { t.matchSnapshot(err, 'should exit usage error message') result = '' log = '' @@ -239,7 +240,7 @@ test('add missing pkg name', (t) => { test('set existing version', (t) => { prefix = t.testdir({}) - distTag(['set', '@scoped/another@0.6.0', 'b'], (err) => { + distTag.exec(['set', '@scoped/another@0.6.0', 'b'], (err) => { t.ifError(err, 'npm dist-tags set') t.matchSnapshot( log, @@ -256,7 +257,7 @@ test('remove existing tag', (t) => { t.equal(opts.method, 'DELETE', 'should trigger request to remove tag') } prefix = t.testdir({}) - distTag(['rm', '@scoped/another', 'c'], (err) => { + distTag.exec(['rm', '@scoped/another', 'c'], (err) => { t.ifError(err, 'npm dist-tags rm') t.matchSnapshot(log, 'should log remove info') t.matchSnapshot(result, 'should return success msg') @@ -269,7 +270,7 @@ test('remove existing tag', (t) => { test('remove non-existing tag', (t) => { prefix = t.testdir({}) - distTag(['rm', '@scoped/another', 'nonexistent'], (err) => { + distTag.exec(['rm', '@scoped/another', 'nonexistent'], (err) => { t.match( err, /Error: nonexistent is not a dist-tag on @scoped\/another/, @@ -284,7 +285,7 @@ test('remove non-existing tag', (t) => { test('remove missing pkg name', (t) => { prefix = t.testdir({}) - distTag(['rm', null], (err) => { + distTag.exec(['rm', null], (err) => { t.matchSnapshot(err, 'should exit usage error message') result = '' log = '' diff --git a/deps/npm/test/lib/docs.js b/deps/npm/test/lib/docs.js index 8a59ed7cc0..a7325738ba 100644 --- a/deps/npm/test/lib/docs.js +++ b/deps/npm/test/lib/docs.js @@ -33,17 +33,18 @@ const pacote = { // keep a tally of which urls got opened const opened = {} -const openUrl = (url, errMsg, cb) => { +const openUrl = async (npm, url, errMsg) => { opened[url] = opened[url] || 0 opened[url]++ - process.nextTick(cb) } -const docs = requireInject('../../lib/docs.js', { +const Docs = requireInject('../../lib/docs.js', { pacote, '../../lib/utils/open-url.js': openUrl, }) +const docs = new Docs({ flatOptions: {} }) + t.test('open docs urls', t => { const expect = { nodocs: 'https://www.npmjs.com/package/nodocs', @@ -56,7 +57,7 @@ t.test('open docs urls', t => { t.plan(keys.length) keys.forEach(pkg => { t.test(pkg, t => { - docs([pkg], (er) => { + docs.exec([pkg], (er) => { if (er) throw er const url = expect[pkg] @@ -68,7 +69,7 @@ t.test('open docs urls', t => { }) t.test('open default package if none specified', t => { - docs([], (er) => { + docs.exec([], (er) => { if (er) throw er t.equal(opened['https://example.com'], 2, 'opened expected url', {opened}) diff --git a/deps/npm/test/lib/doctor.js b/deps/npm/test/lib/doctor.js index f5e6fd062a..eaa7ad72df 100644 --- a/deps/npm/test/lib/doctor.js +++ b/deps/npm/test/lib/doctor.js @@ -120,18 +120,18 @@ const cacache = { }, } -const doctor = requireInject('../../lib/doctor.js', { +const Doctor = requireInject('../../lib/doctor.js', { '../../lib/utils/is-windows.js': false, '../../lib/utils/ping.js': ping, '../../lib/utils/output.js': (data) => { output.push(data) }, - '../../lib/npm.js': npm, cacache, pacote, 'make-fetch-happen': fetch, which, }) +const doctor = new Doctor(npm) const origVersion = process.version test('node versions', t => { @@ -162,7 +162,7 @@ test('node versions', t => { clearLogs() }) - doctor([], (err) => { + doctor.exec([], (err) => { if (err) { st.fail(output) return st.end() @@ -211,7 +211,7 @@ test('node versions', t => { clearLogs() }) - doctor([], (err) => { + doctor.exec([], (err) => { if (err) { st.fail(err) return st.end() @@ -255,7 +255,7 @@ test('node versions', t => { clearLogs() }) - doctor([], (err) => { + doctor.exec([], (err) => { st.match(err, /Some problems found/, 'detected the ping error') st.match(logs, { checkPing: { finished: true }, @@ -282,18 +282,18 @@ test('node versions', t => { }) vt.test('npm doctor skips some tests in windows', st => { - const winDoctor = requireInject('../../lib/doctor.js', { + const WinDoctor = requireInject('../../lib/doctor.js', { '../../lib/utils/is-windows.js': true, '../../lib/utils/ping.js': ping, '../../lib/utils/output.js': (data) => { output.push(data) }, - '../../lib/npm.js': npm, cacache, pacote, 'make-fetch-happen': fetch, which, }) + const winDoctor = new WinDoctor(npm) const dir = st.testdir() npm.cache = npm.flatOptions.cache = dir @@ -312,7 +312,7 @@ test('node versions', t => { clearLogs() }) - winDoctor([], (err) => { + winDoctor.exec([], (err) => { if (err) { st.fail(output) return st.end() @@ -360,7 +360,7 @@ test('node versions', t => { clearLogs() }) - doctor([], (err) => { + doctor.exec([], (err) => { st.match(err, /Some problems found/, 'detected the ping error') st.match(logs, { checkPing: { finished: true }, @@ -409,7 +409,7 @@ test('node versions', t => { clearLogs() }) - doctor([], (err) => { + doctor.exec([], (err) => { st.match(err, /Some problems found/, 'detected the ping error') st.match(logs, { checkPing: { finished: true }, @@ -458,7 +458,7 @@ test('node versions', t => { clearLogs() }) - doctor([], (err) => { + doctor.exec([], (err) => { st.match(err, /Some problems found/, 'detected the out of date npm') st.match(logs, { checkPing: { finished: true }, @@ -487,7 +487,7 @@ test('node versions', t => { const dir = st.testdir({ cache: { one: 'one', - link: st.fixture('symlink', './one'), + link: st.fixture('symlink', './baddir'), unreadable: 'unreadable', baddir: {}, }, @@ -563,19 +563,19 @@ test('node versions', t => { } } - const doctor = requireInject('../../lib/doctor.js', { + const Doctor = requireInject('../../lib/doctor.js', { '../../lib/utils/is-windows.js': false, '../../lib/utils/ping.js': ping, '../../lib/utils/output.js': (data) => { output.push(data) }, - '../../lib/npm.js': npm, cacache, pacote, 'make-fetch-happen': fetch, which, fs, }) + const doctor = new Doctor(npm) // it's necessary to allow tests in node 10.x to not mark 12.x as lted npm.cache = npm.flatOptions.cache = join(dir, 'cache') @@ -600,7 +600,7 @@ test('node versions', t => { clearLogs() }) - doctor([], (err) => { + doctor.exec([], (err) => { st.match(err, /Some problems found/, 'identified problems') st.match(logs, { checkPing: { finished: true }, @@ -653,7 +653,7 @@ test('node versions', t => { clearLogs() }) - doctor([], (err) => { + doctor.exec([], (err) => { st.match(err, /Some problems found/, 'detected the missing git') st.match(logs, { checkPing: { finished: true }, @@ -706,7 +706,7 @@ test('node versions', t => { clearLogs() }) - doctor([], (err) => { + doctor.exec([], (err) => { // cache verification problems get fixed and so do not throw an error if (err) { st.fail(output) @@ -765,7 +765,7 @@ test('node versions', t => { clearLogs() }) - doctor([], (err) => { + doctor.exec([], (err) => { // cache verification problems get fixed and so do not throw an error if (err) { st.fail(output) @@ -823,7 +823,7 @@ test('node versions', t => { clearLogs() }) - doctor([], (err) => { + doctor.exec([], (err) => { // cache verification problems get fixed and so do not throw an error if (err) { st.fail(output) @@ -878,7 +878,7 @@ test('node versions', t => { clearLogs() }) - doctor([], (err) => { + doctor.exec([], (err) => { // cache verification problems get fixed and so do not throw an error st.match(err, /Some problems found/, 'detected the non-default registry') st.match(logs, { @@ -942,7 +942,7 @@ test('outdated node version', vt => { clearLogs() }) - doctor([], (err) => { + doctor.exec([], (err) => { st.match(err, /Some problems found/, 'detected the out of date nodejs') st.match(logs, { checkPing: { finished: true }, diff --git a/deps/npm/test/lib/edit.js b/deps/npm/test/lib/edit.js index 0d3bbb4c57..acf03fa438 100644 --- a/deps/npm/test/lib/edit.js +++ b/deps/npm/test/lib/edit.js @@ -23,6 +23,7 @@ const childProcess = { } let rebuildArgs = null +let rebuildFail = null let EDITOR = 'vim' const npm = { config: { @@ -32,17 +33,17 @@ const npm = { commands: { rebuild: (args, cb) => { rebuildArgs = args - return cb() + return cb(rebuildFail) }, }, } const gracefulFs = require('graceful-fs') -const edit = requireInject('../../lib/edit.js', { - '../../lib/npm.js': npm, +const Edit = requireInject('../../lib/edit.js', { child_process: childProcess, 'graceful-fs': gracefulFs, }) +const edit = new Edit(npm) test('npm edit', t => { t.teardown(() => { @@ -52,7 +53,7 @@ test('npm edit', t => { editorOpts = null }) - return edit(['semver'], (err) => { + return edit.exec(['semver'], (err) => { if (err) throw err @@ -65,6 +66,27 @@ test('npm edit', t => { }) }) +test('rebuild fails', t => { + t.teardown(() => { + rebuildFail = null + rebuildArgs = null + editorBin = null + editorArgs = null + editorOpts = null + }) + + rebuildFail = new Error('test error') + return edit.exec(['semver'], (err) => { + const path = resolve(__dirname, '../../node_modules/semver') + t.strictSame(editorBin, EDITOR, 'used the correct editor') + t.strictSame(editorArgs, [path], 'edited the correct directory') + t.strictSame(editorOpts, { stdio: 'inherit' }, 'passed the correct opts') + t.strictSame(rebuildArgs, [path], 'passed the correct path to rebuild') + t.match(err, { message: 'test error' }) + t.end() + }) +}) + test('npm edit editor has flags', t => { EDITOR = 'code -w' t.teardown(() => { @@ -75,7 +97,7 @@ test('npm edit editor has flags', t => { EDITOR = 'vim' }) - return edit(['semver'], (err) => { + return edit.exec(['semver'], (err) => { if (err) throw err @@ -89,7 +111,7 @@ test('npm edit editor has flags', t => { }) test('npm edit no args', t => { - return edit([], (err) => { + return edit.exec([], (err) => { t.match(err, /npm edit/, 'throws usage error') t.end() }) @@ -104,7 +126,7 @@ test('npm edit lstat error propagates', t => { gracefulFs.lstat = _lstat }) - return edit(['semver'], (err) => { + return edit.exec(['semver'], (err) => { t.match(err, /lstat failed/, 'user received correct error') t.end() }) @@ -116,7 +138,7 @@ test('npm edit editor exit code error propagates', t => { EDITOR_CODE = 0 }) - return edit(['semver'], (err) => { + return edit.exec(['semver'], (err) => { t.match(err, /exited with code: 137/, 'user received correct error') t.end() }) diff --git a/deps/npm/test/lib/exec.js b/deps/npm/test/lib/exec.js index ac813ade7b..4dc7f31cc3 100644 --- a/deps/npm/test/lib/exec.js +++ b/deps/npm/test/lib/exec.js @@ -90,13 +90,13 @@ const mocks = { '@npmcli/arborist': Arborist, '@npmcli/run-script': runScript, '@npmcli/ci-detect': () => CI_NAME, - '../../lib/npm.js': npm, pacote, read, 'mkdirp-infer-owner': mkdirp, '../../lib/utils/output.js': output, } -const exec = requireInject('../../lib/exec.js', mocks) +const Exec = requireInject('../../lib/exec.js', mocks) +const exec = new Exec(npm) t.afterEach(cb => { MKDIRPS.length = 0 @@ -116,7 +116,7 @@ t.afterEach(cb => { cb() }) -t.test('npx foo, bin already exists locally', async t => { +t.test('npx foo, bin already exists locally', t => { const path = t.testdir({ foo: 'just some file', }) @@ -124,24 +124,25 @@ t.test('npx foo, bin already exists locally', async t => { PROGRESS_IGNORED = true npm.localBin = path - await exec(['foo', 'one arg', 'two arg'], er => { + exec.exec(['foo', 'one arg', 'two arg'], er => { t.ifError(er, 'npm exec') + t.match(RUN_SCRIPTS, [{ + pkg: { scripts: { npx: 'foo' }}, + args: ['one arg', 'two arg'], + banner: false, + path: process.cwd(), + stdioString: true, + event: 'npx', + env: { + PATH: [path, ...PATH].join(delimiter), + }, + stdio: 'inherit', + }]) + t.end() }) - t.match(RUN_SCRIPTS, [{ - pkg: { scripts: { npx: 'foo' }}, - args: ['one arg', 'two arg'], - banner: false, - path: process.cwd(), - stdioString: true, - event: 'npx', - env: { - PATH: [path, ...PATH].join(delimiter), - }, - stdio: 'inherit', - }]) }) -t.test('npx foo, bin already exists globally', async t => { +t.test('npx foo, bin already exists globally', t => { const path = t.testdir({ foo: 'just some file', }) @@ -149,24 +150,25 @@ t.test('npx foo, bin already exists globally', async t => { PROGRESS_IGNORED = true npm.globalBin = path - await exec(['foo', 'one arg', 'two arg'], er => { + exec.exec(['foo', 'one arg', 'two arg'], er => { t.ifError(er, 'npm exec') + t.match(RUN_SCRIPTS, [{ + pkg: { scripts: { npx: 'foo' }}, + args: ['one arg', 'two arg'], + banner: false, + path: process.cwd(), + stdioString: true, + event: 'npx', + env: { + PATH: [path, ...PATH].join(delimiter), + }, + stdio: 'inherit', + }]) + t.end() }) - t.match(RUN_SCRIPTS, [{ - pkg: { scripts: { npx: 'foo' }}, - args: ['one arg', 'two arg'], - banner: false, - path: process.cwd(), - stdioString: true, - event: 'npx', - env: { - PATH: [path, ...PATH].join(delimiter), - }, - stdio: 'inherit', - }]) }) -t.test('npm exec foo, already present locally', async t => { +t.test('npm exec foo, already present locally', t => { const path = t.testdir() npm.localPrefix = path ARB_ACTUAL_TREE[path] = { @@ -180,94 +182,103 @@ t.test('npm exec foo, already present locally', async t => { }, _from: 'foo@', } - await exec(['foo', 'one arg', 'two arg'], er => { + exec.exec(['foo', 'one arg', 'two arg'], er => { if (er) throw er + t.strictSame(MKDIRPS, [], 'no need to make any dirs') + t.match(ARB_CTOR, [{ package: ['foo'], path }]) + t.strictSame(ARB_REIFY, [], 'no need to reify anything') + t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') + t.match(RUN_SCRIPTS, [{ + pkg: { scripts: { npx: 'foo' } }, + args: ['one arg', 'two arg'], + banner: false, + path: process.cwd(), + stdioString: true, + event: 'npx', + env: { PATH: process.env.PATH }, + stdio: 'inherit', + }]) + t.end() }) - t.strictSame(MKDIRPS, [], 'no need to make any dirs') - t.match(ARB_CTOR, [{ package: ['foo'], path }]) - t.strictSame(ARB_REIFY, [], 'no need to reify anything') - t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') - t.match(RUN_SCRIPTS, [{ - pkg: { scripts: { npx: 'foo' } }, - args: ['one arg', 'two arg'], - banner: false, - path: process.cwd(), - stdioString: true, - event: 'npx', - env: { PATH: process.env.PATH }, - stdio: 'inherit', - }]) }) -t.test('npm exec <noargs>, run interactive shell', async t => { +t.test('npm exec <noargs>, run interactive shell', t => { CI_NAME = null const { isTTY } = process.stdin process.stdin.isTTY = true t.teardown(() => process.stdin.isTTY = isTTY) - const run = async (t, doRun = true) => { + const run = (t, doRun, cb) => { LOG_WARN.length = 0 ARB_CTOR.length = 0 MKDIRPS.length = 0 ARB_REIFY.length = 0 OUTPUT.length = 0 - await exec([], er => { + exec.exec([], er => { if (er) throw er + t.strictSame(MKDIRPS, [], 'no need to make any dirs') + t.strictSame(ARB_CTOR, [], 'no need to instantiate arborist') + t.strictSame(ARB_REIFY, [], 'no need to reify anything') + t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') + if (doRun) { + t.match(RUN_SCRIPTS, [{ + pkg: { scripts: { npx: 'shell-cmd' } }, + args: [], + banner: false, + path: process.cwd(), + stdioString: true, + event: 'npx', + env: { PATH: process.env.PATH }, + stdio: 'inherit', + }]) + } else + t.strictSame(RUN_SCRIPTS, []) + + RUN_SCRIPTS.length = 0 + cb() }) - t.strictSame(MKDIRPS, [], 'no need to make any dirs') - t.strictSame(ARB_CTOR, [], 'no need to instantiate arborist') - t.strictSame(ARB_REIFY, [], 'no need to reify anything') - t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') - if (doRun) { - t.match(RUN_SCRIPTS, [{ - pkg: { scripts: { npx: 'shell-cmd' } }, - args: [], - banner: false, - path: process.cwd(), - stdioString: true, - event: 'npx', - env: { PATH: process.env.PATH }, - stdio: 'inherit', - }]) - } else - t.strictSame(RUN_SCRIPTS, []) - RUN_SCRIPTS.length = 0 } - t.test('print message when tty and not in CI', async t => { + t.test('print message when tty and not in CI', t => { CI_NAME = null process.stdin.isTTY = true - await run(t) - t.strictSame(LOG_WARN, []) - t.strictSame(OUTPUT, [ - ['\nEntering npm script environment\nType \'exit\' or ^D when finished\n'], - ], 'printed message about interactive shell') + run(t, true, () => { + t.strictSame(LOG_WARN, []) + t.strictSame(OUTPUT, [ + ['\nEntering npm script environment\nType \'exit\' or ^D when finished\n'], + ], 'printed message about interactive shell') + t.end() + }) }) - t.test('no message when not TTY', async t => { + t.test('no message when not TTY', t => { CI_NAME = null process.stdin.isTTY = false - await run(t) - t.strictSame(LOG_WARN, []) - t.strictSame(OUTPUT, [], 'no message about interactive shell') + run(t, true, () => { + t.strictSame(LOG_WARN, []) + t.strictSame(OUTPUT, [], 'no message about interactive shell') + t.end() + }) }) - t.test('print warning when in CI and interactive', async t => { + t.test('print warning when in CI and interactive', t => { CI_NAME = 'travis-ci' process.stdin.isTTY = true - await run(t, false) - t.strictSame(LOG_WARN, [ - ['exec', 'Interactive mode disabled in CI environment'], - ]) - t.strictSame(OUTPUT, [], 'no message about interactive shell') + run(t, false, () => { + t.strictSame(LOG_WARN, [ + ['exec', 'Interactive mode disabled in CI environment'], + ]) + t.strictSame(OUTPUT, [], 'no message about interactive shell') + t.end() + }) }) t.end() }) -t.test('npm exec foo, not present locally or in central loc', async t => { +t.test('npm exec foo, not present locally or in central loc', t => { const path = t.testdir() const installDir = resolve('cache-dir/_npx/f7fbba6e0636f890') npm.localPrefix = path @@ -285,28 +296,29 @@ t.test('npm exec foo, not present locally or in central loc', async t => { }, _from: 'foo@', } - await exec(['foo', 'one arg', 'two arg'], er => { + exec.exec(['foo', 'one arg', 'two arg'], er => { if (er) throw er + t.strictSame(MKDIRPS, [installDir], 'need to make install dir') + t.match(ARB_CTOR, [{ package: ['foo'], 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}` + t.match(RUN_SCRIPTS, [{ + pkg: { scripts: { npx: 'foo' } }, + args: ['one arg', 'two arg'], + banner: false, + path: process.cwd(), + stdioString: true, + event: 'npx', + env: { PATH }, + stdio: 'inherit', + }]) + t.end() }) - t.strictSame(MKDIRPS, [installDir], 'need to make install dir') - t.match(ARB_CTOR, [{ package: ['foo'], 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}` - t.match(RUN_SCRIPTS, [{ - pkg: { scripts: { npx: 'foo' } }, - args: ['one arg', 'two arg'], - banner: false, - path: process.cwd(), - stdioString: true, - event: 'npx', - env: { PATH }, - stdio: 'inherit', - }]) }) -t.test('npm exec foo, not present locally but in central loc', async t => { +t.test('npm exec foo, not present locally but in central loc', t => { const path = t.testdir() const installDir = resolve('cache-dir/_npx/f7fbba6e0636f890') npm.localPrefix = path @@ -324,28 +336,29 @@ t.test('npm exec foo, not present locally but in central loc', async t => { }, _from: 'foo@', } - await exec(['foo', 'one arg', 'two arg'], er => { + exec.exec(['foo', 'one arg', 'two arg'], er => { if (er) throw er + t.strictSame(MKDIRPS, [installDir], 'need to make install dir') + t.match(ARB_CTOR, [{ package: ['foo'], 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}` + t.match(RUN_SCRIPTS, [{ + pkg: { scripts: { npx: 'foo' } }, + args: ['one arg', 'two arg'], + banner: false, + path: process.cwd(), + stdioString: true, + event: 'npx', + env: { PATH }, + stdio: 'inherit', + }]) + t.end() }) - t.strictSame(MKDIRPS, [installDir], 'need to make install dir') - t.match(ARB_CTOR, [{ package: ['foo'], 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}` - t.match(RUN_SCRIPTS, [{ - pkg: { scripts: { npx: 'foo' } }, - args: ['one arg', 'two arg'], - banner: false, - path: process.cwd(), - stdioString: true, - event: 'npx', - env: { PATH }, - stdio: 'inherit', - }]) }) -t.test('npm exec foo, present locally but wrong version', async t => { +t.test('npm exec foo, present locally but wrong version', t => { const path = t.testdir() const installDir = resolve('cache-dir/_npx/2badf4630f1cfaad') npm.localPrefix = path @@ -363,28 +376,29 @@ t.test('npm exec foo, present locally but wrong version', async t => { }, _from: 'foo@2.x', } - await exec(['foo@2.x', 'one arg', 'two arg'], er => { + exec.exec(['foo@2.x', 'one arg', 'two arg'], er => { if (er) throw er + t.strictSame(MKDIRPS, [installDir], 'need to make install dir') + t.match(ARB_CTOR, [{ package: ['foo'], 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}` + t.match(RUN_SCRIPTS, [{ + pkg: { scripts: { npx: 'foo' } }, + args: ['one arg', 'two arg'], + banner: false, + path: process.cwd(), + stdioString: true, + event: 'npx', + env: { PATH }, + stdio: 'inherit', + }]) + t.end() }) - t.strictSame(MKDIRPS, [installDir], 'need to make install dir') - t.match(ARB_CTOR, [{ package: ['foo'], 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}` - t.match(RUN_SCRIPTS, [{ - pkg: { scripts: { npx: 'foo' } }, - args: ['one arg', 'two arg'], - banner: false, - path: process.cwd(), - stdioString: true, - event: 'npx', - env: { PATH }, - stdio: 'inherit', - }]) }) -t.test('npm exec --package=foo bar', async t => { +t.test('npm exec --package=foo bar', t => { const path = t.testdir() npm.localPrefix = path ARB_ACTUAL_TREE[path] = { @@ -399,27 +413,28 @@ t.test('npm exec --package=foo bar', async t => { _from: 'foo@', } npm.flatOptions.package = ['foo'] - await exec(['bar', 'one arg', 'two arg'], er => { + exec.exec(['bar', 'one arg', 'two arg'], er => { if (er) throw er + t.strictSame(MKDIRPS, [], 'no need to make any dirs') + t.match(ARB_CTOR, [{ package: ['foo'], path }]) + t.strictSame(ARB_REIFY, [], 'no need to reify anything') + t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') + t.match(RUN_SCRIPTS, [{ + pkg: { scripts: { npx: 'bar' } }, + args: ['one arg', 'two arg'], + banner: false, + path: process.cwd(), + stdioString: true, + event: 'npx', + env: { PATH: process.env.PATH }, + stdio: 'inherit', + }]) + t.end() }) - t.strictSame(MKDIRPS, [], 'no need to make any dirs') - t.match(ARB_CTOR, [{ package: ['foo'], path }]) - t.strictSame(ARB_REIFY, [], 'no need to reify anything') - t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') - t.match(RUN_SCRIPTS, [{ - pkg: { scripts: { npx: 'bar' } }, - args: ['one arg', 'two arg'], - banner: false, - path: process.cwd(), - stdioString: true, - event: 'npx', - env: { PATH: process.env.PATH }, - stdio: 'inherit', - }]) }) -t.test('npm exec @foo/bar -- --some=arg, locally installed', async t => { +t.test('npm exec @foo/bar -- --some=arg, locally installed', t => { const foobarManifest = { name: '@foo/bar', version: '1.2.3', @@ -440,27 +455,28 @@ t.test('npm exec @foo/bar -- --some=arg, locally installed', async t => { children: new Map([['@foo/bar', { name: '@foo/bar', version: '1.2.3' }]]), } MANIFESTS['@foo/bar'] = foobarManifest - await exec(['@foo/bar', '--some=arg'], er => { + exec.exec(['@foo/bar', '--some=arg'], er => { if (er) throw er + t.strictSame(MKDIRPS, [], 'no need to make any dirs') + t.match(ARB_CTOR, [{ package: ['@foo/bar'], path }]) + t.strictSame(ARB_REIFY, [], 'no need to reify anything') + t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') + t.match(RUN_SCRIPTS, [{ + pkg: { scripts: { npx: 'bar' } }, + args: ['--some=arg'], + banner: false, + path: process.cwd(), + stdioString: true, + event: 'npx', + env: { PATH: process.env.PATH }, + stdio: 'inherit', + }]) + t.end() }) - t.strictSame(MKDIRPS, [], 'no need to make any dirs') - t.match(ARB_CTOR, [{ package: ['@foo/bar'], path }]) - t.strictSame(ARB_REIFY, [], 'no need to reify anything') - t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') - t.match(RUN_SCRIPTS, [{ - pkg: { scripts: { npx: 'bar' } }, - args: ['--some=arg'], - banner: false, - path: process.cwd(), - stdioString: true, - event: 'npx', - env: { PATH: process.env.PATH }, - stdio: 'inherit', - }]) }) -t.test('npm exec @foo/bar, with same bin alias and no unscoped named bin, locally installed', async t => { +t.test('npm exec @foo/bar, with same bin alias and no unscoped named bin, locally installed', t => { const foobarManifest = { name: '@foo/bar', version: '1.2.3', @@ -482,24 +498,25 @@ t.test('npm exec @foo/bar, with same bin alias and no unscoped named bin, locall children: new Map([['@foo/bar', { name: '@foo/bar', version: '1.2.3' }]]), } MANIFESTS['@foo/bar'] = foobarManifest - await exec(['@foo/bar', 'one arg', 'two arg'], er => { + exec.exec(['@foo/bar', 'one arg', 'two arg'], er => { if (er) throw er + t.strictSame(MKDIRPS, [], 'no need to make any dirs') + t.match(ARB_CTOR, [{ package: ['@foo/bar'], path }]) + t.strictSame(ARB_REIFY, [], 'no need to reify anything') + t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') + t.match(RUN_SCRIPTS, [{ + pkg: { scripts: { npx: 'baz' } }, + args: ['one arg', 'two arg'], + banner: false, + path: process.cwd(), + stdioString: true, + event: 'npx', + env: { PATH: process.env.PATH }, + stdio: 'inherit', + }]) + t.end() }) - t.strictSame(MKDIRPS, [], 'no need to make any dirs') - t.match(ARB_CTOR, [{ package: ['@foo/bar'], path }]) - t.strictSame(ARB_REIFY, [], 'no need to reify anything') - t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') - t.match(RUN_SCRIPTS, [{ - pkg: { scripts: { npx: 'baz' } }, - args: ['one arg', 'two arg'], - banner: false, - path: process.cwd(), - stdioString: true, - event: 'npx', - env: { PATH: process.env.PATH }, - stdio: 'inherit', - }]) }) t.test('npm exec @foo/bar, with different bin alias and no unscoped named bin, locally installed', t => { @@ -519,12 +536,12 @@ t.test('npm exec @foo/bar, with different bin alias and no unscoped named bin, l _from: 'foo@', _id: '@foo/bar@1.2.3', } - return t.rejects(exec(['@foo/bar'], er => { - if (er) - throw er - }), { - message: 'could not determine executable to run', - pkgid: '@foo/bar@1.2.3', + exec.exec(['@foo/bar'], er => { + t.match(er, { + message: 'could not determine executable to run', + pkgid: '@foo/bar@1.2.3', + }) + t.end() }) }) @@ -534,7 +551,7 @@ t.test('run command with 2 packages, need install, verify sort', t => { const cases = [['foo', 'bar'], ['bar', 'foo']] t.plan(cases.length) for (const packages of cases) { - t.test(packages.join(', '), async t => { + t.test(packages.join(', '), t => { npm.flatOptions.package = packages const add = packages.map(p => `${p}@`).sort((a, b) => a.localeCompare(b)) const path = t.testdir() @@ -562,25 +579,26 @@ t.test('run command with 2 packages, need install, verify sort', t => { }, _from: 'bar@', } - await exec(['foobar', 'one arg', 'two arg'], er => { + exec.exec(['foobar', 'one arg', 'two arg'], er => { if (er) throw er + t.strictSame(MKDIRPS, [installDir], 'need to make install dir') + t.match(ARB_CTOR, [{ package: packages, 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}` + t.match(RUN_SCRIPTS, [{ + pkg: { scripts: { npx: 'foobar' } }, + args: ['one arg', 'two arg'], + banner: false, + path: process.cwd(), + stdioString: true, + event: 'npx', + env: { PATH }, + stdio: 'inherit', + }]) + t.end() }) - t.strictSame(MKDIRPS, [installDir], 'need to make install dir') - t.match(ARB_CTOR, [{ package: packages, 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}` - t.match(RUN_SCRIPTS, [{ - pkg: { scripts: { npx: 'foobar' } }, - args: ['one arg', 'two arg'], - banner: false, - path: process.cwd(), - stdioString: true, - event: 'npx', - env: { PATH }, - stdio: 'inherit', - }]) }) } }) @@ -597,12 +615,12 @@ t.test('npm exec foo, no bin in package', t => { _from: 'foo@', _id: 'foo@1.2.3', } - return t.rejects(exec(['foo'], er => { - if (er) - throw er - }), { - message: 'could not determine executable to run', - pkgid: 'foo@1.2.3', + exec.exec(['foo'], er => { + t.match(er, { + message: 'could not determine executable to run', + pkgid: 'foo@1.2.3', + }) + t.end() }) }) @@ -622,16 +640,16 @@ t.test('npm exec foo, many bins in package, none named foo', t => { _from: 'foo@', _id: 'foo@1.2.3', } - return t.rejects(exec(['foo'], er => { - if (er) - throw er - }), { - message: 'could not determine executable to run', - pkgid: 'foo@1.2.3', + exec.exec(['foo'], er => { + t.match(er, { + message: 'could not determine executable to run', + pkgid: 'foo@1.2.3', + }) + t.end() }) }) -t.test('npm exec -p foo -c "ls -laF"', async t => { +t.test('npm exec -p foo -c "ls -laF"', t => { const path = t.testdir() npm.localPrefix = path npm.flatOptions.package = ['foo'] @@ -644,31 +662,35 @@ t.test('npm exec -p foo -c "ls -laF"', async t => { version: '1.2.3', _from: 'foo@', } - await exec([], er => { + exec.exec([], er => { if (er) throw er + t.strictSame(MKDIRPS, [], 'no need to make any dirs') + t.match(ARB_CTOR, [{ package: ['foo'], path }]) + t.strictSame(ARB_REIFY, [], 'no need to reify anything') + t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') + t.match(RUN_SCRIPTS, [{ + pkg: { scripts: { npx: 'ls -laF' } }, + banner: false, + path: process.cwd(), + stdioString: true, + event: 'npx', + env: { PATH: process.env.PATH }, + stdio: 'inherit', + }]) + t.end() }) - t.strictSame(MKDIRPS, [], 'no need to make any dirs') - t.match(ARB_CTOR, [{ package: ['foo'], path }]) - t.strictSame(ARB_REIFY, [], 'no need to reify anything') - t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') - t.match(RUN_SCRIPTS, [{ - pkg: { scripts: { npx: 'ls -laF' } }, - banner: false, - path: process.cwd(), - stdioString: true, - event: 'npx', - env: { PATH: process.env.PATH }, - stdio: 'inherit', - }]) }) t.test('positional args and --call together is an error', t => { npm.flatOptions.call = 'true' - return exec(['foo'], er => t.equal(er, exec.usage)) + exec.exec(['foo'], er => { + t.equal(er, exec.usage) + t.end() + }) }) -t.test('prompt when installs are needed if not already present and shell is a TTY', async t => { +t.test('prompt when installs are needed if not already present and shell is a TTY', t => { const stdoutTTY = process.stdout.isTTY const stdinTTY = process.stdin.isTTY t.teardown(() => { @@ -712,31 +734,32 @@ t.test('prompt when installs are needed if not already present and shell is a TT }, _from: 'bar@', } - await exec(['foobar'], er => { + exec.exec(['foobar'], er => { if (er) throw er + t.strictSame(MKDIRPS, [installDir], 'need to make install dir') + t.match(ARB_CTOR, [{ package: packages, 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}` + t.match(RUN_SCRIPTS, [{ + pkg: { scripts: { npx: 'foobar' } }, + banner: false, + path: process.cwd(), + stdioString: true, + event: 'npx', + env: { PATH }, + stdio: 'inherit', + }]) + t.strictSame(READ, [{ + prompt: 'Need to install the following packages:\n bar\n foo\nOk to proceed? ', + default: 'y', + }]) + t.end() }) - t.strictSame(MKDIRPS, [installDir], 'need to make install dir') - t.match(ARB_CTOR, [{ package: packages, 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}` - t.match(RUN_SCRIPTS, [{ - pkg: { scripts: { npx: 'foobar' } }, - banner: false, - path: process.cwd(), - stdioString: true, - event: 'npx', - env: { PATH }, - stdio: 'inherit', - }]) - t.strictSame(READ, [{ - prompt: 'Need to install the following packages:\n bar\n foo\nOk to proceed? ', - default: 'y', - }]) }) -t.test('skip prompt when installs are needed if not already present and shell is not a tty (multiple packages)', async t => { +t.test('skip prompt when installs are needed if not already present and shell is not a tty (multiple packages)', t => { const stdoutTTY = process.stdout.isTTY const stdinTTY = process.stdin.isTTY t.teardown(() => { @@ -780,29 +803,30 @@ t.test('skip prompt when installs are needed if not already present and shell is }, _from: 'bar@', } - await exec(['foobar'], er => { + exec.exec(['foobar'], er => { if (er) throw er + t.strictSame(MKDIRPS, [installDir], 'need to make install dir') + t.match(ARB_CTOR, [{ package: packages, 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}` + t.match(RUN_SCRIPTS, [{ + pkg: { scripts: { npx: 'foobar' } }, + banner: false, + path: process.cwd(), + stdioString: true, + event: 'npx', + env: { PATH }, + stdio: 'inherit', + }]) + t.strictSame(READ, [], 'should not have prompted') + t.strictSame(LOG_WARN, [['exec', 'The following packages were not found and will be installed: bar, foo']], 'should have printed a warning') + t.end() }) - t.strictSame(MKDIRPS, [installDir], 'need to make install dir') - t.match(ARB_CTOR, [{ package: packages, 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}` - t.match(RUN_SCRIPTS, [{ - pkg: { scripts: { npx: 'foobar' } }, - banner: false, - path: process.cwd(), - stdioString: true, - event: 'npx', - env: { PATH }, - stdio: 'inherit', - }]) - t.strictSame(READ, [], 'should not have prompted') - t.strictSame(LOG_WARN, [['exec', 'The following packages were not found and will be installed: bar, foo']], 'should have printed a warning') }) -t.test('skip prompt when installs are needed if not already present and shell is not a tty (single package)', async t => { +t.test('skip prompt when installs are needed if not already present and shell is not a tty (single package)', t => { const stdoutTTY = process.stdout.isTTY const stdinTTY = process.stdin.isTTY t.teardown(() => { @@ -838,29 +862,30 @@ t.test('skip prompt when installs are needed if not already present and shell is }, _from: 'foo@', } - await exec(['foobar'], er => { + exec.exec(['foobar'], er => { if (er) throw er + t.strictSame(MKDIRPS, [installDir], 'need to make install dir') + t.match(ARB_CTOR, [{ package: packages, 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}` + t.match(RUN_SCRIPTS, [{ + pkg: { scripts: { npx: 'foobar' } }, + banner: false, + path: process.cwd(), + stdioString: true, + event: 'npx', + env: { PATH }, + stdio: 'inherit', + }]) + t.strictSame(READ, [], 'should not have prompted') + t.strictSame(LOG_WARN, [['exec', 'The following package was not found and will be installed: foo']], 'should have printed a warning') + t.end() }) - t.strictSame(MKDIRPS, [installDir], 'need to make install dir') - t.match(ARB_CTOR, [{ package: packages, 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}` - t.match(RUN_SCRIPTS, [{ - pkg: { scripts: { npx: 'foobar' } }, - banner: false, - path: process.cwd(), - stdioString: true, - event: 'npx', - env: { PATH }, - stdio: 'inherit', - }]) - t.strictSame(READ, [], 'should not have prompted') - t.strictSame(LOG_WARN, [['exec', 'The following package was not found and will be installed: foo']], 'should have printed a warning') }) -t.test('abort if prompt rejected', async t => { +t.test('abort if prompt rejected', t => { const stdoutTTY = process.stdout.isTTY const stdinTTY = process.stdin.isTTY t.teardown(() => { @@ -903,21 +928,22 @@ t.test('abort if prompt rejected', async t => { }, _from: 'bar@', } - await exec(['foobar'], er => { + 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.strictSame(ARB_REIFY, [], 'no install performed') + t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') + t.strictSame(RUN_SCRIPTS, []) + t.strictSame(READ, [{ + prompt: 'Need to install the following packages:\n bar\n foo\nOk to proceed? ', + default: 'y', + }]) + t.end() }) - t.strictSame(MKDIRPS, [installDir], 'need to make install dir') - t.match(ARB_CTOR, [{ package: packages, path }]) - t.strictSame(ARB_REIFY, [], 'no install performed') - t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') - t.strictSame(RUN_SCRIPTS, []) - t.strictSame(READ, [{ - prompt: 'Need to install the following packages:\n bar\n foo\nOk to proceed? ', - default: 'y', - }]) }) -t.test('abort if prompt false', async t => { +t.test('abort if prompt false', t => { const stdoutTTY = process.stdout.isTTY const stdinTTY = process.stdin.isTTY t.teardown(() => { @@ -960,21 +986,22 @@ t.test('abort if prompt false', async t => { }, _from: 'bar@', } - await exec(['foobar'], er => { + 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.strictSame(ARB_REIFY, [], 'no install performed') + t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') + t.strictSame(RUN_SCRIPTS, []) + t.strictSame(READ, [{ + prompt: 'Need to install the following packages:\n bar\n foo\nOk to proceed? ', + default: 'y', + }]) + t.end() }) - t.strictSame(MKDIRPS, [installDir], 'need to make install dir') - t.match(ARB_CTOR, [{ package: packages, path }]) - t.strictSame(ARB_REIFY, [], 'no install performed') - t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') - t.strictSame(RUN_SCRIPTS, []) - t.strictSame(READ, [{ - prompt: 'Need to install the following packages:\n bar\n foo\nOk to proceed? ', - default: 'y', - }]) }) -t.test('abort if -n provided', async t => { +t.test('abort if -n provided', t => { const stdoutTTY = process.stdout.isTTY const stdinTTY = process.stdin.isTTY t.teardown(() => { @@ -1016,18 +1043,19 @@ t.test('abort if -n provided', async t => { }, _from: 'bar@', } - await exec(['foobar'], er => { + 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.strictSame(ARB_REIFY, [], 'no install performed') + t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') + t.strictSame(RUN_SCRIPTS, []) + t.strictSame(READ, []) + t.done() }) - t.strictSame(MKDIRPS, [installDir], 'need to make install dir') - t.match(ARB_CTOR, [{ package: packages, path }]) - t.strictSame(ARB_REIFY, [], 'no install performed') - t.equal(PROGRESS_ENABLED, true, 'progress re-enabled') - t.strictSame(RUN_SCRIPTS, []) - t.strictSame(READ, []) }) -t.test('forward legacyPeerDeps opt', async t => { +t.test('forward legacyPeerDeps opt', t => { const path = t.testdir() const installDir = resolve('cache-dir/_npx/f7fbba6e0636f890') npm.localPrefix = path @@ -1047,9 +1075,10 @@ t.test('forward legacyPeerDeps opt', async t => { } npm.flatOptions.yes = true npm.flatOptions.legacyPeerDeps = true - await exec(['foo'], er => { + exec.exec(['foo'], er => { if (er) throw er + t.match(ARB_REIFY, [{add: ['foo@'], legacyPeerDeps: true}], 'need to install foo@ using legacyPeerDeps opt') + t.done() }) - t.match(ARB_REIFY, [{add: ['foo@'], legacyPeerDeps: true}], 'need to install foo@ using legacyPeerDeps opt') }) diff --git a/deps/npm/test/lib/explain.js b/deps/npm/test/lib/explain.js index 1eeca8c4c4..22bfb8639e 100644 --- a/deps/npm/test/lib/explain.js +++ b/deps/npm/test/lib/explain.js @@ -9,9 +9,7 @@ const { resolve } = require('path') const OUTPUT = [] -const explain = requireInject('../../lib/explain.js', { - '../../lib/npm.js': npm, - +const Explain = requireInject('../../lib/explain.js', { '../../lib/utils/output.js': (...args) => { OUTPUT.push(args) }, @@ -23,44 +21,40 @@ const explain = requireInject('../../lib/explain.js', { }, }, }) +const explain = new Explain(npm) -t.test('no args throws usage', async t => { +t.test('no args throws usage', t => { t.plan(1) - try { - await explain([], er => { - throw er - }) - } catch (er) { + explain.exec([], er => { t.equal(er, explain.usage) - } + t.done() + }) }) -t.test('no match throws not found', async t => { +t.test('no match throws not found', t => { npm.prefix = t.testdir() t.plan(1) - try { - await explain(['foo@1.2.3', 'node_modules/baz'], er => { - throw er - }) - } catch (er) { + explain.exec(['foo@1.2.3', 'node_modules/baz'], er => { t.equal(er, 'No dependencies found matching foo@1.2.3, node_modules/baz') - } + }) }) -t.test('invalid package name throws not found', async t => { +t.test('invalid package name throws not found', t => { npm.prefix = t.testdir() t.plan(1) const badName = ' not a valid package name ' - try { - await explain([`${badName}@1.2.3`], er => { - throw er - }) - } catch (er) { + explain.exec([`${badName}@1.2.3`], er => { t.equal(er, `No dependencies found matching ${badName}@1.2.3`) - } + }) }) -t.test('explain some nodes', async t => { +t.test('explain some nodes', t => { + t.afterEach((cb) => { + OUTPUT.length = 0 + npm.flatOptions.json = false + cb() + }) + npm.prefix = t.testdir({ node_modules: { foo: { @@ -111,61 +105,75 @@ t.test('explain some nodes', async t => { }), }) - // works with either a full actual path or the location - const p = 'node_modules/foo' - for (const path of [p, resolve(npm.prefix, p)]) { - await explain([path], er => { + t.test('works with the location', t => { + const path = 'node_modules/foo' + explain.exec([path], er => { if (er) throw er + t.strictSame(OUTPUT, [['foo@1.2.3 depth=Infinity color=true']]) + t.end() }) - t.strictSame(OUTPUT, [['foo@1.2.3 depth=Infinity color=true']]) - OUTPUT.length = 0 - } + }) + t.test('works with a full actual path', t => { + const path = resolve(npm.prefix, 'node_modules/foo') + explain.exec([path], er => { + if (er) + throw er + t.strictSame(OUTPUT, [['foo@1.2.3 depth=Infinity color=true']]) + t.end() + }) + }) - // finds all nodes by name - await explain(['bar'], er => { - if (er) - throw er + t.test('finds all nodes by name', t => { + explain.exec(['bar'], er => { + if (er) + throw er + t.strictSame(OUTPUT, [[ + 'bar@1.2.3 depth=Infinity color=true\n\n' + + 'bar@2.3.4 depth=Infinity color=true', + ]]) + t.end() + }) }) - t.strictSame(OUTPUT, [[ - 'bar@1.2.3 depth=Infinity color=true\n\n' + - 'bar@2.3.4 depth=Infinity color=true', - ]]) - OUTPUT.length = 0 - // finds only nodes that match the spec - await explain(['bar@1'], er => { - if (er) - throw er + t.test('finds only nodes that match the spec', t => { + explain.exec(['bar@1'], er => { + if (er) + throw er + t.strictSame(OUTPUT, [['bar@1.2.3 depth=Infinity color=true']]) + t.end() + }) }) - t.strictSame(OUTPUT, [['bar@1.2.3 depth=Infinity color=true']]) - OUTPUT.length = 0 - // finds extraneous nodes - await explain(['extra'], er => { - if (er) - throw er + t.test('finds extraneous nodes', t => { + explain.exec(['extra'], er => { + if (er) + throw er + t.strictSame(OUTPUT, [['extra@99.9999.999999 depth=Infinity color=true']]) + t.end() + }) }) - t.strictSame(OUTPUT, [['extra@99.9999.999999 depth=Infinity color=true']]) - OUTPUT.length = 0 - npm.flatOptions.json = true - await explain(['node_modules/foo'], er => { - if (er) - throw er + t.test('json output', t => { + npm.flatOptions.json = true + explain.exec(['node_modules/foo'], er => { + if (er) + throw er + t.match(JSON.parse(OUTPUT[0][0]), [{ + name: 'foo', + version: '1.2.3', + dependents: Array, + }]) + t.end() + }) }) - t.match(JSON.parse(OUTPUT[0][0]), [{ - name: 'foo', - version: '1.2.3', - dependents: Array, - }]) - OUTPUT.length = 0 - npm.flatOptions.json = false - t.test('report if no nodes found', async t => { + t.test('report if no nodes found', t => { t.plan(1) - await explain(['asdf/foo/bar', 'quux@1.x'], er => { + explain.exec(['asdf/foo/bar', 'quux@1.x'], er => { t.equal(er, 'No dependencies found matching asdf/foo/bar, quux@1.x') + t.done() }) }) + t.end() }) diff --git a/deps/npm/test/lib/explore.js b/deps/npm/test/lib/explore.js index 23eab1172a..6f1f3bb47f 100644 --- a/deps/npm/test/lib/explore.js +++ b/deps/npm/test/lib/explore.js @@ -46,14 +46,20 @@ const mockRunScript = ({ pkg, banner, path, event, stdio }) => { const output = [] let ERROR_HANDLER_CALLED = null const logs = [] -const getExplore = windows => requireInject('../../lib/explore.js', { - '../../lib/utils/is-windows.js': windows, - path: require('path')[windows ? 'win32' : 'posix'], - '../../lib/utils/error-handler.js': er => { - ERROR_HANDLER_CALLED = er - }, - 'read-package-json-fast': mockRPJ, - '../../lib/npm.js': { +const getExplore = (windows) => { + const Explore = requireInject('../../lib/explore.js', { + '../../lib/utils/is-windows.js': windows, + path: require('path')[windows ? 'win32' : 'posix'], + '../../lib/utils/error-handler.js': er => { + ERROR_HANDLER_CALLED = er + }, + 'read-package-json-fast': mockRPJ, + '@npmcli/run-script': mockRunScript, + '../../lib/utils/output.js': out => { + output.push(out) + }, + }) + const npm = { dir: windows ? 'c:\\npm\\dir' : '/npm/dir', log: { error: (...msg) => logs.push(msg), @@ -63,12 +69,9 @@ const getExplore = windows => requireInject('../../lib/explore.js', { flatOptions: { shell: 'shell-command', }, - }, - '@npmcli/run-script': mockRunScript, - '../../lib/utils/output.js': out => { - output.push(out) - }, -}) + } + return new Explore(npm) +} const windowsExplore = getExplore(true) const posixExplore = getExplore(false) @@ -79,7 +82,7 @@ t.test('basic interactive', t => { cb() }) - t.test('windows', t => windowsExplore(['pkg'], er => { + t.test('windows', t => windowsExplore.exec(['pkg'], er => { if (er) throw er @@ -95,9 +98,10 @@ t.test('basic interactive', t => { t.strictSame(output, [ "\nExploring c:\\npm\\dir\\pkg\nType 'exit' or ^D when finished\n", ]) + t.end() })) - t.test('posix', t => posixExplore(['pkg'], er => { + t.test('posix', t => posixExplore.exec(['pkg'], er => { if (er) throw er @@ -113,6 +117,7 @@ t.test('basic interactive', t => { t.strictSame(output, [ "\nExploring /npm/dir/pkg\nType 'exit' or ^D when finished\n", ]) + t.end() })) t.end() @@ -132,7 +137,7 @@ t.test('interactive tracks exit code', t => { cb() }) - t.test('windows', t => windowsExplore(['pkg'], er => { + t.test('windows', t => windowsExplore.exec(['pkg'], er => { if (er) throw er @@ -149,9 +154,10 @@ t.test('interactive tracks exit code', t => { "\nExploring c:\\npm\\dir\\pkg\nType 'exit' or ^D when finished\n", ]) t.equal(process.exitCode, 99) + t.end() })) - t.test('posix', t => posixExplore(['pkg'], er => { + t.test('posix', t => posixExplore.exec(['pkg'], er => { if (er) throw er @@ -168,18 +174,20 @@ t.test('interactive tracks exit code', t => { "\nExploring /npm/dir/pkg\nType 'exit' or ^D when finished\n", ]) t.equal(process.exitCode, 99) + t.end() })) t.test('posix spawn fail', t => { RUN_SCRIPT_ERROR = Object.assign(new Error('glorb'), { code: 33, }) - return posixExplore(['pkg'], er => { + posixExplore.exec(['pkg'], er => { t.match(er, { message: 'glorb', code: 33 }) t.strictSame(output, [ "\nExploring /npm/dir/pkg\nType 'exit' or ^D when finished\n", ]) t.equal(process.exitCode, 33) + t.end() }) }) @@ -187,12 +195,13 @@ t.test('interactive tracks exit code', t => { RUN_SCRIPT_ERROR = Object.assign(new Error('glorb'), { code: 0, }) - return posixExplore(['pkg'], er => { + posixExplore.exec(['pkg'], er => { t.match(er, { message: 'glorb', code: 0 }) t.strictSame(output, [ "\nExploring /npm/dir/pkg\nType 'exit' or ^D when finished\n", ]) t.equal(process.exitCode, 1) + t.end() }) }) @@ -200,12 +209,13 @@ t.test('interactive tracks exit code', t => { RUN_SCRIPT_ERROR = Object.assign(new Error('command failed'), { code: 'EPROBLEM', }) - return posixExplore(['pkg'], er => { + posixExplore.exec(['pkg'], er => { t.match(er, { message: 'command failed', code: 'EPROBLEM' }) t.strictSame(output, [ "\nExploring /npm/dir/pkg\nType 'exit' or ^D when finished\n", ]) t.equal(process.exitCode, 1) + t.end() }) }) @@ -218,7 +228,7 @@ t.test('basic non-interactive', t => { cb() }) - t.test('windows', t => windowsExplore(['pkg', 'ls'], er => { + t.test('windows', t => windowsExplore.exec(['pkg', 'ls'], er => { if (er) throw er @@ -232,9 +242,10 @@ t.test('basic non-interactive', t => { RUN_SCRIPT_EXEC: 'ls', }) t.strictSame(output, []) + t.end() })) - t.test('posix', t => posixExplore(['pkg', 'ls'], er => { + t.test('posix', t => posixExplore.exec(['pkg', 'ls'], er => { if (er) throw er @@ -248,6 +259,7 @@ t.test('basic non-interactive', t => { RUN_SCRIPT_EXEC: 'ls', }) t.strictSame(output, []) + t.end() })) t.end() @@ -272,7 +284,7 @@ t.test('signal fails non-interactive', t => { cb() }) - t.test('windows', t => windowsExplore(['pkg', 'ls'], er => { + t.test('windows', t => windowsExplore.exec(['pkg', 'ls'], er => { t.match(er, { message: 'command failed', signal: 'SIGPROBLEM', @@ -286,9 +298,10 @@ t.test('signal fails non-interactive', t => { RUN_SCRIPT_EXEC: 'ls', }) t.strictSame(output, []) + t.end() })) - t.test('posix', t => posixExplore(['pkg', 'ls'], er => { + t.test('posix', t => posixExplore.exec(['pkg', 'ls'], er => { t.match(er, { message: 'command failed', signal: 'SIGPROBLEM', @@ -302,6 +315,7 @@ t.test('signal fails non-interactive', t => { RUN_SCRIPT_EXEC: 'ls', }) t.strictSame(output, []) + t.end() })) t.end() @@ -322,29 +336,28 @@ t.test('usage if no pkg provided', t => { ] t.plan(noPkg.length) for (const args of noPkg) { - t.test(JSON.stringify(args), t => posixExplore(args, er => { - t.equal(er, 'npm explore <pkg> [ -- <command>]') - t.strictSame({ - ERROR_HANDLER_CALLED: null, - RPJ_CALLED, - RUN_SCRIPT_EXEC, - }, { - ERROR_HANDLER_CALLED: null, - RPJ_CALLED: '/npm/dir/pkg/package.json', - RUN_SCRIPT_EXEC: 'ls', + t.test(JSON.stringify(args), t => { + posixExplore.exec(args, er => { + t.equal(er, 'npm explore <pkg> [ -- <command>]') + t.strictSame({ + ERROR_HANDLER_CALLED: null, + RPJ_CALLED, + RUN_SCRIPT_EXEC, + }, { + ERROR_HANDLER_CALLED: null, + RPJ_CALLED: '/npm/dir/pkg/package.json', + RUN_SCRIPT_EXEC: 'ls', + }) + t.end() }) - })) + }) } }) t.test('pkg not installed', t => { RPJ_ERROR = new Error('plurple') - t.plan(2) - - posixExplore(['pkg', 'ls'], er => { - if (er) - throw er + posixExplore.exec(['pkg', 'ls'], er => { t.strictSame({ ERROR_HANDLER_CALLED, RPJ_CALLED, @@ -355,9 +368,9 @@ t.test('pkg not installed', t => { RUN_SCRIPT_EXEC: 'ls', }) t.strictSame(output, []) - }).catch(er => { t.match(er, { message: 'plurple' }) t.match(logs, [['explore', `It doesn't look like pkg is installed.`]]) + t.end() logs.length = 0 }) }) diff --git a/deps/npm/test/lib/find-dupes.js b/deps/npm/test/lib/find-dupes.js index 73c8fa2dc2..c7b33ceb6e 100644 --- a/deps/npm/test/lib/find-dupes.js +++ b/deps/npm/test/lib/find-dupes.js @@ -1,15 +1,24 @@ -const { test } = require('tap') -const requireInject = require('require-inject') +const t = require('tap') -test('should run dedupe in dryRun mode', (t) => { - const findDupes = requireInject('../../lib/find-dupes.js', { - '../../lib/dedupe.js': function (args, cb) { - t.ok(args.dryRun, 'dryRun is true') - cb() +const FindDupes = require('../../lib/find-dupes.js') + +t.test('should run dedupe in dryRun mode', (t) => { + t.plan(3) + const findDupesTest = new FindDupes({ + config: { + set: (k, v) => { + t.match(k, 'dry-run') + t.match(v, true) + }, + }, + commands: { + dedupe: (args, cb) => { + t.match(args, []) + cb() + }, }, }) - findDupes(null, () => { - t.ok(true, 'callback is called') + findDupesTest.exec({}, () => { t.end() }) }) diff --git a/deps/npm/test/lib/fund.js b/deps/npm/test/lib/fund.js index 73f639b6ce..831d76f151 100644 --- a/deps/npm/test/lib/fund.js +++ b/deps/npm/test/lib/fund.js @@ -188,11 +188,10 @@ const _flatOptions = { unicode: false, which: undefined, } -const openUrl = (url, msg, cb) => { - if (url === 'http://npmjs.org') { - cb(new Error('ERROR')) - return - } +const openUrl = async (npm, url, msg) => { + if (url === 'http://npmjs.org') + throw new Error('ERROR') + if (_flatOptions.json) { printUrl = JSON.stringify({ title: msg, @@ -200,16 +199,8 @@ const openUrl = (url, msg, cb) => { }) } else printUrl = `${msg}:\n ${url}` - - cb() } -const fund = requireInject('../../lib/fund.js', { - '../../lib/npm.js': { - flatOptions: _flatOptions, - get prefix () { - return _flatOptions.prefix - }, - }, +const Fund = requireInject('../../lib/fund.js', { '../../lib/utils/open-url.js': openUrl, '../../lib/utils/output.js': msg => { result += msg + '\n' @@ -222,6 +213,12 @@ const fund = requireInject('../../lib/fund.js', { : Promise.reject(new Error('ERROR')), }, }) +const fund = new Fund({ + flatOptions: _flatOptions, + get prefix () { + return _flatOptions.prefix + }, +}) test('fund with no package containing funding', t => { _flatOptions.prefix = t.testdir({ @@ -231,7 +228,7 @@ test('fund with no package containing funding', t => { }), }) - fund([], (err) => { + fund.exec([], (err) => { t.ifError(err, 'should not error out') t.matchSnapshot(result, 'should print empty funding info') result = '' @@ -242,7 +239,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) - fund([], (err) => { + fund.exec([], (err) => { t.ifError(err, 'should not error out') t.matchSnapshot(result, 'should print stack packages together') result = '' @@ -254,7 +251,7 @@ test('fund in which same maintainer owns all its deps, using --json option', t = _flatOptions.json = true _flatOptions.prefix = t.testdir(maintainerOwnsAllDeps) - fund([], (err) => { + fund.exec([], (err) => { t.ifError(err, 'should not error out') t.deepEqual( JSON.parse(result), @@ -292,7 +289,7 @@ test('fund in which same maintainer owns all its deps, using --json option', t = test('fund containing multi-level nested deps with no funding', t => { _flatOptions.prefix = t.testdir(nestedNoFundingPackages) - fund([], (err) => { + fund.exec([], (err) => { t.ifError(err, 'should not error out') t.matchSnapshot( result, @@ -308,7 +305,7 @@ test('fund containing multi-level nested deps with no funding, using --json opti _flatOptions.prefix = t.testdir(nestedNoFundingPackages) _flatOptions.json = true - fund([], (err) => { + fund.exec([], (err) => { t.ifError(err, 'should not error out') t.deepEqual( JSON.parse(result), @@ -340,7 +337,7 @@ test('fund containing multi-level nested deps with no funding, using --json opti _flatOptions.prefix = t.testdir(nestedMultipleFundingPackages) _flatOptions.json = true - fund([], (err) => { + fund.exec([], (err) => { t.ifError(err, 'should not error out') t.deepEqual( JSON.parse(result), @@ -397,7 +394,7 @@ test('fund does not support global', t => { _flatOptions.prefix = t.testdir({}) _flatOptions.global = true - fund([], (err) => { + fund.exec([], (err) => { t.match(err.code, 'EFUNDGLOBAL', 'should throw EFUNDGLOBAL error') result = '' @@ -409,7 +406,7 @@ test('fund does not support global', t => { test('fund using package argument', t => { _flatOptions.prefix = t.testdir(maintainerOwnsAllDeps) - fund(['.'], (err) => { + fund.exec(['.'], (err) => { t.ifError(err, 'should not error out') t.matchSnapshot(printUrl, 'should open funding url') @@ -423,7 +420,7 @@ test('fund does not support global, using --json option', t => { _flatOptions.global = true _flatOptions.json = true - fund([], (err) => { + fund.exec([], (err) => { t.equal(err.code, 'EFUNDGLOBAL', 'should use EFUNDGLOBAL error code') t.equal( err.message, @@ -446,7 +443,7 @@ test('fund using string shorthand', t => { }), }) - fund(['.'], (err) => { + fund.exec(['.'], (err) => { t.ifError(err, 'should not error out') t.matchSnapshot(printUrl, 'should open string-only url') @@ -458,7 +455,7 @@ test('fund using string shorthand', t => { test('fund using nested packages with multiple sources', t => { _flatOptions.prefix = t.testdir(nestedMultipleFundingPackages) - fund(['.'], (err) => { + fund.exec(['.'], (err) => { t.ifError(err, 'should not error out') t.matchSnapshot(result, 'should prompt with all available URLs') @@ -486,7 +483,7 @@ test('fund using symlink ref', t => { }) // using symlinked ref - fund(['./node_modules/a'], (err) => { + fund.exec(['./node_modules/a'], (err) => { t.ifError(err, 'should not error out') t.match( printUrl, @@ -497,7 +494,7 @@ test('fund using symlink ref', t => { printUrl = '' // using target ref - fund(['./a'], (err) => { + fund.exec(['./a'], (err) => { t.ifError(err, 'should not error out') t.match( @@ -547,7 +544,7 @@ test('fund using data from actual tree', t => { }) // using symlinked ref - fund(['a'], (err) => { + fund.exec(['a'], (err) => { t.ifError(err, 'should not error out') t.match( printUrl, @@ -564,7 +561,7 @@ test('fund using nested packages with multiple sources, with a source number', t _flatOptions.prefix = t.testdir(nestedMultipleFundingPackages) _flatOptions.which = '1' - fund(['.'], (err) => { + fund.exec(['.'], (err) => { t.ifError(err, 'should not error out') t.matchSnapshot(printUrl, 'should open the numbered URL') @@ -578,7 +575,7 @@ test('fund using pkg name while having conflicting versions', t => { _flatOptions.prefix = t.testdir(conflictingFundingPackages) _flatOptions.which = '1' - fund(['foo'], (err) => { + fund.exec(['foo'], (err) => { t.ifError(err, 'should not error out') t.matchSnapshot(printUrl, 'should open greatest version') @@ -591,7 +588,7 @@ test('fund using package argument with no browser, using --json option', t => { _flatOptions.prefix = t.testdir(maintainerOwnsAllDeps) _flatOptions.json = true - fund(['.'], (err) => { + fund.exec(['.'], (err) => { t.ifError(err, 'should not error out') t.deepEqual( JSON.parse(printUrl), @@ -611,7 +608,7 @@ test('fund using package argument with no browser, using --json option', t => { test('fund using package info fetch from registry', t => { _flatOptions.prefix = t.testdir({}) - fund(['ntl'], (err) => { + fund.exec(['ntl'], (err) => { t.ifError(err, 'should not error out') t.match( printUrl, @@ -627,7 +624,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({}) - fund(['foo'], (err) => { + fund.exec(['foo'], (err) => { t.equal(err.code, 'ENOFUND', 'should have ENOFUND error code') t.equal( err.message, @@ -643,7 +640,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) - fund(['foo'], (err) => { + fund.exec(['foo'], (err) => { t.equal(err.code, 'ENOFUND', 'should have ENOFUND error code') t.equal( err.message, @@ -660,7 +657,7 @@ test('fund using bad which value', t => { _flatOptions.prefix = t.testdir(nestedMultipleFundingPackages) _flatOptions.which = 3 - fund(['bar'], (err) => { + fund.exec(['bar'], (err) => { t.equal(err.code, 'EFUNDNUMBER', 'should have EFUNDNUMBER error code') t.equal( err.message, @@ -682,7 +679,7 @@ test('fund pkg missing version number', t => { }), }) - fund([], (err) => { + fund.exec([], (err) => { t.ifError(err, 'should not error out') t.matchSnapshot(result, 'should print name only') result = '' @@ -699,7 +696,7 @@ test('fund a package throws on openUrl', t => { }), }) - fund(['.'], (err) => { + fund.exec(['.'], (err) => { t.equal(err.message, 'ERROR', 'should throw unknown error') result = '' t.end() @@ -723,7 +720,7 @@ test('fund a package with type and multiple sources', t => { }), }) - fund(['.'], (err) => { + fund.exec(['.'], (err) => { t.ifError(err, 'should not error out') t.matchSnapshot(result, 'should print prompt select message') @@ -787,7 +784,7 @@ test('fund colors', t => { }) _flatOptions.color = true - fund([], (err) => { + fund.exec([], (err) => { t.ifError(err, 'should not error out') t.matchSnapshot(result, 'should print output with color info') @@ -837,7 +834,7 @@ test('sub dep with fund info and a parent with no funding info', t => { }, }) - fund([], (err) => { + fund.exec([], (err) => { t.ifError(err, 'should not error out') t.matchSnapshot(result, 'should nest sub dep as child of root') diff --git a/deps/npm/test/lib/get.js b/deps/npm/test/lib/get.js index 5260c00bae..a11597d268 100644 --- a/deps/npm/test/lib/get.js +++ b/deps/npm/test/lib/get.js @@ -2,17 +2,16 @@ const { test } = require('tap') const requireInject = require('require-inject') test('should retrieve values from npm.commands.config', (t) => { - const get = requireInject('../../lib/get.js', { - '../../lib/npm.js': { - commands: { - config: ([action, arg]) => { - t.equal(action, 'get', 'should use config get action') - t.equal(arg, 'foo', 'should use expected key') - t.end() - }, + const Get = requireInject('../../lib/get.js') + const get = new Get({ + commands: { + config: ([action, arg]) => { + t.equal(action, 'get', 'should use config get action') + t.equal(arg, 'foo', 'should use expected key') + t.end() }, }, }) - get(['foo']) + get.exec(['foo']) }) diff --git a/deps/npm/test/lib/help-search.js b/deps/npm/test/lib/help-search.js index f74e2f1efe..8b1ecd46eb 100644 --- a/deps/npm/test/lib/help-search.js +++ b/deps/npm/test/lib/help-search.js @@ -24,7 +24,7 @@ const npm = { } let npmUsageArg = null -const npmUsage = (arg) => { +const npmUsage = (npm, arg) => { npmUsageArg = arg } @@ -43,12 +43,12 @@ const globDir = { const glob = (p, cb) => cb(null, Object.keys(globDir).map((file) => join(globRoot, file))) -const helpSearch = requireInject('../../lib/help-search.js', { - '../../lib/npm.js': npm, +const HelpSearch = requireInject('../../lib/help-search.js', { '../../lib/utils/npm-usage.js': npmUsage, '../../lib/utils/output.js': output, glob, }) +const helpSearch = new HelpSearch(npm) test('npm help-search', t => { globRoot = t.testdir(globDir) @@ -57,7 +57,7 @@ test('npm help-search', t => { globRoot = null }) - return helpSearch(['exec'], (err) => { + return helpSearch.exec(['exec'], (err) => { if (err) throw err @@ -74,7 +74,7 @@ test('npm help-search multiple terms', t => { globRoot = null }) - return helpSearch(['run', 'script'], (err) => { + return helpSearch.exec(['run', 'script'], (err) => { if (err) throw err @@ -92,7 +92,7 @@ test('npm help-search single result prints full section', t => { globRoot = null }) - return helpSearch(['does not exist in'], (err) => { + return helpSearch.exec(['does not exist in'], (err) => { if (err) throw err @@ -111,7 +111,7 @@ test('npm help-search single result propagates error', t => { globRoot = null }) - return helpSearch(['does not exist in'], (err) => { + return helpSearch.exec(['does not exist in'], (err) => { t.strictSame(npmHelpArgs, ['npm-install'], 'identified the correct man page and called help with it') t.match(err, /help broke/, 'propagated the error from help') t.end() @@ -127,7 +127,7 @@ test('npm help-search long output', t => { globRoot = null }) - return helpSearch(['exec'], (err) => { + return helpSearch.exec(['exec'], (err) => { if (err) throw err @@ -147,7 +147,7 @@ test('npm help-search long output with color', t => { globRoot = null }) - return helpSearch(['help-search'], (err) => { + return helpSearch.exec(['help-search'], (err) => { if (err) throw err @@ -158,7 +158,7 @@ test('npm help-search long output with color', t => { }) test('npm help-search no args', t => { - return helpSearch([], (err) => { + return helpSearch.exec([], (err) => { t.match(err, /npm help-search/, 'throws usage') t.end() }) @@ -172,7 +172,7 @@ test('npm help-search no matches', t => { globRoot = null }) - return helpSearch(['asdfasdf'], (err) => { + return helpSearch.exec(['asdfasdf'], (err) => { if (err) throw err diff --git a/deps/npm/test/lib/help.js b/deps/npm/test/lib/help.js index fc4a32e07b..addbe4dcc1 100644 --- a/deps/npm/test/lib/help.js +++ b/deps/npm/test/lib/help.js @@ -3,7 +3,7 @@ const requireInject = require('require-inject') const { EventEmitter } = require('events') let npmUsageArg = null -const npmUsage = (arg) => { +const npmUsage = (npm, arg) => { npmUsageArg = arg } @@ -67,13 +67,11 @@ const spawn = (bin, args) => { } let openUrlArg = null -const openUrl = (url, msg, cb) => { +const openUrl = async (npm, url, msg) => { openUrlArg = url - return cb() } -const help = requireInject('../../lib/help.js', { - '../../lib/npm.js': npm, +const Help = requireInject('../../lib/help.js', { '../../lib/utils/npm-usage.js': npmUsage, '../../lib/utils/open-url.js': openUrl, '../../lib/utils/output.js': output, @@ -82,13 +80,14 @@ const help = requireInject('../../lib/help.js', { }, glob, }) +const help = new Help(npm) test('npm help', t => { t.teardown(() => { npmUsageArg = null }) - return help([], (err) => { + return help.exec([], (err) => { if (err) throw err @@ -117,7 +116,7 @@ test('npm help -h', t => { OUTPUT.length = 0 }) - return help(['help'], (err) => { + return help.exec(['help'], (err) => { if (err) throw err @@ -131,7 +130,7 @@ test('npm help multiple args calls search', t => { helpSearchArgs = null }) - return help(['run', 'script'], (err) => { + return help.exec(['run', 'script'], (err) => { if (err) throw err @@ -147,7 +146,7 @@ test('npm help no matches calls search', t => { globResult = globDefaults }) - return help(['asdfasdf'], (err) => { + return help.exec(['asdfasdf'], (err) => { if (err) throw err @@ -164,7 +163,7 @@ test('npm help glob errors propagate', t => { spawnArgs = null }) - return help(['whoami'], (err) => { + return help.exec(['whoami'], (err) => { t.match(err, /glob failed/, 'glob error propagates') t.end() }) @@ -178,7 +177,7 @@ test('npm help whoami', t => { spawnArgs = null }) - return help(['whoami'], (err) => { + return help.exec(['whoami'], (err) => { if (err) throw err @@ -202,7 +201,7 @@ test('npm help 1 install', t => { spawnArgs = null }) - return help(['1', 'install'], (err) => { + return help.exec(['1', 'install'], (err) => { if (err) throw err @@ -225,7 +224,7 @@ test('npm help 5 install', t => { spawnArgs = null }) - return help(['5', 'install'], (err) => { + return help.exec(['5', 'install'], (err) => { if (err) throw err @@ -247,7 +246,7 @@ test('npm help 7 config', t => { spawnArgs = null }) - return help(['7', 'config'], (err) => { + return help.exec(['7', 'config'], (err) => { if (err) throw err @@ -270,7 +269,7 @@ test('npm help with browser viewer and invalid section throws', t => { spawnArgs = null }) - return help(['9', 'config'], (err) => { + return help.exec(['9', 'config'], (err) => { t.match(err, /invalid man section: 9/, 'throws appropriate error') t.end() }) @@ -284,7 +283,7 @@ test('npm help global redirects to folders', t => { spawnArgs = null }) - return help(['global'], (err) => { + return help.exec(['global'], (err) => { if (err) throw err @@ -302,7 +301,7 @@ test('npm help package.json redirects to package-json', t => { spawnArgs = null }) - return help(['package.json'], (err) => { + return help.exec(['package.json'], (err) => { if (err) throw err @@ -325,7 +324,7 @@ test('npm help ?(un)star', t => { spawnArgs = null }) - return help(['?(un)star'], (err) => { + return help.exec(['?(un)star'], (err) => { if (err) throw err @@ -350,7 +349,7 @@ test('npm help - woman viewer propagates errors', t => { spawnArgs = null }) - return help(['?(un)star'], (err) => { + return help.exec(['?(un)star'], (err) => { t.match(err, /help process exited with code: 1/, 'received the correct error') t.equal(spawnBin, 'emacsclient', 'maps woman to emacs correctly') t.strictSame(spawnArgs, ['-e', `(woman-find-file '/root/man/man1/npm-unstar.1')`], 'passes the correct arguments') @@ -370,7 +369,7 @@ test('npm help un*', t => { spawnArgs = null }) - return help(['un*'], (err) => { + return help.exec(['un*'], (err) => { if (err) throw err @@ -394,7 +393,7 @@ test('npm help - man viewer propagates errors', t => { spawnArgs = null }) - return help(['un*'], (err) => { + return help.exec(['un*'], (err) => { t.match(err, /help process exited with code: 1/, 'received correct error') t.equal(spawnBin, 'man', 'calls man by default') t.strictSame(spawnArgs, ['1', 'npm-unstar'], 'passes the correct arguments') diff --git a/deps/npm/test/lib/hook.js b/deps/npm/test/lib/hook.js index 3599042021..923f86e81d 100644 --- a/deps/npm/test/lib/hook.js +++ b/deps/npm/test/lib/hook.js @@ -52,17 +52,17 @@ const libnpmhook = { } const output = [] -const hook = requireInject('../../lib/hook.js', { - '../../lib/npm.js': npm, +const Hook = requireInject('../../lib/hook.js', { '../../lib/utils/otplease.js': async (opts, fn) => fn(opts), '../../lib/utils/output.js': (msg) => { output.push(msg) }, libnpmhook, }) +const hook = new Hook(npm) test('npm hook no args', t => { - return hook([], (err) => { + return hook.exec([], (err) => { t.match(err, /npm hook add/, 'throws usage with no arguments') t.end() }) @@ -74,7 +74,7 @@ test('npm hook add', t => { output.length = 0 }) - return hook(['add', 'semver', 'https://google.com', 'some-secret'], (err) => { + return hook.exec(['add', 'semver', 'https://google.com', 'some-secret'], (err) => { if (err) throw err @@ -97,7 +97,7 @@ test('npm hook add - unicode output', t => { output.length = 0 }) - return hook(['add', 'semver', 'https://google.com', 'some-secret'], (err) => { + return hook.exec(['add', 'semver', 'https://google.com', 'some-secret'], (err) => { if (err) throw err @@ -120,7 +120,7 @@ test('npm hook add - json output', t => { output.length = 0 }) - return hook(['add', '@npmcli', 'https://google.com', 'some-secret'], (err) => { + return hook.exec(['add', '@npmcli', 'https://google.com', 'some-secret'], (err) => { if (err) throw err @@ -148,7 +148,7 @@ test('npm hook add - parseable output', t => { output.length = 0 }) - return hook(['add', '@npmcli', 'https://google.com', 'some-secret'], (err) => { + return hook.exec(['add', '@npmcli', 'https://google.com', 'some-secret'], (err) => { if (err) throw err @@ -176,7 +176,7 @@ test('npm hook add - silent output', t => { output.length = 0 }) - return hook(['add', '@npmcli', 'https://google.com', 'some-secret'], (err) => { + return hook.exec(['add', '@npmcli', 'https://google.com', 'some-secret'], (err) => { if (err) throw err @@ -197,7 +197,7 @@ test('npm hook ls', t => { output.length = 0 }) - return hook(['ls'], (err) => { + return hook.exec(['ls'], (err) => { if (err) throw err @@ -222,7 +222,7 @@ test('npm hook ls, no results', t => { output.length = 0 }) - return hook(['ls'], (err) => { + return hook.exec(['ls'], (err) => { if (err) throw err @@ -249,7 +249,7 @@ test('npm hook ls, single result', t => { output.length = 0 }) - return hook(['ls'], (err) => { + return hook.exec(['ls'], (err) => { if (err) throw err @@ -272,7 +272,7 @@ test('npm hook ls - json output', t => { output.length = 0 }) - return hook(['ls'], (err) => { + return hook.exec(['ls'], (err) => { if (err) throw err @@ -309,7 +309,7 @@ test('npm hook ls - parseable output', t => { output.length = 0 }) - return hook(['ls'], (err) => { + return hook.exec(['ls'], (err) => { if (err) throw err @@ -335,7 +335,7 @@ test('npm hook ls - silent output', t => { output.length = 0 }) - return hook(['ls'], (err) => { + return hook.exec(['ls'], (err) => { if (err) throw err @@ -354,7 +354,7 @@ test('npm hook rm', t => { output.length = 0 }) - return hook(['rm', '1'], (err) => { + return hook.exec(['rm', '1'], (err) => { if (err) throw err @@ -377,7 +377,7 @@ test('npm hook rm - unicode output', t => { output.length = 0 }) - return hook(['rm', '1'], (err) => { + return hook.exec(['rm', '1'], (err) => { if (err) throw err @@ -400,7 +400,7 @@ test('npm hook rm - silent output', t => { output.length = 0 }) - return hook(['rm', '1'], (err) => { + return hook.exec(['rm', '1'], (err) => { if (err) throw err @@ -421,7 +421,7 @@ test('npm hook rm - json output', t => { output.length = 0 }) - return hook(['rm', '1'], (err) => { + return hook.exec(['rm', '1'], (err) => { if (err) throw err @@ -447,7 +447,7 @@ test('npm hook rm - parseable output', t => { output.length = 0 }) - return hook(['rm', '1'], (err) => { + return hook.exec(['rm', '1'], (err) => { if (err) throw err @@ -469,7 +469,7 @@ test('npm hook update', t => { output.length = 0 }) - return hook(['update', '1', 'https://google.com', 'some-secret'], (err) => { + return hook.exec(['update', '1', 'https://google.com', 'some-secret'], (err) => { if (err) throw err @@ -494,7 +494,7 @@ test('npm hook update - unicode', t => { output.length = 0 }) - return hook(['update', '1', 'https://google.com', 'some-secret'], (err) => { + return hook.exec(['update', '1', 'https://google.com', 'some-secret'], (err) => { if (err) throw err @@ -519,7 +519,7 @@ test('npm hook update - json output', t => { output.length = 0 }) - return hook(['update', '1', 'https://google.com', 'some-secret'], (err) => { + return hook.exec(['update', '1', 'https://google.com', 'some-secret'], (err) => { if (err) throw err @@ -547,7 +547,7 @@ test('npm hook update - parseable output', t => { output.length = 0 }) - return hook(['update', '1', 'https://google.com', 'some-secret'], (err) => { + return hook.exec(['update', '1', 'https://google.com', 'some-secret'], (err) => { if (err) throw err @@ -573,7 +573,7 @@ test('npm hook update - silent output', t => { output.length = 0 }) - return hook(['update', '1', 'https://google.com', 'some-secret'], (err) => { + return hook.exec(['update', '1', 'https://google.com', 'some-secret'], (err) => { if (err) throw err diff --git a/deps/npm/test/lib/init.js b/deps/npm/test/lib/init.js index e73cc4b309..db5411ba76 100644 --- a/deps/npm/test/lib/init.js +++ b/deps/npm/test/lib/init.js @@ -17,13 +17,13 @@ const npm = { } const mocks = { 'init-package-json': (dir, initFile, config, cb) => cb(null, 'data'), - '../../lib/npm.js': npm, '../../lib/utils/usage.js': () => 'usage instructions', '../../lib/utils/output.js': (...msg) => { result += msg.join('\n') }, } -const init = requireInject('../../lib/init.js', mocks) +const Init = requireInject('../../lib/init.js', mocks) +const init = new Init(npm) t.afterEach(cb => { result = '' @@ -40,7 +40,7 @@ t.test('classic npm init no args', t => { return '~/.npm-init.js' }, } - init([], err => { + init.exec([], err => { t.ifError(err, 'npm init no args') t.matchSnapshot(result, 'should print helper info') t.end() @@ -65,7 +65,7 @@ t.test('classic npm init -y', t => { t.equal(title, 'init', 'should print title') t.equal(msg, 'written successfully', 'should print done info') } - init([], err => { + init.exec([], err => { t.ifError(err, 'npm init -y') t.equal(result, '') }) @@ -87,7 +87,7 @@ t.test('npm init <arg>', t => { ) cb() } - init(['react-app'], err => { + init.exec(['react-app'], err => { t.ifError(err, 'npm init react-app') }) }) @@ -102,7 +102,7 @@ t.test('npm init @scope/name', t => { ) cb() } - init(['@npmcli/something'], err => { + init.exec(['@npmcli/something'], err => { t.ifError(err, 'npm init init @scope/name') }) }) @@ -117,7 +117,7 @@ t.test('npm init git spec', t => { ) cb() } - init(['npm/something'], err => { + init.exec(['npm/something'], err => { t.ifError(err, 'npm init init @scope/name') }) }) @@ -132,13 +132,13 @@ t.test('npm init @scope', t => { ) cb() } - init(['@npmcli'], err => { + init.exec(['@npmcli'], err => { t.ifError(err, 'npm init init @scope/create') }) }) t.test('npm init tgz', t => { - init(['something.tgz'], err => { + init.exec(['something.tgz'], err => { t.match( err, /Error: Unrecognized initializer: something.tgz/, @@ -158,7 +158,7 @@ t.test('npm init <arg>@next', t => { ) cb() } - init(['something@next'], err => { + init.exec(['something@next'], err => { t.ifError(err, 'npm init init something@next') }) }) @@ -167,7 +167,7 @@ t.test('npm init exec error', t => { npm.commands.exec = (arr, cb) => { cb(new Error('ERROR')) } - init(['something@next'], err => { + init.exec(['something@next'], err => { t.match( err, /ERROR/, @@ -199,37 +199,39 @@ t.test('should not rewrite flatOptions', t => { ) cb() } - init(['react-app', 'my-app'], err => { + init.exec(['react-app', 'my-app'], err => { t.ifError(err, 'npm init react-app') }) }) t.test('npm init cancel', t => { t.plan(3) - const init = requireInject('../../lib/init.js', { + const Init = requireInject('../../lib/init.js', { ...mocks, 'init-package-json': (dir, initFile, config, cb) => cb( new Error('canceled') ), }) + const init = new Init(npm) npm.log = { ...npm.log } npm.log.warn = (title, msg) => { t.equal(title, 'init', 'should have init title') t.equal(msg, 'canceled', 'should log canceled') } - init([], err => { + init.exec([], err => { t.ifError(err, 'npm init cancel') }) }) t.test('npm init error', t => { - const init = requireInject('../../lib/init.js', { + const Init = requireInject('../../lib/init.js', { ...mocks, 'init-package-json': (dir, initFile, config, cb) => cb( new Error('Unknown Error') ), }) - init([], err => { + const init = new Init(npm) + init.exec([], err => { t.match(err, /Unknown Error/, 'should throw error') t.end() }) diff --git a/deps/npm/test/lib/install-ci-test.js b/deps/npm/test/lib/install-ci-test.js new file mode 100644 index 0000000000..5f30efcabf --- /dev/null +++ b/deps/npm/test/lib/install-ci-test.js @@ -0,0 +1,57 @@ +const t = require('tap') + +const InstallCITest = require('../../lib/install-ci-test.js') + +let ciArgs = null +let ciCalled = false +let testArgs = null +let testCalled = false +let ciError = null + +const installCITest = new InstallCITest({ + commands: { + ci: (args, cb) => { + ciArgs = args + ciCalled = true + cb(ciError) + }, + test: (args, cb) => { + testArgs = args + testCalled = true + cb() + }, + }, +}) + +t.test('the install-ci-test command', t => { + t.afterEach(cb => { + ciArgs = null + ciCalled = false + testArgs = null + testCalled = false + ciError = null + cb() + }) + + t.test('ci and test', t => { + installCITest.exec(['extra'], () => { + t.equal(ciCalled, true) + t.equal(testCalled, true) + t.match(ciArgs, ['extra']) + t.match(testArgs, []) + t.end() + }) + }) + + t.test('ci fails', t => { + ciError = new Error('test fail') + installCITest.exec(['extra'], (err) => { + t.equal(ciCalled, true) + t.equal(testCalled, false) + t.match(ciArgs, ['extra']) + t.match(err, { message: 'test fail' }) + t.end() + }) + }) + t.end() +}) diff --git a/deps/npm/test/lib/install-test.js b/deps/npm/test/lib/install-test.js new file mode 100644 index 0000000000..0c52bd5e3c --- /dev/null +++ b/deps/npm/test/lib/install-test.js @@ -0,0 +1,57 @@ +const t = require('tap') + +const InstallTest = require('../../lib/install-test.js') + +let installArgs = null +let installCalled = false +let testArgs = null +let testCalled = false +let installError = null + +const installTest = new InstallTest({ + commands: { + install: (args, cb) => { + installArgs = args + installCalled = true + cb(installError) + }, + test: (args, cb) => { + testArgs = args + testCalled = true + cb() + }, + }, +}) + +t.test('the install-test command', t => { + t.afterEach(cb => { + installArgs = null + installCalled = false + testArgs = null + testCalled = false + installError = null + cb() + }) + + t.test('install and test', t => { + installTest.exec(['extra'], () => { + t.equal(installCalled, true) + t.equal(testCalled, true) + t.match(installArgs, ['extra']) + t.match(testArgs, []) + t.end() + }) + }) + + t.test('install fails', t => { + installError = new Error('test fail') + installTest.exec(['extra'], (err) => { + t.equal(installCalled, true) + t.equal(testCalled, false) + t.match(installArgs, ['extra']) + t.match(err, { message: 'test fail' }) + t.end() + }) + }) + t.end() +}) diff --git a/deps/npm/test/lib/install.js b/deps/npm/test/lib/install.js index 859a4bdaaa..8b7a968511 100644 --- a/deps/npm/test/lib/install.js +++ b/deps/npm/test/lib/install.js @@ -1,6 +1,6 @@ const { test } = require('tap') -const install = require('../../lib/install.js') +const Install = require('../../lib/install.js') const requireInject = require('require-inject') test('should install using Arborist', (t) => { @@ -9,17 +9,7 @@ test('should install using Arborist', (t) => { let REIFY_CALLED = false let ARB_OBJ = null - const install = requireInject('../../lib/install.js', { - '../../lib/npm.js': { - globalDir: 'path/to/node_modules/', - prefix: 'foo', - flatOptions: { - global: false, - }, - config: { - get: () => true, - }, - }, + const Install = requireInject('../../lib/install.js', { '@npmcli/run-script': ({ event }) => { SCRIPTS.push(event) }, @@ -33,14 +23,24 @@ test('should install using Arborist', (t) => { REIFY_CALLED = true } }, - '../../lib/utils/reify-finish.js': arb => { + '../../lib/utils/reify-finish.js': (npm, arb) => { if (arb !== ARB_OBJ) throw new Error('got wrong object passed to reify-finish') }, }) + const install = new Install({ + globalDir: 'path/to/node_modules/', + prefix: 'foo', + flatOptions: { + global: false, + }, + config: { + get: () => true, + }, + }) t.test('with args', t => { - install(['fizzbuzz'], er => { + install.exec(['fizzbuzz'], er => { if (er) throw er t.match(ARB_ARGS, { global: false, path: 'foo' }) @@ -51,7 +51,7 @@ test('should install using Arborist', (t) => { }) t.test('just a local npm install', t => { - install([], er => { + install.exec([], er => { if (er) throw er t.match(ARB_ARGS, { global: false, path: 'foo' }) @@ -75,19 +75,8 @@ test('should install using Arborist', (t) => { test('should ignore scripts with --ignore-scripts', (t) => { const SCRIPTS = [] let REIFY_CALLED = false - const install = requireInject('../../lib/install.js', { + const Install = requireInject('../../lib/install.js', { '../../lib/utils/reify-finish.js': async () => {}, - '../../lib/npm.js': { - globalDir: 'path/to/node_modules/', - prefix: 'foo', - flatOptions: { - global: false, - ignoreScripts: true, - }, - config: { - get: () => false, - }, - }, '@npmcli/run-script': ({ event }) => { SCRIPTS.push(event) }, @@ -97,7 +86,18 @@ test('should ignore scripts with --ignore-scripts', (t) => { } }, }) - install([], er => { + const install = new Install({ + globalDir: 'path/to/node_modules/', + prefix: 'foo', + flatOptions: { + global: false, + ignoreScripts: true, + }, + config: { + get: () => false, + }, + }) + install.exec([], er => { if (er) throw er t.equal(REIFY_CALLED, true, 'called reify') @@ -107,23 +107,23 @@ test('should ignore scripts with --ignore-scripts', (t) => { }) test('should install globally using Arborist', (t) => { - const install = requireInject('../../lib/install.js', { + const Install = requireInject('../../lib/install.js', { '../../lib/utils/reify-finish.js': async () => {}, - '../../lib/npm.js': { - globalDir: 'path/to/node_modules/', - prefix: 'foo', - flatOptions: { - global: true, - }, - config: { - get: () => false, - }, - }, '@npmcli/arborist': function () { this.reify = () => {} }, }) - install([], er => { + const install = new Install({ + globalDir: 'path/to/node_modules/', + prefix: 'foo', + flatOptions: { + global: true, + }, + config: { + get: () => false, + }, + }) + install.exec([], er => { if (er) throw er t.end() @@ -131,7 +131,7 @@ test('should install globally using Arborist', (t) => { }) test('completion to folder', async t => { - const install = requireInject('../../lib/install.js', { + const Install = requireInject('../../lib/install.js', { '../../lib/utils/reify-finish.js': async () => {}, util: { promisify: (fn) => fn, @@ -145,6 +145,7 @@ test('completion to folder', async t => { }, }, }) + const install = new Install({}) const res = await install.completion({ partialWord: '/ar' }) const expect = process.platform === 'win32' ? '\\arborist' : '/arborist' t.strictSame(res, [expect], 'package dir match') @@ -152,7 +153,7 @@ test('completion to folder', async t => { }) test('completion to folder - invalid dir', async t => { - const install = requireInject('../../lib/install.js', { + const Install = requireInject('../../lib/install.js', { '../../lib/utils/reify-finish.js': async () => {}, util: { promisify: (fn) => fn, @@ -163,13 +164,14 @@ test('completion to folder - invalid dir', async t => { }, }, }) + const install = new Install({}) const res = await install.completion({ partialWord: 'path/to/folder' }) t.strictSame(res, [], 'invalid dir: no matching') t.end() }) test('completion to folder - no matches', async t => { - const install = requireInject('../../lib/install.js', { + const Install = requireInject('../../lib/install.js', { '../../lib/utils/reify-finish.js': async () => {}, util: { promisify: (fn) => fn, @@ -180,13 +182,14 @@ test('completion to folder - no matches', async t => { }, }, }) + const install = new Install({}) const res = await install.completion({ partialWord: '/pa' }) t.strictSame(res, [], 'no name match') t.end() }) test('completion to folder - match is not a package', async t => { - const install = requireInject('../../lib/install.js', { + const Install = requireInject('../../lib/install.js', { '../../lib/utils/reify-finish.js': async () => {}, util: { promisify: (fn) => fn, @@ -200,18 +203,21 @@ test('completion to folder - match is not a package', async t => { }, }, }) + const install = new Install({}) const res = await install.completion({ partialWord: '/ar' }) t.strictSame(res, [], 'no name match') t.end() }) test('completion to url', async t => { + const install = new Install({}) const res = await install.completion({ partialWord: 'http://path/to/url' }) t.strictSame(res, []) t.end() }) test('completion', async t => { + const install = new Install({}) const res = await install.completion({ partialWord: 'toto' }) t.notOk(res) t.end() diff --git a/deps/npm/test/lib/link.js b/deps/npm/test/lib/link.js index b1048427d7..be7af3f524 100644 --- a/deps/npm/test/lib/link.js +++ b/deps/npm/test/lib/link.js @@ -40,11 +40,11 @@ const printLinks = async (opts) => { } const mocks = { - '../../lib/npm.js': npm, '../../lib/utils/reify-output.js': () => reifyOutput(), } -const link = requireInject('../../lib/link.js', mocks) +const Link = requireInject('../../lib/link.js', mocks) +const link = new Link(npm) t.test('link to globalDir when in current working dir of pkg and no args', (t) => { t.plan(2) @@ -83,7 +83,7 @@ t.test('link to globalDir when in current working dir of pkg and no args', (t) = t.matchSnapshot(links, 'should create a global link to current pkg') } - link([], (err) => { + link.exec([], (err) => { t.ifError(err, 'should not error out') }) }) @@ -185,7 +185,7 @@ t.test('link global linked pkg to local nm when using args', (t) => { // - @myscope/bar: prev installed scoped package available in globalDir // - a: prev installed package available in globalDir // - file:./link-me-too: pkg that needs to be reified in globalDir first - link([ + link.exec([ 'test-pkg-link', '@myscope/linked', '@myscope/bar', @@ -254,7 +254,7 @@ t.test('link pkg already in global space', (t) => { // - @myscope/bar: prev installed scoped package available in globalDir // - a: prev installed package available in globalDir // - file:./link-me-too: pkg that needs to be reified in globalDir first - link(['@myscope/linked'], (err) => { + link.exec(['@myscope/linked'], (err) => { t.ifError(err, 'should not error out') }) }) @@ -312,7 +312,7 @@ t.test('link pkg already in global space when prefix is a symlink', (t) => { t.matchSnapshot(links, 'should create a local symlink to global pkg') } - link(['@myscope/linked'], (err) => { + link.exec(['@myscope/linked'], (err) => { t.ifError(err, 'should not error out') }) }) @@ -341,21 +341,18 @@ t.test('completion', async t => { t.end() }) -t.test('--global option', async t => { +t.test('--global option', t => { const _config = npm.config npm.config = { get () { return true } } - try { - await link([]) - t.fail('should not get here') - } catch (err) { + link.exec([], (err) => { npm.config = _config t.match( err.message, /link should never be --global/, 'should throw an useful error' ) - } - t.end() + t.end() + }) }) diff --git a/deps/npm/test/lib/ll.js b/deps/npm/test/lib/ll.js index 7d4e2b94f2..45eb4ec95b 100644 --- a/deps/npm/test/lib/ll.js +++ b/deps/npm/test/lib/ll.js @@ -1,31 +1,34 @@ -const t = require('tap') const requireInject = require('require-inject') -const configs = {} -let lsCalled = false -const ll = requireInject('../../lib/ll.js', { - '../../lib/npm.js': { +const t = require('tap') + +t.test('ll', t => { + t.plan(3) + + class LS { + constructor (npm) { + this.npm = npm + } + + exec (args, cb) { + t.deepEqual(args, ['pkg'], 'should forward args') + cb() + } + } + + const LL = requireInject('../../lib/ll.js', { + '../../lib/ls.js': LS, + }) + const ll = new LL({ config: { - set: (k, v) => { - configs[k] = v - }, - }, - commands: { - ls: (args, cb) => { - lsCalled = true - cb() + set: (key, value) => { + t.equal(key, 'long', 'should set long config value') + t.equal(value, true, 'should set a truthy value') }, }, - }, -}) + }) -const ls = require('../../lib/ls.js') -const { usage, completion } = ls -t.equal(ll.usage, usage) -t.equal(ll.completion.toString(), completion.toString()) -t.test('the ll command', t => { - ll([], () => { - t.equal(lsCalled, true) - t.strictSame(configs, { long: true }) - t.end() + ll.exec(['pkg'], err => { + if (err) + throw err }) }) diff --git a/deps/npm/test/lib/load-all-commands.js b/deps/npm/test/lib/load-all-commands.js index 02cb0be650..f6d1ae9e18 100644 --- a/deps/npm/test/lib/load-all-commands.js +++ b/deps/npm/test/lib/load-all-commands.js @@ -1,4 +1,6 @@ -// just for gathering coverage info +// Thanks to nyc not working properly with proxies this +// doesn't affect coverage. but it does ensure that every command +// has a usage, and if it has completion it is a function const npm = require('../../lib/npm.js') const t = require('tap') const { cmdList } = require('../../lib/utils/cmd-list.js') @@ -19,7 +21,7 @@ t.test('load each command', t => { } else t.plan(2) t.isa(impl, 'function', 'implementation is a function') - t.isa(impl.usage, 'string', 'usage is a string') + t.match(impl.usage, cmd, 'usage contains the command') }) } }) diff --git a/deps/npm/test/lib/logout.js b/deps/npm/test/lib/logout.js index 96b1bcc7fe..b00fa641d8 100644 --- a/deps/npm/test/lib/logout.js +++ b/deps/npm/test/lib/logout.js @@ -17,13 +17,13 @@ const npmFetch = (url, opts) => { const mocks = { npmlog, 'npm-registry-fetch': npmFetch, - '../../lib/npm.js': { - flatOptions: _flatOptions, - config, - }, } -const logout = requireInject('../../lib/logout.js', mocks) +const Logout = requireInject('../../lib/logout.js', mocks) +const logout = new Logout({ + flatOptions: _flatOptions, + config, +}) test('token logout', async (t) => { t.plan(6) @@ -52,7 +52,7 @@ test('token logout', async (t) => { } await new Promise((res, rej) => { - logout([], (err) => { + logout.exec([], (err) => { t.ifError(err, 'should not error out') t.deepEqual( @@ -121,7 +121,7 @@ test('token scoped logout', async (t) => { } await new Promise((res, rej) => { - logout([], (err) => { + logout.exec([], (err) => { t.ifError(err, 'should not error out') t.deepEqual( @@ -174,7 +174,7 @@ test('user/pass logout', async (t) => { config.save = () => null await new Promise((res, rej) => { - logout([], (err) => { + logout.exec([], (err) => { t.ifError(err, 'should not error out') delete _flatOptions.username @@ -189,7 +189,7 @@ test('user/pass logout', async (t) => { }) test('missing credentials', (t) => { - logout([], (err) => { + logout.exec([], (err) => { t.match( err.message, /not logged in to https:\/\/registry.npmjs.org\/, so can't log out!/, @@ -228,7 +228,7 @@ test('ignore invalid scoped registry config', async (t) => { config.save = () => null await new Promise((res, rej) => { - logout([], (err) => { + logout.exec([], (err) => { t.ifError(err, 'should not error out') t.deepEqual( diff --git a/deps/npm/test/lib/ls.js b/deps/npm/test/lib/ls.js index b1df9067c3..bd81776d5f 100644 --- a/deps/npm/test/lib/ls.js +++ b/deps/npm/test/lib/ls.js @@ -106,28 +106,28 @@ const _flatOptions = { }, production: false, } -const ls = requireInject('../../lib/ls.js', { - '../../lib/npm.js': { - flatOptions: _flatOptions, - limit: { - fetch: 3, - }, - get prefix () { - return _flatOptions.prefix - }, - get globalDir () { - return globalDir - }, - config: { - get (key) { - return _flatOptions[key] - }, - }, - }, +const LS = requireInject('../../lib/ls.js', { '../../lib/utils/output.js': msg => { result = msg }, }) +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 redactCwd = res => res && res.replace(/\\+/g, '/').replace(new RegExp(__dirname.replace(/\\+/g, '/'), 'gi'), '{CWD}') @@ -155,7 +155,7 @@ t.test('ls', (t) => { }), ...simpleNmFixture, }) - ls([], (err) => { + ls.exec([], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output tree representation of dependencies structure') t.end() @@ -166,7 +166,7 @@ t.test('ls', (t) => { prefix = t.testdir({ ...simpleNmFixture, }) - ls([], (err) => { + ls.exec([], (err) => { t.match(err.code, 'ELSPROBLEMS', 'should have ELSPROBLEMS error code') t.matchSnapshot( redactCwd(err.message), @@ -188,7 +188,7 @@ t.test('ls', (t) => { }), ...simpleNmFixture, }) - ls([], (err) => { + ls.exec([], (err) => { t.equal(err.code, 'ELSPROBLEMS', 'should have error code') t.equal( redactCwd(err.message), @@ -213,7 +213,7 @@ t.test('ls', (t) => { }), ...simpleNmFixture, }) - ls(['lorem'], (err) => { + 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 @@ -235,7 +235,7 @@ t.test('ls', (t) => { }), ...simpleNmFixture, }) - ls(['.'], (err) => { + 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 @@ -256,7 +256,7 @@ t.test('ls', (t) => { }), ...simpleNmFixture, }) - ls(['bar'], (err) => { + ls.exec(['bar'], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output tree contaning only occurrences of filtered package and its ancestors') t.end() @@ -284,7 +284,7 @@ t.test('ls', (t) => { }, }, }) - ls(['bar@*', 'lorem@1.0.0'], (err) => { + ls.exec(['bar@*', 'lorem@1.0.0'], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output tree contaning only occurrences of multiple filtered packages and their ancestors') t.end() @@ -303,7 +303,7 @@ t.test('ls', (t) => { }), ...simpleNmFixture, }) - ls(['notadep'], (err) => { + ls.exec(['notadep'], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output tree containing no dependencies info') t.equal( @@ -330,7 +330,7 @@ t.test('ls', (t) => { }), ...simpleNmFixture, }) - ls([], (err) => { + ls.exec([], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output tree containing only top-level dependencies') _flatOptions.all = true @@ -353,7 +353,7 @@ t.test('ls', (t) => { }), ...simpleNmFixture, }) - ls([], (err) => { + ls.exec([], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output tree containing only top-level dependencies') _flatOptions.all = true @@ -414,7 +414,7 @@ t.test('ls', (t) => { }, }, }) - ls([], (err) => { + 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 @@ -435,7 +435,7 @@ t.test('ls', (t) => { }), ...simpleNmFixture, }) - ls([], (err) => { + ls.exec([], (err) => { t.equal(err.code, 'ELSPROBLEMS', 'should have error code') t.equal( redactCwd(err.message).replace(/\r\n/g, '\n'), @@ -462,7 +462,7 @@ t.test('ls', (t) => { }), ...simpleNmFixture, }) - ls([], (err) => { + 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 @@ -492,7 +492,7 @@ t.test('ls', (t) => { }), ...diffDepTypesNmFixture, }) - ls([], () => { + ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing dev deps') _flatOptions.dev = false t.end() @@ -521,7 +521,7 @@ t.test('ls', (t) => { }), ...diffDepTypesNmFixture, }) - ls([], () => { + ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing only development deps') _flatOptions.only = null t.end() @@ -560,7 +560,7 @@ t.test('ls', (t) => { ...diffDepTypesNmFixture.node_modules, }, }) - ls([], () => { + ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing linked deps') _flatOptions.link = false t.end() @@ -596,7 +596,7 @@ t.test('ls', (t) => { b: t.fixture('symlink', '../b'), }, }) - ls([], () => { + ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing linked deps') _flatOptions.link = false t.end() @@ -625,7 +625,7 @@ t.test('ls', (t) => { }), ...diffDepTypesNmFixture, }) - ls([], () => { + ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing production deps') _flatOptions.production = false t.end() @@ -654,7 +654,7 @@ t.test('ls', (t) => { }), ...diffDepTypesNmFixture, }) - ls([], () => { + ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing only prod deps') _flatOptions.only = null t.end() @@ -683,7 +683,7 @@ t.test('ls', (t) => { }), ...diffDepTypesNmFixture, }) - ls([], () => { + ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree info with descriptions') _flatOptions.long = true t.end() @@ -714,7 +714,7 @@ t.test('ls', (t) => { }), ...diffDepTypesNmFixture, }) - ls([], () => { + ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing top-level deps with descriptions') _flatOptions.all = true _flatOptions.depth = Infinity @@ -727,7 +727,7 @@ t.test('ls', (t) => { prefix = t.testdir({ 'package.json': '{broken json', }) - ls([], (err) => { + ls.exec([], (err) => { t.match(err, { code: 'EJSONPARSE' }, 'should throw EJSONPARSE error') t.matchSnapshot(redactCwd(result), 'should print empty result') t.end() @@ -736,7 +736,7 @@ t.test('ls', (t) => { t.test('empty location', (t) => { prefix = t.testdir({}) - ls([], (err) => { + ls.exec([], (err) => { t.ifError(err, 'should not error out on empty locations') t.matchSnapshot(redactCwd(result), 'should print empty result') t.end() @@ -764,7 +764,7 @@ t.test('ls', (t) => { }), ...diffDepTypesNmFixture, }) - ls([], () => { + ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree signaling mismatching peer dep in problems') t.end() }) @@ -799,7 +799,7 @@ t.test('ls', (t) => { }, }, }) - ls([], () => { + ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree signaling mismatching peer dep in problems') _flatOptions.color = false t.end() @@ -828,7 +828,7 @@ t.test('ls', (t) => { }, }, }) - ls([], (err) => { + ls.exec([], (err) => { t.match(err.code, 'ELSPROBLEMS', 'should have ELSPROBLEMS error code') t.match(err.message, /missing: b@\^1.0.0/, 'should list missing dep problem') t.matchSnapshot(redactCwd(result), 'should output parseable signaling missing peer dep in problems') @@ -846,7 +846,7 @@ t.test('ls', (t) => { }, }), }) - ls([], (err) => { + ls.exec([], (err) => { t.match(err.code, 'ELSPROBLEMS', 'should have ELSPROBLEMS error code') t.match(err.message, 'missing: peer-dep@*, required by test-npm-ls@1.0.0', 'should have missing peer-dep error msg') t.matchSnapshot(redactCwd(result), 'should output tree signaling missing peer dep in problems') @@ -877,7 +877,7 @@ t.test('ls', (t) => { }), ...diffDepTypesNmFixture, }) - ls([], (err) => { + ls.exec([], (err) => { 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') @@ -916,7 +916,7 @@ t.test('ls', (t) => { }, }, }) - ls([], (err) => { + ls.exec([], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should print tree output containing deduped ref') t.end() @@ -954,7 +954,7 @@ t.test('ls', (t) => { }, }, }) - ls(['a'], (err) => { + ls.exec(['a'], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should print tree output containing deduped ref') _flatOptions.color = false @@ -1002,7 +1002,7 @@ t.test('ls', (t) => { }, }, }) - ls([], (err) => { + ls.exec([], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should print tree output containing deduped ref') t.end() @@ -1051,7 +1051,7 @@ t.test('ls', (t) => { }, }, }) - ls([], (err) => { + ls.exec([], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should print tree output containing deduped ref') _flatOptions.all = true @@ -1101,7 +1101,7 @@ t.test('ls', (t) => { }, }, }) - ls(['@npmcli/b'], (err) => { + ls.exec(['@npmcli/b'], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should print tree output containing deduped ref') _flatOptions.color = false @@ -1149,7 +1149,7 @@ t.test('ls', (t) => { }, }, }) - ls(['@npmcli/c'], (err) => { + ls.exec(['@npmcli/c'], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should print tree output containing deduped ref') t.end() @@ -1193,7 +1193,7 @@ t.test('ls', (t) => { }, }) touchHiddenPackageLock(prefix) - ls([], () => { + ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing aliases') t.end() }) @@ -1239,7 +1239,7 @@ t.test('ls', (t) => { }, }) touchHiddenPackageLock(prefix) - ls([], (err) => { + ls.exec([], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output tree containing git refs') t.end() @@ -1283,7 +1283,7 @@ t.test('ls', (t) => { }, }), }) - ls([], (err) => { + ls.exec([], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should NOT print git refs in output tree') t.end() @@ -1338,7 +1338,7 @@ t.test('ls', (t) => { }, }) touchHiddenPackageLock(prefix) - ls([], () => { + ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should not be printed in tree output') t.end() }) @@ -1374,7 +1374,7 @@ t.test('ls', (t) => { // mimics lib/npm.js globalDir getter but pointing to fixtures globalDir = resolve(fixtures, 'node_modules') - ls([], () => { + ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should print tree and not mark top-level items extraneous') globalDir = 'MISSING_GLOBAL_DIR' _flatOptions.global = false @@ -1427,7 +1427,7 @@ t.test('ls', (t) => { }, }) - ls(['c'], (err) => { + ls.exec(['c'], (err) => { t.match(err.code, 'ELSPROBLEMS', 'should have ELSPROBLEMS error code') t.matchSnapshot(redactCwd(result), 'should print tree and not duplicate child of missing items') t.end() @@ -1471,12 +1471,12 @@ t.test('ls', (t) => { }, }) - ls([], (err) => { + ls.exec([], (err) => { t.ifError(err, 'should NOT have ELSPROBLEMS error code') t.matchSnapshot(redactCwd(result), 'should list workspaces properly') // should also be able to filter out one of the workspaces - ls(['a'], (err) => { + ls.exec(['a'], (err) => { t.ifError(err, 'should NOT have ELSPROBLEMS error code when filter') t.matchSnapshot(redactCwd(result), 'should filter single workspace') @@ -1534,17 +1534,17 @@ t.test('ls', (t) => { }) t.plan(6) - ls(['a'], (err) => { + ls.exec(['a'], (err) => { t.ifError(err, 'should NOT have ELSPROBLEMS error code') t.matchSnapshot(redactCwd(result), 'should list a in top-level only') - ls(['d'], (err) => { + ls.exec(['d'], (err) => { t.ifError(err, 'should NOT have ELSPROBLEMS error code when filter') 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 - ls(['d'], (err) => { + ls.exec(['d'], (err) => { t.ifError(err, 'should NOT have ELSPROBLEMS error code when filter') t.matchSnapshot(redactCwd(result), 'should print expected result') }) @@ -1576,7 +1576,7 @@ t.test('ls --parseable', (t) => { }), ...simpleNmFixture, }) - ls([], (err) => { + ls.exec([], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output parseable representation of dependencies structure') t.end() @@ -1587,7 +1587,7 @@ t.test('ls --parseable', (t) => { prefix = t.testdir({ ...simpleNmFixture, }) - ls([], (err) => { + ls.exec([], (err) => { t.match(err.code, 'ELSPROBLEMS', 'should have ELSPROBLEMS error code') t.matchSnapshot( redactCwd(err.message), @@ -1609,7 +1609,7 @@ t.test('ls --parseable', (t) => { }), ...simpleNmFixture, }) - ls([], (err) => { + ls.exec([], (err) => { t.equal(err.code, 'ELSPROBLEMS', 'should have error code') t.matchSnapshot(redactCwd(result), 'should output containing problems info') t.end() @@ -1628,7 +1628,7 @@ t.test('ls --parseable', (t) => { }), ...simpleNmFixture, }) - ls(['lorem'], (err) => { + ls.exec(['lorem'], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output parseable contaning only occurrences of filtered by package') t.end() @@ -1647,7 +1647,7 @@ t.test('ls --parseable', (t) => { }), ...simpleNmFixture, }) - ls(['bar'], (err) => { + ls.exec(['bar'], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output parseable contaning only occurrences of filtered package') t.end() @@ -1675,7 +1675,7 @@ t.test('ls --parseable', (t) => { }, }, }) - ls(['bar@*', 'lorem@1.0.0'], (err) => { + ls.exec(['bar@*', 'lorem@1.0.0'], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output parseable contaning only occurrences of multiple filtered packages and their ancestors') t.end() @@ -1694,7 +1694,7 @@ t.test('ls --parseable', (t) => { }), ...simpleNmFixture, }) - ls(['notadep'], (err) => { + ls.exec(['notadep'], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output parseable output containing no dependencies info') t.equal( @@ -1721,7 +1721,7 @@ t.test('ls --parseable', (t) => { }), ...simpleNmFixture, }) - ls([], (err) => { + ls.exec([], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output parseable output containing only top-level dependencies') _flatOptions.all = true @@ -1744,7 +1744,7 @@ t.test('ls --parseable', (t) => { }), ...simpleNmFixture, }) - ls([], (err) => { + ls.exec([], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output tree containing only top-level dependencies') _flatOptions.all = true @@ -1767,7 +1767,7 @@ t.test('ls --parseable', (t) => { }), ...simpleNmFixture, }) - ls([], (err) => { + 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 @@ -1788,7 +1788,7 @@ t.test('ls --parseable', (t) => { }), ...simpleNmFixture, }) - ls([], (err) => { + ls.exec([], (err) => { t.match(err, { code: 'ELSPROBLEMS' }, 'should list dep problems') t.matchSnapshot(redactCwd(result), 'should output parseable containing top-level deps and their deps only') t.end() @@ -1817,7 +1817,7 @@ t.test('ls --parseable', (t) => { }), ...diffDepTypesNmFixture, }) - ls([], () => { + ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing dev deps') _flatOptions.dev = false t.end() @@ -1846,7 +1846,7 @@ t.test('ls --parseable', (t) => { }), ...diffDepTypesNmFixture, }) - ls([], () => { + ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing only development deps') _flatOptions.only = null t.end() @@ -1885,7 +1885,7 @@ t.test('ls --parseable', (t) => { ...diffDepTypesNmFixture.node_modules, }, }) - ls([], () => { + ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing linked deps') _flatOptions.link = false t.end() @@ -1914,7 +1914,7 @@ t.test('ls --parseable', (t) => { }), ...diffDepTypesNmFixture, }) - ls([], () => { + ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing production deps') _flatOptions.production = false t.end() @@ -1943,7 +1943,7 @@ t.test('ls --parseable', (t) => { }), ...diffDepTypesNmFixture, }) - ls([], () => { + ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing only prod deps') _flatOptions.only = null t.end() @@ -1972,7 +1972,7 @@ t.test('ls --parseable', (t) => { }), ...diffDepTypesNmFixture, }) - ls([], () => { + ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree info with descriptions') _flatOptions.long = true t.end() @@ -1990,7 +1990,7 @@ t.test('ls --parseable', (t) => { }), ...simpleNmFixture, }) - ls([], (err) => { + ls.exec([], (err) => { t.equal(err.code, 'ELSPROBLEMS', 'should have error code') t.match(redactCwd(err.message), 'extraneous: lorem@1.0.0 {CWD}/ls-ls-parseable--long-with-extraneous-deps/node_modules/lorem', 'should have error code') t.matchSnapshot(redactCwd(result), 'should output long parseable output with extraneous info') @@ -2011,7 +2011,7 @@ t.test('ls --parseable', (t) => { }), ...simpleNmFixture, }) - ls([], (err) => { + 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 @@ -2051,7 +2051,7 @@ t.test('ls --parseable', (t) => { ...diffDepTypesNmFixture.node_modules, }, }) - ls([], (err) => { + ls.exec([], (err) => { t.ifError(err, 'npm ls') t.matchSnapshot(redactCwd(result), 'should output parseable results with symlink targets') _flatOptions.long = false @@ -2083,7 +2083,7 @@ t.test('ls --parseable', (t) => { }), ...diffDepTypesNmFixture, }) - ls([], () => { + ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing top-level deps with descriptions') _flatOptions.all = true _flatOptions.depth = Infinity @@ -2096,7 +2096,7 @@ t.test('ls --parseable', (t) => { prefix = t.testdir({ 'package.json': '{broken json', }) - ls([], (err) => { + ls.exec([], (err) => { t.match(err, { code: 'EJSONPARSE' }, 'should throw EJSONPARSE error') t.matchSnapshot(redactCwd(result), 'should print empty result') t.end() @@ -2105,7 +2105,7 @@ t.test('ls --parseable', (t) => { t.test('empty location', (t) => { prefix = t.testdir({}) - ls([], (err) => { + ls.exec([], (err) => { t.ifError(err, 'should not error out on empty locations') t.matchSnapshot(redactCwd(result), 'should print empty result') t.end() @@ -2133,7 +2133,7 @@ t.test('ls --parseable', (t) => { }), ...diffDepTypesNmFixture, }) - ls([], () => { + ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output parseable signaling missing peer dep in problems') t.end() }) @@ -2161,7 +2161,7 @@ t.test('ls --parseable', (t) => { }), ...diffDepTypesNmFixture, }) - ls([], (err) => { + ls.exec([], (err) => { 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 parseable with empty entry for missing optional deps') @@ -2199,7 +2199,7 @@ t.test('ls --parseable', (t) => { }, }, }) - ls([], () => { + ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should print tree output omitting deduped ref') t.end() }) @@ -2238,7 +2238,7 @@ t.test('ls --parseable', (t) => { }, }) touchHiddenPackageLock(prefix) - ls([], () => { + ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing aliases') t.end() }) @@ -2283,7 +2283,7 @@ t.test('ls --parseable', (t) => { }, }) touchHiddenPackageLock(prefix) - ls([], () => { + ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should output tree containing git refs') t.end() }) @@ -2337,7 +2337,7 @@ t.test('ls --parseable', (t) => { }, }) touchHiddenPackageLock(prefix) - ls([], () => { + ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should not be printed in tree output') t.end() }) @@ -2373,7 +2373,7 @@ t.test('ls --parseable', (t) => { // mimics lib/npm.js globalDir getter but pointing to fixtures globalDir = resolve(fixtures, 'node_modules') - ls([], () => { + ls.exec([], () => { t.matchSnapshot(redactCwd(result), 'should print parseable output for global deps') globalDir = 'MISSING_GLOBAL_DIR' _flatOptions.global = false @@ -2400,7 +2400,7 @@ t.test('ls --json', (t) => { }), ...simpleNmFixture, }) - ls([], (err) => { + ls.exec([], (err) => { t.ifError(err, 'npm ls') t.deepEqual( jsonParse(result), @@ -2431,7 +2431,7 @@ t.test('ls --json', (t) => { prefix = t.testdir({ ...simpleNmFixture, }) - ls([], (err) => { + ls.exec([], (err) => { t.match(err, { code: 'ELSPROBLEMS' }, 'should list dep problems') t.deepEqual( jsonParse(result), @@ -2487,7 +2487,7 @@ t.test('ls --json', (t) => { }), ...simpleNmFixture, }) - ls([], (err) => { + ls.exec([], (err) => { t.equal( redactCwd(err.message), 'extraneous: lorem@1.0.0 {CWD}/ls-ls-json-extraneous-deps/node_modules/lorem', @@ -2542,7 +2542,7 @@ t.test('ls --json', (t) => { }), ...simpleNmFixture, }) - ls(['lorem'], (err) => { + ls.exec(['lorem'], (err) => { t.ifError(err, 'npm ls') t.deepEqual( jsonParse(result), @@ -2578,7 +2578,7 @@ t.test('ls --json', (t) => { }), ...simpleNmFixture, }) - ls(['bar'], (err) => { + ls.exec(['bar'], (err) => { t.ifError(err, 'npm ls') t.deepEqual( jsonParse(result), @@ -2623,7 +2623,7 @@ t.test('ls --json', (t) => { }, }, }) - ls(['bar@*', 'lorem@1.0.0'], (err) => { + ls.exec(['bar@*', 'lorem@1.0.0'], (err) => { t.ifError(err, 'npm ls') t.deepEqual( jsonParse(result), @@ -2662,7 +2662,7 @@ t.test('ls --json', (t) => { }), ...simpleNmFixture, }) - ls(['notadep'], (err) => { + ls.exec(['notadep'], (err) => { t.ifError(err, 'npm ls') t.deepEqual( jsonParse(result), @@ -2696,7 +2696,7 @@ t.test('ls --json', (t) => { }), ...simpleNmFixture, }) - ls([], (err) => { + ls.exec([], (err) => { t.ifError(err, 'npm ls') t.deepEqual( jsonParse(result), @@ -2734,7 +2734,7 @@ t.test('ls --json', (t) => { }), ...simpleNmFixture, }) - ls([], (err) => { + ls.exec([], (err) => { t.ifError(err, 'npm ls') t.deepEqual( jsonParse(result), @@ -2772,7 +2772,7 @@ t.test('ls --json', (t) => { }), ...simpleNmFixture, }) - ls([], (err) => { + ls.exec([], (err) => { t.ifError(err, 'npm ls') t.deepEqual( jsonParse(result), @@ -2813,7 +2813,7 @@ t.test('ls --json', (t) => { }), ...simpleNmFixture, }) - ls([], (err) => { + ls.exec([], (err) => { t.match(err, { code: 'ELSPROBLEMS' }, 'should list dep problems') t.deepEqual( jsonParse(result), @@ -2882,7 +2882,7 @@ t.test('ls --json', (t) => { }), ...diffDepTypesNmFixture, }) - ls([], () => { + ls.exec([], () => { t.deepEqual( jsonParse(result), { @@ -2929,7 +2929,7 @@ t.test('ls --json', (t) => { }), ...diffDepTypesNmFixture, }) - ls([], () => { + ls.exec([], () => { t.deepEqual( jsonParse(result), { @@ -2986,7 +2986,7 @@ t.test('ls --json', (t) => { ...diffDepTypesNmFixture.node_modules, }, }) - ls([], () => { + ls.exec([], () => { t.deepEqual( jsonParse(result), { @@ -3028,7 +3028,7 @@ t.test('ls --json', (t) => { }), ...diffDepTypesNmFixture, }) - ls([], () => { + ls.exec([], () => { t.deepEqual( jsonParse(result), { @@ -3069,7 +3069,7 @@ t.test('ls --json', (t) => { }), ...diffDepTypesNmFixture, }) - ls([], () => { + ls.exec([], () => { t.deepEqual( jsonParse(result), { @@ -3182,7 +3182,7 @@ t.test('ls --json', (t) => { }, }), }) - ls([], () => { + ls.exec([], () => { t.deepEqual( jsonParse(result), { @@ -3239,7 +3239,7 @@ t.test('ls --json', (t) => { }), ...diffDepTypesNmFixture, }) - ls([], () => { + ls.exec([], () => { t.deepEqual( jsonParse(result), { @@ -3377,7 +3377,7 @@ t.test('ls --json', (t) => { }), ...diffDepTypesNmFixture, }) - ls([], () => { + ls.exec([], () => { t.deepEqual( jsonParse(result), { @@ -3460,7 +3460,7 @@ t.test('ls --json', (t) => { prefix = t.testdir({ 'package.json': '{broken json', }) - ls([], (err) => { + ls.exec([], (err) => { t.match(err.message, 'Failed to parse root package.json', 'should have missin root package.json msg') t.match(err.code, 'EJSONPARSE', 'should have EJSONPARSE error code') t.deepEqual( @@ -3479,7 +3479,7 @@ t.test('ls --json', (t) => { t.test('empty location', (t) => { prefix = t.testdir({}) - ls([], (err) => { + ls.exec([], (err) => { t.ifError(err, 'should not error out on empty locations') t.deepEqual( jsonParse(result), @@ -3511,7 +3511,7 @@ t.test('ls --json', (t) => { }), ...diffDepTypesNmFixture, }) - ls([], (err) => { + ls.exec([], (err) => { t.match(err.code, 'ELSPROBLEMS', 'Should have ELSPROBLEMS error code') t.deepEqual( jsonParse(result), @@ -3571,7 +3571,7 @@ t.test('ls --json', (t) => { }), ...diffDepTypesNmFixture, }) - ls([], (err) => { + ls.exec([], (err) => { 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.deepEqual( @@ -3643,7 +3643,7 @@ t.test('ls --json', (t) => { }, }, }) - ls([], () => { + ls.exec([], () => { t.deepEqual( jsonParse(result), { @@ -3701,7 +3701,7 @@ t.test('ls --json', (t) => { }, }) touchHiddenPackageLock(prefix) - ls([], () => { + ls.exec([], () => { t.deepEqual( jsonParse(result), { @@ -3761,7 +3761,7 @@ t.test('ls --json', (t) => { }, }) touchHiddenPackageLock(prefix) - ls([], () => { + ls.exec([], () => { t.deepEqual( jsonParse(result), { @@ -3845,7 +3845,7 @@ t.test('ls --json', (t) => { }, }) touchHiddenPackageLock(prefix) - ls([], () => { + ls.exec([], () => { t.deepEqual( jsonParse(result), { @@ -3870,7 +3870,7 @@ t.test('ls --json', (t) => { version: '1.0.0', }), }) - ls([], () => { + ls.exec([], () => { t.deepEqual( jsonParse(result), { @@ -3913,7 +3913,7 @@ t.test('ls --json', (t) => { // mimics lib/npm.js globalDir getter but pointing to fixtures globalDir = resolve(fixtures, 'node_modules') - ls([], () => { + ls.exec([], () => { t.deepEqual( jsonParse(result), { diff --git a/deps/npm/test/lib/npm.js b/deps/npm/test/lib/npm.js index 9fd9888b97..1f7a54e228 100644 --- a/deps/npm/test/lib/npm.js +++ b/deps/npm/test/lib/npm.js @@ -1,5 +1,4 @@ const t = require('tap') -const fs = require('fs') // delete this so that we don't have configs from the fact that it // is being run by 'npm test' @@ -16,12 +15,12 @@ for (const env of Object.keys(process.env).filter(e => /^npm_/.test(e))) { 'should match "npm test" or "npm run test"' ) } else - t.match(process.env[env], 'run-script') + t.match(process.env[env], /^(run)|(run-script)|(exec)$/) } delete process.env[env] } -const { resolve } = require('path') +const { resolve, dirname } = require('path') const actualPlatform = process.platform @@ -249,13 +248,11 @@ t.test('npm.load', t => { const node = actualPlatform === 'win32' ? 'node.exe' : 'node' const dir = t.testdir({ '.npmrc': 'foo = bar', + bin: t.fixture('symlink', dirname(process.execPath)), }) - // create manually to set the 'file' option in windows - fs.symlinkSync(process.execPath, resolve(dir, node), 'file') - const PATH = process.env.PATH || process.env.Path - process.env.PATH = dir + process.env.PATH = resolve(dir, 'bin') const { execPath, argv: processArgv } = process process.argv = [ node, @@ -294,7 +291,7 @@ t.test('npm.load', t => { [ 'verbose', 'node symlink', - resolve(dir, node), + resolve(dir, 'bin', node), ], [ 'timing', @@ -303,17 +300,17 @@ t.test('npm.load', t => { ], ]) logs.length = 0 - t.equal(process.execPath, resolve(dir, node)) + t.equal(process.execPath, resolve(dir, 'bin', node)) }) await npm.commands.ll([], (er) => { if (er) throw er - t.same(consoleLogs, [[require('../../lib/ls.js').usage]], 'print usage') + t.same(consoleLogs, [[npm.commands.ll.usage]], 'print usage') consoleLogs.length = 0 npm.config.set('usage', false) - t.equal(npm.commands.ll, npm.commands.la, 'same command, different name') + t.equal(npm.commands.ll, npm.commands.ll, 'same command, different name') logs.length = 0 }) @@ -352,13 +349,15 @@ t.test('npm.load', t => { t.test('loading as main will load the cli', t => { const { spawn } = require('child_process') const npm = require.resolve('../../lib/npm.js') + const LS = require('../../lib/ls.js') + const ls = new LS({}) const p = spawn(process.execPath, [npm, 'ls', '-h']) const out = [] p.stdout.on('data', c => out.push(c)) p.on('close', (code, signal) => { t.equal(code, 0) t.equal(signal, null) - t.equal(Buffer.concat(out).toString().trim(), require('../../lib/ls.js').usage) + t.equal(Buffer.concat(out).toString().trim(), ls.usage) t.end() }) }) diff --git a/deps/npm/test/lib/org.js b/deps/npm/test/lib/org.js index 1e8aabc1d7..d21df85d64 100644 --- a/deps/npm/test/lib/org.js +++ b/deps/npm/test/lib/org.js @@ -39,14 +39,14 @@ const libnpmorg = { }, } -const org = requireInject('../../lib/org.js', { - '../../lib/npm.js': npm, +const Org = requireInject('../../lib/org.js', { '../../lib/utils/otplease.js': async (opts, fn) => fn(opts), '../../lib/utils/output.js': (msg) => { output.push(msg) }, libnpmorg, }) +const org = new Org(npm) test('completion', async t => { const completion = (argv) => @@ -67,7 +67,7 @@ test('completion', async t => { }) test('npm org - invalid subcommand', t => { - return org(['foo'], (err) => { + org.exec(['foo'], (err) => { t.match(err, /npm org set/, 'prints usage information') t.end() }) @@ -79,7 +79,7 @@ test('npm org add', t => { output.length = 0 }) - return org(['add', 'orgname', 'username'], (err) => { + org.exec(['add', 'orgname', 'username'], (err) => { if (err) throw err @@ -100,7 +100,7 @@ test('npm org add - no org', t => { output.length = 0 }) - return org(['add', '', 'username'], (err) => { + org.exec(['add', '', 'username'], (err) => { t.match(err, /`orgname` is required/, 'returns the correct error') t.end() }) @@ -112,7 +112,7 @@ test('npm org add - no user', t => { output.length = 0 }) - return org(['add', 'orgname', ''], (err) => { + org.exec(['add', 'orgname', ''], (err) => { t.match(err, /`username` is required/, 'returns the correct error') t.end() }) @@ -124,7 +124,7 @@ test('npm org add - invalid role', t => { output.length = 0 }) - return org(['add', 'orgname', 'username', 'person'], (err) => { + org.exec(['add', 'orgname', 'username', 'person'], (err) => { t.match(err, /`role` must be one of/, 'returns the correct error') t.end() }) @@ -138,7 +138,7 @@ test('npm org add - more users', t => { output.length = 0 }) - return org(['add', 'orgname', 'username'], (err) => { + org.exec(['add', 'orgname', 'username'], (err) => { if (err) throw err @@ -161,7 +161,7 @@ test('npm org add - json output', t => { output.length = 0 }) - return org(['add', 'orgname', 'username'], (err) => { + org.exec(['add', 'orgname', 'username'], (err) => { if (err) throw err @@ -191,7 +191,7 @@ test('npm org add - parseable output', t => { output.length = 0 }) - return org(['add', 'orgname', 'username'], (err) => { + org.exec(['add', 'orgname', 'username'], (err) => { if (err) throw err @@ -217,7 +217,7 @@ test('npm org add - silent output', t => { output.length = 0 }) - return org(['add', 'orgname', 'username'], (err) => { + org.exec(['add', 'orgname', 'username'], (err) => { if (err) throw err @@ -239,7 +239,7 @@ test('npm org rm', t => { output.length = 0 }) - return org(['rm', 'orgname', 'username'], (err) => { + org.exec(['rm', 'orgname', 'username'], (err) => { if (err) throw err @@ -264,7 +264,7 @@ test('npm org rm - no org', t => { output.length = 0 }) - return org(['rm', '', 'username'], (err) => { + org.exec(['rm', '', 'username'], (err) => { t.match(err, /`orgname` is required/, 'threw the correct error') t.end() }) @@ -277,7 +277,7 @@ test('npm org rm - no user', t => { output.length = 0 }) - return org(['rm', 'orgname'], (err) => { + org.exec(['rm', 'orgname'], (err) => { t.match(err, /`username` is required/, 'threw the correct error') t.end() }) @@ -295,7 +295,7 @@ test('npm org rm - one user left', t => { output.length = 0 }) - return org(['rm', 'orgname', 'username'], (err) => { + org.exec(['rm', 'orgname', 'username'], (err) => { if (err) throw err @@ -322,7 +322,7 @@ test('npm org rm - json output', t => { output.length = 0 }) - return org(['rm', 'orgname', 'username'], (err) => { + org.exec(['rm', 'orgname', 'username'], (err) => { if (err) throw err @@ -354,7 +354,7 @@ test('npm org rm - parseable output', t => { output.length = 0 }) - return org(['rm', 'orgname', 'username'], (err) => { + org.exec(['rm', 'orgname', 'username'], (err) => { if (err) throw err @@ -384,7 +384,7 @@ test('npm org rm - silent output', t => { output.length = 0 }) - return org(['rm', 'orgname', 'username'], (err) => { + org.exec(['rm', 'orgname', 'username'], (err) => { if (err) throw err @@ -414,7 +414,7 @@ test('npm org ls', t => { output.length = 0 }) - return org(['ls', 'orgname'], (err) => { + org.exec(['ls', 'orgname'], (err) => { if (err) throw err @@ -441,7 +441,7 @@ test('npm org ls - user filter', t => { output.length = 0 }) - return org(['ls', 'orgname', 'username'], (err) => { + org.exec(['ls', 'orgname', 'username'], (err) => { if (err) throw err @@ -466,7 +466,7 @@ test('npm org ls - user filter, missing user', t => { output.length = 0 }) - return org(['ls', 'orgname', 'username'], (err) => { + org.exec(['ls', 'orgname', 'username'], (err) => { if (err) throw err @@ -487,7 +487,7 @@ test('npm org ls - no org', t => { output.length = 0 }) - return org(['ls'], (err) => { + org.exec(['ls'], (err) => { t.match(err, /`orgname` is required/, 'throws the correct error') t.end() }) @@ -507,7 +507,7 @@ test('npm org ls - json output', t => { output.length = 0 }) - return org(['ls', 'orgname'], (err) => { + org.exec(['ls', 'orgname'], (err) => { if (err) throw err @@ -534,7 +534,7 @@ test('npm org ls - parseable output', t => { output.length = 0 }) - return org(['ls', 'orgname'], (err) => { + org.exec(['ls', 'orgname'], (err) => { if (err) throw err @@ -566,7 +566,7 @@ test('npm org ls - silent output', t => { output.length = 0 }) - return org(['ls', 'orgname'], (err) => { + org.exec(['ls', 'orgname'], (err) => { if (err) throw err diff --git a/deps/npm/test/lib/outdated.js b/deps/npm/test/lib/outdated.js index da53b6031d..aa8a1bcb6b 100644 --- a/deps/npm/test/lib/outdated.js +++ b/deps/npm/test/lib/outdated.js @@ -92,19 +92,18 @@ const globalDir = t.testdir({ }, }) -const outdated = (dir, opts) => requireInject( - '../../lib/outdated.js', - { - '../../lib/npm.js': { - prefix: dir, - globalDir: `${globalDir}/node_modules`, - flatOptions: opts, - }, +const outdated = (dir, opts) => { + const Outdated = requireInject('../../lib/outdated.js', { pacote: { packument, }, - } -) + }) + return new Outdated({ + prefix: dir, + globalDir: `${globalDir}/node_modules`, + flatOptions: opts, + }) +} t.beforeEach(cleanLogs) @@ -180,7 +179,7 @@ t.test('should display outdated deps', t => { t.test('outdated global', t => { outdated(null, { global: true, - })([], () => { + }).exec([], () => { t.matchSnapshot(logs) t.end() }) @@ -190,7 +189,7 @@ t.test('should display outdated deps', t => { outdated(testDir, { global: false, color: true, - })([], () => { + }).exec([], () => { t.matchSnapshot(logs) t.end() }) @@ -201,7 +200,7 @@ t.test('should display outdated deps', t => { global: false, color: true, omit: ['dev'], - })([], () => { + }).exec([], () => { t.matchSnapshot(logs) t.end() }) @@ -212,7 +211,7 @@ t.test('should display outdated deps', t => { global: false, color: true, omit: ['dev', 'peer'], - })([], () => { + }).exec([], () => { t.matchSnapshot(logs) t.end() }) @@ -223,7 +222,7 @@ t.test('should display outdated deps', t => { global: false, color: true, omit: ['prod'], - })([], () => { + }).exec([], () => { t.matchSnapshot(logs) t.end() }) @@ -233,7 +232,7 @@ t.test('should display outdated deps', t => { outdated(testDir, { global: false, long: true, - })([], () => { + }).exec([], () => { t.matchSnapshot(logs) t.end() }) @@ -243,7 +242,7 @@ t.test('should display outdated deps', t => { outdated(testDir, { global: false, json: true, - })([], () => { + }).exec([], () => { t.matchSnapshot(logs) t.end() }) @@ -254,7 +253,7 @@ t.test('should display outdated deps', t => { global: false, json: true, long: true, - })([], () => { + }).exec([], () => { t.matchSnapshot(logs) t.end() }) @@ -264,7 +263,7 @@ t.test('should display outdated deps', t => { outdated(testDir, { global: false, parseable: true, - })([], () => { + }).exec([], () => { t.matchSnapshot(logs) t.end() }) @@ -275,7 +274,7 @@ t.test('should display outdated deps', t => { global: false, parseable: true, long: true, - })([], () => { + }).exec([], () => { t.matchSnapshot(logs) t.end() }) @@ -284,7 +283,7 @@ t.test('should display outdated deps', t => { t.test('outdated --all', t => { outdated(testDir, { all: true, - })([], () => { + }).exec([], () => { t.matchSnapshot(logs) t.end() }) @@ -293,7 +292,7 @@ t.test('should display outdated deps', t => { t.test('outdated specific dep', t => { outdated(testDir, { global: false, - })(['alpha'], () => { + }).exec(['alpha'], () => { t.matchSnapshot(logs) t.end() }) @@ -323,7 +322,7 @@ t.test('should return if no outdated deps', t => { outdated(testDir, { global: false, - })([], () => { + }).exec([], () => { t.equals(logs.length, 0, 'no logs') t.end() }) @@ -350,7 +349,7 @@ t.test('throws if error with a dep', t => { outdated(testDir, { global: false, - })([], (err) => { + }).exec([], (err) => { t.equals(err.message, 'There is an error with this package.') t.end() }) @@ -370,7 +369,7 @@ t.test('should skip missing non-prod deps', t => { outdated(testDir, { global: false, - })([], () => { + }).exec([], () => { t.equals(logs.length, 0, 'no logs') t.end() }) @@ -395,7 +394,7 @@ t.test('should skip invalid pkg ranges', t => { }, }) - outdated(testDir, {})([], () => { + outdated(testDir, {}).exec([], () => { t.equals(logs.length, 0, 'no logs') t.end() }) @@ -420,7 +419,7 @@ t.test('should skip git specs', t => { }, }) - outdated(testDir, {})([], () => { + outdated(testDir, {}).exec([], () => { t.equals(logs.length, 0, 'no logs') t.end() }) diff --git a/deps/npm/test/lib/owner.js b/deps/npm/test/lib/owner.js index aa5e3ee637..4f8f430886 100644 --- a/deps/npm/test/lib/owner.js +++ b/deps/npm/test/lib/owner.js @@ -15,7 +15,6 @@ const mocks = { npmlog, 'npm-registry-fetch': npmFetch, pacote, - '../../lib/npm.js': npm, '../../lib/utils/output.js': (...msg) => { result += msg.join('\n') }, @@ -31,7 +30,8 @@ const npmcliMaintainers = [ { email: 'i@izs.me', name: 'isaacs' }, ] -const owner = requireInject('../../lib/owner.js', mocks) +const Owner = requireInject('../../lib/owner.js', mocks) +const owner = new Owner(npm) t.test('owner no args', t => { result = '' @@ -39,7 +39,7 @@ t.test('owner no args', t => { result = '' }) - owner([], err => { + owner.exec([], err => { t.equal( err.message, 'usage instructions', @@ -73,7 +73,7 @@ t.test('owner ls no args', t => { readLocalPkgResponse = null }) - owner(['ls'], err => { + owner.exec(['ls'], err => { t.ifError(err, 'npm owner ls no args') t.matchSnapshot(result, 'should output owners of cwd package') }) @@ -86,7 +86,7 @@ t.test('owner ls no args no cwd package', t => { npmlog.error = noop }) - owner(['ls'], err => { + owner.exec(['ls'], err => { t.equal( err.message, 'usage instructions', @@ -115,7 +115,7 @@ t.test('owner ls fails to retrieve packument', t => { pacote.packument = noop }) - owner(['ls'], err => { + owner.exec(['ls'], err => { t.match( err, /ERR/, @@ -145,7 +145,7 @@ t.test('owner ls <pkg>', t => { pacote.packument = noop }) - owner(['ls', '@npmcli/map-workspaces'], err => { + owner.exec(['ls', '@npmcli/map-workspaces'], err => { t.ifError(err, 'npm owner ls <pkg>') t.matchSnapshot(result, 'should output owners of <pkg>') }) @@ -161,7 +161,7 @@ t.test('owner ls <pkg> no maintainers', t => { pacote.packument = noop }) - owner(['ls', '@npmcli/map-workspaces'], err => { + owner.exec(['ls', '@npmcli/map-workspaces'], err => { t.ifError(err, 'npm owner ls <pkg> no maintainers') t.equal(result, 'no admin found', 'should output no admint found msg') t.end() @@ -232,7 +232,7 @@ t.test('owner add <user> <pkg>', t => { pacote.packument = noop }) - owner(['add', 'foo', '@npmcli/map-workspaces'], err => { + owner.exec(['add', 'foo', '@npmcli/map-workspaces'], err => { t.ifError(err, 'npm owner add <user> <pkg>') t.equal(result, '+ foo (@npmcli/map-workspaces)', 'should output add result') }) @@ -265,7 +265,7 @@ t.test('owner add <user> cwd package', t => { pacote.packument = noop }) - owner(['add', 'foo'], err => { + owner.exec(['add', 'foo'], err => { t.ifError(err, 'npm owner add <user> cwd package') t.equal(result, '+ foo (@npmcli/map-workspaces)', 'should output add result') t.end() @@ -308,7 +308,7 @@ t.test('owner add <user> <pkg> already an owner', t => { pacote.packument = noop }) - owner(['add', 'ruyadorno', '@npmcli/map-workspaces'], err => { + owner.exec(['add', 'ruyadorno', '@npmcli/map-workspaces'], err => { t.ifError(err, 'npm owner add <user> <pkg> already an owner') }) }) @@ -336,7 +336,7 @@ t.test('owner add <user> <pkg> fails to retrieve user', t => { pacote.packument = noop }) - owner(['add', 'foo', '@npmcli/map-workspaces'], err => { + owner.exec(['add', 'foo', '@npmcli/map-workspaces'], err => { t.match( err, /Error: Couldn't get user data for foo: {"ok":false}/, @@ -377,7 +377,7 @@ t.test('owner add <user> <pkg> fails to PUT updates', t => { pacote.packument = noop }) - owner(['add', 'foo', '@npmcli/map-workspaces'], err => { + owner.exec(['add', 'foo', '@npmcli/map-workspaces'], err => { t.match( err.message, /Failed to update package/, @@ -417,7 +417,7 @@ t.test('owner add <user> <pkg> fails to retrieve user info', t => { pacote.packument = noop }) - owner(['add', 'foo', '@npmcli/map-workspaces'], err => { + owner.exec(['add', 'foo', '@npmcli/map-workspaces'], err => { t.match( err.message, "I'm a teapot", @@ -453,7 +453,7 @@ t.test('owner add <user> <pkg> no previous maintainers property from server', t pacote.packument = noop }) - owner(['add', 'foo', '@npmcli/no-owners-pkg'], err => { + owner.exec(['add', 'foo', '@npmcli/no-owners-pkg'], err => { t.ifError(err, 'npm owner add <user> <pkg>') t.equal(result, '+ foo (@npmcli/no-owners-pkg)', 'should output add result') t.end() @@ -466,7 +466,7 @@ t.test('owner add no user', t => { result = '' }) - owner(['add'], err => { + owner.exec(['add'], err => { t.equal( err.message, 'usage instructions', @@ -482,7 +482,7 @@ t.test('owner add <user> no cwd package', t => { result = '' }) - owner(['add', 'foo'], err => { + owner.exec(['add', 'foo'], err => { t.equal( err.message, 'usage instructions', @@ -549,7 +549,7 @@ t.test('owner rm <user> <pkg>', t => { pacote.packument = noop }) - owner(['rm', 'ruyadorno', '@npmcli/map-workspaces'], err => { + owner.exec(['rm', 'ruyadorno', '@npmcli/map-workspaces'], err => { t.ifError(err, 'npm owner rm <user> <pkg>') t.equal(result, '- ruyadorno (@npmcli/map-workspaces)', 'should output rm result') }) @@ -589,7 +589,7 @@ t.test('owner rm <user> <pkg> not a current owner', t => { pacote.packument = noop }) - owner(['rm', 'foo', '@npmcli/map-workspaces'], err => { + owner.exec(['rm', 'foo', '@npmcli/map-workspaces'], err => { t.ifError(err, 'npm owner rm <user> <pkg> not a current owner') }) }) @@ -621,7 +621,7 @@ t.test('owner rm <user> cwd package', t => { pacote.packument = noop }) - owner(['rm', 'ruyadorno'], err => { + owner.exec(['rm', 'ruyadorno'], err => { t.ifError(err, 'npm owner rm <user> cwd package') t.equal(result, '- ruyadorno (@npmcli/map-workspaces)', 'should output rm result') t.end() @@ -656,7 +656,7 @@ t.test('owner rm <user> only user', t => { pacote.packument = noop }) - owner(['rm', 'ruyadorno'], err => { + owner.exec(['rm', 'ruyadorno'], err => { t.equal( err.message, 'Cannot remove all owners of a package. Add someone else first.', @@ -673,7 +673,7 @@ t.test('owner rm no user', t => { result = '' }) - owner(['rm'], err => { + owner.exec(['rm'], err => { t.equal( err.message, 'usage instructions', @@ -689,7 +689,7 @@ t.test('owner rm <user> no cwd package', t => { result = '' }) - owner(['rm', 'foo'], err => { + owner.exec(['rm', 'foo'], err => { t.equal( err.message, 'usage instructions', @@ -700,10 +700,8 @@ t.test('owner rm <user> no cwd package', t => { }) t.test('completion', async t => { - const { completion } = owner - const testComp = async (argv, expect) => { - const res = await completion({ conf: { argv: { remain: argv } } }) + const res = await owner.completion({ conf: { argv: { remain: argv } } }) t.strictSame(res, expect, argv.join(' ')) } @@ -730,7 +728,7 @@ t.test('completion', async t => { pacote.packument = noop }) - const res = await completion({ conf: { argv: { remain: ['npm', 'owner', 'rm'] } } }) + const res = await owner.completion({ conf: { argv: { remain: ['npm', 'owner', 'rm'] } } }) t.strictSame(res, ['nlf', 'ruyadorno', 'darcyclarke', 'isaacs'], 'should return list of current owners' @@ -738,7 +736,7 @@ t.test('completion', async t => { }) t.test('completion npm owner rm no cwd package', async t => { - const res = await completion({ conf: { argv: { remain: ['npm', 'owner', 'rm'] } } }) + const res = await owner.completion({ conf: { argv: { remain: ['npm', 'owner', 'rm'] } } }) t.strictSame(res, [], 'should have no owners to autocomplete if not cwd package') t.end() }) @@ -757,7 +755,7 @@ t.test('completion', async t => { pacote.packument = noop }) - const res = await completion({ conf: { argv: { remain: ['npm', 'owner', 'rm'] } } }) + const res = await owner.completion({ conf: { argv: { remain: ['npm', 'owner', 'rm'] } } }) t.strictSame(res, [], 'should return no owners if not found') }) diff --git a/deps/npm/test/lib/pack.js b/deps/npm/test/lib/pack.js index 851174d259..73a19baa3e 100644 --- a/deps/npm/test/lib/pack.js +++ b/deps/npm/test/lib/pack.js @@ -17,15 +17,8 @@ t.afterEach(cb => { }) t.test('should pack current directory with no arguments', (t) => { - const pack = requireInject('../../lib/pack.js', { + const Pack = requireInject('../../lib/pack.js', { '../../lib/utils/output.js': output, - '../../lib/npm.js': { - flatOptions: { - unicode: false, - json: false, - dryRun: false, - }, - }, libnpmpack, npmlog: { notice: () => {}, @@ -33,13 +26,21 @@ t.test('should pack current directory with no arguments', (t) => { clearProgress: () => {}, }, }) + const pack = new Pack({ + flatOptions: { + unicode: false, + json: false, + dryRun: false, + }, + }) - return pack([], er => { + pack.exec([], er => { if (er) throw er const filename = `npm-${require('../../package.json').version}.tgz` t.strictSame(OUTPUT, [[filename]]) + t.end() }) }) @@ -51,15 +52,8 @@ t.test('should pack given directory', (t) => { }, null, 2), }) - const pack = requireInject('../../lib/pack.js', { + const Pack = requireInject('../../lib/pack.js', { '../../lib/utils/output.js': output, - '../../lib/npm.js': { - flatOptions: { - unicode: true, - json: true, - dryRun: true, - }, - }, libnpmpack, npmlog: { notice: () => {}, @@ -67,13 +61,21 @@ t.test('should pack given directory', (t) => { clearProgress: () => {}, }, }) + const pack = new Pack({ + flatOptions: { + unicode: true, + json: true, + dryRun: true, + }, + }) - return pack([testDir], er => { + pack.exec([testDir], er => { if (er) throw er const filename = 'my-cool-pkg-1.0.0.tgz' t.strictSame(OUTPUT, [[filename]]) + t.end() }) }) @@ -85,15 +87,8 @@ t.test('should pack given directory for scoped package', (t) => { }, null, 2), }) - const pack = requireInject('../../lib/pack.js', { + const Pack = requireInject('../../lib/pack.js', { '../../lib/utils/output.js': output, - '../../lib/npm.js': { - flatOptions: { - unicode: true, - json: true, - dryRun: true, - }, - }, libnpmpack, npmlog: { notice: () => {}, @@ -101,18 +96,26 @@ t.test('should pack given directory for scoped package', (t) => { clearProgress: () => {}, }, }) + const pack = new Pack({ + flatOptions: { + unicode: true, + json: true, + dryRun: true, + }, + }) - return pack([testDir], er => { + return pack.exec([testDir], er => { if (er) throw er const filename = 'cool-my-pkg-1.0.0.tgz' t.strictSame(OUTPUT, [[filename]]) + t.end() }) }) t.test('should log pack contents', (t) => { - const pack = requireInject('../../lib/pack.js', { + const Pack = requireInject('../../lib/pack.js', { '../../lib/utils/output.js': output, '../../lib/utils/tar.js': { ...require('../../lib/utils/tar.js'), @@ -120,13 +123,6 @@ t.test('should log pack contents', (t) => { t.ok(true, 'logTar is called') }, }, - '../../lib/npm.js': { - flatOptions: { - unicode: false, - json: false, - dryRun: false, - }, - }, libnpmpack, npmlog: { notice: () => {}, @@ -134,12 +130,20 @@ t.test('should log pack contents', (t) => { clearProgress: () => {}, }, }) + const pack = new Pack({ + flatOptions: { + unicode: false, + json: false, + dryRun: false, + }, + }) - return pack([], er => { + pack.exec([], er => { if (er) throw er const filename = `npm-${require('../../package.json').version}.tgz` t.strictSame(OUTPUT, [[filename]]) + t.end() }) }) diff --git a/deps/npm/test/lib/ping.js b/deps/npm/test/lib/ping.js index a185919ddd..cf47530749 100644 --- a/deps/npm/test/lib/ping.js +++ b/deps/npm/test/lib/ping.js @@ -6,8 +6,7 @@ test('pings', (t) => { const flatOptions = { registry: 'https://registry.npmjs.org' } let noticeCalls = 0 - const ping = requireInject('../../lib/ping.js', { - '../../lib/npm.js': { flatOptions }, + const Ping = requireInject('../../lib/ping.js', { '../../lib/utils/ping.js': function (spec) { t.equal(spec, flatOptions, 'passes flatOptions') return {} @@ -25,8 +24,9 @@ test('pings', (t) => { }, }, }) + const ping = new Ping({ flatOptions }) - ping([], (err) => { + ping.exec([], (err) => { t.equal(noticeCalls, 2, 'should have logged 2 lines') t.ifError(err, 'npm ping') t.ok('should be able to ping') @@ -39,8 +39,7 @@ test('pings and logs details', (t) => { const flatOptions = { registry: 'https://registry.npmjs.org' } const details = { extra: 'data' } let noticeCalls = 0 - const ping = requireInject('../../lib/ping.js', { - '../../lib/npm.js': { flatOptions }, + const Ping = requireInject('../../lib/ping.js', { '../../lib/utils/ping.js': function (spec) { t.equal(spec, flatOptions, 'passes flatOptions') return details @@ -62,8 +61,9 @@ test('pings and logs details', (t) => { }, }, }) + const ping = new Ping({ flatOptions }) - ping([], (err) => { + ping.exec([], (err) => { t.equal(noticeCalls, 3, 'should have logged 3 lines') t.ifError(err, 'npm ping') t.ok('should be able to ping') @@ -76,8 +76,7 @@ test('pings and returns json', (t) => { const flatOptions = { registry: 'https://registry.npmjs.org', json: true } const details = { extra: 'data' } let noticeCalls = 0 - const ping = requireInject('../../lib/ping.js', { - '../../lib/npm.js': { flatOptions }, + const Ping = requireInject('../../lib/ping.js', { '../../lib/utils/ping.js': function (spec) { t.equal(spec, flatOptions, 'passes flatOptions') return details @@ -101,8 +100,9 @@ test('pings and returns json', (t) => { }, }, }) + const ping = new Ping({ flatOptions }) - ping([], (err) => { + ping.exec([], (err) => { t.equal(noticeCalls, 2, 'should have logged 2 lines') t.ifError(err, 'npm ping') t.ok('should be able to ping') diff --git a/deps/npm/test/lib/prefix.js b/deps/npm/test/lib/prefix.js index 83e2d63680..dfb50f174f 100644 --- a/deps/npm/test/lib/prefix.js +++ b/deps/npm/test/lib/prefix.js @@ -5,14 +5,14 @@ test('prefix', (t) => { t.plan(3) const dir = '/prefix/dir' - const prefix = requireInject('../../lib/prefix.js', { - '../../lib/npm.js': { prefix: dir }, + const Prefix = requireInject('../../lib/prefix.js', { '../../lib/utils/output.js': (output) => { t.equal(output, dir, 'prints the correct directory') }, }) + const prefix = new Prefix({ prefix: dir }) - prefix([], (err) => { + prefix.exec([], (err) => { t.ifError(err, 'npm prefix') t.ok('should have printed directory') }) diff --git a/deps/npm/test/lib/profile.js b/deps/npm/test/lib/profile.js index 3b2e140036..743ba2d687 100644 --- a/deps/npm/test/lib/profile.js +++ b/deps/npm/test/lib/profile.js @@ -32,7 +32,6 @@ const mocks = { .join('\n') } }, - '../../lib/npm.js': npm, '../../lib/utils/output.js': (...msg) => { result += msg.join('\n') }, @@ -68,10 +67,11 @@ t.afterEach(cb => { cb() }) -const profile = requireInject('../../lib/profile.js', mocks) +const Profile = requireInject('../../lib/profile.js', mocks) +const profile = new Profile(npm) t.test('no args', t => { - profile([], err => { + profile.exec([], err => { t.match( err, /usage instructions/, @@ -88,13 +88,14 @@ t.test('profile get no args', t => { }, } - const profile = requireInject('../../lib/profile.js', { + const Profile = requireInject('../../lib/profile.js', { ...mocks, 'npm-profile': npmProfile, }) + const profile = new Profile(npm) t.test('default output', t => { - profile(['get'], err => { + profile.exec(['get'], err => { if (err) throw err @@ -109,7 +110,7 @@ t.test('profile get no args', t => { t.test('--json', t => { npm.flatOptions.json = true - profile(['get'], err => { + profile.exec(['get'], err => { if (err) throw err @@ -125,7 +126,7 @@ t.test('profile get no args', t => { t.test('--parseable', t => { npm.flatOptions.parseable = true - profile(['get'], err => { + profile.exec(['get'], err => { if (err) throw err @@ -147,12 +148,13 @@ t.test('profile get no args', t => { }, } - const profile = requireInject('../../lib/profile.js', { + const Profile = requireInject('../../lib/profile.js', { ...mocks, 'npm-profile': npmProfile, }) + const profile = new Profile(npm) - profile(['get'], err => { + profile.exec(['get'], err => { if (err) throw err @@ -174,12 +176,13 @@ t.test('profile get no args', t => { }, } - const profile = requireInject('../../lib/profile.js', { + const Profile = requireInject('../../lib/profile.js', { ...mocks, 'npm-profile': npmProfile, }) + const profile = new Profile(npm) - profile(['get'], err => { + profile.exec(['get'], err => { if (err) throw err @@ -201,12 +204,13 @@ t.test('profile get no args', t => { }, } - const profile = requireInject('../../lib/profile.js', { + const Profile = requireInject('../../lib/profile.js', { ...mocks, 'npm-profile': npmProfile, }) + const profile = new Profile(npm) - profile(['get'], err => { + profile.exec(['get'], err => { if (err) throw err @@ -228,13 +232,14 @@ t.test('profile get <key>', t => { }, } - const profile = requireInject('../../lib/profile.js', { + const Profile = requireInject('../../lib/profile.js', { ...mocks, 'npm-profile': npmProfile, }) + const profile = new Profile(npm) t.test('default output', t => { - profile(['get', 'name'], err => { + profile.exec(['get', 'name'], err => { if (err) throw err @@ -250,7 +255,7 @@ t.test('profile get <key>', t => { t.test('--json', t => { npm.flatOptions.json = true - profile(['get', 'name'], err => { + profile.exec(['get', 'name'], err => { if (err) throw err @@ -266,7 +271,7 @@ t.test('profile get <key>', t => { t.test('--parseable', t => { npm.flatOptions.parseable = true - profile(['get', 'name'], err => { + profile.exec(['get', 'name'], err => { if (err) throw err @@ -288,13 +293,14 @@ t.test('profile get multiple args', t => { }, } - const profile = requireInject('../../lib/profile.js', { + const Profile = requireInject('../../lib/profile.js', { ...mocks, 'npm-profile': npmProfile, }) + const profile = new Profile(npm) t.test('default output', t => { - profile(['get', 'name', 'email', 'github'], err => { + profile.exec(['get', 'name', 'email', 'github'], err => { if (err) throw err @@ -309,7 +315,7 @@ t.test('profile get multiple args', t => { t.test('--json', t => { npm.flatOptions.json = true - profile(['get', 'name', 'email', 'github'], err => { + profile.exec(['get', 'name', 'email', 'github'], err => { if (err) throw err @@ -325,7 +331,7 @@ t.test('profile get multiple args', t => { t.test('--parseable', t => { npm.flatOptions.parseable = true - profile(['get', 'name', 'email', 'github'], err => { + profile.exec(['get', 'name', 'email', 'github'], err => { if (err) throw err @@ -338,7 +344,7 @@ t.test('profile get multiple args', t => { }) t.test('comma separated', t => { - profile(['get', 'name,email,github'], err => { + profile.exec(['get', 'name,email,github'], err => { if (err) throw err @@ -374,7 +380,7 @@ t.test('profile set <key> <value>', t => { }) t.test('no key', t => { - profile(['set'], err => { + profile.exec(['set'], err => { t.match( err, /npm profile set <prop> <value>/, @@ -385,7 +391,7 @@ t.test('profile set <key> <value>', t => { }) t.test('no value', t => { - profile(['set', 'email'], err => { + profile.exec(['set', 'email'], err => { t.match( err, /npm profile set <prop> <value>/, @@ -396,7 +402,7 @@ t.test('profile set <key> <value>', t => { }) t.test('set password', t => { - profile(['set', 'password', '1234'], err => { + profile.exec(['set', 'password', '1234'], err => { t.match( err, /Do not include your current or new passwords on the command line./, @@ -407,7 +413,7 @@ t.test('profile set <key> <value>', t => { }) t.test('unwritable key', t => { - profile(['set', 'name', 'foo'], err => { + profile.exec(['set', 'name', 'foo'], err => { t.match( err, /"name" is not a property we can set./, @@ -421,12 +427,13 @@ t.test('profile set <key> <value>', t => { t.test('default output', t => { t.plan(2) - const profile = requireInject('../../lib/profile.js', { + const Profile = requireInject('../../lib/profile.js', { ...mocks, 'npm-profile': npmProfile(t), }) + const profile = new Profile(npm) - profile(['set', 'fullname', 'Lorem Ipsum'], err => { + profile.exec(['set', 'fullname', 'Lorem Ipsum'], err => { if (err) throw err @@ -443,12 +450,13 @@ t.test('profile set <key> <value>', t => { npm.flatOptions.json = true - const profile = requireInject('../../lib/profile.js', { + const Profile = requireInject('../../lib/profile.js', { ...mocks, 'npm-profile': npmProfile(t), }) + const profile = new Profile(npm) - profile(['set', 'fullname', 'Lorem Ipsum'], err => { + profile.exec(['set', 'fullname', 'Lorem Ipsum'], err => { if (err) throw err @@ -467,12 +475,13 @@ t.test('profile set <key> <value>', t => { npm.flatOptions.parseable = true - const profile = requireInject('../../lib/profile.js', { + const Profile = requireInject('../../lib/profile.js', { ...mocks, 'npm-profile': npmProfile(t), }) + const profile = new Profile(npm) - profile(['set', 'fullname', 'Lorem Ipsum'], err => { + profile.exec(['set', 'fullname', 'Lorem Ipsum'], err => { if (err) throw err @@ -513,12 +522,13 @@ t.test('profile set <key> <value>', t => { }, } - const profile = requireInject('../../lib/profile.js', { + const Profile = requireInject('../../lib/profile.js', { ...mocks, 'npm-profile': npmProfile, }) + const profile = new Profile(npm) - profile(['set', 'email', 'foo@npmjs.com'], err => { + profile.exec(['set', 'email', 'foo@npmjs.com'], err => { if (err) throw err @@ -576,13 +586,14 @@ t.test('profile set <key> <value>', t => { }, } - const profile = requireInject('../../lib/profile.js', { + const Profile = requireInject('../../lib/profile.js', { ...mocks, 'npm-profile': npmProfile, '../../lib/utils/read-user-info.js': readUserInfo, }) + const profile = new Profile(npm) - profile(['set', 'password'], err => { + profile.exec(['set', 'password'], err => { if (err) throw err @@ -643,14 +654,15 @@ t.test('profile set <key> <value>', t => { }, } - const profile = requireInject('../../lib/profile.js', { + const Profile = requireInject('../../lib/profile.js', { ...mocks, npmlog, 'npm-profile': npmProfile, '../../lib/utils/read-user-info.js': readUserInfo, }) + const profile = new Profile(npm) - profile(['set', 'password'], err => { + profile.exec(['set', 'password'], err => { if (err) throw err @@ -668,7 +680,7 @@ t.test('profile set <key> <value>', t => { t.test('enable-2fa', t => { t.test('invalid args', t => { - profile(['enable-2fa', 'foo', 'bar'], err => { + profile.exec(['enable-2fa', 'foo', 'bar'], err => { t.match( err, /npm profile enable-2fa \[auth-and-writes|auth-only\]/, @@ -679,7 +691,7 @@ t.test('enable-2fa', t => { }) t.test('invalid two factor auth mode', t => { - profile(['enable-2fa', 'foo'], err => { + profile.exec(['enable-2fa', 'foo'], err => { t.match( err, /Invalid two-factor authentication mode "foo"/, @@ -692,7 +704,7 @@ t.test('enable-2fa', t => { t.test('no support for --json output', t => { npm.flatOptions.json = true - profile(['enable-2fa', 'auth-only'], err => { + profile.exec(['enable-2fa', 'auth-only'], err => { t.match( err.message, 'Enabling two-factor authentication is an interactive ' + @@ -706,7 +718,7 @@ t.test('enable-2fa', t => { t.test('no support for --parseable output', t => { npm.flatOptions.parseable = true - profile(['enable-2fa', 'auth-only'], err => { + profile.exec(['enable-2fa', 'auth-only'], err => { t.match( err.message, 'Enabling two-factor authentication is an interactive ' + @@ -733,12 +745,13 @@ t.test('enable-2fa', t => { }, } - const profile = requireInject('../../lib/profile.js', { + const Profile = requireInject('../../lib/profile.js', { ...mocks, 'npm-profile': npmProfile, }) + const profile = new Profile(npm) - profile(['enable-2fa', 'auth-only'], err => { + profile.exec(['enable-2fa', 'auth-only'], err => { t.match( err.message, 'Your registry https://registry.npmjs.org/ does ' + @@ -761,12 +774,13 @@ t.test('enable-2fa', t => { }, } - const profile = requireInject('../../lib/profile.js', { + const Profile = requireInject('../../lib/profile.js', { ...mocks, 'npm-profile': npmProfile, }) + const profile = new Profile(npm) - profile(['enable-2fa', 'auth-only'], err => { + profile.exec(['enable-2fa', 'auth-only'], err => { t.match( err.message, 'Your registry https://registry.npmjs.org/ does ' + @@ -781,11 +795,12 @@ t.test('enable-2fa', t => { t.test('no auth found', t => { npm.config.getCredentialsByURI = () => ({}) - const profile = requireInject('../../lib/profile.js', { + const Profile = requireInject('../../lib/profile.js', { ...mocks, }) + const profile = new Profile(npm) - profile(['enable-2fa', 'auth-only'], err => { + profile.exec(['enable-2fa', 'auth-only'], err => { t.match( err.message, 'You need to be logged in to registry ' + @@ -861,13 +876,14 @@ t.test('enable-2fa', t => { }, } - const profile = requireInject('../../lib/profile.js', { + const Profile = requireInject('../../lib/profile.js', { ...mocks, 'npm-profile': npmProfile, '../../lib/utils/read-user-info.js': readUserInfo, }) + const profile = new Profile(npm) - profile(['enable-2fa', 'auth-only'], err => { + profile.exec(['enable-2fa', 'auth-only'], err => { if (err) throw err @@ -964,14 +980,15 @@ t.test('enable-2fa', t => { generate: (url, cb) => cb('qrcode'), } - const profile = requireInject('../../lib/profile.js', { + const Profile = requireInject('../../lib/profile.js', { ...mocks, 'npm-profile': npmProfile, 'qrcode-terminal': qrcode, '../../lib/utils/read-user-info.js': readUserInfo, }) + const profile = new Profile(npm) - profile(['enable-2fa', 'auth-only'], err => { + profile.exec(['enable-2fa', 'auth-only'], err => { if (err) throw err @@ -1017,13 +1034,14 @@ t.test('enable-2fa', t => { }, } - const profile = requireInject('../../lib/profile.js', { + const Profile = requireInject('../../lib/profile.js', { ...mocks, 'npm-profile': npmProfile, '../../lib/utils/read-user-info.js': readUserInfo, }) + const profile = new Profile(npm) - profile(['enable-2fa', 'auth-only'], err => { + profile.exec(['enable-2fa', 'auth-only'], err => { t.match( err, /Unknown error enabling two-factor authentication./, @@ -1063,13 +1081,14 @@ t.test('enable-2fa', t => { }, } - const profile = requireInject('../../lib/profile.js', { + const Profile = requireInject('../../lib/profile.js', { ...mocks, 'npm-profile': npmProfile, '../../lib/utils/read-user-info.js': readUserInfo, }) + const profile = new Profile(npm) - profile(['enable-2fa', 'auth-and-writes'], err => { + profile.exec(['enable-2fa', 'auth-and-writes'], err => { if (err) throw err @@ -1113,13 +1132,14 @@ t.test('enable-2fa', t => { }, } - const profile = requireInject('../../lib/profile.js', { + const Profile = requireInject('../../lib/profile.js', { ...mocks, 'npm-profile': npmProfile, '../../lib/utils/read-user-info.js': readUserInfo, }) + const profile = new Profile(npm) - profile(['enable-2fa', 'auth-only'], err => { + profile.exec(['enable-2fa', 'auth-only'], err => { if (err) throw err @@ -1163,13 +1183,14 @@ t.test('enable-2fa', t => { }, } - const profile = requireInject('../../lib/profile.js', { + const Profile = requireInject('../../lib/profile.js', { ...mocks, 'npm-profile': npmProfile, '../../lib/utils/read-user-info.js': readUserInfo, }) + const profile = new Profile(npm) - profile(['enable-2fa'], err => { + profile.exec(['enable-2fa'], err => { if (err) throw err @@ -1196,12 +1217,13 @@ t.test('disable-2fa', t => { }, } - const profile = requireInject('../../lib/profile.js', { + const Profile = requireInject('../../lib/profile.js', { ...mocks, 'npm-profile': npmProfile, }) + const profile = new Profile(npm) - profile(['disable-2fa'], err => { + profile.exec(['disable-2fa'], err => { if (err) throw err @@ -1257,13 +1279,14 @@ t.test('disable-2fa', t => { }) t.test('default output', t => { - const profile = requireInject('../../lib/profile.js', { + const Profile = requireInject('../../lib/profile.js', { ...mocks, 'npm-profile': npmProfile(t), '../../lib/utils/read-user-info.js': readUserInfo(t), }) + const profile = new Profile(npm) - profile(['disable-2fa'], err => { + profile.exec(['disable-2fa'], err => { if (err) throw err @@ -1279,13 +1302,14 @@ t.test('disable-2fa', t => { t.test('--json', t => { npm.flatOptions.json = true - const profile = requireInject('../../lib/profile.js', { + const Profile = requireInject('../../lib/profile.js', { ...mocks, 'npm-profile': npmProfile(t), '../../lib/utils/read-user-info.js': readUserInfo(t), }) + const profile = new Profile(npm) - profile(['disable-2fa'], err => { + profile.exec(['disable-2fa'], err => { if (err) throw err @@ -1301,13 +1325,14 @@ t.test('disable-2fa', t => { t.test('--parseable', t => { npm.flatOptions.parseable = true - const profile = requireInject('../../lib/profile.js', { + const Profile = requireInject('../../lib/profile.js', { ...mocks, 'npm-profile': npmProfile(t), '../../lib/utils/read-user-info.js': readUserInfo(t), }) + const profile = new Profile(npm) - profile(['disable-2fa'], err => { + profile.exec(['disable-2fa'], err => { if (err) throw err @@ -1363,13 +1388,14 @@ t.test('disable-2fa', t => { }, } - const profile = requireInject('../../lib/profile.js', { + const Profile = requireInject('../../lib/profile.js', { ...mocks, 'npm-profile': npmProfile, '../../lib/utils/read-user-info.js': readUserInfo, }) + const profile = new Profile(npm) - profile(['disable-2fa'], err => { + profile.exec(['disable-2fa'], err => { if (err) throw err @@ -1385,7 +1411,7 @@ t.test('disable-2fa', t => { }) t.test('unknown subcommand', t => { - profile(['asfd'], err => { + profile.exec(['asfd'], err => { t.match( err, /Unknown profile command: asfd/, @@ -1396,11 +1422,9 @@ t.test('unknown subcommand', t => { }) t.test('completion', t => { - const { completion } = profile - const testComp = async ({ t, argv, expect, title }) => { t.resolveMatch( - completion({ conf: { argv: { remain: argv } } }), + profile.completion({ conf: { argv: { remain: argv } } }), expect, title ) @@ -1444,7 +1468,7 @@ t.test('completion', t => { t.test('npm profile unknown subcommand autocomplete', async t => { t.rejects( - completion({ conf: { argv: { remain: ['npm', 'profile', 'asdf'] } } }), + profile.completion({ conf: { argv: { remain: ['npm', 'profile', 'asdf'] } } }), { message: 'asdf not recognized' }, 'should throw unknown cmd error' ) t.end() diff --git a/deps/npm/test/lib/prune.js b/deps/npm/test/lib/prune.js index 074f4eac6e..8cd148806e 100644 --- a/deps/npm/test/lib/prune.js +++ b/deps/npm/test/lib/prune.js @@ -2,13 +2,7 @@ const { test } = require('tap') const requireInject = require('require-inject') test('should prune using Arborist', (t) => { - const prune = requireInject('../../lib/prune.js', { - '../../lib/npm.js': { - prefix: 'foo', - flatOptions: { - foo: 'bar', - }, - }, + const Prune = requireInject('../../lib/prune.js', { '@npmcli/arborist': function (args) { t.ok(args, 'gets options object') t.ok(args.path, 'gets path option') @@ -20,7 +14,13 @@ test('should prune using Arborist', (t) => { t.ok(arb, 'gets arborist tree') }, }) - prune(null, er => { + const prune = new Prune({ + prefix: 'foo', + flatOptions: { + foo: 'bar', + }, + }) + prune.exec(null, er => { if (er) throw er t.ok(true, 'callback is called') diff --git a/deps/npm/test/lib/publish.js b/deps/npm/test/lib/publish.js index 5243b52542..0e857fafdd 100644 --- a/deps/npm/test/lib/publish.js +++ b/deps/npm/test/lib/publish.js @@ -34,19 +34,7 @@ t.test('should publish with libnpmpublish, passing through flatOptions and respe }, null, 2), }) - const publish = requireInject('../../lib/publish.js', { - '../../lib/npm.js': { - flatOptions: { - customValue: true, - }, - config: { - ...config, - getCredentialsByURI: (uri) => { - t.same(uri, registry, 'gets credentials for expected registry') - return { token: 'some.registry.token' } - }, - }, - }, + const Publish = requireInject('../../lib/publish.js', { // verify that we do NOT remove publishConfig if it was there originally // and then removed during the script/pack process libnpmpack: async () => { @@ -66,11 +54,24 @@ t.test('should publish with libnpmpublish, passing through flatOptions and respe }, }, }) + const publish = new Publish({ + flatOptions: { + customValue: true, + }, + config: { + ...config, + getCredentialsByURI: (uri) => { + t.same(uri, registry, 'gets credentials for expected registry') + return { token: 'some.registry.token' } + }, + }, + }) - return publish([testDir], (er) => { + publish.exec([testDir], (er) => { if (er) throw er t.pass('got to callback') + t.end() }) }) @@ -85,16 +86,7 @@ t.test('re-loads publishConfig.registry if added during script process', (t) => }, null, 2), }) - const publish = requireInject('../../lib/publish.js', { - '../../lib/npm.js': { - config: { - ...config, - getCredentialsByURI: (uri) => { - t.same(uri, registry, 'gets credentials for expected registry') - return { token: 'some.registry.token' } - }, - }, - }, + const Publish = requireInject('../../lib/publish.js', { libnpmpack: async () => { fs.writeFileSync(`${testDir}/package.json`, JSON.stringify({ name: 'my-cool-pkg', @@ -112,11 +104,21 @@ 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' } + }, + }, + }) - return publish([testDir], (er) => { + publish.exec([testDir], (er) => { if (er) throw er t.pass('got to callback') + t.end() }) }) @@ -131,19 +133,7 @@ t.test('if loglevel=info and json, should not output package contents', (t) => { }) log.level = 'info' - const publish = requireInject('../../lib/publish.js', { - '../../lib/npm.js': { - flatOptions: { - json: true, - }, - config: { - ...config, - getCredentialsByURI: (uri) => { - t.same(uri, defaults.registry, 'gets credentials for expected registry') - return { token: 'some.registry.token' } - }, - }, - }, + const Publish = requireInject('../../lib/publish.js', { '../../lib/utils/output.js': () => { t.pass('output is called') }, @@ -161,11 +151,24 @@ 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' } + }, + }, + }) - return publish([testDir], (er) => { + publish.exec([testDir], (er) => { if (er) throw er t.pass('got to callback') + t.end() }) }) @@ -180,18 +183,7 @@ t.test('if loglevel=silent and dry-run, should not output package contents or pu }) log.level = 'silent' - const publish = requireInject('../../lib/publish.js', { - '../../lib/npm.js': { - flatOptions: { - dryRun: true, - }, - config: { - ...config, - getCredentialsByURI: () => { - throw new Error('should not call getCredentialsByURI in dry run') - }, - }, - }, + const Publish = requireInject('../../lib/publish.js', { '../../lib/utils/output.js': () => { throw new Error('should not output in dry run mode') }, @@ -209,11 +201,23 @@ 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') + }, + }, + }) - return publish([testDir], (er) => { + publish.exec([testDir], (er) => { if (er) throw er t.pass('got to callback') + t.end() }) }) @@ -228,17 +232,7 @@ t.test('if loglevel=info and dry-run, should not publish, should log package con }) log.level = 'info' - const publish = requireInject('../../lib/publish.js', { - '../../lib/npm.js': { - flatOptions: { - dryRun: true, - }, - config: { - ...config, - getCredentialsByURI: () => { - throw new Error('should not call getCredentialsByURI in dry run') - }}, - }, + const Publish = requireInject('../../lib/publish.js', { '../../lib/utils/tar.js': { getContents: () => ({ id: 'someid', @@ -256,37 +250,52 @@ 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') + }}, + }) - return publish([testDir], (er) => { + publish.exec([testDir], (er) => { if (er) throw er t.pass('got to callback') + t.end() }) }) t.test('shows usage with wrong set of arguments', (t) => { t.plan(1) - const publish = requireInject('../../lib/publish.js') + const Publish = requireInject('../../lib/publish.js') + const publish = new Publish({}) - return publish(['a', 'b', 'c'], (er) => t.matchSnapshot(er, 'should print usage')) + publish.exec(['a', 'b', 'c'], (er) => { + t.matchSnapshot(er, 'should print usage') + t.end() + }) }) t.test('throws when invalid tag', (t) => { t.plan(1) - const publish = requireInject('../../lib/publish.js', { - '../../lib/npm.js': { - flatOptions: { - defaultTag: '0.0.13', - }, - config, + const Publish = requireInject('../../lib/publish.js') + const publish = new Publish({ + flatOptions: { + defaultTag: '0.0.13', }, + config, }) - return publish([], (err) => { + publish.exec([], (err) => { t.match(err, { message: /Tag name must not be a valid SemVer range: /, }, 'throws when tag name is a valid SemVer range') + t.end() }) }) @@ -310,16 +319,7 @@ t.test('can publish a tarball', t => { }, ['package']) const tarFile = fs.readFileSync(`${testDir}/tarball/package.tgz`) - const publish = requireInject('../../lib/publish.js', { - '../../lib/npm.js': { - config: { - ...config, - getCredentialsByURI: (uri) => { - t.same(uri, defaults.registry, 'gets credentials for expected registry') - return { token: 'some.registry.token' } - }, - }, - }, + const Publish = requireInject('../../lib/publish.js', { libnpmpublish: { publish: (manifest, tarData, opts) => { t.match(manifest, { @@ -330,63 +330,73 @@ 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' } + }, + }, + }) - return publish([`${testDir}/tarball/package.tgz`], (er) => { + publish.exec([`${testDir}/tarball/package.tgz`], (er) => { if (er) throw er t.pass('got to callback') + t.end() }) }) -t.test('should check auth for default registry', async t => { +t.test('should check auth for default registry', t => { t.plan(2) - const publish = requireInject('../../lib/publish.js', { - '../../lib/npm.js': { - config: { - ...config, - getCredentialsByURI: (uri) => { - t.same(uri, defaults.registry, 'gets credentials for expected registry') - return {} - }, + 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 {} }, }, }) - return publish([], (err) => { + publish.exec([], (err) => { t.match(err, { message: 'This command requires you to be logged in.', code: 'ENEEDAUTH', }, 'throws when not logged in') + t.end() }) }) -t.test('should check auth for configured registry', async t => { +t.test('should check auth for configured registry', t => { t.plan(2) const registry = 'https://some.registry' - const publish = requireInject('../../lib/publish.js', { - '../../lib/npm.js': { - flatOptions: { - registry, - }, - config: { - ...config, - getCredentialsByURI: (uri) => { - t.same(uri, registry, 'gets credentials for expected registry') - return {} - }, + 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 {} }, }, }) - return publish([], (err) => { + publish.exec([], (err) => { t.match(err, { message: 'This command requires you to be logged in.', code: 'ENEEDAUTH', }, 'throws when not logged in') + t.end() }) }) -t.test('should check auth for scope specific registry', async t => { +t.test('should check auth for scope specific registry', t => { t.plan(2) const registry = 'https://some.registry' const testDir = t.testdir({ @@ -396,26 +406,26 @@ t.test('should check auth for scope specific registry', async t => { }, null, 2), }) - const publish = requireInject('../../lib/publish.js', { - '../../lib/npm.js': { - flatOptions: { - '@npm:registry': registry, - }, - config: { - ...config, - getCredentialsByURI: (uri) => { - t.same(uri, registry, 'gets credentials for expected registry') - return {} - }, + 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 {} }, }, }) - return publish([testDir], (err) => { + publish.exec([testDir], (err) => { t.match(err, { message: 'This command requires you to be logged in.', code: 'ENEEDAUTH', }, 'throws when not logged in') + t.end() }) }) @@ -429,19 +439,7 @@ t.test('should use auth for scope specific registry', t => { }, null, 2), }) - const publish = requireInject('../../lib/publish.js', { - '../../lib/npm.js': { - flatOptions: { - '@npm:registry': registry, - }, - config: { - ...config, - getCredentialsByURI: (uri) => { - t.same(uri, registry, 'gets credentials for expected registry') - return { token: 'some.registry.token' } - }, - }, - }, + const Publish = requireInject('../../lib/publish.js', { libnpmpublish: { publish: (manifest, tarData, opts) => { t.ok(opts, 'gets opts object') @@ -449,10 +447,23 @@ t.test('should use auth for scope specific registry', t => { }, }, }) - return publish([testDir], (er) => { + 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' } + }, + }, + }) + publish.exec([testDir], (er) => { if (er) throw er t.pass('got to callback') + t.end() }) }) @@ -469,16 +480,7 @@ t.test('read registry only from publishConfig', t => { }, null, 2), }) - const publish = requireInject('../../lib/publish.js', { - '../../lib/npm.js': { - config: { - ...config, - getCredentialsByURI: (uri) => { - t.same(uri, registry, 'gets credentials for expected registry') - return { token: 'some.registry.token' } - }, - }, - }, + const Publish = requireInject('../../lib/publish.js', { libnpmpublish: { publish: (manifest, tarData, opts) => { t.match(manifest, { name: 'my-cool-pkg', version: '1.0.0' }, 'gets manifest') @@ -486,10 +488,20 @@ 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' } + }, + }, + }) - return publish([testDir], (er) => { + publish.exec([testDir], (er) => { if (er) throw er t.pass('got to callback') + t.end() }) }) diff --git a/deps/npm/test/lib/rebuild.js b/deps/npm/test/lib/rebuild.js index d9df048d90..ee081c087f 100644 --- a/deps/npm/test/lib/rebuild.js +++ b/deps/npm/test/lib/rebuild.js @@ -13,14 +13,14 @@ const npm = { prefix: '', } const mocks = { - '../../lib/npm.js': npm, '../../lib/utils/output.js': (...msg) => { result += msg.join('\n') }, '../../lib/utils/usage.js': () => 'usage instructions', } -const rebuild = requireInject('../../lib/rebuild.js', mocks) +const Rebuild = requireInject('../../lib/rebuild.js', mocks) +const rebuild = new Rebuild(npm) t.afterEach(cb => { npm.prefix = '' @@ -67,7 +67,7 @@ t.test('no args', t => { npm.prefix = path - rebuild([], err => { + rebuild.exec([], err => { if (err) throw err @@ -115,7 +115,7 @@ t.test('filter by pkg name', t => { t.throws(() => fs.statSync(aBinFile)) t.throws(() => fs.statSync(bBinFile)) - rebuild(['b'], err => { + rebuild.exec(['b'], err => { if (err) throw err @@ -163,7 +163,7 @@ t.test('filter by pkg@<range>', t => { const bBinFile = resolve(path, 'node_modules/.bin/b') const nestedBinFile = resolve(path, 'node_modules/a/node_modules/.bin/b') - rebuild(['b@2'], err => { + rebuild.exec(['b@2'], err => { if (err) throw err @@ -203,7 +203,7 @@ t.test('filter by directory', t => { t.throws(() => fs.statSync(aBinFile)) t.throws(() => fs.statSync(bBinFile)) - rebuild(['file:node_modules/b'], err => { + rebuild.exec(['file:node_modules/b'], err => { if (err) throw err @@ -215,7 +215,7 @@ t.test('filter by directory', t => { }) t.test('filter must be a semver version/range, or directory', t => { - rebuild(['git+ssh://github.com/npm/arborist'], err => { + rebuild.exec(['git+ssh://github.com/npm/arborist'], err => { t.match( err, /Error: `npm rebuild` only supports SemVer version\/range specifiers/, @@ -245,7 +245,7 @@ t.test('global prefix', t => { npm.flatOptions.global = true npm.globalDir = resolve(globalPath, 'lib', 'node_modules') - rebuild([], err => { + rebuild.exec([], err => { if (err) throw err diff --git a/deps/npm/test/lib/repo.js b/deps/npm/test/lib/repo.js index 3367f7c885..9c22bbaea3 100644 --- a/deps/npm/test/lib/repo.js +++ b/deps/npm/test/lib/repo.js @@ -108,16 +108,16 @@ const pacote = { // keep a tally of which urls got opened const opened = {} -const openUrl = (url, errMsg, cb) => { +const openUrl = async (npm, url, errMsg) => { opened[url] = opened[url] || 0 opened[url]++ - process.nextTick(cb) } -const repo = requireInject('../../lib/repo.js', { +const Repo = requireInject('../../lib/repo.js', { pacote, '../../lib/utils/open-url.js': openUrl, }) +const repo = new Repo({ flatOptions: {} }) t.test('open repo urls', t => { const expect = { @@ -150,7 +150,7 @@ t.test('open repo urls', t => { t.plan(keys.length) keys.forEach(pkg => { t.test(pkg, t => { - repo([pkg], (er) => { + repo.exec([pkg], (er) => { if (er) throw er const url = expect[pkg] @@ -173,7 +173,7 @@ t.test('fail if cannot figure out repo url', t => { cases.forEach(pkg => { t.test(pkg, t => { - repo([pkg], er => { + repo.exec([pkg], er => { t.match(er, { pkgid: pkg }) t.end() }) @@ -182,7 +182,7 @@ t.test('fail if cannot figure out repo url', t => { }) t.test('open default package if none specified', t => { - repo([], (er) => { + repo.exec([], (er) => { if (er) throw er t.equal(opened['https://example.com/thispkg'], 2, 'opened expected url', {opened}) diff --git a/deps/npm/test/lib/restart.js b/deps/npm/test/lib/restart.js index a19bfd0d41..f29592d9bf 100644 --- a/deps/npm/test/lib/restart.js +++ b/deps/npm/test/lib/restart.js @@ -1,4 +1,17 @@ const t = require('tap') -const restart = require('../../lib/restart.js') -t.isa(restart, Function) +let runArgs +const npm = { + commands: { + 'run-script': (args, cb) => { + runArgs = args + cb() + }, + }, +} +const Restart = require('../../lib/restart.js') +const restart = new Restart(npm) t.equal(restart.usage, 'npm restart [-- <args>]') +restart.exec(['foo'], () => { + t.match(runArgs, ['restart', 'foo']) + t.end() +}) diff --git a/deps/npm/test/lib/root.js b/deps/npm/test/lib/root.js index 8c23152b3e..e8ccc1106d 100644 --- a/deps/npm/test/lib/root.js +++ b/deps/npm/test/lib/root.js @@ -5,14 +5,14 @@ test('root', (t) => { t.plan(3) const dir = '/root/dir' - const root = requireInject('../../lib/root.js', { - '../../lib/npm.js': { dir }, + const Root = requireInject('../../lib/root.js', { '../../lib/utils/output.js': (output) => { t.equal(output, dir, 'prints the correct directory') }, }) + const root = new Root({ dir }) - root([], (err) => { + root.exec([], (err) => { t.ifError(err, 'npm root') t.ok('should have printed directory') }) diff --git a/deps/npm/test/lib/run-script.js b/deps/npm/test/lib/run-script.js index 974202aa8c..43592d3243 100644 --- a/deps/npm/test/lib/run-script.js +++ b/deps/npm/test/lib/run-script.js @@ -22,19 +22,29 @@ const npm = { const output = [] -const npmlog = { level: 'warn' } -const getRS = windows => requireInject('../../lib/run-script.js', { - '@npmcli/run-script': Object.assign(async opts => { - RUN_SCRIPTS.push(opts) - }, { - isServerPackage: require('@npmcli/run-script').isServerPackage, - }), - npmlog, - '../../lib/npm.js': npm, - '../../lib/utils/is-windows-shell.js': windows, - '../../lib/utils/output.js': (...msg) => output.push(msg), +t.afterEach(cb => { + output.length = 0 + RUN_SCRIPTS.length = 0 + npm.flatOptions.json = false + npm.flatOptions.parseable = false + cb() }) +const npmlog = { level: 'warn' } +const getRS = windows => { + const RunScript = requireInject('../../lib/run-script.js', { + '@npmcli/run-script': Object.assign(async opts => { + RUN_SCRIPTS.push(opts) + }, { + isServerPackage: require('@npmcli/run-script').isServerPackage, + }), + npmlog, + '../../lib/utils/is-windows-shell.js': windows, + '../../lib/utils/output.js': (...msg) => output.push(msg), + }) + return new RunScript(npm) +} + const runScript = getRS(false) const runScriptWin = getRS(true) @@ -69,109 +79,119 @@ t.test('completion', t => { t.end() }) -t.test('fail if no package.json', async t => { +t.test('fail if no package.json', t => { + t.plan(2) npm.localPrefix = t.testdir() - await runScript([], er => t.match(er, { code: 'ENOENT' })) - await runScript(['test'], er => t.match(er, { code: 'ENOENT' })) + runScript.exec([], er => t.match(er, { code: 'ENOENT' })) + runScript.exec(['test'], er => t.match(er, { code: 'ENOENT' })) }) -t.test('default env, start, and restart scripts', async t => { +t.test('default env, start, and restart scripts', t => { npm.localPrefix = t.testdir({ 'package.json': JSON.stringify({ name: 'x', version: '1.2.3' }), 'server.js': 'console.log("hello, world")', }) - await runScript(['start'], er => { - if (er) - throw er + t.test('start', t => { + runScript.exec(['start'], er => { + if (er) + throw er - t.match(RUN_SCRIPTS, [ - { - path: npm.localPrefix, - args: [], - scriptShell: undefined, - stdio: 'inherit', - stdioString: true, - pkg: { name: 'x', version: '1.2.3', _id: 'x@1.2.3', scripts: {}}, - event: 'start', - }, - ]) + t.match(RUN_SCRIPTS, [ + { + path: npm.localPrefix, + args: [], + scriptShell: undefined, + stdio: 'inherit', + stdioString: true, + pkg: { name: 'x', version: '1.2.3', _id: 'x@1.2.3', scripts: {}}, + event: 'start', + }, + ]) + t.end() + }) }) - RUN_SCRIPTS.length = 0 - await runScript(['env'], er => { - if (er) - throw er + t.test('env', t => { + runScript.exec(['env'], er => { + if (er) + throw er - t.match(RUN_SCRIPTS, [ - { - path: npm.localPrefix, - args: [], - scriptShell: undefined, - stdio: 'inherit', - stdioString: true, - pkg: { - name: 'x', - version: '1.2.3', - _id: 'x@1.2.3', - scripts: { - env: 'env', + t.match(RUN_SCRIPTS, [ + { + path: npm.localPrefix, + args: [], + scriptShell: undefined, + stdio: 'inherit', + stdioString: true, + pkg: { + name: 'x', + version: '1.2.3', + _id: 'x@1.2.3', + scripts: { + env: 'env', + }, }, + event: 'env', }, - event: 'env', - }, - ]) + ]) + t.end() + }) }) - RUN_SCRIPTS.length = 0 - await runScriptWin(['env'], er => { - if (er) - throw er + t.test('windows env', t => { + runScriptWin.exec(['env'], er => { + if (er) + throw er - t.match(RUN_SCRIPTS, [ - { - path: npm.localPrefix, - args: [], - scriptShell: undefined, - stdio: 'inherit', - stdioString: true, - pkg: { name: 'x', - version: '1.2.3', - _id: 'x@1.2.3', - scripts: { - env: 'SET', - } }, - event: 'env', - }, - ]) + t.match(RUN_SCRIPTS, [ + { + path: npm.localPrefix, + args: [], + scriptShell: undefined, + stdio: 'inherit', + stdioString: true, + pkg: { name: 'x', + version: '1.2.3', + _id: 'x@1.2.3', + scripts: { + env: 'SET', + } }, + event: 'env', + }, + ]) + t.end() + }) }) - RUN_SCRIPTS.length = 0 - await runScript(['restart'], er => { - if (er) - throw er + t.test('restart', t => { + runScript.exec(['restart'], er => { + if (er) + throw er - t.match(RUN_SCRIPTS, [ - { - path: npm.localPrefix, - args: [], - scriptShell: undefined, - stdio: 'inherit', - stdioString: true, - pkg: { name: 'x', - version: '1.2.3', - _id: 'x@1.2.3', - scripts: { - restart: 'npm stop --if-present && npm start', - } }, - event: 'restart', - }, - ]) + t.match(RUN_SCRIPTS, [ + { + path: npm.localPrefix, + args: [], + scriptShell: undefined, + stdio: 'inherit', + stdioString: true, + pkg: { name: 'x', + version: '1.2.3', + _id: 'x@1.2.3', + scripts: { + restart: 'npm stop --if-present && npm start', + } }, + event: 'restart', + }, + ]) + t.end() + }) }) - RUN_SCRIPTS.length = 0 + t.end() }) -t.test('non-default env script', async t => { +t.test('non-default env script', t => { npm.localPrefix = t.testdir({ 'package.json': JSON.stringify({ name: 'x', @@ -182,54 +202,59 @@ t.test('non-default env script', async t => { }), }) - await runScript(['env'], er => { - if (er) - throw er + t.test('env', t => { + runScript.exec(['env'], er => { + if (er) + throw er - t.match(RUN_SCRIPTS, [ - { - path: npm.localPrefix, - args: [], - scriptShell: undefined, - stdio: 'inherit', - stdioString: true, - pkg: { - name: 'x', - version: '1.2.3', - _id: 'x@1.2.3', - scripts: { - env: 'hello', + t.match(RUN_SCRIPTS, [ + { + path: npm.localPrefix, + args: [], + scriptShell: undefined, + stdio: 'inherit', + stdioString: true, + pkg: { + name: 'x', + version: '1.2.3', + _id: 'x@1.2.3', + scripts: { + env: 'hello', + }, }, + event: 'env', }, - event: 'env', - }, - ]) + ]) + t.end() + }) }) - RUN_SCRIPTS.length = 0 - await runScriptWin(['env'], er => { - if (er) - throw er + t.test('env windows', t => { + runScriptWin.exec(['env'], er => { + if (er) + throw er - t.match(RUN_SCRIPTS, [ - { - path: npm.localPrefix, - args: [], - scriptShell: undefined, - stdio: 'inherit', - stdioString: true, - pkg: { name: 'x', - version: '1.2.3', - _id: 'x@1.2.3', - scripts: { - env: 'hello', + t.match(RUN_SCRIPTS, [ + { + path: npm.localPrefix, + args: [], + scriptShell: undefined, + stdio: 'inherit', + stdioString: true, + pkg: { name: 'x', + version: '1.2.3', + _id: 'x@1.2.3', + scripts: { + env: 'hello', + }, }, + event: 'env', }, - event: 'env', - }, - ]) + ]) + t.end() + }) }) - RUN_SCRIPTS.length = 0 + t.end() }) t.test('try to run missing script', t => { @@ -238,33 +263,36 @@ t.test('try to run missing script', t => { scripts: { hello: 'world' }, }), }) - t.test('no suggestions', async t => { - await runScript(['notevenclose'], er => { + t.test('no suggestions', t => { + runScript.exec(['notevenclose'], er => { t.match(er, { message: 'missing script: notevenclose', }) + t.end() }) }) - t.test('suggestions', async t => { - await runScript(['helo'], er => { + t.test('suggestions', t => { + runScript.exec(['helo'], er => { t.match(er, { message: 'missing script: helo\n\nDid you mean this?\n hello', }) + t.end() }) }) - t.test('with --if-present', async t => { + t.test('with --if-present', t => { npm.config.set('if-present', true) - await runScript(['goodbye'], er => { + runScript.exec(['goodbye'], er => { if (er) throw er t.strictSame(RUN_SCRIPTS, [], 'did not try to run anything') + t.end() }) }) t.end() }) -t.test('run pre/post hooks', async t => { +t.test('run pre/post hooks', t => { npm.localPrefix = t.testdir({ 'package.json': JSON.stringify({ name: 'x', @@ -276,7 +304,7 @@ t.test('run pre/post hooks', async t => { }), }) - await runScript(['env'], er => { + runScript.exec(['env'], er => { if (er) throw er @@ -298,11 +326,11 @@ t.test('run pre/post hooks', async t => { }, { event: 'postenv' }, ]) + t.end() }) - RUN_SCRIPTS.length = 0 }) -t.test('skip pre/post hooks when using ignoreScripts', async t => { +t.test('skip pre/post hooks when using ignoreScripts', t => { npm.flatOptions.ignoreScripts = true npm.localPrefix = t.testdir({ @@ -316,7 +344,7 @@ t.test('skip pre/post hooks when using ignoreScripts', async t => { }), }) - await runScript(['env'], er => { + runScript.exec(['env'], er => { if (er) throw er @@ -339,13 +367,12 @@ t.test('skip pre/post hooks when using ignoreScripts', async t => { event: 'env', }, ]) - + t.end() delete npm.flatOptions.ignoreScripts }) - RUN_SCRIPTS.length = 0 }) -t.test('run silent', async t => { +t.test('run silent', t => { npmlog.level = 'silent' t.teardown(() => { npmlog.level = 'warn' @@ -362,7 +389,7 @@ t.test('run silent', async t => { }), }) - await runScript(['env'], er => { + runScript.exec(['env'], er => { if (er) throw er @@ -391,11 +418,11 @@ t.test('run silent', async t => { stdio: 'inherit', }, ]) + t.end() }) - RUN_SCRIPTS.length = 0 }) -t.test('list scripts', async t => { +t.test('list scripts', t => { const scripts = { test: 'exit 2', start: 'node server.js', @@ -411,55 +438,62 @@ t.test('list scripts', async t => { }), }) - await runScript([], er => { - if (er) - throw er + t.test('no args', t => { + runScript.exec([], er => { + if (er) + throw er + t.strictSame(output, [ + ['Lifecycle scripts included in x:'], + [' test\n exit 2'], + [' start\n node server.js'], + [' stop\n node kill-server.js'], + ['\navailable via `npm run-script`:'], + [' preenv\n echo before the env'], + [' postenv\n echo after the env'], + ], 'basic report') + t.end() + }) }) - t.strictSame(output, [ - ['Lifecycle scripts included in x:'], - [' test\n exit 2'], - [' start\n node server.js'], - [' stop\n node kill-server.js'], - ['\navailable via `npm run-script`:'], - [' preenv\n echo before the env'], - [' postenv\n echo after the env'], - ], 'basic report') - output.length = 0 - npmlog.level = 'silent' - await runScript([], er => { - if (er) - throw er + t.test('silent', t => { + npmlog.level = 'silent' + runScript.exec([], er => { + if (er) + throw er + t.strictSame(output, []) + t.end() + }) }) - t.strictSame(output, []) - npmlog.level = 'warn' - - npm.flatOptions.json = true - await runScript([], er => { - if (er) - throw er + t.test('warn json', t => { + npmlog.level = 'warn' + npm.flatOptions.json = true + runScript.exec([], er => { + if (er) + throw er + t.strictSame(output, [[JSON.stringify(scripts, 0, 2)]], 'json report') + t.end() + }) }) - t.strictSame(output, [[JSON.stringify(scripts, 0, 2)]], 'json report') - output.length = 0 - npm.flatOptions.json = false - npm.flatOptions.parseable = true - await runScript([], er => { - if (er) - throw er + t.test('parseable', t => { + npm.flatOptions.parseable = true + runScript.exec([], er => { + if (er) + throw er + t.strictSame(output, [ + ['test:exit 2'], + ['start:node server.js'], + ['stop:node kill-server.js'], + ['preenv:echo before the env'], + ['postenv:echo after the env'], + ]) + t.end() + }) }) - t.strictSame(output, [ - ['test:exit 2'], - ['start:node server.js'], - ['stop:node kill-server.js'], - ['preenv:echo before the env'], - ['postenv:echo after the env'], - ]) - output.length = 0 - npm.flatOptions.parseable = false + t.end() }) -t.test('list scripts when no scripts', async t => { +t.test('list scripts when no scripts', t => { npm.localPrefix = t.testdir({ 'package.json': JSON.stringify({ name: 'x', @@ -467,15 +501,15 @@ t.test('list scripts when no scripts', async t => { }), }) - await runScript([], er => { + runScript.exec([], er => { if (er) throw er + t.strictSame(output, [], 'nothing to report') + t.end() }) - t.strictSame(output, [], 'nothing to report') - output.length = 0 }) -t.test('list scripts, only commands', async t => { +t.test('list scripts, only commands', t => { npm.localPrefix = t.testdir({ 'package.json': JSON.stringify({ name: 'x', @@ -484,18 +518,18 @@ t.test('list scripts, only commands', async t => { }), }) - await runScript([], er => { + runScript.exec([], er => { if (er) throw er + t.strictSame(output, [ + ['Lifecycle scripts included in x:'], + [' preversion\n echo doing the version dance'], + ]) + t.end() }) - t.strictSame(output, [ - ['Lifecycle scripts included in x:'], - [' preversion\n echo doing the version dance'], - ]) - output.length = 0 }) -t.test('list scripts, only non-commands', async t => { +t.test('list scripts, only non-commands', t => { npm.localPrefix = t.testdir({ 'package.json': JSON.stringify({ name: 'x', @@ -504,13 +538,13 @@ t.test('list scripts, only non-commands', async t => { }), }) - await runScript([], er => { + runScript.exec([], er => { if (er) throw er + t.strictSame(output, [ + ['Scripts available in x via `npm run-script`:'], + [' glorp\n echo doing the glerp glop'], + ]) + t.end() }) - t.strictSame(output, [ - ['Scripts available in x via `npm run-script`:'], - [' glorp\n echo doing the glerp glop'], - ]) - output.length = 0 }) diff --git a/deps/npm/test/lib/search.js b/deps/npm/test/lib/search.js index 1dba1250e0..59c59f3b96 100644 --- a/deps/npm/test/lib/search.js +++ b/deps/npm/test/lib/search.js @@ -23,7 +23,6 @@ const libnpmsearch = { const mocks = { npmlog, libnpmsearch, - '../../lib/npm.js': npm, '../../lib/utils/output.js': (...msg) => { result += msg.join('\n') }, @@ -37,10 +36,11 @@ t.afterEach(cb => { cb() }) -const search = requireInject('../../lib/search.js', mocks) +const Search = requireInject('../../lib/search.js', mocks) +const search = new Search(npm) t.test('no args', t => { - search([], err => { + search.exec([], err => { t.match( err, /search must be called with arguments/, @@ -59,12 +59,13 @@ t.test('search <name>', t => { }, } - const search = requireInject('../../lib/search.js', { + const Search = requireInject('../../lib/search.js', { ...mocks, libnpmsearch, }) + const search = new Search(npm) - search(['libnpm'], err => { + search.exec(['libnpm'], err => { if (err) throw err @@ -93,12 +94,13 @@ t.test('search <name> --searchexclude --searchopts', t => { }, } - const search = requireInject('../../lib/search.js', { + const Search = requireInject('../../lib/search.js', { ...mocks, libnpmsearch, }) + const search = new Search(npm) - search(['foo'], err => { + search.exec(['foo'], err => { if (err) throw err @@ -146,12 +148,13 @@ t.test('empty search results', t => { }, } - const search = requireInject('../../lib/search.js', { + const Search = requireInject('../../lib/search.js', { ...mocks, libnpmsearch, }) + const search = new Search(npm) - search(['foo'], err => { + search.exec(['foo'], err => { if (err) throw err @@ -172,12 +175,13 @@ t.test('search api response error', t => { }, } - const search = requireInject('../../lib/search.js', { + const Search = requireInject('../../lib/search.js', { ...mocks, libnpmsearch, }) + const search = new Search(npm) - search(['foo'], err => { + search.exec(['foo'], err => { t.match( err, /ERR/, diff --git a/deps/npm/test/lib/set-script.js b/deps/npm/test/lib/set-script.js index 196fd3d3e6..7a057c5036 100644 --- a/deps/npm/test/lib/set-script.js +++ b/deps/npm/test/lib/set-script.js @@ -1,54 +1,50 @@ const test = require('tap') const requireInject = require('require-inject') -const setScriptDefault = require('../../lib/set-script.js') const parseJSON = require('json-parse-even-better-errors') -test.type(setScriptDefault, 'function', 'command is function') -test.equal(setScriptDefault.usage, 'npm set-script [<script>] [<command>]', 'usage matches') test.test('fails on invalid arguments', (t) => { - const setScript = requireInject('../../lib/set-script.js', { - fs: {}, + const SetScript = requireInject('../../lib/set-script.js', { npmlog: {}, }) + const setScript = new SetScript({}) t.plan(3) - setScript(['arg1'], (fail) => t.match(fail, /Expected 2 arguments: got 1/)) - setScript(['arg1', 'arg2', 'arg3'], (fail) => t.match(fail, /Expected 2 arguments: got 3/)) - setScript(['arg1', 'arg2', 'arg3', 'arg4'], (fail) => t.match(fail, /Expected 2 arguments: got 4/)) + setScript.exec(['arg1'], (fail) => t.match(fail, /Expected 2 arguments: got 1/)) + setScript.exec(['arg1', 'arg2', 'arg3'], (fail) => t.match(fail, /Expected 2 arguments: got 3/)) + setScript.exec(['arg1', 'arg2', 'arg3', 'arg4'], (fail) => t.match(fail, /Expected 2 arguments: got 4/)) }) test.test('fails if run in postinstall script', (t) => { - var originalVar = process.env.npm_lifecycle_event + const originalVar = process.env.npm_lifecycle_event process.env.npm_lifecycle_event = 'postinstall' - const setScript = requireInject('../../lib/set-script.js', { - fs: {}, + const SetScript = requireInject('../../lib/set-script.js', { npmlog: {}, }) t.plan(1) - setScript(['arg1', 'arg2'], (fail) => t.equal(fail.toString(), 'Error: Scripts can’t set from the postinstall script')) + const setScript = new SetScript({}) + setScript.exec(['arg1', 'arg2'], (fail) => t.equal(fail.toString(), 'Error: Scripts can’t set from the postinstall script')) process.env.npm_lifecycle_event = originalVar }) test.test('fails when package.json not found', (t) => { - const setScript = requireInject('../../lib/set-script.js', { - '../../lib/npm.js': { - localPrefix: 'IDONTEXIST', - }, - }) + const SetScript = requireInject('../../lib/set-script.js') + const setScript = new SetScript({}) t.plan(1) - setScript(['arg1', 'arg2'], (fail) => t.match(fail, /package.json not found/)) + setScript.exec(['arg1', 'arg2'], (fail) => t.match(fail, /package.json not found/)) }) test.test('fails on invalid JSON', (t) => { - const setScript = requireInject('../../lib/set-script.js', { + const SetScript = requireInject('../../lib/set-script.js', { fs: { + readFile: () => {}, // read-package-json-fast explodes w/o this readFileSync: (name, charcode) => { return 'iamnotjson' }, }, }) + const setScript = new SetScript({}) t.plan(1) - setScript(['arg1', 'arg2'], (fail) => t.match(fail, /Invalid package.json: JSONParseError/)) + setScript.exec(['arg1', 'arg2'], (fail) => t.match(fail, /Invalid package.json: JSONParseError/)) }) test.test('creates scripts object', (t) => { var mockFile = '' - const setScript = requireInject('../../lib/set-script.js', { + const SetScript = requireInject('../../lib/set-script.js', { fs: { readFileSync: (name, charcode) => { return '{}' @@ -64,15 +60,16 @@ test.test('creates scripts object', (t) => { } }, }) + const setScript = new SetScript({}) t.plan(2) - setScript(['arg1', 'arg2'], (error) => { + setScript.exec(['arg1', 'arg2'], (error) => { t.equal(error, undefined) t.assert(parseJSON(mockFile), {scripts: {arg1: 'arg2'}}) }) }) test.test('warns before overwriting', (t) => { var warningListened = '' - const setScript = requireInject('../../lib/set-script.js', { + const SetScript = requireInject('../../lib/set-script.js', { fs: { readFileSync: (name, charcode) => { return JSON.stringify({ @@ -95,15 +92,16 @@ test.test('warns before overwriting', (t) => { }, }, }) + const setScript = new SetScript({}) t.plan(2) - setScript(['arg1', 'arg2'], (error) => { + setScript.exec(['arg1', 'arg2'], (error) => { t.equal(error, undefined, 'no error') t.equal(warningListened, 'Script "arg1" was overwritten') }) }) test.test('provided indentation and eol is used', (t) => { var mockFile = '' - const setScript = requireInject('../../lib/set-script.js', { + const SetScript = requireInject('../../lib/set-script.js', { fs: { readFileSync: (name, charcode) => { return '{}' @@ -119,8 +117,9 @@ test.test('provided indentation and eol is used', (t) => { } }, }) + const setScript = new SetScript({}) t.plan(3) - setScript(['arg1', 'arg2'], (error) => { + setScript.exec(['arg1', 'arg2'], (error) => { t.equal(error, undefined) t.equal(mockFile.split('\r\n').length > 1, true) t.equal(mockFile.split('\r\n').every((value) => !value.startsWith(' ') || value.startsWith(' '.repeat(6))), true) @@ -128,7 +127,7 @@ test.test('provided indentation and eol is used', (t) => { }) test.test('goes to default when undefined indent and eol provided', (t) => { var mockFile = '' - const setScript = requireInject('../../lib/set-script.js', { + const SetScript = requireInject('../../lib/set-script.js', { fs: { readFileSync: (name, charcode) => { return '{}' @@ -144,8 +143,9 @@ test.test('goes to default when undefined indent and eol provided', (t) => { } }, }) + const setScript = new SetScript({}) t.plan(3) - setScript(['arg1', 'arg2'], (error) => { + setScript.exec(['arg1', 'arg2'], (error) => { t.equal(error, undefined) t.equal(mockFile.split('\n').length > 1, true) t.equal(mockFile.split('\n').every((value) => !value.startsWith(' ') || value.startsWith(' ')), true) diff --git a/deps/npm/test/lib/set.js b/deps/npm/test/lib/set.js index aeb239e9c4..3b38fdc276 100644 --- a/deps/npm/test/lib/set.js +++ b/deps/npm/test/lib/set.js @@ -1,4 +1,4 @@ -const { test } = require('tap') +const t = require('tap') const requireInject = require('require-inject') let configArgs = null @@ -6,24 +6,23 @@ const npm = { commands: { config: (args, cb) => { configArgs = args - return cb() + cb() }, }, } -const set = requireInject('../../lib/set.js', { - '../../lib/npm.js': npm, -}) +const Set = requireInject('../../lib/set.js') +const set = new Set(npm) -test('npm set - no args', t => { - return set([], (err) => { +t.test('npm set - no args', t => { + set.exec([], (err) => { t.match(err, /npm set/, 'prints usage') t.end() }) }) -test('npm set', t => { - return set(['email', 'me@me.me'], (err) => { +t.test('npm set', t => { + set.exec(['email', 'me@me.me'], (err) => { if (err) throw err diff --git a/deps/npm/test/lib/shrinkwrap.js b/deps/npm/test/lib/shrinkwrap.js index 51fd7931a1..dc4bc3b220 100644 --- a/deps/npm/test/lib/shrinkwrap.js +++ b/deps/npm/test/lib/shrinkwrap.js @@ -31,7 +31,6 @@ const mocks = { return tree } }, - '../../lib/npm.js': npm, '../../lib/utils/usage.js': () => 'usage instructions', } @@ -80,13 +79,14 @@ t.test('no args', t => { }, } - const shrinkwrap = requireInject('../../lib/shrinkwrap.js', { + const Shrinkwrap = requireInject('../../lib/shrinkwrap.js', { ...mocks, npmlog, '@npmcli/arborist': Arborist, }) + const shrinkwrap = new Shrinkwrap(npm) - shrinkwrap([], err => { + shrinkwrap.exec([], err => { if (err) throw err }) @@ -134,13 +134,14 @@ t.test('no virtual tree', t => { }, } - const shrinkwrap = requireInject('../../lib/shrinkwrap.js', { + const Shrinkwrap = requireInject('../../lib/shrinkwrap.js', { ...mocks, npmlog, '@npmcli/arborist': Arborist, }) + const shrinkwrap = new Shrinkwrap(npm) - shrinkwrap([], err => { + shrinkwrap.exec([], err => { if (err) throw err }) @@ -194,14 +195,15 @@ t.test('existing package-json file', t => { }, } - const shrinkwrap = requireInject('../../lib/shrinkwrap.js', { + const Shrinkwrap = requireInject('../../lib/shrinkwrap.js', { ...mocks, fs, npmlog, '@npmcli/arborist': Arborist, }) + const shrinkwrap = new Shrinkwrap(npm) - shrinkwrap([], err => { + shrinkwrap.exec([], err => { if (err) throw err }) @@ -248,13 +250,14 @@ t.test('update shrinkwrap file version', t => { }, } - const shrinkwrap = requireInject('../../lib/shrinkwrap.js', { + const Shrinkwrap = requireInject('../../lib/shrinkwrap.js', { ...mocks, npmlog, '@npmcli/arborist': Arborist, }) + const shrinkwrap = new Shrinkwrap(npm) - shrinkwrap([], err => { + shrinkwrap.exec([], err => { if (err) throw err }) @@ -301,24 +304,26 @@ t.test('update to date shrinkwrap file', t => { }, } - const shrinkwrap = requireInject('../../lib/shrinkwrap.js', { + const Shrinkwrap = requireInject('../../lib/shrinkwrap.js', { ...mocks, npmlog, '@npmcli/arborist': Arborist, }) + const shrinkwrap = new Shrinkwrap(npm) - shrinkwrap([], err => { + shrinkwrap.exec([], err => { if (err) throw err }) }) t.test('shrinkwrap --global', t => { - const shrinkwrap = requireInject('../../lib/shrinkwrap.js', mocks) + const Shrinkwrap = requireInject('../../lib/shrinkwrap.js', mocks) npm.flatOptions.global = true + const shrinkwrap = new Shrinkwrap(npm) - shrinkwrap([], err => { + shrinkwrap.exec([], err => { t.match( err, /does not work for global packages/, @@ -330,8 +335,11 @@ t.test('shrinkwrap --global', t => { }) t.test('works without fs.promises', async t => { - t.doesNotThrow(() => requireInject('../../lib/shrinkwrap.js', { - ...mocks, - fs: { ...fs, promises: null }, - })) + t.doesNotThrow(() => { + const Shrinkwrap = requireInject('../../lib/shrinkwrap.js', { + ...mocks, + fs: { ...fs, promises: null }, + }) + new Shrinkwrap(npm) + }) }) diff --git a/deps/npm/test/lib/star.js b/deps/npm/test/lib/star.js index ea5e07b94f..64efd9ef8c 100644 --- a/deps/npm/test/lib/star.js +++ b/deps/npm/test/lib/star.js @@ -10,7 +10,6 @@ const npmlog = { error: noop, info: noop, verbose: noop } const mocks = { npmlog, 'npm-registry-fetch': npmFetch, - '../../lib/npm.js': npm, '../../lib/utils/output.js': (...msg) => { result += msg.join('\n') }, @@ -18,7 +17,8 @@ const mocks = { '../../lib/utils/usage.js': () => 'usage instructions', } -const star = requireInject('../../lib/star.js', mocks) +const Star = requireInject('../../lib/star.js', mocks) +const star = new Star(npm) t.afterEach(cb => { npm.config = { get () {} } @@ -29,7 +29,7 @@ t.afterEach(cb => { }) t.test('no args', t => { - star([], err => { + star.exec([], err => { t.match( err, /usage instructions/, @@ -56,7 +56,7 @@ t.test('star a package', t => { t.equal(msg, 'starring', 'should use expected msg') t.equal(id, pkgName, 'should use expected id') } - star([pkgName], err => { + star.exec([pkgName], err => { if (err) throw err t.equal( @@ -84,7 +84,7 @@ t.test('unstar a package', t => { t.equal(msg, 'unstarring', 'should use expected msg') t.equal(id, pkgName, 'should use expected id') } - star([pkgName], err => { + star.exec([pkgName], err => { if (err) throw err t.equal( @@ -99,7 +99,7 @@ t.test('unicode', async t => { t.test('star a package', t => { npm.flatOptions.unicode = true npmFetch.json = async (uri, opts) => ({}) - star(['pkg'], err => { + star.exec(['pkg'], err => { if (err) throw err t.equal( @@ -115,7 +115,7 @@ t.test('unicode', async t => { npm.flatOptions.unicode = true npm.config.get = key => key === 'star.unstar' npmFetch.json = async (uri, opts) => ({}) - star(['pkg'], err => { + star.exec(['pkg'], err => { if (err) throw err t.equal( @@ -129,11 +129,12 @@ t.test('unicode', async t => { }) t.test('logged out user', t => { - const star = requireInject('../../lib/star.js', { + const Star = requireInject('../../lib/star.js', { ...mocks, '../../lib/utils/get-identity.js': async () => undefined, }) - star(['@npmcli/arborist'], err => { + const star = new Star(npm) + star.exec(['@npmcli/arborist'], err => { t.match( err, /You need to be logged in/, diff --git a/deps/npm/test/lib/stars.js b/deps/npm/test/lib/stars.js index ff636a5e54..383b5adf42 100644 --- a/deps/npm/test/lib/stars.js +++ b/deps/npm/test/lib/stars.js @@ -10,7 +10,6 @@ const npmlog = { warn: noop } const mocks = { npmlog, 'npm-registry-fetch': npmFetch, - '../../lib/npm.js': npm, '../../lib/utils/output.js': (...msg) => { result = [result, ...msg].join('\n') }, @@ -18,7 +17,8 @@ const mocks = { '../../lib/utils/usage.js': () => 'usage instructions', } -const stars = requireInject('../../lib/stars.js', mocks) +const Stars = requireInject('../../lib/stars.js', mocks) +const stars = new Stars(npm) t.afterEach(cb => { npm.config = { get () {} } @@ -43,7 +43,7 @@ t.test('no args', t => { } } - stars([], err => { + stars.exec([], err => { if (err) throw err @@ -67,7 +67,7 @@ t.test('npm star <user>', t => { } } - stars(['ruyadorno'], err => { + stars.exec(['ruyadorno'], err => { if (err) throw err @@ -97,7 +97,7 @@ t.test('unauthorized request', t => { ) } - stars([], err => { + stars.exec([], err => { t.match( err, /Not logged in/, @@ -121,7 +121,7 @@ t.test('unexpected error', t => { throw new Error('Should not output extra warning msgs') } - stars([], err => { + stars.exec([], err => { t.match( err, /ERROR/, @@ -144,7 +144,7 @@ t.test('no pkg starred', t => { ) } - stars([], err => { + stars.exec([], err => { if (err) throw err }) diff --git a/deps/npm/test/lib/start.js b/deps/npm/test/lib/start.js index 4f599223d7..9a3328309b 100644 --- a/deps/npm/test/lib/start.js +++ b/deps/npm/test/lib/start.js @@ -1,4 +1,17 @@ const t = require('tap') -const start = require('../../lib/start.js') -t.isa(start, Function) +let runArgs +const npm = { + commands: { + 'run-script': (args, cb) => { + runArgs = args + cb() + }, + }, +} +const Start = require('../../lib/start.js') +const start = new Start(npm) t.equal(start.usage, 'npm start [-- <args>]') +start.exec(['foo'], () => { + t.match(runArgs, ['start', 'foo']) + t.end() +}) diff --git a/deps/npm/test/lib/stop.js b/deps/npm/test/lib/stop.js index 4e26703c93..e6cb193b62 100644 --- a/deps/npm/test/lib/stop.js +++ b/deps/npm/test/lib/stop.js @@ -1,4 +1,17 @@ const t = require('tap') -const stop = require('../../lib/stop.js') -t.isa(stop, Function) +let runArgs +const npm = { + commands: { + 'run-script': (args, cb) => { + runArgs = args + cb() + }, + }, +} +const Stop = require('../../lib/stop.js') +const stop = new Stop(npm) t.equal(stop.usage, 'npm stop [-- <args>]') +stop.exec(['foo'], () => { + t.match(runArgs, ['stop', 'foo']) + t.end() +}) diff --git a/deps/npm/test/lib/team.js b/deps/npm/test/lib/team.js index 9edaf58ee7..a264597258 100644 --- a/deps/npm/test/lib/team.js +++ b/deps/npm/test/lib/team.js @@ -14,7 +14,6 @@ const npm = { flatOptions: {} } const mocks = { libnpmteam, 'cli-columns': a => a.join(' '), - '../../lib/npm.js': npm, '../../lib/utils/output.js': (...msg) => { result += msg.join('\n') }, @@ -28,10 +27,11 @@ t.afterEach(cb => { cb() }) -const team = requireInject('../../lib/team.js', mocks) +const Team = requireInject('../../lib/team.js', mocks) +const team = new Team(npm) t.test('no args', t => { - team([], err => { + team.exec([], err => { t.match( err, 'usage instructions', @@ -43,7 +43,7 @@ t.test('no args', t => { t.test('team add <scope:team> <user>', t => { t.test('default output', t => { - team(['add', '@npmcli:developers', 'foo'], err => { + team.exec(['add', '@npmcli:developers', 'foo'], err => { if (err) throw err @@ -55,7 +55,7 @@ t.test('team add <scope:team> <user>', t => { t.test('--parseable', t => { npm.flatOptions.parseable = true - team(['add', '@npmcli:developers', 'foo'], err => { + team.exec(['add', '@npmcli:developers', 'foo'], err => { if (err) throw err @@ -70,7 +70,7 @@ t.test('team add <scope:team> <user>', t => { t.test('--json', t => { npm.flatOptions.json = true - team(['add', '@npmcli:developers', 'foo'], err => { + team.exec(['add', '@npmcli:developers', 'foo'], err => { if (err) throw err @@ -90,7 +90,7 @@ t.test('team add <scope:team> <user>', t => { t.test('--silent', t => { npm.flatOptions.silent = true - team(['add', '@npmcli:developers', 'foo'], err => { + team.exec(['add', '@npmcli:developers', 'foo'], err => { if (err) throw err @@ -104,7 +104,7 @@ t.test('team add <scope:team> <user>', t => { t.test('team create <scope:team>', t => { t.test('default output', t => { - team(['create', '@npmcli:newteam'], err => { + team.exec(['create', '@npmcli:newteam'], err => { if (err) throw err @@ -116,7 +116,7 @@ t.test('team create <scope:team>', t => { t.test('--parseable', t => { npm.flatOptions.parseable = true - team(['create', '@npmcli:newteam'], err => { + team.exec(['create', '@npmcli:newteam'], err => { if (err) throw err @@ -131,7 +131,7 @@ t.test('team create <scope:team>', t => { t.test('--json', t => { npm.flatOptions.json = true - team(['create', '@npmcli:newteam'], err => { + team.exec(['create', '@npmcli:newteam'], err => { if (err) throw err @@ -150,7 +150,7 @@ t.test('team create <scope:team>', t => { t.test('--silent', t => { npm.flatOptions.silent = true - team(['create', '@npmcli:newteam'], err => { + team.exec(['create', '@npmcli:newteam'], err => { if (err) throw err @@ -164,7 +164,7 @@ t.test('team create <scope:team>', t => { t.test('team destroy <scope:team>', t => { t.test('default output', t => { - team(['destroy', '@npmcli:newteam'], err => { + team.exec(['destroy', '@npmcli:newteam'], err => { if (err) throw err @@ -176,7 +176,7 @@ t.test('team destroy <scope:team>', t => { t.test('--parseable', t => { npm.flatOptions.parseable = true - team(['destroy', '@npmcli:newteam'], err => { + team.exec(['destroy', '@npmcli:newteam'], err => { if (err) throw err @@ -188,7 +188,7 @@ t.test('team destroy <scope:team>', t => { t.test('--json', t => { npm.flatOptions.json = true - team(['destroy', '@npmcli:newteam'], err => { + team.exec(['destroy', '@npmcli:newteam'], err => { if (err) throw err @@ -207,7 +207,7 @@ t.test('team destroy <scope:team>', t => { t.test('--silent', t => { npm.flatOptions.silent = true - team(['destroy', '@npmcli:newteam'], err => { + team.exec(['destroy', '@npmcli:newteam'], err => { if (err) throw err @@ -230,13 +230,14 @@ t.test('team ls <scope>', t => { }, } - const team = requireInject('../../lib/team.js', { + const Team = requireInject('../../lib/team.js', { ...mocks, libnpmteam, }) + const team = new Team(npm) t.test('default output', t => { - team(['ls', '@npmcli'], err => { + team.exec(['ls', '@npmcli'], err => { if (err) throw err @@ -248,7 +249,7 @@ t.test('team ls <scope>', t => { t.test('--parseable', t => { npm.flatOptions.parseable = true - team(['ls', '@npmcli'], err => { + team.exec(['ls', '@npmcli'], err => { if (err) throw err @@ -260,7 +261,7 @@ t.test('team ls <scope>', t => { t.test('--json', t => { npm.flatOptions.json = true - team(['ls', '@npmcli'], err => { + team.exec(['ls', '@npmcli'], err => { if (err) throw err @@ -280,7 +281,7 @@ t.test('team ls <scope>', t => { t.test('--silent', t => { npm.flatOptions.silent = true - team(['ls', '@npmcli'], err => { + team.exec(['ls', '@npmcli'], err => { if (err) throw err @@ -296,12 +297,13 @@ t.test('team ls <scope>', t => { }, } - const team = requireInject('../../lib/team.js', { + const Team = requireInject('../../lib/team.js', { ...mocks, libnpmteam, }) + const team = new Team(npm) - team(['ls', '@npmcli'], err => { + team.exec(['ls', '@npmcli'], err => { if (err) throw err @@ -317,12 +319,13 @@ t.test('team ls <scope>', t => { }, } - const team = requireInject('../../lib/team.js', { + const Team = requireInject('../../lib/team.js', { ...mocks, libnpmteam, }) + const team = new Team(npm) - team(['ls', '@npmcli'], err => { + team.exec(['ls', '@npmcli'], err => { if (err) throw err @@ -340,13 +343,14 @@ t.test('team ls <scope:team>', t => { return ['nlf', 'ruyadorno', 'darcyclarke', 'isaacs'] }, } - const team = requireInject('../../lib/team.js', { + const Team = requireInject('../../lib/team.js', { ...mocks, libnpmteam, }) + const team = new Team(npm) t.test('default output', t => { - team(['ls', '@npmcli:developers'], err => { + team.exec(['ls', '@npmcli:developers'], err => { if (err) throw err @@ -358,7 +362,7 @@ t.test('team ls <scope:team>', t => { t.test('--parseable', t => { npm.flatOptions.parseable = true - team(['ls', '@npmcli:developers'], err => { + team.exec(['ls', '@npmcli:developers'], err => { if (err) throw err @@ -370,7 +374,7 @@ t.test('team ls <scope:team>', t => { t.test('--json', t => { npm.flatOptions.json = true - team(['ls', '@npmcli:developers'], err => { + team.exec(['ls', '@npmcli:developers'], err => { if (err) throw err @@ -391,7 +395,7 @@ t.test('team ls <scope:team>', t => { t.test('--silent', t => { npm.flatOptions.silent = true - team(['ls', '@npmcli:developers'], err => { + team.exec(['ls', '@npmcli:developers'], err => { if (err) throw err @@ -407,12 +411,13 @@ t.test('team ls <scope:team>', t => { }, } - const team = requireInject('../../lib/team.js', { + const Team = requireInject('../../lib/team.js', { ...mocks, libnpmteam, }) + const team = new Team(npm) - team(['ls', '@npmcli:developers'], err => { + team.exec(['ls', '@npmcli:developers'], err => { if (err) throw err @@ -428,12 +433,13 @@ t.test('team ls <scope:team>', t => { }, } - const team = requireInject('../../lib/team.js', { + const Team = requireInject('../../lib/team.js', { ...mocks, libnpmteam, }) + const team = new Team(npm) - team(['ls', '@npmcli:developers'], err => { + team.exec(['ls', '@npmcli:developers'], err => { if (err) throw err @@ -447,7 +453,7 @@ t.test('team ls <scope:team>', t => { t.test('team rm <scope:team> <user>', t => { t.test('default output', t => { - team(['rm', '@npmcli:newteam', 'foo'], err => { + team.exec(['rm', '@npmcli:newteam', 'foo'], err => { if (err) throw err @@ -459,7 +465,7 @@ t.test('team rm <scope:team> <user>', t => { t.test('--parseable', t => { npm.flatOptions.parseable = true - team(['rm', '@npmcli:newteam', 'foo'], err => { + team.exec(['rm', '@npmcli:newteam', 'foo'], err => { if (err) throw err @@ -471,7 +477,7 @@ t.test('team rm <scope:team> <user>', t => { t.test('--json', t => { npm.flatOptions.json = true - team(['rm', '@npmcli:newteam', 'foo'], err => { + team.exec(['rm', '@npmcli:newteam', 'foo'], err => { if (err) throw err @@ -491,7 +497,7 @@ t.test('team rm <scope:team> <user>', t => { t.test('--silent', t => { npm.flatOptions.silent = true - team(['rm', '@npmcli:newteam', 'foo'], err => { + team.exec(['rm', '@npmcli:newteam', 'foo'], err => { if (err) throw err diff --git a/deps/npm/test/lib/test.js b/deps/npm/test/lib/test.js index 6f4a7395d7..f6f3d7afb8 100644 --- a/deps/npm/test/lib/test.js +++ b/deps/npm/test/lib/test.js @@ -1,7 +1,7 @@ const t = require('tap') const requireInject = require('require-inject') let RUN_ARGS = null -const npmock = { +const npm = { commands: { 'run-script': (args, cb) => { RUN_ARGS = args @@ -9,15 +9,14 @@ const npmock = { }, }, } -const test = requireInject('../../lib/test.js', { - '../../lib/npm.js': npmock, -}) +const Test = requireInject('../../lib/test.js') +const test = new Test(npm) t.test('run a test', t => { - test([], (er) => { + test.exec([], (er) => { t.strictSame(RUN_ARGS, ['test'], 'added "test" to the args') }) - test(['hello', 'world'], (er) => { + test.exec(['hello', 'world'], (er) => { t.strictSame(RUN_ARGS, ['test', 'hello', 'world'], 'added positional args') }) @@ -26,13 +25,13 @@ t.test('run a test', t => { }) const otherErr = new Error('should see this') - npmock.commands['run-script'] = (args, cb) => cb(lcErr) - test([], (er) => { + npm.commands['run-script'] = (args, cb) => cb(lcErr) + test.exec([], (er) => { t.equal(er, 'Test failed. See above for more details.') }) - npmock.commands['run-script'] = (args, cb) => cb(otherErr) - test([], (er) => { + npm.commands['run-script'] = (args, cb) => cb(otherErr) + test.exec([], (er) => { t.match(er, { message: 'should see this' }) }) diff --git a/deps/npm/test/lib/token.js b/deps/npm/test/lib/token.js index 6ab841f499..412d2746be 100644 --- a/deps/npm/test/lib/token.js +++ b/deps/npm/test/lib/token.js @@ -2,15 +2,13 @@ const { test } = require('tap') const requireInject = require('require-inject') const mocks = { - npm: {}, profile: {}, output: () => {}, log: {}, readUserInfo: {}, } -const tokenMock = requireInject('../../lib/token.js', { - '../../lib/npm.js': mocks.npm, +const Token = requireInject('../../lib/token.js', { '../../lib/utils/output.js': (...args) => mocks.output(...args), '../../lib/utils/otplease.js': (opts, fn) => { return Promise.resolve().then(() => fn(opts)) @@ -19,36 +17,42 @@ const tokenMock = requireInject('../../lib/token.js', { 'npm-profile': mocks.profile, npmlog: mocks.log, }) +const token = new Token({}) const tokenWithMocks = (mockRequests) => { for (const mod in mockRequests) { - if (typeof mockRequests[mod] === 'function') - mocks[mod] = mockRequests[mod] - else { - for (const key in mockRequests[mod]) - mocks[mod][key] = mockRequests[mod][key] + if (mod !== 'npm') { + if (typeof mockRequests[mod] === 'function') + mocks[mod] = mockRequests[mod] + else { + for (const key in mockRequests[mod]) + mocks[mod][key] = mockRequests[mod][key] + } } } const reset = () => { for (const mod in mockRequests) { - if (typeof mockRequests[mod] === 'function') - mocks[mod] = () => {} - else { - for (const key in mockRequests[mod]) - delete mocks[mod][key] + if (mod !== 'npm') { + if (typeof mockRequests[mod] === 'function') + mocks[mod] = () => {} + else { + for (const key in mockRequests[mod]) + delete mocks[mod][key] + } } } } - return [tokenMock, reset] + const token = new Token(mockRequests.npm || {}) + return [token, reset] } test('completion', (t) => { t.plan(5) const testComp = (argv, expect) => { - t.resolveMatch(tokenMock.completion({ conf: { argv: { remain: argv } } }), expect, argv.join(' ')) + t.resolveMatch(token.completion({ conf: { argv: { remain: argv } } }), expect, argv.join(' ')) } testComp(['npm', 'token'], ['list', 'revoke', 'create']) @@ -57,7 +61,7 @@ test('completion', (t) => { testComp(['npm', 'token', 'create'], []) t.rejects( - tokenMock.completion({ conf: { argv: { remain: ['npm', 'token', 'foobar'] } } }), + token.completion({ conf: { argv: { remain: ['npm', 'token', 'foobar'] } } }), { message: 'foobar not recognize' } ) }) @@ -77,7 +81,7 @@ test('token foobar', (t) => { t.tearDown(reset) - tokenMock(['foobar'], (err) => { + token.exec(['foobar'], (err) => { t.match(err.message, 'foobar is not a recognized subcommand') }) }) @@ -145,7 +149,7 @@ test('token list', (t) => { t.tearDown(reset) - token([], (err) => { + token.exec([], (err) => { t.ifError(err, 'npm token list') }) }) @@ -199,7 +203,7 @@ test('token list json output', (t) => { t.tearDown(reset) - token(['list'], (err) => { + token.exec(['list'], (err) => { t.ifError(err, 'npm token list') }) }) @@ -267,7 +271,7 @@ test('token list parseable output', (t) => { t.tearDown(reset) - token(['list'], (err) => { + token.exec(['list'], (err) => { t.ifError(err, 'npm token list') }) }) @@ -320,7 +324,7 @@ test('token revoke', (t) => { t.tearDown(reset) - token(['rm', 'abcd'], (err) => { + token.exec(['rm', 'abcd'], (err) => { t.ifError(err, 'npm token rm') }) }) @@ -372,7 +376,7 @@ test('token revoke multiple tokens', (t) => { t.tearDown(reset) - token(['revoke', 'abcd', 'efgh'], (err) => { + token.exec(['revoke', 'abcd', 'efgh'], (err) => { t.ifError(err, 'npm token rm') }) }) @@ -424,7 +428,7 @@ test('token revoke json output', (t) => { t.tearDown(reset) - token(['delete', 'abcd'], (err) => { + token.exec(['delete', 'abcd'], (err) => { t.ifError(err, 'npm token rm') }) }) @@ -474,7 +478,7 @@ test('token revoke parseable output', (t) => { t.tearDown(reset) - token(['remove', 'abcd'], (err) => { + token.exec(['remove', 'abcd'], (err) => { t.ifError(err, 'npm token rm') }) }) @@ -524,7 +528,7 @@ test('token revoke by token', (t) => { t.tearDown(reset) - token(['rm', 'efgh5678'], (err) => { + token.exec(['rm', 'efgh5678'], (err) => { t.ifError(err, 'npm token rm') }) }) @@ -544,7 +548,7 @@ test('token revoke requires an id', (t) => { t.tearDown(reset) - token(['rm'], (err) => { + token.exec(['rm'], (err) => { t.match(err.message, '`<tokenKey>` argument is required') }) }) @@ -589,7 +593,7 @@ test('token revoke ambiguous id errors', (t) => { t.tearDown(reset) - token(['rm', 'abcd'], (err) => { + token.exec(['rm', 'abcd'], (err) => { t.match(err.message, 'Token ID "abcd" was ambiguous') }) }) @@ -633,7 +637,7 @@ test('token revoke unknown id errors', (t) => { t.tearDown(reset) - token(['rm', 'efgh'], (err) => { + token.exec(['rm', 'efgh'], (err) => { t.match(err.message, 'Unknown token id or value "efgh".') }) }) @@ -697,7 +701,7 @@ test('token create', (t) => { t.tearDown(reset) - token(['create'], (err) => { + token.exec(['create'], (err) => { t.ifError(err, 'npm token create') }) }) @@ -756,7 +760,7 @@ test('token create json output', (t) => { t.tearDown(reset) - token(['create'], (err) => { + token.exec(['create'], (err) => { t.ifError(err, 'npm token create') }) }) @@ -822,7 +826,7 @@ test('token create parseable output', (t) => { t.tearDown(reset) - token(['create'], (err) => { + token.exec(['create'], (err) => { t.ifError(err, 'npm token create') }) }) @@ -856,7 +860,7 @@ test('token create ipv6 cidr', (t) => { t.tearDown(reset) - token(['create'], (err) => { + token.exec(['create'], (err) => { t.equal(err.message, 'CIDR whitelist can only contain IPv4 addresses, ::1/128 is IPv6', 'returns correct error') t.equal(err.code, 'EINVALIDCIDR') }) @@ -891,7 +895,7 @@ test('token create invalid cidr', (t) => { t.tearDown(reset) - token(['create'], (err) => { + token.exec(['create'], (err) => { t.equal(err.message, 'CIDR whitelist contains invalid CIDR entry: apple/cider', 'returns correct error') t.equal(err.code, 'EINVALIDCIDR') }) diff --git a/deps/npm/test/lib/uninstall.js b/deps/npm/test/lib/uninstall.js index 69040c0f25..c62b59950b 100644 --- a/deps/npm/test/lib/uninstall.js +++ b/deps/npm/test/lib/uninstall.js @@ -12,12 +12,12 @@ const npm = { localPrefix: '', } const mocks = { - '../../lib/npm.js': npm, '../../lib/utils/reify-finish.js': () => Promise.resolve(), '../../lib/utils/usage.js': () => 'usage instructions', } -const uninstall = requireInject('../../lib/uninstall.js', mocks) +const Uninstall = requireInject('../../lib/uninstall.js', mocks) +const uninstall = new Uninstall(npm) t.afterEach(cb => { npm.globalDir = '' @@ -87,7 +87,7 @@ t.test('remove single installed lib', t => { npm.flatOptions.prefix = path - uninstall(['b'], err => { + uninstall.exec(['b'], err => { if (err) throw err @@ -150,7 +150,7 @@ t.test('remove multiple installed libs', t => { npm.flatOptions.prefix = path - uninstall(['b'], err => { + uninstall.exec(['b'], err => { if (err) throw err @@ -165,7 +165,7 @@ t.test('no args local', t => { npm.flatOptions.prefix = path - uninstall([], err => { + uninstall.exec([], err => { t.match( err, /Must provide a package name to remove/, @@ -201,7 +201,7 @@ t.test('no args global', t => { const a = resolve(path, 'lib/node_modules/a') t.ok(() => fs.statSync(a)) - uninstall([], err => { + uninstall.exec([], err => { if (err) throw err @@ -218,7 +218,7 @@ t.test('no args global but no package.json', t => { npm.localPrefix = path npm.flatOptions.global = true - uninstall([], err => { + uninstall.exec([], err => { t.match( err, 'usage instructions', @@ -232,16 +232,17 @@ t.test('no args global but no package.json', t => { t.test('unknown error reading from localPrefix package.json', t => { const path = t.testdir({}) - const uninstall = requireInject('../../lib/uninstall.js', { + const Uninstall = requireInject('../../lib/uninstall.js', { ...mocks, 'read-package-json-fast': () => Promise.reject(new Error('ERR')), }) + const uninstall = new Uninstall(npm) npm.prefix = path npm.localPrefix = path npm.flatOptions.global = true - uninstall([], err => { + uninstall.exec([], err => { t.match( err, /ERR/, diff --git a/deps/npm/test/lib/unpublish.js b/deps/npm/test/lib/unpublish.js index c1fbed57eb..80a879cb6e 100644 --- a/deps/npm/test/lib/unpublish.js +++ b/deps/npm/test/lib/unpublish.js @@ -18,7 +18,6 @@ const mocks = { 'npm-package-arg': noop, 'npm-registry-fetch': { json: noop }, 'read-package-json': cb => cb(), - '../../lib/npm.js': npm, '../../lib/utils/output.js': (...msg) => { result += msg.join('\n') }, @@ -78,7 +77,7 @@ t.test('no args --force', t => { }, } - const unpublish = requireInject('../../lib/unpublish.js', { + const Unpublish = requireInject('../../lib/unpublish.js', { ...mocks, npmlog, libnpmpublish, @@ -88,8 +87,9 @@ t.test('no args --force', t => { version: '1.0.0', }), }) + const unpublish = new Unpublish(npm) - unpublish([], err => { + unpublish.exec([], err => { if (err) throw err @@ -104,15 +104,16 @@ t.test('no args --force', t => { t.test('no args --force missing package.json', t => { npm.flatOptions.force = true - const unpublish = requireInject('../../lib/unpublish.js', { + const Unpublish = requireInject('../../lib/unpublish.js', { ...mocks, 'read-package-json': (path, cb) => cb(Object.assign( new Error('ENOENT'), { code: 'ENOENT' } )), }) + const unpublish = new Unpublish(npm) - unpublish([], err => { + unpublish.exec([], err => { t.match( err, /usage instructions/, @@ -125,12 +126,13 @@ t.test('no args --force missing package.json', t => { t.test('no args --force unknown error reading package.json', t => { npm.flatOptions.force = true - const unpublish = requireInject('../../lib/unpublish.js', { + const Unpublish = requireInject('../../lib/unpublish.js', { ...mocks, 'read-package-json': (path, cb) => cb(new Error('ERR')), }) + const unpublish = new Unpublish(npm) - unpublish([], err => { + unpublish.exec([], err => { t.match( err, /ERR/, @@ -141,11 +143,12 @@ t.test('no args --force unknown error reading package.json', t => { }) t.test('no args', t => { - const unpublish = requireInject('../../lib/unpublish.js', { + const Unpublish = requireInject('../../lib/unpublish.js', { ...mocks, }) + const unpublish = new Unpublish(npm) - unpublish([], err => { + unpublish.exec([], err => { t.match( err, /Refusing to delete entire project/, @@ -156,11 +159,12 @@ t.test('no args', t => { }) t.test('too many args', t => { - const unpublish = requireInject('../../lib/unpublish.js', { + const Unpublish = requireInject('../../lib/unpublish.js', { ...mocks, }) + const unpublish = new Unpublish(npm) - unpublish(['a', 'b'], err => { + unpublish.exec(['a', 'b'], err => { t.match( err, /usage instructions/, @@ -206,14 +210,15 @@ t.test('unpublish <pkg>@version', t => { }, } - const unpublish = requireInject('../../lib/unpublish.js', { + const Unpublish = requireInject('../../lib/unpublish.js', { ...mocks, npmlog, libnpmpublish, 'npm-package-arg': npa, }) + const unpublish = new Unpublish(npm) - unpublish(['pkg@1.0.0'], err => { + unpublish.exec(['pkg@1.0.0'], err => { if (err) throw err @@ -235,15 +240,16 @@ t.test('no version found in package.json', t => { npa.resolve = () => '' - const unpublish = requireInject('../../lib/unpublish.js', { + const Unpublish = requireInject('../../lib/unpublish.js', { ...mocks, 'npm-package-arg': npa, 'read-package-json': (path, cb) => cb(null, { name: 'pkg', }), }) + const unpublish = new Unpublish(npm) - unpublish([], err => { + unpublish.exec([], err => { if (err) throw err @@ -259,7 +265,7 @@ t.test('no version found in package.json', t => { t.test('unpublish <pkg> --force no version set', t => { npm.flatOptions.force = true - const unpublish = requireInject('../../lib/unpublish.js', { + const Unpublish = requireInject('../../lib/unpublish.js', { ...mocks, 'npm-package-arg': () => ({ name: 'pkg', @@ -267,8 +273,9 @@ t.test('unpublish <pkg> --force no version set', t => { type: 'tag', }), }) + const unpublish = new Unpublish(npm) - unpublish(['pkg'], err => { + unpublish.exec(['pkg'], err => { if (err) throw err @@ -292,12 +299,13 @@ t.test('silent', t => { npa.resolve = () => '' - const unpublish = requireInject('../../lib/unpublish.js', { + const Unpublish = requireInject('../../lib/unpublish.js', { ...mocks, 'npm-package-arg': npa, }) + const unpublish = new Unpublish(npm) - unpublish(['pkg@1.0.0'], err => { + unpublish.exec(['pkg@1.0.0'], err => { if (err) throw err @@ -312,13 +320,15 @@ t.test('silent', t => { t.test('completion', async t => { const testComp = - async (t, { completion, argv, partialWord, expect, title }) => { - const res = await completion({conf: {argv: {remain: argv}}, partialWord}) + async (t, { unpublish, argv, partialWord, expect, title }) => { + const res = await unpublish.completion( + {conf: {argv: {remain: argv}}, partialWord} + ) t.strictSame(res, expect, title || argv.join(' ')) } t.test('completing with multiple versions from the registry', async t => { - const { completion } = requireInject('../../lib/unpublish.js', { + const Unpublish = requireInject('../../lib/unpublish.js', { ...mocks, libnpmaccess: { async lsPackages () { @@ -341,9 +351,10 @@ t.test('completion', async t => { }, }, }) + const unpublish = new Unpublish(npm) await testComp(t, { - completion, + unpublish, argv: ['npm', 'unpublish'], partialWord: 'pkg', expect: [ @@ -355,7 +366,7 @@ t.test('completion', async t => { }) t.test('no versions retrieved', async t => { - const { completion } = requireInject('../../lib/unpublish.js', { + const Unpublish = requireInject('../../lib/unpublish.js', { ...mocks, libnpmaccess: { async lsPackages () { @@ -374,9 +385,10 @@ t.test('completion', async t => { }, }, }) + const unpublish = new Unpublish(npm) await testComp(t, { - completion, + unpublish, argv: ['npm', 'unpublish'], partialWord: 'pkg', expect: [ @@ -387,7 +399,7 @@ t.test('completion', async t => { }) t.test('packages starting with same letters', async t => { - const { completion } = requireInject('../../lib/unpublish.js', { + const Unpublish = requireInject('../../lib/unpublish.js', { ...mocks, libnpmaccess: { async lsPackages () { @@ -400,9 +412,10 @@ t.test('completion', async t => { }, 'npm-package-arg': require('npm-package-arg'), }) + const unpublish = new Unpublish(npm) await testComp(t, { - completion, + unpublish, argv: ['npm', 'unpublish'], partialWord: 'pkg', expect: [ @@ -414,7 +427,7 @@ t.test('completion', async t => { }) t.test('no packages retrieved', async t => { - const { completion } = requireInject('../../lib/unpublish.js', { + const Unpublish = requireInject('../../lib/unpublish.js', { ...mocks, libnpmaccess: { async lsPackages () { @@ -422,9 +435,10 @@ t.test('completion', async t => { }, }, }) + const unpublish = new Unpublish(npm) await testComp(t, { - completion, + unpublish, argv: ['npm', 'unpublish'], partialWord: 'pkg', expect: [], @@ -433,7 +447,7 @@ t.test('completion', async t => { }) t.test('no pkg name to complete', async t => { - const { completion } = requireInject('../../lib/unpublish.js', { + const Unpublish = requireInject('../../lib/unpublish.js', { ...mocks, libnpmaccess: { async lsPackages () { @@ -444,9 +458,10 @@ t.test('completion', async t => { }, }, }) + const unpublish = new Unpublish(npm) await testComp(t, { - completion, + unpublish, argv: ['npm', 'unpublish'], partialWord: undefined, expect: ['pkg', 'bar'], @@ -455,7 +470,7 @@ t.test('completion', async t => { }) t.test('no pkg names retrieved from user account', async t => { - const { completion } = requireInject('../../lib/unpublish.js', { + const Unpublish = requireInject('../../lib/unpublish.js', { ...mocks, libnpmaccess: { async lsPackages () { @@ -463,9 +478,10 @@ t.test('completion', async t => { }, }, }) + const unpublish = new Unpublish(npm) await testComp(t, { - completion, + unpublish, argv: ['npm', 'unpublish'], partialWord: 'pkg', expect: [], @@ -474,13 +490,14 @@ t.test('completion', async t => { }) t.test('logged out user', async t => { - const { completion } = requireInject('../../lib/unpublish.js', { + const Unpublish = requireInject('../../lib/unpublish.js', { ...mocks, '../../lib/utils/get-identity.js': () => Promise.reject(new Error('ERR')), }) + const unpublish = new Unpublish(npm) await testComp(t, { - completion, + unpublish, argv: ['npm', 'unpublish'], partialWord: 'pkg', expect: [], @@ -488,10 +505,11 @@ t.test('completion', async t => { }) t.test('too many args', async t => { - const { completion } = requireInject('../../lib/unpublish.js', mocks) + const Unpublish = requireInject('../../lib/unpublish.js', mocks) + const unpublish = new Unpublish(npm) await testComp(t, { - completion, + unpublish, argv: ['npm', 'unpublish', 'foo'], partialWord: undefined, expect: [], diff --git a/deps/npm/test/lib/unstar.js b/deps/npm/test/lib/unstar.js index 63b2028a18..3f3487176d 100644 --- a/deps/npm/test/lib/unstar.js +++ b/deps/npm/test/lib/unstar.js @@ -4,24 +4,30 @@ const t = require('tap') t.test('unstar', t => { t.plan(3) - const unstar = requireInject('../../lib/unstar.js', { - '../../lib/npm.js': { - config: { - set: (key, value) => { - t.equal(key, 'star.unstar', 'should set unstar config value') - t.equal(value, true, 'should set a truthy value') - }, - }, - commands: { - star: (args, cb) => { - t.deepEqual(args, ['pkg'], 'should forward packages') - cb() - }, + class Star { + constructor (npm) { + this.npm = npm + } + + exec (args, cb) { + t.deepEqual(args, ['pkg'], 'should forward packages') + cb() + } + } + const Unstar = requireInject('../../lib/unstar.js', { + '../../lib/star.js': Star, + }) + + const unstar = new Unstar({ + config: { + set: (key, value) => { + t.equal(key, 'star.unstar', 'should set unstar config value') + t.equal(value, true, 'should set a truthy value') }, }, }) - unstar(['pkg'], err => { + unstar.exec(['pkg'], err => { if (err) throw err }) diff --git a/deps/npm/test/lib/update.js b/deps/npm/test/lib/update.js index 993fbbab56..15195573f5 100644 --- a/deps/npm/test/lib/update.js +++ b/deps/npm/test/lib/update.js @@ -16,7 +16,6 @@ const mocks = { '@npmcli/arborist': class { reify () {} }, - '../../lib/npm.js': npm, '../../lib/utils/reify-finish.js': noop, '../../lib/utils/usage.js': () => 'usage instructions', } @@ -47,15 +46,16 @@ t.test('no args', t => { } } - const update = requireInject('../../lib/update.js', { + const Update = requireInject('../../lib/update.js', { ...mocks, - '../../lib/utils/reify-finish.js': (arb) => { + '../../lib/utils/reify-finish.js': (npm, arb) => { t.isLike(arb, Arborist, 'should reify-finish with arborist instance') }, '@npmcli/arborist': Arborist, }) + const update = new Update(npm) - update([], err => { + update.exec([], err => { if (err) throw err }) @@ -80,15 +80,16 @@ t.test('with args', t => { } } - const update = requireInject('../../lib/update.js', { + const Update = requireInject('../../lib/update.js', { ...mocks, - '../../lib/utils/reify-finish.js': (arb) => { + '../../lib/utils/reify-finish.js': (npm, arb) => { t.isLike(arb, Arborist, 'should reify-finish with arborist instance') }, '@npmcli/arborist': Arborist, }) + const update = new Update(npm) - update(['ipt'], err => { + update.exec(['ipt'], err => { if (err) throw err }) @@ -100,7 +101,7 @@ t.test('update --depth=<number>', t => { npm.prefix = '/project/a' npm.flatOptions.depth = 1 - const update = requireInject('../../lib/update.js', { + const Update = requireInject('../../lib/update.js', { ...mocks, npmlog: { warn: (title, msg) => { @@ -113,8 +114,9 @@ t.test('update --depth=<number>', t => { }, }, }) + const update = new Update(npm) - update([], err => { + update.exec([], err => { if (err) throw err }) @@ -150,12 +152,13 @@ t.test('update --global', t => { reify () {} } - const update = requireInject('../../lib/update.js', { + const Update = requireInject('../../lib/update.js', { ...mocks, '@npmcli/arborist': Arborist, }) + const update = new Update(npm) - update([], err => { + update.exec([], err => { if (err) throw err }) diff --git a/deps/npm/test/lib/utils/audit-error.js b/deps/npm/test/lib/utils/audit-error.js index cc5f4c006e..ea7c84373e 100644 --- a/deps/npm/test/lib/utils/audit-error.js +++ b/deps/npm/test/lib/utils/audit-error.js @@ -12,7 +12,6 @@ const npm = { const OUTPUT = [] const output = (...msg) => OUTPUT.push(msg) const auditError = requireInject('../../../lib/utils/audit-error.js', { - '../../../lib/npm.js': npm, '../../../lib/utils/output.js': output, }) @@ -25,7 +24,7 @@ t.afterEach(cb => { t.test('no error, not audit command', t => { npm.command = 'install' - t.equal(auditError({}), false, 'no error') + t.equal(auditError(npm, {}), false, 'no error') t.strictSame(OUTPUT, [], 'no output') t.strictSame(LOGS, [], 'no warnings') t.end() @@ -33,7 +32,7 @@ t.test('no error, not audit command', t => { t.test('error, not audit command', t => { npm.command = 'install' - t.equal(auditError({ + t.equal(auditError(npm, { error: { message: 'message', body: Buffer.from('body'), @@ -53,7 +52,7 @@ t.test('error, not audit command', t => { t.test('error, audit command, not json', t => { npm.command = 'audit' npm.flatOptions.json = false - t.throws(() => auditError({ + t.throws(() => auditError(npm, { error: { message: 'message', body: Buffer.from('body'), @@ -74,7 +73,7 @@ t.test('error, audit command, not json', t => { t.test('error, audit command, json', t => { npm.command = 'audit' npm.flatOptions.json = true - t.throws(() => auditError({ + t.throws(() => auditError(npm, { error: { message: 'message', body: { response: 'body' }, diff --git a/deps/npm/test/lib/utils/completion/installed-deep.js b/deps/npm/test/lib/utils/completion/installed-deep.js index bd61ab4280..0e80a5a198 100644 --- a/deps/npm/test/lib/utils/completion/installed-deep.js +++ b/deps/npm/test/lib/utils/completion/installed-deep.js @@ -12,22 +12,21 @@ const _flatOptions = { }, } const p = '../../../../lib/utils/completion/installed-deep.js' -const installedDeep = requireInject(p, { - '../../../../lib/npm.js': { - flatOptions: _flatOptions, - get prefix () { - return _flatOptions.prefix - }, - get globalDir () { - return globalDir - }, - config: { - get (key) { - return _flatOptions[key] - }, +const installedDeep = requireInject(p) +const npm = { + flatOptions: _flatOptions, + get prefix () { + return _flatOptions.prefix + }, + get globalDir () { + return globalDir + }, + config: { + get (key) { + return _flatOptions[key] }, }, -}) +} const fixture = { 'package.json': JSON.stringify({ @@ -154,7 +153,7 @@ test('get list of package names', async t => { prefix = resolve(fix, 'local') globalDir = resolve(fix, 'global/node_modules') - const res = await installedDeep(null) + const res = await installedDeep(npm, null) t.deepEqual( res, [ @@ -181,7 +180,7 @@ test('get list of package names as global', async t => { _flatOptions.global = true - const res = await installedDeep(null) + const res = await installedDeep(npm, null) t.deepEqual( res, [ @@ -206,7 +205,7 @@ test('limit depth', async t => { _flatOptions.depth = 0 - const res = await installedDeep(null) + const res = await installedDeep(npm, null) t.deepEqual( res, [ @@ -235,7 +234,7 @@ test('limit depth as global', async t => { _flatOptions.global = true _flatOptions.depth = 0 - const res = await installedDeep(null) + const res = await installedDeep(npm, null) t.deepEqual( res, [ diff --git a/deps/npm/test/lib/utils/completion/installed-shallow.js b/deps/npm/test/lib/utils/completion/installed-shallow.js index 1da68810b5..1067a50acd 100644 --- a/deps/npm/test/lib/utils/completion/installed-shallow.js +++ b/deps/npm/test/lib/utils/completion/installed-shallow.js @@ -5,9 +5,7 @@ const t = require('tap') const { resolve } = require('path') const p = '../../../../lib/utils/completion/installed-shallow.js' -const installed = requireInject(p, { - '../../../../lib/npm.js': npm, -}) +const installed = requireInject(p) t.test('global not set, include globals with -g', async t => { const dir = t.testdir({ @@ -32,7 +30,7 @@ t.test('global not set, include globals with -g', async t => { npm.localDir = resolve(dir, 'local/node_modules') flatOptions.global = false const opt = { conf: { argv: { remain: [] } } } - const res = await installed(opt) + const res = await installed(npm, opt) t.strictSame(res.sort(), [ '@scope/y -g', 'x -g', @@ -65,7 +63,7 @@ t.test('global set, include globals and not locals', async t => { npm.localDir = resolve(dir, 'local/node_modules') flatOptions.global = true const opt = { conf: { argv: { remain: [] } } } - const res = await installed(opt) + const res = await installed(npm, opt) t.strictSame(res.sort(), [ '@scope/y', 'x', @@ -96,7 +94,7 @@ t.test('more than 3 items in argv, skip it', async t => { npm.localDir = resolve(dir, 'local/node_modules') flatOptions.global = false const opt = { conf: { argv: { remain: [1, 2, 3, 4, 5, 6] } } } - const res = await installed(opt) + const res = await installed(npm, opt) t.strictSame(res, null) t.end() }) diff --git a/deps/npm/test/lib/utils/explain-dep.js b/deps/npm/test/lib/utils/explain-dep.js index 1b8c4ae380..1fee610508 100644 --- a/deps/npm/test/lib/utils/explain-dep.js +++ b/deps/npm/test/lib/utils/explain-dep.js @@ -107,6 +107,7 @@ const cases = { type: 'prod', name: 'prod-dep', spec: '1.x', + bundled: true, from: { location: '/path/to/project', }, diff --git a/deps/npm/test/lib/utils/get-identity.js b/deps/npm/test/lib/utils/get-identity.js index 8a4de88352..cc713b3378 100644 --- a/deps/npm/test/lib/utils/get-identity.js +++ b/deps/npm/test/lib/utils/get-identity.js @@ -3,12 +3,10 @@ const requireInject = require('require-inject') test('throws ENOREGISTRY when no registry option is provided', async (t) => { t.plan(2) - const getIdentity = requireInject('../../../lib/utils/get-identity.js', { - '../../../lib/npm.js': {}, - }) + const getIdentity = requireInject('../../../lib/utils/get-identity.js') try { - await getIdentity() + await getIdentity({}) } catch (err) { t.equal(err.code, 'ENOREGISTRY', 'assigns the appropriate error code') t.equal(err.message, 'No registry specified.', 'returns the correct error message') @@ -18,17 +16,16 @@ test('throws ENOREGISTRY when no registry option is provided', async (t) => { test('returns username from uri when provided', async (t) => { t.plan(1) - const getIdentity = requireInject('../../../lib/utils/get-identity.js', { - '../../../lib/npm.js': { - config: { - getCredentialsByURI: () => { - return { username: 'foo' } - }, + const getIdentity = requireInject('../../../lib/utils/get-identity.js') + const npm = { + config: { + getCredentialsByURI: () => { + return { username: 'foo' } }, }, - }) + } - const identity = await getIdentity({ registry: 'https://registry.npmjs.org' }) + const identity = await getIdentity(npm, { registry: 'https://registry.npmjs.org' }) t.equal(identity, 'foo', 'returns username from uri') }) @@ -41,11 +38,6 @@ test('calls registry whoami when token is provided', async (t) => { } const getIdentity = requireInject('../../../lib/utils/get-identity.js', { - '../../../lib/npm.js': { - config: { - getCredentialsByURI: () => options, - }, - }, 'npm-registry-fetch': { json: (path, opts) => { t.equal(path, '/-/whoami', 'calls whoami') @@ -54,8 +46,13 @@ test('calls registry whoami when token is provided', async (t) => { }, }, }) + const npm = { + config: { + getCredentialsByURI: () => options, + }, + } - const identity = await getIdentity(options) + const identity = await getIdentity(npm, options) t.equal(identity, 'foo', 'fetched username from registry') }) @@ -68,11 +65,6 @@ test('throws ENEEDAUTH when response does not include a username', async (t) => } const getIdentity = requireInject('../../../lib/utils/get-identity.js', { - '../../../lib/npm.js': { - config: { - getCredentialsByURI: () => options, - }, - }, 'npm-registry-fetch': { json: (path, opts) => { t.equal(path, '/-/whoami', 'calls whoami') @@ -81,9 +73,14 @@ test('throws ENEEDAUTH when response does not include a username', async (t) => }, }, }) + const npm = { + config: { + getCredentialsByURI: () => options, + }, + } try { - await getIdentity(options) + await getIdentity(npm, options) } catch (err) { t.equal(err.code, 'ENEEDAUTH', 'throws correct error code') } @@ -92,15 +89,15 @@ test('throws ENEEDAUTH when response does not include a username', async (t) => test('throws ENEEDAUTH when neither username nor token is configured', async (t) => { t.plan(1) const getIdentity = requireInject('../../../lib/utils/get-identity.js', { - '../../../lib/npm.js': { - config: { - getCredentialsByURI: () => ({}), - }, - }, }) + const npm = { + config: { + getCredentialsByURI: () => ({}), + }, + } try { - await getIdentity({ registry: 'https://registry.npmjs.org' }) + await getIdentity(npm, { registry: 'https://registry.npmjs.org' }) } catch (err) { t.equal(err.code, 'ENEEDAUTH', 'throws correct error code') } diff --git a/deps/npm/test/lib/utils/lifecycle-cmd.js b/deps/npm/test/lib/utils/lifecycle-cmd.js index 3928c1b26e..2f1f693f2d 100644 --- a/deps/npm/test/lib/utils/lifecycle-cmd.js +++ b/deps/npm/test/lib/utils/lifecycle-cmd.js @@ -1,13 +1,19 @@ const t = require('tap') -const lifecycleCmd = require('../../../lib/utils/lifecycle-cmd.js') +const LifecycleCmd = require('../../../lib/utils/lifecycle-cmd.js') +let runArgs = null const npm = { commands: { - 'run-script': (args, cb) => cb(null, 'called npm.commands.run'), + 'run-script': (args, cb) => { + runArgs = args + cb(null, 'called npm.commands.run') + }, }, } t.test('create a lifecycle command', t => { - const cmd = lifecycleCmd(npm, 'asdf') - cmd(['some', 'args'], (er, result) => { + const cmd = new LifecycleCmd(npm, 'test-stage') + t.match(cmd.usage, /test-stage/) + cmd.exec(['some', 'args'], (er, result) => { + t.same(runArgs, ['test-stage', 'some', 'args']) t.strictSame(result, 'called npm.commands.run') t.end() }) diff --git a/deps/npm/test/lib/utils/npm-usage.js b/deps/npm/test/lib/utils/npm-usage.js index 72504b90cb..dbbde947ce 100644 --- a/deps/npm/test/lib/utils/npm-usage.js +++ b/deps/npm/test/lib/utils/npm-usage.js @@ -1,127 +1,121 @@ const t = require('tap') -const deref = require('../../../lib/utils/deref-command.js') -const npm = { - argv: [], - deref, - config: { - _options: { - viewer: null, - long: false, - userconfig: '/some/config/file/.npmrc', - }, - get: k => { - if (npm.config._options[k] === undefined) - throw new Error('unknown config') - return npm.config._options[k] - }, - set: (k, v) => { - npm.config._options[k] = v - }, - }, - log: {}, - version: '{VERSION}', -} - const OUTPUT = [] const output = (...msg) => OUTPUT.push(msg) - -const { dirname } = require('path') -const basedir = dirname(dirname(dirname(__dirname))) -t.cleanSnapshot = str => str.split(basedir).join('{BASEDIR}') - .split(require('../../../package.json').version).join('{VERSION}') - const requireInject = require('require-inject') const usage = requireInject('../../../lib/utils/npm-usage.js', { - '../../../lib/npm.js': npm, '../../../lib/utils/output.js': output, }) +const npm = requireInject('../../../lib/npm.js') -t.test('basic usage', t => { - usage() - t.equal(OUTPUT.length, 1) - t.equal(OUTPUT[0].length, 1) - t.matchSnapshot(OUTPUT[0][0]) - OUTPUT.length = 0 - t.end() -}) - -t.test('with browser', t => { - npm.config.set('viewer', 'browser') - usage() - t.equal(OUTPUT.length, 1) - t.equal(OUTPUT[0].length, 1) - t.matchSnapshot(OUTPUT[0][0]) - OUTPUT.length = 0 - npm.config.set('viewer', null) - t.end() -}) +t.test('usage', t => { + t.afterEach((cb) => { + npm.config.set('viewer', null) + npm.config.set('long', false) + npm.config.set('userconfig', '/some/config/file/.npmrc') + cb() + }) + const { dirname } = require('path') + const basedir = dirname(dirname(dirname(__dirname))) + t.cleanSnapshot = str => str.split(basedir).join('{BASEDIR}') + .split(require('../../../package.json').version).join('{VERSION}') -t.test('with long', t => { - npm.config.set('long', true) - usage() - t.equal(OUTPUT.length, 1) - t.equal(OUTPUT[0].length, 1) - t.matchSnapshot(OUTPUT[0][0]) - OUTPUT.length = 0 - npm.config.set('long', false) - t.end() -}) + npm.load(err => { + if (err) + throw err -t.test('did you mean?', t => { - npm.argv.push('unistnall') - usage() - t.equal(OUTPUT.length, 2) - t.equal(OUTPUT[0].length, 1) - t.equal(OUTPUT[1].length, 1) - t.matchSnapshot(OUTPUT[0][0]) - t.matchSnapshot(OUTPUT[1][0]) - OUTPUT.length = 0 - npm.argv.length = 0 - t.end() -}) + npm.config.set('viewer', null) + npm.config.set('long', false) + npm.config.set('userconfig', '/some/config/file/.npmrc') -t.test('did you mean?', t => { - npm.argv.push('unistnall') - const { exitCode } = process - t.teardown(() => { - if (t.passing()) - process.exitCode = exitCode - }) - // make sure it fails when invalid - usage(false) - t.equal(process.exitCode, 1) - OUTPUT.length = 0 - npm.argv.length = 0 - t.end() -}) + t.test('basic usage', t => { + usage(npm) + t.equal(OUTPUT.length, 1) + t.equal(OUTPUT[0].length, 1) + t.matchSnapshot(OUTPUT[0][0]) + OUTPUT.length = 0 + t.end() + }) -t.test('set process.stdout.columns', t => { - const { columns } = process.stdout - t.teardown(() => { - Object.defineProperty(process.stdout, 'columns', { - value: columns, - enumerable: true, - configurable: true, - writable: true, + t.test('with browser', t => { + npm.config.set('viewer', 'browser') + usage(npm) + t.equal(OUTPUT.length, 1) + t.equal(OUTPUT[0].length, 1) + t.matchSnapshot(OUTPUT[0][0]) + OUTPUT.length = 0 + npm.config.set('viewer', null) + t.end() }) - }) - const cases = [0, 90] - for (const cols of cases) { - t.test(`columns=${cols}`, t => { - Object.defineProperty(process.stdout, 'columns', { - value: cols, - enumerable: true, - configurable: true, - writable: true, - }) - usage() + + t.test('with long', t => { + npm.config.set('long', true) + usage(npm) t.equal(OUTPUT.length, 1) t.equal(OUTPUT[0].length, 1) t.matchSnapshot(OUTPUT[0][0]) OUTPUT.length = 0 + npm.config.set('long', false) + t.end() + }) + + t.test('did you mean?', t => { + npm.argv.push('unistnall') + usage(npm) + t.equal(OUTPUT.length, 2) + t.equal(OUTPUT[0].length, 1) + t.equal(OUTPUT[1].length, 1) + t.matchSnapshot(OUTPUT[0][0]) + t.matchSnapshot(OUTPUT[1][0]) + OUTPUT.length = 0 + npm.argv.length = 0 t.end() }) - } - t.end() + + t.test('did you mean?', t => { + npm.argv.push('unistnall') + const { exitCode } = process + t.teardown(() => { + if (t.passing()) + process.exitCode = exitCode + }) + // make sure it fails when invalid + usage(npm, false) + t.equal(process.exitCode, 1) + OUTPUT.length = 0 + npm.argv.length = 0 + t.end() + }) + + t.test('set process.stdout.columns', t => { + const { columns } = process.stdout + t.teardown(() => { + Object.defineProperty(process.stdout, 'columns', { + value: columns, + enumerable: true, + configurable: true, + writable: true, + }) + }) + const cases = [0, 90] + for (const cols of cases) { + t.test(`columns=${cols}`, t => { + Object.defineProperty(process.stdout, 'columns', { + value: cols, + enumerable: true, + configurable: true, + writable: true, + }) + usage(npm) + t.equal(OUTPUT.length, 1) + t.equal(OUTPUT[0].length, 1) + t.matchSnapshot(OUTPUT[0][0]) + OUTPUT.length = 0 + t.end() + }) + } + t.end() + }) + t.end() + }) }) diff --git a/deps/npm/test/lib/utils/open-url.js b/deps/npm/test/lib/utils/open-url.js index ce1783dadc..e8ab8f15a1 100644 --- a/deps/npm/test/lib/utils/open-url.js +++ b/deps/npm/test/lib/utils/open-url.js @@ -27,59 +27,49 @@ const opener = (url, opts, cb) => { } const openUrl = requireInject('../../../lib/utils/open-url.js', { - '../../../lib/npm.js': npm, '../../../lib/utils/output.js': output, opener, }) -test('opens a url', (t) => { +test('opens a url', async (t) => { t.teardown(() => { openerUrl = null openerOpts = null OUTPUT.length = 0 }) - openUrl('https://www.npmjs.com', 'npm home', (err) => { - if (err) - throw err - - t.equal(openerUrl, 'https://www.npmjs.com', 'opened the given url') - t.same(openerOpts, { command: null }, 'passed command as null (the default)') - t.same(OUTPUT, [], 'printed no output') - t.done() - }) + await openUrl(npm, 'https://www.npmjs.com', 'npm home') + t.equal(openerUrl, 'https://www.npmjs.com', 'opened the given url') + t.same(openerOpts, { command: null }, 'passed command as null (the default)') + t.same(OUTPUT, [], 'printed no output') }) -test('returns error for non-https and non-file url', (t) => { +test('returns error for non-https and non-file url', async (t) => { t.teardown(() => { openerUrl = null openerOpts = null OUTPUT.length = 0 }) - openUrl('ftp://www.npmjs.com', 'npm home', (err) => { - t.match(err, /Invalid URL/, 'got the correct error') - t.equal(openerUrl, null, 'did not open') - t.same(openerOpts, null, 'did not open') - t.same(OUTPUT, [], 'printed no output') - t.done() - }) + t.rejects(openUrl(npm, 'ftp://www.npmjs.com', 'npm home'), /Invalid URL/, 'got the correct error') + t.equal(openerUrl, null, 'did not open') + t.same(openerOpts, null, 'did not open') + t.same(OUTPUT, [], 'printed no output') + t.done() }) -test('returns error for non-parseable url', (t) => { +test('returns error for non-parseable url', async (t) => { t.teardown(() => { openerUrl = null openerOpts = null OUTPUT.length = 0 }) - openUrl('git+ssh://user@host:repo.git', 'npm home', (err) => { - t.match(err, /Invalid URL/, 'got the correct error') - t.equal(openerUrl, null, 'did not open') - t.same(openerOpts, null, 'did not open') - t.same(OUTPUT, [], 'printed no output') - t.done() - }) + t.rejects(openUrl(npm, 'git+ssh://user@host:repo.git', 'npm home'), /Invalid URL/, 'got the correct error') + t.equal(openerUrl, null, 'did not open') + t.same(openerOpts, null, 'did not open') + t.same(OUTPUT, [], 'printed no output') + t.done() }) -test('opens a url with the given browser', (t) => { +test('opens a url with the given browser', async (t) => { npm.config.set('browser', 'chrome') t.teardown(() => { openerUrl = null @@ -87,18 +77,14 @@ test('opens a url with the given browser', (t) => { OUTPUT.length = 0 npm.config.set('browser', true) }) - openUrl('https://www.npmjs.com', 'npm home', (err) => { - if (err) - throw err - - t.equal(openerUrl, 'https://www.npmjs.com', 'opened the given url') - t.same(openerOpts, { command: 'chrome' }, 'passed the given browser as command') - t.same(OUTPUT, [], 'printed no output') - t.done() - }) + await openUrl(npm, 'https://www.npmjs.com', 'npm home') + t.equal(openerUrl, 'https://www.npmjs.com', 'opened the given url') + t.same(openerOpts, { command: 'chrome' }, 'passed the given browser as command') + t.same(OUTPUT, [], 'printed no output') + t.done() }) -test('prints where to go when browser is disabled', (t) => { +test('prints where to go when browser is disabled', async (t) => { npm.config.set('browser', false) t.teardown(() => { openerUrl = null @@ -106,20 +92,16 @@ test('prints where to go when browser is disabled', (t) => { OUTPUT.length = 0 npm.config.set('browser', true) }) - openUrl('https://www.npmjs.com', 'npm home', (err) => { - if (err) - throw err - - t.equal(openerUrl, null, 'did not open') - t.same(openerOpts, null, 'did not open') - t.equal(OUTPUT.length, 1, 'got one logged message') - t.equal(OUTPUT[0].length, 1, 'logged message had one value') - t.matchSnapshot(OUTPUT[0][0], 'printed expected message') - t.done() - }) + await openUrl(npm, 'https://www.npmjs.com', 'npm home') + t.equal(openerUrl, null, 'did not open') + t.same(openerOpts, null, 'did not open') + t.equal(OUTPUT.length, 1, 'got one logged message') + t.equal(OUTPUT[0].length, 1, 'logged message had one value') + t.matchSnapshot(OUTPUT[0][0], 'printed expected message') + t.done() }) -test('prints where to go when browser is disabled and json is enabled', (t) => { +test('prints where to go when browser is disabled and json is enabled', async (t) => { npm.config.set('browser', false) npm.config.set('json', true) t.teardown(() => { @@ -129,20 +111,16 @@ test('prints where to go when browser is disabled and json is enabled', (t) => { npm.config.set('browser', true) npm.config.set('json', false) }) - openUrl('https://www.npmjs.com', 'npm home', (err) => { - if (err) - throw err - - t.equal(openerUrl, null, 'did not open') - t.same(openerOpts, null, 'did not open') - t.equal(OUTPUT.length, 1, 'got one logged message') - t.equal(OUTPUT[0].length, 1, 'logged message had one value') - t.matchSnapshot(OUTPUT[0][0], 'printed expected message') - t.done() - }) + await openUrl(npm, 'https://www.npmjs.com', 'npm home') + t.equal(openerUrl, null, 'did not open') + t.same(openerOpts, null, 'did not open') + t.equal(OUTPUT.length, 1, 'got one logged message') + t.equal(OUTPUT[0].length, 1, 'logged message had one value') + t.matchSnapshot(OUTPUT[0][0], 'printed expected message') + t.done() }) -test('prints where to go when given browser does not exist', (t) => { +test('prints where to go when given browser does not exist', async (t) => { npm.config.set('browser', 'firefox') openerResult = Object.assign(new Error('failed'), { code: 'ENOENT' }) t.teardown(() => { @@ -151,15 +129,24 @@ test('prints where to go when given browser does not exist', (t) => { OUTPUT.length = 0 npm.config.set('browser', true) }) - openUrl('https://www.npmjs.com', 'npm home', (err) => { - if (err) - throw err + await openUrl(npm, 'https://www.npmjs.com', 'npm home') + t.equal(openerUrl, 'https://www.npmjs.com', 'tried to open the correct url') + t.same(openerOpts, { command: 'firefox' }, 'tried to use the correct browser') + t.equal(OUTPUT.length, 1, 'got one logged message') + t.equal(OUTPUT[0].length, 1, 'logged message had one value') + t.matchSnapshot(OUTPUT[0][0], 'printed expected message') + t.done() +}) - t.equal(openerUrl, 'https://www.npmjs.com', 'tried to open the correct url') - t.same(openerOpts, { command: 'firefox' }, 'tried to use the correct browser') - t.equal(OUTPUT.length, 1, 'got one logged message') - t.equal(OUTPUT[0].length, 1, 'logged message had one value') - t.matchSnapshot(OUTPUT[0][0], 'printed expected message') - t.done() +test('handles unknown opener error', async (t) => { + npm.config.set('browser', 'firefox') + openerResult = Object.assign(new Error('failed'), { code: 'ENOBRIAN' }) + t.teardown(() => { + openerUrl = null + openerOpts = null + OUTPUT.length = 0 + npm.config.set('browser', true) }) + t.rejects(openUrl(npm, 'https://www.npmjs.com', 'npm home'), 'failed', 'got the correct error') + t.done() }) diff --git a/deps/npm/test/lib/utils/read-local-package.js b/deps/npm/test/lib/utils/read-local-package.js index 33a408eb53..9ae21f7d62 100644 --- a/deps/npm/test/lib/utils/read-local-package.js +++ b/deps/npm/test/lib/utils/read-local-package.js @@ -10,11 +10,10 @@ const _flatOptions = { }, } -const readLocalPackageName = requireInject('../../../lib/utils/read-local-package.js', { - '../../../lib/npm.js': { - flatOptions: _flatOptions, - }, -}) +const readLocalPackageName = requireInject('../../../lib/utils/read-local-package.js') +const npm = { + flatOptions: _flatOptions, +} test('read local package.json', async (t) => { prefix = t.testdir({ @@ -23,7 +22,7 @@ test('read local package.json', async (t) => { version: '1.0.0', }), }) - const packageName = await readLocalPackageName() + const packageName = await readLocalPackageName(npm) t.equal( packageName, 'my-local-package', @@ -38,7 +37,7 @@ test('read local scoped-package.json', async (t) => { version: '1.0.0', }), }) - const packageName = await readLocalPackageName() + const packageName = await readLocalPackageName(npm) t.equal( packageName, '@my-scope/my-local-package', @@ -49,7 +48,7 @@ test('read local scoped-package.json', async (t) => { test('read using --global', async (t) => { prefix = t.testdir({}) _flatOptions.global = true - const packageName = await readLocalPackageName() + const packageName = await readLocalPackageName(npm) t.equal( packageName, undefined, diff --git a/deps/npm/test/lib/utils/reify-finish.js b/deps/npm/test/lib/utils/reify-finish.js index 39277f21a0..7ff5146a6b 100644 --- a/deps/npm/test/lib/utils/reify-finish.js +++ b/deps/npm/test/lib/utils/reify-finish.js @@ -32,13 +32,12 @@ const fs = { const reifyFinish = requireInject('../../../lib/utils/reify-finish.js', { fs, - '../../../lib/npm.js': npm, '../../../lib/utils/reify-output.js': reifyOutput, }) t.test('should not write if not global', async t => { expectWrite = false - await reifyFinish({ + await reifyFinish(npm, { options: { global: false }, actualTree: {}, }) @@ -46,7 +45,7 @@ t.test('should not write if not global', async t => { t.test('should not write if no global npm module', async t => { expectWrite = false - await reifyFinish({ + await reifyFinish(npm, { options: { global: true }, actualTree: { inventory: new Map(), @@ -56,7 +55,7 @@ t.test('should not write if no global npm module', async t => { t.test('should not write if builtin conf had load error', async t => { expectWrite = false - await reifyFinish({ + await reifyFinish(npm, { options: { global: true }, actualTree: { inventory: new Map([['node_modules/npm', {}]]), @@ -68,7 +67,7 @@ t.test('should write if everything above passes', async t => { expectWrite = true delete builtinConfMock.loadError const path = t.testdir() - await reifyFinish({ + await reifyFinish(npm, { options: { global: true }, actualTree: { inventory: new Map([['node_modules/npm', {path}]]), diff --git a/deps/npm/test/lib/utils/reify-output.js b/deps/npm/test/lib/utils/reify-output.js index f88f072e1e..e41eabcb89 100644 --- a/deps/npm/test/lib/utils/reify-output.js +++ b/deps/npm/test/lib/utils/reify-output.js @@ -9,7 +9,7 @@ t.cleanSnapshot = str => str.replace(/in [0-9]+m?s/g, 'in {TIME}') const settings = { fund: true, } -const npmock = { +const npm = { started: Date.now(), flatOptions: settings, } @@ -17,7 +17,6 @@ const getReifyOutput = tester => requireInject( '../../../lib/utils/reify-output.js', { - '../../../lib/npm.js': npmock, '../../../lib/utils/output.js': tester, } ) @@ -32,7 +31,7 @@ t.test('missing info', (t) => { ) ) - reifyOutput({ + reifyOutput(npm, { actualTree: { children: [], }, @@ -52,7 +51,7 @@ t.test('even more missing info', t => { ) ) - reifyOutput({ + reifyOutput(npm, { actualTree: { children: [], }, @@ -73,7 +72,7 @@ t.test('single package', (t) => { } ) - reifyOutput({ + reifyOutput(npm, { // a report with an error is the same as no report at all, if // the command is not 'audit' auditReport: { @@ -118,7 +117,7 @@ t.test('no message when funding config is false', (t) => { } ) - reifyOutput({ + reifyOutput(npm, { actualTree: { name: 'foo', package: { @@ -160,7 +159,7 @@ t.test('print appropriate message for many packages', (t) => { } ) - reifyOutput({ + reifyOutput(npm, { actualTree: { name: 'foo', package: { @@ -212,7 +211,7 @@ t.test('no output when silent', t => { }) t.teardown(() => log.level = 'warn') log.level = 'silent' - reifyOutput({ + reifyOutput(npm, { actualTree: { inventory: { size: 999 }, children: [] }, auditReport: { toJSON: () => { @@ -243,7 +242,7 @@ t.test('packages changed message', t => { // return a test function that builds up the mock and snapshots output const testCase = (t, added, removed, changed, audited, json, command) => { settings.json = json - npmock.command = command + npm.command = command const mock = { actualTree: { inventory: { size: audited, has: () => true }, @@ -276,7 +275,7 @@ t.test('packages changed message', t => { mock.diff.children.push({ action: 'CHANGE', actual, ideal }) } output.length = 0 - reifyOutput(mock) + reifyOutput(npm, mock) t.matchSnapshot(output.join('\n'), JSON.stringify({ added, removed, @@ -316,7 +315,7 @@ t.test('added packages should be looked up within returned tree', t => { out => t.matchSnapshot(out) ) - reifyOutput({ + reifyOutput(npm, { actualTree: { name: 'foo', inventory: { @@ -337,7 +336,7 @@ t.test('added packages should be looked up within returned tree', t => { out => t.matchSnapshot(out) ) - reifyOutput({ + reifyOutput(npm, { actualTree: { name: 'foo', inventory: { diff --git a/deps/npm/test/lib/version.js b/deps/npm/test/lib/version.js index a69953bb8a..e0e07f5172 100644 --- a/deps/npm/test/lib/version.js +++ b/deps/npm/test/lib/version.js @@ -14,7 +14,6 @@ const npm = { } const mocks = { libnpmversion: noop, - '../../lib/npm.js': npm, '../../lib/utils/output.js': (...msg) => { for (const m of msg) result.push(m) @@ -22,7 +21,8 @@ const mocks = { '../../lib/utils/usage.js': () => 'usage instructions', } -const version = requireInject('../../lib/version.js', mocks) +const Version = requireInject('../../lib/version.js', mocks) +const version = new Version(npm) const _processVersions = process.versions t.afterEach(cb => { @@ -43,7 +43,7 @@ t.test('no args', t => { npm.prefix = prefix Object.defineProperty(process, 'versions', { value: { node: '1.0.0' } }) - version([], err => { + version.exec([], err => { if (err) throw err @@ -62,7 +62,7 @@ t.test('no args', t => { }) t.test('too many args', t => { - version(['foo', 'bar'], err => { + version.exec(['foo', 'bar'], err => { t.match( err, 'usage instructions', @@ -74,10 +74,8 @@ t.test('too many args', t => { }) t.test('completion', async t => { - const { completion } = version - const testComp = async (argv, expect) => { - const res = await completion({ conf: { argv: { remain: argv } } }) + const res = await version.completion({ conf: { argv: { remain: argv } } }) t.strictSame(res, expect, argv.join(' ')) } @@ -100,7 +98,7 @@ t.test('failure reading package.json', t => { const prefix = t.testdir({}) npm.prefix = prefix - version([], err => { + version.exec([], err => { if (err) throw err @@ -123,7 +121,7 @@ t.test('--json option', t => { npm.prefix = prefix Object.defineProperty(process, 'versions', { value: {} }) - version([], err => { + version.exec([], err => { if (err) throw err t.deepEqual( @@ -136,7 +134,7 @@ t.test('--json option', t => { }) t.test('with one arg', t => { - const version = requireInject('../../lib/version.js', { + const Version = requireInject('../../lib/version.js', { ...mocks, libnpmversion: (arg, opts) => { t.equal(arg, 'major', 'should forward expected value') @@ -152,8 +150,9 @@ t.test('with one arg', t => { return '4.0.0' }, }) + const version = new Version(npm) - version(['major'], err => { + version.exec(['major'], err => { if (err) throw err t.same(result, ['v4.0.0'], 'outputs the new version prefixed by the tagVersionPrefix') diff --git a/deps/npm/test/lib/view.js b/deps/npm/test/lib/view.js index 9419ab7ec8..1363a5b9f9 100644 --- a/deps/npm/test/lib/view.js +++ b/deps/npm/test/lib/view.js @@ -238,98 +238,98 @@ const packument = (nv, opts) => { t.beforeEach(cleanLogs) t.test('should log package info', t => { - const view = requireInject('../../lib/view.js', { - '../../lib/npm.js': { - flatOptions: { - global: false, - }, - }, + const View = requireInject('../../lib/view.js', { pacote: { packument, }, }) - - const viewJson = requireInject('../../lib/view.js', { - '../../lib/npm.js': { - flatOptions: { - json: true, - }, + const view = new View({ + flatOptions: { + global: false, }, + }) + + const ViewJson = requireInject('../../lib/view.js', { pacote: { packument, }, }) - - const viewUnicode = requireInject('../../lib/view.js', { - '../../lib/npm.js': { - flatOptions: { - global: false, - unicode: true, - }, + const viewJson = new ViewJson({ + flatOptions: { + json: true, }, + }) + + const ViewUnicode = requireInject('../../lib/view.js', { pacote: { packument, }, }) + const viewUnicode = new ViewUnicode({ + flatOptions: { + global: false, + unicode: true, + }, + }) t.test('package with license, bugs, repository and other fields', t => { - view(['green@1.0.0'], () => { + view.exec(['green@1.0.0'], () => { t.matchSnapshot(logs) t.end() }) }) t.test('package with more than 25 deps', t => { - view(['black@1.0.0'], () => { + view.exec(['black@1.0.0'], () => { t.matchSnapshot(logs) t.end() }) }) t.test('package with maintainers info as object', t => { - view(['pink@1.0.0'], () => { + view.exec(['pink@1.0.0'], () => { t.matchSnapshot(logs) t.end() }) }) t.test('package with homepage', t => { - view(['orange@1.0.0'], () => { + view.exec(['orange@1.0.0'], () => { t.matchSnapshot(logs) t.end() }) }) t.test('package with no versions', t => { - view(['brown'], () => { + view.exec(['brown'], () => { t.equals(logs, '', 'no info to display') t.end() }) }) t.test('package with no repo or homepage', t => { - view(['blue@1.0.0'], () => { + view.exec(['blue@1.0.0'], () => { t.matchSnapshot(logs) t.end() }) }) t.test('package with no modified time', t => { - viewUnicode(['cyan@1.0.0'], () => { + viewUnicode.exec(['cyan@1.0.0'], () => { t.matchSnapshot(logs) t.end() }) }) t.test('package with --json and semver range', t => { - viewJson(['cyan@^1.0.0'], () => { + viewJson.exec(['cyan@^1.0.0'], () => { t.matchSnapshot(logs) t.end() }) }) t.test('package with --json and no versions', t => { - viewJson(['brown'], () => { + viewJson.exec(['brown'], () => { t.equals(logs, '', 'no info to display') t.end() }) @@ -346,28 +346,28 @@ t.test('should log info of package in current working dir', t => { }, null, 2), }) - const view = requireInject('../../lib/view.js', { - '../../lib/npm.js': { - prefix: testDir, - flatOptions: { - defaultTag: '1.0.0', - global: false, - }, - }, + const View = requireInject('../../lib/view.js', { pacote: { packument, }, }) + const view = new View({ + prefix: testDir, + flatOptions: { + defaultTag: '1.0.0', + global: false, + }, + }) t.test('specific version', t => { - view(['.@1.0.0'], () => { + view.exec(['.@1.0.0'], () => { t.matchSnapshot(logs) t.end() }) }) t.test('non-specific version', t => { - view(['.'], () => { + view.exec(['.'], () => { t.matchSnapshot(logs) t.end() }) @@ -377,87 +377,87 @@ t.test('should log info of package in current working dir', t => { }) t.test('should log info by field name', t => { - const viewJson = requireInject('../../lib/view.js', { - '../../lib/npm.js': { - flatOptions: { - json: true, - global: false, - }, - }, + const ViewJson = requireInject('../../lib/view.js', { pacote: { packument, }, }) - - const view = requireInject('../../lib/view.js', { - '../../lib/npm.js': { - flatOptions: { - global: false, - }, + const viewJson = new ViewJson({ + flatOptions: { + json: true, + global: false, }, + }) + + const View = requireInject('../../lib/view.js', { pacote: { packument, }, }) + const view = new View({ + flatOptions: { + global: false, + }, + }) t.test('readme', t => { - view(['yellow@1.0.0', 'readme'], () => { + view.exec(['yellow@1.0.0', 'readme'], () => { t.matchSnapshot(logs) t.end() }) }) t.test('several fields', t => { - viewJson(['yellow@1.0.0', 'name', 'version', 'foo[bar]'], () => { + viewJson.exec(['yellow@1.0.0', 'name', 'version', 'foo[bar]'], () => { t.matchSnapshot(logs) t.end() }) }) t.test('several fields with several versions', t => { - view(['yellow@1.x.x', 'author'], () => { + view.exec(['yellow@1.x.x', 'author'], () => { t.matchSnapshot(logs) t.end() }) }) t.test('nested field with brackets', t => { - viewJson(['orange@1.0.0', 'dist[shasum]'], () => { + viewJson.exec(['orange@1.0.0', 'dist[shasum]'], () => { t.matchSnapshot(logs) t.end() }) }) t.test('maintainers with email', t => { - viewJson(['yellow@1.0.0', 'maintainers', 'name'], () => { + viewJson.exec(['yellow@1.0.0', 'maintainers', 'name'], () => { t.matchSnapshot(logs) t.end() }) }) t.test('maintainers with url', t => { - viewJson(['pink@1.0.0', 'maintainers'], () => { + viewJson.exec(['pink@1.0.0', 'maintainers'], () => { t.matchSnapshot(logs) t.end() }) }) t.test('unknown nested field ', t => { - view(['yellow@1.0.0', 'dist.foobar'], () => { + view.exec(['yellow@1.0.0', 'dist.foobar'], () => { t.equals(logs, '', 'no info to display') t.end() }) }) t.test('array field - 1 element', t => { - view(['purple@1.0.0', 'maintainers.name'], () => { + view.exec(['purple@1.0.0', 'maintainers.name'], () => { t.matchSnapshot(logs) t.end() }) }) t.test('array field - 2 elements', t => { - view(['yellow@1.x.x', 'maintainers.name'], () => { + view.exec(['yellow@1.x.x', 'maintainers.name'], () => { t.matchSnapshot(logs) t.end() }) @@ -467,14 +467,13 @@ t.test('should log info by field name', t => { }) t.test('throw error if global mode', (t) => { - const view = requireInject('../../lib/view.js', { - '../../lib/npm.js': { - flatOptions: { - global: true, - }, + const View = requireInject('../../lib/view.js') + const view = new View({ + flatOptions: { + global: true, }, }) - view([], (err) => { + view.exec([], (err) => { t.equals(err.message, 'Cannot use view command in global mode.') t.end() }) @@ -483,15 +482,14 @@ t.test('throw error if global mode', (t) => { t.test('throw ENOENT error if package.json misisng', (t) => { const testDir = t.testdir({}) - const view = requireInject('../../lib/view.js', { - '../../lib/npm.js': { - prefix: testDir, - flatOptions: { - global: false, - }, + const View = requireInject('../../lib/view.js') + const view = new View({ + prefix: testDir, + flatOptions: { + global: false, }, }) - view([], (err) => { + view.exec([], (err) => { t.match(err, { code: 'ENOENT' }) t.end() }) @@ -502,15 +500,14 @@ t.test('throw EJSONPARSE error if package.json not json', (t) => { 'package.json': 'not json, nope, not even a little bit!', }) - const view = requireInject('../../lib/view.js', { - '../../lib/npm.js': { - prefix: testDir, - flatOptions: { - global: false, - }, + const View = requireInject('../../lib/view.js') + const view = new View({ + prefix: testDir, + flatOptions: { + global: false, }, }) - view([], (err) => { + view.exec([], (err) => { t.match(err, { code: 'EJSONPARSE' }) t.end() }) @@ -521,50 +518,49 @@ t.test('throw error if package.json has no name', (t) => { 'package.json': '{}', }) - const view = requireInject('../../lib/view.js', { - '../../lib/npm.js': { - prefix: testDir, - flatOptions: { - global: false, - }, + const View = requireInject('../../lib/view.js') + const view = new View({ + prefix: testDir, + flatOptions: { + global: false, }, }) - view([], (err) => { + view.exec([], (err) => { t.equals(err.message, 'Invalid package.json, no "name" field') t.end() }) }) t.test('throws when unpublished', (t) => { - const view = requireInject('../../lib/view.js', { - '../../lib/npm.js': { - flatOptions: { - defaultTag: '1.0.1', - global: false, - }, - }, + const View = requireInject('../../lib/view.js', { pacote: { packument, }, }) - view(['red'], (err) => { + const view = new View({ + flatOptions: { + defaultTag: '1.0.1', + global: false, + }, + }) + view.exec(['red'], (err) => { t.equals(err.code, 'E404') t.end() }) }) t.test('completion', async t => { - const view = requireInject('../../lib/view.js', { - '../../lib/npm.js': { - flatOptions: { - defaultTag: '1.0.1', - global: false, - }, - }, + const View = requireInject('../../lib/view.js', { pacote: { packument, }, }) + const view = new View({ + flatOptions: { + defaultTag: '1.0.1', + global: false, + }, + }) const res = await view.completion({ conf: { argv: { remain: ['npm', 'view', 'green@1.0.0'] } }, }) @@ -573,11 +569,10 @@ t.test('completion', async t => { }) t.test('no registry completion', async t => { - const view = requireInject('../../lib/view.js', { - '../../lib/npm.js': { - flatOptions: { - defaultTag: '1.0.1', - }, + const View = requireInject('../../lib/view.js') + const view = new View({ + flatOptions: { + defaultTag: '1.0.1', }, }) const res = await view.completion({conf: { argv: { remain: ['npm', 'view'] } } }) diff --git a/deps/npm/test/lib/whoami.js b/deps/npm/test/lib/whoami.js index d54814db36..3d9618ffa7 100644 --- a/deps/npm/test/lib/whoami.js +++ b/deps/npm/test/lib/whoami.js @@ -3,15 +3,15 @@ const requireInject = require('require-inject') test('whoami', (t) => { t.plan(3) - const whoami = requireInject('../../lib/whoami.js', { + const Whoami = requireInject('../../lib/whoami.js', { '../../lib/utils/get-identity.js': () => Promise.resolve('foo'), - '../../lib/npm.js': { flatOptions: {} }, '../../lib/utils/output.js': (output) => { t.equal(output, 'foo', 'should output the username') }, }) + const whoami = new Whoami({ flatOptions: {} }) - whoami([], (err) => { + whoami.exec([], (err) => { t.ifError(err, 'npm whoami') t.ok('should successfully print username') }) @@ -19,15 +19,15 @@ test('whoami', (t) => { test('whoami json', (t) => { t.plan(3) - const whoami = requireInject('../../lib/whoami.js', { + const Whoami = requireInject('../../lib/whoami.js', { '../../lib/utils/get-identity.js': () => Promise.resolve('foo'), - '../../lib/npm.js': { flatOptions: { json: true } }, '../../lib/utils/output.js': (output) => { t.equal(output, '"foo"', 'should output the username as json') }, }) + const whoami = new Whoami({ flatOptions: { json: true } }) - whoami([], (err) => { + whoami.exec([], (err) => { t.ifError(err, 'npm whoami') t.ok('should successfully print username as json') }) |