From d46dfccd77c81ca8191950a56be96a5e6b1708b9 Mon Sep 17 00:00:00 2001 From: npm CLI robot Date: Wed, 4 Nov 2026 23:50:30 +0000 Subject: [PATCH] deps: upgrade npm to 11.21.2 --- deps/npm/docs/content/commands/npm-ls.md | 2 +- deps/npm/docs/content/commands/npm-stage.md | 3 +- deps/npm/docs/content/commands/npm.md | 2 +- deps/npm/docs/output/commands/npm-access.html | 4 +- .../npm/docs/output/commands/npm-adduser.html | 4 +- .../output/commands/npm-approve-scripts.html | 4 +- deps/npm/docs/output/commands/npm-audit.html | 4 +- deps/npm/docs/output/commands/npm-bugs.html | 4 +- deps/npm/docs/output/commands/npm-cache.html | 4 +- deps/npm/docs/output/commands/npm-ci.html | 4 +- .../docs/output/commands/npm-completion.html | 4 +- deps/npm/docs/output/commands/npm-config.html | 4 +- deps/npm/docs/output/commands/npm-dedupe.html | 4 +- .../output/commands/npm-deny-scripts.html | 4 +- .../docs/output/commands/npm-deprecate.html | 4 +- deps/npm/docs/output/commands/npm-diff.html | 4 +- .../docs/output/commands/npm-dist-tag.html | 4 +- deps/npm/docs/output/commands/npm-docs.html | 4 +- deps/npm/docs/output/commands/npm-doctor.html | 4 +- deps/npm/docs/output/commands/npm-edit.html | 4 +- deps/npm/docs/output/commands/npm-exec.html | 4 +- .../npm/docs/output/commands/npm-explain.html | 4 +- .../npm/docs/output/commands/npm-explore.html | 4 +- .../docs/output/commands/npm-find-dupes.html | 4 +- deps/npm/docs/output/commands/npm-fund.html | 4 +- deps/npm/docs/output/commands/npm-get.html | 4 +- .../docs/output/commands/npm-help-search.html | 4 +- deps/npm/docs/output/commands/npm-help.html | 4 +- deps/npm/docs/output/commands/npm-init.html | 4 +- .../output/commands/npm-install-ci-test.html | 4 +- .../output/commands/npm-install-scripts.html | 4 +- .../output/commands/npm-install-test.html | 4 +- .../npm/docs/output/commands/npm-install.html | 4 +- deps/npm/docs/output/commands/npm-link.html | 4 +- deps/npm/docs/output/commands/npm-ll.html | 4 +- deps/npm/docs/output/commands/npm-login.html | 4 +- deps/npm/docs/output/commands/npm-logout.html | 4 +- deps/npm/docs/output/commands/npm-ls.html | 6 +- deps/npm/docs/output/commands/npm-org.html | 4 +- .../docs/output/commands/npm-outdated.html | 4 +- deps/npm/docs/output/commands/npm-owner.html | 4 +- deps/npm/docs/output/commands/npm-pack.html | 4 +- deps/npm/docs/output/commands/npm-ping.html | 4 +- deps/npm/docs/output/commands/npm-pkg.html | 4 +- deps/npm/docs/output/commands/npm-prefix.html | 4 +- .../npm/docs/output/commands/npm-profile.html | 4 +- deps/npm/docs/output/commands/npm-prune.html | 4 +- .../npm/docs/output/commands/npm-publish.html | 4 +- deps/npm/docs/output/commands/npm-query.html | 4 +- .../npm/docs/output/commands/npm-rebuild.html | 4 +- deps/npm/docs/output/commands/npm-repo.html | 4 +- .../npm/docs/output/commands/npm-restart.html | 4 +- deps/npm/docs/output/commands/npm-root.html | 4 +- deps/npm/docs/output/commands/npm-run.html | 4 +- deps/npm/docs/output/commands/npm-sbom.html | 4 +- deps/npm/docs/output/commands/npm-search.html | 4 +- deps/npm/docs/output/commands/npm-set.html | 4 +- .../docs/output/commands/npm-shrinkwrap.html | 4 +- deps/npm/docs/output/commands/npm-stage.html | 7 +- deps/npm/docs/output/commands/npm-star.html | 4 +- deps/npm/docs/output/commands/npm-stars.html | 4 +- deps/npm/docs/output/commands/npm-start.html | 4 +- deps/npm/docs/output/commands/npm-stop.html | 4 +- deps/npm/docs/output/commands/npm-team.html | 4 +- deps/npm/docs/output/commands/npm-test.html | 4 +- deps/npm/docs/output/commands/npm-token.html | 4 +- deps/npm/docs/output/commands/npm-trust.html | 4 +- .../docs/output/commands/npm-undeprecate.html | 4 +- .../docs/output/commands/npm-uninstall.html | 4 +- .../docs/output/commands/npm-unpublish.html | 4 +- deps/npm/docs/output/commands/npm-unstar.html | 4 +- deps/npm/docs/output/commands/npm-update.html | 4 +- .../npm/docs/output/commands/npm-version.html | 4 +- deps/npm/docs/output/commands/npm-view.html | 4 +- deps/npm/docs/output/commands/npm-whoami.html | 4 +- deps/npm/docs/output/commands/npm.html | 6 +- deps/npm/docs/output/commands/npx.html | 4 +- .../docs/output/configuring-npm/folders.html | 4 +- .../docs/output/configuring-npm/install.html | 4 +- .../output/configuring-npm/npm-global.html | 4 +- .../docs/output/configuring-npm/npm-json.html | 4 +- .../configuring-npm/npm-shrinkwrap-json.html | 4 +- .../docs/output/configuring-npm/npmrc.html | 4 +- .../output/configuring-npm/package-json.html | 4 +- .../configuring-npm/package-lock-json.html | 4 +- deps/npm/docs/output/using-npm/config.html | 4 +- .../using-npm/dependency-selectors.html | 4 +- .../npm/docs/output/using-npm/developers.html | 4 +- deps/npm/docs/output/using-npm/logging.html | 4 +- deps/npm/docs/output/using-npm/orgs.html | 4 +- .../docs/output/using-npm/package-spec.html | 4 +- deps/npm/docs/output/using-npm/registry.html | 4 +- deps/npm/docs/output/using-npm/removal.html | 4 +- deps/npm/docs/output/using-npm/scope.html | 4 +- deps/npm/docs/output/using-npm/scripts.html | 4 +- .../npm/docs/output/using-npm/workspaces.html | 4 +- deps/npm/lib/cli/entry.js | 2 +- deps/npm/lib/commands/dist-tag.js | 14 + deps/npm/lib/commands/publish.js | 8 +- deps/npm/lib/utils/allow-scripts-cmd.js | 14 +- deps/npm/lib/utils/allow-scripts-prune.js | 3 +- deps/npm/lib/utils/oidc.js | 91 +++- deps/npm/man/man1/npm-access.1 | 2 +- deps/npm/man/man1/npm-adduser.1 | 2 +- deps/npm/man/man1/npm-approve-scripts.1 | 2 +- deps/npm/man/man1/npm-audit.1 | 2 +- deps/npm/man/man1/npm-bugs.1 | 2 +- deps/npm/man/man1/npm-cache.1 | 2 +- deps/npm/man/man1/npm-ci.1 | 2 +- deps/npm/man/man1/npm-completion.1 | 2 +- deps/npm/man/man1/npm-config.1 | 2 +- deps/npm/man/man1/npm-dedupe.1 | 2 +- deps/npm/man/man1/npm-deny-scripts.1 | 2 +- deps/npm/man/man1/npm-deprecate.1 | 2 +- deps/npm/man/man1/npm-diff.1 | 2 +- deps/npm/man/man1/npm-dist-tag.1 | 2 +- deps/npm/man/man1/npm-docs.1 | 2 +- deps/npm/man/man1/npm-doctor.1 | 2 +- deps/npm/man/man1/npm-edit.1 | 2 +- deps/npm/man/man1/npm-exec.1 | 2 +- deps/npm/man/man1/npm-explain.1 | 2 +- deps/npm/man/man1/npm-explore.1 | 2 +- deps/npm/man/man1/npm-find-dupes.1 | 2 +- deps/npm/man/man1/npm-fund.1 | 2 +- deps/npm/man/man1/npm-get.1 | 2 +- deps/npm/man/man1/npm-help-search.1 | 2 +- deps/npm/man/man1/npm-help.1 | 2 +- deps/npm/man/man1/npm-init.1 | 2 +- deps/npm/man/man1/npm-install-ci-test.1 | 2 +- deps/npm/man/man1/npm-install-scripts.1 | 2 +- deps/npm/man/man1/npm-install-test.1 | 2 +- deps/npm/man/man1/npm-install.1 | 2 +- deps/npm/man/man1/npm-link.1 | 2 +- deps/npm/man/man1/npm-ll.1 | 2 +- deps/npm/man/man1/npm-login.1 | 2 +- deps/npm/man/man1/npm-logout.1 | 2 +- deps/npm/man/man1/npm-ls.1 | 4 +- deps/npm/man/man1/npm-org.1 | 2 +- deps/npm/man/man1/npm-outdated.1 | 2 +- deps/npm/man/man1/npm-owner.1 | 2 +- deps/npm/man/man1/npm-pack.1 | 2 +- deps/npm/man/man1/npm-ping.1 | 2 +- deps/npm/man/man1/npm-pkg.1 | 2 +- deps/npm/man/man1/npm-prefix.1 | 2 +- deps/npm/man/man1/npm-profile.1 | 2 +- deps/npm/man/man1/npm-prune.1 | 2 +- deps/npm/man/man1/npm-publish.1 | 2 +- deps/npm/man/man1/npm-query.1 | 2 +- deps/npm/man/man1/npm-rebuild.1 | 2 +- deps/npm/man/man1/npm-repo.1 | 2 +- deps/npm/man/man1/npm-restart.1 | 2 +- deps/npm/man/man1/npm-root.1 | 2 +- deps/npm/man/man1/npm-run.1 | 2 +- deps/npm/man/man1/npm-sbom.1 | 2 +- deps/npm/man/man1/npm-search.1 | 2 +- deps/npm/man/man1/npm-set.1 | 2 +- deps/npm/man/man1/npm-shrinkwrap.1 | 2 +- deps/npm/man/man1/npm-stage.1 | 6 +- deps/npm/man/man1/npm-star.1 | 2 +- deps/npm/man/man1/npm-stars.1 | 2 +- deps/npm/man/man1/npm-start.1 | 2 +- deps/npm/man/man1/npm-stop.1 | 2 +- deps/npm/man/man1/npm-team.1 | 2 +- deps/npm/man/man1/npm-test.1 | 2 +- deps/npm/man/man1/npm-token.1 | 2 +- deps/npm/man/man1/npm-trust.1 | 2 +- deps/npm/man/man1/npm-undeprecate.1 | 2 +- deps/npm/man/man1/npm-uninstall.1 | 2 +- deps/npm/man/man1/npm-unpublish.1 | 2 +- deps/npm/man/man1/npm-unstar.1 | 2 +- deps/npm/man/man1/npm-update.1 | 2 +- deps/npm/man/man1/npm-version.1 | 2 +- deps/npm/man/man1/npm-view.1 | 2 +- deps/npm/man/man1/npm-whoami.1 | 2 +- deps/npm/man/man1/npm.1 | 4 +- deps/npm/man/man1/npx.1 | 2 +- deps/npm/man/man5/folders.5 | 2 +- deps/npm/man/man5/install.5 | 2 +- deps/npm/man/man5/npm-global.5 | 2 +- deps/npm/man/man5/npm-json.5 | 2 +- deps/npm/man/man5/npm-shrinkwrap-json.5 | 2 +- deps/npm/man/man5/npmrc.5 | 2 +- deps/npm/man/man5/package-json.5 | 2 +- deps/npm/man/man5/package-lock-json.5 | 2 +- deps/npm/man/man7/config.7 | 2 +- deps/npm/man/man7/dependency-selectors.7 | 2 +- deps/npm/man/man7/developers.7 | 2 +- deps/npm/man/man7/logging.7 | 2 +- deps/npm/man/man7/orgs.7 | 2 +- deps/npm/man/man7/package-spec.7 | 2 +- deps/npm/man/man7/registry.7 | 2 +- deps/npm/man/man7/removal.7 | 2 +- deps/npm/man/man7/scope.7 | 2 +- deps/npm/man/man7/scripts.7 | 2 +- deps/npm/man/man7/workspaces.7 | 2 +- .../arborist/lib/arborist/build-ideal-tree.js | 4 +- .../@npmcli/arborist/lib/script-allowed.js | 56 ++- .../@npmcli/arborist/package.json | 2 +- .../npm/node_modules/@npmcli/config/README.md | 2 + .../node_modules/@npmcli/config/lib/index.js | 2 + .../node_modules/@npmcli/config/package.json | 2 +- .../@npmcli/promise-spawn/lib/index.js | 11 +- .../@npmcli/promise-spawn/package.json | 5 +- .../node_modules/bin-links/lib/get-paths.js | 2 + deps/npm/node_modules/bin-links/package.json | 3 +- .../brace-expansion/dist/commonjs/index.js | 94 +++- .../brace-expansion/dist/esm/index.js | 92 +++- .../node_modules/brace-expansion/package.json | 3 +- .../http-cache-semantics/index.js | 41 +- .../http-cache-semantics/package.json | 6 +- .../node_modules/ip-address/dist/common.js | 52 +- deps/npm/node_modules/ip-address/dist/ipv4.js | 107 +++- deps/npm/node_modules/ip-address/dist/ipv6.js | 178 +++++-- .../ip-address/dist/v4/constants.js | 41 +- .../ip-address/dist/v6/constants.js | 51 +- deps/npm/node_modules/ip-address/package.json | 5 +- deps/npm/node_modules/libnpmdiff/package.json | 4 +- deps/npm/node_modules/libnpmexec/README.md | 10 +- deps/npm/node_modules/libnpmexec/lib/index.js | 8 +- .../node_modules/libnpmexec/lib/run-script.js | 27 +- deps/npm/node_modules/libnpmexec/package.json | 4 +- deps/npm/node_modules/libnpmfund/package.json | 4 +- deps/npm/node_modules/libnpmpack/package.json | 4 +- deps/npm/node_modules/pacote/lib/registry.js | 73 ++- deps/npm/node_modules/pacote/package.json | 5 +- .../postcss-selector-parser/dist/parser.js | 45 +- .../postcss-selector-parser/package.json | 9 +- deps/npm/node_modules/semver/classes/range.js | 4 +- .../node_modules/semver/internal/constants.js | 30 ++ .../internal/trim-comparator-whitespace.js | 438 ++++++++++++++++ deps/npm/node_modules/semver/package.json | 2 +- .../undici/docs/docs/api/Client.md | 2 + .../docs/docs/api/DiagnosticsChannel.md | 4 +- .../node_modules/undici/lib/core/request.js | 70 ++- .../undici/lib/dispatcher/agent.js | 8 +- .../undici/lib/dispatcher/client-h1.js | 43 +- .../undici/lib/dispatcher/client-h2.js | 92 +++- .../undici/lib/dispatcher/client.js | 5 +- .../undici/lib/dispatcher/dispatcher-base.js | 9 + .../undici/lib/handler/retry-handler.js | 37 +- .../lib/web/eventsource/eventsource-stream.js | 476 ++++++++++++------ .../undici/lib/web/eventsource/eventsource.js | 5 +- .../undici/lib/web/fetch/index.js | 16 +- .../undici/lib/web/websocket/connection.js | 2 +- .../lib/web/websocket/permessage-deflate.js | 5 + .../undici/lib/web/websocket/receiver.js | 14 + deps/npm/node_modules/undici/package.json | 2 +- deps/npm/package.json | 20 +- deps/npm/test/fixtures/mock-oidc.js | 5 +- deps/npm/test/lib/cli/entry.js | 30 ++ deps/npm/test/lib/commands/approve-scripts.js | 247 +++++++++ deps/npm/test/lib/commands/dist-tag.js | 376 ++++++++++++++ deps/npm/test/lib/commands/install-scripts.js | 61 +++ deps/npm/test/lib/commands/publish.js | 117 +++++ deps/npm/test/lib/commands/stage/index.js | 27 + deps/npm/test/lib/utils/oidc.js | 239 +++++++++ 256 files changed, 3369 insertions(+), 681 deletions(-) create mode 100644 deps/npm/node_modules/semver/internal/trim-comparator-whitespace.js create mode 100644 deps/npm/test/lib/utils/oidc.js diff --git a/deps/npm/docs/content/commands/npm-ls.md b/deps/npm/docs/content/commands/npm-ls.md index e3be39fcf040..3e01552722cf 100644 --- a/deps/npm/docs/content/commands/npm-ls.md +++ b/deps/npm/docs/content/commands/npm-ls.md @@ -23,7 +23,7 @@ Note that nested packages will *also* show the paths to the specified packages. For example, running `npm ls promzard` in npm's source tree will show: ```bash -npm@11.20.0 /path/to/npm +npm@11.21.2 /path/to/npm └─┬ init-package-json@0.0.4 └── promzard@0.1.5 ``` diff --git a/deps/npm/docs/content/commands/npm-stage.md b/deps/npm/docs/content/commands/npm-stage.md index 4228a43cce77..e4154e82b78d 100644 --- a/deps/npm/docs/content/commands/npm-stage.md +++ b/deps/npm/docs/content/commands/npm-stage.md @@ -40,6 +40,7 @@ Key behaviors: version for that package. * You can still publish packages normally while you have staged packages pending. +* Staged publishing will allow you to create a new package if it does not exist. * You can stage multiple versions of the same package. * `npm stage publish` has parity with `npm publish` and will respect `"private": true` in `package.json`, refusing to stage the package. @@ -50,8 +51,6 @@ Before using `npm stage` commands, ensure the following requirements are met: * **Write permissions on the package:** You must have write access to the package you're configuring. -* **Package must exist:** The package you're configuring must already exist - on the npm registry. * **2FA enabled on your account:** Commands that require 2FA will prompt you to authenticate. If you don't already have 2FA enabled on your account, you must enable it before using these commands. diff --git a/deps/npm/docs/content/commands/npm.md b/deps/npm/docs/content/commands/npm.md index 7789159b987f..0206d6aa087a 100644 --- a/deps/npm/docs/content/commands/npm.md +++ b/deps/npm/docs/content/commands/npm.md @@ -14,7 +14,7 @@ Note: This command is unaware of workspaces. ### Version -11.20.0 +11.21.2 ### Description diff --git a/deps/npm/docs/output/commands/npm-access.html b/deps/npm/docs/output/commands/npm-access.html index 9e232c895e99..3feec9298ef3 100644 --- a/deps/npm/docs/output/commands/npm-access.html +++ b/deps/npm/docs/output/commands/npm-access.html @@ -186,9 +186,9 @@
-

+

npm-access - @11.20.0 + @11.21.2

Set access level on published packages
diff --git a/deps/npm/docs/output/commands/npm-adduser.html b/deps/npm/docs/output/commands/npm-adduser.html index f1fdd66f3096..af06b3609bcb 100644 --- a/deps/npm/docs/output/commands/npm-adduser.html +++ b/deps/npm/docs/output/commands/npm-adduser.html @@ -186,9 +186,9 @@
-

+

npm-adduser - @11.20.0 + @11.21.2

Add a registry user account
diff --git a/deps/npm/docs/output/commands/npm-approve-scripts.html b/deps/npm/docs/output/commands/npm-approve-scripts.html index 8b54f89168b2..9324166c7b94 100644 --- a/deps/npm/docs/output/commands/npm-approve-scripts.html +++ b/deps/npm/docs/output/commands/npm-approve-scripts.html @@ -186,9 +186,9 @@
-

+

npm-approve-scripts - @11.20.0 + @11.21.2

Approve install scripts for specific dependencies
diff --git a/deps/npm/docs/output/commands/npm-audit.html b/deps/npm/docs/output/commands/npm-audit.html index d093dc1d253d..fec2c0eeec44 100644 --- a/deps/npm/docs/output/commands/npm-audit.html +++ b/deps/npm/docs/output/commands/npm-audit.html @@ -186,9 +186,9 @@
-

+

npm-audit - @11.20.0 + @11.21.2

Run a security audit
diff --git a/deps/npm/docs/output/commands/npm-bugs.html b/deps/npm/docs/output/commands/npm-bugs.html index 98cc06a6698f..6bc1b9c4f263 100644 --- a/deps/npm/docs/output/commands/npm-bugs.html +++ b/deps/npm/docs/output/commands/npm-bugs.html @@ -186,9 +186,9 @@
-

+

npm-bugs - @11.20.0 + @11.21.2

Report bugs for a package in a web browser
diff --git a/deps/npm/docs/output/commands/npm-cache.html b/deps/npm/docs/output/commands/npm-cache.html index 9f49c5378467..831035e343f9 100644 --- a/deps/npm/docs/output/commands/npm-cache.html +++ b/deps/npm/docs/output/commands/npm-cache.html @@ -186,9 +186,9 @@
-

+

npm-cache - @11.20.0 + @11.21.2

Manipulates packages cache
diff --git a/deps/npm/docs/output/commands/npm-ci.html b/deps/npm/docs/output/commands/npm-ci.html index 5ef13e2947be..2f2900c3eaee 100644 --- a/deps/npm/docs/output/commands/npm-ci.html +++ b/deps/npm/docs/output/commands/npm-ci.html @@ -186,9 +186,9 @@
-

+

npm-ci - @11.20.0 + @11.21.2

Clean install a project
diff --git a/deps/npm/docs/output/commands/npm-completion.html b/deps/npm/docs/output/commands/npm-completion.html index 69e1b5771a2d..a89ad0fa1105 100644 --- a/deps/npm/docs/output/commands/npm-completion.html +++ b/deps/npm/docs/output/commands/npm-completion.html @@ -186,9 +186,9 @@
-

+

npm-completion - @11.20.0 + @11.21.2

Tab Completion for npm
diff --git a/deps/npm/docs/output/commands/npm-config.html b/deps/npm/docs/output/commands/npm-config.html index bb6a7770b935..c08848fb8e19 100644 --- a/deps/npm/docs/output/commands/npm-config.html +++ b/deps/npm/docs/output/commands/npm-config.html @@ -186,9 +186,9 @@
-

+

npm-config - @11.20.0 + @11.21.2

Manage the npm configuration files
diff --git a/deps/npm/docs/output/commands/npm-dedupe.html b/deps/npm/docs/output/commands/npm-dedupe.html index 8437b66e5339..6f33135ca9a6 100644 --- a/deps/npm/docs/output/commands/npm-dedupe.html +++ b/deps/npm/docs/output/commands/npm-dedupe.html @@ -186,9 +186,9 @@
-

+

npm-dedupe - @11.20.0 + @11.21.2

Reduce duplication in the package tree
diff --git a/deps/npm/docs/output/commands/npm-deny-scripts.html b/deps/npm/docs/output/commands/npm-deny-scripts.html index 537897998557..f844e3968441 100644 --- a/deps/npm/docs/output/commands/npm-deny-scripts.html +++ b/deps/npm/docs/output/commands/npm-deny-scripts.html @@ -186,9 +186,9 @@
-

+

npm-deny-scripts - @11.20.0 + @11.21.2

Deny install scripts for specific dependencies
diff --git a/deps/npm/docs/output/commands/npm-deprecate.html b/deps/npm/docs/output/commands/npm-deprecate.html index bbd8d76fac03..9bc0fc612a52 100644 --- a/deps/npm/docs/output/commands/npm-deprecate.html +++ b/deps/npm/docs/output/commands/npm-deprecate.html @@ -186,9 +186,9 @@
-

+

npm-deprecate - @11.20.0 + @11.21.2

Deprecate a version of a package
diff --git a/deps/npm/docs/output/commands/npm-diff.html b/deps/npm/docs/output/commands/npm-diff.html index 0a23ac8cf7b3..4b41ca886c29 100644 --- a/deps/npm/docs/output/commands/npm-diff.html +++ b/deps/npm/docs/output/commands/npm-diff.html @@ -186,9 +186,9 @@
-

+

npm-diff - @11.20.0 + @11.21.2

The registry diff command
diff --git a/deps/npm/docs/output/commands/npm-dist-tag.html b/deps/npm/docs/output/commands/npm-dist-tag.html index b5d56fa965f6..032b78e75f4d 100644 --- a/deps/npm/docs/output/commands/npm-dist-tag.html +++ b/deps/npm/docs/output/commands/npm-dist-tag.html @@ -186,9 +186,9 @@
-

+

npm-dist-tag - @11.20.0 + @11.21.2

Modify package distribution tags
diff --git a/deps/npm/docs/output/commands/npm-docs.html b/deps/npm/docs/output/commands/npm-docs.html index ee20bd23abd1..e39ea14464d4 100644 --- a/deps/npm/docs/output/commands/npm-docs.html +++ b/deps/npm/docs/output/commands/npm-docs.html @@ -186,9 +186,9 @@
-

+

npm-docs - @11.20.0 + @11.21.2

Open documentation for a package in a web browser
diff --git a/deps/npm/docs/output/commands/npm-doctor.html b/deps/npm/docs/output/commands/npm-doctor.html index c7a3368b35cd..30492a8f0389 100644 --- a/deps/npm/docs/output/commands/npm-doctor.html +++ b/deps/npm/docs/output/commands/npm-doctor.html @@ -186,9 +186,9 @@
-

+

npm-doctor - @11.20.0 + @11.21.2

Check the health of your npm environment
diff --git a/deps/npm/docs/output/commands/npm-edit.html b/deps/npm/docs/output/commands/npm-edit.html index ab465f52017f..aed37190cefe 100644 --- a/deps/npm/docs/output/commands/npm-edit.html +++ b/deps/npm/docs/output/commands/npm-edit.html @@ -186,9 +186,9 @@
-

+

npm-edit - @11.20.0 + @11.21.2

Edit an installed package
diff --git a/deps/npm/docs/output/commands/npm-exec.html b/deps/npm/docs/output/commands/npm-exec.html index 67b114949b73..0dd426d8d279 100644 --- a/deps/npm/docs/output/commands/npm-exec.html +++ b/deps/npm/docs/output/commands/npm-exec.html @@ -186,9 +186,9 @@
-

+

npm-exec - @11.20.0 + @11.21.2

Run a command from a local or remote npm package
diff --git a/deps/npm/docs/output/commands/npm-explain.html b/deps/npm/docs/output/commands/npm-explain.html index 3cc89e27ceb0..fe176731c99c 100644 --- a/deps/npm/docs/output/commands/npm-explain.html +++ b/deps/npm/docs/output/commands/npm-explain.html @@ -186,9 +186,9 @@
-

+

npm-explain - @11.20.0 + @11.21.2

Explain installed packages
diff --git a/deps/npm/docs/output/commands/npm-explore.html b/deps/npm/docs/output/commands/npm-explore.html index 028b6ba5ba81..1e4ff4af713e 100644 --- a/deps/npm/docs/output/commands/npm-explore.html +++ b/deps/npm/docs/output/commands/npm-explore.html @@ -186,9 +186,9 @@
-

+

npm-explore - @11.20.0 + @11.21.2

Browse an installed package
diff --git a/deps/npm/docs/output/commands/npm-find-dupes.html b/deps/npm/docs/output/commands/npm-find-dupes.html index 3476968389e8..fd42595f230e 100644 --- a/deps/npm/docs/output/commands/npm-find-dupes.html +++ b/deps/npm/docs/output/commands/npm-find-dupes.html @@ -186,9 +186,9 @@
-

+

npm-find-dupes - @11.20.0 + @11.21.2

Find duplication in the package tree
diff --git a/deps/npm/docs/output/commands/npm-fund.html b/deps/npm/docs/output/commands/npm-fund.html index 181e029e6c12..180479da8c0b 100644 --- a/deps/npm/docs/output/commands/npm-fund.html +++ b/deps/npm/docs/output/commands/npm-fund.html @@ -186,9 +186,9 @@
-

+

npm-fund - @11.20.0 + @11.21.2

Retrieve funding information
diff --git a/deps/npm/docs/output/commands/npm-get.html b/deps/npm/docs/output/commands/npm-get.html index 02feaf3e7370..03cd64c2413b 100644 --- a/deps/npm/docs/output/commands/npm-get.html +++ b/deps/npm/docs/output/commands/npm-get.html @@ -186,9 +186,9 @@
-

+

npm-get - @11.20.0 + @11.21.2

Get a value from the npm configuration
diff --git a/deps/npm/docs/output/commands/npm-help-search.html b/deps/npm/docs/output/commands/npm-help-search.html index b32d15859944..51925baf1aa2 100644 --- a/deps/npm/docs/output/commands/npm-help-search.html +++ b/deps/npm/docs/output/commands/npm-help-search.html @@ -186,9 +186,9 @@
-

+

npm-help-search - @11.20.0 + @11.21.2

Search npm help documentation
diff --git a/deps/npm/docs/output/commands/npm-help.html b/deps/npm/docs/output/commands/npm-help.html index 4e0a516a694a..69383c4ea7a4 100644 --- a/deps/npm/docs/output/commands/npm-help.html +++ b/deps/npm/docs/output/commands/npm-help.html @@ -186,9 +186,9 @@
-

+

npm-help - @11.20.0 + @11.21.2

Get help on npm
diff --git a/deps/npm/docs/output/commands/npm-init.html b/deps/npm/docs/output/commands/npm-init.html index b0fefd704084..6b9f1fb1f305 100644 --- a/deps/npm/docs/output/commands/npm-init.html +++ b/deps/npm/docs/output/commands/npm-init.html @@ -186,9 +186,9 @@
-

+

npm-init - @11.20.0 + @11.21.2

Create a package.json file
diff --git a/deps/npm/docs/output/commands/npm-install-ci-test.html b/deps/npm/docs/output/commands/npm-install-ci-test.html index 1c62ffb640b1..506ef87743df 100644 --- a/deps/npm/docs/output/commands/npm-install-ci-test.html +++ b/deps/npm/docs/output/commands/npm-install-ci-test.html @@ -186,9 +186,9 @@
-

+

npm-install-ci-test - @11.20.0 + @11.21.2

Install a project with a clean slate and run tests
diff --git a/deps/npm/docs/output/commands/npm-install-scripts.html b/deps/npm/docs/output/commands/npm-install-scripts.html index 746b9bcbb9c9..494a80c78066 100644 --- a/deps/npm/docs/output/commands/npm-install-scripts.html +++ b/deps/npm/docs/output/commands/npm-install-scripts.html @@ -186,9 +186,9 @@
-

+

npm-install-scripts - @11.20.0 + @11.21.2

Manage install-script approvals for dependencies
diff --git a/deps/npm/docs/output/commands/npm-install-test.html b/deps/npm/docs/output/commands/npm-install-test.html index 17542aae9e77..9c567c396c93 100644 --- a/deps/npm/docs/output/commands/npm-install-test.html +++ b/deps/npm/docs/output/commands/npm-install-test.html @@ -186,9 +186,9 @@
-

+

npm-install-test - @11.20.0 + @11.21.2

Install package(s) and run tests
diff --git a/deps/npm/docs/output/commands/npm-install.html b/deps/npm/docs/output/commands/npm-install.html index 47ac2f056289..5954c8221931 100644 --- a/deps/npm/docs/output/commands/npm-install.html +++ b/deps/npm/docs/output/commands/npm-install.html @@ -186,9 +186,9 @@
-

+

npm-install - @11.20.0 + @11.21.2

Install a package
diff --git a/deps/npm/docs/output/commands/npm-link.html b/deps/npm/docs/output/commands/npm-link.html index bb707c9d73be..49656649d22f 100644 --- a/deps/npm/docs/output/commands/npm-link.html +++ b/deps/npm/docs/output/commands/npm-link.html @@ -186,9 +186,9 @@
-

+

npm-link - @11.20.0 + @11.21.2

Symlink a package folder
diff --git a/deps/npm/docs/output/commands/npm-ll.html b/deps/npm/docs/output/commands/npm-ll.html index c54f5a6e41ef..e56f1fcf0f1a 100644 --- a/deps/npm/docs/output/commands/npm-ll.html +++ b/deps/npm/docs/output/commands/npm-ll.html @@ -186,9 +186,9 @@
-

+

npm-ll - @11.20.0 + @11.21.2

List installed packages
diff --git a/deps/npm/docs/output/commands/npm-login.html b/deps/npm/docs/output/commands/npm-login.html index 16703d1ce173..efa0880e3527 100644 --- a/deps/npm/docs/output/commands/npm-login.html +++ b/deps/npm/docs/output/commands/npm-login.html @@ -186,9 +186,9 @@
-

+

npm-login - @11.20.0 + @11.21.2

Login to a registry user account
diff --git a/deps/npm/docs/output/commands/npm-logout.html b/deps/npm/docs/output/commands/npm-logout.html index f5712b456340..1d365feb30a2 100644 --- a/deps/npm/docs/output/commands/npm-logout.html +++ b/deps/npm/docs/output/commands/npm-logout.html @@ -186,9 +186,9 @@
-

+

npm-logout - @11.20.0 + @11.21.2

Log out of the registry
diff --git a/deps/npm/docs/output/commands/npm-ls.html b/deps/npm/docs/output/commands/npm-ls.html index e09112e22d87..b78078a33662 100644 --- a/deps/npm/docs/output/commands/npm-ls.html +++ b/deps/npm/docs/output/commands/npm-ls.html @@ -186,9 +186,9 @@
-

+

npm-ls - @11.20.0 + @11.21.2

List installed packages
@@ -209,7 +209,7 @@

Description

Positional arguments are name@version-range identifiers, which will limit the results to only the paths to the packages named. Note that nested packages will also show the paths to the specified packages. For example, running npm ls promzard in npm's source tree will show:

-
npm@11.20.0 /path/to/npm
+
npm@11.21.2 /path/to/npm
 └─┬ init-package-json@0.0.4
   └── promzard@0.1.5
 
diff --git a/deps/npm/docs/output/commands/npm-org.html b/deps/npm/docs/output/commands/npm-org.html index 7618c07e5557..ec6986644d9b 100644 --- a/deps/npm/docs/output/commands/npm-org.html +++ b/deps/npm/docs/output/commands/npm-org.html @@ -186,9 +186,9 @@
-

+

npm-org - @11.20.0 + @11.21.2

Manage orgs
diff --git a/deps/npm/docs/output/commands/npm-outdated.html b/deps/npm/docs/output/commands/npm-outdated.html index b4a7eaf72238..a8285b18ce30 100644 --- a/deps/npm/docs/output/commands/npm-outdated.html +++ b/deps/npm/docs/output/commands/npm-outdated.html @@ -186,9 +186,9 @@
-

+

npm-outdated - @11.20.0 + @11.21.2

Check for outdated packages
diff --git a/deps/npm/docs/output/commands/npm-owner.html b/deps/npm/docs/output/commands/npm-owner.html index 83a0cabfe398..114fcd7ff583 100644 --- a/deps/npm/docs/output/commands/npm-owner.html +++ b/deps/npm/docs/output/commands/npm-owner.html @@ -186,9 +186,9 @@
-

+

npm-owner - @11.20.0 + @11.21.2

Manage package owners
diff --git a/deps/npm/docs/output/commands/npm-pack.html b/deps/npm/docs/output/commands/npm-pack.html index 9acd4c9d20e9..d5c2bc9ecc3b 100644 --- a/deps/npm/docs/output/commands/npm-pack.html +++ b/deps/npm/docs/output/commands/npm-pack.html @@ -186,9 +186,9 @@
-

+

npm-pack - @11.20.0 + @11.21.2

Create a tarball from a package
diff --git a/deps/npm/docs/output/commands/npm-ping.html b/deps/npm/docs/output/commands/npm-ping.html index 72779cc45121..0ae2afe05211 100644 --- a/deps/npm/docs/output/commands/npm-ping.html +++ b/deps/npm/docs/output/commands/npm-ping.html @@ -186,9 +186,9 @@
-

+

npm-ping - @11.20.0 + @11.21.2

Ping npm registry
diff --git a/deps/npm/docs/output/commands/npm-pkg.html b/deps/npm/docs/output/commands/npm-pkg.html index 7ae9d0b42fba..5ad85163e907 100644 --- a/deps/npm/docs/output/commands/npm-pkg.html +++ b/deps/npm/docs/output/commands/npm-pkg.html @@ -186,9 +186,9 @@
-

+

npm-pkg - @11.20.0 + @11.21.2

Manages your package.json
diff --git a/deps/npm/docs/output/commands/npm-prefix.html b/deps/npm/docs/output/commands/npm-prefix.html index b0043c87e35f..60db61a48dc7 100644 --- a/deps/npm/docs/output/commands/npm-prefix.html +++ b/deps/npm/docs/output/commands/npm-prefix.html @@ -186,9 +186,9 @@
-

+

npm-prefix - @11.20.0 + @11.21.2

Display prefix
diff --git a/deps/npm/docs/output/commands/npm-profile.html b/deps/npm/docs/output/commands/npm-profile.html index 1183e9ef1f20..663552d02e7b 100644 --- a/deps/npm/docs/output/commands/npm-profile.html +++ b/deps/npm/docs/output/commands/npm-profile.html @@ -186,9 +186,9 @@
-

+

npm-profile - @11.20.0 + @11.21.2

Change settings on your registry profile
diff --git a/deps/npm/docs/output/commands/npm-prune.html b/deps/npm/docs/output/commands/npm-prune.html index 97728f904ef0..f6a02b96841c 100644 --- a/deps/npm/docs/output/commands/npm-prune.html +++ b/deps/npm/docs/output/commands/npm-prune.html @@ -186,9 +186,9 @@
-

+

npm-prune - @11.20.0 + @11.21.2

Remove extraneous packages
diff --git a/deps/npm/docs/output/commands/npm-publish.html b/deps/npm/docs/output/commands/npm-publish.html index 60a816323cde..8b6334352a95 100644 --- a/deps/npm/docs/output/commands/npm-publish.html +++ b/deps/npm/docs/output/commands/npm-publish.html @@ -186,9 +186,9 @@
-

+

npm-publish - @11.20.0 + @11.21.2

Publish a package
diff --git a/deps/npm/docs/output/commands/npm-query.html b/deps/npm/docs/output/commands/npm-query.html index 0211cd7a25e2..c58709f7c5bf 100644 --- a/deps/npm/docs/output/commands/npm-query.html +++ b/deps/npm/docs/output/commands/npm-query.html @@ -186,9 +186,9 @@
-

+

npm-query - @11.20.0 + @11.21.2

Dependency selector query
diff --git a/deps/npm/docs/output/commands/npm-rebuild.html b/deps/npm/docs/output/commands/npm-rebuild.html index f25623251ff1..012f165685d5 100644 --- a/deps/npm/docs/output/commands/npm-rebuild.html +++ b/deps/npm/docs/output/commands/npm-rebuild.html @@ -186,9 +186,9 @@
-

+

npm-rebuild - @11.20.0 + @11.21.2

Rebuild a package
diff --git a/deps/npm/docs/output/commands/npm-repo.html b/deps/npm/docs/output/commands/npm-repo.html index 943c5177cbf3..eb9f27e5bb91 100644 --- a/deps/npm/docs/output/commands/npm-repo.html +++ b/deps/npm/docs/output/commands/npm-repo.html @@ -186,9 +186,9 @@
-

+

npm-repo - @11.20.0 + @11.21.2

Open package repository page in the browser
diff --git a/deps/npm/docs/output/commands/npm-restart.html b/deps/npm/docs/output/commands/npm-restart.html index fce2e5b294ed..f64c839eef16 100644 --- a/deps/npm/docs/output/commands/npm-restart.html +++ b/deps/npm/docs/output/commands/npm-restart.html @@ -186,9 +186,9 @@
-

+

npm-restart - @11.20.0 + @11.21.2

Restart a package
diff --git a/deps/npm/docs/output/commands/npm-root.html b/deps/npm/docs/output/commands/npm-root.html index 88acaa9e893f..1c72502851bc 100644 --- a/deps/npm/docs/output/commands/npm-root.html +++ b/deps/npm/docs/output/commands/npm-root.html @@ -186,9 +186,9 @@
-

+

npm-root - @11.20.0 + @11.21.2

Display npm root
diff --git a/deps/npm/docs/output/commands/npm-run.html b/deps/npm/docs/output/commands/npm-run.html index ae867e8897f4..f5fc445afc2d 100644 --- a/deps/npm/docs/output/commands/npm-run.html +++ b/deps/npm/docs/output/commands/npm-run.html @@ -186,9 +186,9 @@
-

+

npm-run - @11.20.0 + @11.21.2

Run arbitrary package scripts
diff --git a/deps/npm/docs/output/commands/npm-sbom.html b/deps/npm/docs/output/commands/npm-sbom.html index ec88592fa2df..161177320787 100644 --- a/deps/npm/docs/output/commands/npm-sbom.html +++ b/deps/npm/docs/output/commands/npm-sbom.html @@ -186,9 +186,9 @@
-

+

npm-sbom - @11.20.0 + @11.21.2

Generate a Software Bill of Materials (SBOM)
diff --git a/deps/npm/docs/output/commands/npm-search.html b/deps/npm/docs/output/commands/npm-search.html index 57b07bf1be84..b81f53e77068 100644 --- a/deps/npm/docs/output/commands/npm-search.html +++ b/deps/npm/docs/output/commands/npm-search.html @@ -186,9 +186,9 @@
-

+

npm-search - @11.20.0 + @11.21.2

Search for packages
diff --git a/deps/npm/docs/output/commands/npm-set.html b/deps/npm/docs/output/commands/npm-set.html index 8ae84be02493..51dbbbeed71b 100644 --- a/deps/npm/docs/output/commands/npm-set.html +++ b/deps/npm/docs/output/commands/npm-set.html @@ -186,9 +186,9 @@
-

+

npm-set - @11.20.0 + @11.21.2

Set a value in the npm configuration
diff --git a/deps/npm/docs/output/commands/npm-shrinkwrap.html b/deps/npm/docs/output/commands/npm-shrinkwrap.html index 761e5b79b266..41c7ef2bbf26 100644 --- a/deps/npm/docs/output/commands/npm-shrinkwrap.html +++ b/deps/npm/docs/output/commands/npm-shrinkwrap.html @@ -186,9 +186,9 @@
-

+

npm-shrinkwrap - @11.20.0 + @11.21.2

Lock down dependency versions for publication
diff --git a/deps/npm/docs/output/commands/npm-stage.html b/deps/npm/docs/output/commands/npm-stage.html index 4ffbe2c9cc0d..6cf400f96210 100644 --- a/deps/npm/docs/output/commands/npm-stage.html +++ b/deps/npm/docs/output/commands/npm-stage.html @@ -186,9 +186,9 @@
-

+

npm-stage - @11.20.0 + @11.21.2

Stage packages for publishing
@@ -226,6 +226,7 @@

Description

version for that package.
  • You can still publish packages normally while you have staged packages pending.
  • +
  • Staged publishing will allow you to create a new package if it does not exist.
  • You can stage multiple versions of the same package.
  • npm stage publish has parity with npm publish and will respect "private": true in package.json, refusing to stage the package.
  • @@ -235,8 +236,6 @@

    Prerequisites

    • Write permissions on the package: You must have write access to the package you're configuring.
    • -
    • Package must exist: The package you're configuring must already exist -on the npm registry.
    • 2FA enabled on your account: Commands that require 2FA will prompt you to authenticate. If you don't already have 2FA enabled on your account, you must enable it before using these commands.
    • diff --git a/deps/npm/docs/output/commands/npm-star.html b/deps/npm/docs/output/commands/npm-star.html index e627cb613ee6..de2c5e677c2e 100644 --- a/deps/npm/docs/output/commands/npm-star.html +++ b/deps/npm/docs/output/commands/npm-star.html @@ -186,9 +186,9 @@
      -

      +

      npm-star - @11.20.0 + @11.21.2

      Mark your favorite packages
      diff --git a/deps/npm/docs/output/commands/npm-stars.html b/deps/npm/docs/output/commands/npm-stars.html index d87d857cbebd..39cad8e82e7b 100644 --- a/deps/npm/docs/output/commands/npm-stars.html +++ b/deps/npm/docs/output/commands/npm-stars.html @@ -186,9 +186,9 @@
      -

      +

      npm-stars - @11.20.0 + @11.21.2

      View packages marked as favorites
      diff --git a/deps/npm/docs/output/commands/npm-start.html b/deps/npm/docs/output/commands/npm-start.html index d276c536d4e0..08a76f247d8e 100644 --- a/deps/npm/docs/output/commands/npm-start.html +++ b/deps/npm/docs/output/commands/npm-start.html @@ -186,9 +186,9 @@
      -

      +

      npm-start - @11.20.0 + @11.21.2

      Start a package
      diff --git a/deps/npm/docs/output/commands/npm-stop.html b/deps/npm/docs/output/commands/npm-stop.html index bb7a86fe6e0c..ec42d3fe2d12 100644 --- a/deps/npm/docs/output/commands/npm-stop.html +++ b/deps/npm/docs/output/commands/npm-stop.html @@ -186,9 +186,9 @@
      -

      +

      npm-stop - @11.20.0 + @11.21.2

      Stop a package
      diff --git a/deps/npm/docs/output/commands/npm-team.html b/deps/npm/docs/output/commands/npm-team.html index 1d4fe77056d9..61767122694d 100644 --- a/deps/npm/docs/output/commands/npm-team.html +++ b/deps/npm/docs/output/commands/npm-team.html @@ -186,9 +186,9 @@
      -

      +

      npm-team - @11.20.0 + @11.21.2

      Manage organization teams and team memberships
      diff --git a/deps/npm/docs/output/commands/npm-test.html b/deps/npm/docs/output/commands/npm-test.html index 22662fa99ad3..137f2279d0ac 100644 --- a/deps/npm/docs/output/commands/npm-test.html +++ b/deps/npm/docs/output/commands/npm-test.html @@ -186,9 +186,9 @@
      -

      +

      npm-test - @11.20.0 + @11.21.2

      Test a package
      diff --git a/deps/npm/docs/output/commands/npm-token.html b/deps/npm/docs/output/commands/npm-token.html index 7329915528df..b499c363b12e 100644 --- a/deps/npm/docs/output/commands/npm-token.html +++ b/deps/npm/docs/output/commands/npm-token.html @@ -186,9 +186,9 @@
      -

      +

      npm-token - @11.20.0 + @11.21.2

      Manage your authentication tokens
      diff --git a/deps/npm/docs/output/commands/npm-trust.html b/deps/npm/docs/output/commands/npm-trust.html index 4f2cd57ecd5c..c86b8052fc98 100644 --- a/deps/npm/docs/output/commands/npm-trust.html +++ b/deps/npm/docs/output/commands/npm-trust.html @@ -186,9 +186,9 @@
      -

      +

      npm-trust - @11.20.0 + @11.21.2

      Manage trusted publishing relationships between packages and CI/CD providers
      diff --git a/deps/npm/docs/output/commands/npm-undeprecate.html b/deps/npm/docs/output/commands/npm-undeprecate.html index 6bf5195e4f09..7d5033e57bbf 100644 --- a/deps/npm/docs/output/commands/npm-undeprecate.html +++ b/deps/npm/docs/output/commands/npm-undeprecate.html @@ -186,9 +186,9 @@
      -

      +

      npm-undeprecate - @11.20.0 + @11.21.2

      Undeprecate a version of a package
      diff --git a/deps/npm/docs/output/commands/npm-uninstall.html b/deps/npm/docs/output/commands/npm-uninstall.html index c7bd23fe17fd..c01ed879ceb7 100644 --- a/deps/npm/docs/output/commands/npm-uninstall.html +++ b/deps/npm/docs/output/commands/npm-uninstall.html @@ -186,9 +186,9 @@
      -

      +

      npm-uninstall - @11.20.0 + @11.21.2

      Remove a package
      diff --git a/deps/npm/docs/output/commands/npm-unpublish.html b/deps/npm/docs/output/commands/npm-unpublish.html index 80fbe894b176..351433008738 100644 --- a/deps/npm/docs/output/commands/npm-unpublish.html +++ b/deps/npm/docs/output/commands/npm-unpublish.html @@ -186,9 +186,9 @@
      -

      +

      npm-unpublish - @11.20.0 + @11.21.2

      Remove a package from the registry
      diff --git a/deps/npm/docs/output/commands/npm-unstar.html b/deps/npm/docs/output/commands/npm-unstar.html index f061163d223a..7143910a99e7 100644 --- a/deps/npm/docs/output/commands/npm-unstar.html +++ b/deps/npm/docs/output/commands/npm-unstar.html @@ -186,9 +186,9 @@
      -

      +

      npm-unstar - @11.20.0 + @11.21.2

      Remove an item from your favorite packages
      diff --git a/deps/npm/docs/output/commands/npm-update.html b/deps/npm/docs/output/commands/npm-update.html index 6d8f260b7be9..c0ed8d2107c3 100644 --- a/deps/npm/docs/output/commands/npm-update.html +++ b/deps/npm/docs/output/commands/npm-update.html @@ -186,9 +186,9 @@
      -

      +

      npm-update - @11.20.0 + @11.21.2

      Update packages
      diff --git a/deps/npm/docs/output/commands/npm-version.html b/deps/npm/docs/output/commands/npm-version.html index 22667d199ee0..58cfc01644cc 100644 --- a/deps/npm/docs/output/commands/npm-version.html +++ b/deps/npm/docs/output/commands/npm-version.html @@ -186,9 +186,9 @@
      -

      +

      npm-version - @11.20.0 + @11.21.2

      Bump a package version
      diff --git a/deps/npm/docs/output/commands/npm-view.html b/deps/npm/docs/output/commands/npm-view.html index d6892fd94d54..510ffb92a0e4 100644 --- a/deps/npm/docs/output/commands/npm-view.html +++ b/deps/npm/docs/output/commands/npm-view.html @@ -186,9 +186,9 @@
      -

      +

      npm-view - @11.20.0 + @11.21.2

      View registry info
      diff --git a/deps/npm/docs/output/commands/npm-whoami.html b/deps/npm/docs/output/commands/npm-whoami.html index de1960ac1728..102c605edced 100644 --- a/deps/npm/docs/output/commands/npm-whoami.html +++ b/deps/npm/docs/output/commands/npm-whoami.html @@ -186,9 +186,9 @@
      -

      +

      npm-whoami - @11.20.0 + @11.21.2

      Display npm username
      diff --git a/deps/npm/docs/output/commands/npm.html b/deps/npm/docs/output/commands/npm.html index 0f03bc8548e8..7f63a2be16db 100644 --- a/deps/npm/docs/output/commands/npm.html +++ b/deps/npm/docs/output/commands/npm.html @@ -186,9 +186,9 @@
      -

      +

      npm - @11.20.0 + @11.21.2

      javascript package manager
      @@ -203,7 +203,7 @@

      Table of contents

    Note: This command is unaware of workspaces.

    Version

    -

    11.20.0

    +

    11.21.2

    Description

    npm is the package manager for the Node JavaScript platform. It puts modules in place so that node can find them, and manages dependency conflicts intelligently.

    diff --git a/deps/npm/docs/output/commands/npx.html b/deps/npm/docs/output/commands/npx.html index 80673580a6fb..b3b0e89ed19d 100644 --- a/deps/npm/docs/output/commands/npx.html +++ b/deps/npm/docs/output/commands/npx.html @@ -186,9 +186,9 @@
    -

    +

    npx - @11.20.0 + @11.21.2

    Run a command from a local or remote npm package
    diff --git a/deps/npm/docs/output/configuring-npm/folders.html b/deps/npm/docs/output/configuring-npm/folders.html index edf68a7fda4f..c5a561062e58 100644 --- a/deps/npm/docs/output/configuring-npm/folders.html +++ b/deps/npm/docs/output/configuring-npm/folders.html @@ -186,9 +186,9 @@
    -

    +

    Folders - @11.20.0 + @11.21.2

    Folder structures used by npm
    diff --git a/deps/npm/docs/output/configuring-npm/install.html b/deps/npm/docs/output/configuring-npm/install.html index 8356ea4bc554..e3d7cf17d6af 100644 --- a/deps/npm/docs/output/configuring-npm/install.html +++ b/deps/npm/docs/output/configuring-npm/install.html @@ -186,9 +186,9 @@
    -

    +

    Install - @11.20.0 + @11.21.2

    Download and install node and npm
    diff --git a/deps/npm/docs/output/configuring-npm/npm-global.html b/deps/npm/docs/output/configuring-npm/npm-global.html index edf68a7fda4f..c5a561062e58 100644 --- a/deps/npm/docs/output/configuring-npm/npm-global.html +++ b/deps/npm/docs/output/configuring-npm/npm-global.html @@ -186,9 +186,9 @@
    -

    +

    Folders - @11.20.0 + @11.21.2

    Folder structures used by npm
    diff --git a/deps/npm/docs/output/configuring-npm/npm-json.html b/deps/npm/docs/output/configuring-npm/npm-json.html index 70ef8ced0fcf..517bbe8a44a4 100644 --- a/deps/npm/docs/output/configuring-npm/npm-json.html +++ b/deps/npm/docs/output/configuring-npm/npm-json.html @@ -186,9 +186,9 @@
    -

    +

    package.json - @11.20.0 + @11.21.2

    Specifics of npm's package.json handling
    diff --git a/deps/npm/docs/output/configuring-npm/npm-shrinkwrap-json.html b/deps/npm/docs/output/configuring-npm/npm-shrinkwrap-json.html index eb90cfd59de3..ba197a3ed140 100644 --- a/deps/npm/docs/output/configuring-npm/npm-shrinkwrap-json.html +++ b/deps/npm/docs/output/configuring-npm/npm-shrinkwrap-json.html @@ -186,9 +186,9 @@
    -

    +

    npm-shrinkwrap.json - @11.20.0 + @11.21.2

    A publishable lockfile
    diff --git a/deps/npm/docs/output/configuring-npm/npmrc.html b/deps/npm/docs/output/configuring-npm/npmrc.html index 462664c73259..ac82e59af633 100644 --- a/deps/npm/docs/output/configuring-npm/npmrc.html +++ b/deps/npm/docs/output/configuring-npm/npmrc.html @@ -186,9 +186,9 @@
    -

    +

    .npmrc - @11.20.0 + @11.21.2

    The npm config files
    diff --git a/deps/npm/docs/output/configuring-npm/package-json.html b/deps/npm/docs/output/configuring-npm/package-json.html index 70ef8ced0fcf..517bbe8a44a4 100644 --- a/deps/npm/docs/output/configuring-npm/package-json.html +++ b/deps/npm/docs/output/configuring-npm/package-json.html @@ -186,9 +186,9 @@
    -

    +

    package.json - @11.20.0 + @11.21.2

    Specifics of npm's package.json handling
    diff --git a/deps/npm/docs/output/configuring-npm/package-lock-json.html b/deps/npm/docs/output/configuring-npm/package-lock-json.html index 8022b532fa43..0cb259d286ac 100644 --- a/deps/npm/docs/output/configuring-npm/package-lock-json.html +++ b/deps/npm/docs/output/configuring-npm/package-lock-json.html @@ -186,9 +186,9 @@
    -

    +

    package-lock.json - @11.20.0 + @11.21.2

    A manifestation of the manifest
    diff --git a/deps/npm/docs/output/using-npm/config.html b/deps/npm/docs/output/using-npm/config.html index 457b0d079bd1..0d6ec849317e 100644 --- a/deps/npm/docs/output/using-npm/config.html +++ b/deps/npm/docs/output/using-npm/config.html @@ -186,9 +186,9 @@
    -

    +

    Config - @11.20.0 + @11.21.2

    About npm configuration
    diff --git a/deps/npm/docs/output/using-npm/dependency-selectors.html b/deps/npm/docs/output/using-npm/dependency-selectors.html index baa45c36cbbf..94dbed948363 100644 --- a/deps/npm/docs/output/using-npm/dependency-selectors.html +++ b/deps/npm/docs/output/using-npm/dependency-selectors.html @@ -186,9 +186,9 @@
    -

    +

    Dependency Selectors - @11.20.0 + @11.21.2

    Dependency Selector Syntax & Querying
    diff --git a/deps/npm/docs/output/using-npm/developers.html b/deps/npm/docs/output/using-npm/developers.html index 27c9b109d8ec..d9774e68bff6 100644 --- a/deps/npm/docs/output/using-npm/developers.html +++ b/deps/npm/docs/output/using-npm/developers.html @@ -186,9 +186,9 @@
    -

    +

    Developers - @11.20.0 + @11.21.2

    Developer guide
    diff --git a/deps/npm/docs/output/using-npm/logging.html b/deps/npm/docs/output/using-npm/logging.html index 08314392eea5..0d191c03717b 100644 --- a/deps/npm/docs/output/using-npm/logging.html +++ b/deps/npm/docs/output/using-npm/logging.html @@ -186,9 +186,9 @@
    -

    +

    Logging - @11.20.0 + @11.21.2

    Why, What & How we Log
    diff --git a/deps/npm/docs/output/using-npm/orgs.html b/deps/npm/docs/output/using-npm/orgs.html index 242620475df7..20f2cb67cd09 100644 --- a/deps/npm/docs/output/using-npm/orgs.html +++ b/deps/npm/docs/output/using-npm/orgs.html @@ -186,9 +186,9 @@
    -

    +

    Organizations - @11.20.0 + @11.21.2

    Working with teams & organizations
    diff --git a/deps/npm/docs/output/using-npm/package-spec.html b/deps/npm/docs/output/using-npm/package-spec.html index b7adbecf2d54..0ba71462d0f6 100644 --- a/deps/npm/docs/output/using-npm/package-spec.html +++ b/deps/npm/docs/output/using-npm/package-spec.html @@ -186,9 +186,9 @@
    -

    +

    Package spec - @11.20.0 + @11.21.2

    Package name specifier
    diff --git a/deps/npm/docs/output/using-npm/registry.html b/deps/npm/docs/output/using-npm/registry.html index 4da60fd44fad..54d4f433e726 100644 --- a/deps/npm/docs/output/using-npm/registry.html +++ b/deps/npm/docs/output/using-npm/registry.html @@ -186,9 +186,9 @@
    -

    +

    Registry - @11.20.0 + @11.21.2

    The JavaScript Package Registry
    diff --git a/deps/npm/docs/output/using-npm/removal.html b/deps/npm/docs/output/using-npm/removal.html index 26f86fd39215..b058622923a1 100644 --- a/deps/npm/docs/output/using-npm/removal.html +++ b/deps/npm/docs/output/using-npm/removal.html @@ -186,9 +186,9 @@
    -

    +

    Removal - @11.20.0 + @11.21.2

    Cleaning the slate
    diff --git a/deps/npm/docs/output/using-npm/scope.html b/deps/npm/docs/output/using-npm/scope.html index fa268bacf0e5..81dcafdb2764 100644 --- a/deps/npm/docs/output/using-npm/scope.html +++ b/deps/npm/docs/output/using-npm/scope.html @@ -186,9 +186,9 @@
    -

    +

    Scope - @11.20.0 + @11.21.2

    Scoped packages
    diff --git a/deps/npm/docs/output/using-npm/scripts.html b/deps/npm/docs/output/using-npm/scripts.html index bc7875c23682..c0d35eceb43d 100644 --- a/deps/npm/docs/output/using-npm/scripts.html +++ b/deps/npm/docs/output/using-npm/scripts.html @@ -186,9 +186,9 @@
    -

    +

    Scripts - @11.20.0 + @11.21.2

    How npm handles the "scripts" field
    diff --git a/deps/npm/docs/output/using-npm/workspaces.html b/deps/npm/docs/output/using-npm/workspaces.html index 412a16b9f039..c5ce10d261a9 100644 --- a/deps/npm/docs/output/using-npm/workspaces.html +++ b/deps/npm/docs/output/using-npm/workspaces.html @@ -186,9 +186,9 @@
    -

    +

    Workspaces - @11.20.0 + @11.21.2

    Working with workspaces
    diff --git a/deps/npm/lib/cli/entry.js b/deps/npm/lib/cli/entry.js index 962c62a5766d..fb15683f48e1 100644 --- a/deps/npm/lib/cli/entry.js +++ b/deps/npm/lib/cli/entry.js @@ -26,7 +26,7 @@ module.exports = async (process, validateEngines) => { // It is now safe to log a warning if they are using a version of node that is not going to fail on syntax errors but is still unsupported and untested and might not work reliably. // This is safe to use the logger now which we want since this will show up in the error log too. - if (!satisfies(validateEngines.node, validateEngines.engines)) { + if (!satisfies(validateEngines.node, validateEngines.engines, { includePrerelease: true })) { log.warn('cli', validateEngines.unsupportedMessage) } diff --git a/deps/npm/lib/commands/dist-tag.js b/deps/npm/lib/commands/dist-tag.js index 4b51e839d78a..fe4b4c8e809c 100644 --- a/deps/npm/lib/commands/dist-tag.js +++ b/deps/npm/lib/commands/dist-tag.js @@ -3,6 +3,7 @@ const npmFetch = require('npm-registry-fetch') const semver = require('semver') const { log, output } = require('proc-log') const { otplease } = require('../utils/auth.js') +const { oidc } = require('../utils/oidc.js') const pkgJson = require('@npmcli/package-json') const BaseCommand = require('../base-cmd.js') @@ -101,6 +102,7 @@ class DistTag extends BaseCommand { throw new Error('Tag name must not be a valid SemVer range: ' + t) } + await this.#oidc(spec, opts) const tags = await this.fetchTags(spec, opts) if (tags[t] === version) { log.warn('dist-tag add', t, 'is already set to version', version) @@ -130,6 +132,7 @@ class DistTag extends BaseCommand { throw this.usageError() } + await this.#oidc(spec, opts) const tags = await this.fetchTags(spec, opts) if (!tags[tag]) { log.info('dist-tag del', tag, 'is not a dist-tag on', spec.name) @@ -163,6 +166,7 @@ class DistTag extends BaseCommand { spec = npa(spec) try { + await this.#oidc(spec, opts) const tags = await this.fetchTags(spec, opts) const msg = Object.keys(tags).map(k => `${k}: ${tags[k]}`).sort().join('\n') @@ -202,6 +206,16 @@ class DistTag extends BaseCommand { return data } + + async #oidc (spec, opts) { + const registry = npmFetch.pickRegistry(spec, opts) + await oidc({ + packageName: spec.name, + registry, + opts, + config: this.npm.config, + }) + } } module.exports = DistTag diff --git a/deps/npm/lib/commands/publish.js b/deps/npm/lib/commands/publish.js index aea01cc10e7a..dcc6a8be8c28 100644 --- a/deps/npm/lib/commands/publish.js +++ b/deps/npm/lib/commands/publish.js @@ -144,7 +144,13 @@ class Publish extends BaseCommand { const registry = npmFetch.pickRegistry(resolved, opts) - await oidc({ packageName: manifest.name, registry, opts, config: this.npm.config }) + await oidc({ + packageName: manifest.name, + registry, + opts, + config: this.npm.config, + enableProvenanceAutoConfiguration: true, + }) const creds = this.npm.config.getCredentialsByURI(registry) const noCreds = !(creds.token || creds.username || creds.certfile && creds.keyfile) diff --git a/deps/npm/lib/utils/allow-scripts-cmd.js b/deps/npm/lib/utils/allow-scripts-cmd.js index 315b7b0b4fb9..cbcbe4df29ae 100644 --- a/deps/npm/lib/utils/allow-scripts-cmd.js +++ b/deps/npm/lib/utils/allow-scripts-cmd.js @@ -221,15 +221,13 @@ class AllowScriptsCmd extends BaseCommand { // edge name. A version or range on the arg narrows the match to installed // versions that satisfy it. Bundled deps are excluded for the same reason // as --all. Args that match nothing are returned in `unmatched`. + // Links stay matchable by their edge name (e.g. `foo` -> `file:./local-foo`), but each match is dereferenced to its target and deduplicated, since scripts only run on the target and keying policy off a Link's resolved spec writes store paths under install-strategy=linked (npm/cli#9939). const matched = [] const unmatched = [] for (const arg of args) { const { name: wantName, range } = parsePositional(arg) - const found = [] + const found = new Set() for (const node of arb.actualTree.inventory.values()) { - if (node.isProjectRoot || node.isWorkspace || node.inBundle) { - continue - } const { name, version } = trustedDisplay(node) if (!name || name !== wantName) { continue @@ -237,9 +235,13 @@ class AllowScriptsCmd extends BaseCommand { if (range && (!version || !semver.satisfies(version, range, { loose: true }))) { continue } - found.push(node) + const target = node.isLink ? node.target : node + if (!target || target.isProjectRoot || target.isWorkspace || target.inBundle) { + continue + } + found.add(target) } - if (found.length === 0) { + if (found.size === 0) { unmatched.push(arg) } else { matched.push(...found) diff --git a/deps/npm/lib/utils/allow-scripts-prune.js b/deps/npm/lib/utils/allow-scripts-prune.js index 1111b45d9ca3..264ad6dcc96d 100644 --- a/deps/npm/lib/utils/allow-scripts-prune.js +++ b/deps/npm/lib/utils/allow-scripts-prune.js @@ -29,7 +29,8 @@ const classifyUnusedEntries = (allowScripts, nodes) => { continue } - const matching = nodes.filter(({ node }) => matches(node, key)) + // Pass deny intent so an unverifiable version fails closed: an active versioned deny must not be classified as unused while its broad allow survives. + const matching = nodes.filter(({ node }) => matches(node, key, value === false)) if (matching.length === 0) { removed.push({ key, value, reason: 'not-installed' }) continue diff --git a/deps/npm/lib/utils/oidc.js b/deps/npm/lib/utils/oidc.js index 203aaf3143a7..7f18170b1b0a 100644 --- a/deps/npm/lib/utils/oidc.js +++ b/deps/npm/lib/utils/oidc.js @@ -5,6 +5,62 @@ const fetch = require('make-fetch-happen') const npa = require('npm-package-arg') const libaccess = require('libnpmaccess') +/** + * Caches the runtime state of the active OIDC token exchange + * so it can be cleared on subsequent OIDC attempts in workflows + * that run OIDC multiple times within one runtime execution, + * preventing the previous exchange token from being reused. + */ +let activeExchangeRecord + +const setOidcToken = ({ config, opts, authTokenKey, token }) => { + activeExchangeRecord = { + config, + authTokenKey, + token, + previousCliToken: config.get(authTokenKey, 'cli'), + opts, + previousOptsToken: opts[authTokenKey], + } + opts[authTokenKey] = token + config.set(authTokenKey, token, 'cli') + log.verbose('oidc', 'Successfully retrieved and set token') +} + +const clearOidcToken = ({ config, opts }) => { + if (!activeExchangeRecord) { + return + } + + const { + config: previousConfig, + authTokenKey, + token, + previousCliToken, + opts: previousOpts, + previousOptsToken, + } = activeExchangeRecord + if (previousConfig.get(authTokenKey, 'cli') === token) { + if (previousCliToken === undefined) { + previousConfig.delete(authTokenKey, 'cli') + } else { + previousConfig.set(authTokenKey, previousCliToken, 'cli') + } + } + for (const requestOpts of new Set([previousOpts, opts])) { + if (requestOpts[authTokenKey] === token) { + const fallback = requestOpts === previousOpts ? previousOptsToken : config.get(authTokenKey) + if (fallback === undefined) { + delete requestOpts[authTokenKey] + } else { + requestOpts[authTokenKey] = fallback + } + } + } + activeExchangeRecord = undefined + log.verbose('oidc', 'Cleared previous exchange token') +} + /** * Handles OpenID Connect (OIDC) token retrieval and exchange for CI environments. * @@ -13,18 +69,35 @@ const libaccess = require('libnpmaccess') * * This function is intended to never throw, as it mutates the state of the `opts` and `config` objects on success. * OIDC is always an optional feature, and the function should not throw if OIDC is not configured by the registry. + * Before each attempt, it restores credentials replaced by the previous exchange so package-specific tokens are never used as fallback credentials. * * @see https://github.com/watson/ci-info for CI environment detection. * @see https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect for GitHub Actions OIDC. * @see https://circleci.com/docs/openid-connect-tokens/ for CircleCI OIDC. */ -async function oidc ({ packageName, registry, opts, config }) { +async function oidc ({ + // Selects the package-specific npm token exchange endpoint. + packageName, + // Determines the OIDC audience, exchange host, and registry-scoped auth key. + registry, + // Mutable request options for the current command, often called flatOptions. + // The exchanged token is added here so subsequent registry requests use it immediately. + opts, + // npm's live configuration state. This is also updated so credential lookups see the token. + config, + // Publish and stage publish can infer provenance from trusted-publishing claims and package + // visibility so users do not need to set --provenance. Other commands that use OIDC, such as + // npm dist-tag, only need token exchange and must not enable unrelated publish provenance. + enableProvenanceAutoConfiguration = false, +}) { /* * This code should never run when people try to publish locally on their machines. * It is designed to execute only in Continuous Integration (CI) environments. */ try { + clearOidcToken({ config, opts }) + if (!( /** @see https://github.com/watson/ci-info/blob/v4.2.0/vendors.json#L152 */ ciInfo.GITHUB_ACTIONS || @@ -133,13 +206,17 @@ async function oidc ({ packageName, registry, opts, config }) { } /* - * The "opts" object is a clone of npm.flatOptions and is passed through the `publish` command, eventually reaching `otplease`. - * To ensure the token is accessible during the publishing process, it must be directly attached to the `opts` object. - * Additionally, the token is required by the "live" configuration or getters within `config`. + * `opts` is derived from npm.flatOptions and carried through the current command. + * Publish and dist-tag pass it to later registry requests, including mutations wrapped by + * `otplease`, so the exchanged token must be attached directly to this object. + * The non-persistable cli layer also makes the token available to credential lookups without + * modifying any file-backed configuration. */ - opts[authTokenKey] = response.token - config.set(authTokenKey, response.token, 'user') - log.verbose('oidc', `Successfully retrieved and set token`) + setOidcToken({ config, opts, authTokenKey, token: response.token }) + + if (!enableProvenanceAutoConfiguration) { + return undefined + } try { const isDefaultProvenance = config.isDefault('provenance') diff --git a/deps/npm/man/man1/npm-access.1 b/deps/npm/man/man1/npm-access.1 index ce8b4b2347e6..ceb3f6c17fa9 100644 --- a/deps/npm/man/man1/npm-access.1 +++ b/deps/npm/man/man1/npm-access.1 @@ -1,4 +1,4 @@ -.TH "NPM-ACCESS" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-ACCESS" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-access\fR - Set access level on published packages .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-adduser.1 b/deps/npm/man/man1/npm-adduser.1 index 99c40da90214..cf7986395837 100644 --- a/deps/npm/man/man1/npm-adduser.1 +++ b/deps/npm/man/man1/npm-adduser.1 @@ -1,4 +1,4 @@ -.TH "NPM-ADDUSER" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-ADDUSER" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-adduser\fR - Add a registry user account .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-approve-scripts.1 b/deps/npm/man/man1/npm-approve-scripts.1 index ea3b8559af31..dedc97ea43fb 100644 --- a/deps/npm/man/man1/npm-approve-scripts.1 +++ b/deps/npm/man/man1/npm-approve-scripts.1 @@ -1,4 +1,4 @@ -.TH "NPM-APPROVE-SCRIPTS" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-APPROVE-SCRIPTS" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-approve-scripts\fR - Approve install scripts for specific dependencies .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-audit.1 b/deps/npm/man/man1/npm-audit.1 index d66317be2a68..f849ab67df27 100644 --- a/deps/npm/man/man1/npm-audit.1 +++ b/deps/npm/man/man1/npm-audit.1 @@ -1,4 +1,4 @@ -.TH "NPM-AUDIT" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-AUDIT" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-audit\fR - Run a security audit .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-bugs.1 b/deps/npm/man/man1/npm-bugs.1 index be2e0ec4073a..666d56f830ad 100644 --- a/deps/npm/man/man1/npm-bugs.1 +++ b/deps/npm/man/man1/npm-bugs.1 @@ -1,4 +1,4 @@ -.TH "NPM-BUGS" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-BUGS" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-bugs\fR - Report bugs for a package in a web browser .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-cache.1 b/deps/npm/man/man1/npm-cache.1 index 08a904d5e7ac..e31c02d01975 100644 --- a/deps/npm/man/man1/npm-cache.1 +++ b/deps/npm/man/man1/npm-cache.1 @@ -1,4 +1,4 @@ -.TH "NPM-CACHE" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-CACHE" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-cache\fR - Manipulates packages cache .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-ci.1 b/deps/npm/man/man1/npm-ci.1 index b0611e9a121d..9c9d0a3fb1a2 100644 --- a/deps/npm/man/man1/npm-ci.1 +++ b/deps/npm/man/man1/npm-ci.1 @@ -1,4 +1,4 @@ -.TH "NPM-CI" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-CI" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-ci\fR - Clean install a project .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-completion.1 b/deps/npm/man/man1/npm-completion.1 index b84d2dc1fd65..efd483b409d6 100644 --- a/deps/npm/man/man1/npm-completion.1 +++ b/deps/npm/man/man1/npm-completion.1 @@ -1,4 +1,4 @@ -.TH "NPM-COMPLETION" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-COMPLETION" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-completion\fR - Tab Completion for npm .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-config.1 b/deps/npm/man/man1/npm-config.1 index 2bbcce24963f..976fc2f6d8b4 100644 --- a/deps/npm/man/man1/npm-config.1 +++ b/deps/npm/man/man1/npm-config.1 @@ -1,4 +1,4 @@ -.TH "NPM-CONFIG" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-CONFIG" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-config\fR - Manage the npm configuration files .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-dedupe.1 b/deps/npm/man/man1/npm-dedupe.1 index f7f3cfc935ff..a49b48681deb 100644 --- a/deps/npm/man/man1/npm-dedupe.1 +++ b/deps/npm/man/man1/npm-dedupe.1 @@ -1,4 +1,4 @@ -.TH "NPM-DEDUPE" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-DEDUPE" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-dedupe\fR - Reduce duplication in the package tree .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-deny-scripts.1 b/deps/npm/man/man1/npm-deny-scripts.1 index 0477e41503b1..826b8ca1333f 100644 --- a/deps/npm/man/man1/npm-deny-scripts.1 +++ b/deps/npm/man/man1/npm-deny-scripts.1 @@ -1,4 +1,4 @@ -.TH "NPM-DENY-SCRIPTS" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-DENY-SCRIPTS" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-deny-scripts\fR - Deny install scripts for specific dependencies .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-deprecate.1 b/deps/npm/man/man1/npm-deprecate.1 index 4caab6c3ad9a..0aa94ec5898b 100644 --- a/deps/npm/man/man1/npm-deprecate.1 +++ b/deps/npm/man/man1/npm-deprecate.1 @@ -1,4 +1,4 @@ -.TH "NPM-DEPRECATE" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-DEPRECATE" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-deprecate\fR - Deprecate a version of a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-diff.1 b/deps/npm/man/man1/npm-diff.1 index e2122d844c60..777fe47a026c 100644 --- a/deps/npm/man/man1/npm-diff.1 +++ b/deps/npm/man/man1/npm-diff.1 @@ -1,4 +1,4 @@ -.TH "NPM-DIFF" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-DIFF" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-diff\fR - The registry diff command .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-dist-tag.1 b/deps/npm/man/man1/npm-dist-tag.1 index 7a85fbab68d9..37c86f432d9e 100644 --- a/deps/npm/man/man1/npm-dist-tag.1 +++ b/deps/npm/man/man1/npm-dist-tag.1 @@ -1,4 +1,4 @@ -.TH "NPM-DIST-TAG" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-DIST-TAG" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-dist-tag\fR - Modify package distribution tags .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-docs.1 b/deps/npm/man/man1/npm-docs.1 index ef586a86d27b..50e31e8735aa 100644 --- a/deps/npm/man/man1/npm-docs.1 +++ b/deps/npm/man/man1/npm-docs.1 @@ -1,4 +1,4 @@ -.TH "NPM-DOCS" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-DOCS" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-docs\fR - Open documentation for a package in a web browser .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-doctor.1 b/deps/npm/man/man1/npm-doctor.1 index 05c5c4d9dcc8..8ef2fa2ad597 100644 --- a/deps/npm/man/man1/npm-doctor.1 +++ b/deps/npm/man/man1/npm-doctor.1 @@ -1,4 +1,4 @@ -.TH "NPM-DOCTOR" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-DOCTOR" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-doctor\fR - Check the health of your npm environment .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-edit.1 b/deps/npm/man/man1/npm-edit.1 index 7bf09de5223b..8f1a7dfcb893 100644 --- a/deps/npm/man/man1/npm-edit.1 +++ b/deps/npm/man/man1/npm-edit.1 @@ -1,4 +1,4 @@ -.TH "NPM-EDIT" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-EDIT" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-edit\fR - Edit an installed package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-exec.1 b/deps/npm/man/man1/npm-exec.1 index b3132e062c73..dc36459b4d5f 100644 --- a/deps/npm/man/man1/npm-exec.1 +++ b/deps/npm/man/man1/npm-exec.1 @@ -1,4 +1,4 @@ -.TH "NPM-EXEC" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-EXEC" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-exec\fR - Run a command from a local or remote npm package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-explain.1 b/deps/npm/man/man1/npm-explain.1 index 1abb4a0e94da..069fcd70580b 100644 --- a/deps/npm/man/man1/npm-explain.1 +++ b/deps/npm/man/man1/npm-explain.1 @@ -1,4 +1,4 @@ -.TH "NPM-EXPLAIN" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-EXPLAIN" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-explain\fR - Explain installed packages .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-explore.1 b/deps/npm/man/man1/npm-explore.1 index fc45ae27ae20..341994714fa7 100644 --- a/deps/npm/man/man1/npm-explore.1 +++ b/deps/npm/man/man1/npm-explore.1 @@ -1,4 +1,4 @@ -.TH "NPM-EXPLORE" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-EXPLORE" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-explore\fR - Browse an installed package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-find-dupes.1 b/deps/npm/man/man1/npm-find-dupes.1 index 5804305f900a..bafc5d9f5497 100644 --- a/deps/npm/man/man1/npm-find-dupes.1 +++ b/deps/npm/man/man1/npm-find-dupes.1 @@ -1,4 +1,4 @@ -.TH "NPM-FIND-DUPES" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-FIND-DUPES" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-find-dupes\fR - Find duplication in the package tree .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-fund.1 b/deps/npm/man/man1/npm-fund.1 index 10eca78547ae..a3c47fad3b81 100644 --- a/deps/npm/man/man1/npm-fund.1 +++ b/deps/npm/man/man1/npm-fund.1 @@ -1,4 +1,4 @@ -.TH "NPM-FUND" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-FUND" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-fund\fR - Retrieve funding information .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-get.1 b/deps/npm/man/man1/npm-get.1 index b67dfc6ed7e9..1e3cb0119c92 100644 --- a/deps/npm/man/man1/npm-get.1 +++ b/deps/npm/man/man1/npm-get.1 @@ -1,4 +1,4 @@ -.TH "NPM-GET" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-GET" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-get\fR - Get a value from the npm configuration .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-help-search.1 b/deps/npm/man/man1/npm-help-search.1 index 44c464dc229f..10eac50e9bb4 100644 --- a/deps/npm/man/man1/npm-help-search.1 +++ b/deps/npm/man/man1/npm-help-search.1 @@ -1,4 +1,4 @@ -.TH "NPM-HELP-SEARCH" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-HELP-SEARCH" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-help-search\fR - Search npm help documentation .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-help.1 b/deps/npm/man/man1/npm-help.1 index 6c0dac1a9b93..221b0526e37e 100644 --- a/deps/npm/man/man1/npm-help.1 +++ b/deps/npm/man/man1/npm-help.1 @@ -1,4 +1,4 @@ -.TH "NPM-HELP" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-HELP" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-help\fR - Get help on npm .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-init.1 b/deps/npm/man/man1/npm-init.1 index 3774607b93d3..73ebb727ff5c 100644 --- a/deps/npm/man/man1/npm-init.1 +++ b/deps/npm/man/man1/npm-init.1 @@ -1,4 +1,4 @@ -.TH "NPM-INIT" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-INIT" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-init\fR - Create a package.json file .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-install-ci-test.1 b/deps/npm/man/man1/npm-install-ci-test.1 index 0725d13e7a9a..01ea550da997 100644 --- a/deps/npm/man/man1/npm-install-ci-test.1 +++ b/deps/npm/man/man1/npm-install-ci-test.1 @@ -1,4 +1,4 @@ -.TH "NPM-INSTALL-CI-TEST" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-INSTALL-CI-TEST" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-install-ci-test\fR - Install a project with a clean slate and run tests .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-install-scripts.1 b/deps/npm/man/man1/npm-install-scripts.1 index 9d4076cd8fd3..1de6e2c71efa 100644 --- a/deps/npm/man/man1/npm-install-scripts.1 +++ b/deps/npm/man/man1/npm-install-scripts.1 @@ -1,4 +1,4 @@ -.TH "NPM-INSTALL-SCRIPTS" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-INSTALL-SCRIPTS" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-install-scripts\fR - Manage install-script approvals for dependencies .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-install-test.1 b/deps/npm/man/man1/npm-install-test.1 index f552f5d39356..fb7f6ad652d8 100644 --- a/deps/npm/man/man1/npm-install-test.1 +++ b/deps/npm/man/man1/npm-install-test.1 @@ -1,4 +1,4 @@ -.TH "NPM-INSTALL-TEST" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-INSTALL-TEST" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-install-test\fR - Install package(s) and run tests .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-install.1 b/deps/npm/man/man1/npm-install.1 index 06e7759a10f1..6e6b595c5bbb 100644 --- a/deps/npm/man/man1/npm-install.1 +++ b/deps/npm/man/man1/npm-install.1 @@ -1,4 +1,4 @@ -.TH "NPM-INSTALL" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-INSTALL" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-install\fR - Install a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-link.1 b/deps/npm/man/man1/npm-link.1 index 9e03ea12635a..9c884b32fbf2 100644 --- a/deps/npm/man/man1/npm-link.1 +++ b/deps/npm/man/man1/npm-link.1 @@ -1,4 +1,4 @@ -.TH "NPM-LINK" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-LINK" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-link\fR - Symlink a package folder .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-ll.1 b/deps/npm/man/man1/npm-ll.1 index 8fd1de7651e5..a5794eeb02d2 100644 --- a/deps/npm/man/man1/npm-ll.1 +++ b/deps/npm/man/man1/npm-ll.1 @@ -1,4 +1,4 @@ -.TH "NPM-LL" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-LL" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-ll\fR - List installed packages .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-login.1 b/deps/npm/man/man1/npm-login.1 index aef0a9f0387a..00f30771b596 100644 --- a/deps/npm/man/man1/npm-login.1 +++ b/deps/npm/man/man1/npm-login.1 @@ -1,4 +1,4 @@ -.TH "NPM-LOGIN" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-LOGIN" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-login\fR - Login to a registry user account .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-logout.1 b/deps/npm/man/man1/npm-logout.1 index 941e7fdc0a51..3e7200cc1e91 100644 --- a/deps/npm/man/man1/npm-logout.1 +++ b/deps/npm/man/man1/npm-logout.1 @@ -1,4 +1,4 @@ -.TH "NPM-LOGOUT" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-LOGOUT" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-logout\fR - Log out of the registry .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-ls.1 b/deps/npm/man/man1/npm-ls.1 index 96705c0c5aac..dc12c29f1a0f 100644 --- a/deps/npm/man/man1/npm-ls.1 +++ b/deps/npm/man/man1/npm-ls.1 @@ -1,4 +1,4 @@ -.TH "NPM-LS" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-LS" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-ls\fR - List installed packages .SS "Synopsis" @@ -20,7 +20,7 @@ Positional arguments are \fBname@version-range\fR identifiers, which will limit .P .RS 2 .nf -npm@11.20.0 /path/to/npm +npm@11.21.2 /path/to/npm └─┬ init-package-json@0.0.4 └── promzard@0.1.5 .fi diff --git a/deps/npm/man/man1/npm-org.1 b/deps/npm/man/man1/npm-org.1 index 38b8cdcf2454..58bea33b5e2e 100644 --- a/deps/npm/man/man1/npm-org.1 +++ b/deps/npm/man/man1/npm-org.1 @@ -1,4 +1,4 @@ -.TH "NPM-ORG" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-ORG" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-org\fR - Manage orgs .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-outdated.1 b/deps/npm/man/man1/npm-outdated.1 index 2995ee084341..d34c347088f4 100644 --- a/deps/npm/man/man1/npm-outdated.1 +++ b/deps/npm/man/man1/npm-outdated.1 @@ -1,4 +1,4 @@ -.TH "NPM-OUTDATED" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-OUTDATED" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-outdated\fR - Check for outdated packages .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-owner.1 b/deps/npm/man/man1/npm-owner.1 index a1644110b92b..d5d234979a8c 100644 --- a/deps/npm/man/man1/npm-owner.1 +++ b/deps/npm/man/man1/npm-owner.1 @@ -1,4 +1,4 @@ -.TH "NPM-OWNER" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-OWNER" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-owner\fR - Manage package owners .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-pack.1 b/deps/npm/man/man1/npm-pack.1 index ea2ef9106377..f3a7f77cced7 100644 --- a/deps/npm/man/man1/npm-pack.1 +++ b/deps/npm/man/man1/npm-pack.1 @@ -1,4 +1,4 @@ -.TH "NPM-PACK" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-PACK" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-pack\fR - Create a tarball from a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-ping.1 b/deps/npm/man/man1/npm-ping.1 index d3590506a26e..c6a347a93bba 100644 --- a/deps/npm/man/man1/npm-ping.1 +++ b/deps/npm/man/man1/npm-ping.1 @@ -1,4 +1,4 @@ -.TH "NPM-PING" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-PING" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-ping\fR - Ping npm registry .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-pkg.1 b/deps/npm/man/man1/npm-pkg.1 index 3c72c3739a52..05ca067c19ca 100644 --- a/deps/npm/man/man1/npm-pkg.1 +++ b/deps/npm/man/man1/npm-pkg.1 @@ -1,4 +1,4 @@ -.TH "NPM-PKG" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-PKG" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-pkg\fR - Manages your package.json .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-prefix.1 b/deps/npm/man/man1/npm-prefix.1 index 1b5cf9e8e5b5..d257e9ba0aad 100644 --- a/deps/npm/man/man1/npm-prefix.1 +++ b/deps/npm/man/man1/npm-prefix.1 @@ -1,4 +1,4 @@ -.TH "NPM-PREFIX" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-PREFIX" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-prefix\fR - Display prefix .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-profile.1 b/deps/npm/man/man1/npm-profile.1 index 256f451dc450..e60248cdfe29 100644 --- a/deps/npm/man/man1/npm-profile.1 +++ b/deps/npm/man/man1/npm-profile.1 @@ -1,4 +1,4 @@ -.TH "NPM-PROFILE" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-PROFILE" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-profile\fR - Change settings on your registry profile .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-prune.1 b/deps/npm/man/man1/npm-prune.1 index 8c2823dc7a8b..f7f4bf2d6956 100644 --- a/deps/npm/man/man1/npm-prune.1 +++ b/deps/npm/man/man1/npm-prune.1 @@ -1,4 +1,4 @@ -.TH "NPM-PRUNE" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-PRUNE" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-prune\fR - Remove extraneous packages .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-publish.1 b/deps/npm/man/man1/npm-publish.1 index 5b89d5a10208..aad3180fee85 100644 --- a/deps/npm/man/man1/npm-publish.1 +++ b/deps/npm/man/man1/npm-publish.1 @@ -1,4 +1,4 @@ -.TH "NPM-PUBLISH" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-PUBLISH" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-publish\fR - Publish a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-query.1 b/deps/npm/man/man1/npm-query.1 index 244e8b964694..62d17dc5dbf1 100644 --- a/deps/npm/man/man1/npm-query.1 +++ b/deps/npm/man/man1/npm-query.1 @@ -1,4 +1,4 @@ -.TH "NPM-QUERY" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-QUERY" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-query\fR - Dependency selector query .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-rebuild.1 b/deps/npm/man/man1/npm-rebuild.1 index 3fb1df08bc54..e4c818dcff99 100644 --- a/deps/npm/man/man1/npm-rebuild.1 +++ b/deps/npm/man/man1/npm-rebuild.1 @@ -1,4 +1,4 @@ -.TH "NPM-REBUILD" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-REBUILD" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-rebuild\fR - Rebuild a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-repo.1 b/deps/npm/man/man1/npm-repo.1 index a68132d520b7..64fb13ad727f 100644 --- a/deps/npm/man/man1/npm-repo.1 +++ b/deps/npm/man/man1/npm-repo.1 @@ -1,4 +1,4 @@ -.TH "NPM-REPO" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-REPO" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-repo\fR - Open package repository page in the browser .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-restart.1 b/deps/npm/man/man1/npm-restart.1 index 8d1deb3ff11a..6b7333de08e8 100644 --- a/deps/npm/man/man1/npm-restart.1 +++ b/deps/npm/man/man1/npm-restart.1 @@ -1,4 +1,4 @@ -.TH "NPM-RESTART" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-RESTART" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-restart\fR - Restart a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-root.1 b/deps/npm/man/man1/npm-root.1 index 7090dca70da7..e43130a5de04 100644 --- a/deps/npm/man/man1/npm-root.1 +++ b/deps/npm/man/man1/npm-root.1 @@ -1,4 +1,4 @@ -.TH "NPM-ROOT" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-ROOT" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-root\fR - Display npm root .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-run.1 b/deps/npm/man/man1/npm-run.1 index fe72ce5905a0..44a0a4139450 100644 --- a/deps/npm/man/man1/npm-run.1 +++ b/deps/npm/man/man1/npm-run.1 @@ -1,4 +1,4 @@ -.TH "NPM-RUN" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-RUN" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-run\fR - Run arbitrary package scripts .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-sbom.1 b/deps/npm/man/man1/npm-sbom.1 index 531b802b6596..977e99d3680d 100644 --- a/deps/npm/man/man1/npm-sbom.1 +++ b/deps/npm/man/man1/npm-sbom.1 @@ -1,4 +1,4 @@ -.TH "NPM-SBOM" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-SBOM" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-sbom\fR - Generate a Software Bill of Materials (SBOM) .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-search.1 b/deps/npm/man/man1/npm-search.1 index bf07a91bbc17..fdf8b0d9d4b8 100644 --- a/deps/npm/man/man1/npm-search.1 +++ b/deps/npm/man/man1/npm-search.1 @@ -1,4 +1,4 @@ -.TH "NPM-SEARCH" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-SEARCH" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-search\fR - Search for packages .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-set.1 b/deps/npm/man/man1/npm-set.1 index 4478b11a645f..98aa226928ee 100644 --- a/deps/npm/man/man1/npm-set.1 +++ b/deps/npm/man/man1/npm-set.1 @@ -1,4 +1,4 @@ -.TH "NPM-SET" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-SET" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-set\fR - Set a value in the npm configuration .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-shrinkwrap.1 b/deps/npm/man/man1/npm-shrinkwrap.1 index 48949aa9d494..10b329ad53fd 100644 --- a/deps/npm/man/man1/npm-shrinkwrap.1 +++ b/deps/npm/man/man1/npm-shrinkwrap.1 @@ -1,4 +1,4 @@ -.TH "NPM-SHRINKWRAP" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-SHRINKWRAP" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-shrinkwrap\fR - Lock down dependency versions for publication .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-stage.1 b/deps/npm/man/man1/npm-stage.1 index 0f2df23539ce..e7f5ee6dabdb 100644 --- a/deps/npm/man/man1/npm-stage.1 +++ b/deps/npm/man/man1/npm-stage.1 @@ -1,4 +1,4 @@ -.TH "NPM-STAGE" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-STAGE" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-stage\fR - Stage packages for publishing .SS "Synopsis" @@ -25,6 +25,8 @@ Staged packages share the same semver version unique index as published packages .IP \(bu 4 You can still publish packages normally while you have staged packages pending. .IP \(bu 4 +Staged publishing will allow you to create a new package if it does not exist. +.IP \(bu 4 You can stage multiple versions of the same package. .IP \(bu 4 \fBnpm stage publish\fR has parity with \fBnpm publish\fR and will respect \fB"private": true\fR in \fBpackage.json\fR, refusing to stage the package. @@ -37,8 +39,6 @@ Before using \fBnpm stage\fR commands, ensure the following requirements are met .IP \(bu 4 \fBWrite permissions on the package:\fR You must have write access to the package you're configuring. .IP \(bu 4 -\fBPackage must exist:\fR The package you're configuring must already exist on the npm registry. -.IP \(bu 4 \fB2FA enabled on your account:\fR Commands that require 2FA will prompt you to authenticate. If you don't already have 2FA enabled on your account, you must enable it before using these commands. .RE 0 diff --git a/deps/npm/man/man1/npm-star.1 b/deps/npm/man/man1/npm-star.1 index 1c05090c3a22..cfe66082f5d6 100644 --- a/deps/npm/man/man1/npm-star.1 +++ b/deps/npm/man/man1/npm-star.1 @@ -1,4 +1,4 @@ -.TH "NPM-STAR" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-STAR" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-star\fR - Mark your favorite packages .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-stars.1 b/deps/npm/man/man1/npm-stars.1 index 27ee3c674f0f..3edd9394710c 100644 --- a/deps/npm/man/man1/npm-stars.1 +++ b/deps/npm/man/man1/npm-stars.1 @@ -1,4 +1,4 @@ -.TH "NPM-STARS" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-STARS" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-stars\fR - View packages marked as favorites .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-start.1 b/deps/npm/man/man1/npm-start.1 index 17c42ab4023a..482e12f920bd 100644 --- a/deps/npm/man/man1/npm-start.1 +++ b/deps/npm/man/man1/npm-start.1 @@ -1,4 +1,4 @@ -.TH "NPM-START" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-START" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-start\fR - Start a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-stop.1 b/deps/npm/man/man1/npm-stop.1 index 1d544669cbf0..28b9dcca489f 100644 --- a/deps/npm/man/man1/npm-stop.1 +++ b/deps/npm/man/man1/npm-stop.1 @@ -1,4 +1,4 @@ -.TH "NPM-STOP" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-STOP" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-stop\fR - Stop a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-team.1 b/deps/npm/man/man1/npm-team.1 index 1d42f0f514c9..0fbcab4e8cfa 100644 --- a/deps/npm/man/man1/npm-team.1 +++ b/deps/npm/man/man1/npm-team.1 @@ -1,4 +1,4 @@ -.TH "NPM-TEAM" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-TEAM" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-team\fR - Manage organization teams and team memberships .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-test.1 b/deps/npm/man/man1/npm-test.1 index 8136624750b3..5d7fce9ea538 100644 --- a/deps/npm/man/man1/npm-test.1 +++ b/deps/npm/man/man1/npm-test.1 @@ -1,4 +1,4 @@ -.TH "NPM-TEST" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-TEST" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-test\fR - Test a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-token.1 b/deps/npm/man/man1/npm-token.1 index dafb3148e684..6b8b7f1432b4 100644 --- a/deps/npm/man/man1/npm-token.1 +++ b/deps/npm/man/man1/npm-token.1 @@ -1,4 +1,4 @@ -.TH "NPM-TOKEN" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-TOKEN" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-token\fR - Manage your authentication tokens .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-trust.1 b/deps/npm/man/man1/npm-trust.1 index 87d202f3cef8..49b9493aed9c 100644 --- a/deps/npm/man/man1/npm-trust.1 +++ b/deps/npm/man/man1/npm-trust.1 @@ -1,4 +1,4 @@ -.TH "NPM-TRUST" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-TRUST" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-trust\fR - Manage trusted publishing relationships between packages and CI/CD providers .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-undeprecate.1 b/deps/npm/man/man1/npm-undeprecate.1 index 6b3b29e1e560..dcede198e83f 100644 --- a/deps/npm/man/man1/npm-undeprecate.1 +++ b/deps/npm/man/man1/npm-undeprecate.1 @@ -1,4 +1,4 @@ -.TH "NPM-UNDEPRECATE" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-UNDEPRECATE" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-undeprecate\fR - Undeprecate a version of a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-uninstall.1 b/deps/npm/man/man1/npm-uninstall.1 index 896258939adf..5be1016a76ec 100644 --- a/deps/npm/man/man1/npm-uninstall.1 +++ b/deps/npm/man/man1/npm-uninstall.1 @@ -1,4 +1,4 @@ -.TH "NPM-UNINSTALL" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-UNINSTALL" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-uninstall\fR - Remove a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-unpublish.1 b/deps/npm/man/man1/npm-unpublish.1 index f99202071c02..f60f2629ebd5 100644 --- a/deps/npm/man/man1/npm-unpublish.1 +++ b/deps/npm/man/man1/npm-unpublish.1 @@ -1,4 +1,4 @@ -.TH "NPM-UNPUBLISH" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-UNPUBLISH" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-unpublish\fR - Remove a package from the registry .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-unstar.1 b/deps/npm/man/man1/npm-unstar.1 index 60bf9fea8bde..14c130030cf1 100644 --- a/deps/npm/man/man1/npm-unstar.1 +++ b/deps/npm/man/man1/npm-unstar.1 @@ -1,4 +1,4 @@ -.TH "NPM-UNSTAR" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-UNSTAR" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-unstar\fR - Remove an item from your favorite packages .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-update.1 b/deps/npm/man/man1/npm-update.1 index 3e13678d0b98..09149b799a3c 100644 --- a/deps/npm/man/man1/npm-update.1 +++ b/deps/npm/man/man1/npm-update.1 @@ -1,4 +1,4 @@ -.TH "NPM-UPDATE" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-UPDATE" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-update\fR - Update packages .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-version.1 b/deps/npm/man/man1/npm-version.1 index 064e3a0b752b..099e09b920f4 100644 --- a/deps/npm/man/man1/npm-version.1 +++ b/deps/npm/man/man1/npm-version.1 @@ -1,4 +1,4 @@ -.TH "NPM-VERSION" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-VERSION" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-version\fR - Bump a package version .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-view.1 b/deps/npm/man/man1/npm-view.1 index de67d3f900c7..d3ae4185cd1a 100644 --- a/deps/npm/man/man1/npm-view.1 +++ b/deps/npm/man/man1/npm-view.1 @@ -1,4 +1,4 @@ -.TH "NPM-VIEW" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-VIEW" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-view\fR - View registry info .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-whoami.1 b/deps/npm/man/man1/npm-whoami.1 index ddfde867876f..c3ce1bc97b3b 100644 --- a/deps/npm/man/man1/npm-whoami.1 +++ b/deps/npm/man/man1/npm-whoami.1 @@ -1,4 +1,4 @@ -.TH "NPM-WHOAMI" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM-WHOAMI" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-whoami\fR - Display npm username .SS "Synopsis" diff --git a/deps/npm/man/man1/npm.1 b/deps/npm/man/man1/npm.1 index 2f4b30a1f131..c8ea8a2cc74d 100644 --- a/deps/npm/man/man1/npm.1 +++ b/deps/npm/man/man1/npm.1 @@ -1,4 +1,4 @@ -.TH "NPM" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPM" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm\fR - javascript package manager .SS "Synopsis" @@ -12,7 +12,7 @@ npm Note: This command is unaware of workspaces. .SS "Version" .P -11.20.0 +11.21.2 .SS "Description" .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 conflicts intelligently. diff --git a/deps/npm/man/man1/npx.1 b/deps/npm/man/man1/npx.1 index a50bf96cf460..c99e96924693 100644 --- a/deps/npm/man/man1/npx.1 +++ b/deps/npm/man/man1/npx.1 @@ -1,4 +1,4 @@ -.TH "NPX" "1" "September 2026" "NPM@11.20.0" "" +.TH "NPX" "1" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpx\fR - Run a command from a local or remote npm package .SS "Synopsis" diff --git a/deps/npm/man/man5/folders.5 b/deps/npm/man/man5/folders.5 index 40aa02afd341..ecca3409f8d5 100644 --- a/deps/npm/man/man5/folders.5 +++ b/deps/npm/man/man5/folders.5 @@ -1,4 +1,4 @@ -.TH "FOLDERS" "5" "September 2026" "NPM@11.20.0" "" +.TH "FOLDERS" "5" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBFolders\fR - Folder structures used by npm .SS "Description" diff --git a/deps/npm/man/man5/install.5 b/deps/npm/man/man5/install.5 index af38748dc564..9687aa670267 100644 --- a/deps/npm/man/man5/install.5 +++ b/deps/npm/man/man5/install.5 @@ -1,4 +1,4 @@ -.TH "INSTALL" "5" "September 2026" "NPM@11.20.0" "" +.TH "INSTALL" "5" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBInstall\fR - Download and install node and npm .SS "Description" diff --git a/deps/npm/man/man5/npm-global.5 b/deps/npm/man/man5/npm-global.5 index 40aa02afd341..ecca3409f8d5 100644 --- a/deps/npm/man/man5/npm-global.5 +++ b/deps/npm/man/man5/npm-global.5 @@ -1,4 +1,4 @@ -.TH "FOLDERS" "5" "September 2026" "NPM@11.20.0" "" +.TH "FOLDERS" "5" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBFolders\fR - Folder structures used by npm .SS "Description" diff --git a/deps/npm/man/man5/npm-json.5 b/deps/npm/man/man5/npm-json.5 index 41f16abf240c..639e431c897b 100644 --- a/deps/npm/man/man5/npm-json.5 +++ b/deps/npm/man/man5/npm-json.5 @@ -1,4 +1,4 @@ -.TH "PACKAGE.JSON" "5" "September 2026" "NPM@11.20.0" "" +.TH "PACKAGE.JSON" "5" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBpackage.json\fR - Specifics of npm's package.json handling .SS "Description" diff --git a/deps/npm/man/man5/npm-shrinkwrap-json.5 b/deps/npm/man/man5/npm-shrinkwrap-json.5 index ec1a2cdcfab4..d8fae509e4a1 100644 --- a/deps/npm/man/man5/npm-shrinkwrap-json.5 +++ b/deps/npm/man/man5/npm-shrinkwrap-json.5 @@ -1,4 +1,4 @@ -.TH "NPM-SHRINKWRAP.JSON" "5" "September 2026" "NPM@11.20.0" "" +.TH "NPM-SHRINKWRAP.JSON" "5" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBnpm-shrinkwrap.json\fR - A publishable lockfile .SS "Description" diff --git a/deps/npm/man/man5/npmrc.5 b/deps/npm/man/man5/npmrc.5 index d41c7e36a63f..03557880a5e8 100644 --- a/deps/npm/man/man5/npmrc.5 +++ b/deps/npm/man/man5/npmrc.5 @@ -1,4 +1,4 @@ -.TH ".NPMRC" "5" "September 2026" "NPM@11.20.0" "" +.TH ".NPMRC" "5" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fB.npmrc\fR - The npm config files .SS "Description" diff --git a/deps/npm/man/man5/package-json.5 b/deps/npm/man/man5/package-json.5 index 41f16abf240c..639e431c897b 100644 --- a/deps/npm/man/man5/package-json.5 +++ b/deps/npm/man/man5/package-json.5 @@ -1,4 +1,4 @@ -.TH "PACKAGE.JSON" "5" "September 2026" "NPM@11.20.0" "" +.TH "PACKAGE.JSON" "5" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBpackage.json\fR - Specifics of npm's package.json handling .SS "Description" diff --git a/deps/npm/man/man5/package-lock-json.5 b/deps/npm/man/man5/package-lock-json.5 index 46d19e3b57a8..960fef79eb2b 100644 --- a/deps/npm/man/man5/package-lock-json.5 +++ b/deps/npm/man/man5/package-lock-json.5 @@ -1,4 +1,4 @@ -.TH "PACKAGE-LOCK.JSON" "5" "September 2026" "NPM@11.20.0" "" +.TH "PACKAGE-LOCK.JSON" "5" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBpackage-lock.json\fR - A manifestation of the manifest .SS "Description" diff --git a/deps/npm/man/man7/config.7 b/deps/npm/man/man7/config.7 index 92fba90eec0a..bd5661545e4e 100644 --- a/deps/npm/man/man7/config.7 +++ b/deps/npm/man/man7/config.7 @@ -1,4 +1,4 @@ -.TH "CONFIG" "7" "September 2026" "NPM@11.20.0" "" +.TH "CONFIG" "7" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBConfig\fR - About npm configuration .SS "Description" diff --git a/deps/npm/man/man7/dependency-selectors.7 b/deps/npm/man/man7/dependency-selectors.7 index c81faa51b8cb..5884af196f78 100644 --- a/deps/npm/man/man7/dependency-selectors.7 +++ b/deps/npm/man/man7/dependency-selectors.7 @@ -1,4 +1,4 @@ -.TH "SELECTORS" "7" "September 2026" "NPM@11.20.0" "" +.TH "SELECTORS" "7" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBSelectors\fR - Dependency Selector Syntax & Querying .SS "Description" diff --git a/deps/npm/man/man7/developers.7 b/deps/npm/man/man7/developers.7 index 8d240d09d82c..89a7b8e8647b 100644 --- a/deps/npm/man/man7/developers.7 +++ b/deps/npm/man/man7/developers.7 @@ -1,4 +1,4 @@ -.TH "DEVELOPERS" "7" "September 2026" "NPM@11.20.0" "" +.TH "DEVELOPERS" "7" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBDevelopers\fR - Developer guide .SS "Description" diff --git a/deps/npm/man/man7/logging.7 b/deps/npm/man/man7/logging.7 index a0464b5231ca..a20bb662ed03 100644 --- a/deps/npm/man/man7/logging.7 +++ b/deps/npm/man/man7/logging.7 @@ -1,4 +1,4 @@ -.TH "LOGGING" "7" "September 2026" "NPM@11.20.0" "" +.TH "LOGGING" "7" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBLogging\fR - Why, What & How we Log .SS "Description" diff --git a/deps/npm/man/man7/orgs.7 b/deps/npm/man/man7/orgs.7 index 7c01081ea037..58ad4741081d 100644 --- a/deps/npm/man/man7/orgs.7 +++ b/deps/npm/man/man7/orgs.7 @@ -1,4 +1,4 @@ -.TH "ORGANIZATIONS" "7" "September 2026" "NPM@11.20.0" "" +.TH "ORGANIZATIONS" "7" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBOrganizations\fR - Working with teams & organizations .SS "Description" diff --git a/deps/npm/man/man7/package-spec.7 b/deps/npm/man/man7/package-spec.7 index 9261a4edc18e..1eda237ed7a9 100644 --- a/deps/npm/man/man7/package-spec.7 +++ b/deps/npm/man/man7/package-spec.7 @@ -1,4 +1,4 @@ -.TH "SPEC" "7" "September 2026" "NPM@11.20.0" "" +.TH "SPEC" "7" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBspec\fR - Package name specifier .SS "Description" diff --git a/deps/npm/man/man7/registry.7 b/deps/npm/man/man7/registry.7 index a529ad8c25da..1e0ac20aa4ad 100644 --- a/deps/npm/man/man7/registry.7 +++ b/deps/npm/man/man7/registry.7 @@ -1,4 +1,4 @@ -.TH "REGISTRY" "7" "September 2026" "NPM@11.20.0" "" +.TH "REGISTRY" "7" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBRegistry\fR - The JavaScript Package Registry .SS "Description" diff --git a/deps/npm/man/man7/removal.7 b/deps/npm/man/man7/removal.7 index de3d5663c0ba..fa1a46000a00 100644 --- a/deps/npm/man/man7/removal.7 +++ b/deps/npm/man/man7/removal.7 @@ -1,4 +1,4 @@ -.TH "REMOVAL" "7" "September 2026" "NPM@11.20.0" "" +.TH "REMOVAL" "7" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBRemoval\fR - Cleaning the slate .SS "Synopsis" diff --git a/deps/npm/man/man7/scope.7 b/deps/npm/man/man7/scope.7 index 37b246569f7c..914ea7891184 100644 --- a/deps/npm/man/man7/scope.7 +++ b/deps/npm/man/man7/scope.7 @@ -1,4 +1,4 @@ -.TH "SCOPE" "7" "September 2026" "NPM@11.20.0" "" +.TH "SCOPE" "7" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBScope\fR - Scoped packages .SS "Description" diff --git a/deps/npm/man/man7/scripts.7 b/deps/npm/man/man7/scripts.7 index d49e9941ba4c..b1b268cc8d1c 100644 --- a/deps/npm/man/man7/scripts.7 +++ b/deps/npm/man/man7/scripts.7 @@ -1,4 +1,4 @@ -.TH "SCRIPTS" "7" "September 2026" "NPM@11.20.0" "" +.TH "SCRIPTS" "7" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBScripts\fR - How npm handles the "scripts" field .SS "Description" diff --git a/deps/npm/man/man7/workspaces.7 b/deps/npm/man/man7/workspaces.7 index 7e74e51a60e0..b866bab8d2f5 100644 --- a/deps/npm/man/man7/workspaces.7 +++ b/deps/npm/man/man7/workspaces.7 @@ -1,4 +1,4 @@ -.TH "WORKSPACES" "7" "September 2026" "NPM@11.20.0" "" +.TH "WORKSPACES" "7" "November 2026" "NPM@11.21.2" "" .SH "NAME" \fBWorkspaces\fR - Working with workspaces .SS "Description" diff --git a/deps/npm/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js b/deps/npm/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js index 7ff7a21cf9b6..02fcb86aaa0d 100644 --- a/deps/npm/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js +++ b/deps/npm/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js @@ -1526,7 +1526,7 @@ This is a one-time fix-up, please be patient... // allow it. either we're overriding, or it's not something // that will be installed by default anyway, and we'll fail when // we get to the point where we need to, if we need to. - if (conflictOK || !required.has(dep)) { + if (conflictOK || !required.has(dep) || this.#loadFailures.has(dep)) { edge.peerConflicted = true continue } @@ -1560,7 +1560,7 @@ This is a one-time fix-up, please be patient... // isn't also required, then there's a good chance we won't need it, // so allow it for now and let it conflict if it turns out to actually // be necessary for the installation. - if (conflictOK || !required.has(edge.from)) { + if (conflictOK || !required.has(edge.from) || this.#loadFailures.has(current)) { continue } diff --git a/deps/npm/node_modules/@npmcli/arborist/lib/script-allowed.js b/deps/npm/node_modules/@npmcli/arborist/lib/script-allowed.js index 629625f8e4f8..b560884aac66 100644 --- a/deps/npm/node_modules/@npmcli/arborist/lib/script-allowed.js +++ b/deps/npm/node_modules/@npmcli/arborist/lib/script-allowed.js @@ -1,3 +1,4 @@ +const { resolve, sep } = require('node:path') const npa = require('npm-package-arg') const semver = require('semver') const versionFromTgz = require('./version-from-tgz.js') @@ -99,6 +100,23 @@ const matches = (node, key, failClosed) => { } } +// True when the node lives in the linked install strategy's `node_modules/.store` directory; such registry-managed packages must never derive identity from the store's internal `file:` link specs. +const isStoreBacked = (node) => { + if (node?.isInStore) { + return true + } + // Nodes loaded from the hidden lockfile do not retain isInStore, so fall back to the path, anchored to this tree's own store so an unrelated project's `.store` never qualifies. + const paths = [node?.path, node?.realpath].filter(p => typeof p === 'string') + const roots = [node?.root?.path, node?.root?.realpath].filter(p => typeof p === 'string') + for (const root of roots) { + const store = resolve(root, 'node_modules', '.store') + if (paths.some(path => path.startsWith(`${store}${sep}`))) { + return true + } + } + return false +} + const resolvedSourceSpecs = (node) => { const specs = [] const seen = new Set() @@ -112,7 +130,8 @@ const resolvedSourceSpecs = (node) => { add(node?.resolved) - if (!node?.resolved && node?.linksIn && typeof node.linksIn[Symbol.iterator] === 'function') { + if (!node?.resolved && !isStoreBacked(node) && + node?.linksIn && typeof node.linksIn[Symbol.iterator] === 'function') { let hasIncomingLink = false for (const link of node.linksIn) { hasIncomingLink = true @@ -235,10 +254,32 @@ const getTrustedRegistryIdentity = (node) => { } const nameFromEdges = (node) => { - if (!node.edgesIn || typeof node.edgesIn[Symbol.iterator] !== 'function') { + const name = nameFromEdgeSet(node?.edgesIn) + if (name || !isStoreBacked(node) || node?.edgesIn?.size !== 0) { + return name + } + // A store-backed link target carries no edges of its own; they land on the incoming Links, so consult their edges instead, refusing when a Link is non-registry, unidentified, or disagrees with another Link. + let linkName = null + if (node?.linksIn && typeof node.linksIn[Symbol.iterator] === 'function') { + for (const link of node.linksIn) { + if (!link.isRegistryDependency) { + return null + } + const name = nameFromEdgeSet(link.edgesIn) + if (!name || (linkName && linkName !== name)) { + return null + } + linkName = name + } + } + return linkName +} + +const nameFromEdgeSet = (edgesIn) => { + if (!edgesIn || typeof edgesIn[Symbol.iterator] !== 'function') { return null } - for (const edge of node.edgesIn) { + for (const edge of edgesIn) { let parsed try { parsed = npa.resolve(edge.name, edge.spec) @@ -350,7 +391,14 @@ const isRegistryNode = (node) => { // edge resolves to a registry spec, which is much harder to spoof than // the URL. if (typeof node.isRegistryDependency === 'boolean') { - return node.isRegistryDependency + if (node.isRegistryDependency) { + return true + } + // A store-backed link target carries no edges of its own; they land on the incoming Links (npm/cli#9939), so delegate the edge-based check to them, but never for ordinary local symlink targets that share the same topology. + if (isStoreBacked(node) && node.edgesIn?.size === 0 && node.linksIn?.size > 0) { + return [...node.linksIn].every(link => link.isRegistryDependency) + } + return false } // Fall back to URL parsing for nodes without the arborist getter // (e.g. test fixtures, lockfiles with omit-lockfile-registry-resolved). diff --git a/deps/npm/node_modules/@npmcli/arborist/package.json b/deps/npm/node_modules/@npmcli/arborist/package.json index 8bd0ffeaa946..feb07ad86ff6 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": "9.9.2", + "version": "9.9.3", "description": "Manage node_modules trees", "dependencies": { "@gar/promise-retry": "^1.0.0", diff --git a/deps/npm/node_modules/@npmcli/config/README.md b/deps/npm/node_modules/@npmcli/config/README.md index 6a948d9b11a9..b86557d6b31e 100644 --- a/deps/npm/node_modules/@npmcli/config/README.md +++ b/deps/npm/node_modules/@npmcli/config/README.md @@ -180,6 +180,8 @@ Set the key to the specified value, at the specified level in the config stack. Delete the configuration key from the specified level in the config stack. +Deleting an override exposes the next applicable value from the lower-priority sources and invalidates cached flattened options. + ### `config.validate(where)` Verify that all known configuration options are set to valid values, and log a warning if they are invalid. diff --git a/deps/npm/node_modules/@npmcli/config/lib/index.js b/deps/npm/node_modules/@npmcli/config/lib/index.js index a1acb7969b29..2f3a487be89f 100644 --- a/deps/npm/node_modules/@npmcli/config/lib/index.js +++ b/deps/npm/node_modules/@npmcli/config/lib/index.js @@ -236,6 +236,8 @@ class Config { if (['global', 'user', 'project'].includes(where)) { delete raw[key] } + this.data.get(where)[_valid] = null + this.#flatOptions = null } async load () { diff --git a/deps/npm/node_modules/@npmcli/config/package.json b/deps/npm/node_modules/@npmcli/config/package.json index dcc39cef2ac9..546f7696afa2 100644 --- a/deps/npm/node_modules/@npmcli/config/package.json +++ b/deps/npm/node_modules/@npmcli/config/package.json @@ -1,6 +1,6 @@ { "name": "@npmcli/config", - "version": "10.13.0", + "version": "10.14.0", "files": [ "bin/", "lib/" diff --git a/deps/npm/node_modules/@npmcli/promise-spawn/lib/index.js b/deps/npm/node_modules/@npmcli/promise-spawn/lib/index.js index 1faf62c9157d..d6d2c8cd048e 100644 --- a/deps/npm/node_modules/@npmcli/promise-spawn/lib/index.js +++ b/deps/npm/node_modules/@npmcli/promise-spawn/lib/index.js @@ -83,20 +83,27 @@ const spawnWithShell = (cmd, args, opts, extra) => { if (isCmd) { let doubleEscape = false - // find the actual command we're running + // Decode the executable token so escaped batch shims still get double escaping. let initialCmd = '' let insideQuotes = false for (let i = 0; i < cmd.length; ++i) { const char = cmd.charAt(i) + if (char === '^' && !insideQuotes && i + 1 < cmd.length) { + initialCmd += cmd.charAt(++i) + continue + } if (char === ' ' && !insideQuotes) { break } initialCmd += char - if (char === '"' || char === "'") { + if (char === '"') { insideQuotes = !insideQuotes } } + if (initialCmd.startsWith('"') && initialCmd.endsWith('"')) { + initialCmd = initialCmd.slice(1, -1) + } let pathToInitial try { diff --git a/deps/npm/node_modules/@npmcli/promise-spawn/package.json b/deps/npm/node_modules/@npmcli/promise-spawn/package.json index f00ee324355c..747e7e1dcadc 100644 --- a/deps/npm/node_modules/@npmcli/promise-spawn/package.json +++ b/deps/npm/node_modules/@npmcli/promise-spawn/package.json @@ -1,6 +1,6 @@ { "name": "@npmcli/promise-spawn", - "version": "9.0.1", + "version": "9.0.2", "files": [ "bin/", "lib/" @@ -43,7 +43,8 @@ "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", "version": "4.28.0", - "publish": true + "publish": true, + "backport": 9 }, "dependencies": { "which": "^6.0.0" diff --git a/deps/npm/node_modules/bin-links/lib/get-paths.js b/deps/npm/node_modules/bin-links/lib/get-paths.js index b93e6982dce2..871a9c4a812e 100644 --- a/deps/npm/node_modules/bin-links/lib/get-paths.js +++ b/deps/npm/node_modules/bin-links/lib/get-paths.js @@ -5,11 +5,13 @@ const binTarget = require('./bin-target.js') const manTarget = require('./man-target.js') const { resolve, basename, extname } = require('path') const isWindows = require('./is-windows.js') +const normalize = require('npm-normalize-package-bin') module.exports = ({ path, pkg, global, top }) => { if (top && !global) { return [] } + pkg = normalize({ ...pkg }) const binSet = [] const binTarg = binTarget({ path, top }) if (pkg.bin) { diff --git a/deps/npm/node_modules/bin-links/package.json b/deps/npm/node_modules/bin-links/package.json index 80a63323e884..ad14153c11e2 100644 --- a/deps/npm/node_modules/bin-links/package.json +++ b/deps/npm/node_modules/bin-links/package.json @@ -1,6 +1,6 @@ { "name": "bin-links", - "version": "6.0.2", + "version": "6.0.3", "description": "JavaScript package binary linker", "main": "./lib/index.js", "scripts": { @@ -57,6 +57,7 @@ "windowsCI": false, "version": "4.30.0", "publish": true, + "backport": 6, "updateNpm": false, "latestCiVersion": 24 } diff --git a/deps/npm/node_modules/brace-expansion/dist/commonjs/index.js b/deps/npm/node_modules/brace-expansion/dist/commonjs/index.js index 869a6bee2380..48cf0d3dabc8 100644 --- a/deps/npm/node_modules/brace-expansion/dist/commonjs/index.js +++ b/deps/npm/node_modules/brace-expansion/dist/commonjs/index.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.EXPANSION_MAX_LENGTH = exports.EXPANSION_MAX = void 0; +exports.EXPANSION_MAX_REWRITES = exports.EXPANSION_MAX_DEPTH = exports.EXPANSION_MAX_LENGTH = exports.EXPANSION_MAX = void 0; exports.expand = expand; const balanced_match_1 = require("balanced-match"); const escSlash = '\0SLASH' + Math.random() + '\0'; @@ -30,6 +30,24 @@ exports.EXPANSION_MAX = 100_000; // realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M // characters) so legitimate input is unaffected. exports.EXPANSION_MAX_LENGTH = 4_000_000; +// `expand_` recurses once per level of brace *nesting* - both when expanding a +// set's comma members and when re-wrapping a set whose body is a single part. +// The CVE-2026-14257 fix made the *tail* iterative (recursion on `m.post`, one +// level per chained group), which left nesting depth unbounded: about 3,100 +// levels of `{{{...a,b...}}}` - only ~6KB of input - exhausted the native stack +// and crashed the process. `EXPANSION_MAX_DEPTH` bounds how deep the parser +// will follow nesting. It sits far above any realistic pattern and well below +// the depth at which the stack runs out. +exports.EXPANSION_MAX_DEPTH = 1_000; +// Bash keeps a quirk where a brace group followed by a comma set still expands +// (`{a},b}`). The parser implements it by rewriting the string and restarting +// the scan, absorbing one `}` per pass. `n` trailing braces therefore cost `n` +// full passes over a string that itself grows by one `escClose` sentinel each +// time - quadratic in `n`, with a ~26x constant from the sentinel's length. +// 128KB of `'{a}' + '}'.repeat(n) + ',z}'` blocked the event loop for 27 +// seconds to produce two results. `EXPANSION_MAX_REWRITES` bounds how many +// times the scan may restart. Real `{a},b}` input needs a handful. +exports.EXPANSION_MAX_REWRITES = 1_000; function numeric(str) { return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0); } @@ -49,37 +67,52 @@ function unescapeBraces(str) { .replace(escCommaPattern, ',') .replace(escPeriodPattern, '.'); } +// Like `target.push(...items)` but doesn't overflow the stack +function pushAll(target, items) { + for (let i = 0; i < items.length; i++) { + target.push(items[i]); + } +} /** * Basically just str.split(","), but handling cases * where we have nested braced sections, which should be * treated as individual members, like {a,{b,c},d} */ function parseCommaParts(str) { - if (!str) { - return ['']; - } const parts = []; - const m = (0, balanced_match_1.balanced)('{', '}', str); - if (!m) { - return str.split(','); - } - const { pre, body, post } = m; - const p = pre.split(','); - p[p.length - 1] += '{' + body + '}'; - const postParts = parseCommaParts(post); - if (post.length) { - ; - p[p.length - 1] += postParts.shift(); - p.push.apply(p, postParts); + // Walk the brace groups iteratively. Recursing on `post` once per group let a + // chain of them exhaust the stack - the parsing-side counterpart to + // the `expand_` overflow fixed for CVE-2026-14257, and not something `max` or + // `maxLength` can bound, since it happens before expansion. + // + // The part the next chunk continues + let carry = ''; + for (;;) { + const m = (0, balanced_match_1.balanced)('{', '}', str); + if (!m) { + const tail = str.split(','); + tail[0] = carry + tail[0]; + pushAll(parts, tail); + return parts; + } + const { pre, body, post } = m; + const p = pre.split(','); + p[0] = carry + p[0]; + p[p.length - 1] += '{' + body + '}'; + if (!post.length) { + pushAll(parts, p); + return parts; + } + carry = p.pop(); + pushAll(parts, p); + str = post; } - parts.push.apply(parts, p); - return parts; } function expand(str, options = {}) { if (!str) { return []; } - const { max = exports.EXPANSION_MAX, maxLength = exports.EXPANSION_MAX_LENGTH } = options; + const { max = exports.EXPANSION_MAX, maxLength = exports.EXPANSION_MAX_LENGTH, maxDepth = exports.EXPANSION_MAX_DEPTH, maxRewrites = exports.EXPANSION_MAX_REWRITES, } = options; // I don't know why Bash 4.3 does this, but it does. // Anything starting with {} will have the first two bytes preserved // but *only* at the top level, so {},a}b will not expand to anything, @@ -89,7 +122,7 @@ function expand(str, options = {}) { if (str.slice(0, 2) === '{}') { str = '\\{\\}' + str.slice(2); } - return expand_(escapeBraces(str), max, maxLength, true).map(unescapeBraces); + return expand_(escapeBraces(str), max, maxLength, maxDepth, 0, maxRewrites, true).map(unescapeBraces); } function embrace(str) { return '{' + str + '}'; @@ -184,7 +217,13 @@ function expandSequence(body, isAlphaSequence, max, maxLength) { } return N; } -function expand_(str, max, maxLength, isTop) { +function expand_(str, max, maxLength, maxDepth, depth, maxRewrites, isTop) { + // Too deeply nested to keep following: treat the rest as literal, the same + // way a group that cannot expand is already handled. Truncating rather than + // throwing keeps `expand` total, matching `max` and `maxLength`. + if (depth > maxDepth) { + return [str]; + } // Consume the string's top-level brace groups left to right, threading a // running set of combined prefixes (`acc`). Expanding the tail iteratively - // rather than recursing on `m.post` once per group - keeps the native stack @@ -196,6 +235,9 @@ function expand_(str, max, maxLength, isTop) { // comma set - a sequence like `{a..\}` may legitimately yield ''. The drop // is on the final strings, so it is applied to whichever `combine` produces // them (the one with no brace set left in the tail). + // How many times the `{a},b}` rewrite below has restarted the scan. Each pass + // re-reads the whole string, so leaving this unbounded is quadratic. + let rewrites = 0; let dropEmpties = false; let firstGroup = true; for (;;) { @@ -220,7 +262,8 @@ function expand_(str, max, maxLength, isTop) { const isOptions = m.body.indexOf(',') >= 0; if (!isSequence && !isOptions) { // {a},b} - if (m.post.match(/,(?!,).*\}/)) { + if (rewrites < maxRewrites && m.post.match(/,(?!,).*\}/)) { + rewrites++; str = m.pre + '{' + m.body + escClose + m.post; isTop = true; continue; @@ -240,7 +283,7 @@ function expand_(str, max, maxLength, isTop) { let n = parseCommaParts(m.body); if (n.length === 1 && n[0] !== undefined) { // x{{a,b}}y ==> x{a}y x{b}y - n = expand_(n[0], max, maxLength, false).map(embrace); + n = expand_(n[0], max, maxLength, maxDepth, depth + 1, maxRewrites, false).map(embrace); //XXX is this necessary? Can't seem to hit it in tests. /* c8 ignore start */ if (n.length === 1) { @@ -266,12 +309,13 @@ function expand_(str, max, maxLength, isTop) { values = []; let valuesLength = 0; outer: for (let j = 0; j < n.length; j++) { - const expanded = expand_(n[j], max, maxLength, false); + const expanded = expand_(n[j], max, maxLength, maxDepth, depth + 1, maxRewrites, false); for (let k = 0; k < expanded.length; k++) { const v = expanded[k]; if (dropsEmpties && !v) continue; - if (values.length >= max || valuesLength + v.length > maxLength) { + if (values.length >= max || + valuesLength + v.length > maxLength) { break outer; } values.push(v); diff --git a/deps/npm/node_modules/brace-expansion/dist/esm/index.js b/deps/npm/node_modules/brace-expansion/dist/esm/index.js index fd68f5702920..0e0cc962307e 100644 --- a/deps/npm/node_modules/brace-expansion/dist/esm/index.js +++ b/deps/npm/node_modules/brace-expansion/dist/esm/index.js @@ -26,6 +26,24 @@ export const EXPANSION_MAX = 100_000; // realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M // characters) so legitimate input is unaffected. export const EXPANSION_MAX_LENGTH = 4_000_000; +// `expand_` recurses once per level of brace *nesting* - both when expanding a +// set's comma members and when re-wrapping a set whose body is a single part. +// The CVE-2026-14257 fix made the *tail* iterative (recursion on `m.post`, one +// level per chained group), which left nesting depth unbounded: about 3,100 +// levels of `{{{...a,b...}}}` - only ~6KB of input - exhausted the native stack +// and crashed the process. `EXPANSION_MAX_DEPTH` bounds how deep the parser +// will follow nesting. It sits far above any realistic pattern and well below +// the depth at which the stack runs out. +export const EXPANSION_MAX_DEPTH = 1_000; +// Bash keeps a quirk where a brace group followed by a comma set still expands +// (`{a},b}`). The parser implements it by rewriting the string and restarting +// the scan, absorbing one `}` per pass. `n` trailing braces therefore cost `n` +// full passes over a string that itself grows by one `escClose` sentinel each +// time - quadratic in `n`, with a ~26x constant from the sentinel's length. +// 128KB of `'{a}' + '}'.repeat(n) + ',z}'` blocked the event loop for 27 +// seconds to produce two results. `EXPANSION_MAX_REWRITES` bounds how many +// times the scan may restart. Real `{a},b}` input needs a handful. +export const EXPANSION_MAX_REWRITES = 1_000; function numeric(str) { return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0); } @@ -45,37 +63,52 @@ function unescapeBraces(str) { .replace(escCommaPattern, ',') .replace(escPeriodPattern, '.'); } +// Like `target.push(...items)` but doesn't overflow the stack +function pushAll(target, items) { + for (let i = 0; i < items.length; i++) { + target.push(items[i]); + } +} /** * Basically just str.split(","), but handling cases * where we have nested braced sections, which should be * treated as individual members, like {a,{b,c},d} */ function parseCommaParts(str) { - if (!str) { - return ['']; - } const parts = []; - const m = balanced('{', '}', str); - if (!m) { - return str.split(','); - } - const { pre, body, post } = m; - const p = pre.split(','); - p[p.length - 1] += '{' + body + '}'; - const postParts = parseCommaParts(post); - if (post.length) { - ; - p[p.length - 1] += postParts.shift(); - p.push.apply(p, postParts); + // Walk the brace groups iteratively. Recursing on `post` once per group let a + // chain of them exhaust the stack - the parsing-side counterpart to + // the `expand_` overflow fixed for CVE-2026-14257, and not something `max` or + // `maxLength` can bound, since it happens before expansion. + // + // The part the next chunk continues + let carry = ''; + for (;;) { + const m = balanced('{', '}', str); + if (!m) { + const tail = str.split(','); + tail[0] = carry + tail[0]; + pushAll(parts, tail); + return parts; + } + const { pre, body, post } = m; + const p = pre.split(','); + p[0] = carry + p[0]; + p[p.length - 1] += '{' + body + '}'; + if (!post.length) { + pushAll(parts, p); + return parts; + } + carry = p.pop(); + pushAll(parts, p); + str = post; } - parts.push.apply(parts, p); - return parts; } export function expand(str, options = {}) { if (!str) { return []; } - const { max = EXPANSION_MAX, maxLength = EXPANSION_MAX_LENGTH } = options; + const { max = EXPANSION_MAX, maxLength = EXPANSION_MAX_LENGTH, maxDepth = EXPANSION_MAX_DEPTH, maxRewrites = EXPANSION_MAX_REWRITES, } = options; // I don't know why Bash 4.3 does this, but it does. // Anything starting with {} will have the first two bytes preserved // but *only* at the top level, so {},a}b will not expand to anything, @@ -85,7 +118,7 @@ export function expand(str, options = {}) { if (str.slice(0, 2) === '{}') { str = '\\{\\}' + str.slice(2); } - return expand_(escapeBraces(str), max, maxLength, true).map(unescapeBraces); + return expand_(escapeBraces(str), max, maxLength, maxDepth, 0, maxRewrites, true).map(unescapeBraces); } function embrace(str) { return '{' + str + '}'; @@ -180,7 +213,13 @@ function expandSequence(body, isAlphaSequence, max, maxLength) { } return N; } -function expand_(str, max, maxLength, isTop) { +function expand_(str, max, maxLength, maxDepth, depth, maxRewrites, isTop) { + // Too deeply nested to keep following: treat the rest as literal, the same + // way a group that cannot expand is already handled. Truncating rather than + // throwing keeps `expand` total, matching `max` and `maxLength`. + if (depth > maxDepth) { + return [str]; + } // Consume the string's top-level brace groups left to right, threading a // running set of combined prefixes (`acc`). Expanding the tail iteratively - // rather than recursing on `m.post` once per group - keeps the native stack @@ -192,6 +231,9 @@ function expand_(str, max, maxLength, isTop) { // comma set - a sequence like `{a..\}` may legitimately yield ''. The drop // is on the final strings, so it is applied to whichever `combine` produces // them (the one with no brace set left in the tail). + // How many times the `{a},b}` rewrite below has restarted the scan. Each pass + // re-reads the whole string, so leaving this unbounded is quadratic. + let rewrites = 0; let dropEmpties = false; let firstGroup = true; for (;;) { @@ -216,7 +258,8 @@ function expand_(str, max, maxLength, isTop) { const isOptions = m.body.indexOf(',') >= 0; if (!isSequence && !isOptions) { // {a},b} - if (m.post.match(/,(?!,).*\}/)) { + if (rewrites < maxRewrites && m.post.match(/,(?!,).*\}/)) { + rewrites++; str = m.pre + '{' + m.body + escClose + m.post; isTop = true; continue; @@ -236,7 +279,7 @@ function expand_(str, max, maxLength, isTop) { let n = parseCommaParts(m.body); if (n.length === 1 && n[0] !== undefined) { // x{{a,b}}y ==> x{a}y x{b}y - n = expand_(n[0], max, maxLength, false).map(embrace); + n = expand_(n[0], max, maxLength, maxDepth, depth + 1, maxRewrites, false).map(embrace); //XXX is this necessary? Can't seem to hit it in tests. /* c8 ignore start */ if (n.length === 1) { @@ -262,12 +305,13 @@ function expand_(str, max, maxLength, isTop) { values = []; let valuesLength = 0; outer: for (let j = 0; j < n.length; j++) { - const expanded = expand_(n[j], max, maxLength, false); + const expanded = expand_(n[j], max, maxLength, maxDepth, depth + 1, maxRewrites, false); for (let k = 0; k < expanded.length; k++) { const v = expanded[k]; if (dropsEmpties && !v) continue; - if (values.length >= max || valuesLength + v.length > maxLength) { + if (values.length >= max || + valuesLength + v.length > maxLength) { break outer; } values.push(v); diff --git a/deps/npm/node_modules/brace-expansion/package.json b/deps/npm/node_modules/brace-expansion/package.json index 4376400796c9..a5c96ebe02f5 100644 --- a/deps/npm/node_modules/brace-expansion/package.json +++ b/deps/npm/node_modules/brace-expansion/package.json @@ -1,7 +1,7 @@ { "name": "brace-expansion", "description": "Brace expansion as known from sh/bash", - "version": "5.0.9", + "version": "5.0.12", "files": [ "dist" ], @@ -29,6 +29,7 @@ "test": "tap", "snap": "tap", "format": "prettier --write .", + "format:check": "prettier --check .", "benchmark": "node benchmark/index.js", "typedoc": "typedoc --tsconfig .tshy/esm.json ./src/*.ts" }, diff --git a/deps/npm/node_modules/http-cache-semantics/index.js b/deps/npm/node_modules/http-cache-semantics/index.js index f01e9a16b478..56c26025074c 100644 --- a/deps/npm/node_modules/http-cache-semantics/index.js +++ b/deps/npm/node_modules/http-cache-semantics/index.js @@ -334,12 +334,13 @@ module.exports = class CachePolicy { /** * @param {{headers: Record, synchronous: boolean}|undefined} revalidation - Revalidation information, if any. - * @returns {{response: {headers: Record}, revalidation: {headers: Record, synchronous: boolean}|undefined}} An object with a cached response headers and revalidation info. + * @returns {{response: HttpResponse, revalidation: {headers: Record, synchronous: boolean}|undefined}} An object with a cached response headers and revalidation info. */ _evaluateRequestHitResult(revalidation) { return { response: { headers: this.responseHeaders(), + status: this._status, }, revalidation, }; @@ -387,9 +388,9 @@ module.exports = class CachePolicy { * } * ``` * @param {HttpRequest} req - new incoming HTTP request - * @returns {{response: {headers: Record}|undefined, revalidation: {headers: Record, synchronous: boolean}|undefined}} An object containing keys: + * @returns {{response: HttpResponse|undefined, revalidation: {headers: Record, synchronous: boolean}|undefined}} An object containing keys: * - revalidation: { headers: Record, synchronous: boolean } Set if you should send this to the origin server - * - response: { headers: Record } Set if you can respond to the client with these cached headers + * - response: HttpResponse Set if you can respond to the client with these cached headers */ evaluateRequest(req) { this._assertRequestHasHeaders(req); @@ -484,18 +485,32 @@ module.exports = class CachePolicy { return true; } - // A Vary header field-value of "*" always fails to match - if (this._resHeaders.vary === '*') { - return false; - } - const fields = this._resHeaders.vary .trim() .toLowerCase() .split(/\s*,\s*/); + for (const name of fields) { - if (req.headers[name] !== this._reqHeaders[name]) return false; + // A Vary header field-value of "*" always fails to match + if (name === '*') { + return false; + } + + const newReq = Object.prototype.hasOwnProperty.call( + req.headers, + name + ) && req.headers[name]; + + const cachedReq = Object.prototype.hasOwnProperty.call( + this._reqHeaders, + name + ) && this._reqHeaders[name]; + + if (newReq !== cachedReq) { + return false; + } } + return true; } @@ -556,6 +571,14 @@ module.exports = class CachePolicy { return headers; } + /** + * Returns the status code of the cached response. + * @returns {number} The response status code. + */ + status() { + return this._status; + } + /** * Returns the Date header value from the response or the current time if invalid. * @returns {number} Timestamp (in milliseconds) representing the Date header or response time. diff --git a/deps/npm/node_modules/http-cache-semantics/package.json b/deps/npm/node_modules/http-cache-semantics/package.json index d45dfa1274e0..06a565ffb7e1 100644 --- a/deps/npm/node_modules/http-cache-semantics/package.json +++ b/deps/npm/node_modules/http-cache-semantics/package.json @@ -1,6 +1,6 @@ { "name": "http-cache-semantics", - "version": "4.2.0", + "version": "4.3.0", "description": "Parses Cache-Control and other headers. Helps building correct HTTP caches and proxies", "repository": { "type": "git", @@ -14,9 +14,9 @@ "files": [ "index.js" ], - "author": "Kornel Lesiński (https://kornel.ski/)", + "author": "Kornel Lesiński (https://kornel.ski/)", "license": "BSD-2-Clause", "devDependencies": { - "mocha": "^11.0" + "mocha": "^12.0" } } diff --git a/deps/npm/node_modules/ip-address/dist/common.js b/deps/npm/node_modules/ip-address/dist/common.js index 0c15d21e3a39..9c9ecf03ff2e 100644 --- a/deps/npm/node_modules/ip-address/dist/common.js +++ b/deps/npm/node_modules/ip-address/dist/common.js @@ -2,6 +2,8 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.isInSubnet = isInSubnet; exports.isHostInSubnet = isHostInSubnet; +exports.isGloballyReachable = isGloballyReachable; +exports.offsetBigInt = offsetBigInt; exports.isCorrect = isCorrect; exports.prefixLengthFromMask = prefixLengthFromMask; exports.assertByteArray = assertByteArray; @@ -36,10 +38,56 @@ function isInSubnet(address) { * {@link isInSubnet} when classifying a single address — notably when the * address came from untrusted input and the result backs a trust-boundary * decision such as an SSRF allow/deny filter. + * + * An address of one family is never inside a network of the other, so an + * `Address4` against an `Address6` (or the reverse) is `false`. To compare + * across families, convert first: `Address6.fromAddress4()`, `to4()`, or + * `toAddress4Nat64()`. */ function isHostInSubnet(address) { + // mask() is a bit string of the family's width, and the leading bits of a + // 32-bit string can coincide with those of a 128-bit one (a00::1 and + // 10.0.0.0/8 both mask to 00001010), so the widths must agree before the + // strings are compared. + if (this.binaryZeroPad().length !== address.binaryZeroPad().length) { + return false; + } return this.mask(address.subnetMask) === address.mask(); } +/** + * Returns whether the registry marks this address globally reachable: the + * answer of the most specific entry containing it that has one, or `true` + * when no entry contains it. + */ +function isGloballyReachable(entries) { + let best = null; + for (let i = 0; i < entries.length; i++) { + const entry = entries[i]; + if (entry.reachable !== null && + isHostInSubnet.call(this, entry.subnet) && + (best === null || entry.subnet.subnetMask > best.subnet.subnetMask)) { + best = entry; + } + } + return best === null ? true : best.reachable; +} +/** + * Adds `n` to `value` and returns the result, throwing `AddressError` unless + * `n` is an integer and the result stays within `[0, 2**bits - 1]`. + */ +function offsetBigInt(value, n, bits, family) { + if (typeof n === 'number' && !Number.isSafeInteger(n)) { + throw new address_error_1.AddressError(`${family} offset must be an integer`); + } + if (typeof n !== 'number' && typeof n !== 'bigint') { + throw new address_error_1.AddressError(`${family} offset must be an integer`); + } + const result = value + BigInt(n); + if (result < BigInt(0) || result > (BigInt(1) << BigInt(bits)) - BigInt(1)) { + throw new address_error_1.AddressError(`${family} offset leaves the address space`); + } + return result; +} function isCorrect(defaultBits) { return function isCorrectForm() { if (this.addressMinusSuffix !== this.correctForm()) { @@ -97,10 +145,10 @@ function stringToPaddedHex(numberString) { */ function testBit(binaryValue, position) { const { length } = binaryValue; - if (position > length) { + if (position >= length) { return false; } - const positionInString = length - position; + const positionInString = length - position - 1; return binaryValue.substring(positionInString, positionInString + 1) === '1'; } //# sourceMappingURL=common.js.map \ No newline at end of file diff --git a/deps/npm/node_modules/ip-address/dist/ipv4.js b/deps/npm/node_modules/ip-address/dist/ipv4.js index 1360e1836a0d..4b9cf98b6084 100644 --- a/deps/npm/node_modules/ip-address/dist/ipv4.js +++ b/deps/npm/node_modules/ip-address/dist/ipv4.js @@ -48,7 +48,9 @@ class Address4 { */ this.isCorrect = isCorrect4; /** - * Returns true if the given address is in the subnet of the current address + * Returns true if the given address is in the subnet of the current address. + * An `Address6` is never in the subnet of an `Address4`; convert with + * `to4()` or `Address6.fromAddress4()` to compare across families. * @returns {boolean} */ this.isInSubnet = common.isInSubnet; @@ -58,6 +60,8 @@ class Address4 { * when classifying a single address, so the answer doesn't change with the * CIDR suffix the caller happened to write — notably when the address came * from untrusted input and the result backs a trust-boundary decision. + * An `Address6` is never in the subnet of an `Address4`; convert with + * `to4()` or `Address6.fromAddress4()` to compare across families. * @returns {boolean} */ this.isHostInSubnet = common.isHostInSubnet; @@ -72,6 +76,13 @@ class Address4 { } address = address.replace(constants.RE_SUBNET_STRING, ''); } + // Four three-digit octets and three dots: the longest well-formed address + // is 15 characters. Longer input is rejected before parsing, as Address6 + // does at its own limit. + const longest = constants.GROUPS * 4 - 1; + if (address.length > longest) { + throw new address_error_1.AddressError(`IPv4 addresses are at most ${longest} characters.`); + } this.addressMinusSuffix = address; this.parsedAddress = this.parse(address); } @@ -213,16 +224,27 @@ class Address4 { return Address4.fromHex(integer.toString(16).padStart(8, '0')); } /** - * Return an address from in-addr.arpa form + * Return an address from in-addr.arpa form: the four octets reversed, with + * or without the `.in-addr.arpa` suffix and root dot, in any case. Throws + * `AddressError` unless the reversed labels form a valid IPv4 address, so + * `fromArpa(x.reverseForm())` round-trips {@link reverseForm}. * @param {string} arpaFormAddress - an 'in-addr.arpa' form ipv4 address * @returns {Adress4} * @example - * var address = Address4.fromArpa(42.2.0.192.in-addr.arpa.) + * var address = Address4.fromArpa('42.2.0.192.in-addr.arpa.') * address.correctForm(); // '192.0.2.42' */ static fromArpa(arpaFormAddress) { - // remove ending ".in-addr.arpa." or just "." - const leader = arpaFormAddress.replace(/(\.in-addr\.arpa)?\.$/, ''); + // A 15-character address, a "/32" prefix length and ".in-addr.arpa.": the + // longest name is 32 characters. Longer input is rejected before its + // labels are split, as the constructor does at its own limit. + const longest = constants.GROUPS * 4 - 1 + '/32'.length + '.in-addr.arpa.'.length; + if (arpaFormAddress.length > longest) { + throw new address_error_1.AddressError(`in-addr.arpa names are at most ${longest} characters.`); + } + // remove an ending ".in-addr.arpa", in any case and with or without the + // root dot, as Address6.fromArpa does for ".ip6.arpa" + const leader = arpaFormAddress.replace(/(\.in-addr\.arpa)?\.?$/i, ''); const address = leader.split('.').reverse().join('.'); return new Address4(address); } @@ -285,6 +307,33 @@ class Address4 { const adjust = BigInt('1'); return Address4.fromBigInt(this._startAddress() + adjust); } + /** + * Returns the address `n` addresses after this one (or before, when `n` is + * negative), keeping this address's subnet mask. Throws `AddressError` when + * the result would fall outside the IPv4 address space or `n` is not an + * integer. + * @param {number | bigint} n + * @returns {Address4} + * @example + * new Address4('10.0.0.0/24').offset(1).correctForm(); // '10.0.0.1' + */ + offset(n) { + return Address4.fromBigInt(common.offsetBigInt(this.bigInt(), n, constants.BITS, 'IPv4')).withSubnetMask(this.subnetMask); + } + /** + * Returns the network that follows this address's network: the address after + * {@link endAddress}, with the same subnet mask. Throws `AddressError` when + * this network is the last one in the address space. + * @returns {Address4} + * @example + * new Address4('10.0.0.0/24').nextNetwork().networkForm(); // '10.0.1.0/24' + */ + nextNetwork() { + return Address4.fromBigInt(common.offsetBigInt(this._endAddress(), 1, constants.BITS, 'IPv4')).withSubnetMask(this.subnetMask); + } + withSubnetMask(subnetMask) { + return new Address4(`${this.correctForm()}/${subnetMask}`); + } /** * Helper function getting end address. * @returns {bigint} @@ -462,6 +511,43 @@ class Address4 { isCGNAT() { return this.isHostInSubnet(CGNAT_V4); } + /** + * Returns true if the address is in one of the documentation ranges + * `192.0.2.0/24`, `198.51.100.0/24`, or `203.0.113.0/24` ([RFC 5737](https://datatracker.ietf.org/doc/html/rfc5737)). + * @returns {boolean} + */ + isDocumentation() { + return DOCUMENTATION_V4.some((subnet) => this.isHostInSubnet(subnet)); + } + /** + * Returns true if the address is in the benchmarking range `198.18.0.0/15` ([RFC 2544](https://datatracker.ietf.org/doc/html/rfc2544)). + * @returns {boolean} + */ + isBenchmarking() { + return this.isHostInSubnet(BENCHMARKING_V4); + } + /** + * Returns true if the address is in the reserved range `240.0.0.0/4` ([RFC 1112](https://datatracker.ietf.org/doc/html/rfc1112)), + * which includes the limited broadcast address. + * @returns {boolean} + */ + isReserved() { + return this.isHostInSubnet(RESERVED_V4); + } + /** + * Returns true if the address is globally reachable: not multicast, and not + * in any block the [IANA IPv4 Special-Purpose Address Registry](https://www.iana.org/assignments/iana-ipv4-special-registry/) + * marks as not globally reachable. That covers everything the individual + * classifiers name (private, loopback, link-local, CGNAT, unspecified, + * broadcast, documentation, benchmarking, reserved) and the blocks they do + * not, such as `0.0.0.0/8` and the IETF protocol assignments in + * `192.0.0.0/24`. This is the single predicate to use where a request must + * not reach an internal or special-purpose destination; see SECURITY.md. + * @returns {boolean} + */ + isGlobal() { + return !this.isMulticast() && common.isGloballyReachable.call(this, SPECIAL_PURPOSE_V4); + } /** * Returns a zero-padded base-2 string representation of the address * @returns {string} @@ -502,4 +588,15 @@ const LINK_LOCAL_V4 = new Address4('169.254.0.0/16'); const UNSPECIFIED_V4 = new Address4('0.0.0.0/32'); const BROADCAST_V4 = new Address4('255.255.255.255/32'); const CGNAT_V4 = new Address4('100.64.0.0/10'); +const DOCUMENTATION_V4 = [ + new Address4('192.0.2.0/24'), + new Address4('198.51.100.0/24'), + new Address4('203.0.113.0/24'), +]; +const BENCHMARKING_V4 = new Address4('198.18.0.0/15'); +const RESERVED_V4 = new Address4('240.0.0.0/4'); +const SPECIAL_PURPOSE_V4 = constants.SPECIAL_PURPOSE.map(([cidr, , reachable]) => ({ + subnet: new Address4(cidr), + reachable, +})); //# sourceMappingURL=ipv4.js.map \ No newline at end of file diff --git a/deps/npm/node_modules/ip-address/dist/ipv6.js b/deps/npm/node_modules/ip-address/dist/ipv6.js index d5f4fdb9c87a..e1c840c02b9a 100644 --- a/deps/npm/node_modules/ip-address/dist/ipv6.js +++ b/deps/npm/node_modules/ip-address/dist/ipv6.js @@ -92,7 +92,9 @@ class Address6 { this.zone = ''; // #region Attributes /** - * Returns true if the given address is in the subnet of the current address + * Returns true if the given address is in the subnet of the current address. + * An `Address4` is never in the subnet of an `Address6`; convert with + * `Address6.fromAddress4()` or `to4()` to compare across families. * @returns {boolean} */ this.isInSubnet = common.isInSubnet; @@ -102,6 +104,8 @@ class Address6 { * when classifying a single address, so the answer doesn't change with the * CIDR suffix the caller happened to write — notably when the address came * from untrusted input and the result backs a trust-boundary decision. + * An `Address4` is never in the subnet of an `Address6`; convert with + * `Address6.fromAddress4()` or `to4()` to compare across families. * @returns {boolean} */ this.isHostInSubnet = common.isHostInSubnet; @@ -135,10 +139,23 @@ class Address6 { if (/\//.test(address)) { throw new address_error_1.AddressError('Invalid subnet mask.'); } - const zone = constants6.RE_ZONE_STRING.exec(address); - if (zone) { - this.zone = zone[0]; - address = address.replace(constants6.RE_ZONE_STRING, ''); + // A zone runs from the first `%` to the end of the input and holds no line + // terminator. Finding it by index reads the input once, where matching + // RE_ZONE_STRING retries from every `%` that a line terminator disqualifies. + const zoneStart = address.indexOf('%'); + if (zoneStart !== -1 && /^.*$/.test(address.slice(zoneStart))) { + this.zone = address.slice(zoneStart); + address = address.slice(0, zoneStart); + } + // The longest well-formed address is all but the last two groups written + // as four hex digits with their colons, then a 15-character dotted quad: + // 5 * (groups - 2) + 15, which is 45 for eight groups, the same line + // CPython's ipaddress module draws. Rejecting longer input here keeps the + // parse diagnostics, which wrap every offending character in a span, + // proportional to an address rather than to whatever was passed in. + const longest = this.groups * 5 + 5; + if (address.length > longest) { + throw new address_error_1.AddressError(`IPv6 addresses are at most ${longest} characters.`); } this.addressMinusSuffix = address; this.parsedAddress = this.parse(this.addressMinusSuffix); @@ -339,28 +356,32 @@ class Address6 { return new Address6(`::ffff:${address4.correctForm()}/${mask6}`); } /** - * Return an address from ip6.arpa form + * Return an address from ip6.arpa form. A full 32-nibble name gives a /128 + * address; a shorter name, as used for a delegated reverse zone, gives the + * network it covers, with a subnet mask of four bits per nibble, so + * `fromArpa(x.reverseForm())` round-trips {@link reverseForm} for any prefix. * @param {string} arpaFormAddress - an 'ip6.arpa' form address * @returns {Adress6} * @example * var address = Address6.fromArpa(e.f.f.f.3.c.2.6.f.f.f.e.6.6.8.e.1.0.6.7.9.4.e.c.0.0.0.0.1.0.0.2.ip6.arpa.) * address.correctForm(); // '2001:0:ce49:7601:e866:efff:62c3:fffe' + * Address6.fromArpa('8.b.d.0.1.0.0.2.ip6.arpa.').networkForm(); // '2001:db8::/32' */ static fromArpa(arpaFormAddress) { - // remove ending ".ip6.arpa." or just "." - let address = arpaFormAddress.replace(/(\.ip6\.arpa)?\.$/, ''); - const semicolonAmount = 7; - // correct ip6.arpa form with ending removed will be 63 characters - if (address.length !== 63) { + // remove an ending ".ip6.arpa", in any case and with or without the root + // dot + const nibbles = arpaFormAddress.replace(/(\.ip6\.arpa)?\.?$/i, ''); + if (!/^[0-9a-f](\.[0-9a-f]){0,31}$/i.test(nibbles)) { throw new address_error_1.AddressError("Invalid 'ip6.arpa' form."); } - const parts = address.split('.').reverse(); - for (let i = semicolonAmount; i > 0; i--) { - const insertIndex = i * 4; - parts.splice(insertIndex, 0, ':'); + const reversed = nibbles.split('.').reverse(); + const subnetMask = reversed.length * 4; + const hex = reversed.join('').padEnd(32, '0'); + const groups = []; + for (let i = 0; i < constants6.GROUPS; i++) { + groups.push(hex.slice(i * 4, (i + 1) * 4)); } - address = parts.join(''); - return new Address6(address); + return new Address6(`${groups.join(':')}/${subnetMask}`); } /** * Return the Microsoft UNC transcription of the address @@ -424,22 +445,53 @@ class Address6 { return BigInt(`0b${this.mask() + '1'.repeat(constants6.BITS - this.subnetMask)}`); } /** - * The last address in the range given by this address' subnet - * Often referred to as the Broadcast + * The last address in the range given by this address's subnet. IPv6 has + * no broadcast address, so this is an ordinary assignable address (in a + * 64-bit-interface-identifier subnet it falls inside the reserved + * subnet-anycast block of [RFC 2526](https://datatracker.ietf.org/doc/html/rfc2526)). * @returns {Address6} */ endAddress() { return Address6.fromBigInt(this._endAddress()); } /** - * The last host address in the range given by this address's subnet ie - * the last address prior to the Broadcast Address + * The address one before {@link endAddress}. This is the IPv6 counterpart + * of the IPv4 method that skips the broadcast address; IPv6 has no broadcast, + * so it drops exactly one address and does not model the 128 reserved + * subnet-anycast identifiers of [RFC 2526](https://datatracker.ietf.org/doc/html/rfc2526). * @returns {Address6} */ endAddressExclusive() { const adjust = BigInt('1'); return Address6.fromBigInt(this._endAddress() - adjust); } + /** + * Returns the address `n` addresses after this one (or before, when `n` is + * negative), keeping this address's subnet mask. Throws `AddressError` when + * the result would fall outside the IPv6 address space or `n` is not an + * integer. + * @param {number | bigint} n + * @returns {Address6} + * @example + * new Address6('2001:db8::/64').offset(1).correctForm(); // '2001:db8::1' + */ + offset(n) { + return Address6.fromBigInt(common.offsetBigInt(this.bigInt(), n, constants6.BITS, 'IPv6')).withSubnetMask(this.subnetMask); + } + /** + * Returns the network that follows this address's network: the address after + * {@link endAddress}, with the same subnet mask. Throws `AddressError` when + * this network is the last one in the address space. + * @returns {Address6} + * @example + * new Address6('2001:db8::/64').nextNetwork().networkForm(); // '2001:db8:0:1::/64' + */ + nextNetwork() { + return Address6.fromBigInt(common.offsetBigInt(this._endAddress(), 1, constants6.BITS, 'IPv6')).withSubnetMask(this.subnetMask); + } + withSubnetMask(subnetMask) { + return new Address6(`${this.correctForm()}/${subnetMask}`); + } /** * The hex form of the subnet mask, e.g. `ffff:ffff:ffff:ffff::` for a * `/64`. Returns an `Address6`; call `.correctForm()` for the string. @@ -1022,7 +1074,10 @@ class Address6 { return this.addressMinusSuffix === this.canonicalForm(); } /** - * Returns true if the address is a link local address, false otherwise + * Returns true if the address is a link-local unicast address in `fe80::/10` + * ([RFC 4291 §2.4](https://datatracker.ietf.org/doc/html/rfc4291#section-2.4)) + * or an IPv4-mapped / NAT64 address whose embedded IPv4 address is link-local + * (`169.254.0.0/16`, e.g. `::ffff:169.254.169.254`), false otherwise. * @returns {boolean} */ isLinkLocal() { @@ -1030,12 +1085,7 @@ class Address6 { if (embedded) { return embedded.isLinkLocal(); } - // Zeroes are required, i.e. we can't check isHostInSubnet with 'fe80::/10' - if (this.getBitsBase2(0, 64) === - '1111111010000000000000000000000000000000000000000000000000000000') { - return true; - } - return false; + return this.isHostInSubnet(LINK_LOCAL_SUBNET); } /** * Returns true if the address is a multicast address, false otherwise @@ -1127,12 +1177,20 @@ class Address6 { } /** * Returns true if the address is private, i.e. a Unique Local Address in - * `fc00::/7` ([RFC 4193](https://datatracker.ietf.org/doc/html/rfc4193)) or an - * IPv4-mapped / NAT64 address whose embedded IPv4 address is in one of the - * [RFC 1918](https://datatracker.ietf.org/doc/html/rfc1918) private ranges - * (e.g. `::ffff:10.0.0.1`). This is the IPv6 counterpart to + * `fc00::/7` ([RFC 4193](https://datatracker.ietf.org/doc/html/rfc4193)), an + * address in the NAT64 local-use range `64:ff9b:1::/48` + * ([RFC 8215](https://datatracker.ietf.org/doc/html/rfc8215)), or an + * IPv4-mapped / NAT64 well-known address whose embedded IPv4 address is in + * one of the [RFC 1918](https://datatracker.ietf.org/doc/html/rfc1918) + * private ranges (e.g. `::ffff:10.0.0.1`). This is the IPv6 counterpart to * {@link Address4.isPrivate}; use it instead of {@link isULA} when you need to * catch mapped RFC 1918 addresses as well as native ULAs. + * + * The local-use NAT64 range is reported private as a whole rather than by + * its embedded IPv4 address: an operator may carve a prefix of any RFC 6052 + * length out of `64:ff9b:1::/48`, so the same bits decode to different IPv4 + * addresses under different deployments and no single decoding is correct. + * Use {@link toAddress4Nat64} with the deployment's prefix to decode one. * @returns {boolean} */ isPrivate() { @@ -1140,7 +1198,7 @@ class Address6 { if (embedded) { return embedded.isPrivate(); } - return this.isULA(); + return this.isULA() || this.isHostInSubnet(NAT64_LOCAL_USE_SUBNET); } /** * Returns true if the address is an IPv4-mapped / NAT64 address whose embedded @@ -1188,7 +1246,49 @@ class Address6 { * @returns {boolean} */ isDocumentation() { - return this.isHostInSubnet(DOCUMENTATION_SUBNET); + return DOCUMENTATION_SUBNETS.some((subnet) => this.isHostInSubnet(subnet)); + } + /** + * Returns true if the address is in the benchmarking range `2001:2::/48` + * ([RFC 5180](https://datatracker.ietf.org/doc/html/rfc5180)) or is an + * IPv4-mapped / NAT64 address whose embedded IPv4 address is in + * `198.18.0.0/15`, false otherwise. + * @returns {boolean} + */ + isBenchmarking() { + const embedded = this.embeddedIPv4(); + if (embedded) { + return embedded.isBenchmarking(); + } + return this.isHostInSubnet(BENCHMARKING_SUBNET); + } + /** + * Returns true if the address is globally reachable: inside the global + * unicast allocation `2000::/3` (the only range the [IANA IPv6 Address Space + * Registry](https://www.iana.org/assignments/ipv6-address-space/) assigns + * for global unicast; everything else is reserved, ULA, link-local, or + * multicast) and not in any block the [IANA IPv6 Special-Purpose Address Registry](https://www.iana.org/assignments/iana-ipv6-special-registry/) + * marks as not globally reachable. An IPv4-mapped or NAT64 well-known + * address answers for its embedded IPv4 address, so `::ffff:10.0.0.1` and + * `64:ff9b::7f00:1` are not global. Teredo (`2001::/32`) and 6to4 + * (`2002::/16`) are not global either: the registry lists them as N/A and a + * packet to one needs a relay. + * + * This covers everything the individual classifiers name and the blocks they + * do not: the discard-only prefix `100::/64`, the IETF protocol assignments + * in `2001::/23`, the deprecated site-local `fec0::/10` and IPv4-compatible + * `::/96` ranges, and unallocated space such as `4000::/3`. It is the single + * predicate to use where a request must not reach an internal or + * special-purpose destination; see SECURITY.md. + * @returns {boolean} + */ + isGlobal() { + const embedded = this.embeddedIPv4(); + if (embedded) { + return embedded.isGlobal(); + } + return (this.isHostInSubnet(GLOBAL_UNICAST_SUBNET) && + common.isGloballyReachable.call(this, SPECIAL_PURPOSE_V6)); } // #endregion // #region HTML @@ -1347,7 +1447,15 @@ const TYPE_SUBNETS = Object.keys(constants6.TYPES).map((subnet) => [ const TEREDO_SUBNET = new Address6('2001::/32'); const SIX_TO_FOUR_SUBNET = new Address6('2002::/16'); const ULA_SUBNET = new Address6('fc00::/7'); -const DOCUMENTATION_SUBNET = new Address6('2001:db8::/32'); +const LINK_LOCAL_SUBNET = new Address6('fe80::/10'); +const DOCUMENTATION_SUBNETS = [new Address6('2001:db8::/32'), new Address6('3fff::/20')]; +const BENCHMARKING_SUBNET = new Address6('2001:2::/48'); +const GLOBAL_UNICAST_SUBNET = new Address6('2000::/3'); +const SPECIAL_PURPOSE_V6 = constants6.SPECIAL_PURPOSE.map(([cidr, , reachable]) => ({ + subnet: new Address6(cidr), + reachable, +})); const IPV4_MAPPED_SUBNET = new Address6('::ffff:0:0/96'); const NAT64_WELL_KNOWN_SUBNET = new Address6('64:ff9b::/96'); +const NAT64_LOCAL_USE_SUBNET = new Address6('64:ff9b:1::/48'); //# sourceMappingURL=ipv6.js.map \ No newline at end of file diff --git a/deps/npm/node_modules/ip-address/dist/v4/constants.js b/deps/npm/node_modules/ip-address/dist/v4/constants.js index 158288b7de65..f25120d60687 100644 --- a/deps/npm/node_modules/ip-address/dist/v4/constants.js +++ b/deps/npm/node_modules/ip-address/dist/v4/constants.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.RE_SUBNET_STRING = exports.RE_ADDRESS = exports.GROUPS = exports.BITS = void 0; +exports.SPECIAL_PURPOSE = exports.RE_SUBNET_STRING = exports.RE_ADDRESS = exports.GROUPS = exports.BITS = void 0; exports.BITS = 32; exports.GROUPS = 4; // Each octet is 0-255 written without a leading zero. A leading zero is @@ -9,4 +9,43 @@ exports.GROUPS = 4; // disagree with the network stack about which host a string names. exports.RE_ADDRESS = /^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$/g; exports.RE_SUBNET_STRING = /\/\d{1,2}$/; +/** + * The IANA IPv4 Special-Purpose Address Registry + * (https://www.iana.org/assignments/iana-ipv4-special-registry/), one entry + * per block: `[cidr, name, globallyReachable]`. A `null` reachability means + * the registry leaves the column blank and the block inherits the answer of + * the block containing it (or is global when nothing contains it). + * + * `Address4.isGlobal()` answers from the most specific entry containing the + * address. `test/data/iana-corpus.json` is generated from the registry's CSV + * and pins this table to it. + */ +exports.SPECIAL_PURPOSE = [ + ['0.0.0.0/8', 'This network', false], + ['0.0.0.0/32', 'This host on this network', false], + ['10.0.0.0/8', 'Private-Use', false], + ['100.64.0.0/10', 'Shared Address Space', false], + ['127.0.0.0/8', 'Loopback', false], + ['169.254.0.0/16', 'Link Local', false], + ['172.16.0.0/12', 'Private-Use', false], + ['192.0.0.0/24', 'IETF Protocol Assignments', false], + ['192.0.0.0/29', 'IPv4 Service Continuity Prefix', false], + ['192.0.0.8/32', 'IPv4 dummy address', false], + ['192.0.0.9/32', 'Port Control Protocol Anycast', true], + ['192.0.0.10/32', 'Traversal Using Relays around NAT Anycast', true], + ['192.0.0.170/32', 'NAT64/DNS64 Discovery', false], + ['192.0.0.171/32', 'NAT64/DNS64 Discovery', false], + ['192.0.2.0/24', 'Documentation (TEST-NET-1)', false], + ['192.31.196.0/24', 'AS112-v4', true], + ['192.52.193.0/24', 'AMT', true], + ['192.88.99.0/24', 'Deprecated (6to4 Relay Anycast)', null], + ['192.88.99.2/32', '6a44-relay anycast address', false], + ['192.168.0.0/16', 'Private-Use', false], + ['192.175.48.0/24', 'Direct Delegation AS112 Service', true], + ['198.18.0.0/15', 'Benchmarking', false], + ['198.51.100.0/24', 'Documentation (TEST-NET-2)', false], + ['203.0.113.0/24', 'Documentation (TEST-NET-3)', false], + ['240.0.0.0/4', 'Reserved', false], + ['255.255.255.255/32', 'Limited Broadcast', false], +]; //# sourceMappingURL=constants.js.map \ No newline at end of file diff --git a/deps/npm/node_modules/ip-address/dist/v6/constants.js b/deps/npm/node_modules/ip-address/dist/v6/constants.js index 4616cad66b6a..777146c1a1aa 100644 --- a/deps/npm/node_modules/ip-address/dist/v6/constants.js +++ b/deps/npm/node_modules/ip-address/dist/v6/constants.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.RE_URL_WITH_PORT = exports.RE_URL = exports.RE_ZONE_STRING = exports.RE_SUBNET_STRING = exports.RE_BAD_ADDRESS = exports.RE_BAD_CHARACTERS = exports.TYPES = exports.SCOPES = exports.GROUPS = exports.BITS = void 0; +exports.SPECIAL_PURPOSE = exports.RE_URL_WITH_PORT = exports.RE_URL = exports.RE_ZONE_STRING = exports.RE_SUBNET_STRING = exports.RE_BAD_ADDRESS = exports.RE_BAD_CHARACTERS = exports.TYPES = exports.SCOPES = exports.GROUPS = exports.BITS = void 0; exports.BITS = 128; exports.GROUPS = 8; /** @@ -48,8 +48,14 @@ exports.TYPES = { 'ff00::/8': 'Multicast', 'fe80::/10': 'Link-local unicast', 'fc00::/7': 'Unique local', + '2001::/32': 'Teredo', + '2001:2::/48': 'Benchmarking', '2002::/16': '6to4', '2001:db8::/32': 'Documentation', + '3fff::/20': 'Documentation', + '100::/64': 'Discard-only', + 'fec0::/10': 'Site-local unicast (deprecated)', + '::/96': 'IPv4-compatible (deprecated)', '64:ff9b::/96': 'NAT64 (well-known)', '64:ff9b:1::/48': 'NAT64 (local-use)', }; @@ -75,8 +81,51 @@ exports.RE_SUBNET_STRING = /\/\d{1,3}(?=%|$)/; * A regular expression that matches an IPv6 zone * @memberof Address6 * @static + * @deprecated Takes quadratic time on input holding many `%` characters + * followed by a line terminator. The library finds the zone by index, as + * `Address6`'s constructor shows, and 11.0.0 removes this export. */ exports.RE_ZONE_STRING = /%.*$/; exports.RE_URL = /^(?:\[([0-9a-f:.]+)\]|([0-9a-f:.]+))(?:[/?#].*)?$/i; exports.RE_URL_WITH_PORT = /^\[([0-9a-f:.]+)\]:([0-9]{1,5})(?:[/?#].*)?$/i; +/** + * The IANA IPv6 Special-Purpose Address Registry + * (https://www.iana.org/assignments/iana-ipv6-special-registry/), one entry + * per block: `[cidr, name, globallyReachable]`. A `null` reachability means + * the registry says N/A or leaves the column blank; N/A blocks (Teredo, 6to4) + * are treated as not globally reachable, since a packet to one needs a relay, + * and blank blocks inherit the answer of the block containing them. + * + * `Address6.isGlobal()` answers from the most specific entry containing the + * address, after delegating IPv4-mapped and NAT64 well-known addresses to the + * embedded IPv4 address. `test/data/iana-corpus.json` is generated from the + * registry's CSV and pins this table to it. + */ +exports.SPECIAL_PURPOSE = [ + ['::1/128', 'Loopback Address', false], + ['::/128', 'Unspecified Address', false], + ['::ffff:0:0/96', 'IPv4-mapped Address', false], + ['64:ff9b::/96', 'IPv4-IPv6 Translat.', true], + ['64:ff9b:1::/48', 'IPv4-IPv6 Translat.', false], + ['100::/64', 'Discard-Only Address Block', false], + ['100:0:0:1::/64', 'Dummy IPv6 Prefix', false], + ['2001::/23', 'IETF Protocol Assignments', false], + ['2001::/32', 'TEREDO', false], + ['2001:1::1/128', 'Port Control Protocol Anycast', true], + ['2001:1::2/128', 'Traversal Using Relays around NAT Anycast', true], + ['2001:1::3/128', 'DNS-SD Service Registration Protocol Anycast', true], + ['2001:2::/48', 'Benchmarking', false], + ['2001:3::/32', 'AMT', true], + ['2001:4:112::/48', 'AS112-v6', true], + ['2001:10::/28', 'Deprecated (previously ORCHID)', null], + ['2001:20::/28', 'ORCHIDv2', true], + ['2001:30::/28', 'Drone Remote ID Protocol Entity Tags (DETs) Prefix', true], + ['2001:db8::/32', 'Documentation', false], + ['2002::/16', '6to4', false], + ['2620:4f:8000::/48', 'Direct Delegation AS112 Service', true], + ['3fff::/20', 'Documentation', false], + ['5f00::/16', 'Segment Routing (SRv6) SIDs', false], + ['fc00::/7', 'Unique-Local', false], + ['fe80::/10', 'Link-Local Unicast', false], +]; //# sourceMappingURL=constants.js.map \ No newline at end of file diff --git a/deps/npm/node_modules/ip-address/package.json b/deps/npm/node_modules/ip-address/package.json index 6ea2d24bd62b..ee0bdeca6196 100644 --- a/deps/npm/node_modules/ip-address/package.json +++ b/deps/npm/node_modules/ip-address/package.json @@ -16,13 +16,14 @@ "bigint", "browser" ], - "version": "10.5.0", + "version": "10.7.5", "author": "Beau Gunderson (https://beaugunderson.com/)", "license": "MIT", "main": "dist/ip-address.js", "types": "dist/ip-address.d.ts", "scripts": { "docs": "tsx scripts/build-readme.ts", + "docs:check": "tsx scripts/build-readme.ts --check", "build": "rm -rf dist; mkdir dist; tsc", "prepack": "npm run docs && npm run build", "prepare": "git config core.hooksPath hooks || true", @@ -79,7 +80,7 @@ "eslint-plugin-import": "^2.32.0", "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-sort-imports-es6-autofix": "^0.6.0", - "mocha": "^11.7.5", + "mocha": "^12.0.3", "monocart-coverage-reports": "^2.12.11", "prettier": "^3.8.3", "source-map-support": "^0.5.21", diff --git a/deps/npm/node_modules/libnpmdiff/package.json b/deps/npm/node_modules/libnpmdiff/package.json index f9836046f44b..c94bf2cfd549 100644 --- a/deps/npm/node_modules/libnpmdiff/package.json +++ b/deps/npm/node_modules/libnpmdiff/package.json @@ -1,6 +1,6 @@ { "name": "libnpmdiff", - "version": "8.1.13", + "version": "8.1.14", "description": "The registry diff", "repository": { "type": "git", @@ -47,7 +47,7 @@ "tap": "^16.3.8" }, "dependencies": { - "@npmcli/arborist": "^9.9.2", + "@npmcli/arborist": "^9.9.3", "@npmcli/installed-package-contents": "^4.0.0", "binary-extensions": "^3.0.0", "diff": "^8.0.2", diff --git a/deps/npm/node_modules/libnpmexec/README.md b/deps/npm/node_modules/libnpmexec/README.md index 84512ac59049..59c35f8a6394 100644 --- a/deps/npm/node_modules/libnpmexec/README.md +++ b/deps/npm/node_modules/libnpmexec/README.md @@ -28,7 +28,7 @@ await libexec({ - `opts`: - `args`: List of pkgs to execute **Array**, defaults to `[]` - - `call`: An alternative command to run when using `packages` option **String**, defaults to empty string. + - `call`: An alternative shell script to run when using `packages` option **String**, defaults to empty string. Unlike an executable selected from `args` or a package's `bin`, this is intentionally interpreted as shell syntax. - `cache`: The path location to where the npm cache folder is placed **String** - `npxCache`: The path location to where the npx cache folder is placed **String** - `chalk`: Chalk instance to use for colors? **Required** @@ -42,6 +42,14 @@ await libexec({ - `yes`: Should skip download confirmation prompt when fetching missing packages from the registry? **Boolean** - `registry`, `cache`, and more options that are forwarded to [@npmcli/arborist](https://github.com/npm/cli/blob/latest/workspaces/arborist/README.md) and [pacote](https://github.com/npm/pacote/#options) **Object** + Executable names are escaped for the selected shell, including POSIX shells and + PowerShell used on Windows. For inferred PowerShell invocations, arguments are + also escaped as literal strings. With `cmd.exe`, executable names containing double + quotes, percent signs, exclamation marks, or control characters are rejected + with `EINVALIDCOMMAND`. Use a bin name without these characters. On a matching + npx-cache hit, command selection uses the installed package's `bin` rather than + the registry's bin metadata. + ## LICENSE [ISC](./LICENSE) diff --git a/deps/npm/node_modules/libnpmexec/lib/index.js b/deps/npm/node_modules/libnpmexec/lib/index.js index 4d7c12665468..56dcf7c73694 100644 --- a/deps/npm/node_modules/libnpmexec/lib/index.js +++ b/deps/npm/node_modules/libnpmexec/lib/index.js @@ -267,8 +267,8 @@ const exec = async (opts) => { }) const lockPath = join(installDir, 'concurrency.lock') const npxTree = await withLock(lockPath, () => npxArb.loadActual()) - await Promise.all(needInstall.map(async ({ spec }) => { - const { manifest } = await missingFromTree({ + await Promise.all(needInstall.map(async ({ spec, manifest: requestedManifest }) => { + const { manifest, node } = await missingFromTree({ spec, tree: npxTree, flatOptions, @@ -281,6 +281,10 @@ const exec = async (opts) => { } else { add.push(manifest._id) } + } else if (needPackageCommandSwap && commandManifest === requestedManifest) { + // A cache hit must use the installed bin, not mutable registry metadata. + commandManifest = node.package + args[0] = getBinFromManifest(commandManifest) } })) diff --git a/deps/npm/node_modules/libnpmexec/lib/run-script.js b/deps/npm/node_modules/libnpmexec/lib/run-script.js index 2de40454f9dc..a7ce6915c74e 100644 --- a/deps/npm/node_modules/libnpmexec/lib/run-script.js +++ b/deps/npm/node_modules/libnpmexec/lib/run-script.js @@ -3,7 +3,10 @@ const runScript = require('@npmcli/run-script') const pkgJson = require('@npmcli/package-json') const { log, output } = require('proc-log') const noTTY = require('./no-tty.js') -const isWindowsShell = require('./is-windows.js') +const isWindows = require('./is-windows.js') + +const isCmd = shell => /(?:^|[\\/])cmd(?:\.exe)?$/i.test(shell) +const isPowerShell = shell => /(?:^|[\\/])(?:powershell|pwsh)(?:\.exe)?$/i.test(shell) const run = async ({ args, @@ -15,10 +18,24 @@ const run = async ({ runPath, scriptShell, }) => { - // escape executable path - // necessary for preventing bash/cmd keywords from overriding - if (!isWindowsShell) { - if (args.length > 0) { + if (!call && args.length > 0) { + const shell = scriptShell || (isWindows ? process.env.ComSpec || 'cmd' : 'sh') + if (isCmd(shell)) { + // Variable expansion (including delayed expansion) and embedded quotes + // cannot be safely escaped here. Control characters are invalid filenames. + if (/["%!]/.test(args[0]) || [...args[0]].some(c => c.charCodeAt(0) < 32)) { + throw Object.assign( + new Error(`Invalid executable name for cmd.exe: ${JSON.stringify(args[0])}`), + { code: 'EINVALIDCOMMAND' } + ) + } + // Protect both cmd.exe's metacharacter parsing and executable-name parsing. + args[0] = `"${args[0]}"`.replace(/[ ^&()<>|";,*?=@]/g, '^$&') + } else if (isPowerShell(shell)) { + // Encode the whole invocation to avoid promise-spawn's POSIX argument escaping. + const quotedArgs = args.map(arg => `'${arg.replace(/['\u2018-\u201b]/g, '$&$&')}'`) + args = [`& ${quotedArgs.join(' ')}`] + } else { // single-quote so shell metacharacters in the executable name are taken // literally; double quotes still expand $(), backticks, $var and " args[0] = `'${args[0].replace(/'/g, `'\\''`)}'` diff --git a/deps/npm/node_modules/libnpmexec/package.json b/deps/npm/node_modules/libnpmexec/package.json index 116a7194043a..4a050f2e609e 100644 --- a/deps/npm/node_modules/libnpmexec/package.json +++ b/deps/npm/node_modules/libnpmexec/package.json @@ -1,6 +1,6 @@ { "name": "libnpmexec", - "version": "10.3.3", + "version": "10.3.5", "files": [ "bin/", "lib/" @@ -61,7 +61,7 @@ }, "dependencies": { "@gar/promise-retry": "^1.0.0", - "@npmcli/arborist": "^9.9.2", + "@npmcli/arborist": "^9.9.3", "@npmcli/package-json": "^7.0.0", "@npmcli/run-script": "^10.0.0", "ci-info": "^4.0.0", diff --git a/deps/npm/node_modules/libnpmfund/package.json b/deps/npm/node_modules/libnpmfund/package.json index dcae1eb7476e..a11ee6d5b98a 100644 --- a/deps/npm/node_modules/libnpmfund/package.json +++ b/deps/npm/node_modules/libnpmfund/package.json @@ -1,6 +1,6 @@ { "name": "libnpmfund", - "version": "7.0.27", + "version": "7.0.28", "main": "lib/index.js", "files": [ "bin/", @@ -46,7 +46,7 @@ "tap": "^16.3.8" }, "dependencies": { - "@npmcli/arborist": "^9.9.2" + "@npmcli/arborist": "^9.9.3" }, "engines": { "node": "^20.17.0 || >=22.9.0" diff --git a/deps/npm/node_modules/libnpmpack/package.json b/deps/npm/node_modules/libnpmpack/package.json index c4b3dc30d7e9..e77a05ef6a1a 100644 --- a/deps/npm/node_modules/libnpmpack/package.json +++ b/deps/npm/node_modules/libnpmpack/package.json @@ -1,6 +1,6 @@ { "name": "libnpmpack", - "version": "9.1.14", + "version": "9.1.15", "description": "Programmatic API for the bits behind npm pack", "author": "GitHub Inc.", "main": "lib/index.js", @@ -37,7 +37,7 @@ "bugs": "https://github.com/npm/libnpmpack/issues", "homepage": "https://npmjs.com/package/libnpmpack", "dependencies": { - "@npmcli/arborist": "^9.9.2", + "@npmcli/arborist": "^9.9.3", "@npmcli/run-script": "^10.0.0", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2" diff --git a/deps/npm/node_modules/pacote/lib/registry.js b/deps/npm/node_modules/pacote/lib/registry.js index f50b27956dbe..25278564da91 100644 --- a/deps/npm/node_modules/pacote/lib/registry.js +++ b/deps/npm/node_modules/pacote/lib/registry.js @@ -113,6 +113,70 @@ class RegistryFetcher extends Fetcher { } } + // Registry signatures and attestations authenticate `name@version`, so the + // manifest must claim the requested name and be listed under its own + // version. Otherwise a registry could replay validly signed metadata from + // another package (or another version) and have it verify as this one. + #verifyManifestIdentity (packument, picked) { + const pickedId = `${picked.name}@${picked.version}` + if (picked.name !== this.spec.name) { + const message = [ + `${this.spec.name} registry manifest has mismatched package name:`, + `expected ${this.spec.name} but got ${pickedId}`, + ].join(' ') + throw Object.assign(new Error(message), { + code: 'EMANIFESTMISMATCH', + expected: this.spec.name, + found: pickedId, + }) + } + if (picked._id && picked._id !== pickedId) { + const message = [ + `${pickedId} registry manifest has mismatched id:`, + `expected ${pickedId} but got ${picked._id}`, + ].join(' ') + throw Object.assign(new Error(message), { + code: 'EMANIFESTMISMATCH', + expected: pickedId, + found: picked._id, + }) + } + + // without dist there are no signatures or attestations to authenticate + if (!picked.dist) { + return + } + + // npm-pick-manifest may return a shallow copy, so match on the dist object + const matchesPickedManifest = manifest => manifest.dist === picked.dist + const versionCollections = [ + packument.versions, + packument.stagedVersions?.versions, + packument.policyRestrictions?.versions, + ] + const matchingVersionKeys = [] + for (const manifestsByVersion of versionCollections) { + for (const versionKey in manifestsByVersion) { + if (Object.hasOwn(manifestsByVersion, versionKey) && + matchesPickedManifest(manifestsByVersion[versionKey])) { + matchingVersionKeys.push(versionKey) + } + } + } + + if (matchingVersionKeys.some(versionKey => versionKey !== picked.version)) { + const message = [ + `${pickedId} registry manifest has mismatched version:`, + `listed in the packument as ${matchingVersionKeys.join(', ')}`, + ].join(' ') + throw Object.assign(new Error(message), { + code: 'EMANIFESTMISMATCH', + expected: matchingVersionKeys, + found: pickedId, + }) + } + } + async manifest () { if (this.package) { return this.package @@ -127,11 +191,16 @@ class RegistryFetcher extends Fetcher { const packument = await this.packument() const steps = PackageJson.normalizeSteps.filter(s => s !== '_attributes') - const mani = await new PackageJson().fromContent(pickManifest(packument, this.spec.fetchSpec, { + const picked = pickManifest(packument, this.spec.fetchSpec, { ...this.opts, defaultTag: this.defaultTag, before: this.before, - })).normalize({ steps }).then(p => p.content) + }) + if (this.opts.verifySignatures || this.opts.verifyAttestations) { + this.#verifyManifestIdentity(packument, picked) + } + const mani = await new PackageJson().fromContent(picked) + .normalize({ steps }).then(p => p.content) /* XXX add ETARGET and E403 revalidation of cached packuments here */ diff --git a/deps/npm/node_modules/pacote/package.json b/deps/npm/node_modules/pacote/package.json index ee9bea58fc08..7b90e43f095a 100644 --- a/deps/npm/node_modules/pacote/package.json +++ b/deps/npm/node_modules/pacote/package.json @@ -1,6 +1,6 @@ { "name": "pacote", - "version": "21.5.1", + "version": "21.5.2", "description": "JavaScript package downloader", "author": "GitHub Inc.", "bin": { @@ -75,6 +75,7 @@ "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", "version": "5.1.0", "windowsCI": false, - "publish": "true" + "publish": "true", + "backport": 21 } } diff --git a/deps/npm/node_modules/postcss-selector-parser/dist/parser.js b/deps/npm/node_modules/postcss-selector-parser/dist/parser.js index d462e71aad7a..50cf16a86fea 100644 --- a/deps/npm/node_modules/postcss-selector-parser/dist/parser.js +++ b/deps/npm/node_modules/postcss-selector-parser/dist/parser.js @@ -154,8 +154,9 @@ function indexesOf(array, item) { return indexes; } function uniqs() { - var list = Array.prototype.concat.apply([], arguments); - return list.filter(function (item, i) { return i === list.indexOf(item); }); + // A Set keeps the first-occurrence order the previous filter/indexOf pass + // produced, without its quadratic cost on long index lists. + return Array.from(new Set(Array.prototype.concat.apply([], arguments))); } var Parser = /** @class */ (function () { function Parser(rule, options) { @@ -200,6 +201,11 @@ var Parser = /** @class */ (function () { attr.push(this.currToken); this.position++; } + if (!this.currToken) { + // Ran off the end of the token stream: the attribute was never closed. + // Point at the opening bracket, which is where the author needs to look. + return this.expected("closing square bracket", startingToken[tokenize_1.FIELDS.START_POS]); + } if (this.currToken[tokenize_1.FIELDS.TYPE] !== tokens.closeSquare) { return this.expected("closing square bracket", this.currToken[tokenize_1.FIELDS.START_POS]); } @@ -260,7 +266,7 @@ var Parser = /** @class */ (function () { } if (commentBefore) { (0, util_1.ensureObject)(node, "raws", "spaces", "attribute"); - node.raws.spaces.attribute.before = spaceBefore; + node.raws.spaces.attribute.before = commentBefore; commentBefore = ""; } node.namespace = (node.namespace || "") + content; @@ -686,7 +692,21 @@ var Parser = /** @class */ (function () { return this.error("Unexpected '|'.", this.currToken[tokenize_1.FIELDS.START_POS]); }; Parser.prototype.namespace = function () { - var before = (this.prevToken && this.content(this.prevToken)) || true; + var prev = this.prevToken; + // Only treat the previous token as a namespace prefix when it can actually + // be one (a type/word or the universal `*`). A comment, comma, combinator + // or whitespace before `|` means an empty namespace, not a prefix. + var before = prev && + (prev[tokenize_1.FIELDS.TYPE] === tokens.word || + prev[tokenize_1.FIELDS.TYPE] === tokens.asterisk || + prev[tokenize_1.FIELDS.TYPE] === tokens.ampersand) + ? this.content(prev) + : true; + if (!this.nextToken) { + // A trailing `|` with nothing after it. `unexpectedPipe` reports against + // `currToken`, which is the pipe itself and always present here. + return this.unexpectedPipe(); + } if (this.nextToken[tokenize_1.FIELDS.TYPE] === tokens.word) { this.position++; return this.word(before); @@ -716,6 +736,7 @@ var Parser = /** @class */ (function () { Parser.prototype.parentheses = function () { var last = this.current.last; var unbalanced = 1; + var openingToken = this.currToken; this.position++; if (last && last.type === types.PSEUDO) { var selector = new selector_1.default({ @@ -785,7 +806,9 @@ var Parser = /** @class */ (function () { } } if (unbalanced) { - return this.expected("closing parenthesis", this.currToken[tokenize_1.FIELDS.START_POS]); + // `currToken` is undefined when the token stream ran out before the + // parenthesis was closed; fall back to the opening one. + return this.expected("closing parenthesis", (this.currToken || openingToken)[tokenize_1.FIELDS.START_POS]); } }; Parser.prototype.pseudo = function () { @@ -890,8 +913,14 @@ var Parser = /** @class */ (function () { // Eliminate Sass interpolations from the list of id indexes var interpolations = indexesOf(word, "#{"); if (interpolations.length) { - hasId = hasId.filter(function (hashIndex) { return !~interpolations.indexOf(hashIndex); }); + var interpolationIndexes_1 = new Set(interpolations); + hasId = hasId.filter(function (hashIndex) { return !interpolationIndexes_1.has(hashIndex); }); } + // Membership tests are built once. Scanning the arrays per index would make + // a flat selector such as `.a.a.a...` quadratic in its number of class/id + // indexes, which is enough to pin a CPU core on attacker-supplied input. + var classIndexes = new Set(hasClass); + var idIndexes = new Set(hasId); var indices = (0, sortAscending_1.default)(uniqs(__spreadArray(__spreadArray([0], __read(hasClass), false), __read(hasId), false))); indices.forEach(function (ind, i) { var index = indices[i + 1] || word.length; @@ -903,7 +932,7 @@ var Parser = /** @class */ (function () { var current = _this.currToken; var sourceIndex = current[tokenize_1.FIELDS.START_POS] + indices[i]; var source = getSource(current[1], current[2] + ind, current[3], current[2] + (index - 1)); - if (~hasClass.indexOf(ind)) { + if (classIndexes.has(ind)) { var classNameOpts = { value: value.slice(1), source: source, @@ -911,7 +940,7 @@ var Parser = /** @class */ (function () { }; node = new className_1.default(unescapeProp(classNameOpts, "value")); } - else if (~hasId.indexOf(ind)) { + else if (idIndexes.has(ind)) { var idOpts = { value: value.slice(1), source: source, diff --git a/deps/npm/node_modules/postcss-selector-parser/package.json b/deps/npm/node_modules/postcss-selector-parser/package.json index 12b12199b436..5a7f19963ffd 100644 --- a/deps/npm/node_modules/postcss-selector-parser/package.json +++ b/deps/npm/node_modules/postcss-selector-parser/package.json @@ -1,7 +1,7 @@ { "name": "postcss-selector-parser", "description": "Selector parser with built in methods for working with selector strings.", - "version": "7.1.4", + "version": "7.1.6", "devDependencies": { "oxfmt": "^0.54.0", "oxlint": "^1.69.0", @@ -45,6 +45,10 @@ }, "homepage": "https://github.com/postcss/postcss-selector-parser", "contributors": [ + { + "name": "Maxime Thirouin", + "url": "https://moox.io" + }, { "name": "Ben Briggs", "email": "beneb.info@gmail.com", @@ -52,8 +56,7 @@ }, { "name": "Chris Eppstein", - "email": "chris@eppsteins.net", - "url": "http://twitter.com/chriseppstein" + "email": "chris@eppsteins.net" } ], "repository": { diff --git a/deps/npm/node_modules/semver/classes/range.js b/deps/npm/node_modules/semver/classes/range.js index a7d6556febb4..a14845a9668b 100644 --- a/deps/npm/node_modules/semver/classes/range.js +++ b/deps/npm/node_modules/semver/classes/range.js @@ -119,7 +119,7 @@ class Range { debug('hyphen replace', range) // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5` - range = range.replace(re[t.COMPARATORTRIM], comparatorTrimReplace) + range = trimComparatorWhitespace(range) debug('comparator trim', range) // `~ 1.2.3` => `~1.2.3` @@ -224,11 +224,11 @@ const parseOptions = require('../internal/parse-options') const Comparator = require('./comparator') const debug = require('../internal/debug') const SemVer = require('./semver') +const trimComparatorWhitespace = require('../internal/trim-comparator-whitespace') const { safeRe: re, src, t, - comparatorTrimReplace, tildeTrimReplace, caretTrimReplace, } = require('../internal/re') diff --git a/deps/npm/node_modules/semver/internal/constants.js b/deps/npm/node_modules/semver/internal/constants.js index 6d1db9154331..0c8d4ba26b1b 100644 --- a/deps/npm/node_modules/semver/internal/constants.js +++ b/deps/npm/node_modules/semver/internal/constants.js @@ -15,6 +15,35 @@ const MAX_SAFE_COMPONENT_LENGTH = 16 // the shortest version with a build 0.0.0+BUILD. const MAX_SAFE_BUILD_LENGTH = MAX_LENGTH - 6 +// ECMAScript whitespace and line terminators matched by \s. +const WHITESPACE_CHARACTERS = [ + '\t', + '\n', + '\v', + '\f', + '\r', + ' ', + '\u00a0', + '\u1680', + '\u2000', + '\u2001', + '\u2002', + '\u2003', + '\u2004', + '\u2005', + '\u2006', + '\u2007', + '\u2008', + '\u2009', + '\u200a', + '\u2028', + '\u2029', + '\u202f', + '\u205f', + '\u3000', + '\ufeff', +] + const RELEASE_TYPES = [ 'major', 'premajor', @@ -30,6 +59,7 @@ module.exports = { MAX_SAFE_COMPONENT_LENGTH, MAX_SAFE_BUILD_LENGTH, MAX_SAFE_INTEGER, + WHITESPACE_CHARACTERS, RELEASE_TYPES, SEMVER_SPEC_VERSION, FLAG_INCLUDE_PRERELEASE: 0b001, diff --git a/deps/npm/node_modules/semver/internal/trim-comparator-whitespace.js b/deps/npm/node_modules/semver/internal/trim-comparator-whitespace.js new file mode 100644 index 000000000000..de6e6387b6b4 --- /dev/null +++ b/deps/npm/node_modules/semver/internal/trim-comparator-whitespace.js @@ -0,0 +1,438 @@ +// @ts-check +'use strict' + +// Reproduce safeRe[COMPARATORTRIM] replacement boundaries, not version +// validation. Each end-index finder returns an exclusive end, or its start +// on no match. +// Positions count from zero. An end of 3 means characters 0, 1, and 2 were read; +// range.slice(start, end) gives the matched text, not a converted number. +const { + MAX_LENGTH, + MAX_SAFE_BUILD_LENGTH, + WHITESPACE_CHARACTERS, +} = require('./constants') + +/** + * Remembers the last two rows of digits so their characters need not be reread. + * Positions start at -1 until a row has been found. + * + * @typedef {object} DigitRunCache + * @property {number} start The first character of the most recently found row. + * @property {number} end The position after that row's last character. + * @property {number} previousStart The first character of the previous row. + * @property {number} previousEnd The position after the previous row. + */ + +// One entry per UTF-16 code unit keeps membership checks constant-time. +const whitespace = new Uint8Array(0x10000) +for (const char of WHITESPACE_CHARACTERS) { + whitespace[char.charCodeAt(0)] = 1 +} + +/** + * Checks whether a character is whitespace, like a space, tab, or newline. + * Looking past the end of the string returns false. + * + * @param {string|undefined} char One character, or undefined past the string's end. + * @returns {boolean} Whether the character is whitespace. + * @example isWhitespace(' ') // true + * @example isWhitespace('1') // false + */ +const isWhitespace = char => + char !== undefined && whitespace[char.charCodeAt(0)] === 1 + +/** + * Checks for v, =, or whitespace before the version number. + * These are historical version prefixes, not range operators: + * < and > are handled separately, as are tilde and caret ranges. + * + * @param {string|undefined} char One character to check. + * @returns {boolean} Whether the character can belong to a version prefix. + * @example isVersionPrefix('=') // true + * @example isVersionPrefix('>') // false + */ +const isVersionPrefix = char => + char === 'v' || + char === '=' || + isWhitespace(char) + +/** + * Checks whether a character is one of the digits 0 through 9. + * + * @param {string|undefined} char One character to check. + * @returns {boolean} Whether the character is an ASCII digit. + * @example isDigit('7') // true + * @example isDigit('.') // false + */ +const isDigit = char => char !== undefined && char >= '0' && char <= '9' + +/** + * Checks whether a character is an English letter, uppercase or lowercase. + * + * @param {string|undefined} char One character to check. + * @returns {boolean} Whether the character is an ASCII letter. + * @example isLetter('b') // true + * @example isLetter('7') // false + */ +const isLetter = char => + char !== undefined && ( + (char >= 'a' && char <= 'z') || + (char >= 'A' && char <= 'Z') + ) + +/** + * Checks for a letter, digit, or hyphen: the characters allowed inside + * prerelease and build names such as beta-1. + * + * @param {string|undefined} char One character to check. + * @returns {boolean} Whether the character can be part of an identifier. + * @example isIdentifierCharacter('-') // true + * @example isIdentifierCharacter('.') // false; dots separate identifiers + */ +const isIdentifierCharacter = char => isDigit(char) || isLetter(char) || char === '-' + +/** + * Checks whether a version component can begin here: with a digit or + * a wildcard such as x, X, or *. + * + * @param {string|undefined} char One character to check. + * @returns {boolean} Whether a number or wildcard can start with this character. + * @example isVersionStart('x') // true + * @example isVersionStart('v') // false; v belongs to the prefix + */ +const isVersionStart = char => + isDigit(char) || + char === 'x' || + char === 'X' || + char === '*' + +/** + * Finds the end of a row of digits, but returns no more than limit digits. + * Remembers the last two rows so looking at them again does not reread them. + * + * @param {string} range The text to read; it is not changed. + * @param {number} start The position of the first character to check. + * @param {number} limit The most digit characters the returned match can include. + * @param {DigitRunCache} digitRuns Shared cache of digit-run positions, updated in place. + * @returns {number} The position after the matched digits, or start if none match. + * @example findDigitsEndIndex('123abc', 0, 10, digitRuns) // 3, pointing at a + * @example findDigitsEndIndex('123abc', 0, 2, digitRuns) // 2, pointing at the unread 3 + */ +const findDigitsEndIndex = (range, start, limit, digitRuns) => { + if (!isDigit(range[start])) { + return start + } + if (start >= digitRuns.start && start < digitRuns.end) { + return Math.min(start + limit, digitRuns.end) + } + if (start >= digitRuns.previousStart && start < digitRuns.previousEnd) { + return Math.min(start + limit, digitRuns.previousEnd) + } + let end = start + 1 + while (isDigit(range[end])) { + end++ + } + // A failed full version can rewind from its second run to its first. + digitRuns.previousStart = digitRuns.start + digitRuns.previousEnd = digitRuns.end + digitRuns.start = start + digitRuns.end = end + return Math.min(start + limit, end) +} + +/** + * Reads a number using the old regex's length and leading-zero rules. + * Strict mode reads just the first zero in 012; loose mode can read all three. + * + * @param {string} range The text to read; it is not changed. + * @param {number} start The position where the number should begin. + * @param {boolean} loose Whether to allow leading zeros. + * @param {DigitRunCache} digitRuns Shared digit-run cache, updated as needed. + * @returns {number} The position after the matched digits, not their numeric value. + * @example findVersionNumberEndIndex('012', 0, false, digitRuns) // 1 + * @example findVersionNumberEndIndex('012', 0, true, digitRuns) // 3 + */ +const findVersionNumberEndIndex = (range, start, loose, digitRuns) => { + if (!loose && range[start] === '0') { + return start + 1 + } + // Strict syntax allows an initial nonzero digit plus MAX_LENGTH digits. + return findDigitsEndIndex(range, start, loose ? MAX_LENGTH : MAX_LENGTH + 1, digitRuns) +} + +/** + * Reads one piece of a prerelease name, such as beta or 1 in beta.1. + * Tries the letter-or-hyphen form first, then the number-only form. + * + * @param {string} range The text to read; it is not changed. + * @param {number} start The position where the identifier should begin. + * @param {boolean} loose Whether numeric identifiers may have leading zeros. + * @param {DigitRunCache} digitRuns Shared digit-run cache, updated as needed. + * @returns {number} The position after the identifier, or start if none matches. + * @example findPrereleaseIdentifierEndIndex('beta.1', 0, false, digitRuns) // 4, at the dot + * @example findPrereleaseIdentifierEndIndex('001beta', 0, false, digitRuns) // 7 + */ +const findPrereleaseIdentifierEndIndex = (range, start, loose, digitRuns) => { + const digitsEnd = findDigitsEndIndex(range, start, MAX_LENGTH, digitRuns) + // The legacy regex tries a nonnumeric identifier before a numeric one. + if (isLetter(range[digitsEnd]) || range[digitsEnd] === '-') { + let end = digitsEnd + 1 + const limit = end + MAX_SAFE_BUILD_LENGTH + while (end < limit && isIdentifierCharacter(range[end])) { + end++ + } + return end + } + return findVersionNumberEndIndex(range, start, loose, digitRuns) +} + +/** + * Reads a prerelease suffix such as -beta.1, one dot-separated piece at a time. + * Loose mode also allows the name without its opening hyphen. + * + * @param {string} range The text to read; it is not changed. + * @param {number} start The position where the suffix should begin, including -. + * @param {boolean} loose Whether the opening hyphen is optional and zeros allowed. + * @param {DigitRunCache} digitRuns Shared digit-run cache, updated as needed. + * @returns {number} The position after the matched suffix, or start if absent. + * @example findPrereleaseEndIndex('-beta.1+sha', 0, false, digitRuns) // 7, at the plus + * @example findPrereleaseEndIndex('beta', 0, false, digitRuns) // 0; no opening hyphen + */ +const findPrereleaseEndIndex = (range, start, loose, digitRuns) => { + const hyphen = range[start] === '-' + if (!loose && !hyphen) { + return start + } + const identifierStart = start + (hyphen ? 1 : 0) + let end = findPrereleaseIdentifierEndIndex(range, identifierStart, loose, digitRuns) + if (end === identifierStart) { + if (!loose || !hyphen) { + return start + } + // In loose syntax, a lone hyphen can itself be the identifier. + end = findPrereleaseIdentifierEndIndex(range, start, loose, digitRuns) + } + while (range[end] === '.') { + const next = findPrereleaseIdentifierEndIndex(range, end + 1, loose, digitRuns) + if (next === end + 1) { + break + } + end = next + } + return end +} + +/** + * Reads one build-name piece, such as sha or 123 in +sha.123. + * Stops at a disallowed character or the old regex's length limit. + * + * @param {string} range The text to read; it is not changed. + * @param {number} start The position where the identifier should begin. + * @returns {number} The position after the identifier, or start if none matches. + * @example findBuildIdentifierEndIndex('sha.123', 0) // 3, at the dot + * @example findBuildIdentifierEndIndex('001', 0) // 3; leading zeros are allowed + */ +const findBuildIdentifierEndIndex = (range, start) => { + let end = start + while (end - start < MAX_SAFE_BUILD_LENGTH && isIdentifierCharacter(range[end])) { + end++ + } + return end +} + +/** + * Reads optional build metadata such as +sha.123. + * Leaves the position unchanged if there is no plus sign followed by a name. + * + * @param {string} range The text to read; it is not changed. + * @param {number} start The position where the opening + should be. + * @returns {number} The position after the matched metadata, or start if absent. + * @example findBuildEndIndex('+sha.123 <2', 0) // 8, at the space + * @example findBuildEndIndex('+', 0) // 0; a plus sign alone is not a match + */ +const findBuildEndIndex = (range, start) => { + if (range[start] !== '+') { + return start + } + let end = findBuildIdentifierEndIndex(range, start + 1) + if (end === start + 1) { + return start + } + while (range[end] === '.') { + const next = findBuildIdentifierEndIndex(range, end + 1) + if (next === end + 1) { + break + } + end = next + } + return end +} + +/** + * Reads three numbers separated by dots, then any prerelease and build suffixes. + * Uses loose rules, so forms such as 01.02.03beta can match here. + * + * @param {string} range The text to read; it is not changed. + * @param {number} start The position of the first number, after any prefix. + * @param {DigitRunCache} digitRuns Shared digit-run cache, updated as needed. + * @returns {number} The position after the version match, or start if none matches. + * @example findLooseFullVersionEndIndex('1.2.3-beta+sha <2', 0, digitRuns) // 14 + * @example findLooseFullVersionEndIndex('1.2', 0, digitRuns) // 0; needs three numbers + */ +const findLooseFullVersionEndIndex = (range, start, digitRuns) => { + let end = start + for (let component = 0; component < 3; component++) { + const next = findVersionNumberEndIndex(range, end, true, digitRuns) + if (next === end) { + return start + } + end = next + if (component < 2) { + if (range[end] !== '.') { + return start + } + end++ + } + } + const prereleaseEnd = findPrereleaseEndIndex(range, end, true, digitRuns) + return findBuildEndIndex(range, prereleaseEnd) +} + +/** + * Reads one component of a partial or wildcard version, such as 2 or x in 2.x. + * Numbers use strict leading-zero rules; wildcards take up one character. + * + * @param {string} range The text to read; it is not changed. + * @param {number} start The position where the component should begin. + * @param {DigitRunCache} digitRuns Shared digit-run cache, updated as needed. + * @returns {number} The position after the component, or start if none matches. + * @example findPartialVersionComponentEndIndex('2.x', 0, digitRuns) // 1, at the dot + * @example findPartialVersionComponentEndIndex('2.x', 2, digitRuns) // 3, after x + */ +const findPartialVersionComponentEndIndex = (range, start, digitRuns) => { + if (isDigit(range[start])) { + return findVersionNumberEndIndex(range, start, false, digitRuns) + } + return isVersionStart(range[start]) ? start + 1 : start +} + +/** + * Reads up to three components, allowing forms such as 1, 1.2, or 1.x. + * After the third component, also reads any strict prerelease and build suffixes. + * + * @param {string} range The text to read; it is not changed. + * @param {number} start The position of a number or wildcard, after any prefix. + * @param {DigitRunCache} digitRuns Shared digit-run cache, updated as needed. + * @returns {number} The position after the matched version text. + * @example findPartialVersionEndIndex('1.x <2', 0, digitRuns) // 3, at the space + * @example findPartialVersionEndIndex('1.', 0, digitRuns) // 1; the trailing dot is unread + */ +const findPartialVersionEndIndex = (range, start, digitRuns) => { + let end = findPartialVersionComponentEndIndex(range, start, digitRuns) + for (let component = 0; component < 2; component++) { + if (range[end] !== '.') { + return end + } + const next = findPartialVersionComponentEndIndex(range, end + 1, digitRuns) + if (next === end + 1) { + return end + } + end = next + } + const prereleaseEnd = findPrereleaseEndIndex(range, end, false, digitRuns) + return findBuildEndIndex(range, prereleaseEnd) +} + +/** + * Finds where a version match ends, without deciding whether the range is valid. + * Tries a loose full version first, then a partial or wildcard version, just + * like the old regex's LOOSEPLAIN and XRANGEPLAIN alternatives. + * + * @param {string} range The text to read; it is not changed. + * @param {number} start The position of a number or wildcard, after any prefix. + * @param {DigitRunCache} digitRuns Shared digit-run cache, updated as needed. + * @returns {number} The position where scanning the next candidate can resume. + * @example findVersionEndIndex('1.2.3 <2', 0, digitRuns) // 5, at the space + * @example findVersionEndIndex('1.x <2', 0, digitRuns) // 3, at the space + */ +const findVersionEndIndex = (range, start, digitRuns) => { + const fullVersionEnd = findLooseFullVersionEndIndex(range, start, digitRuns) + return fullVersionEnd !== start + ? fullVersionEnd + : findPartialVersionEndIndex(range, start, digitRuns) +} + +/** + * Finds the one whitespace character the old replacement would remove. + * Keeps leading whitespace, skips an optional operator, and checks immediately + * after it. Returns -1 if there is nothing to remove. + * + * @param {string} range The text containing a candidate already found by the caller. + * @param {number} matchStart The candidate's position, including leading whitespace. + * @returns {number} The position of the removable whitespace, or -1 if absent. + * @example findRemovableWhitespaceIndex(' > 1', 0) // 2; keep the space at position 0 + * @example findRemovableWhitespaceIndex('>1', 0) // -1 + */ +const findRemovableWhitespaceIndex = (range, matchStart) => { + let position = matchStart + if (isWhitespace(range[position])) { + position++ + } + if (range[position] === '<' || range[position] === '>') { + position++ + } + if (range[position] === '=') { + position++ + } + return isWhitespace(range[position]) ? position : -1 +} + +/** + * Joins an operator to its version, turning > 1.2.3 into >1.2.3. + * Copies everything else according to the old regex's matching rules. + * Moves forward through prefixes and whole versions to avoid repeatedly + * searching the same characters on long inputs. + * + * @param {string} range The range text to process; the original string is unchanged. + * @returns {string} The range with matched operator whitespace removed. + * @example trimComparatorWhitespace('> 1.2.3 < 2') // '>1.2.3 <2' + * @example trimComparatorWhitespace('> invalid') // '> invalid' + */ +const trimComparatorWhitespace = range => { + let result = '' + let copyFrom = 0 + let cursor = 0 + const digitRuns = { start: -1, end: -1, previousStart: -1, previousEnd: -1 } + + while (cursor < range.length) { + const matchStart = cursor + if (range[cursor] === '<' || range[cursor] === '>') { + cursor++ + } else if (!isVersionPrefix(range[cursor]) && !isVersionStart(range[cursor])) { + cursor++ + continue + } + + // Consume a v/=/whitespace run once, even when no version follows it. + while (cursor < range.length && isVersionPrefix(range[cursor])) { + cursor++ + } + if (!isVersionStart(range[cursor])) { + continue + } + + const whitespacePosition = findRemovableWhitespaceIndex(range, matchStart) + if (whitespacePosition !== -1) { + result += range.slice(copyFrom, whitespacePosition) + copyFrom = whitespacePosition + 1 + } + + // Skip the entire match, so a prerelease's trailing v cannot be + // reconsidered as the prefix of another version. + cursor = findVersionEndIndex(range, cursor, digitRuns) + } + + return result + range.slice(copyFrom) +} + +module.exports = trimComparatorWhitespace diff --git a/deps/npm/node_modules/semver/package.json b/deps/npm/node_modules/semver/package.json index 0cb7c7bb465e..80bf748cd049 100644 --- a/deps/npm/node_modules/semver/package.json +++ b/deps/npm/node_modules/semver/package.json @@ -1,6 +1,6 @@ { "name": "semver", - "version": "7.8.5", + "version": "7.8.6", "description": "The semantic version parser used by npm.", "main": "index.js", "scripts": { diff --git a/deps/npm/node_modules/undici/docs/docs/api/Client.md b/deps/npm/node_modules/undici/docs/docs/api/Client.md index e0d41b47805e..ca6e336f226e 100644 --- a/deps/npm/node_modules/undici/docs/docs/api/Client.md +++ b/deps/npm/node_modules/undici/docs/docs/api/Client.md @@ -29,6 +29,8 @@ Returns: `Client` * **webSocket** `WebSocketOptions` (optional) - WebSocket-specific configuration options. * **maxFragments** `number` (optional) - Default: `131072` - Maximum number of fragments in a message. Set to 0 to disable the limit. * **maxPayloadSize** `number` (optional) - Default: `134217728` (128 MB) - Maximum allowed payload size in bytes for WebSocket messages. Applied to uncompressed messages, compressed frame payloads, and decompressed (permessage-deflate) messages. Set to 0 to disable the limit. +* **eventSource** `EventSourceOptions` (optional) - EventSource-specific configuration options. + * **maxEventSize** `number` (optional) - The maximum total bytes of data and metadata field values retained for a pending EventSource event. Each unfinished line is separately limited to this value plus seven syntax bytes. The number of `data:` lines per event and input chunks buffered for an unfinished line are each limited to `min(131072, max(1024, floor(maxEventSize / 128)))` to bound object overhead. Set to `0` to disable these limits. Default: `buffer.kStringMaxLength`. * **pipelining** `number | null` (optional) - Default: `1` - The amount of concurrent requests to be sent over the single TCP/TLS connection according to [RFC7230](https://tools.ietf.org/html/rfc7230#section-6.3.2). Carefully consider your workload and environment before enabling concurrent requests as pipelining may reduce performance if used incorrectly. Pipelining is sensitive to network stack settings as well as head of line blocking caused by e.g. long running requests. Set to `0` to disable keep-alive connections. * **connect** `ConnectOptions | Function | null` (optional) - Default: `null`. * **strictContentLength** `Boolean` (optional) - Default: `true` - Whether to treat request content length mismatches as errors. If true, an error is thrown when the request content-length header doesn't match the length of the request body. diff --git a/deps/npm/node_modules/undici/docs/docs/api/DiagnosticsChannel.md b/deps/npm/node_modules/undici/docs/docs/api/DiagnosticsChannel.md index 099c072f6c6c..aefb1009c63a 100644 --- a/deps/npm/node_modules/undici/docs/docs/api/DiagnosticsChannel.md +++ b/deps/npm/node_modules/undici/docs/docs/api/DiagnosticsChannel.md @@ -40,7 +40,8 @@ diagnosticsChannel.channel('undici:request:bodySent').subscribe(({ request }) => ## `undici:request:headers` -This message is published after the response headers have been received, i.e. the response has been completed. +This message is published after the response headers have been received. This includes a successful CONNECT or +protocol upgrade response. ```js import diagnosticsChannel from 'diagnostics_channel' @@ -57,6 +58,7 @@ diagnosticsChannel.channel('undici:request:headers').subscribe(({ request, respo ## `undici:request:trailers` This message is published after the response body and trailers have been received, i.e. the response has been completed. +After an upgraded socket is passed to the request handler, this message is published with an empty `trailers` array. ```js import diagnosticsChannel from 'diagnostics_channel' diff --git a/deps/npm/node_modules/undici/lib/core/request.js b/deps/npm/node_modules/undici/lib/core/request.js index 8e7ecc73084b..0b2b1a8d9d7b 100644 --- a/deps/npm/node_modules/undici/lib/core/request.js +++ b/deps/npm/node_modules/undici/lib/core/request.js @@ -263,11 +263,77 @@ class Request { } } - onUpgrade (statusCode, headers, socket) { + /** + * @param {number|null} statusCode + * @param {Buffer[]|null} headers + * @param {import('node:stream').Duplex} socket + * @param {string} [statusText] + */ + onUpgrade (statusCode, headers, socket, statusText = '') { + this.onFinally() + assert(!this.aborted) assert(!this.completed) - return this[kHandler].onUpgrade(statusCode, headers, socket) + if (statusCode !== null) { + this.#publishUpgradeHeaders(statusCode, headers, statusText) + } + + const result = this[kHandler].onUpgrade(statusCode, headers, socket) + + if (!this.aborted) { + this.completed = true + if (statusCode !== null) { + this.#publishUpgradeTrailers() + } + } + + return result + } + + /** + * @param {number} statusCode + * @param {import('node:http2').IncomingHttpHeaders} headers + * @param {(headers: import('node:http2').IncomingHttpHeaders) => Buffer[]} parseHeaders + * @param {string} [statusText] + */ + onUpgradeResponse (statusCode, headers, parseHeaders, statusText = '') { + assert(!this.aborted) + assert(this.completed) + + if (channels.headers.hasSubscribers) { + this.#publishUpgradeHeaders(statusCode, parseHeaders(headers), statusText) + } + this.#publishUpgradeTrailers() + } + + /** + * @param {Error} error + */ + onUpgradeError (error) { + assert(!this.aborted) + assert(this.completed) + + if (channels.error.hasSubscribers) { + channels.error.publish({ request: this, error }) + } + } + + /** + * @param {number} statusCode + * @param {Buffer[]} headers + * @param {string} statusText + */ + #publishUpgradeHeaders (statusCode, headers, statusText) { + if (channels.headers.hasSubscribers) { + channels.headers.publish({ request: this, response: { statusCode, headers, statusText } }) + } + } + + #publishUpgradeTrailers () { + if (channels.trailers.hasSubscribers) { + channels.trailers.publish({ request: this, trailers: [] }) + } } onComplete (trailers) { diff --git a/deps/npm/node_modules/undici/lib/dispatcher/agent.js b/deps/npm/node_modules/undici/lib/dispatcher/agent.js index 90b46fe3aeb4..416ab3edda30 100644 --- a/deps/npm/node_modules/undici/lib/dispatcher/agent.js +++ b/deps/npm/node_modules/undici/lib/dispatcher/agent.js @@ -23,7 +23,7 @@ function defaultFactory (origin, opts) { } class Agent extends DispatcherBase { - constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) { + constructor ({ factory = defaultFactory, maxRedirections = 0, connect, tls, ...options } = {}) { if (typeof factory !== 'function') { throw new InvalidArgumentError('factory must be a function.') } @@ -42,11 +42,15 @@ class Agent extends DispatcherBase { connect = { ...connect } } + if (tls && typeof tls !== 'function') { + tls = { ...tls } + } + this[kInterceptors] = options.interceptors?.Agent && Array.isArray(options.interceptors.Agent) ? options.interceptors.Agent : [createRedirectInterceptor({ maxRedirections })] - this[kOptions] = { ...util.deepClone(options), connect } + this[kOptions] = { ...util.deepClone(options), connect, tls } this[kOptions].interceptors = options.interceptors ? { ...options.interceptors } : undefined diff --git a/deps/npm/node_modules/undici/lib/dispatcher/client-h1.js b/deps/npm/node_modules/undici/lib/dispatcher/client-h1.js index a801ecb36046..7d1fdbdf6e7b 100644 --- a/deps/npm/node_modules/undici/lib/dispatcher/client-h1.js +++ b/deps/npm/node_modules/undici/lib/dispatcher/client-h1.js @@ -432,7 +432,7 @@ class Parser { } onUpgrade (head) { - const { upgrade, client, socket, headers, statusCode } = this + const { upgrade, client, socket, headers, statusCode, statusText } = this assert(upgrade) assert(client[kSocket] === socket) @@ -467,9 +467,10 @@ class Parser { client.emit('disconnect', client[kUrl], [client], new InformationalError('upgrade')) try { - request.onUpgrade(statusCode, headers, socket) - } catch (err) { - util.destroy(socket, err) + request.onUpgrade(statusCode, headers, socket, statusText) + } catch (error) { + util.errorRequest(client, request, error) + util.destroy(socket, error) } client[kResume]() @@ -726,8 +727,6 @@ async function connectH1 (client, socket) { socket[kParser] = new Parser(client, socket, llhttpInstance) addListener(socket, 'error', function (err) { - assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID') - const parser = this[kParser] // On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded @@ -876,7 +875,7 @@ async function connectH1 (client, socket) { function clearIdleSocketValidation (socket) { if (socket[kIdleSocketValidationTimeout]) { - clearTimeout(socket[kIdleSocketValidationTimeout]) + clearImmediate(socket[kIdleSocketValidationTimeout]) socket[kIdleSocketValidationTimeout] = null } @@ -885,15 +884,23 @@ function clearIdleSocketValidation (socket) { function scheduleIdleSocketValidation (client, socket) { socket[kIdleSocketValidation] = 1 - socket[kIdleSocketValidationTimeout] = setTimeout(() => { + // Yield to the check phase (after poll) so unsolicited bytes / FIN / RST + // already pending on this idle keep-alive socket are processed before the + // next request is written (GHSA-35p6-xmwp-9g52). + // + // setTimeout(0) pays Node's ~1ms timer floor on every sequential reuse + // (#5493). setImmediate avoids that, but an *unref'd* Immediate lets poll + // block for ~500ms when the event loop is otherwise idle (#5600 / #5606). + // A ref'd Immediate both keeps the pending request alive and makes poll + // return immediately — the hybrid those issues asked for. + socket[kIdleSocketValidationTimeout] = setImmediate(() => { socket[kIdleSocketValidationTimeout] = null socket[kIdleSocketValidation] = 2 if (client[kSocket] === socket && !socket.destroyed) { client[kResume]() } - }, 0) - socket[kIdleSocketValidationTimeout].unref?.() + }) } /** @@ -1042,12 +1049,22 @@ function writeH1 (client, request) { const socket = client[kSocket] clearIdleSocketValidation(socket) - const abort = (err) => { - if (request.aborted || request.completed) { + /** + * @param {Error} [error] + */ + const abort = (error) => { + if (request.aborted) { + return + } + + if (request.completed) { + if (request.upgrade || request.method === 'CONNECT') { + util.destroy(socket, new InformationalError('aborted')) + } return } - util.errorRequest(client, request, err || new RequestAbortedError()) + util.errorRequest(client, request, error || new RequestAbortedError()) util.destroy(body) util.destroy(socket, new InformationalError('aborted')) diff --git a/deps/npm/node_modules/undici/lib/dispatcher/client-h2.js b/deps/npm/node_modules/undici/lib/dispatcher/client-h2.js index 4a52effb1f3b..f417aa3d1867 100644 --- a/deps/npm/node_modules/undici/lib/dispatcher/client-h2.js +++ b/deps/npm/node_modules/undici/lib/dispatcher/client-h2.js @@ -1,6 +1,7 @@ 'use strict' const assert = require('node:assert') +const { errorMonitor } = require('node:events') const { pipeline } = require('node:stream') const util = require('../core/util.js') const { @@ -77,6 +78,15 @@ function parseH2Headers (headers) { return result } +/** + * @param {import('node:http2').IncomingHttpHeaders} headers + * @returns {Buffer[]} + */ +function parseH2ResponseHeaders (headers) { + const { [HTTP2_HEADER_STATUS]: _statusCode, ...realHeaders } = headers + return parseH2Headers(realHeaders) +} + async function connectH2 (client, socket) { client[kSocket] = socket @@ -126,8 +136,6 @@ async function connectH2 (client, socket) { socket[kHTTP2Session] = session util.addListener(socket, 'error', function (err) { - assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID') - this[kError] = err this[kClient][kOnError](err) @@ -201,8 +209,6 @@ function resumeH2 (client) { } function onHttp2SessionError (err) { - assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID') - this[kSocket][kError] = err this[kClient][kOnError](err) } @@ -297,22 +303,32 @@ function writeH2 (client, request) { headers[HTTP2_HEADER_AUTHORITY] = host || `${hostname}${port ? `:${port}` : ''}` headers[HTTP2_HEADER_METHOD] = method - const abort = (err) => { - if (request.aborted || request.completed) { + /** + * @param {Error} [error] + */ + const abort = (error) => { + if (request.aborted) { return } - err = err || new RequestAbortedError() + if (request.completed) { + if (method === 'CONNECT' && stream != null) { + util.destroy(stream, error || new RequestAbortedError()) + } + return + } - util.errorRequest(client, request, err) + error = error || new RequestAbortedError() + + util.errorRequest(client, request, error) if (stream != null) { - util.destroy(stream, err) + util.destroy(stream, error) } // We do not destroy the socket as we can continue using the session // the stream get's destroyed and the session remains to create new streams - util.destroy(body, err) + util.destroy(body, error) client[kQueue][client[kRunningIdx]++] = null client[kResume]() } @@ -331,25 +347,57 @@ function writeH2 (client, request) { if (method === 'CONNECT') { session.ref() - // We are already connected, streams are pending, first request - // will create a new stream. We trigger a request to create the stream and wait until - // `ready` event is triggered // We disabled endStream to allow the user to write to the stream stream = session.request(headers, { endStream: false, signal }) + let upgradeResponseFinished = false + + /** + * @param {import('node:http2').IncomingHttpHeaders} headers + */ + const onResponse = (headers) => { + upgradeResponseFinished = true + stream.off(errorMonitor, onUpgradeError) + request.onUpgradeResponse(Number(headers[HTTP2_HEADER_STATUS]), headers, parseH2ResponseHeaders) + } - if (stream.id && !stream.pending) { - request.onUpgrade(null, null, stream) - ++session[kOpenStreams] - client[kQueue][client[kRunningIdx]++] = null - } else { - stream.once('ready', () => { + /** + * @param {Error} error + */ + const onUpgradeError = (error) => { + upgradeResponseFinished = true + stream.off('response', onResponse) + request.onUpgradeError(error) + } + + const onReady = () => { + try { request.onUpgrade(null, null, stream) - ++session[kOpenStreams] - client[kQueue][client[kRunningIdx]++] = null - }) + } catch (error) { + stream.off('response', onResponse) + abort(error) + return + } + + if (request.aborted) { + return + } + + stream.off('error', abort) + stream.once(errorMonitor, onUpgradeError) + client[kQueue][client[kRunningIdx]++] = null } + stream.once('response', onResponse) + stream.once('error', abort) + ++session[kOpenStreams] + onReady() + stream.once('close', () => { + if (!upgradeResponseFinished && request.completed) { + stream.off('response', onResponse) + stream.off(errorMonitor, onUpgradeError) + request.onUpgradeError(new InformationalError(`HTTP/2: "stream error" received - code ${stream.rstCode}`)) + } session[kOpenStreams] -= 1 if (session[kOpenStreams] === 0) session.unref() }) diff --git a/deps/npm/node_modules/undici/lib/dispatcher/client.js b/deps/npm/node_modules/undici/lib/dispatcher/client.js index 18472fffd773..9efe80e3002b 100644 --- a/deps/npm/node_modules/undici/lib/dispatcher/client.js +++ b/deps/npm/node_modules/undici/lib/dispatcher/client.js @@ -107,9 +107,10 @@ class Client extends DispatcherBase { // h2 maxConcurrentStreams, allowH2, - webSocket + webSocket, + eventSource } = {}) { - super({ webSocket }) + super({ webSocket, eventSource }) if (keepAlive !== undefined) { throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead') diff --git a/deps/npm/node_modules/undici/lib/dispatcher/dispatcher-base.js b/deps/npm/node_modules/undici/lib/dispatcher/dispatcher-base.js index 371a3ea1f6f1..e90e4b379ef9 100644 --- a/deps/npm/node_modules/undici/lib/dispatcher/dispatcher-base.js +++ b/deps/npm/node_modules/undici/lib/dispatcher/dispatcher-base.js @@ -1,5 +1,6 @@ 'use strict' +const buffer = require('node:buffer') const Dispatcher = require('./dispatcher') const { ClientDestroyedError, @@ -12,6 +13,7 @@ const kOnDestroyed = Symbol('onDestroyed') const kOnClosed = Symbol('onClosed') const kInterceptedDispatch = Symbol('Intercepted Dispatch') const kWebSocketOptions = Symbol('webSocketOptions') +const kEventSourceOptions = Symbol('eventSourceOptions') class DispatcherBase extends Dispatcher { constructor (opts) { @@ -22,6 +24,7 @@ class DispatcherBase extends Dispatcher { this[kClosed] = false this[kOnClosed] = [] this[kWebSocketOptions] = opts?.webSocket ?? {} + this[kEventSourceOptions] = opts?.eventSource ?? {} } get webSocketOptions () { @@ -31,6 +34,12 @@ class DispatcherBase extends Dispatcher { } } + get eventSourceOptions () { + return { + maxEventSize: this[kEventSourceOptions].maxEventSize ?? buffer.kStringMaxLength + } + } + get destroyed () { return this[kDestroyed] } diff --git a/deps/npm/node_modules/undici/lib/handler/retry-handler.js b/deps/npm/node_modules/undici/lib/handler/retry-handler.js index c62a2409f3ae..7084c2b56520 100644 --- a/deps/npm/node_modules/undici/lib/handler/retry-handler.js +++ b/deps/npm/node_modules/undici/lib/handler/retry-handler.js @@ -90,6 +90,7 @@ class RetryHandler { this.end = null this.etag = null this.resume = null + this.headersSent = false // Handle possible onConnect duplication this.handler.onConnect(reason => { @@ -102,6 +103,20 @@ class RetryHandler { }) } + checkpointResponseEnd (headers, resume) { + if (this.end == null && this.opts.method !== 'HEAD') { + const contentLength = headers['content-length'] + this.end = contentLength != null ? Number(contentLength) - 1 : null + + assert( + this.end == null || Number.isFinite(this.end), + 'invalid content-length' + ) + } + + this.resume = this.end != null ? resume : null + } + onRequestSent () { if (this.handler.onRequestSent) { this.handler.onRequestSent() @@ -190,7 +205,12 @@ class RetryHandler { this.retryCount += 1 if (statusCode >= 300) { - if (this.retryOpts.statusCodes.includes(statusCode) === false) { + // Only expose a response if no earlier attempt has reached the caller. + // Otherwise abort this attempt so the error settles the existing body + // instead of replacing it with a new response. + if (!this.headersSent && this.retryOpts.statusCodes.includes(statusCode) === false) { + this.headersSent = true + this.checkpointResponseEnd(headers, resume) return this.handler.onHeaders( statusCode, rawHeaders, @@ -259,8 +279,15 @@ class RetryHandler { const { start, size, end = size - 1 } = contentRange - assert(this.start === start, 'content-range mismatch') - assert(this.end == null || this.end === end, 'content-range mismatch') + if (this.start !== start || (this.end != null && this.end !== end)) { + this.abort( + new RequestRetryError('Content-Range mismatch', statusCode, { + headers, + data: { count: this.retryCount } + }) + ) + return false + } this.resume = resume return true @@ -272,6 +299,7 @@ class RetryHandler { const range = parseRangeHeader(headers['content-range']) if (range == null) { + this.headersSent = true return this.handler.onHeaders( statusCode, rawHeaders, @@ -310,6 +338,7 @@ class RetryHandler { ) this.resume = resume + this.headersSent = true this.etag = headers.etag != null ? headers.etag : null // Weak etags are not useful for comparison nor cache @@ -349,7 +378,7 @@ class RetryHandler { } onError (err) { - if (this.aborted || isDisturbed(this.opts.body)) { + if (this.aborted || isDisturbed(this.opts.body) || (this.headersSent && this.resume == null)) { return this.handler.onError(err) } diff --git a/deps/npm/node_modules/undici/lib/web/eventsource/eventsource-stream.js b/deps/npm/node_modules/undici/lib/web/eventsource/eventsource-stream.js index 754934568d05..9172602ad056 100644 --- a/deps/npm/node_modules/undici/lib/web/eventsource/eventsource-stream.js +++ b/deps/npm/node_modules/undici/lib/web/eventsource/eventsource-stream.js @@ -1,4 +1,5 @@ 'use strict' +const buffer = require('node:buffer') const { Transform } = require('node:stream') const { isASCIINumber, isValidLastEventId } = require('./util') @@ -23,6 +24,63 @@ const COLON = 0x3A */ const SPACE = 0x20 +const defaultMaxEventSize = buffer.kStringMaxLength + +const DATA = Buffer.from('data') +const EVENT = Buffer.from('event') +const ID = Buffer.from('id') +const RETRY = Buffer.from('retry') + +// Each data field creates a string node, and each unconsumed input chunk keeps +// a Buffer wrapper. The absolute ceiling protects the large default byte limit. +const minParserItems = 1024 +const maxParserItems = 131072 +const bytesPerParserItem = 128 + +function isASCIINumberBytes (buffer, start) { + if (start >= buffer.length) { + return false + } + + for (let i = start; i < buffer.length; i++) { + if (buffer[i] < 0x30 || buffer[i] > 0x39) { + return false + } + } + + return true +} + +function isValidLastEventIdBytes (buffer, start) { + for (let i = start; i < buffer.length; i++) { + if (buffer[i] === 0x00) { + return false + } + } + + return true +} + +function isFieldName (line, length, field) { + if (length !== field.length) { + return false + } + + for (let i = 0; i < length; i++) { + if (line[i] !== field[i]) { + return false + } + } + + return true +} + +function createMaxEventSizeExceededError () { + const error = new Error('EventSource message size exceeded') + error.aborted = false + return error +} + /** * @typedef {object} EventSourceStreamEvent * @type {object} @@ -63,11 +121,21 @@ class EventSourceStream extends Transform { eventEndCheck = false /** - * @type {Buffer} + * @type {Buffer[]} */ - buffer = null + chunks = [] + chunkIndex = 0 pos = 0 + lineChunkIndex = 0 + linePos = 0 + lineLength = 0 + eventSize = 0 + eventFieldSizes = { data: 0, event: 0, id: 0, retry: 0 } + dataLineCount = 0 + maxEventSize + maxDataLines + maxBufferedChunks event = { data: undefined, @@ -78,6 +146,7 @@ class EventSourceStream extends Transform { /** * @param {object} options + * @param {number} [options.maxEventSize] * @param {eventSourceSettings} options.eventSourceSettings * @param {Function} [options.push] */ @@ -89,6 +158,12 @@ class EventSourceStream extends Transform { super(options) this.state = options.eventSourceSettings || {} + this.maxEventSize = options.maxEventSize ?? defaultMaxEventSize + const parserItemLimit = this.maxEventSize > 0 + ? Math.min(maxParserItems, Math.max(minParserItems, Math.floor(this.maxEventSize / bytesPerParserItem))) + : 0 + this.maxDataLines = parserItemLimit + this.maxBufferedChunks = parserItemLimit if (options.push) { this.push = options.push } @@ -106,92 +181,28 @@ class EventSourceStream extends Transform { return } - // Cache the chunk in the buffer, as the data might not be complete while - // processing it - // TODO: Investigate if there is a more performant way to handle - // incoming chunks - // see: https://github.com/nodejs/undici/issues/2630 - if (this.buffer) { - this.buffer = Buffer.concat([this.buffer, chunk]) - } else { - this.buffer = chunk + // A leading line ending can immediately release the currently buffered + // line, even when it uses the final allowed chunk slot. + if (this.maxEventSize > 0 && + this.chunks.length >= this.maxBufferedChunks && + chunk[0] !== LF && chunk[0] !== CR) { + callback(createMaxEventSizeExceededError()) + return } + this.chunks.push(chunk) // Strip leading byte-order-mark if we opened the stream and started // the processing of the incoming data if (this.checkBOM) { - switch (this.buffer.length) { - case 1: - // Check if the first byte is the same as the first byte of the BOM - if (this.buffer[0] === BOM[0]) { - // If it is, we need to wait for more data - callback() - return - } - // Set the checkBOM flag to false as we don't need to check for the - // BOM anymore - this.checkBOM = false - - // The buffer only contains one byte so we need to wait for more data - callback() - return - case 2: - // Check if the first two bytes are the same as the first two bytes - // of the BOM - if ( - this.buffer[0] === BOM[0] && - this.buffer[1] === BOM[1] - ) { - // If it is, we need to wait for more data, because the third byte - // is needed to determine if it is the BOM or not - callback() - return - } - - // Set the checkBOM flag to false as we don't need to check for the - // BOM anymore - this.checkBOM = false - break - case 3: - // Check if the first three bytes are the same as the first three - // bytes of the BOM - if ( - this.buffer[0] === BOM[0] && - this.buffer[1] === BOM[1] && - this.buffer[2] === BOM[2] - ) { - // If it is, we can drop the buffered data, as it is only the BOM - this.buffer = Buffer.alloc(0) - // Set the checkBOM flag to false as we don't need to check for the - // BOM anymore - this.checkBOM = false - - // Await more data - callback() - return - } - // If it is not the BOM, we can start processing the data - this.checkBOM = false - break - default: - // The buffer is longer than 3 bytes, so we can drop the BOM if it is - // present - if ( - this.buffer[0] === BOM[0] && - this.buffer[1] === BOM[1] && - this.buffer[2] === BOM[2] - ) { - // Remove the BOM from the buffer - this.buffer = this.buffer.subarray(3) - } - - // Set the checkBOM flag to false as we don't need to check for the - this.checkBOM = false - break + if (this.handleBOM()) { + callback() + return } } - while (this.pos < this.buffer.length) { + while (this.hasCurrentByte()) { + const byte = this.currentByte() + // If the previous line ended with an end-of-line, we need to check // if the next character is also an end-of-line. if (this.eventEndCheck) { @@ -204,10 +215,9 @@ class EventSourceStream extends Transform { if (this.crlfCheck) { // If the current character is a line feed, we can remove it // from the buffer and reset the crlfCheck flag - if (this.buffer[this.pos] === LF) { - this.buffer = this.buffer.subarray(this.pos + 1) - this.pos = 0 + if (byte === LF) { this.crlfCheck = false + this.consumeCurrentByte() // It is possible that the line feed is not the end of the // event. We need to check if the next character is an @@ -223,19 +233,17 @@ class EventSourceStream extends Transform { this.crlfCheck = false } - if (this.buffer[this.pos] === LF || this.buffer[this.pos] === CR) { + if (byte === LF || byte === CR) { // If the current character is a carriage return, we need to // set the crlfCheck flag to true, as we need to check if the // next character is a line feed so we can remove it from the // buffer - if (this.buffer[this.pos] === CR) { + if (byte === CR) { this.crlfCheck = true } - this.buffer = this.buffer.subarray(this.pos + 1) - this.pos = 0 - if ( - this.event.data !== undefined || this.event.event || this.event.id || this.event.retry) { + this.consumeCurrentByte() + if (this.hasPendingEvent()) { this.processEvent(this.event) } this.clearEvent() @@ -249,22 +257,23 @@ class EventSourceStream extends Transform { // If the current character is an end-of-line, we can process the // line - if (this.buffer[this.pos] === LF || this.buffer[this.pos] === CR) { + if (byte === LF || byte === CR) { // If the current character is a carriage return, we need to // set the crlfCheck flag to true, as we need to check if the // next character is a line feed - if (this.buffer[this.pos] === CR) { + if (byte === CR) { this.crlfCheck = true } // In any case, we can process the line as we reached an // end-of-line character - this.parseLine(this.buffer.subarray(0, this.pos), this.event) - - // Remove the processed line from the buffer - this.buffer = this.buffer.subarray(this.pos + 1) - // Reset the position as we removed the processed line from the buffer - this.pos = 0 + try { + this.parseLine(this.readLine(), this.event) + } catch (error) { + callback(error) + return + } + this.consumeCurrentByte() // A line was processed and this could be the end of the event. We need // to check if the next line is empty to determine if the event is // finished. @@ -272,15 +281,28 @@ class EventSourceStream extends Transform { continue } - this.pos++ + // readLine() is only called after CR or LF. Reject an unfinished line + // before it can retain an unbounded number of decoded chunks. The seven + // extra bytes allow the longest field prefix ("event: " or "retry: "). + if (this.maxEventSize > 0 && ++this.lineLength > this.maxEventSize + 7) { + callback(createMaxEventSizeExceededError()) + return + } + this.advanceCursor() } callback() } + _destroy (error, callback) { + this.chunks.length = 0 + this.clearEvent() + super._destroy(error, callback) + } + /** * @param {Buffer} line - * @param {EventStreamEvent} event + * @param {EventSourceStreamEvent} event */ parseLine (line, event) { // If the line is empty (a blank line) @@ -297,67 +319,74 @@ class EventSourceStream extends Transform { return } - let field = '' - let value = '' + let fieldLength = line.length + let valueStart = line.length // If the line contains a U+003A COLON character (:) if (colonPosition !== -1) { - // Collect the characters on the line before the first U+003A COLON - // character (:), and let field be that string. - // TODO: Investigate if there is a more performant way to extract the - // field - // see: https://github.com/nodejs/undici/issues/2630 - field = line.subarray(0, colonPosition).toString('utf8') + fieldLength = colonPosition // Collect the characters on the line after the first U+003A COLON // character (:), and let value be that string. // If value starts with a U+0020 SPACE character, remove it from value. - let valueStart = colonPosition + 1 + valueStart = colonPosition + 1 if (line[valueStart] === SPACE) { ++valueStart } - // TODO: Investigate if there is a more performant way to extract the - // value - // see: https://github.com/nodejs/undici/issues/2630 - value = line.subarray(valueStart).toString('utf8') - - // Otherwise, the string is not empty but does not contain a U+003A COLON - // character (:) - } else { - // Process the field using the steps described below, using the whole - // line as the field name, and the empty string as the field value. - field = line.toString('utf8') - value = '' } - // Modify the event with the field name and value. The value is also - // decoded as UTF-8 - switch (field) { - case 'data': - if (event[field] === undefined) { - event[field] = value - } else { - event[field] += `\n${value}` - } - break - case 'retry': - if (isASCIINumber(value)) { - event[field] = value - } - break - case 'id': - if (isValidLastEventId(value)) { - event[field] = value - } - break - case 'event': - if (value.length > 0) { - event[field] = value - } - break + if (isFieldName(line, fieldLength, DATA)) { + if (this.maxEventSize > 0 && this.dataLineCount >= this.maxDataLines) { + throw createMaxEventSizeExceededError() + } + const valueBytes = line.length - valueStart + const dataSize = this.eventFieldSizes.data + (event.data === undefined ? 0 : 1) + valueBytes + this.setEventFieldSize('data', dataSize) + + const value = line.toString('utf8', valueStart) + + if (event.data === undefined) { + event.data = value + } else { + event.data += `\n${value}` + } + this.dataLineCount++ + return + } + + if (isFieldName(line, fieldLength, RETRY)) { + if (isASCIINumberBytes(line, valueStart)) { + this.setEventFieldSize('retry', line.length - valueStart) + event.retry = line.toString('utf8', valueStart) + } + return + } + + if (isFieldName(line, fieldLength, ID)) { + if (isValidLastEventIdBytes(line, valueStart)) { + this.setEventFieldSize('id', line.length - valueStart) + event.id = line.toString('utf8', valueStart) + } + return + } + + if (isFieldName(line, fieldLength, EVENT)) { + if (valueStart < line.length) { + this.setEventFieldSize('event', line.length - valueStart) + event.event = line.toString('utf8', valueStart) + } } } + setEventFieldSize (field, size) { + const eventSize = this.eventSize - this.eventFieldSizes[field] + size + if (this.maxEventSize > 0 && eventSize > this.maxEventSize) { + throw createMaxEventSizeExceededError() + } + this.eventSize = eventSize + this.eventFieldSizes[field] = size + } + /** * @param {EventSourceStreamEvent} event */ @@ -384,13 +413,160 @@ class EventSourceStream extends Transform { } clearEvent () { - this.event = { - data: undefined, - event: undefined, - id: undefined, - retry: undefined + this.event.data = undefined + this.event.event = undefined + this.event.id = undefined + this.event.retry = undefined + this.eventSize = 0 + this.dataLineCount = 0 + this.eventFieldSizes.data = 0 + this.eventFieldSizes.event = 0 + this.eventFieldSizes.id = 0 + this.eventFieldSizes.retry = 0 + } + + hasPendingEvent () { + return this.event.data !== undefined || + this.event.event !== undefined || + this.event.id !== undefined || + this.event.retry !== undefined + } + + hasCurrentByte () { + return this.chunkIndex < this.chunks.length && + this.pos < this.chunks[this.chunkIndex].length + } + + currentByte () { + return this.chunks[this.chunkIndex][this.pos] + } + + consumeCurrentByte () { + this.advanceCursor() + this.syncLineStartToCursor() + } + + advanceCursor () { + this.pos++ + + while (this.chunkIndex < this.chunks.length && this.pos >= this.chunks[this.chunkIndex].length) { + this.chunkIndex++ + this.pos = 0 + } + } + + syncLineStartToCursor () { + this.lineChunkIndex = this.chunkIndex + this.linePos = this.pos + this.lineLength = 0 + this.dropConsumedChunks() + } + + dropConsumedChunks () { + if (this.lineChunkIndex > 0) { + const consumed = this.lineChunkIndex + this.chunks.splice(0, consumed) + this.lineChunkIndex = 0 + this.chunkIndex -= consumed + } + + if (this.chunkIndex === this.chunks.length) { + this.chunks.length = 0 + this.chunkIndex = 0 + this.pos = 0 + this.lineChunkIndex = 0 + this.linePos = 0 } } + + readLine () { + if (this.lineChunkIndex === this.chunkIndex) { + return this.chunks[this.chunkIndex].subarray(this.linePos, this.pos) + } + + const chunks = [] + let length = 0 + + for (let i = this.lineChunkIndex; i <= this.chunkIndex; i++) { + const chunk = this.chunks[i] + const start = i === this.lineChunkIndex ? this.linePos : 0 + const end = i === this.chunkIndex ? this.pos : chunk.length + const slice = chunk.subarray(start, end) + length += slice.length + chunks.push(slice) + } + + return Buffer.concat(chunks, length) + } + + peekBufferedByte (offset) { + let chunkIndex = this.lineChunkIndex + let pos = this.linePos + + while (chunkIndex < this.chunks.length) { + const chunk = this.chunks[chunkIndex] + const remaining = chunk.length - pos + + if (offset < remaining) { + return chunk[pos + offset] + } + + offset -= remaining + chunkIndex++ + pos = 0 + } + } + + discardLeadingBytes (count) { + while (count > 0 && this.lineChunkIndex < this.chunks.length) { + const chunk = this.chunks[this.lineChunkIndex] + const remaining = chunk.length - this.linePos + + if (count < remaining) { + this.linePos += count + count = 0 + } else { + count -= remaining + this.lineChunkIndex++ + this.linePos = 0 + } + } + + this.chunkIndex = this.lineChunkIndex + this.pos = this.linePos + this.dropConsumedChunks() + } + + handleBOM () { + const first = this.peekBufferedByte(0) + const second = this.peekBufferedByte(1) + const third = this.peekBufferedByte(2) + + if (second === undefined) { + if (first === BOM[0]) { + return true + } + + this.checkBOM = false + return true + } + + if (third === undefined) { + if (first === BOM[0] && second === BOM[1]) { + return true + } + + this.checkBOM = false + return false + } + + if (first === BOM[0] && second === BOM[1] && third === BOM[2]) { + this.discardLeadingBytes(3) + } + + this.checkBOM = false + return !this.hasCurrentByte() + } } module.exports = { diff --git a/deps/npm/node_modules/undici/lib/web/eventsource/eventsource.js b/deps/npm/node_modules/undici/lib/web/eventsource/eventsource.js index 5a488ffce276..6092645bf2c7 100644 --- a/deps/npm/node_modules/undici/lib/web/eventsource/eventsource.js +++ b/deps/npm/node_modules/undici/lib/web/eventsource/eventsource.js @@ -11,6 +11,7 @@ const { isNetworkError } = require('../fetch/response') const { delay } = require('./util') const { kEnumerableProperty } = require('../../core/util') const { environmentSettingsObject } = require('../fetch/util') +const { getGlobalDispatcher } = require('../../global') let experimentalWarned = false @@ -292,6 +293,7 @@ class EventSource extends EventTarget { const eventSourceStream = new EventSourceStream({ eventSourceSettings: this.#state, + maxEventSize: this.#dispatcher.eventSourceOptions?.maxEventSize, push: (event) => { this.dispatchEvent(createFastMessageEvent( event.type, @@ -470,7 +472,8 @@ webidl.converters.EventSourceInitDict = webidl.dictionaryConverter([ }, { key: 'dispatcher', // undici only - converter: webidl.converters.any + converter: webidl.converters.any, + defaultValue: () => getGlobalDispatcher() } ]) diff --git a/deps/npm/node_modules/undici/lib/web/fetch/index.js b/deps/npm/node_modules/undici/lib/web/fetch/index.js index 9a685d680a1c..b3b21873dcd5 100644 --- a/deps/npm/node_modules/undici/lib/web/fetch/index.js +++ b/deps/npm/node_modules/undici/lib/web/fetch/index.js @@ -1950,8 +1950,10 @@ async function httpNetworkFetch ( // 19. Run these steps in parallel: // 1. Run these steps, but abort when fetchParams is canceled: - fetchParams.controller.onAborted = onAborted - fetchParams.controller.on('terminated', onAborted) + if (!fetchParams.controller.resume) { + fetchParams.controller.on('terminated', onAborted) + } + fetchParams.controller.resume = async () => { // 1. While true while (true) { @@ -2120,8 +2122,6 @@ async function httpNetworkFetch ( } location = headersList.get('location', true) - this.body = new Readable({ read: resume }) - const decoders = [] const willFollow = location && request.redirect === 'follow' && @@ -2139,7 +2139,7 @@ async function httpNetworkFetch ( // CVE fix similar to urllib3 (GHSA-gm62-xv2j-4w53) and curl (CVE-2022-32206). const maxContentEncodings = 5 if (codings.length > maxContentEncodings) { - reject(new Error(`too many content-encodings in response: ${codings.length}, maximum allowed is ${maxContentEncodings}`)) + this.abort(new Error(`too many content-encodings in response: ${codings.length}, maximum allowed is ${maxContentEncodings}`)) return true } @@ -2172,6 +2172,8 @@ async function httpNetworkFetch ( } } + this.body = new Readable({ read: resume }) + const onError = this.onError.bind(this) resolve({ @@ -2218,10 +2220,6 @@ async function httpNetworkFetch ( fetchParams.controller.off('terminated', this.abort) } - if (fetchParams.controller.onAborted) { - fetchParams.controller.off('terminated', fetchParams.controller.onAborted) - } - fetchParams.controller.ended = true this.body.push(null) diff --git a/deps/npm/node_modules/undici/lib/web/websocket/connection.js b/deps/npm/node_modules/undici/lib/web/websocket/connection.js index bb87d361e4b7..1197b9b650c2 100644 --- a/deps/npm/node_modules/undici/lib/web/websocket/connection.js +++ b/deps/npm/node_modules/undici/lib/web/websocket/connection.js @@ -192,7 +192,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish, // is specified, the server needs to include the same field and one of // the selected subprotocol values in its response for the connection to // be established. - if (!requestProtocols.includes(secProtocol)) { + if (requestProtocols === null || !requestProtocols.includes(secProtocol)) { failWebsocketConnection(ws, 'Protocol was not set in the opening handshake.') return } diff --git a/deps/npm/node_modules/undici/lib/web/websocket/permessage-deflate.js b/deps/npm/node_modules/undici/lib/web/websocket/permessage-deflate.js index 6a6e43899c5a..0b3d493db820 100644 --- a/deps/npm/node_modules/undici/lib/web/websocket/permessage-deflate.js +++ b/deps/npm/node_modules/undici/lib/web/websocket/permessage-deflate.js @@ -63,7 +63,12 @@ class PerMessageDeflate { if (this.#maxPayloadSize > 0 && this.#inflate[kLength] > this.#maxPayloadSize) { callback(new MessageSizeExceededError()) + // The inflater may still hold buffered input that can emit a late + // zlib error. Remove the data listener, then deterministically stop + // the stream so a subsequent 'error' cannot fire without a listener + // (which would terminate the process as an unhandled error event). this.#inflate.removeAllListeners() + this.#inflate.destroy() this.#inflate = null return } diff --git a/deps/npm/node_modules/undici/lib/web/websocket/receiver.js b/deps/npm/node_modules/undici/lib/web/websocket/receiver.js index a7dea7fae1c1..b36ef9f93781 100644 --- a/deps/npm/node_modules/undici/lib/web/websocket/receiver.js +++ b/deps/npm/node_modules/undici/lib/web/websocket/receiver.js @@ -249,6 +249,20 @@ class ByteParser extends Writable { if (isControlFrame(this.#info.opcode)) { this.#loop = this.parseControlFrame(body) this.#state = parserStates.INFO + + // If the automatic PONG/CLOSE reply filled the socket's write buffer, + // stop parsing and resume on drain so a PING flood cannot grow the + // outbound queue without bound. + const socket = this.ws[kResponse].socket + if (this.#loop && socket.writableNeedDrain) { + this.#loop = false + socket.once('drain', () => { + if (this.destroyed) return + this.#loop = true + this.run(callback) + }) + return + } } else { if (!this.#info.compressed) { if (!this.writeFragments(body)) { diff --git a/deps/npm/node_modules/undici/package.json b/deps/npm/node_modules/undici/package.json index cd46deca2448..4bccc5455b89 100644 --- a/deps/npm/node_modules/undici/package.json +++ b/deps/npm/node_modules/undici/package.json @@ -1,6 +1,6 @@ { "name": "undici", - "version": "6.28.0", + "version": "6.29.1", "description": "An HTTP/1.1 client, written from scratch for Node.js", "homepage": "https://undici.nodejs.org", "bugs": { diff --git a/deps/npm/package.json b/deps/npm/package.json index 44b3ca520292..49b1151d17d2 100644 --- a/deps/npm/package.json +++ b/deps/npm/package.json @@ -1,5 +1,5 @@ { - "version": "11.20.0", + "version": "11.21.2", "name": "npm", "description": "a package manager for JavaScript", "workspaces": [ @@ -52,13 +52,13 @@ }, "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^9.9.2", - "@npmcli/config": "^10.13.0", + "@npmcli/arborist": "^9.9.3", + "@npmcli/config": "^10.14.0", "@npmcli/fs": "^5.0.0", "@npmcli/map-workspaces": "^5.0.3", "@npmcli/metavuln-calculator": "^9.0.3", "@npmcli/package-json": "^7.0.5", - "@npmcli/promise-spawn": "^9.0.1", + "@npmcli/promise-spawn": "^9.0.2", "@npmcli/redact": "^4.0.0", "@npmcli/run-script": "^10.0.4", "@sigstore/tuf": "^4.0.2", @@ -77,11 +77,11 @@ "is-cidr": "^6.0.4", "json-parse-even-better-errors": "^5.0.0", "libnpmaccess": "^10.0.3", - "libnpmdiff": "^8.1.13", - "libnpmexec": "^10.3.3", - "libnpmfund": "^7.0.27", + "libnpmdiff": "^8.1.14", + "libnpmexec": "^10.3.5", + "libnpmfund": "^7.0.28", "libnpmorg": "^8.0.1", - "libnpmpack": "^9.1.14", + "libnpmpack": "^9.1.15", "libnpmpublish": "^11.2.1", "libnpmsearch": "^9.0.1", "libnpmteam": "^8.0.2", @@ -101,12 +101,12 @@ "npm-registry-fetch": "^19.1.1", "npm-user-validate": "^4.0.0", "p-map": "^7.0.4", - "pacote": "^21.5.1", + "pacote": "^21.5.2", "parse-conflict-json": "^5.0.1", "proc-log": "^6.1.0", "qrcode-terminal": "^0.12.0", "read": "^5.0.1", - "semver": "^7.8.5", + "semver": "^7.8.6", "spdx-expression-parse": "^4.0.0", "ssri": "^13.0.1", "supports-color": "^10.2.2", diff --git a/deps/npm/test/fixtures/mock-oidc.js b/deps/npm/test/fixtures/mock-oidc.js index e2e11b27224b..86735571b94d 100644 --- a/deps/npm/test/fixtures/mock-oidc.js +++ b/deps/npm/test/fixtures/mock-oidc.js @@ -54,6 +54,7 @@ const mockOidc = async (t, { mockGithubOidcOptions = false, mockOidcTokenExchangeOptions = false, publishOptions = {}, + publish = true, provenance = false, oidcVisibilityOptions = false, }) => { @@ -139,7 +140,9 @@ const mockOidc = async (t, { registry.getVisibility({ spec: packageName, visibility: oidcVisibilityOptions }) } - registry.publish(packageName, publishOptions) + if (publish) { + registry.publish(packageName, publishOptions) + } /** * this will nock / mock all the successful requirements for provenance and diff --git a/deps/npm/test/lib/cli/entry.js b/deps/npm/test/lib/cli/entry.js index 369927dace0f..640b06359ccf 100644 --- a/deps/npm/test/lib/cli/entry.js +++ b/deps/npm/test/lib/cli/entry.js @@ -150,6 +150,36 @@ t.test('unsupported node version', async t => { ) }) +t.test('prerelease node versions', async t => { + for (const [version, supported] of [ + ['v27.0.0-alpha.0.0.0', true], + ['v28.0.0-alpha.0.0.0', true], + ['v27.0.0-rc.0', true], + ['v26.0.0-alpha.0', true], + ['v23.0.0-alpha.0', true], + ['v22.9.1-alpha.0', true], + ['v20.17.1-alpha.0', true], + ['v22.9.0-alpha.0', false], + ['v21.0.0-alpha.0', false], + ['v20.17.0-alpha.0', false], + ]) { + await t.test(version, async t => { + const { cli, logs } = await cliMock(t, { + globals: { + 'process.argv': ['node', 'npm', '--version'], + 'process.version': version, + }, + }) + await cli(process) + if (supported) { + t.strictSame(logs.warn, []) + } else { + t.match(logs.warn[0], `does not support Node.js ${version}.`) + } + }) + } +}) + t.test('non-ascii dash', async t => { const { cli, logs } = await cliMock(t, { globals: { diff --git a/deps/npm/test/lib/commands/approve-scripts.js b/deps/npm/test/lib/commands/approve-scripts.js index 1f29cb898949..f9ab63510a2e 100644 --- a/deps/npm/test/lib/commands/approve-scripts.js +++ b/deps/npm/test/lib/commands/approve-scripts.js @@ -859,3 +859,250 @@ t.test('approve-scripts --all with only bundled deps has nothing to review', asy const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8')) t.notOk(pkg.allowScripts, 'no allowScripts written') }) + +// Layout produced by install-strategy=linked: the real package lives in the store, node_modules holds a symlink to it, and the hidden lockfile records that isolated layout (npm/cli#9939). +const setupLinkedProject = (t, { allowScripts, noResolved = false } = {}) => { + const storeDir = 'canvas@1.0.0-c2FsdHNhbHRzYWx0c2FsdA' + const storeLoc = `node_modules/.store/${storeDir}/node_modules/canvas` + const tarUrl = noResolved + ? undefined + : 'https://registry.npmjs.org/canvas/-/canvas-1.0.0.tgz' + const pkg = { + name: 'host', + version: '1.0.0', + dependencies: { canvas: '^1.0.0' }, + } + if (allowScripts !== undefined) { + pkg.allowScripts = allowScripts + } + return { + 'package.json': JSON.stringify(pkg, null, 2), + 'package-lock.json': JSON.stringify({ + name: pkg.name, + version: pkg.version, + lockfileVersion: 3, + requires: true, + packages: { + '': pkg, + 'node_modules/canvas': { + version: '1.0.0', + resolved: tarUrl, + hasInstallScript: true, + }, + }, + }), + node_modules: { + '.package-lock.json': JSON.stringify({ + lockfileVersion: 3, + requires: true, + packages: { + [`node_modules/.store/${storeDir}`]: {}, + [storeLoc]: { + version: '1.0.0', + resolved: tarUrl, + hasInstallScript: true, + }, + 'node_modules/canvas': { + resolved: storeLoc, + link: true, + }, + }, + }), + '.store': { + [storeDir]: { + node_modules: { + canvas: { + 'package.json': JSON.stringify({ + name: 'canvas', + version: '1.0.0', + scripts: { install: 'echo install' }, + }), + }, + }, + }, + }, + canvas: t.fixture('symlink', `.store/${storeDir}/node_modules/canvas`), + }, + } +} + +// loadActual only trusts the hidden lockfile when it is newer than every dir under node_modules (assertNoNewer allows 10ms), and fixture creation order makes that racy, so bump its mtime past the fixture writes. +const trustHiddenLockfile = (prefix) => { + const future = new Date(Date.now() + 60_000) + fs.utimesSync(resolve(prefix, 'node_modules', '.package-lock.json'), future, future) +} + +t.test('approve-scripts under linked strategy writes a registry pin, not store paths', async t => { + const { npm, prefix } = await mockNpm(t, { + prefixDir: setupLinkedProject(t), + }) + trustHiddenLockfile(prefix) + await npm.exec('approve-scripts', ['canvas']) + + const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8')) + t.strictSame(pkg.allowScripts, { 'canvas@1.0.0': true }) +}) + +t.test('approve-scripts --all under linked strategy writes a registry pin, not store paths', async t => { + const { npm, prefix } = await mockNpm(t, { + prefixDir: setupLinkedProject(t), + config: { all: true }, + }) + trustHiddenLockfile(prefix) + await npm.exec('approve-scripts', []) + + const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8')) + t.strictSame(pkg.allowScripts, { 'canvas@1.0.0': true }) +}) + +t.test('approve-scripts --pending is empty when the registry pin covers a linked store package', async t => { + const { npm, prefix, joinedOutput } = await mockNpm(t, { + prefixDir: setupLinkedProject(t, { allowScripts: { 'canvas@1.0.0': true } }), + config: { 'allow-scripts-pending': true }, + }) + trustHiddenLockfile(prefix) + await npm.exec('approve-scripts', []) + t.match(joinedOutput(), /No packages with unreviewed install scripts/) +}) + +t.test('deny-scripts under linked strategy writes a name-only deny', async t => { + const { npm, prefix } = await mockNpm(t, { + prefixDir: setupLinkedProject(t), + }) + trustHiddenLockfile(prefix) + await npm.exec('deny-scripts', ['canvas']) + + const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8')) + t.strictSame(pkg.allowScripts, { canvas: false }) +}) + +const setupLocalFileDepProject = (t) => { + const pkg = { + name: 'host', + version: '1.0.0', + dependencies: { foo: 'file:./local-foo' }, + } + return { + 'package.json': JSON.stringify(pkg, null, 2), + 'package-lock.json': JSON.stringify({ + name: 'host', + version: '1.0.0', + lockfileVersion: 3, + requires: true, + packages: { + '': pkg, + 'local-foo': { + name: 'local-foo', + version: '1.0.0', + hasInstallScript: true, + }, + 'node_modules/foo': { + resolved: 'local-foo', + link: true, + }, + }, + }), + 'local-foo': { + 'package.json': JSON.stringify({ + name: 'local-foo', + version: '1.0.0', + scripts: { install: 'echo install' }, + }), + }, + node_modules: { + foo: t.fixture('symlink', '../local-foo'), + }, + } +} + +t.test('approve-scripts finds a local file: dep through its link', async t => { + // The Link is named `foo` while its target is named `local-foo`; positional matching must dereference the Link instead of skipping it (npm/cli#9939 review). + const { npm, prefix } = await mockNpm(t, { + prefixDir: setupLocalFileDepProject(t), + }) + await npm.exec('approve-scripts', ['foo']) + + const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8')) + t.strictSame(pkg.allowScripts, { 'file:../local-foo': true }) +}) + +t.test('deny-scripts finds a local file: dep through its link', async t => { + const { npm, prefix } = await mockNpm(t, { + prefixDir: setupLocalFileDepProject(t), + }) + await npm.exec('deny-scripts', ['foo']) + + const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8')) + t.strictSame(pkg.allowScripts, { 'file:../local-foo': false }) +}) + +t.test('approve-scripts under linked strategy without resolved URLs approves by name', async t => { + // omit-lockfile-registry-resolved: the store package has no resolved URL, so it cannot be pinned but must still be approved by name via the incoming Link's edge. + const { npm, prefix } = await mockNpm(t, { + prefixDir: setupLinkedProject(t, { noResolved: true }), + }) + trustHiddenLockfile(prefix) + await npm.exec('approve-scripts', ['canvas']) + + const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8')) + t.strictSame(pkg.allowScripts, { canvas: true }) +}) + +for (const state of ['missing', 'stale']) { + t.test(`prune keeps denies with a ${state} hidden lockfile`, async t => { + // With no trusted version, the versioned deny still blocks at runtime, so prune must fail closed and keep it instead of flipping the effective policy to allowed (npm/cli#9941 review). + const Arborist = require('@npmcli/arborist') + const isScriptAllowed = require('@npmcli/arborist/lib/script-allowed.js') + const allowScripts = { + canvas: true, + 'canvas@1.0.0': false, + } + + const { npm, prefix } = await mockNpm(t, { + prefixDir: setupLinkedProject(t, { allowScripts }), + config: { 'install-strategy': 'linked' }, + }) + + const hidden = resolve(prefix, 'node_modules', '.package-lock.json') + if (state === 'missing') { + fs.unlinkSync(hidden) + } else { + fs.utimesSync(hidden, new Date(0), new Date(0)) + } + + t.equal(npm.config.get('omit-lockfile-registry-resolved'), false) + + const arb = new Arborist({ ...npm.flatOptions, path: prefix }) + const tree = await arb.loadActual() + const target = [...tree.inventory.values()] + .find(node => !node.isLink && node.name === 'canvas') + + t.ok(target, 'finds the installed target') + t.strictSame( + isScriptAllowed.getTrustedRegistryIdentity(target), + { name: 'canvas', version: null }, + 'recovers the registry name without inventing a trusted version' + ) + t.equal( + isScriptAllowed(target, allowScripts), + false, + 'the versioned deny blocks the package before pruning' + ) + + await npm.exec('install-scripts', ['prune']) + + const pkg = JSON.parse( + fs.readFileSync(resolve(prefix, 'package.json'), 'utf8') + ) + t.strictSame( + pkg.allowScripts, + allowScripts, + 'pruning preserves both the allow and its active deny exception' + ) + t.equal( + isScriptAllowed(target, pkg.allowScripts), + false, + 'the package remains denied after pruning' + ) + }) +} diff --git a/deps/npm/test/lib/commands/dist-tag.js b/deps/npm/test/lib/commands/dist-tag.js index 5de9acb1da81..531ade8aab7e 100644 --- a/deps/npm/test/lib/commands/dist-tag.js +++ b/deps/npm/test/lib/commands/dist-tag.js @@ -1,6 +1,13 @@ const t = require('tap') const realFetch = require('npm-registry-fetch') +const npa = require('npm-package-arg') const mockNpm = require('../../fixtures/mock-npm') +const { + circleciIdToken, + githubIdToken, + gitlabIdToken, + mockOidc, +} = require('../../fixtures/mock-oidc') const fixtures = { workspace: { @@ -61,6 +68,30 @@ const tags = { }, } +const mockTagRequests = ({ + registry, + packageName, + token, + tags: tagData, + mutation, +}) => { + const spec = npa(packageName) + const path = `/-/package/${spec.escapedName}/dist-tags` + const authorization = `Bearer ${token}` + + registry.nock.get(registry.fullPath(path)) + .matchHeader('authorization', authorization) + .reply(200, tagData) + + if (mutation) { + registry.nock[mutation.method.toLowerCase()]( + registry.fullPath(`${path}/${encodeURIComponent(mutation.tag)}`) + ) + .matchHeader('authorization', authorization) + .reply(200, {}) + } +} + const mockDist = async (t, { ...npmOpts } = {}) => { const getTag = async (url) => ({ ...tags })[url] @@ -362,6 +393,351 @@ t.test('remove missing pkg name', async t => { ) }) +t.test('oidc token exchange', async t => { + const packageName = '@npmcli/test-package' + const fallbackToken = 'existing-fallback-token' + const exchangeToken = 'exchange-token' + const privateGithubToken = githubIdToken({ visibility: 'private' }) + + const setup = (t, options = {}) => mockOidc(t, { + packageName, + publish: false, + ...options, + }) + + t.test('list uses the exchanged token', async t => { + const { npm, registry } = await setup(t, { + oidcOptions: { github: true }, + mockGithubOidcOptions: { + audience: 'npm:registry.npmjs.org', + idToken: privateGithubToken, + }, + mockOidcTokenExchangeOptions: { + idToken: privateGithubToken, + body: { token: exchangeToken }, + }, + }) + mockTagRequests({ + registry, + packageName, + token: exchangeToken, + tags: { latest: '1.0.0' }, + }) + + await npm.exec('dist-tag', ['list', packageName]) + }) + + t.test('add uses the exchanged token for GET and PUT', async t => { + const { npm, registry } = await setup(t, { + oidcOptions: { github: true }, + mockGithubOidcOptions: { + audience: 'npm:registry.npmjs.org', + idToken: privateGithubToken, + }, + mockOidcTokenExchangeOptions: { + idToken: privateGithubToken, + body: { token: exchangeToken }, + }, + }) + mockTagRequests({ + registry, + packageName, + token: exchangeToken, + tags: { latest: '1.0.0' }, + mutation: { method: 'PUT', tag: 'next' }, + }) + + await npm.exec('dist-tag', ['add', `${packageName}@2.0.0`, 'next']) + }) + + t.test('remove uses the exchanged token for GET and DELETE', async t => { + const { npm, registry } = await setup(t, { + oidcOptions: { github: true }, + mockGithubOidcOptions: { + audience: 'npm:registry.npmjs.org', + idToken: privateGithubToken, + }, + mockOidcTokenExchangeOptions: { + idToken: privateGithubToken, + body: { token: exchangeToken }, + }, + }) + mockTagRequests({ + registry, + packageName, + token: exchangeToken, + tags: { latest: '1.0.0', old: '0.9.0' }, + mutation: { method: 'DELETE', tag: 'old' }, + }) + + await npm.exec('dist-tag', ['remove', packageName, 'old']) + }) + + for (const [provider, oidcOptions, idToken] of [ + ['gitlab', { gitlab: true, NPM_ID_TOKEN: gitlabIdToken({ visibility: 'private' }) }, gitlabIdToken({ visibility: 'private' })], + ['circleci', { circleci: true, NPM_ID_TOKEN: circleciIdToken() }, circleciIdToken()], + ]) { + t.test(`${provider} uses NPM_ID_TOKEN for exchange`, async t => { + const providerIdToken = oidcOptions.NPM_ID_TOKEN || idToken + const { npm, registry } = await setup(t, { + oidcOptions, + mockOidcTokenExchangeOptions: { + idToken: providerIdToken, + body: { token: exchangeToken }, + }, + }) + mockTagRequests({ + registry, + packageName, + token: exchangeToken, + tags: { latest: '1.0.0' }, + }) + + await npm.exec('dist-tag', ['list', packageName]) + }) + } + + t.test('custom scoped registry uses its audience and auth key', async t => { + const registryUrl = 'https://registry.zzz.org' + const { npm, registry } = await setup(t, { + oidcOptions: { github: true }, + config: { + '@npmcli:registry': registryUrl, + }, + mockGithubOidcOptions: { + audience: 'npm:registry.zzz.org', + idToken: privateGithubToken, + }, + mockOidcTokenExchangeOptions: { + idToken: privateGithubToken, + body: { token: exchangeToken }, + }, + load: { + registry: registryUrl, + }, + }) + mockTagRequests({ + registry, + packageName, + token: exchangeToken, + tags: { latest: '1.0.0' }, + }) + + await npm.exec('dist-tag', ['list', packageName]) + }) + + for (const testCase of [ + { + name: 'no CI', + oidcOptions: {}, + }, + { + name: 'missing GitHub OIDC permissions', + oidcOptions: { github: true, ACTIONS_ID_TOKEN_REQUEST_URL: '' }, + }, + { + name: 'GitHub identity token request failure', + oidcOptions: { github: true }, + mockGithubOidcOptions: { + audience: 'npm:registry.npmjs.org', + statusCode: 500, + }, + }, + { + name: 'token exchange failure', + oidcOptions: { github: true }, + mockGithubOidcOptions: { + audience: 'npm:registry.npmjs.org', + idToken: privateGithubToken, + }, + mockOidcTokenExchangeOptions: { + idToken: privateGithubToken, + statusCode: 500, + body: { message: 'exchange failed' }, + }, + }, + { + name: 'token exchange response missing token', + oidcOptions: { github: true }, + mockGithubOidcOptions: { + audience: 'npm:registry.npmjs.org', + idToken: privateGithubToken, + }, + mockOidcTokenExchangeOptions: { + idToken: privateGithubToken, + body: {}, + }, + }, + ]) { + t.test(`${testCase.name} preserves fallback credentials`, async t => { + const { npm, registry } = await setup(t, { + config: { + '//registry.npmjs.org/:_authToken': fallbackToken, + }, + ...testCase, + }) + mockTagRequests({ + registry, + packageName, + token: fallbackToken, + tags: { latest: '1.0.0' }, + }) + + await npm.exec('dist-tag', ['list', packageName]) + }) + } + + t.test('current package list exchanges after resolving its name', async t => { + const { npm, registry } = await setup(t, { + oidcOptions: { github: true }, + mockGithubOidcOptions: { + audience: 'npm:registry.npmjs.org', + idToken: privateGithubToken, + }, + mockOidcTokenExchangeOptions: { + idToken: privateGithubToken, + body: { token: exchangeToken }, + }, + }) + mockTagRequests({ + registry, + packageName, + token: exchangeToken, + tags: { latest: '1.0.0' }, + }) + + await npm.exec('dist-tag', ['list']) + }) + + t.test('workspace list exchanges separately for each package', async t => { + const workspaceToken = githubIdToken({ visibility: 'private' }) + const prefixDir = { + 'package.json': JSON.stringify({ + name: 'workspace-root', + version: '1.0.0', + workspaces: ['workspace-a', 'workspace-b'], + }), + 'workspace-a': { + 'package.json': JSON.stringify({ + name: 'workspace-a', + version: '1.0.0', + }), + }, + 'workspace-b': { + 'package.json': JSON.stringify({ + name: 'workspace-b', + version: '1.0.0', + }), + }, + } + const { npm, registry } = await setup(t, { + oidcOptions: { github: true }, + config: { workspaces: true }, + mockGithubOidcOptions: { + audience: 'npm:registry.npmjs.org', + idToken: workspaceToken, + times: 2, + }, + load: { prefixDir }, + }) + + for (const [name, token] of [ + ['workspace-a', 'workspace-a-exchange-token'], + ['workspace-b', 'workspace-b-exchange-token'], + ]) { + registry.mockOidcTokenExchange({ + packageName: name, + idToken: workspaceToken, + body: { token }, + }) + mockTagRequests({ + registry, + packageName: name, + token, + tags: { latest: '1.0.0' }, + }) + } + + await npm.exec('dist-tag', []) + }) + + t.test('workspace list restores fallback credentials after a failed exchange', async t => { + const { npm, registry } = await setup(t, { + oidcOptions: { github: true }, + config: { workspaces: true }, + mockGithubOidcOptions: { + audience: 'npm:registry.npmjs.org', + idToken: privateGithubToken, + times: 3, + }, + load: { + prefixDir: fixtures.workspace, + homeDir: { + '.npmrc': `//registry.npmjs.org/:_authToken=${fallbackToken}`, + }, + }, + }) + for (const [name, token] of [ + ['workspace-a', exchangeToken], + ['workspace-b', fallbackToken], + ['workspace-c', 'workspace-c-exchange-token'], + ]) { + const failed = name === 'workspace-b' + registry.mockOidcTokenExchange({ + packageName: name, + idToken: privateGithubToken, + statusCode: failed ? 404 : 200, + body: failed ? { message: 'No trusted publisher configured' } : { token }, + }) + mockTagRequests({ + registry, + packageName: name, + token, + tags: { latest: '1.0.0' }, + }) + } + + await npm.exec('dist-tag', []) + }) + + t.test('does not run publish provenance checks', async t => { + const publicGithubToken = githubIdToken({ visibility: 'public' }) + let visibilityCalls = 0 + const { npm, registry } = await setup(t, { + oidcOptions: { github: true }, + mockGithubOidcOptions: { + audience: 'npm:registry.npmjs.org', + idToken: publicGithubToken, + }, + mockOidcTokenExchangeOptions: { + idToken: publicGithubToken, + body: { token: exchangeToken }, + }, + load: { + mocks: { + libnpmaccess: { + getVisibility: () => { + visibilityCalls++ + return { public: true } + }, + }, + }, + }, + }) + mockTagRequests({ + registry, + packageName, + token: exchangeToken, + tags: { latest: '1.0.0' }, + }) + + await npm.exec('dist-tag', ['list', packageName]) + t.equal(visibilityCalls, 0, 'must not check package visibility') + }) + + t.end() +}) + t.test('completion', async t => { const { distTag } = await mockDist(t) diff --git a/deps/npm/test/lib/commands/install-scripts.js b/deps/npm/test/lib/commands/install-scripts.js index 523aba42bdd9..e3c42ac2485f 100644 --- a/deps/npm/test/lib/commands/install-scripts.js +++ b/deps/npm/test/lib/commands/install-scripts.js @@ -334,3 +334,64 @@ t.test('install-scripts prune fails for global installs', async t => { { code: 'EGLOBAL' } ) }) + +t.test('install-scripts prune under linked strategy keeps the registry pin, drops store paths', async t => { + // Regression for npm/cli#9939: the buggy writer produced file:.store keys; prune must drop those and keep the valid registry pin covering the store package. + const storeDir = 'canvas@1.0.0-c2FsdHNhbHRzYWx0c2FsdA' + const storeLoc = `node_modules/.store/${storeDir}/node_modules/canvas` + const tarUrl = 'https://registry.npmjs.org/canvas/-/canvas-1.0.0.tgz' + const pkg = { + name: 'host', + version: '1.0.0', + dependencies: { canvas: '^1.0.0' }, + allowScripts: { + 'canvas@1.0.0': true, + [`file:.store/${storeDir}/node_modules/canvas`]: true, + }, + } + const { npm, prefix, joinedOutput } = await mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify(pkg, null, 2), + node_modules: { + '.package-lock.json': JSON.stringify({ + lockfileVersion: 3, + requires: true, + packages: { + [`node_modules/.store/${storeDir}`]: {}, + [storeLoc]: { + version: '1.0.0', + resolved: tarUrl, + hasInstallScript: true, + }, + 'node_modules/canvas': { + resolved: storeLoc, + link: true, + }, + }, + }), + '.store': { + [storeDir]: { + node_modules: { + canvas: { + 'package.json': JSON.stringify({ + name: 'canvas', + version: '1.0.0', + scripts: { install: 'echo install' }, + }), + }, + }, + }, + }, + canvas: t.fixture('symlink', `.store/${storeDir}/node_modules/canvas`), + }, + }, + }) + // loadActual only trusts the hidden lockfile when it is newer than every dir under node_modules (assertNoNewer allows 10ms), and fixture creation order makes that racy, so bump its mtime past the fixture writes. + const future = new Date(Date.now() + 60_000) + fs.utimesSync(resolve(prefix, 'node_modules', '.package-lock.json'), future, future) + await npm.exec('install-scripts', ['prune']) + + const updated = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8')) + t.strictSame(updated.allowScripts, { 'canvas@1.0.0': true }) + t.match(joinedOutput(), /file:\.store.*\(package not installed\)/) +}) diff --git a/deps/npm/test/lib/commands/publish.js b/deps/npm/test/lib/commands/publish.js index 8b20c5671aad..c0f231dc9dbe 100644 --- a/deps/npm/test/lib/commands/publish.js +++ b/deps/npm/test/lib/commands/publish.js @@ -1363,6 +1363,123 @@ t.test('oidc token exchange - no provenance', t => { }, })) + for (const fallback of [true, false]) { + t.test(`workspace exchange failure ${fallback ? 'restores fallback' : 'requires auth'}`, async t => { + const fallbackToken = 'existing-fallback-token' + const { npm, registry } = await mockOidc(t, { + oidcOptions: { github: true }, + publish: false, + config: { workspaces: true }, + mockGithubOidcOptions: { + audience: 'npm:registry.npmjs.org', + idToken: githubPrivateIdToken, + times: 2, + }, + load: { + homeDir: fallback ? { + '.npmrc': `//registry.npmjs.org/:_authToken=${fallbackToken}`, + } : {}, + prefixDir: { + 'package.json': JSON.stringify({ + name: 'workspace-root', + version: '1.0.0', + workspaces: ['workspace-a', 'workspace-b'], + }), + 'workspace-a': { + 'package.json': JSON.stringify({ name: 'workspace-a', version: '1.0.0' }), + }, + 'workspace-b': { + 'package.json': JSON.stringify({ name: 'workspace-b', version: '1.0.0' }), + }, + }, + }, + }) + registry.mockOidcTokenExchange({ + packageName: 'workspace-a', + idToken: githubPrivateIdToken, + body: { token: 'workspace-a-exchange-token' }, + }) + registry.publish('workspace-a', { token: 'workspace-a-exchange-token' }) + registry.mockOidcTokenExchange({ + packageName: 'workspace-b', + idToken: githubPrivateIdToken, + statusCode: 404, + body: { message: 'No trusted publisher configured' }, + }) + if (fallback) { + registry.publish('workspace-b', { token: fallbackToken }) + await npm.exec('publish', []) + } else { + await t.rejects(npm.exec('publish', []), { code: 'ENEEDAUTH' }) + } + t.equal( + npm.config.getCredentialsByURI('https://registry.npmjs.org/').token, + fallback ? fallbackToken : undefined + ) + }) + } + + t.test('sequential operations clear OIDC tokens after exchange failure', async t => { + // These calls share one npm instance, unlike separate CLI invocations. + // The workspace tests above cover one real --workspaces command; this also checks recovery. + const packages = ['workflow-one', 'workflow-two', 'workflow-three'] + const registryUrl = 'https://registry.npmjs.org/' + const authTokenKey = '//registry.npmjs.org/:_authToken' + const { npm, registry, joinedOutput } = await mockOidc(t, { + oidcOptions: { github: true }, + publish: false, + mockGithubOidcOptions: { + audience: 'npm:registry.npmjs.org', + idToken: githubPrivateIdToken, + times: 3, + }, + load: { + prefixDir: { + 'package.json': JSON.stringify({ name: 'workflow-root', version: '1.0.0' }), + ...Object.fromEntries(packages.map(name => [name, { + 'package.json': JSON.stringify({ name, version: '1.0.0' }), + }])), + }, + }, + }) + t.equal(npm.config.getCredentialsByURI(registryUrl).token, undefined, 'no fallback token') + + registry.mockOidcTokenExchange({ + packageName: packages[0], + idToken: githubPrivateIdToken, + body: { token: 'first-exchange-token' }, + }) + registry.publish(packages[0], { token: 'first-exchange-token' }) + await npm.exec('publish', [`./${packages[0]}`]) + t.equal(npm.flatOptions[authTokenKey], 'first-exchange-token') + t.match(joinedOutput(), '+ workflow-one@1.0.0') + + registry.mockOidcTokenExchange({ + packageName: packages[1], + idToken: githubPrivateIdToken, + statusCode: 404, + body: { message: 'No trusted publisher configured' }, + }) + await t.rejects( + npm.exec('publish', [`./${packages[1]}`]), + { code: 'ENEEDAUTH' }, + 'failed exchange cannot reuse the previous operation token' + ) + t.equal(npm.config.getCredentialsByURI(registryUrl).token, undefined, 'token is cleared') + t.notOk(Object.hasOwn(npm.flatOptions, authTokenKey), 'flat options contain no stale token') + t.notMatch(joinedOutput(), '+ workflow-two@1.0.0') + + registry.mockOidcTokenExchange({ + packageName: packages[2], + idToken: githubPrivateIdToken, + body: { token: 'third-exchange-token' }, + }) + registry.publish(packages[2], { token: 'third-exchange-token' }) + await npm.exec('publish', [`./${packages[2]}`]) + t.equal(npm.flatOptions[authTokenKey], 'third-exchange-token') + t.match(joinedOutput(), '+ workflow-three@1.0.0') + }) + t.test('dry-run can be used to check oidc config but not publish', oidcPublishTest({ oidcOptions: { github: true }, config: { diff --git a/deps/npm/test/lib/commands/stage/index.js b/deps/npm/test/lib/commands/stage/index.js index bd81c7aab5e4..127c0ea82528 100644 --- a/deps/npm/test/lib/commands/stage/index.js +++ b/deps/npm/test/lib/commands/stage/index.js @@ -3,6 +3,7 @@ const { load: loadMockNpm } = require('../../../fixtures/mock-npm') const MockRegistry = require('@npmcli/mock-registry') const path = require('node:path') const fs = require('node:fs') +const { githubIdToken, mockOidc } = require('../../../fixtures/mock-oidc') const pkg = '@npmcli/test-package' const token = 'test-auth-token' @@ -31,6 +32,32 @@ t.test('stages a package from cwd', async t => { t.match(joinedOutput(), /\+ @npmcli\/test-package@1\.0\.0 \(staged\)/) }) +t.test('stages a package with the OIDC exchange token', async t => { + const idToken = githubIdToken({ visibility: 'private' }) + const exchangeToken = 'exchange-token' + const { npm, registry } = await mockOidc(t, { + packageName: pkg, + oidcOptions: { github: true }, + mockGithubOidcOptions: { + audience: 'npm:registry.npmjs.org', + idToken, + }, + mockOidcTokenExchangeOptions: { + idToken, + body: { token: exchangeToken }, + }, + publishOptions: { + noPut: true, + }, + }) + + registry.nock.post('/-/stage/package/@npmcli%2ftest-package') + .matchHeader('authorization', `Bearer ${exchangeToken}`) + .reply(201, {}) + + await npm.exec('stage', ['publish']) +}) + t.test('stages with --dry-run', async t => { const { npm, joinedOutput, logs } = await loadMockNpm(t, { config: { ...authConfig, 'dry-run': true }, diff --git a/deps/npm/test/lib/utils/oidc.js b/deps/npm/test/lib/utils/oidc.js new file mode 100644 index 000000000000..72f772612663 --- /dev/null +++ b/deps/npm/test/lib/utils/oidc.js @@ -0,0 +1,239 @@ +const t = require('tap') +const { readFile } = require('node:fs/promises') +const { join } = require('node:path') +const mockGlobals = require('@npmcli/mock-globals') +const mockNpm = require('../../fixtures/mock-npm') +const tmock = require('../../fixtures/tmock') + +const registry = 'https://registry.npmjs.org/' +const authTokenKey = '//registry.npmjs.org/:_authToken' +const fallbackToken = 'fallback-token' +const exchangeToken = 'exchange-token' + +const setup = async t => { + const { npm } = await mockNpm(t, { + config: ({ globalPrefix, home, prefix }) => ({ + globalconfig: join(globalPrefix, 'etc', 'npmrc'), + prefix, + userconfig: join(home, '.npmrc'), + }), + globals: ({ globalPrefix, home, prefix }) => ({ + 'process.env.NPM_CONFIG_GLOBALCONFIG': join(globalPrefix, 'etc', 'npmrc'), + 'process.env.NPM_CONFIG_PREFIX': prefix, + 'process.env.NPM_CONFIG_USERCONFIG': join(home, '.npmrc'), + }), + npm: ({ other }) => ({ + npmRoot: join(other, 'node'), + }), + otherDirs: { + node: { npmrc: '' }, + }, + }) + mockGlobals(t, { + 'process.env.NPM_ID_TOKEN': 'identity-token', + 'process.env.ACTIONS_ID_TOKEN_REQUEST_URL': 'https://github.com/actions/id-token', + 'process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN': 'request-token', + }) + const ci = { GITLAB: true } + const responses = [{ token: exchangeToken }] + const { oidc } = tmock(t, '{LIB}/utils/oidc.js', { + 'ci-info': ci, + 'npm-registry-fetch': { + json: async (_url, opts) => { + const response = responses.shift() + if (response instanceof Error) { + throw response + } + if (typeof response === 'function') { + return response(opts) + } + return response + }, + }, + 'make-fetch-happen': async () => ({ + ok: false, + status: 500, + json: async () => ({}), + }), + }) + const run = async (opts = { ...npm.flatOptions }, registryUrl = registry) => { + await oidc({ packageName: 'test-package', registry: registryUrl, opts, config: npm.config }) + return opts + } + return { npm, ci, responses, run, oidc } +} + +for (const failure of [ + 'exchange failure', + 'missing exchange token', + 'missing identity token', + 'unsupported CI', + 'missing GitHub permissions', + 'GitHub identity request failure', +]) { + t.test(`restores credentials before ${failure}`, async t => { + const { npm, ci, responses, run } = await setup(t) + npm.config.set(authTokenKey, fallbackToken, 'user') + const firstOpts = await run() + t.equal(firstOpts[authTokenKey], exchangeToken) + t.equal(npm.config.get(authTokenKey), exchangeToken) + t.equal(npm.config.get(authTokenKey, 'cli'), exchangeToken) + t.equal(npm.config.get(authTokenKey, 'user'), fallbackToken, 'user config is never overwritten') + + if (failure === 'exchange failure') { + responses.push(new Error('exchange failed')) + } else if (failure === 'missing exchange token') { + responses.push({}) + } else if (failure === 'unsupported CI') { + ci.GITLAB = false + } else { + delete process.env.NPM_ID_TOKEN + if (failure !== 'missing identity token') { + ci.GITLAB = false + ci.GITHUB_ACTIONS = true + if (failure === 'missing GitHub permissions') { + delete process.env.ACTIONS_ID_TOKEN_REQUEST_URL + } + } + } + + const nextOpts = await run() + t.equal(nextOpts[authTokenKey], fallbackToken, 'next request uses fallback credentials') + t.equal(firstOpts[authTokenKey], fallbackToken, 'previous options no longer contain the token') + t.equal(npm.config.get(authTokenKey, 'cli'), undefined, 'clears the runtime override') + t.equal(npm.config.get(authTokenKey, 'user'), fallbackToken, 'preserves user config') + t.equal(npm.flatOptions[authTokenKey], fallbackToken, 'restores flattened config') + }) +} + +for (const source of ['user', 'global', 'project', 'env', 'cli', 'none']) { + t.test(`preserves ${source} credentials when reusing flat options`, async t => { + const { npm, responses, run } = await setup(t) + if (source !== 'none') { + npm.config.set(authTokenKey, fallbackToken, source) + } + const opts = npm.flatOptions + await run(opts) + t.equal(npm.config.getCredentialsByURI(registry).token, exchangeToken) + // Populate the flat-options cache with the temporary runtime token. + const nextOpts = npm.flatOptions + responses.push({}) + await run(opts) + + const expected = source === 'none' ? undefined : fallbackToken + t.equal(opts[authTokenKey], expected) + t.equal(npm.config.get(authTokenKey), expected) + t.equal(npm.flatOptions[authTokenKey], expected) + t.equal(npm.config.get(authTokenKey, 'user'), source === 'user' ? fallbackToken : undefined) + t.not(npm.flatOptions, nextOpts, 'invalidates the cached temporary credentials') + if (source === 'none') { + t.notOk(Object.hasOwn(opts, authTokenKey), 'removes the temporary option entirely') + } + }) +} + +t.test('does not restore an earlier exchange token after multiple successes', async t => { + const { npm, responses, run } = await setup(t) + npm.config.set(authTokenKey, fallbackToken, 'user') + await run() + responses.push({ token: 'second-exchange-token' }) + const secondOpts = await run() + t.equal(secondOpts[authTokenKey], 'second-exchange-token') + responses.push({}) + const thirdOpts = await run() + t.equal(thirdOpts[authTokenKey], fallbackToken) + t.equal(npm.config.get(authTokenKey), fallbackToken) +}) + +t.test('clears the previous token before starting another exchange', async t => { + const { npm, responses, run } = await setup(t) + npm.config.set(authTokenKey, fallbackToken, 'user') + const firstOpts = await run() + responses.push(opts => { + t.equal(npm.config.get(authTokenKey), fallbackToken, 'old token is already cleared') + t.equal(firstOpts[authTokenKey], fallbackToken, 'old request options are already cleared') + t.equal(opts[authTokenKey], 'identity-token', 'exchange uses the identity token') + return { token: 'new-exchange-token' } + }) + const nextOpts = await run() + t.equal(nextOpts[authTokenKey], 'new-exchange-token') + t.equal(npm.config.get(authTokenKey), 'new-exchange-token') +}) + +t.test('clears the previous registry token when switching registries', async t => { + const { npm, responses, run } = await setup(t) + const otherRegistry = 'https://other.registry.npmjs.org/' + const otherKey = '//other.registry.npmjs.org/:_authToken' + npm.config.set(authTokenKey, fallbackToken, 'user') + npm.config.set(otherKey, 'other-fallback', 'user') + await run() + responses.push({ token: 'other-exchange-token' }) + const otherOpts = await run(undefined, otherRegistry) + t.equal(otherOpts[authTokenKey], fallbackToken) + t.equal(otherOpts[otherKey], 'other-exchange-token') + responses.push({}) + const nextOpts = await run() + t.equal(nextOpts[authTokenKey], fallbackToken) + t.equal(nextOpts[otherKey], 'other-fallback') + t.equal(npm.config.get(otherKey), 'other-fallback') +}) + +t.test('preserves credentials explicitly replaced after an exchange', async t => { + const { npm, responses, run } = await setup(t) + const opts = await run() + npm.config.set(authTokenKey, 'updated-user-token', 'user') + opts[authTokenKey] = 'updated-request-token' + responses.push({}) + await run(opts) + t.equal(npm.config.get(authTokenKey), 'updated-user-token') + t.equal(opts[authTokenKey], 'updated-request-token') +}) + +t.test('preserves a runtime credential replaced independently of OIDC', async t => { + const { npm, responses, run } = await setup(t) + await run() + npm.config.set(authTokenKey, 'updated-runtime-token', 'cli') + responses.push({}) + const opts = await run() + t.equal(npm.config.get(authTokenKey, 'cli'), 'updated-runtime-token') + t.equal(opts[authTokenKey], 'updated-runtime-token') +}) + +t.test('saving config during an exchange never persists the OIDC token', async t => { + const { npm, run } = await setup(t) + const sources = ['user', 'project', 'global'] + for (const source of sources) { + npm.config.set(authTokenKey, `${source}-fallback`, source) + } + await run() + t.equal(npm.config.get(authTokenKey, 'cli'), exchangeToken) + await t.rejects(npm.config.save('cli'), { message: 'invalid config location param: cli' }) + for (const source of sources) { + t.equal(npm.config.get(authTokenKey, source), `${source}-fallback`) + await npm.config.save(source) + const content = await readFile(npm.config.data.get(source).source, 'utf8') + t.match(content, `${authTokenKey}=${source}-fallback`) + t.notMatch(content, exchangeToken) + } +}) + +t.test('the next invocation clears the token even with a different config', async t => { + const { npm, ci, run, oidc } = await setup(t) + npm.config.set(authTokenKey, fallbackToken, 'user') + const opts = await run() + ci.GITLAB = false + await oidc({ packageName: 'other-package', registry, opts: {}, config: {} }) + t.equal(npm.config.get(authTokenKey, 'cli'), undefined) + t.equal(npm.config.get(authTokenKey), fallbackToken) + t.equal(opts[authTokenKey], fallbackToken) +}) + +t.test('does not copy package-specific fallback options into the next package', async t => { + const { npm, responses, run } = await setup(t) + npm.config.set(authTokenKey, fallbackToken, 'user') + const firstOpts = await run({ ...npm.flatOptions, [authTokenKey]: 'package-specific-token' }) + responses.push({}) + const nextOpts = await run() + t.equal(firstOpts[authTokenKey], 'package-specific-token') + t.equal(nextOpts[authTokenKey], fallbackToken) +})