diff --git a/deps/npm/docs/content/commands/npm-approve-scripts.md b/deps/npm/docs/content/commands/npm-approve-scripts.md new file mode 100644 index 00000000000000..e3445447c79052 --- /dev/null +++ b/deps/npm/docs/content/commands/npm-approve-scripts.md @@ -0,0 +1,125 @@ +--- +title: npm-approve-scripts +section: 1 +description: Approve install scripts for specific dependencies +--- + +### Synopsis + +```bash +npm approve-scripts [ ...] +npm approve-scripts --all +npm approve-scripts --allow-scripts-pending +``` + +Note: This command is unaware of workspaces. + +### Description + +Manages the `allowScripts` field in your project's `package.json`, which +records which of your dependencies are permitted to run install scripts +(`preinstall`, `install`, `postinstall`, and `prepare` for non-registry +sources). This command is the recommended way to maintain that field. + +In the current release, this field is advisory: install scripts still run +by default, but installs print a list of packages whose scripts have not +been reviewed. A future release will block unreviewed install scripts. + +There are three modes: + +```bash +npm approve-scripts [ ...] +npm approve-scripts --all +npm approve-scripts --allow-scripts-pending +``` + +`` matches every installed version of that package. By default the +command writes pinned entries (`pkg@1.2.3`), which keep their approval +narrowed to the specific version you reviewed. Pass `--no-allow-scripts-pin` to write +name-only entries that allow any future version. + +`--all` approves every package with unreviewed install scripts in one go. + +`--allow-scripts-pending` is read-only: it lists every package whose install scripts +are not yet covered by `allowScripts`, without modifying `package.json`. + +`approve-scripts` honours the asymmetric pin rule: if you re-approve a +package whose installed version has changed, the existing pin is rewritten +to track the new installed version. Multi-version statements +(`pkg@1 || 2`) are left alone, since they likely capture intent that +the command cannot infer. Existing `false` entries always win; +`approve-scripts` will not silently re-allow a package you previously +denied. + +### Examples + +```bash +# Approve all currently-installed install scripts after reviewing them +npm approve-scripts --all + +# Approve specific packages, pinned to their installed version +npm approve-scripts canvas sharp + +# Approve name-only (any version of this package is allowed) +npm approve-scripts --no-allow-scripts-pin canvas + +# Preview which packages still need review +npm approve-scripts --allow-scripts-pending +``` + +### Configuration + +#### `all` + +* Default: false +* Type: Boolean + +When running `npm outdated` and `npm ls`, setting `--all` will show all +outdated or installed packages, rather than only those directly depended +upon by the current project. + + + +#### `allow-scripts-pending` + +* Default: false +* Type: Boolean + +List packages with install scripts that are not yet covered by the +`allowScripts` policy, without modifying `package.json`. Only meaningful for +`npm approve-scripts`. + + + +#### `allow-scripts-pin` + +* Default: true +* Type: Boolean + +Write pinned (`pkg@version`) entries when approving install scripts. Set to +`false` to write name-only entries that allow any version. Has no effect on +`npm deny-scripts`, which always writes name-only entries regardless of this +setting. + + + +#### `json` + +* Default: false +* Type: Boolean + +Whether or not to output JSON data, rather than the normal output. + +* In `npm pkg set` it enables parsing set values with JSON.parse() before + saving them to your `package.json`. + +Not supported by all npm commands. + + + +### See Also + +* [npm deny-scripts](/commands/npm-deny-scripts) +* [npm install](/commands/npm-install) +* [npm rebuild](/commands/npm-rebuild) +* [package.json](/configuring-npm/package-json) diff --git a/deps/npm/docs/content/commands/npm-ci.md b/deps/npm/docs/content/commands/npm-ci.md index 6f8dd5bd3f6655..bc460070459604 100644 --- a/deps/npm/docs/content/commands/npm-ci.md +++ b/deps/npm/docs/content/commands/npm-ci.md @@ -262,6 +262,56 @@ like `npm view` +#### `allow-scripts` + +* Default: "" +* Type: String (can be set multiple times) + +Comma-separated list of packages whose install-time lifecycle scripts +(`preinstall`, `install`, `postinstall`, and `prepare` for non-registry +dependencies) are allowed to run. + +This setting is intended for one-off and global contexts: `npm exec`, `npx`, +and `npm install -g`, where no project `package.json` is involved. For +team-wide policy in a project, use the `allowScripts` field in +`package.json` (which also supports explicit denials), or configure it in +`.npmrc`. Passing `--allow-scripts` on the command line during a +project-scoped `npm install`, `ci`, `update`, or `rebuild` is an error. + +Each name is matched against a dependency's resolved identity, not against +the package's self-reported name. `--ignore-scripts` and +`--dangerously-allow-all-scripts` both override this setting. + + + +#### `strict-allow-scripts` + +* Default: false +* Type: Boolean + +If `true`, turn the install-script policy from a warning into a hard error: +any dependency with install scripts not covered by `allowScripts` will fail +the install instead of running with a notice. + +Dependencies explicitly denied with `false` in `allowScripts` are always +silently skipped; this setting only affects unreviewed entries. +`--ignore-scripts` and `--dangerously-allow-all-scripts` both override this +setting. + + + +#### `dangerously-allow-all-scripts` + +* Default: false +* Type: Boolean + +If `true`, bypass the `allowScripts` policy entirely and run every +dependency install script regardless of whether it was approved or denied. +Intended as a migration escape hatch only; its use is strongly discouraged. +`--ignore-scripts` still takes precedence over this setting. + + + #### `audit` * Default: true diff --git a/deps/npm/docs/content/commands/npm-deny-scripts.md b/deps/npm/docs/content/commands/npm-deny-scripts.md new file mode 100644 index 00000000000000..51915b09fe1dfc --- /dev/null +++ b/deps/npm/docs/content/commands/npm-deny-scripts.md @@ -0,0 +1,109 @@ +--- +title: npm-deny-scripts +section: 1 +description: Deny install scripts for specific dependencies +--- + +### Synopsis + +```bash +npm deny-scripts [ ...] +npm deny-scripts --all +``` + +Note: This command is unaware of workspaces. + +### Description + +The companion command to [`npm approve-scripts`](/commands/npm-approve-scripts). +Writes `false` entries into the `allowScripts` field of your project's +`package.json`, recording that a dependency must not run install scripts +even if a future version would otherwise be eligible. + +In the current release, install scripts still run by default, so `deny-scripts` +only affects how installs of denied packages are reported. A future release +will block unreviewed install scripts and respect deny entries at install +time. + +```bash +npm deny-scripts [ ...] +npm deny-scripts --all +``` + +`` matches every installed version of that package. Denies are always +written name-only (`"pkg": false`), regardless of `--allow-scripts-pin`. Pinning a deny +to a specific version would silently re-allow scripts for any other version +of the same package, which defeats the purpose; the command picks the +safer default for you. + +`--all` denies every package with unreviewed install scripts. + +If a `true` (pinned or name-only) entry exists for a package and you then +deny it, the existing allow entries are removed so the name-only deny is +unambiguous. + +### Examples + +```bash +# Deny a specific package outright +npm deny-scripts telemetry-pkg + +# Deny everything that has install scripts and isn't already approved +npm deny-scripts --all +``` + +### Configuration + +#### `all` + +* Default: false +* Type: Boolean + +When running `npm outdated` and `npm ls`, setting `--all` will show all +outdated or installed packages, rather than only those directly depended +upon by the current project. + + + +#### `allow-scripts-pending` + +* Default: false +* Type: Boolean + +List packages with install scripts that are not yet covered by the +`allowScripts` policy, without modifying `package.json`. Only meaningful for +`npm approve-scripts`. + + + +#### `allow-scripts-pin` + +* Default: true +* Type: Boolean + +Write pinned (`pkg@version`) entries when approving install scripts. Set to +`false` to write name-only entries that allow any version. Has no effect on +`npm deny-scripts`, which always writes name-only entries regardless of this +setting. + + + +#### `json` + +* Default: false +* Type: Boolean + +Whether or not to output JSON data, rather than the normal output. + +* In `npm pkg set` it enables parsing set values with JSON.parse() before + saving them to your `package.json`. + +Not supported by all npm commands. + + + +### See Also + +* [npm approve-scripts](/commands/npm-approve-scripts) +* [npm install](/commands/npm-install) +* [package.json](/configuring-npm/package-json) diff --git a/deps/npm/docs/content/commands/npm-exec.md b/deps/npm/docs/content/commands/npm-exec.md index 72c63163be4d2f..13a0939209a5ea 100644 --- a/deps/npm/docs/content/commands/npm-exec.md +++ b/deps/npm/docs/content/commands/npm-exec.md @@ -158,6 +158,56 @@ the specified workspaces, and not on the root project. This value is not exported to the environment for child processes. +#### `allow-scripts` + +* Default: "" +* Type: String (can be set multiple times) + +Comma-separated list of packages whose install-time lifecycle scripts +(`preinstall`, `install`, `postinstall`, and `prepare` for non-registry +dependencies) are allowed to run. + +This setting is intended for one-off and global contexts: `npm exec`, `npx`, +and `npm install -g`, where no project `package.json` is involved. For +team-wide policy in a project, use the `allowScripts` field in +`package.json` (which also supports explicit denials), or configure it in +`.npmrc`. Passing `--allow-scripts` on the command line during a +project-scoped `npm install`, `ci`, `update`, or `rebuild` is an error. + +Each name is matched against a dependency's resolved identity, not against +the package's self-reported name. `--ignore-scripts` and +`--dangerously-allow-all-scripts` both override this setting. + + + +#### `strict-allow-scripts` + +* Default: false +* Type: Boolean + +If `true`, turn the install-script policy from a warning into a hard error: +any dependency with install scripts not covered by `allowScripts` will fail +the install instead of running with a notice. + +Dependencies explicitly denied with `false` in `allowScripts` are always +silently skipped; this setting only affects unreviewed entries. +`--ignore-scripts` and `--dangerously-allow-all-scripts` both override this +setting. + + + +#### `dangerously-allow-all-scripts` + +* Default: false +* Type: Boolean + +If `true`, bypass the `allowScripts` policy entirely and run every +dependency install script regardless of whether it was approved or denied. +Intended as a migration escape hatch only; its use is strongly discouraged. +`--ignore-scripts` still takes precedence over this setting. + + + ### Examples Run the version of `tap` in the local dependencies, with the provided arguments: diff --git a/deps/npm/docs/content/commands/npm-install-ci-test.md b/deps/npm/docs/content/commands/npm-install-ci-test.md index 22dc87ce8bb6ca..4528f63dfe28e8 100644 --- a/deps/npm/docs/content/commands/npm-install-ci-test.md +++ b/deps/npm/docs/content/commands/npm-install-ci-test.md @@ -215,6 +215,56 @@ like `npm view` +#### `allow-scripts` + +* Default: "" +* Type: String (can be set multiple times) + +Comma-separated list of packages whose install-time lifecycle scripts +(`preinstall`, `install`, `postinstall`, and `prepare` for non-registry +dependencies) are allowed to run. + +This setting is intended for one-off and global contexts: `npm exec`, `npx`, +and `npm install -g`, where no project `package.json` is involved. For +team-wide policy in a project, use the `allowScripts` field in +`package.json` (which also supports explicit denials), or configure it in +`.npmrc`. Passing `--allow-scripts` on the command line during a +project-scoped `npm install`, `ci`, `update`, or `rebuild` is an error. + +Each name is matched against a dependency's resolved identity, not against +the package's self-reported name. `--ignore-scripts` and +`--dangerously-allow-all-scripts` both override this setting. + + + +#### `strict-allow-scripts` + +* Default: false +* Type: Boolean + +If `true`, turn the install-script policy from a warning into a hard error: +any dependency with install scripts not covered by `allowScripts` will fail +the install instead of running with a notice. + +Dependencies explicitly denied with `false` in `allowScripts` are always +silently skipped; this setting only affects unreviewed entries. +`--ignore-scripts` and `--dangerously-allow-all-scripts` both override this +setting. + + + +#### `dangerously-allow-all-scripts` + +* Default: false +* Type: Boolean + +If `true`, bypass the `allowScripts` policy entirely and run every +dependency install script regardless of whether it was approved or denied. +Intended as a migration escape hatch only; its use is strongly discouraged. +`--ignore-scripts` still takes precedence over this setting. + + + #### `audit` * Default: true diff --git a/deps/npm/docs/content/commands/npm-install-test.md b/deps/npm/docs/content/commands/npm-install-test.md index 999019bde3668d..5a2f33a84ca96d 100644 --- a/deps/npm/docs/content/commands/npm-install-test.md +++ b/deps/npm/docs/content/commands/npm-install-test.md @@ -292,6 +292,56 @@ like `npm view` +#### `allow-scripts` + +* Default: "" +* Type: String (can be set multiple times) + +Comma-separated list of packages whose install-time lifecycle scripts +(`preinstall`, `install`, `postinstall`, and `prepare` for non-registry +dependencies) are allowed to run. + +This setting is intended for one-off and global contexts: `npm exec`, `npx`, +and `npm install -g`, where no project `package.json` is involved. For +team-wide policy in a project, use the `allowScripts` field in +`package.json` (which also supports explicit denials), or configure it in +`.npmrc`. Passing `--allow-scripts` on the command line during a +project-scoped `npm install`, `ci`, `update`, or `rebuild` is an error. + +Each name is matched against a dependency's resolved identity, not against +the package's self-reported name. `--ignore-scripts` and +`--dangerously-allow-all-scripts` both override this setting. + + + +#### `strict-allow-scripts` + +* Default: false +* Type: Boolean + +If `true`, turn the install-script policy from a warning into a hard error: +any dependency with install scripts not covered by `allowScripts` will fail +the install instead of running with a notice. + +Dependencies explicitly denied with `false` in `allowScripts` are always +silently skipped; this setting only affects unreviewed entries. +`--ignore-scripts` and `--dangerously-allow-all-scripts` both override this +setting. + + + +#### `dangerously-allow-all-scripts` + +* Default: false +* Type: Boolean + +If `true`, bypass the `allowScripts` policy entirely and run every +dependency install script regardless of whether it was approved or denied. +Intended as a migration escape hatch only; its use is strongly discouraged. +`--ignore-scripts` still takes precedence over this setting. + + + #### `audit` * Default: true diff --git a/deps/npm/docs/content/commands/npm-install.md b/deps/npm/docs/content/commands/npm-install.md index 925439ceb21dd3..7bc00701e7bf2d 100644 --- a/deps/npm/docs/content/commands/npm-install.md +++ b/deps/npm/docs/content/commands/npm-install.md @@ -634,6 +634,56 @@ like `npm view` +#### `allow-scripts` + +* Default: "" +* Type: String (can be set multiple times) + +Comma-separated list of packages whose install-time lifecycle scripts +(`preinstall`, `install`, `postinstall`, and `prepare` for non-registry +dependencies) are allowed to run. + +This setting is intended for one-off and global contexts: `npm exec`, `npx`, +and `npm install -g`, where no project `package.json` is involved. For +team-wide policy in a project, use the `allowScripts` field in +`package.json` (which also supports explicit denials), or configure it in +`.npmrc`. Passing `--allow-scripts` on the command line during a +project-scoped `npm install`, `ci`, `update`, or `rebuild` is an error. + +Each name is matched against a dependency's resolved identity, not against +the package's self-reported name. `--ignore-scripts` and +`--dangerously-allow-all-scripts` both override this setting. + + + +#### `strict-allow-scripts` + +* Default: false +* Type: Boolean + +If `true`, turn the install-script policy from a warning into a hard error: +any dependency with install scripts not covered by `allowScripts` will fail +the install instead of running with a notice. + +Dependencies explicitly denied with `false` in `allowScripts` are always +silently skipped; this setting only affects unreviewed entries. +`--ignore-scripts` and `--dangerously-allow-all-scripts` both override this +setting. + + + +#### `dangerously-allow-all-scripts` + +* Default: false +* Type: Boolean + +If `true`, bypass the `allowScripts` policy entirely and run every +dependency install script regardless of whether it was approved or denied. +Intended as a migration escape hatch only; its use is strongly discouraged. +`--ignore-scripts` still takes precedence over this setting. + + + #### `audit` * Default: true diff --git a/deps/npm/docs/content/commands/npm-ls.md b/deps/npm/docs/content/commands/npm-ls.md index 91e06ed30836d4..c0a341e46fd10b 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.15.0 /path/to/npm +npm@11.16.0 /path/to/npm └─┬ init-package-json@0.0.4 └── promzard@0.1.5 ``` diff --git a/deps/npm/docs/content/commands/npm-publish.md b/deps/npm/docs/content/commands/npm-publish.md index c69e187429eabb..04c020b3563f7d 100644 --- a/deps/npm/docs/content/commands/npm-publish.md +++ b/deps/npm/docs/content/commands/npm-publish.md @@ -117,7 +117,7 @@ the package submitted to the registry. * Default: 'public' for new packages, existing packages it will not change the current level -* Type: null, "restricted", or "public" +* Type: null, "restricted", "public", or "private" If you do not want your scoped package to be publicly viewable (and installable) set `--access=restricted`. @@ -129,6 +129,8 @@ packages. Specifying a value of `restricted` or `public` during publish will change the access for an existing package the same way that `npm access set status` would. +The value `private` is an alias for `restricted`. + #### `dry-run` diff --git a/deps/npm/docs/content/commands/npm-rebuild.md b/deps/npm/docs/content/commands/npm-rebuild.md index 9fb43567ac2eb4..18b1d37c779956 100644 --- a/deps/npm/docs/content/commands/npm-rebuild.md +++ b/deps/npm/docs/content/commands/npm-rebuild.md @@ -100,6 +100,56 @@ run any pre- or post-scripts. +#### `allow-scripts` + +* Default: "" +* Type: String (can be set multiple times) + +Comma-separated list of packages whose install-time lifecycle scripts +(`preinstall`, `install`, `postinstall`, and `prepare` for non-registry +dependencies) are allowed to run. + +This setting is intended for one-off and global contexts: `npm exec`, `npx`, +and `npm install -g`, where no project `package.json` is involved. For +team-wide policy in a project, use the `allowScripts` field in +`package.json` (which also supports explicit denials), or configure it in +`.npmrc`. Passing `--allow-scripts` on the command line during a +project-scoped `npm install`, `ci`, `update`, or `rebuild` is an error. + +Each name is matched against a dependency's resolved identity, not against +the package's self-reported name. `--ignore-scripts` and +`--dangerously-allow-all-scripts` both override this setting. + + + +#### `strict-allow-scripts` + +* Default: false +* Type: Boolean + +If `true`, turn the install-script policy from a warning into a hard error: +any dependency with install scripts not covered by `allowScripts` will fail +the install instead of running with a notice. + +Dependencies explicitly denied with `false` in `allowScripts` are always +silently skipped; this setting only affects unreviewed entries. +`--ignore-scripts` and `--dangerously-allow-all-scripts` both override this +setting. + + + +#### `dangerously-allow-all-scripts` + +* Default: false +* Type: Boolean + +If `true`, bypass the `allowScripts` policy entirely and run every +dependency install script regardless of whether it was approved or denied. +Intended as a migration escape hatch only; its use is strongly discouraged. +`--ignore-scripts` still takes precedence over this setting. + + + #### `workspace` * Default: diff --git a/deps/npm/docs/content/commands/npm-stage.md b/deps/npm/docs/content/commands/npm-stage.md index 9a761bbd8a5146..cda1b493f9ac4f 100644 --- a/deps/npm/docs/content/commands/npm-stage.md +++ b/deps/npm/docs/content/commands/npm-stage.md @@ -152,9 +152,7 @@ npm stage publish | Flag | Default | Type | Description | | --- | --- | --- | --- | | `--tag` | "latest" | String | If you ask npm to install a package and don't tell it a specific version, then it will install the specified tag. It is the tag added to the package@version specified in the `npm dist-tag add` command, if no explicit tag is given. When used by the `npm diff` command, this is the tag used to fetch the tarball that will be compared with the local files by default. If used in the `npm publish` command, this is the tag that will be added to the package submitted to the registry. | -| `--access` | - 'public' for new packages, existing packages it will not change the current level - | null, "restricted", or "public" | If you do not want your scoped package to be publicly viewable (and installable) set `--access=restricted`. Unscoped packages cannot be set to `restricted`. Note: This defaults to not changing the current access level for existing packages. Specifying a value of `restricted` or `public` during publish will change the access for an existing package the same way that `npm access set status` would. | +| `--access` | 'public' for new packages, existing packages it will not change the current level | null, "restricted", "public", or "private" | If you do not want your scoped package to be publicly viewable (and installable) set `--access=restricted`. Unscoped packages cannot be set to `restricted`. Note: This defaults to not changing the current access level for existing packages. Specifying a value of `restricted` or `public` during publish will change the access for an existing package the same way that `npm access set status` would. The value `private` is an alias for `restricted`. | | `--dry-run` | false | Boolean | Indicates that you don't want npm to make any changes and that it should only report what it would have done. This can be passed into any of the commands that modify your local installation, eg, `install`, `update`, `dedupe`, `uninstall`, as well as `pack` and `publish`. Note: This is NOT honored by other network related commands, eg `dist-tags`, `owner`, etc. | | `--otp` | null | null or String | This is a one-time password from a two-factor authenticator. It's needed when publishing or changing package permissions with `npm access`. If not set, and a registry response fails with a challenge for a one-time password, npm will prompt on the command line for one. | | `--workspace`, `-w` | | String (can be set multiple times) | Enable running a command in the context of the configured workspaces of the current project while filtering by running only the workspaces defined by this configuration option. Valid values for the `workspace` config are either: * Workspace names * Path to a workspace directory * Path to a parent workspace directory (will result in selecting all workspaces within that folder) When set for the `npm init` command, this may be set to the folder of a workspace which does not yet exist, to create the folder and set it up as a brand new workspace within the project. | diff --git a/deps/npm/docs/content/commands/npm-update.md b/deps/npm/docs/content/commands/npm-update.md index 4dc7e9a1edccbe..86b54eed070924 100644 --- a/deps/npm/docs/content/commands/npm-update.md +++ b/deps/npm/docs/content/commands/npm-update.md @@ -303,6 +303,56 @@ run any pre- or post-scripts. +#### `allow-scripts` + +* Default: "" +* Type: String (can be set multiple times) + +Comma-separated list of packages whose install-time lifecycle scripts +(`preinstall`, `install`, `postinstall`, and `prepare` for non-registry +dependencies) are allowed to run. + +This setting is intended for one-off and global contexts: `npm exec`, `npx`, +and `npm install -g`, where no project `package.json` is involved. For +team-wide policy in a project, use the `allowScripts` field in +`package.json` (which also supports explicit denials), or configure it in +`.npmrc`. Passing `--allow-scripts` on the command line during a +project-scoped `npm install`, `ci`, `update`, or `rebuild` is an error. + +Each name is matched against a dependency's resolved identity, not against +the package's self-reported name. `--ignore-scripts` and +`--dangerously-allow-all-scripts` both override this setting. + + + +#### `strict-allow-scripts` + +* Default: false +* Type: Boolean + +If `true`, turn the install-script policy from a warning into a hard error: +any dependency with install scripts not covered by `allowScripts` will fail +the install instead of running with a notice. + +Dependencies explicitly denied with `false` in `allowScripts` are always +silently skipped; this setting only affects unreviewed entries. +`--ignore-scripts` and `--dangerously-allow-all-scripts` both override this +setting. + + + +#### `dangerously-allow-all-scripts` + +* Default: false +* Type: Boolean + +If `true`, bypass the `allowScripts` policy entirely and run every +dependency install script regardless of whether it was approved or denied. +Intended as a migration escape hatch only; its use is strongly discouraged. +`--ignore-scripts` still takes precedence over this setting. + + + #### `audit` * Default: true diff --git a/deps/npm/docs/content/commands/npm-version.md b/deps/npm/docs/content/commands/npm-version.md index cd504b37b7f5eb..9016c8071f7593 100644 --- a/deps/npm/docs/content/commands/npm-version.md +++ b/deps/npm/docs/content/commands/npm-version.md @@ -229,6 +229,8 @@ The exact order of execution is as follows: 6. Run the `postversion` script. Use it to clean up the file system or automatically push the commit and/or tag. +For the `preversion`, `version` and `postversion` scripts, npm also sets the [environment variables](/using-npm/scripts#environment) `npm_old_version` and `npm_new_version`. + Take the following example: ```json diff --git a/deps/npm/docs/content/commands/npm.md b/deps/npm/docs/content/commands/npm.md index 89e5dd6ef50d78..ba1890149fcdd0 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.15.0 +11.16.0 ### Description diff --git a/deps/npm/docs/content/using-npm/config.md b/deps/npm/docs/content/using-npm/config.md index 0e99c58f3c002b..d1167e0d14880b 100644 --- a/deps/npm/docs/content/using-npm/config.md +++ b/deps/npm/docs/content/using-npm/config.md @@ -140,7 +140,7 @@ safer to use a registry-provided authentication bearer token stored in the * Default: 'public' for new packages, existing packages it will not change the current level -* Type: null, "restricted", or "public" +* Type: null, "restricted", "public", or "private" If you do not want your scoped package to be publicly viewable (and installable) set `--access=restricted`. @@ -152,6 +152,8 @@ packages. Specifying a value of `restricted` or `public` during publish will change the access for an existing package the same way that `npm access set status` would. +The value `private` is an alias for `restricted`. + #### `all` @@ -248,6 +250,51 @@ to the same value as the current version. +#### `allow-scripts` + +* Default: "" +* Type: String (can be set multiple times) + +Comma-separated list of packages whose install-time lifecycle scripts +(`preinstall`, `install`, `postinstall`, and `prepare` for non-registry +dependencies) are allowed to run. + +This setting is intended for one-off and global contexts: `npm exec`, `npx`, +and `npm install -g`, where no project `package.json` is involved. For +team-wide policy in a project, use the `allowScripts` field in +`package.json` (which also supports explicit denials), or configure it in +`.npmrc`. Passing `--allow-scripts` on the command line during a +project-scoped `npm install`, `ci`, `update`, or `rebuild` is an error. + +Each name is matched against a dependency's resolved identity, not against +the package's self-reported name. `--ignore-scripts` and +`--dangerously-allow-all-scripts` both override this setting. + + + +#### `allow-scripts-pending` + +* Default: false +* Type: Boolean + +List packages with install scripts that are not yet covered by the +`allowScripts` policy, without modifying `package.json`. Only meaningful for +`npm approve-scripts`. + + + +#### `allow-scripts-pin` + +* Default: true +* Type: Boolean + +Write pinned (`pkg@version`) entries when approving install scripts. Set to +`false` to write name-only entries that allow any version. Has no effect on +`npm deny-scripts`, which always writes name-only entries regardless of this +setting. + + + #### `audit` * Default: true @@ -443,6 +490,18 @@ are same as `cpu` field of package.json, which comes from `process.arch`. +#### `dangerously-allow-all-scripts` + +* Default: false +* Type: Boolean + +If `true`, bypass the `allowScripts` policy entirely and run every +dependency install script regardless of whether it was approved or denied. +Intended as a migration escape hatch only; its use is strongly discouraged. +`--ignore-scripts` still takes precedence over this setting. + + + #### `depth` * Default: `Infinity` if `--all` is set; otherwise, `0` @@ -1769,6 +1828,22 @@ this to work properly. +#### `strict-allow-scripts` + +* Default: false +* Type: Boolean + +If `true`, turn the install-script policy from a warning into a hard error: +any dependency with install scripts not covered by `allowScripts` will fail +the install instead of running with a notice. + +Dependencies explicitly denied with `false` in `allowScripts` are always +silently skipped; this setting only affects unreviewed entries. +`--ignore-scripts` and `--dangerously-allow-all-scripts` both override this +setting. + + + #### `strict-peer-deps` * Default: false diff --git a/deps/npm/docs/content/using-npm/scripts.md b/deps/npm/docs/content/using-npm/scripts.md index 91de8f22d47f0a..dcae0c66da0e3f 100644 --- a/deps/npm/docs/content/using-npm/scripts.md +++ b/deps/npm/docs/content/using-npm/scripts.md @@ -290,6 +290,13 @@ For example, if you had `{"name":"foo", "version":"1.2.5"}` in your package.json See [`package.json`](/configuring-npm/package-json) for more on package configs. +#### versioning variables + +For versioning scripts (`preversion`, `version`, `postversion`), npm sets these environment variables: + +* `npm_old_version` - The version before being bumped +* `npm_new_version` – The version after being bumped + #### current lifecycle event Lastly, the `npm_lifecycle_event` environment variable is set to whichever stage of the cycle is being executed. diff --git a/deps/npm/docs/lib/index.js b/deps/npm/docs/lib/index.js index d7a5e83ccf5062..9779d546572930 100644 --- a/deps/npm/docs/lib/index.js +++ b/deps/npm/docs/lib/index.js @@ -151,10 +151,12 @@ const generateFlagsTable = (definitionPool) => { if (!defaultVal) { defaultVal = String(def.default) } + defaultVal = defaultVal.replace(/\n/g, ' ').trim() let typeVal = def.typeDescription || String(def.type) if (def.required) { typeVal = `${typeVal} (required)` } + typeVal = typeVal.replace(/\n/g, ' ').trim() const desc = (def.description || '').replace(/\n/g, ' ').trim() return `| ${flagsStr} | ${defaultVal} | ${typeVal} | ${desc} |` }) diff --git a/deps/npm/docs/output/commands/npm-access.html b/deps/npm/docs/output/commands/npm-access.html index 224177685f2e4a..983b44e86a639d 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.15.0 + @11.16.0

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 c73b60b16ed7ea..8945c6ef6cb33b 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.15.0 + @11.16.0

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 new file mode 100644 index 00000000000000..1849ae8c5011c4 --- /dev/null +++ b/deps/npm/docs/output/commands/npm-approve-scripts.html @@ -0,0 +1,304 @@ + + +npm-approve-scripts + + + + + +
+
+

+ npm-approve-scripts + @11.16.0 +

+Approve install scripts for specific dependencies +
+ +
+

Table of contents

+ +
+ +

Synopsis

+
npm approve-scripts <pkg> [<pkg> ...]
+npm approve-scripts --all
+npm approve-scripts --allow-scripts-pending
+
+

Note: This command is unaware of workspaces.

+

Description

+

Manages the allowScripts field in your project's package.json, which +records which of your dependencies are permitted to run install scripts +(preinstall, install, postinstall, and prepare for non-registry +sources). This command is the recommended way to maintain that field.

+

In the current release, this field is advisory: install scripts still run +by default, but installs print a list of packages whose scripts have not +been reviewed. A future release will block unreviewed install scripts.

+

There are three modes:

+
npm approve-scripts <pkg> [<pkg> ...]
+npm approve-scripts --all
+npm approve-scripts --allow-scripts-pending
+
+

<pkg> matches every installed version of that package. By default the +command writes pinned entries (pkg@1.2.3), which keep their approval +narrowed to the specific version you reviewed. Pass --no-allow-scripts-pin to write +name-only entries that allow any future version.

+

--all approves every package with unreviewed install scripts in one go.

+

--allow-scripts-pending is read-only: it lists every package whose install scripts +are not yet covered by allowScripts, without modifying package.json.

+

approve-scripts honours the asymmetric pin rule: if you re-approve a +package whose installed version has changed, the existing pin is rewritten +to track the new installed version. Multi-version statements +(pkg@1 || 2) are left alone, since they likely capture intent that +the command cannot infer. Existing false entries always win; +approve-scripts will not silently re-allow a package you previously +denied.

+

Examples

+
# Approve all currently-installed install scripts after reviewing them
+npm approve-scripts --all
+
+# Approve specific packages, pinned to their installed version
+npm approve-scripts canvas sharp
+
+# Approve name-only (any version of this package is allowed)
+npm approve-scripts --no-allow-scripts-pin canvas
+
+# Preview which packages still need review
+npm approve-scripts --allow-scripts-pending
+
+

Configuration

+

all

+
    +
  • Default: false
  • +
  • Type: Boolean
  • +
+

When running npm outdated and npm ls, setting --all will show all +outdated or installed packages, rather than only those directly depended +upon by the current project.

+

allow-scripts-pending

+
    +
  • Default: false
  • +
  • Type: Boolean
  • +
+

List packages with install scripts that are not yet covered by the +allowScripts policy, without modifying package.json. Only meaningful for +npm approve-scripts.

+

allow-scripts-pin

+
    +
  • Default: true
  • +
  • Type: Boolean
  • +
+

Write pinned (pkg@version) entries when approving install scripts. Set to +false to write name-only entries that allow any version. Has no effect on +npm deny-scripts, which always writes name-only entries regardless of this +setting.

+

json

+
    +
  • Default: false
  • +
  • Type: Boolean
  • +
+

Whether or not to output JSON data, rather than the normal output.

+
    +
  • In npm pkg set it enables parsing set values with JSON.parse() before +saving them to your package.json.
  • +
+

Not supported by all npm commands.

+

See Also

+
+ + +
+ + + + \ No newline at end of file diff --git a/deps/npm/docs/output/commands/npm-audit.html b/deps/npm/docs/output/commands/npm-audit.html index eff894d79e5adf..f018a7ae7f1c57 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.15.0 + @11.16.0

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 143b82ff563534..45ca5bec8ef537 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.15.0 + @11.16.0

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 185ac37463e9dd..0e561b39dabdaa 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.15.0 + @11.16.0

Manipulates packages cache
diff --git a/deps/npm/docs/output/commands/npm-ci.html b/deps/npm/docs/output/commands/npm-ci.html index 772d4ae14de699..745a22ea53c966 100644 --- a/deps/npm/docs/output/commands/npm-ci.html +++ b/deps/npm/docs/output/commands/npm-ci.html @@ -186,16 +186,16 @@
-

+

npm-ci - @11.15.0 + @11.16.0

Clean install a project

Table of contents

- +

Synopsis

@@ -390,6 +390,44 @@

allow-remote

installed. root only allows urls defined in your project's package.json to be installed. Also allows url dependencies to be used for other commands like npm view

+

allow-scripts

+
    +
  • Default: ""
  • +
  • Type: String (can be set multiple times)
  • +
+

Comma-separated list of packages whose install-time lifecycle scripts +(preinstall, install, postinstall, and prepare for non-registry +dependencies) are allowed to run.

+

This setting is intended for one-off and global contexts: npm exec, npx, +and npm install -g, where no project package.json is involved. For +team-wide policy in a project, use the allowScripts field in +package.json (which also supports explicit denials), or configure it in +.npmrc. Passing --allow-scripts on the command line during a +project-scoped npm install, ci, update, or rebuild is an error.

+

Each name is matched against a dependency's resolved identity, not against +the package's self-reported name. --ignore-scripts and +--dangerously-allow-all-scripts both override this setting.

+

strict-allow-scripts

+
    +
  • Default: false
  • +
  • Type: Boolean
  • +
+

If true, turn the install-script policy from a warning into a hard error: +any dependency with install scripts not covered by allowScripts will fail +the install instead of running with a notice.

+

Dependencies explicitly denied with false in allowScripts are always +silently skipped; this setting only affects unreviewed entries. +--ignore-scripts and --dangerously-allow-all-scripts both override this +setting.

+

dangerously-allow-all-scripts

+
    +
  • Default: false
  • +
  • Type: Boolean
  • +
+

If true, bypass the allowScripts policy entirely and run every +dependency install script regardless of whether it was approved or denied. +Intended as a migration escape hatch only; its use is strongly discouraged. +--ignore-scripts still takes precedence over this setting.

audit

  • Default: true
  • diff --git a/deps/npm/docs/output/commands/npm-completion.html b/deps/npm/docs/output/commands/npm-completion.html index 161ac0c6de585a..866085f42351f5 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.15.0 + @11.16.0

    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 0e8ebe721f7ec2..3f9be6dedfb64a 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.15.0 + @11.16.0

    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 f0d32be8df62b7..e15165a5d00e44 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.15.0 + @11.16.0

    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 new file mode 100644 index 00000000000000..e9b18afb88b2a2 --- /dev/null +++ b/deps/npm/docs/output/commands/npm-deny-scripts.html @@ -0,0 +1,290 @@ + + +npm-deny-scripts + + + + + +
    +
    +

    + npm-deny-scripts + @11.16.0 +

    +Deny install scripts for specific dependencies +
    + +
    +

    Table of contents

    + +
    + +

    Synopsis

    +
    npm deny-scripts <pkg> [<pkg> ...]
    +npm deny-scripts --all
    +
    +

    Note: This command is unaware of workspaces.

    +

    Description

    +

    The companion command to npm approve-scripts. +Writes false entries into the allowScripts field of your project's +package.json, recording that a dependency must not run install scripts +even if a future version would otherwise be eligible.

    +

    In the current release, install scripts still run by default, so deny-scripts +only affects how installs of denied packages are reported. A future release +will block unreviewed install scripts and respect deny entries at install +time.

    +
    npm deny-scripts <pkg> [<pkg> ...]
    +npm deny-scripts --all
    +
    +

    <pkg> matches every installed version of that package. Denies are always +written name-only ("pkg": false), regardless of --allow-scripts-pin. Pinning a deny +to a specific version would silently re-allow scripts for any other version +of the same package, which defeats the purpose; the command picks the +safer default for you.

    +

    --all denies every package with unreviewed install scripts.

    +

    If a true (pinned or name-only) entry exists for a package and you then +deny it, the existing allow entries are removed so the name-only deny is +unambiguous.

    +

    Examples

    +
    # Deny a specific package outright
    +npm deny-scripts telemetry-pkg
    +
    +# Deny everything that has install scripts and isn't already approved
    +npm deny-scripts --all
    +
    +

    Configuration

    +

    all

    +
      +
    • Default: false
    • +
    • Type: Boolean
    • +
    +

    When running npm outdated and npm ls, setting --all will show all +outdated or installed packages, rather than only those directly depended +upon by the current project.

    +

    allow-scripts-pending

    +
      +
    • Default: false
    • +
    • Type: Boolean
    • +
    +

    List packages with install scripts that are not yet covered by the +allowScripts policy, without modifying package.json. Only meaningful for +npm approve-scripts.

    +

    allow-scripts-pin

    +
      +
    • Default: true
    • +
    • Type: Boolean
    • +
    +

    Write pinned (pkg@version) entries when approving install scripts. Set to +false to write name-only entries that allow any version. Has no effect on +npm deny-scripts, which always writes name-only entries regardless of this +setting.

    +

    json

    +
      +
    • Default: false
    • +
    • Type: Boolean
    • +
    +

    Whether or not to output JSON data, rather than the normal output.

    +
      +
    • In npm pkg set it enables parsing set values with JSON.parse() before +saving them to your package.json.
    • +
    +

    Not supported by all npm commands.

    +

    See Also

    +
    + + +
    + + + + \ No newline at end of file diff --git a/deps/npm/docs/output/commands/npm-deprecate.html b/deps/npm/docs/output/commands/npm-deprecate.html index 8286ff6dfde58b..9bda62f1a891ca 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.15.0 + @11.16.0

    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 b006a5fe1565d9..7b72340cb35e86 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.15.0 + @11.16.0

    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 ea3f353bce7e13..3b95fe2e3ebc7e 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.15.0 + @11.16.0

    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 fa3f70a34e43d9..a12dd65697c1df 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.15.0 + @11.16.0

    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 35289c9f4baa65..d9094606fa2a14 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.15.0 + @11.16.0

    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 47a653e76bc67e..a5f7d958498c21 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.15.0 + @11.16.0

    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 b27f5aeef44ff5..333fd7312a6140 100644 --- a/deps/npm/docs/output/commands/npm-exec.html +++ b/deps/npm/docs/output/commands/npm-exec.html @@ -186,16 +186,16 @@
    -

    +

    npm-exec - @11.15.0 + @11.16.0

    Run a command from a local or remote npm package

    Table of contents

    - +

    Synopsis

    @@ -307,6 +307,44 @@

    include-workspace-root

    all workspaces via the workspaces flag, will cause npm to operate only on the specified workspaces, and not on the root project.

    This value is not exported to the environment for child processes.

    +

    allow-scripts

    +
      +
    • Default: ""
    • +
    • Type: String (can be set multiple times)
    • +
    +

    Comma-separated list of packages whose install-time lifecycle scripts +(preinstall, install, postinstall, and prepare for non-registry +dependencies) are allowed to run.

    +

    This setting is intended for one-off and global contexts: npm exec, npx, +and npm install -g, where no project package.json is involved. For +team-wide policy in a project, use the allowScripts field in +package.json (which also supports explicit denials), or configure it in +.npmrc. Passing --allow-scripts on the command line during a +project-scoped npm install, ci, update, or rebuild is an error.

    +

    Each name is matched against a dependency's resolved identity, not against +the package's self-reported name. --ignore-scripts and +--dangerously-allow-all-scripts both override this setting.

    +

    strict-allow-scripts

    +
      +
    • Default: false
    • +
    • Type: Boolean
    • +
    +

    If true, turn the install-script policy from a warning into a hard error: +any dependency with install scripts not covered by allowScripts will fail +the install instead of running with a notice.

    +

    Dependencies explicitly denied with false in allowScripts are always +silently skipped; this setting only affects unreviewed entries. +--ignore-scripts and --dangerously-allow-all-scripts both override this +setting.

    +

    dangerously-allow-all-scripts

    +
      +
    • Default: false
    • +
    • Type: Boolean
    • +
    +

    If true, bypass the allowScripts policy entirely and run every +dependency install script regardless of whether it was approved or denied. +Intended as a migration escape hatch only; its use is strongly discouraged. +--ignore-scripts still takes precedence over this setting.

    Examples

    Run the version of tap in the local dependencies, with the provided arguments:

    $ npm exec -- tap --bail test/foo.js
    diff --git a/deps/npm/docs/output/commands/npm-explain.html b/deps/npm/docs/output/commands/npm-explain.html
    index 4e054a719f6943..feef6d1cf95315 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.15.0 + @11.16.0

    Explain installed packages
    diff --git a/deps/npm/docs/output/commands/npm-explore.html b/deps/npm/docs/output/commands/npm-explore.html index 0302e915dc24df..0985c9bb2e8a19 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.15.0 + @11.16.0

    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 c0e9ecaecd3884..c013dd0db414bf 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.15.0 + @11.16.0

    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 43aa08e534662c..927ce09fa8cde9 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.15.0 + @11.16.0

    Retrieve funding information
    diff --git a/deps/npm/docs/output/commands/npm-get.html b/deps/npm/docs/output/commands/npm-get.html index ba24cd9fe4e5b2..675a4fcecb9855 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.15.0 + @11.16.0

    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 839f3d9e3df53c..ba77fc89ae508a 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.15.0 + @11.16.0

    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 76b8f03fdabd23..5c83c72e328208 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.15.0 + @11.16.0

    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 570d84d38c09e8..321a462a5f0162 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.15.0 + @11.16.0

    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 10cf3475a8cb97..2658ecedd0efb2 100644 --- a/deps/npm/docs/output/commands/npm-install-ci-test.html +++ b/deps/npm/docs/output/commands/npm-install-ci-test.html @@ -186,16 +186,16 @@
    -

    +

    npm-install-ci-test - @11.15.0 + @11.16.0

    Install a project with a clean slate and run tests

    Table of contents

    - +

    Synopsis

    @@ -354,6 +354,44 @@

    allow-remote

    installed. root only allows urls defined in your project's package.json to be installed. Also allows url dependencies to be used for other commands like npm view

    +

    allow-scripts

    +
      +
    • Default: ""
    • +
    • Type: String (can be set multiple times)
    • +
    +

    Comma-separated list of packages whose install-time lifecycle scripts +(preinstall, install, postinstall, and prepare for non-registry +dependencies) are allowed to run.

    +

    This setting is intended for one-off and global contexts: npm exec, npx, +and npm install -g, where no project package.json is involved. For +team-wide policy in a project, use the allowScripts field in +package.json (which also supports explicit denials), or configure it in +.npmrc. Passing --allow-scripts on the command line during a +project-scoped npm install, ci, update, or rebuild is an error.

    +

    Each name is matched against a dependency's resolved identity, not against +the package's self-reported name. --ignore-scripts and +--dangerously-allow-all-scripts both override this setting.

    +

    strict-allow-scripts

    +
      +
    • Default: false
    • +
    • Type: Boolean
    • +
    +

    If true, turn the install-script policy from a warning into a hard error: +any dependency with install scripts not covered by allowScripts will fail +the install instead of running with a notice.

    +

    Dependencies explicitly denied with false in allowScripts are always +silently skipped; this setting only affects unreviewed entries. +--ignore-scripts and --dangerously-allow-all-scripts both override this +setting.

    +

    dangerously-allow-all-scripts

    +
      +
    • Default: false
    • +
    • Type: Boolean
    • +
    +

    If true, bypass the allowScripts policy entirely and run every +dependency install script regardless of whether it was approved or denied. +Intended as a migration escape hatch only; its use is strongly discouraged. +--ignore-scripts still takes precedence over this setting.

    audit

    • Default: true
    • diff --git a/deps/npm/docs/output/commands/npm-install-test.html b/deps/npm/docs/output/commands/npm-install-test.html index 3aa17aef585d3a..cda0bf383f0fe5 100644 --- a/deps/npm/docs/output/commands/npm-install-test.html +++ b/deps/npm/docs/output/commands/npm-install-test.html @@ -186,16 +186,16 @@
      -

      +

      npm-install-test - @11.15.0 + @11.16.0

      Install package(s) and run tests

      Table of contents

      - +

      Synopsis

      @@ -410,6 +410,44 @@

      allow-remote

      installed. root only allows urls defined in your project's package.json to be installed. Also allows url dependencies to be used for other commands like npm view

      +

      allow-scripts

      +
        +
      • Default: ""
      • +
      • Type: String (can be set multiple times)
      • +
      +

      Comma-separated list of packages whose install-time lifecycle scripts +(preinstall, install, postinstall, and prepare for non-registry +dependencies) are allowed to run.

      +

      This setting is intended for one-off and global contexts: npm exec, npx, +and npm install -g, where no project package.json is involved. For +team-wide policy in a project, use the allowScripts field in +package.json (which also supports explicit denials), or configure it in +.npmrc. Passing --allow-scripts on the command line during a +project-scoped npm install, ci, update, or rebuild is an error.

      +

      Each name is matched against a dependency's resolved identity, not against +the package's self-reported name. --ignore-scripts and +--dangerously-allow-all-scripts both override this setting.

      +

      strict-allow-scripts

      +
        +
      • Default: false
      • +
      • Type: Boolean
      • +
      +

      If true, turn the install-script policy from a warning into a hard error: +any dependency with install scripts not covered by allowScripts will fail +the install instead of running with a notice.

      +

      Dependencies explicitly denied with false in allowScripts are always +silently skipped; this setting only affects unreviewed entries. +--ignore-scripts and --dangerously-allow-all-scripts both override this +setting.

      +

      dangerously-allow-all-scripts

      +
        +
      • Default: false
      • +
      • Type: Boolean
      • +
      +

      If true, bypass the allowScripts policy entirely and run every +dependency install script regardless of whether it was approved or denied. +Intended as a migration escape hatch only; its use is strongly discouraged. +--ignore-scripts still takes precedence over this setting.

      audit

      • Default: true
      • diff --git a/deps/npm/docs/output/commands/npm-install.html b/deps/npm/docs/output/commands/npm-install.html index 072f465f26a4b5..c9ae37e393238c 100644 --- a/deps/npm/docs/output/commands/npm-install.html +++ b/deps/npm/docs/output/commands/npm-install.html @@ -186,16 +186,16 @@
        -

        +

        npm-install - @11.15.0 + @11.16.0

        Install a package

        Table of contents

        - +

        Synopsis

        @@ -685,6 +685,44 @@

        allow-remote

        installed. root only allows urls defined in your project's package.json to be installed. Also allows url dependencies to be used for other commands like npm view

        +

        allow-scripts

        +
          +
        • Default: ""
        • +
        • Type: String (can be set multiple times)
        • +
        +

        Comma-separated list of packages whose install-time lifecycle scripts +(preinstall, install, postinstall, and prepare for non-registry +dependencies) are allowed to run.

        +

        This setting is intended for one-off and global contexts: npm exec, npx, +and npm install -g, where no project package.json is involved. For +team-wide policy in a project, use the allowScripts field in +package.json (which also supports explicit denials), or configure it in +.npmrc. Passing --allow-scripts on the command line during a +project-scoped npm install, ci, update, or rebuild is an error.

        +

        Each name is matched against a dependency's resolved identity, not against +the package's self-reported name. --ignore-scripts and +--dangerously-allow-all-scripts both override this setting.

        +

        strict-allow-scripts

        +
          +
        • Default: false
        • +
        • Type: Boolean
        • +
        +

        If true, turn the install-script policy from a warning into a hard error: +any dependency with install scripts not covered by allowScripts will fail +the install instead of running with a notice.

        +

        Dependencies explicitly denied with false in allowScripts are always +silently skipped; this setting only affects unreviewed entries. +--ignore-scripts and --dangerously-allow-all-scripts both override this +setting.

        +

        dangerously-allow-all-scripts

        +
          +
        • Default: false
        • +
        • Type: Boolean
        • +
        +

        If true, bypass the allowScripts policy entirely and run every +dependency install script regardless of whether it was approved or denied. +Intended as a migration escape hatch only; its use is strongly discouraged. +--ignore-scripts still takes precedence over this setting.

        audit

        • Default: true
        • diff --git a/deps/npm/docs/output/commands/npm-link.html b/deps/npm/docs/output/commands/npm-link.html index 06097944e8ca5f..dcc559329dfc51 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.15.0 + @11.16.0

          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 c61d58d80d9ef8..52f363b8b17d68 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.15.0 + @11.16.0

          List installed packages
          diff --git a/deps/npm/docs/output/commands/npm-login.html b/deps/npm/docs/output/commands/npm-login.html index 58c694e1d4e19a..eac37fc5a66665 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.15.0 + @11.16.0

          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 2229efdbe6a7f4..0930332b862b4d 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.15.0 + @11.16.0

          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 00829f01ae061d..cbf7f4e8208c93 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.15.0 + @11.16.0

          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.15.0 /path/to/npm
          +
          npm@11.16.0 /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 257e5fa0c12910..99e8c472dc5f74 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.15.0 + @11.16.0

          Manage orgs
          diff --git a/deps/npm/docs/output/commands/npm-outdated.html b/deps/npm/docs/output/commands/npm-outdated.html index 0c95c2bfa59ead..cb154b4a234c7e 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.15.0 + @11.16.0

          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 5f7dda96d69e2a..fa568741602212 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.15.0 + @11.16.0

          Manage package owners
          diff --git a/deps/npm/docs/output/commands/npm-pack.html b/deps/npm/docs/output/commands/npm-pack.html index 7097d255509ae9..a99ae2dba99c61 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.15.0 + @11.16.0

          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 6f0dd51b517e4e..bd867fbd3ef12b 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.15.0 + @11.16.0

          Ping npm registry
          diff --git a/deps/npm/docs/output/commands/npm-pkg.html b/deps/npm/docs/output/commands/npm-pkg.html index d1953066d18b0f..145b47fde4e069 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.15.0 + @11.16.0

          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 d248c0e19d5d91..2bccd93bb26aae 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.15.0 + @11.16.0

          Display prefix
          diff --git a/deps/npm/docs/output/commands/npm-profile.html b/deps/npm/docs/output/commands/npm-profile.html index 7f27c45182e770..9e3d975aa3c2ca 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.15.0 + @11.16.0

          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 a2782ed0eae29c..f6a356f53cc238 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.15.0 + @11.16.0

          Remove extraneous packages
          diff --git a/deps/npm/docs/output/commands/npm-publish.html b/deps/npm/docs/output/commands/npm-publish.html index 2f194d3c4086a3..b9c7824c8456d7 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.15.0 + @11.16.0

          Publish a package
          @@ -279,7 +279,7 @@

          access

          • Default: 'public' for new packages, existing packages it will not change the current level
          • -
          • Type: null, "restricted", or "public"
          • +
          • Type: null, "restricted", "public", or "private"

          If you do not want your scoped package to be publicly viewable (and installable) set --access=restricted.

          @@ -287,6 +287,7 @@

          access

          Note: This defaults to not changing the current access level for existing packages. Specifying a value of restricted or public during publish will change the access for an existing package the same way that npm access set status would.

          +

          The value private is an alias for restricted.

          dry-run

          • Default: false
          • diff --git a/deps/npm/docs/output/commands/npm-query.html b/deps/npm/docs/output/commands/npm-query.html index 30bddb72964b0d..efa6bd81f130f8 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.15.0 + @11.16.0

            Dependency selector query
            diff --git a/deps/npm/docs/output/commands/npm-rebuild.html b/deps/npm/docs/output/commands/npm-rebuild.html index 30301c4cda1c2e..0aff44579c5075 100644 --- a/deps/npm/docs/output/commands/npm-rebuild.html +++ b/deps/npm/docs/output/commands/npm-rebuild.html @@ -186,16 +186,16 @@
            -

            +

            npm-rebuild - @11.15.0 + @11.16.0

            Rebuild a package

            Table of contents

            - +

            Synopsis

            @@ -269,6 +269,44 @@

            ignore-scripts

            npm start, npm stop, npm restart, npm test, and npm run will still run their intended script if ignore-scripts is set, but they will not run any pre- or post-scripts.

            +

            allow-scripts

            +
              +
            • Default: ""
            • +
            • Type: String (can be set multiple times)
            • +
            +

            Comma-separated list of packages whose install-time lifecycle scripts +(preinstall, install, postinstall, and prepare for non-registry +dependencies) are allowed to run.

            +

            This setting is intended for one-off and global contexts: npm exec, npx, +and npm install -g, where no project package.json is involved. For +team-wide policy in a project, use the allowScripts field in +package.json (which also supports explicit denials), or configure it in +.npmrc. Passing --allow-scripts on the command line during a +project-scoped npm install, ci, update, or rebuild is an error.

            +

            Each name is matched against a dependency's resolved identity, not against +the package's self-reported name. --ignore-scripts and +--dangerously-allow-all-scripts both override this setting.

            +

            strict-allow-scripts

            +
              +
            • Default: false
            • +
            • Type: Boolean
            • +
            +

            If true, turn the install-script policy from a warning into a hard error: +any dependency with install scripts not covered by allowScripts will fail +the install instead of running with a notice.

            +

            Dependencies explicitly denied with false in allowScripts are always +silently skipped; this setting only affects unreviewed entries. +--ignore-scripts and --dangerously-allow-all-scripts both override this +setting.

            +

            dangerously-allow-all-scripts

            +
              +
            • Default: false
            • +
            • Type: Boolean
            • +
            +

            If true, bypass the allowScripts policy entirely and run every +dependency install script regardless of whether it was approved or denied. +Intended as a migration escape hatch only; its use is strongly discouraged. +--ignore-scripts still takes precedence over this setting.

            workspace

            • Default:
            • diff --git a/deps/npm/docs/output/commands/npm-repo.html b/deps/npm/docs/output/commands/npm-repo.html index e6bd1024c2252f..0efe01e32391d1 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.15.0 + @11.16.0

              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 3f08556ca35fd4..d58d9d362a03a9 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.15.0 + @11.16.0

              Restart a package
              diff --git a/deps/npm/docs/output/commands/npm-root.html b/deps/npm/docs/output/commands/npm-root.html index 638fe0c5f29a31..8f1a7319765932 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.15.0 + @11.16.0

              Display npm root
              diff --git a/deps/npm/docs/output/commands/npm-run.html b/deps/npm/docs/output/commands/npm-run.html index 38864357391f68..c234db61b936de 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.15.0 + @11.16.0

              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 a08eee8e909bf4..df30ae75770012 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.15.0 + @11.16.0

              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 8a009f559e779c..63efdaad281e8b 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.15.0 + @11.16.0

              Search for packages
              diff --git a/deps/npm/docs/output/commands/npm-set.html b/deps/npm/docs/output/commands/npm-set.html index c6811ae41180e3..988c341f8fee77 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.15.0 + @11.16.0

              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 3dff3e5d1db87c..46c96bdef91111 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.15.0 + @11.16.0

              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 6abe7e8bb5daa1..e98b5e5aca18a7 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.15.0 + @11.16.0

              Stage packages for publishing
              @@ -395,21 +395,48 @@

              Flags

              --access +'public' for new packages, existing packages it will not change the current level +null, "restricted", "public", or "private" +If you do not want your scoped package to be publicly viewable (and installable) set --access=restricted. Unscoped packages cannot be set to restricted. Note: This defaults to not changing the current access level for existing packages. Specifying a value of restricted or public during publish will change the access for an existing package the same way that npm access set status would. The value private is an alias for restricted. + + +--dry-run +false +Boolean +Indicates that you don't want npm to make any changes and that it should only report what it would have done. This can be passed into any of the commands that modify your local installation, eg, install, update, dedupe, uninstall, as well as pack and publish. Note: This is NOT honored by other network related commands, eg dist-tags, owner, etc. + + +--otp +null +null or String +This is a one-time password from a two-factor authenticator. It's needed when publishing or changing package permissions with npm access. If not set, and a registry response fails with a challenge for a one-time password, npm will prompt on the command line for one. + + +--workspace, -w - - +String (can be set multiple times) +Enable running a command in the context of the configured workspaces of the current project while filtering by running only the workspaces defined by this configuration option. Valid values for the workspace config are either: * Workspace names * Path to a workspace directory * Path to a parent workspace directory (will result in selecting all workspaces within that folder) When set for the npm init command, this may be set to the folder of a workspace which does not yet exist, to create the folder and set it up as a brand new workspace within the project. + + +--workspaces +null +null or Boolean +Set to true to run the command in the context of all configured workspaces. Explicitly setting this to false will cause commands like install to ignore workspaces altogether. When not set explicitly: - Commands that operate on the node_modules tree (install, update, etc.) will link workspaces into the node_modules folder. - Commands that do other things (test, exec, publish, etc.) will operate on the root project, unless one or more workspaces are specified in the workspace config. + + +--include-workspace-root +false +Boolean +Include the workspace root when workspaces are enabled for a command. When false, specifying individual workspaces via the workspace config, or all workspaces via the workspaces flag, will cause npm to operate only on the specified workspaces, and not on the root project. + + +--provenance +false +Boolean +When publishing from a supported cloud CI/CD system, the package will be publicly linked to where it was built and published from. -
              'public' for new packages, existing packages it will not change the current level
              -
              -

              | null, "restricted", or "public" | If you do not want your scoped package to be publicly viewable (and installable) set --access=restricted. Unscoped packages cannot be set to restricted. Note: This defaults to not changing the current access level for existing packages. Specifying a value of restricted or public during publish will change the access for an existing package the same way that npm access set status would. | -| --dry-run | false | Boolean | Indicates that you don't want npm to make any changes and that it should only report what it would have done. This can be passed into any of the commands that modify your local installation, eg, install, update, dedupe, uninstall, as well as pack and publish. Note: This is NOT honored by other network related commands, eg dist-tags, owner, etc. | -| --otp | null | null or String | This is a one-time password from a two-factor authenticator. It's needed when publishing or changing package permissions with npm access. If not set, and a registry response fails with a challenge for a one-time password, npm will prompt on the command line for one. | -| --workspace, -w | | String (can be set multiple times) | Enable running a command in the context of the configured workspaces of the current project while filtering by running only the workspaces defined by this configuration option. Valid values for the workspace config are either: * Workspace names * Path to a workspace directory * Path to a parent workspace directory (will result in selecting all workspaces within that folder) When set for the npm init command, this may be set to the folder of a workspace which does not yet exist, to create the folder and set it up as a brand new workspace within the project. | -| --workspaces | null | null or Boolean | Set to true to run the command in the context of all configured workspaces. Explicitly setting this to false will cause commands like install to ignore workspaces altogether. When not set explicitly: - Commands that operate on the node_modules tree (install, update, etc.) will link workspaces into the node_modules folder. - Commands that do other things (test, exec, publish, etc.) will operate on the root project, unless one or more workspaces are specified in the workspace config. | -| --include-workspace-root | false | Boolean | Include the workspace root when workspaces are enabled for a command. When false, specifying individual workspaces via the workspace config, or all workspaces via the workspaces flag, will cause npm to operate only on the specified workspaces, and not on the root project. | -| --provenance | false | Boolean | When publishing from a supported cloud CI/CD system, the package will be publicly linked to where it was built and published from. |

              npm stage list

              List all staged package versions

              Synopsis

              diff --git a/deps/npm/docs/output/commands/npm-star.html b/deps/npm/docs/output/commands/npm-star.html index 40433712a41fef..5ecd1df01d01d2 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.15.0 + @11.16.0

              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 4fa00ccd89e134..d4e8bb1ae9764d 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.15.0 + @11.16.0

              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 79e15eaeabe32e..bcc5463e6ddfe3 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.15.0 + @11.16.0

              Start a package
              diff --git a/deps/npm/docs/output/commands/npm-stop.html b/deps/npm/docs/output/commands/npm-stop.html index 132a6540c9031e..abbb05aa873c1d 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.15.0 + @11.16.0

              Stop a package
              diff --git a/deps/npm/docs/output/commands/npm-team.html b/deps/npm/docs/output/commands/npm-team.html index 42031ba120ae7b..a1d0941e2e541b 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.15.0 + @11.16.0

              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 2da5db2ff7f08b..d5fb0a1ded18a7 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.15.0 + @11.16.0

              Test a package
              diff --git a/deps/npm/docs/output/commands/npm-token.html b/deps/npm/docs/output/commands/npm-token.html index bff16ccc4e1c42..10163668153233 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.15.0 + @11.16.0

              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 e4041c733f4b3b..e269490efdff39 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.15.0 + @11.16.0

              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 bafbabefdfecef..45fcb65deed0ac 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.15.0 + @11.16.0

              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 a42ee20cf0509b..bdeb05ee1b30f8 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.15.0 + @11.16.0

              Remove a package
              diff --git a/deps/npm/docs/output/commands/npm-unpublish.html b/deps/npm/docs/output/commands/npm-unpublish.html index 3e3f4e3e9d541b..eec4aee8c603e7 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.15.0 + @11.16.0

              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 0e98f60d7bc737..7d3d40d29928a3 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.15.0 + @11.16.0

              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 a57b62afbf6943..1558e84ce06729 100644 --- a/deps/npm/docs/output/commands/npm-update.html +++ b/deps/npm/docs/output/commands/npm-update.html @@ -186,16 +186,16 @@
              -

              +

              npm-update - @11.15.0 + @11.16.0

              Update packages

              Table of contents

              - +

              Synopsis

              @@ -407,6 +407,44 @@

              ignore-scripts

              npm start, npm stop, npm restart, npm test, and npm run will still run their intended script if ignore-scripts is set, but they will not run any pre- or post-scripts.

              +

              allow-scripts

              +
                +
              • Default: ""
              • +
              • Type: String (can be set multiple times)
              • +
              +

              Comma-separated list of packages whose install-time lifecycle scripts +(preinstall, install, postinstall, and prepare for non-registry +dependencies) are allowed to run.

              +

              This setting is intended for one-off and global contexts: npm exec, npx, +and npm install -g, where no project package.json is involved. For +team-wide policy in a project, use the allowScripts field in +package.json (which also supports explicit denials), or configure it in +.npmrc. Passing --allow-scripts on the command line during a +project-scoped npm install, ci, update, or rebuild is an error.

              +

              Each name is matched against a dependency's resolved identity, not against +the package's self-reported name. --ignore-scripts and +--dangerously-allow-all-scripts both override this setting.

              +

              strict-allow-scripts

              +
                +
              • Default: false
              • +
              • Type: Boolean
              • +
              +

              If true, turn the install-script policy from a warning into a hard error: +any dependency with install scripts not covered by allowScripts will fail +the install instead of running with a notice.

              +

              Dependencies explicitly denied with false in allowScripts are always +silently skipped; this setting only affects unreviewed entries. +--ignore-scripts and --dangerously-allow-all-scripts both override this +setting.

              +

              dangerously-allow-all-scripts

              +
                +
              • Default: false
              • +
              • Type: Boolean
              • +
              +

              If true, bypass the allowScripts policy entirely and run every +dependency install script regardless of whether it was approved or denied. +Intended as a migration escape hatch only; its use is strongly discouraged. +--ignore-scripts still takes precedence over this setting.

              audit

              • Default: true
              • diff --git a/deps/npm/docs/output/commands/npm-version.html b/deps/npm/docs/output/commands/npm-version.html index 03468cb82e91b8..4deac0758f8d5d 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.15.0 + @11.16.0

                Bump a package version
                @@ -367,6 +367,7 @@

                Description

              • Run the postversion script. Use it to clean up the file system or automatically push the commit and/or tag.
              • +

                For the preversion, version and postversion scripts, npm also sets the environment variables npm_old_version and npm_new_version.

                Take the following example:

                {
                   "scripts": {
                diff --git a/deps/npm/docs/output/commands/npm-view.html b/deps/npm/docs/output/commands/npm-view.html
                index 2943db55d7643f..71fac734e58daa 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.15.0 + @11.16.0

                View registry info
                diff --git a/deps/npm/docs/output/commands/npm-whoami.html b/deps/npm/docs/output/commands/npm-whoami.html index f18819e8cc4586..f92bd67d1f5d55 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.15.0 + @11.16.0

                Display npm username
                diff --git a/deps/npm/docs/output/commands/npm.html b/deps/npm/docs/output/commands/npm.html index 9f0ceff2c5fea1..52befd0760479b 100644 --- a/deps/npm/docs/output/commands/npm.html +++ b/deps/npm/docs/output/commands/npm.html @@ -186,9 +186,9 @@
                -

                +

                npm - @11.15.0 + @11.16.0

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

                Table of contents

                Note: This command is unaware of workspaces.

                Version

                -

                11.15.0

                +

                11.16.0

                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 de7bab625b3800..5786f4332f3b6f 100644 --- a/deps/npm/docs/output/commands/npx.html +++ b/deps/npm/docs/output/commands/npx.html @@ -186,9 +186,9 @@
                -

                +

                npx - @11.15.0 + @11.16.0

                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 ead948bfa7a22e..c88270a3799930 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.15.0 + @11.16.0

                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 af9419e0f8496c..cb308af4d962a9 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.15.0 + @11.16.0

                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 ead948bfa7a22e..c88270a3799930 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.15.0 + @11.16.0

                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 05565040705356..057c119042240f 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.15.0 + @11.16.0

                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 740e79a65e15f0..2c585ff171c2c7 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.15.0 + @11.16.0

                A publishable lockfile
                diff --git a/deps/npm/docs/output/configuring-npm/npmrc.html b/deps/npm/docs/output/configuring-npm/npmrc.html index 76eca4327e8c69..c90a8b19c6be09 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.15.0 + @11.16.0

                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 05565040705356..057c119042240f 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.15.0 + @11.16.0

                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 63c60f97b4c5ab..64a2dbb13601d2 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.15.0 + @11.16.0

                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 364b56910260da..687d077639eda6 100644 --- a/deps/npm/docs/output/using-npm/config.html +++ b/deps/npm/docs/output/using-npm/config.html @@ -186,16 +186,16 @@
                -

                +

                Config - @11.15.0 + @11.16.0

                About npm configuration

                Table of contents

                -
                +

                Description

                @@ -307,7 +307,7 @@

                access

                • Default: 'public' for new packages, existing packages it will not change the current level
                • -
                • Type: null, "restricted", or "public"
                • +
                • Type: null, "restricted", "public", or "private"

                If you do not want your scoped package to be publicly viewable (and installable) set --access=restricted.

                @@ -315,6 +315,7 @@

                access

                Note: This defaults to not changing the current access level for existing packages. Specifying a value of restricted or public during publish will change the access for an existing package the same way that npm access set status would.

                +

                The value private is an alias for restricted.

                all

                • Default: false
                • @@ -387,6 +388,40 @@

                  allow-same-version

                Prevents throwing an error when npm version is used to set the new version to the same value as the current version.

                +

                allow-scripts

                +
                  +
                • Default: ""
                • +
                • Type: String (can be set multiple times)
                • +
                +

                Comma-separated list of packages whose install-time lifecycle scripts +(preinstall, install, postinstall, and prepare for non-registry +dependencies) are allowed to run.

                +

                This setting is intended for one-off and global contexts: npm exec, npx, +and npm install -g, where no project package.json is involved. For +team-wide policy in a project, use the allowScripts field in +package.json (which also supports explicit denials), or configure it in +.npmrc. Passing --allow-scripts on the command line during a +project-scoped npm install, ci, update, or rebuild is an error.

                +

                Each name is matched against a dependency's resolved identity, not against +the package's self-reported name. --ignore-scripts and +--dangerously-allow-all-scripts both override this setting.

                +

                allow-scripts-pending

                +
                  +
                • Default: false
                • +
                • Type: Boolean
                • +
                +

                List packages with install scripts that are not yet covered by the +allowScripts policy, without modifying package.json. Only meaningful for +npm approve-scripts.

                +

                allow-scripts-pin

                +
                  +
                • Default: true
                • +
                • Type: Boolean
                • +
                +

                Write pinned (pkg@version) entries when approving install scripts. Set to +false to write name-only entries that allow any version. Has no effect on +npm deny-scripts, which always writes name-only entries regardless of this +setting.

                audit

                • Default: true
                • @@ -523,6 +558,15 @@

                  cpu

                Override CPU architecture of native modules to install. Acceptable values are same as cpu field of package.json, which comes from process.arch.

                +

                dangerously-allow-all-scripts

                +
                  +
                • Default: false
                • +
                • Type: Boolean
                • +
                +

                If true, bypass the allowScripts policy entirely and run every +dependency install script regardless of whether it was approved or denied. +Intended as a migration escape hatch only; its use is strongly discouraged. +--ignore-scripts still takes precedence over this setting.

                depth

                • Default: Infinity if --all is set; otherwise, 0
                • @@ -1469,6 +1513,18 @@

                  sign-git-tag

                  -s to add a signature.

                  Note that git requires you to have set up GPG keys in your git configs for this to work properly.

                  +

                  strict-allow-scripts

                  +
                    +
                  • Default: false
                  • +
                  • Type: Boolean
                  • +
                  +

                  If true, turn the install-script policy from a warning into a hard error: +any dependency with install scripts not covered by allowScripts will fail +the install instead of running with a notice.

                  +

                  Dependencies explicitly denied with false in allowScripts are always +silently skipped; this setting only affects unreviewed entries. +--ignore-scripts and --dangerously-allow-all-scripts both override this +setting.

                  strict-peer-deps

                  • Default: false
                  • diff --git a/deps/npm/docs/output/using-npm/dependency-selectors.html b/deps/npm/docs/output/using-npm/dependency-selectors.html index f624240e2ce4b1..da260de09888ec 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.15.0 + @11.16.0

                    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 d9e4e3aff91678..9f8825dccd2dda 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.15.0 + @11.16.0

                    Developer guide
                    diff --git a/deps/npm/docs/output/using-npm/logging.html b/deps/npm/docs/output/using-npm/logging.html index 075cc73a67a09f..675c116ed70c64 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.15.0 + @11.16.0

                    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 53ae0de316be80..4da8761b61ffca 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.15.0 + @11.16.0

                    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 7124466377618f..b682f1889687a2 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.15.0 + @11.16.0

                    Package name specifier
                    diff --git a/deps/npm/docs/output/using-npm/registry.html b/deps/npm/docs/output/using-npm/registry.html index 71cea945e1251b..e6efaed669f708 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.15.0 + @11.16.0

                    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 a4ec8e6adb23ab..0d58d278fa6f8f 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.15.0 + @11.16.0

                    Cleaning the slate
                    diff --git a/deps/npm/docs/output/using-npm/scope.html b/deps/npm/docs/output/using-npm/scope.html index f0dee65b1afa0b..4004c513323c3b 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.15.0 + @11.16.0

                    Scoped packages
                    diff --git a/deps/npm/docs/output/using-npm/scripts.html b/deps/npm/docs/output/using-npm/scripts.html index 8c2de4a8c1fdc5..15ca8072c3450f 100644 --- a/deps/npm/docs/output/using-npm/scripts.html +++ b/deps/npm/docs/output/using-npm/scripts.html @@ -186,16 +186,16 @@
                    -

                    +

                    Scripts - @11.15.0 + @11.16.0

                    How npm handles the "scripts" field

                    Table of contents

                    - +

                    Description

                    @@ -459,6 +459,12 @@

                    package.json vars

                    For example, if you had {"name":"foo", "version":"1.2.5"} in your package.json file, then your package scripts would have the npm_package_name environment variable set to "foo", and the npm_package_version set to "1.2.5". You can access these variables in your code with process.env.npm_package_name and process.env.npm_package_version.

                    Note: In npm 7 and later, most package.json fields are no longer provided as environment variables. Scripts that need access to other package.json fields should read the package.json file directly. The npm_package_json environment variable provides the path to the file for this purpose.

                    See package.json for more on package configs.

                    +

                    versioning variables

                    +

                    For versioning scripts (preversion, version, postversion), npm sets these environment variables:

                    +
                      +
                    • npm_old_version - The version before being bumped
                    • +
                    • npm_new_version – The version after being bumped
                    • +

                    current lifecycle event

                    Lastly, the npm_lifecycle_event environment variable is set to whichever stage of the cycle is being executed. So, you could have a single script used for different parts of the process which switches based on what's currently happening.

                    diff --git a/deps/npm/docs/output/using-npm/workspaces.html b/deps/npm/docs/output/using-npm/workspaces.html index 3b0b1090bf8620..a544b68ce46c23 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.15.0 + @11.16.0

                    Working with workspaces
                    diff --git a/deps/npm/lib/commands/approve-scripts.js b/deps/npm/lib/commands/approve-scripts.js new file mode 100644 index 00000000000000..929c692112f16c --- /dev/null +++ b/deps/npm/lib/commands/approve-scripts.js @@ -0,0 +1,10 @@ +const AllowScriptsCmd = require('../utils/allow-scripts-cmd.js') + +class ApproveScripts extends AllowScriptsCmd { + static description = 'Approve install scripts for specific dependencies' + static name = 'approve-scripts' + static usage = [' [ ...]', '--all', '--allow-scripts-pending'] + static verb = 'approve' +} + +module.exports = ApproveScripts diff --git a/deps/npm/lib/commands/ci.js b/deps/npm/lib/commands/ci.js index 354d68ad7adffd..e82438543295a1 100644 --- a/deps/npm/lib/commands/ci.js +++ b/deps/npm/lib/commands/ci.js @@ -1,4 +1,6 @@ const reifyFinish = require('../utils/reify-finish.js') +const resolveAllowScripts = require('../utils/resolve-allow-scripts.js') +const strictAllowScriptsPreflight = require('../utils/strict-allow-scripts-preflight.js') const runScript = require('@npmcli/run-script') const fs = require('node:fs/promises') const path = require('node:path') @@ -25,6 +27,9 @@ class CI extends ArboristWorkspaceCmd { 'allow-file', 'allow-git', 'allow-remote', + 'allow-scripts', + 'strict-allow-scripts', + 'dangerously-allow-all-scripts', 'audit', 'bin-links', 'fund', @@ -43,12 +48,14 @@ class CI extends ArboristWorkspaceCmd { const ignoreScripts = this.npm.config.get('ignore-scripts') const where = this.npm.prefix const Arborist = require('@npmcli/arborist') + const { policy: allowScriptsPolicy } = await resolveAllowScripts(this.npm) const opts = { ...this.npm.flatOptions, packageLock: true, // npm ci should never skip lock files path: where, save: false, // npm ci should never modify the lockfile or package.json workspaces: this.workspaceNames, + allowScripts: allowScriptsPolicy, } // generate an inventory from the virtual tree in the lockfile @@ -69,6 +76,7 @@ class CI extends ArboristWorkspaceCmd { // We need a new one because the virtual tree fromt the lockfile can have extraneous dependencies in it that won't install on this platform const arb = new Arborist(opts) await arb.buildIdealTree() + await strictAllowScriptsPreflight({ arb, npm: this.npm, idealTreeOpts: opts }) // Verifies that the packages from the ideal tree will match the same versions that are present in the virtual tree (lock file). const errors = validateLockfile(virtualInventory, arb.idealTree.inventory) diff --git a/deps/npm/lib/commands/config.js b/deps/npm/lib/commands/config.js index 015850c48304a6..0a8b84aba2666d 100644 --- a/deps/npm/lib/commands/config.js +++ b/deps/npm/lib/commands/config.js @@ -5,7 +5,7 @@ const { EOL } = require('node:os') const localeCompare = require('@isaacs/string-locale-compare')('en') const pkgJson = require('@npmcli/package-json') const { defaults, definitions, nerfDarts, proxyEnv } = require('@npmcli/config/lib/definitions') -const { log, output } = require('proc-log') +const { log, output, input } = require('proc-log') const BaseCommand = require('../base-cmd.js') const { redact } = require('@npmcli/redact') @@ -266,7 +266,7 @@ ${defData} `.split('\n').join(EOL) await mkdir(dirname(file), { recursive: true }) await writeFile(file, tmpData, 'utf8') - await new Promise((res, rej) => { + await input.start(() => new Promise((res, rej) => { const [bin, ...args] = e.split(/\s+/) const editor = spawn(bin, [...args, file], { stdio: 'inherit' }) editor.on('exit', (code) => { @@ -275,7 +275,7 @@ ${defData} } return res() }) - }) + })) } async fix () { diff --git a/deps/npm/lib/commands/deny-scripts.js b/deps/npm/lib/commands/deny-scripts.js new file mode 100644 index 00000000000000..53b0cdd3cc50a6 --- /dev/null +++ b/deps/npm/lib/commands/deny-scripts.js @@ -0,0 +1,10 @@ +const AllowScriptsCmd = require('../utils/allow-scripts-cmd.js') + +class DenyScripts extends AllowScriptsCmd { + static description = 'Deny install scripts for specific dependencies' + static name = 'deny-scripts' + static usage = [' [ ...]', '--all'] + static verb = 'deny' +} + +module.exports = DenyScripts diff --git a/deps/npm/lib/commands/edit.js b/deps/npm/lib/commands/edit.js index 1140c59efa3e40..0b1a200264d982 100644 --- a/deps/npm/lib/commands/edit.js +++ b/deps/npm/lib/commands/edit.js @@ -1,6 +1,7 @@ const { resolve } = require('node:path') const { lstat } = require('node:fs/promises') const cp = require('node:child_process') +const { input } = require('proc-log') const completion = require('../utils/installed-shallow.js') const BaseCommand = require('../base-cmd.js') @@ -46,16 +47,17 @@ class Edit extends BaseCommand { const dir = resolve(this.npm.dir, path) await lstat(dir) - await new Promise((res, rej) => { + await input.start(() => new Promise((res, rej) => { const [bin, ...spawnArgs] = this.npm.config.get('editor').split(/\s+/) const editor = cp.spawn(bin, [...spawnArgs, dir], { stdio: 'inherit' }) - editor.on('exit', async (code) => { + editor.on('exit', (code) => { if (code) { return rej(new Error(`editor process exited with code: ${code}`)) } - await this.npm.exec('rebuild', [dir]).then(res).catch(rej) + res() }) - }) + })) + await this.npm.exec('rebuild', [dir]) } } diff --git a/deps/npm/lib/commands/exec.js b/deps/npm/lib/commands/exec.js index 5b1d117889a1ee..23c47a0cc1ad77 100644 --- a/deps/npm/lib/commands/exec.js +++ b/deps/npm/lib/commands/exec.js @@ -1,5 +1,6 @@ const { resolve } = require('node:path') const libexec = require('libnpmexec') +const resolveAllowScripts = require('../utils/resolve-allow-scripts.js') const BaseCommand = require('../base-cmd.js') class Exec extends BaseCommand { @@ -10,6 +11,9 @@ class Exec extends BaseCommand { 'workspace', 'workspaces', 'include-workspace-root', + 'allow-scripts', + 'strict-allow-scripts', + 'dangerously-allow-all-scripts', ] static name = 'exec' @@ -74,8 +78,16 @@ class Exec extends BaseCommand { throw this.usageError() } + // Resolve the install-script policy from the user/global .npmrc layer + // only. The RFC requires exec/npx to ignore any project + // package.json#allowScripts; CLI flags still apply. + const { policy: allowScriptsPolicy } = await resolveAllowScripts(this.npm, { + skipProjectConfig: true, + }) + return libexec({ ...flatOptions, + allowScripts: allowScriptsPolicy, // we explicitly set packageLockOnly to false because if it's true when we try to install a missing package, we won't actually install it packageLockOnly: false, // what the user asked to run args[0] is run by default diff --git a/deps/npm/lib/commands/install.js b/deps/npm/lib/commands/install.js index 287b585f132313..0bc3591d4af731 100644 --- a/deps/npm/lib/commands/install.js +++ b/deps/npm/lib/commands/install.js @@ -5,6 +5,8 @@ const runScript = require('@npmcli/run-script') const pacote = require('pacote') const checks = require('npm-install-checks') const reifyFinish = require('../utils/reify-finish.js') +const resolveAllowScripts = require('../utils/resolve-allow-scripts.js') +const strictAllowScriptsPreflight = require('../utils/strict-allow-scripts-preflight.js') const ArboristWorkspaceCmd = require('../arborist-cmd.js') class Install extends ArboristWorkspaceCmd { @@ -31,6 +33,9 @@ class Install extends ArboristWorkspaceCmd { 'allow-file', 'allow-git', 'allow-remote', + 'allow-scripts', + 'strict-allow-scripts', + 'dangerously-allow-all-scripts', 'audit', 'before', 'min-release-age', @@ -138,14 +143,17 @@ class Install extends ArboristWorkspaceCmd { } const Arborist = require('@npmcli/arborist') + const { policy: allowScriptsPolicy } = await resolveAllowScripts(this.npm) const opts = { ...this.npm.flatOptions, auditLevel: null, path: where, add: args, workspaces: this.workspaceNames, + allowScripts: allowScriptsPolicy, } const arb = new Arborist(opts) + await strictAllowScriptsPreflight({ arb, npm: this.npm, idealTreeOpts: opts }) await arb.reify(opts) if (!args.length && !isGlobalInstall && !ignoreScripts) { diff --git a/deps/npm/lib/commands/publish.js b/deps/npm/lib/commands/publish.js index 854633e1d29e08..450c51858ba017 100644 --- a/deps/npm/lib/commands/publish.js +++ b/deps/npm/lib/commands/publish.js @@ -287,7 +287,7 @@ class Publish extends BaseCommand { } else { manifest = await pacote.manifest(spec, { ...opts, - fullmetadata: true, + fullMetadata: true, fullReadJson: true, }) } diff --git a/deps/npm/lib/commands/rebuild.js b/deps/npm/lib/commands/rebuild.js index a23df39f1560be..333a879026cbc1 100644 --- a/deps/npm/lib/commands/rebuild.js +++ b/deps/npm/lib/commands/rebuild.js @@ -1,8 +1,11 @@ const { resolve } = require('node:path') -const { output } = require('proc-log') +const { log, output } = require('proc-log') const npa = require('npm-package-arg') const semver = require('semver') const ArboristWorkspaceCmd = require('../arborist-cmd.js') +const checkAllowScripts = require('../utils/check-allow-scripts.js') +const resolveAllowScripts = require('../utils/resolve-allow-scripts.js') +const strictAllowScriptsPreflight = require('../utils/strict-allow-scripts-preflight.js') class Rebuild extends ArboristWorkspaceCmd { static description = 'Rebuild a package' @@ -12,6 +15,9 @@ class Rebuild extends ArboristWorkspaceCmd { 'bin-links', 'foreground-scripts', 'ignore-scripts', + 'allow-scripts', + 'strict-allow-scripts', + 'dangerously-allow-all-scripts', ...super.params, ] @@ -26,9 +32,11 @@ class Rebuild extends ArboristWorkspaceCmd { const globalTop = resolve(this.npm.globalDir, '..') const where = this.npm.global ? globalTop : this.npm.prefix const Arborist = require('@npmcli/arborist') + const { policy: allowScriptsPolicy } = await resolveAllowScripts(this.npm) const arb = new Arborist({ ...this.npm.flatOptions, path: where, + allowScripts: allowScriptsPolicy, // TODO when extending ReifyCmd // workspaces: this.workspaceNames, }) @@ -50,11 +58,28 @@ class Rebuild extends ArboristWorkspaceCmd { }) const nodes = tree.inventory.filter(node => this.isNode(specs, node)) + await strictAllowScriptsPreflight({ arb, npm: this.npm }) await arb.rebuild({ nodes }) } else { + await arb.loadActual() + await strictAllowScriptsPreflight({ arb, npm: this.npm }) await arb.rebuild() } + // Phase 1 advisory: list any packages whose install scripts ran (or + // would have run) and are not yet covered by allowScripts. Rebuild + // doesn't go through reifyFinish, so the walker is invoked here. + const unreviewed = await checkAllowScripts({ arb, npm: this.npm }) + if (unreviewed.length > 0) { + const count = unreviewed.length + const noun = count === 1 ? 'package has' : 'packages have' + log.warn( + 'rebuild', + `${count} ${noun} install scripts not yet covered by allowScripts. ` + + 'Run `npm approve-scripts --allow-scripts-pending` to review.' + ) + } + output.standard('rebuilt dependencies successfully') } diff --git a/deps/npm/lib/commands/update.js b/deps/npm/lib/commands/update.js index a7fa14d8fcf24f..22f77390b25a31 100644 --- a/deps/npm/lib/commands/update.js +++ b/deps/npm/lib/commands/update.js @@ -1,6 +1,8 @@ const path = require('node:path') const { log } = require('proc-log') const reifyFinish = require('../utils/reify-finish.js') +const resolveAllowScripts = require('../utils/resolve-allow-scripts.js') +const strictAllowScriptsPreflight = require('../utils/strict-allow-scripts-preflight.js') const ArboristWorkspaceCmd = require('../arborist-cmd.js') class Update extends ArboristWorkspaceCmd { @@ -19,6 +21,9 @@ class Update extends ArboristWorkspaceCmd { 'package-lock', 'foreground-scripts', 'ignore-scripts', + 'allow-scripts', + 'strict-allow-scripts', + 'dangerously-allow-all-scripts', 'audit', 'before', 'min-release-age', @@ -51,15 +56,19 @@ class Update extends ArboristWorkspaceCmd { } const Arborist = require('@npmcli/arborist') + const { policy: allowScriptsPolicy } = await resolveAllowScripts(this.npm) const opts = { ...this.npm.flatOptions, path: where, save, workspaces: this.workspaceNames, + allowScripts: allowScriptsPolicy, } const arb = new Arborist(opts) - await arb.reify({ ...opts, update }) + const reifyOpts = { ...opts, update } + await strictAllowScriptsPreflight({ arb, npm: this.npm, idealTreeOpts: reifyOpts }) + await arb.reify(reifyOpts) await reifyFinish(this.npm, arb) } } diff --git a/deps/npm/lib/utils/allow-scripts-cmd.js b/deps/npm/lib/utils/allow-scripts-cmd.js new file mode 100644 index 00000000000000..c1ff242abeaa82 --- /dev/null +++ b/deps/npm/lib/utils/allow-scripts-cmd.js @@ -0,0 +1,245 @@ +const { log, output } = require('proc-log') +const pkgJson = require('@npmcli/package-json') +const { trustedDisplay } = require('@npmcli/arborist/lib/script-allowed.js') +const checkAllowScripts = require('./check-allow-scripts.js') +const resolveAllowScripts = require('./resolve-allow-scripts.js') +const { + applyApprovalForPackage, + applyDenyForPackage, + nameKeyFor, +} = require('./allow-scripts-writer.js') +const BaseCommand = require('../base-cmd.js') + +// Shared implementation for `npm approve-scripts` and `npm deny-scripts`. +// Subclasses set `verb` to `'approve'` or `'deny'`. +// +// Extends `BaseCommand` rather than `ArboristCmd` on purpose. Per RFC, +// `allowScripts` is read from the workspace root's `package.json` only; +// individual workspaces don't have their own `allowScripts` field, and +// running approve/deny inside a sub-workspace is identical to running +// it at the root. There's no per-workspace targeting to do, so the +// `--workspace` / `--workspaces` / `--include-workspace-root` params +// from `ArboristCmd` would be misleading no-ops. +class AllowScriptsCmd extends BaseCommand { + static params = ['all', 'allow-scripts-pending', 'allow-scripts-pin', 'json'] + static ignoreImplicitWorkspace = false + + // Subclasses set `static verb = 'approve' | 'deny'`. + get verb () { + /* istanbul ignore next: every concrete subclass declares static verb */ + return this.constructor.verb + } + + async exec (args) { + if (this.npm.global) { + throw Object.assign( + new Error(`\`npm ${this.constructor.name}\` does not work for global installs`), + { code: 'EGLOBAL' } + ) + } + + const pending = !!this.npm.config.get('allow-scripts-pending') + const all = !!this.npm.config.get('all') + + if (pending && (args.length > 0 || all)) { + throw this.usageError( + '`--allow-scripts-pending` cannot be combined with positional arguments or `--all`.' + ) + } + if (!pending && !all && args.length === 0) { + throw this.usageError() + } + if (this.verb === 'deny' && pending) { + throw this.usageError('`npm deny-scripts --allow-scripts-pending` is not supported.') + } + + const Arborist = require('@npmcli/arborist') + const { policy } = await resolveAllowScripts(this.npm) + const arb = new Arborist({ + ...this.npm.flatOptions, + path: this.npm.prefix, + allowScripts: policy, + }) + await arb.loadActual() + + const unreviewed = await checkAllowScripts({ arb, npm: this.npm }) + + if (pending) { + return this.runPending(unreviewed) + } + + if (all) { + return this.runAll(unreviewed) + } + + return this.runPositional(args, arb) + } + + runPending (unreviewed) { + if (unreviewed.length === 0) { + output.standard('No packages with unreviewed install scripts.') + return + } + const count = unreviewed.length + const has = count === 1 ? 'has' : 'have' + const pkg = count === 1 ? 'package' : 'packages' + output.standard( + `${count} ${pkg} ${has} install scripts not yet covered by allowScripts:` + ) + for (const { node, scripts } of unreviewed) { + const { name, version } = trustedDisplay(node) + /* istanbul ignore next: every test node has a name */ + const display = name || '' + const ver = version ? `@${version}` : '' + const events = Object.entries(scripts) + .map(([event, cmd]) => `${event}: ${cmd}`) + .join('; ') + output.standard(` ${display}${ver} (${events})`) + } + output.standard('') + output.standard( + 'Run `npm approve-scripts ` to allow, or `npm deny-scripts ` to deny.' + ) + } + + async runAll (unreviewed) { + if (unreviewed.length === 0) { + output.standard('No packages with unreviewed install scripts.') + return + } + // Bundled dependencies cannot be allowlisted in Phase 1 (RFC defers + // this to a follow-up because matching by name@version from the + // bundled tarball would reintroduce manifest confusion). Exclude + // them from `--all` so we don't silently write a policy entry under + // attacker-controlled identity. + const candidates = unreviewed.filter(({ node }) => !node.inBundle) + const skipped = unreviewed.length - candidates.length + if (skipped > 0) { + /* istanbul ignore next: plural variant covered separately */ + const noun = skipped === 1 ? 'dependency' : 'dependencies' + log.warn( + this.logTitle, + `Skipping ${skipped} bundled ${noun}; bundled deps with install ` + + 'scripts cannot be allowlisted in this release.' + ) + } + if (candidates.length === 0) { + output.standard('No packages eligible for approval.') + return + } + const groups = this.groupByPackage(candidates.map(({ node }) => node)) + await this.writePolicyChanges(groups) + } + + async runPositional (args, arb) { + const matched = this.findNodesForArgs(args, arb) + const groups = this.groupByPackage(matched) + if (Object.keys(groups).length === 0) { + throw Object.assign( + new Error(`No installed packages match: ${args.join(', ')}`), + { code: 'ENOMATCH' } + ) + } + await this.writePolicyChanges(groups) + } + + findNodesForArgs (args, arb) { + // Match positional args against each node's trusted name. Registry + // deps use the URL-derived name; non-registry deps fall back to the + // dependency edge name. Bundled deps are excluded for the same reason + // as --all. + const wanted = new Set(args) + const matched = [] + for (const node of arb.actualTree.inventory.values()) { + if (node.isProjectRoot || node.isWorkspace || node.inBundle) { + continue + } + const { name } = trustedDisplay(node) + if (name && wanted.has(name)) { + matched.push(node) + } + } + return matched + } + + get logTitle () { + return this.constructor.name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase() + } + + groupByPackage (nodes) { + const groups = {} + for (const node of nodes) { + const key = nameKeyFor(node) + /* istanbul ignore if: callers prefilter via inBundle and trustedDisplay so untrusted nodes don't reach here */ + if (!key) { + log.warn( + this.logTitle, + `skipping ${node.name || ''}: no trusted identity for policy key` + ) + continue + } + if (!groups[key]) { + groups[key] = [] + } + groups[key].push(node) + } + return groups + } + + async writePolicyChanges (groups) { + const pin = this.npm.config.get('allow-scripts-pin') !== false + + const pkg = await pkgJson.load(this.npm.prefix) + const content = pkg.content + const existing = content.allowScripts && typeof content.allowScripts === 'object' + ? content.allowScripts + : {} + + let updated = existing + const summary = [] + + for (const [name, nodes] of Object.entries(groups)) { + const result = this.verb === 'approve' + ? applyApprovalForPackage(updated, nodes, { pin }) + : applyDenyForPackage(updated, nodes) + + if (result.warning) { + log.warn(this.logTitle, result.warning) + } + updated = result.allowScripts + summary.push({ name, changes: result.changes }) + } + + /* istanbul ignore else: writePolicyChanges only called when changes are expected */ + if (updated !== existing) { + pkg.update({ allowScripts: updated }) + await pkg.save() + } + + this.printSummary(summary) + } + + printSummary (summary) { + if (this.npm.flatOptions.json) { + output.buffer({ allowScripts: summary }) + return + } + const verb = this.verb === 'approve' ? 'Approved' : 'Denied' + let touched = 0 + for (const { name, changes } of summary) { + if (changes.length === 0) { + continue + } + touched++ + output.standard(`${verb} ${name}:`) + for (const { key, change } of changes) { + output.standard(` ${change} ${key}`) + } + } + if (touched === 0) { + output.standard(`Nothing to ${this.verb}; allowScripts unchanged.`) + } + } +} + +module.exports = AllowScriptsCmd diff --git a/deps/npm/lib/utils/allow-scripts-writer.js b/deps/npm/lib/utils/allow-scripts-writer.js new file mode 100644 index 00000000000000..5f43bbebeedefa --- /dev/null +++ b/deps/npm/lib/utils/allow-scripts-writer.js @@ -0,0 +1,323 @@ +const npa = require('npm-package-arg') +const { log } = require('proc-log') +const { getTrustedRegistryIdentity } = require('@npmcli/arborist/lib/script-allowed.js') + +// Pure helpers that implement the RFC's pin-mismatch table for +// `npm approve-scripts` and `npm deny-scripts`. +// +// Approving writes either `"": true` or `"": true` to the +// project's `allowScripts` field, depending on `--allow-scripts-pin` and the currently +// installed versions. +// +// Denying always writes `"": false`, regardless of `--allow-scripts-pin`, per the +// RFC's asymmetric-pin rule. + +// Convert an arborist Node into the spec string used for a versioned policy +// entry. Returns `null` if the node cannot be represented as a versioned key +// derived from trusted sources (lockfile URL for registry, hosted shortcut +// for git, the resolved file path for local installs). Never falls back to +// `node.packageName` / `node.version`, which are tarball-controlled. +const versionedKeyFor = (node) => { + if (!node) { + return null + } + /* istanbul ignore next: callers guarantee a string resolved */ + const resolved = typeof node.resolved === 'string' ? node.resolved : '' + if (resolved.startsWith('git')) { + try { + const parsed = npa(resolved) + if (parsed.hosted) { + const committish = parsed.gitCommittish || parsed.hosted.committish + const base = parsed.hosted.shortcut({ noCommittish: true }) + return committish ? `${base}#${committish}` : base + } + } catch { + /* istanbul ignore next: npa already parsed this string in keyTargetsNode */ + return null + } + return null + } + if (/^https?:\/\//.test(resolved)) { + const trusted = getTrustedRegistryIdentity(node) + if (trusted && trusted.version) { + return `${trusted.name}@${trusted.version}` + } + // Registry node with a resolved URL that versionFromTgz couldn't + // parse (private-registry mirror, alternate CDN URL shape). Leave a + // breadcrumb so users notice when policy keys are silently pruned. + log.silly( + 'allow-scripts', + `unable to derive trusted versioned key for ${node.path || node.name || ''} ` + + `(resolved: ${resolved}); key will be pruned on next save` + ) + return null + } + /* istanbul ignore next: 'file:' and '/' branches are each covered separately */ + if (resolved.startsWith('file:') || resolved.startsWith('/')) { + return resolved + } + // No trusted source. Refuse to compose a key from attacker-controlled + // `node.packageName` / `node.version`. + /* istanbul ignore next: callers filter out non-registry/non-file nodes before reaching this fallback */ + return null +} + +// Convert an arborist Node into the spec string used for a name-only policy +// entry. Same trust rules as versionedKeyFor — returns `null` rather than +// falling back to tarball-controlled fields. +const nameKeyFor = (node) => { + if (!node) { + return null + } + /* istanbul ignore next: callers guarantee a string resolved */ + const resolved = typeof node.resolved === 'string' ? node.resolved : '' + if (resolved.startsWith('git')) { + try { + const parsed = npa(resolved) + if (parsed.hosted) { + return parsed.hosted.shortcut({ noCommittish: true }) + } + } catch { + /* istanbul ignore next: npa already parsed this string in keyTargetsNode */ + return null + } + return null + } + if (resolved.startsWith('file:') || resolved.startsWith('/')) { + return resolved + } + // Registry deps: only the URL-derived (or edges-derived, in the + // omit-lockfile case) trusted name is acceptable. + const trusted = getTrustedRegistryIdentity(node) + return trusted ? trusted.name : null +} + +const isSingleVersionPin = (key) => { + try { + const parsed = npa(key) + return parsed.type === 'version' + } catch { + return false + } +} + +// Build the warning string emitted when an existing deny entry blocks +// an approval. Per RFC, a name-only deny ("pkg": false) is widest and +// the only remediation is to remove the entry. A versioned deny +// ("pkg@1.2.3": false or a disjunction) blocks only specific versions; +// the user can either widen it via `npm deny-scripts ` or remove +// it to approve the currently-installed version only. +const denyWarning = (key, subject, name) => { + if (isNameOnlyKey(key)) { + return `${key} is denied; remove the entry from allowScripts to approve ${subject}.` + } + /* istanbul ignore next: name fallback is defensive; callers pass nameKeyFor(sample) */ + const widenTarget = name || 'this package' + return `${key} is a versioned deny; run \`npm deny-scripts ${widenTarget}\` ` + + `to widen the deny to all versions of ${widenTarget}, or remove the entry ` + + `to approve ${subject}.` +} + +const isNameOnlyKey = (key) => { + try { + const parsed = npa(key) + if (parsed.type === 'tag') { + return true + } + if (parsed.type === 'range') { + return parsed.fetchSpec === '*' + || parsed.rawSpec === '' + || parsed.rawSpec === '*' + } + return false + } catch { + /* istanbul ignore next: keys reaching this helper have already parsed via keyTargetsNode */ + return false + } +} + +// Does this policy key target this node by identity (ignoring the +// allow/deny value)? +// +// Registry keys (`tag`, `range`, `version`) require a trusted identity on +// the node. If the node has no `getTrustedRegistryIdentity` result, the +// key does not match — never fall back to `node.name`, which is the +// install-directory name and is forgeable through aliases / manifest +// confusion. +const keyTargetsNode = (key, node) => { + let parsed + try { + parsed = npa(key) + } catch { + return false + } + switch (parsed.type) { + case 'tag': + case 'range': + case 'version': { + const trusted = getTrustedRegistryIdentity(node) + if (!trusted) { + return false + } + return trusted.name === parsed.name + } + case 'git': { + let resolvedParsed + try { + resolvedParsed = node.resolved ? npa(node.resolved) : null + } catch { + /* istanbul ignore next */ + return false + } + const keyHost = parsed.hosted?.ssh({ noCommittish: true }) + const nodeHost = resolvedParsed?.hosted?.ssh({ noCommittish: true }) + return !!(keyHost && nodeHost && keyHost === nodeHost) + } + case 'file': + case 'directory': + case 'remote': + return node.resolved === parsed.saveSpec || node.resolved === parsed.fetchSpec + default: + return false + } +} + +// Apply approvals for all currently-installed versions of a single package. +// +// `nodes` must all share an identity (same package name for registry deps, +// or same hosted shortcut for git deps, etc.). The caller is responsible +// for grouping nodes correctly. +// +// Returns `{ allowScripts, changes, warning }` where: +// - `allowScripts` is the new object (the input is never mutated) +// - `changes` is a list of `{ key, change }` entries describing edits +// - `warning` is an optional message to surface to the user +const applyApprovalForPackage = (existing, nodes, { pin = true } = {}) => { + const allowScripts = { ...existing } + const changes = [] + + if (!Array.isArray(nodes) || nodes.length === 0) { + return { allowScripts, changes } + } + + const sample = nodes[0] + const name = nameKeyFor(sample) + + // Deny-wins: any existing false that targets any installed version aborts. + for (const node of nodes) { + for (const [key, value] of Object.entries(allowScripts)) { + if (value === false && keyTargetsNode(key, node)) { + /* istanbul ignore next: name fallback covers the empty-name edge case */ + const subject = name || 'this package' + return { + allowScripts, + changes, + warning: denyWarning(key, subject, name), + } + } + } + } + + if (!pin) { + // Name-only mode: collapse any single-version pins for this package + // into a single name-only entry. + for (const key of Object.keys(allowScripts)) { + if ( + keyTargetsNode(key, sample) && + key !== name && + isSingleVersionPin(key) && + allowScripts[key] === true + ) { + delete allowScripts[key] + } + } + + /* istanbul ignore else: name === null is the no-identity path tested separately */ + if (name && allowScripts[name] !== true) { + allowScripts[name] = true + changes.push({ key: name, change: 'added' }) + } + return { allowScripts, changes } + } + + // Pin mode. For each currently installed version, write a single-version + // pin if one is not already in place. Stale single-version pins for this + // package are removed. Per the RFC's pin-mismatch table, an existing + // name-only entry (`pkg: true`) is replaced by `pkg@x.y.z: true` once + // every installed version has a pin. + const installedKeys = new Set(nodes.map(versionedKeyFor).filter(Boolean)) + + for (const key of Object.keys(allowScripts)) { + if ( + keyTargetsNode(key, sample) && + isSingleVersionPin(key) && + allowScripts[key] === true && + !installedKeys.has(key) + ) { + delete allowScripts[key] + changes.push({ key, change: 'removed-stale' }) + } + } + + for (const key of installedKeys) { + if (allowScripts[key] !== true) { + allowScripts[key] = true + changes.push({ key, change: 'added' }) + } + } + + // Upgrade: drop the name-only entry once every installed version has a + // pin. The operation is convergent: running the command twice produces + // the same shape regardless of the starting state. + if ( + installedKeys.size > 0 && + name && + !installedKeys.has(name) && + allowScripts[name] === true + ) { + delete allowScripts[name] + changes.push({ key: name, change: 'replaced-by-pin' }) + } + + return { allowScripts, changes } +} + +// Apply a deny for a single package. Always name-only; ignores `--allow-scripts-pin`. +const applyDenyForPackage = (existing, nodes) => { + const allowScripts = { ...existing } + const changes = [] + + if (!Array.isArray(nodes) || nodes.length === 0) { + return { allowScripts, changes } + } + + const sample = nodes[0] + const name = nameKeyFor(sample) + if (!name) { + return { allowScripts, changes } + } + + // Drop any pinned allow entries for this package: the name-only deny + // overrides them anyway, and leaving them in place is confusing. + for (const key of Object.keys(allowScripts)) { + if (keyTargetsNode(key, sample) && key !== name) { + delete allowScripts[key] + changes.push({ key, change: 'removed-pinned-allow' }) + } + } + + if (allowScripts[name] !== false) { + allowScripts[name] = false + changes.push({ key: name, change: 'added' }) + } + return { allowScripts, changes } +} + +module.exports = { + applyApprovalForPackage, + applyDenyForPackage, + versionedKeyFor, + nameKeyFor, + keyTargetsNode, + isSingleVersionPin, +} diff --git a/deps/npm/lib/utils/check-allow-scripts.js b/deps/npm/lib/utils/check-allow-scripts.js new file mode 100644 index 00000000000000..5ef2bfb74cf153 --- /dev/null +++ b/deps/npm/lib/utils/check-allow-scripts.js @@ -0,0 +1,54 @@ +const isScriptAllowed = require('@npmcli/arborist/lib/script-allowed.js') +const getInstallScripts = require('@npmcli/arborist/lib/install-scripts.js') + +// Walks arb.actualTree.inventory and returns the list of dep nodes that +// have install-relevant lifecycle scripts and are not yet covered (or +// explicitly denied) by the allowScripts policy. +// +// Returns an array of `{ node, scripts }` entries. `scripts` is an object +// describing the relevant lifecycle scripts that would run. + +const checkAllowScripts = async ({ arb, npm, tree }) => { + const ignoreScripts = !!arb.options?.ignoreScripts + const dangerouslyAllowAll = !!npm?.flatOptions?.dangerouslyAllowAllScripts + + if (ignoreScripts || dangerouslyAllowAll) { + return [] + } + + // Defaults to actualTree (post-reify) but accepts an explicit tree so + // callers can pre-flight against the idealTree before scripts run. + const targetTree = tree || arb.actualTree + if (!targetTree?.inventory) { + return [] + } + + const policy = arb.options?.allowScripts || null + + const unreviewed = [] + for (const node of targetTree.inventory.values()) { + if (node.isProjectRoot || node.isWorkspace) { + continue + } + if (node.isLink) { + // Linked workspace dependencies are managed by the workspace owner. + continue + } + + const verdict = isScriptAllowed(node, policy) + if (verdict === true || verdict === false) { + continue + } + + const scripts = await getInstallScripts(node) + if (Object.keys(scripts).length === 0) { + continue + } + + unreviewed.push({ node, scripts }) + } + + return unreviewed +} + +module.exports = checkAllowScripts diff --git a/deps/npm/lib/utils/cmd-list.js b/deps/npm/lib/utils/cmd-list.js index 0166b1cc862ba2..1909df0d045469 100644 --- a/deps/npm/lib/utils/cmd-list.js +++ b/deps/npm/lib/utils/cmd-list.js @@ -5,6 +5,7 @@ const abbrev = require('abbrev') const commands = [ 'access', 'adduser', + 'approve-scripts', 'audit', 'bugs', 'cache', @@ -12,6 +13,7 @@ const commands = [ 'completion', 'config', 'dedupe', + 'deny-scripts', 'deprecate', 'diff', 'dist-tag', diff --git a/deps/npm/lib/utils/reify-finish.js b/deps/npm/lib/utils/reify-finish.js index 5e1330f4937bbd..1041c53fdb9357 100644 --- a/deps/npm/lib/utils/reify-finish.js +++ b/deps/npm/lib/utils/reify-finish.js @@ -1,4 +1,6 @@ const reifyOutput = require('./reify-output.js') +const checkAllowScripts = require('./check-allow-scripts.js') +const warnWorkspaceAllowScripts = require('./warn-workspace-allow-scripts.js') const ini = require('ini') const { writeFile } = require('node:fs/promises') const { resolve } = require('node:path') @@ -15,7 +17,9 @@ const reifyFinish = async (npm, arb) => { } } } - reifyOutput(npm, arb) + warnWorkspaceAllowScripts(arb.actualTree) + const unreviewedScripts = await checkAllowScripts({ arb, npm }) + reifyOutput(npm, arb, { unreviewedScripts }) } module.exports = reifyFinish diff --git a/deps/npm/lib/utils/reify-output.js b/deps/npm/lib/utils/reify-output.js index 99427faaf66488..b1e1ffbcddd175 100644 --- a/deps/npm/lib/utils/reify-output.js +++ b/deps/npm/lib/utils/reify-output.js @@ -14,11 +14,12 @@ const { depth } = require('treeverse') const ms = require('ms') const npmAuditReport = require('npm-audit-report') const { readTree: getFundingInfo } = require('libnpmfund') +const { trustedDisplay } = require('@npmcli/arborist/lib/script-allowed.js') const auditError = require('./audit-error.js') -// TODO: output JSON if flatOptions.json is true -const reifyOutput = (npm, arb) => { +const reifyOutput = (npm, arb, extras = {}) => { const { diff, actualTree } = arb + const unreviewedScripts = extras.unreviewedScripts || [] // note: fails and crashes if we're running audit fix and there was an error which is a good thing, because there's no point printing all this other stuff in that case! const auditReport = auditError(npm, arb.auditReport) ? null : arb.auditReport @@ -113,11 +114,23 @@ const reifyOutput = (npm, arb) => { summary.audit = npm.command === 'audit' ? auditReport : auditReport.toJSON().metadata } + if (unreviewedScripts.length) { + summary.unreviewedScripts = unreviewedScripts.map(({ node, scripts }) => { + const { name, version } = trustedDisplay(node) + return { + name, + version, + path: node.path, + scripts, + } + }) + } output.buffer(summary) } else { packagesChangedMessage(npm, summary) packagesFundingMessage(npm, summary) printAuditReport(npm, auditReport) + unreviewedScriptsMessage(npm, unreviewedScripts) } } @@ -217,4 +230,39 @@ const packagesFundingMessage = (npm, { funding }) => { output.standard(' run `npm fund` for details') } +const unreviewedScriptsMessage = (npm, unreviewedScripts) => { + if (!unreviewedScripts.length) { + return + } + + // Goes through log.warn so it respects --loglevel / --silent and lands + // on stderr like every other "FYI, here's something to know" message. + // stdout is reserved for things the user explicitly asked to see + // (npm ls, npm view). + const count = unreviewedScripts.length + const pkg = count === 1 ? 'package has' : 'packages have' + const header = `${count} ${pkg} install scripts not yet covered by allowScripts:` + + const lines = unreviewedScripts.map(({ node, scripts }) => { + const { name, version } = trustedDisplay(node) + /* istanbul ignore next: every test node has a name */ + const display = name || '' + const ver = version ? `@${version}` : '' + const events = Object.entries(scripts) + .map(([event, cmd]) => `${event}: ${cmd}`) + .join('; ') + return ` ${display}${ver} (${events})` + }) + + log.warn( + 'allow-scripts', + [ + header, + ...lines, + '', + 'Run `npm approve-scripts --allow-scripts-pending` to review, or `npm approve-scripts ` to allow.', + ].join('\n') + ) +} + module.exports = reifyOutput diff --git a/deps/npm/lib/utils/resolve-allow-scripts.js b/deps/npm/lib/utils/resolve-allow-scripts.js new file mode 100644 index 00000000000000..b658e1a68ad0cf --- /dev/null +++ b/deps/npm/lib/utils/resolve-allow-scripts.js @@ -0,0 +1,181 @@ +const { log } = require('proc-log') +const npa = require('npm-package-arg') +const pkgJson = require('@npmcli/package-json') +const { isExactVersionDisjunction } = require('@npmcli/arborist/lib/script-allowed.js') +const parseAllowScriptsList = require('@npmcli/config/lib/parse-allow-scripts-list.js') + +const buildPolicyFromNames = (names) => { + /* istanbul ignore if: callers only pass non-empty arrays */ + if (!names.length) { + return null + } + const policy = {} + for (const name of names) { + policy[name] = true + } + return policy +} + +// Read the `allow-scripts` value from one or more named config sources and +// build a policy object. Returns `null` if none of the sources has a value. +const policyFromSources = (npm, sources) => { + for (const where of sources) { + const value = npm.config.get?.('allow-scripts', where) + if (value === undefined) { + continue + } + const names = parseAllowScriptsList(value) + /* istanbul ignore else: parseAllowScriptsList returns non-empty when value is set */ + if (names.length) { + return buildPolicyFromNames(names) + } + } + return null +} + +const validatePolicy = (policy, sourceLabel) => { + // Drop and warn about keys with forbidden semver ranges (^, ~, >=, <, *). + // The RFC only permits exact versions joined by `||`. Bare names like + // `canvas` and explicit name-only wildcards (`canvas@*`) are allowed. + if (!policy) { + return policy + } + const cleaned = {} + for (const [key, value] of Object.entries(policy)) { + let parsed + try { + parsed = npa(key) + } catch { + log.warn('allow-scripts', `${sourceLabel}: ignoring unparseable entry "${key}"`) + continue + } + if (parsed.type === 'tag') { + // `pkg@latest`, `pkg@next`, etc. look like a pin but behave name- + // only — the matcher has no way to verify what the tag points at + // when scripts run. Reject for the same reason as semver ranges. + log.warn( + 'allow-scripts', + `${sourceLabel}: ignoring "${key}" — dist-tag specs (@latest, @next, ...) are not allowed; ` + + 'use exact versions joined by "||", or the bare package name, instead' + ) + continue + } + if (parsed.type === 'range') { + const isNameOnly = parsed.fetchSpec === '*' + || parsed.rawSpec === '' + || parsed.rawSpec === '*' + if (!isNameOnly && !isExactVersionDisjunction(parsed.fetchSpec)) { + log.warn( + 'allow-scripts', + `${sourceLabel}: ignoring "${key}" — semver ranges (^, ~, >=, <) are not allowed; ` + + 'use exact versions joined by "||" instead' + ) + continue + } + } + cleaned[key] = value + } + return Object.keys(cleaned).length > 0 ? cleaned : null +} + +// Resolve the effective allowScripts policy from the layered sources. +// Returns `{ policy, source }` where: +// - `policy` is an object map of `package-spec` -> boolean, or `null` if +// no layer has any configuration +// - `source` is one of `'cli'`, `'package.json'`, `'.npmrc'`, or `null` +// +// Precedence order (highest to lowest), per RFC npm/rfcs#868: +// 1. CLI flags (--allow-scripts) and env vars +// 2. Root `package.json#allowScripts` +// 3. `.npmrc` cascade (project, user, global) +// +// The project `package.json` layer is skipped when: +// - `npm.global` is true (no project context exists for global installs) +// - `skipProjectConfig` is true (e.g. npm exec / npx, which per the RFC +// consult only user/global .npmrc) +// +// In both skipped cases, the CLI and .npmrc layers are still consulted; +// only the project package.json layer is skipped. +// +// The first source with any configuration wins for the entire install; +// lower layers are ignored. A `log.warn` is emitted whenever a setting is +// being suppressed by a higher-priority source. +// +// Reads `package.json` from `npm.prefix` (not `npm.localPrefix`) so an +// install run from a workspace sub-directory still picks up the project +// root's policy. +const resolveAllowScripts = async (npm, { skipProjectConfig = false } = {}) => { + // Independently probe each RFC layer. + const cliPolicy = policyFromSources(npm, ['cli', 'env']) + const npmrcPolicy = policyFromSources(npm, ['project', 'user', 'global', 'builtin']) + + // The --allow-scripts CLI flag is intended for one-off and global + // contexts (npm exec, npx, npm install -g). In a project-scoped install, + // team policy belongs in package.json or .npmrc, so reject the flag + // outright to avoid the "works on my machine" footgun. + if (cliPolicy && !npm.global && !skipProjectConfig) { + throw Object.assign( + new Error( + '--allow-scripts is not allowed in project-scoped installs. ' + + 'Add the entries to the "allowScripts" field in package.json, ' + + 'or to .npmrc, instead.' + ), + { code: 'EALLOWSCRIPTS' } + ) + } + + // Project package.json is consulted only when the caller is operating + // inside a real project (not -g, not npx). + let pkgPolicy = null + if (!npm.global && !skipProjectConfig) { + try { + const { content } = await pkgJson.normalize(npm.prefix) + if (content?.allowScripts && typeof content.allowScripts === 'object') { + const entries = Object.entries(content.allowScripts) + if (entries.length > 0) { + pkgPolicy = Object.fromEntries(entries) + } + } + } catch (err) { + log.silly('allow-scripts', 'no package.json at prefix', err.message) + } + } + + // Validate each candidate layer: drop forbidden ranges, warn the user. + const cli = validatePolicy(cliPolicy, 'CLI flag') + const pkg = validatePolicy(pkgPolicy, 'package.json') + const rc = validatePolicy(npmrcPolicy, '.npmrc') + + // Apply RFC precedence. + if (cli) { + // Note: `pkg` is never set alongside `cli` here. Project package.json is + // only read when `!npm.global && !skipProjectConfig`, but in that same + // case a CLI policy throws above. With `npm.global` or skipProjectConfig + // set, package.json is never consulted. + if (rc) { + log.warn( + 'allow-scripts', + '.npmrc allow-scripts setting is being ignored because --allow-scripts was passed on the command line' + ) + } + return { policy: cli, source: 'cli' } + } + + if (pkg) { + if (rc) { + log.warn( + 'allow-scripts', + '.npmrc allow-scripts setting is being ignored because package.json declares its own allowScripts field' + ) + } + return { policy: pkg, source: 'package.json' } + } + + if (rc) { + return { policy: rc, source: '.npmrc' } + } + + return { policy: null, source: null } +} + +module.exports = resolveAllowScripts diff --git a/deps/npm/lib/utils/strict-allow-scripts-preflight.js b/deps/npm/lib/utils/strict-allow-scripts-preflight.js new file mode 100644 index 00000000000000..a3f83ea4b662bc --- /dev/null +++ b/deps/npm/lib/utils/strict-allow-scripts-preflight.js @@ -0,0 +1,61 @@ +const checkAllowScripts = require('./check-allow-scripts.js') + +// Pre-flight check for `--strict-allow-scripts`. Call after arborist has +// been constructed but before `arb.reify()` runs, so that install scripts +// never execute when strict mode would block them. +// +// Behaviour: +// - No-op unless `npm.flatOptions.strictAllowScripts` is set. +// - Bypassed by `--dangerously-allow-all-scripts` and `--ignore-scripts` +// (the per-flag short-circuits already live in checkAllowScripts). +// - Builds the ideal tree (idempotent — subsequent reify reuses it), +// walks it for nodes whose install scripts have not been covered by +// the `allowScripts` policy, and throws if any are found. +const strictAllowScriptsPreflight = async ({ arb, npm, idealTreeOpts }) => { + if (!npm?.flatOptions?.strictAllowScripts) { + return + } + + // Prefer the idealTree when reify is about to run; fall back to + // actualTree for npm rebuild (which never builds an ideal tree). + let tree + if (idealTreeOpts !== undefined) { + // `npm ci` builds the ideal tree before calling the preflight, so + // skip the rebuild when one already exists. `npm install` calls the + // preflight before reify and needs us to build. + if (!arb.idealTree) { + await arb.buildIdealTree(idealTreeOpts) + } + tree = arb.idealTree + } else { + tree = arb.actualTree + } + + const unreviewed = await checkAllowScripts({ arb, npm, tree }) + if (unreviewed.length === 0) { + return + } + + const lines = unreviewed.map(({ node, scripts }) => { + const events = Object.entries(scripts) + .map(([event, body]) => `${event}: ${body}`) + .join('; ') + const name = node.package?.name || node.name + const version = node.package?.version || '' + const label = version ? `${name}@${version}` : name + return ` ${label} (${events})` + }).join('\n') + + throw Object.assign( + new Error( + `--strict-allow-scripts: ${unreviewed.length} package(s) have install ` + + `scripts not covered by allowScripts:\n${lines}\n` + + 'Approve them with `npm approve-scripts`, deny them with ' + + '`npm deny-scripts`, or bypass this check with ' + + '`--dangerously-allow-all-scripts`.' + ), + { code: 'ESTRICTALLOWSCRIPTS' } + ) +} + +module.exports = strictAllowScriptsPreflight diff --git a/deps/npm/lib/utils/warn-workspace-allow-scripts.js b/deps/npm/lib/utils/warn-workspace-allow-scripts.js new file mode 100644 index 00000000000000..e46e6cf4d2a10b --- /dev/null +++ b/deps/npm/lib/utils/warn-workspace-allow-scripts.js @@ -0,0 +1,40 @@ +const { log } = require('proc-log') + +// The allowScripts policy MUST live at the project root (RFC npm/rfcs#868). +// A non-root workspace declaring its own allowScripts is almost always a +// mistake: that policy would be silently ignored at install time. +// +// `findWorkspaceAllowScripts` returns the list of offending workspace nodes. +// `warnWorkspaceAllowScripts` is the side-effecting variant that emits one +// install-time `log.warn` per offender. + +const findWorkspaceAllowScripts = (tree) => { + const offenders = [] + if (!tree?.inventory) { + return offenders + } + for (const node of tree.inventory.values()) { + if (!node.isWorkspace || node.isProjectRoot) { + continue + } + if (node.package?.allowScripts !== undefined) { + offenders.push(node) + } + } + return offenders +} + +const warnWorkspaceAllowScripts = (tree) => { + for (const node of findWorkspaceAllowScripts(tree)) { + const name = node.packageName || node.name + log.warn( + 'allow-scripts', + `allowScripts in workspace ${name} (${node.path}) is ignored. ` + + 'Move the field to the project root package.json.' + ) + } +} + +module.exports = warnWorkspaceAllowScripts +module.exports.warnWorkspaceAllowScripts = warnWorkspaceAllowScripts +module.exports.findWorkspaceAllowScripts = findWorkspaceAllowScripts diff --git a/deps/npm/man/man1/npm-access.1 b/deps/npm/man/man1/npm-access.1 index 6490de4ada7a33..1137b4d5fbee07 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-ACCESS" "1" "May 2026" "NPM@11.16.0" "" .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 849f6a40416269..b52bf3ed7f980e 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-ADDUSER" "1" "May 2026" "NPM@11.16.0" "" .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 new file mode 100644 index 00000000000000..b6ac279a025584 --- /dev/null +++ b/deps/npm/man/man1/npm-approve-scripts.1 @@ -0,0 +1,113 @@ +.TH "NPM-APPROVE-SCRIPTS" "1" "May 2026" "NPM@11.16.0" "" +.SH "NAME" +\fBnpm-approve-scripts\fR - Approve install scripts for specific dependencies +.SS "Synopsis" +.P +.RS 2 +.nf +npm approve-scripts \[lB] ...\[rB] +npm approve-scripts --all +npm approve-scripts --allow-scripts-pending +.fi +.RE +.P +Note: This command is unaware of workspaces. +.SS "Description" +.P +Manages the \fBallowScripts\fR field in your project's \fBpackage.json\fR, which records which of your dependencies are permitted to run install scripts (\fBpreinstall\fR, \fBinstall\fR, \fBpostinstall\fR, and \fBprepare\fR for non-registry sources). This command is the recommended way to maintain that field. +.P +In the current release, this field is advisory: install scripts still run by default, but installs print a list of packages whose scripts have not been reviewed. A future release will block unreviewed install scripts. +.P +There are three modes: +.P +.RS 2 +.nf +npm approve-scripts \[lB] ...\[rB] +npm approve-scripts --all +npm approve-scripts --allow-scripts-pending +.fi +.RE +.P +\fB\fR matches every installed version of that package. By default the command writes pinned entries (\fBpkg@1.2.3\fR), which keep their approval narrowed to the specific version you reviewed. Pass \fB--no-allow-scripts-pin\fR to write name-only entries that allow any future version. +.P +\fB--all\fR approves every package with unreviewed install scripts in one go. +.P +\fB--allow-scripts-pending\fR is read-only: it lists every package whose install scripts are not yet covered by \fBallowScripts\fR, without modifying \fBpackage.json\fR. +.P +\fBapprove-scripts\fR honours the asymmetric pin rule: if you re-approve a package whose installed version has changed, the existing pin is rewritten to track the new installed version. Multi-version statements (\fBpkg@1 || 2\fR) are left alone, since they likely capture intent that the command cannot infer. Existing \fBfalse\fR entries always win; \fBapprove-scripts\fR will not silently re-allow a package you previously denied. +.SS "Examples" +.P +.RS 2 +.nf +# Approve all currently-installed install scripts after reviewing them +npm approve-scripts --all + +# Approve specific packages, pinned to their installed version +npm approve-scripts canvas sharp + +# Approve name-only (any version of this package is allowed) +npm approve-scripts --no-allow-scripts-pin canvas + +# Preview which packages still need review +npm approve-scripts --allow-scripts-pending +.fi +.RE +.SS "Configuration" +.SS "\fBall\fR" +.RS 0 +.IP \(bu 4 +Default: false +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +When running \fBnpm outdated\fR and \fBnpm ls\fR, setting \fB--all\fR will show all outdated or installed packages, rather than only those directly depended upon by the current project. +.SS "\fBallow-scripts-pending\fR" +.RS 0 +.IP \(bu 4 +Default: false +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +List packages with install scripts that are not yet covered by the \fBallowScripts\fR policy, without modifying \fBpackage.json\fR. Only meaningful for \fBnpm approve-scripts\fR. +.SS "\fBallow-scripts-pin\fR" +.RS 0 +.IP \(bu 4 +Default: true +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +Write pinned (\fBpkg@version\fR) entries when approving install scripts. Set to \fBfalse\fR to write name-only entries that allow any version. Has no effect on \fBnpm deny-scripts\fR, which always writes name-only entries regardless of this setting. +.SS "\fBjson\fR" +.RS 0 +.IP \(bu 4 +Default: false +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +Whether or not to output JSON data, rather than the normal output. +.RS 0 +.IP \(bu 4 +In \fBnpm pkg set\fR it enables parsing set values with JSON.parse() before saving them to your \fBpackage.json\fR. +.RE 0 + +.P +Not supported by all npm commands. +.SS "See Also" +.RS 0 +.IP \(bu 4 +npm help deny-scripts +.IP \(bu 4 +npm help install +.IP \(bu 4 +npm help rebuild +.IP \(bu 4 +\fBpackage.json\fR \fI\(la/configuring-npm/package-json\(ra\fR +.RE 0 diff --git a/deps/npm/man/man1/npm-audit.1 b/deps/npm/man/man1/npm-audit.1 index c0704aafc31b73..f066529a8b8530 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-AUDIT" "1" "May 2026" "NPM@11.16.0" "" .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 77f78edd37ee28..6c9d745c20f3f2 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-BUGS" "1" "May 2026" "NPM@11.16.0" "" .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 8ed27305563e99..17e38319e16223 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-CACHE" "1" "May 2026" "NPM@11.16.0" "" .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 fb0a6b25221d2c..467929979eda6c 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-CI" "1" "May 2026" "NPM@11.16.0" "" .SH "NAME" \fBnpm-ci\fR - Clean install a project .SS "Synopsis" @@ -216,6 +216,42 @@ Type: "all", "none", or "root" Limits the ability for npm to fetch dependencies from urls. That is, dependencies that point to a tarball url instead of a version or semver range. Please note that this could leave your tree incomplete and some packages may not function as intended or designed. Changing this setting will not remove dependencies that are already installed. .P \fBall\fR allows any url to be installed. \fBnone\fR prevents any url from being installed. \fBroot\fR only allows urls defined in your project's package.json to be installed. Also allows url dependencies to be used for other commands like \fBnpm view\fR +.SS "\fBallow-scripts\fR" +.RS 0 +.IP \(bu 4 +Default: "" +.IP \(bu 4 +Type: String (can be set multiple times) +.RE 0 + +.P +Comma-separated list of packages whose install-time lifecycle scripts (\fBpreinstall\fR, \fBinstall\fR, \fBpostinstall\fR, and \fBprepare\fR for non-registry dependencies) are allowed to run. +.P +This setting is intended for one-off and global contexts: \fBnpm exec\fR, \fBnpx\fR, and \fBnpm install -g\fR, where no project \fBpackage.json\fR is involved. For team-wide policy in a project, use the \fBallowScripts\fR field in \fBpackage.json\fR (which also supports explicit denials), or configure it in \fB.npmrc\fR. Passing \fB--allow-scripts\fR on the command line during a project-scoped \fBnpm install\fR, \fBci\fR, \fBupdate\fR, or \fBrebuild\fR is an error. +.P +Each name is matched against a dependency's resolved identity, not against the package's self-reported name. \fB--ignore-scripts\fR and \fB--dangerously-allow-all-scripts\fR both override this setting. +.SS "\fBstrict-allow-scripts\fR" +.RS 0 +.IP \(bu 4 +Default: false +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +If \fBtrue\fR, turn the install-script policy from a warning into a hard error: any dependency with install scripts not covered by \fBallowScripts\fR will fail the install instead of running with a notice. +.P +Dependencies explicitly denied with \fBfalse\fR in \fBallowScripts\fR are always silently skipped; this setting only affects unreviewed entries. \fB--ignore-scripts\fR and \fB--dangerously-allow-all-scripts\fR both override this setting. +.SS "\fBdangerously-allow-all-scripts\fR" +.RS 0 +.IP \(bu 4 +Default: false +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +If \fBtrue\fR, bypass the \fBallowScripts\fR policy entirely and run every dependency install script regardless of whether it was approved or denied. Intended as a migration escape hatch only; its use is strongly discouraged. \fB--ignore-scripts\fR still takes precedence over this setting. .SS "\fBaudit\fR" .RS 0 .IP \(bu 4 diff --git a/deps/npm/man/man1/npm-completion.1 b/deps/npm/man/man1/npm-completion.1 index 9dbfd321c9a643..c6a82af87d93fa 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-COMPLETION" "1" "May 2026" "NPM@11.16.0" "" .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 f73f7d0815a9be..28ae9ed07de461 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-CONFIG" "1" "May 2026" "NPM@11.16.0" "" .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 f0c0134b20e5f2..c62112ff7f9a8c 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-DEDUPE" "1" "May 2026" "NPM@11.16.0" "" .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 new file mode 100644 index 00000000000000..a466da7a30dc8f --- /dev/null +++ b/deps/npm/man/man1/npm-deny-scripts.1 @@ -0,0 +1,99 @@ +.TH "NPM-DENY-SCRIPTS" "1" "May 2026" "NPM@11.16.0" "" +.SH "NAME" +\fBnpm-deny-scripts\fR - Deny install scripts for specific dependencies +.SS "Synopsis" +.P +.RS 2 +.nf +npm deny-scripts \[lB] ...\[rB] +npm deny-scripts --all +.fi +.RE +.P +Note: This command is unaware of workspaces. +.SS "Description" +.P +The companion command to npm help approve-scripts. Writes \fBfalse\fR entries into the \fBallowScripts\fR field of your project's \fBpackage.json\fR, recording that a dependency must not run install scripts even if a future version would otherwise be eligible. +.P +In the current release, install scripts still run by default, so \fBdeny-scripts\fR only affects how installs of denied packages are reported. A future release will block unreviewed install scripts and respect deny entries at install time. +.P +.RS 2 +.nf +npm deny-scripts \[lB] ...\[rB] +npm deny-scripts --all +.fi +.RE +.P +\fB\fR matches every installed version of that package. Denies are always written name-only (\fB"pkg": false\fR), regardless of \fB--allow-scripts-pin\fR. Pinning a deny to a specific version would silently re-allow scripts for any other version of the same package, which defeats the purpose; the command picks the safer default for you. +.P +\fB--all\fR denies every package with unreviewed install scripts. +.P +If a \fBtrue\fR (pinned or name-only) entry exists for a package and you then deny it, the existing allow entries are removed so the name-only deny is unambiguous. +.SS "Examples" +.P +.RS 2 +.nf +# Deny a specific package outright +npm deny-scripts telemetry-pkg + +# Deny everything that has install scripts and isn't already approved +npm deny-scripts --all +.fi +.RE +.SS "Configuration" +.SS "\fBall\fR" +.RS 0 +.IP \(bu 4 +Default: false +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +When running \fBnpm outdated\fR and \fBnpm ls\fR, setting \fB--all\fR will show all outdated or installed packages, rather than only those directly depended upon by the current project. +.SS "\fBallow-scripts-pending\fR" +.RS 0 +.IP \(bu 4 +Default: false +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +List packages with install scripts that are not yet covered by the \fBallowScripts\fR policy, without modifying \fBpackage.json\fR. Only meaningful for \fBnpm approve-scripts\fR. +.SS "\fBallow-scripts-pin\fR" +.RS 0 +.IP \(bu 4 +Default: true +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +Write pinned (\fBpkg@version\fR) entries when approving install scripts. Set to \fBfalse\fR to write name-only entries that allow any version. Has no effect on \fBnpm deny-scripts\fR, which always writes name-only entries regardless of this setting. +.SS "\fBjson\fR" +.RS 0 +.IP \(bu 4 +Default: false +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +Whether or not to output JSON data, rather than the normal output. +.RS 0 +.IP \(bu 4 +In \fBnpm pkg set\fR it enables parsing set values with JSON.parse() before saving them to your \fBpackage.json\fR. +.RE 0 + +.P +Not supported by all npm commands. +.SS "See Also" +.RS 0 +.IP \(bu 4 +npm help approve-scripts +.IP \(bu 4 +npm help install +.IP \(bu 4 +\fBpackage.json\fR \fI\(la/configuring-npm/package-json\(ra\fR +.RE 0 diff --git a/deps/npm/man/man1/npm-deprecate.1 b/deps/npm/man/man1/npm-deprecate.1 index 5d4fdd380cef63..0e67565fba394c 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-DEPRECATE" "1" "May 2026" "NPM@11.16.0" "" .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 4f0e943ec7b4a3..1051276045fc84 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-DIFF" "1" "May 2026" "NPM@11.16.0" "" .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 0faa3fd6b2b388..a2a7e0ea36a018 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-DIST-TAG" "1" "May 2026" "NPM@11.16.0" "" .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 ad5cfb87e86115..943c26eb53fbe3 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-DOCS" "1" "May 2026" "NPM@11.16.0" "" .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 eafa7a811ffc65..0d40b9b45b14d2 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-DOCTOR" "1" "May 2026" "NPM@11.16.0" "" .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 d7f2c198a7046f..d57bdc0ce6f910 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-EDIT" "1" "May 2026" "NPM@11.16.0" "" .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 3c9b5dff8b96bf..8987175e5d8410 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-EXEC" "1" "May 2026" "NPM@11.16.0" "" .SH "NAME" \fBnpm-exec\fR - Run a command from a local or remote npm package .SS "Synopsis" @@ -167,6 +167,42 @@ Include the workspace root when workspaces are enabled for a command. When false, specifying individual workspaces via the \fBworkspace\fR config, or all workspaces via the \fBworkspaces\fR flag, will cause npm to operate only on the specified workspaces, and not on the root project. .P This value is not exported to the environment for child processes. +.SS "\fBallow-scripts\fR" +.RS 0 +.IP \(bu 4 +Default: "" +.IP \(bu 4 +Type: String (can be set multiple times) +.RE 0 + +.P +Comma-separated list of packages whose install-time lifecycle scripts (\fBpreinstall\fR, \fBinstall\fR, \fBpostinstall\fR, and \fBprepare\fR for non-registry dependencies) are allowed to run. +.P +This setting is intended for one-off and global contexts: \fBnpm exec\fR, \fBnpx\fR, and \fBnpm install -g\fR, where no project \fBpackage.json\fR is involved. For team-wide policy in a project, use the \fBallowScripts\fR field in \fBpackage.json\fR (which also supports explicit denials), or configure it in \fB.npmrc\fR. Passing \fB--allow-scripts\fR on the command line during a project-scoped \fBnpm install\fR, \fBci\fR, \fBupdate\fR, or \fBrebuild\fR is an error. +.P +Each name is matched against a dependency's resolved identity, not against the package's self-reported name. \fB--ignore-scripts\fR and \fB--dangerously-allow-all-scripts\fR both override this setting. +.SS "\fBstrict-allow-scripts\fR" +.RS 0 +.IP \(bu 4 +Default: false +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +If \fBtrue\fR, turn the install-script policy from a warning into a hard error: any dependency with install scripts not covered by \fBallowScripts\fR will fail the install instead of running with a notice. +.P +Dependencies explicitly denied with \fBfalse\fR in \fBallowScripts\fR are always silently skipped; this setting only affects unreviewed entries. \fB--ignore-scripts\fR and \fB--dangerously-allow-all-scripts\fR both override this setting. +.SS "\fBdangerously-allow-all-scripts\fR" +.RS 0 +.IP \(bu 4 +Default: false +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +If \fBtrue\fR, bypass the \fBallowScripts\fR policy entirely and run every dependency install script regardless of whether it was approved or denied. Intended as a migration escape hatch only; its use is strongly discouraged. \fB--ignore-scripts\fR still takes precedence over this setting. .SS "Examples" .P Run the version of \fBtap\fR in the local dependencies, with the provided arguments: diff --git a/deps/npm/man/man1/npm-explain.1 b/deps/npm/man/man1/npm-explain.1 index 551df063471993..ec315300c2f1ba 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-EXPLAIN" "1" "May 2026" "NPM@11.16.0" "" .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 abbc9975b705ef..fbc61b1de01014 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-EXPLORE" "1" "May 2026" "NPM@11.16.0" "" .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 bae0c98a90073d..57daa9d322595e 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-FIND-DUPES" "1" "May 2026" "NPM@11.16.0" "" .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 188d91950db7fa..05a0177501955f 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-FUND" "1" "May 2026" "NPM@11.16.0" "" .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 10a03171c209b9..bbcfcae3a21158 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-GET" "1" "May 2026" "NPM@11.16.0" "" .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 d42e5d0d84bc9b..b50eb9a9ac9c5b 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-HELP-SEARCH" "1" "May 2026" "NPM@11.16.0" "" .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 e4e34292fea758..eb4353fbd7c0ea 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-HELP" "1" "May 2026" "NPM@11.16.0" "" .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 66444a2bd30a9a..a55bf5cf7e42d3 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-INIT" "1" "May 2026" "NPM@11.16.0" "" .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 d9991dd61f8fc9..4d0d125aac3a57 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-INSTALL-CI-TEST" "1" "May 2026" "NPM@11.16.0" "" .SH "NAME" \fBnpm-install-ci-test\fR - Install a project with a clean slate and run tests .SS "Synopsis" @@ -164,6 +164,42 @@ Type: "all", "none", or "root" Limits the ability for npm to fetch dependencies from urls. That is, dependencies that point to a tarball url instead of a version or semver range. Please note that this could leave your tree incomplete and some packages may not function as intended or designed. Changing this setting will not remove dependencies that are already installed. .P \fBall\fR allows any url to be installed. \fBnone\fR prevents any url from being installed. \fBroot\fR only allows urls defined in your project's package.json to be installed. Also allows url dependencies to be used for other commands like \fBnpm view\fR +.SS "\fBallow-scripts\fR" +.RS 0 +.IP \(bu 4 +Default: "" +.IP \(bu 4 +Type: String (can be set multiple times) +.RE 0 + +.P +Comma-separated list of packages whose install-time lifecycle scripts (\fBpreinstall\fR, \fBinstall\fR, \fBpostinstall\fR, and \fBprepare\fR for non-registry dependencies) are allowed to run. +.P +This setting is intended for one-off and global contexts: \fBnpm exec\fR, \fBnpx\fR, and \fBnpm install -g\fR, where no project \fBpackage.json\fR is involved. For team-wide policy in a project, use the \fBallowScripts\fR field in \fBpackage.json\fR (which also supports explicit denials), or configure it in \fB.npmrc\fR. Passing \fB--allow-scripts\fR on the command line during a project-scoped \fBnpm install\fR, \fBci\fR, \fBupdate\fR, or \fBrebuild\fR is an error. +.P +Each name is matched against a dependency's resolved identity, not against the package's self-reported name. \fB--ignore-scripts\fR and \fB--dangerously-allow-all-scripts\fR both override this setting. +.SS "\fBstrict-allow-scripts\fR" +.RS 0 +.IP \(bu 4 +Default: false +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +If \fBtrue\fR, turn the install-script policy from a warning into a hard error: any dependency with install scripts not covered by \fBallowScripts\fR will fail the install instead of running with a notice. +.P +Dependencies explicitly denied with \fBfalse\fR in \fBallowScripts\fR are always silently skipped; this setting only affects unreviewed entries. \fB--ignore-scripts\fR and \fB--dangerously-allow-all-scripts\fR both override this setting. +.SS "\fBdangerously-allow-all-scripts\fR" +.RS 0 +.IP \(bu 4 +Default: false +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +If \fBtrue\fR, bypass the \fBallowScripts\fR policy entirely and run every dependency install script regardless of whether it was approved or denied. Intended as a migration escape hatch only; its use is strongly discouraged. \fB--ignore-scripts\fR still takes precedence over this setting. .SS "\fBaudit\fR" .RS 0 .IP \(bu 4 diff --git a/deps/npm/man/man1/npm-install-test.1 b/deps/npm/man/man1/npm-install-test.1 index 33311b005154fb..dd238cbf6c613b 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-INSTALL-TEST" "1" "May 2026" "NPM@11.16.0" "" .SH "NAME" \fBnpm-install-test\fR - Install package(s) and run tests .SS "Synopsis" @@ -241,6 +241,42 @@ Type: "all", "none", or "root" Limits the ability for npm to fetch dependencies from urls. That is, dependencies that point to a tarball url instead of a version or semver range. Please note that this could leave your tree incomplete and some packages may not function as intended or designed. Changing this setting will not remove dependencies that are already installed. .P \fBall\fR allows any url to be installed. \fBnone\fR prevents any url from being installed. \fBroot\fR only allows urls defined in your project's package.json to be installed. Also allows url dependencies to be used for other commands like \fBnpm view\fR +.SS "\fBallow-scripts\fR" +.RS 0 +.IP \(bu 4 +Default: "" +.IP \(bu 4 +Type: String (can be set multiple times) +.RE 0 + +.P +Comma-separated list of packages whose install-time lifecycle scripts (\fBpreinstall\fR, \fBinstall\fR, \fBpostinstall\fR, and \fBprepare\fR for non-registry dependencies) are allowed to run. +.P +This setting is intended for one-off and global contexts: \fBnpm exec\fR, \fBnpx\fR, and \fBnpm install -g\fR, where no project \fBpackage.json\fR is involved. For team-wide policy in a project, use the \fBallowScripts\fR field in \fBpackage.json\fR (which also supports explicit denials), or configure it in \fB.npmrc\fR. Passing \fB--allow-scripts\fR on the command line during a project-scoped \fBnpm install\fR, \fBci\fR, \fBupdate\fR, or \fBrebuild\fR is an error. +.P +Each name is matched against a dependency's resolved identity, not against the package's self-reported name. \fB--ignore-scripts\fR and \fB--dangerously-allow-all-scripts\fR both override this setting. +.SS "\fBstrict-allow-scripts\fR" +.RS 0 +.IP \(bu 4 +Default: false +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +If \fBtrue\fR, turn the install-script policy from a warning into a hard error: any dependency with install scripts not covered by \fBallowScripts\fR will fail the install instead of running with a notice. +.P +Dependencies explicitly denied with \fBfalse\fR in \fBallowScripts\fR are always silently skipped; this setting only affects unreviewed entries. \fB--ignore-scripts\fR and \fB--dangerously-allow-all-scripts\fR both override this setting. +.SS "\fBdangerously-allow-all-scripts\fR" +.RS 0 +.IP \(bu 4 +Default: false +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +If \fBtrue\fR, bypass the \fBallowScripts\fR policy entirely and run every dependency install script regardless of whether it was approved or denied. Intended as a migration escape hatch only; its use is strongly discouraged. \fB--ignore-scripts\fR still takes precedence over this setting. .SS "\fBaudit\fR" .RS 0 .IP \(bu 4 diff --git a/deps/npm/man/man1/npm-install.1 b/deps/npm/man/man1/npm-install.1 index 10163784e45dee..b51006e58e26d1 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-INSTALL" "1" "May 2026" "NPM@11.16.0" "" .SH "NAME" \fBnpm-install\fR - Install a package .SS "Synopsis" @@ -631,6 +631,42 @@ Type: "all", "none", or "root" Limits the ability for npm to fetch dependencies from urls. That is, dependencies that point to a tarball url instead of a version or semver range. Please note that this could leave your tree incomplete and some packages may not function as intended or designed. Changing this setting will not remove dependencies that are already installed. .P \fBall\fR allows any url to be installed. \fBnone\fR prevents any url from being installed. \fBroot\fR only allows urls defined in your project's package.json to be installed. Also allows url dependencies to be used for other commands like \fBnpm view\fR +.SS "\fBallow-scripts\fR" +.RS 0 +.IP \(bu 4 +Default: "" +.IP \(bu 4 +Type: String (can be set multiple times) +.RE 0 + +.P +Comma-separated list of packages whose install-time lifecycle scripts (\fBpreinstall\fR, \fBinstall\fR, \fBpostinstall\fR, and \fBprepare\fR for non-registry dependencies) are allowed to run. +.P +This setting is intended for one-off and global contexts: \fBnpm exec\fR, \fBnpx\fR, and \fBnpm install -g\fR, where no project \fBpackage.json\fR is involved. For team-wide policy in a project, use the \fBallowScripts\fR field in \fBpackage.json\fR (which also supports explicit denials), or configure it in \fB.npmrc\fR. Passing \fB--allow-scripts\fR on the command line during a project-scoped \fBnpm install\fR, \fBci\fR, \fBupdate\fR, or \fBrebuild\fR is an error. +.P +Each name is matched against a dependency's resolved identity, not against the package's self-reported name. \fB--ignore-scripts\fR and \fB--dangerously-allow-all-scripts\fR both override this setting. +.SS "\fBstrict-allow-scripts\fR" +.RS 0 +.IP \(bu 4 +Default: false +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +If \fBtrue\fR, turn the install-script policy from a warning into a hard error: any dependency with install scripts not covered by \fBallowScripts\fR will fail the install instead of running with a notice. +.P +Dependencies explicitly denied with \fBfalse\fR in \fBallowScripts\fR are always silently skipped; this setting only affects unreviewed entries. \fB--ignore-scripts\fR and \fB--dangerously-allow-all-scripts\fR both override this setting. +.SS "\fBdangerously-allow-all-scripts\fR" +.RS 0 +.IP \(bu 4 +Default: false +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +If \fBtrue\fR, bypass the \fBallowScripts\fR policy entirely and run every dependency install script regardless of whether it was approved or denied. Intended as a migration escape hatch only; its use is strongly discouraged. \fB--ignore-scripts\fR still takes precedence over this setting. .SS "\fBaudit\fR" .RS 0 .IP \(bu 4 diff --git a/deps/npm/man/man1/npm-link.1 b/deps/npm/man/man1/npm-link.1 index 0eb208243b715b..5fc0057bfe9b6b 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-LINK" "1" "May 2026" "NPM@11.16.0" "" .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 774052db1d799c..860cb296e86a60 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-LL" "1" "May 2026" "NPM@11.16.0" "" .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 58ecb80834d259..037be1840a4f1a 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-LOGIN" "1" "May 2026" "NPM@11.16.0" "" .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 6248c2010970f8..d3fdd55251f4ab 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-LOGOUT" "1" "May 2026" "NPM@11.16.0" "" .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 7f4e7ee59e3244..ac695c5c633c55 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-LS" "1" "May 2026" "NPM@11.16.0" "" .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.15.0 /path/to/npm +npm@11.16.0 /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 7bcc7393e4de30..25f1ca4680cd27 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-ORG" "1" "May 2026" "NPM@11.16.0" "" .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 8d82a119fcc6de..462141f446fa46 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-OUTDATED" "1" "May 2026" "NPM@11.16.0" "" .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 883d500a403958..7a90c8e0c28856 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-OWNER" "1" "May 2026" "NPM@11.16.0" "" .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 cb23bdb9d82541..945b3f42e3c910 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-PACK" "1" "May 2026" "NPM@11.16.0" "" .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 6ac9326abf40b6..0c9f579acb2cd5 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-PING" "1" "May 2026" "NPM@11.16.0" "" .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 841cf44e420d69..525cc2fa92f67d 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-PKG" "1" "May 2026" "NPM@11.16.0" "" .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 fbfadd6959fef2..0ad1dced9b9fca 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-PREFIX" "1" "May 2026" "NPM@11.16.0" "" .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 e8cb8f6c6a6a14..ca1cdd366d80e1 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-PROFILE" "1" "May 2026" "NPM@11.16.0" "" .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 1afac2948d2e5d..b7dc1e212c4c89 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-PRUNE" "1" "May 2026" "NPM@11.16.0" "" .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 88f976cf9fe908..3bbb7139839af8 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-PUBLISH" "1" "May 2026" "NPM@11.16.0" "" .SH "NAME" \fBnpm-publish\fR - Publish a package .SS "Synopsis" @@ -120,7 +120,7 @@ If used in the \fBnpm publish\fR command, this is the tag that will be added to .IP \(bu 4 Default: 'public' for new packages, existing packages it will not change the current level .IP \(bu 4 -Type: null, "restricted", or "public" +Type: null, "restricted", "public", or "private" .RE 0 .P @@ -130,6 +130,8 @@ Unscoped packages cannot be set to \fBrestricted\fR. .P Note: This defaults to not changing the current access level for existing packages. Specifying a value of \fBrestricted\fR or \fBpublic\fR during publish will change the access for an existing package the same way that \fBnpm access set status\fR would. +.P +The value \fBprivate\fR is an alias for \fBrestricted\fR. .SS "\fBdry-run\fR" .RS 0 .IP \(bu 4 diff --git a/deps/npm/man/man1/npm-query.1 b/deps/npm/man/man1/npm-query.1 index eef55eeddd7af1..cc73ad1e92470f 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-QUERY" "1" "May 2026" "NPM@11.16.0" "" .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 457e8013fb0e5c..af0562603369e6 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-REBUILD" "1" "May 2026" "NPM@11.16.0" "" .SH "NAME" \fBnpm-rebuild\fR - Rebuild a package .SS "Synopsis" @@ -101,6 +101,42 @@ Type: Boolean If true, npm does not run scripts specified in package.json files. .P Note that commands explicitly intended to run a particular script, such as \fBnpm start\fR, \fBnpm stop\fR, \fBnpm restart\fR, \fBnpm test\fR, and \fBnpm run\fR will still run their intended script if \fBignore-scripts\fR is set, but they will \fInot\fR run any pre- or post-scripts. +.SS "\fBallow-scripts\fR" +.RS 0 +.IP \(bu 4 +Default: "" +.IP \(bu 4 +Type: String (can be set multiple times) +.RE 0 + +.P +Comma-separated list of packages whose install-time lifecycle scripts (\fBpreinstall\fR, \fBinstall\fR, \fBpostinstall\fR, and \fBprepare\fR for non-registry dependencies) are allowed to run. +.P +This setting is intended for one-off and global contexts: \fBnpm exec\fR, \fBnpx\fR, and \fBnpm install -g\fR, where no project \fBpackage.json\fR is involved. For team-wide policy in a project, use the \fBallowScripts\fR field in \fBpackage.json\fR (which also supports explicit denials), or configure it in \fB.npmrc\fR. Passing \fB--allow-scripts\fR on the command line during a project-scoped \fBnpm install\fR, \fBci\fR, \fBupdate\fR, or \fBrebuild\fR is an error. +.P +Each name is matched against a dependency's resolved identity, not against the package's self-reported name. \fB--ignore-scripts\fR and \fB--dangerously-allow-all-scripts\fR both override this setting. +.SS "\fBstrict-allow-scripts\fR" +.RS 0 +.IP \(bu 4 +Default: false +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +If \fBtrue\fR, turn the install-script policy from a warning into a hard error: any dependency with install scripts not covered by \fBallowScripts\fR will fail the install instead of running with a notice. +.P +Dependencies explicitly denied with \fBfalse\fR in \fBallowScripts\fR are always silently skipped; this setting only affects unreviewed entries. \fB--ignore-scripts\fR and \fB--dangerously-allow-all-scripts\fR both override this setting. +.SS "\fBdangerously-allow-all-scripts\fR" +.RS 0 +.IP \(bu 4 +Default: false +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +If \fBtrue\fR, bypass the \fBallowScripts\fR policy entirely and run every dependency install script regardless of whether it was approved or denied. Intended as a migration escape hatch only; its use is strongly discouraged. \fB--ignore-scripts\fR still takes precedence over this setting. .SS "\fBworkspace\fR" .RS 0 .IP \(bu 4 diff --git a/deps/npm/man/man1/npm-repo.1 b/deps/npm/man/man1/npm-repo.1 index 8ad564f7ddcaeb..00b1754b495937 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-REPO" "1" "May 2026" "NPM@11.16.0" "" .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 e65ef425c25cca..5fb602be957ba2 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-RESTART" "1" "May 2026" "NPM@11.16.0" "" .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 cc07c34f7efeeb..79ab7c6debcb3e 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-ROOT" "1" "May 2026" "NPM@11.16.0" "" .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 ef4291544ae20a..f20c43ab6a7113 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-RUN" "1" "May 2026" "NPM@11.16.0" "" .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 150c4b165f86bf..e04ac0e689169a 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-SBOM" "1" "May 2026" "NPM@11.16.0" "" .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 f8d393b45317a6..51c9a99e58aefe 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-SEARCH" "1" "May 2026" "NPM@11.16.0" "" .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 13895c62176d38..280d610a6e32c4 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-SET" "1" "May 2026" "NPM@11.16.0" "" .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 d7eadbd5438feb..0f7214bda3fd09 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-SHRINKWRAP" "1" "May 2026" "NPM@11.16.0" "" .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 bbef577334aa9f..0fe39e8ff8cffe 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-STAGE" "1" "May 2026" "NPM@11.16.0" "" .SH "NAME" \fBnpm-stage\fR - Stage packages for publishing .SS "Synopsis" @@ -101,7 +101,7 @@ npm stage publish .RE .SS "Flags" .P -| Flag | Default | Type | Description | | --- | --- | --- | --- | | \fB--tag\fR | "latest" | String | If you ask npm to install a package and don't tell it a specific version, then it will install the specified tag. It is the tag added to the package@version specified in the \fBnpm dist-tag add\fR command, if no explicit tag is given. When used by the \fBnpm diff\fR command, this is the tag used to fetch the tarball that will be compared with the local files by default. If used in the \fBnpm publish\fR command, this is the tag that will be added to the package submitted to the registry. | | \fB--access\fR | 'public' for new packages, existing packages it will not change the current level | null, "restricted", or "public" | If you do not want your scoped package to be publicly viewable (and installable) set \fB--access=restricted\fR. Unscoped packages cannot be set to \fBrestricted\fR. Note: This defaults to not changing the current access level for existing packages. Specifying a value of \fBrestricted\fR or \fBpublic\fR during publish will change the access for an existing package the same way that \fBnpm access set status\fR would. | | \fB--dry-run\fR | false | Boolean | Indicates that you don't want npm to make any changes and that it should only report what it would have done. This can be passed into any of the commands that modify your local installation, eg, \fBinstall\fR, \fBupdate\fR, \fBdedupe\fR, \fBuninstall\fR, as well as \fBpack\fR and \fBpublish\fR. Note: This is NOT honored by other network related commands, eg \fBdist-tags\fR, \fBowner\fR, etc. | | \fB--otp\fR | null | null or String | This is a one-time password from a two-factor authenticator. It's needed when publishing or changing package permissions with \fBnpm access\fR. If not set, and a registry response fails with a challenge for a one-time password, npm will prompt on the command line for one. | | \fB--workspace\fR, \fB-w\fR | | String (can be set multiple times) | Enable running a command in the context of the configured workspaces of the current project while filtering by running only the workspaces defined by this configuration option. Valid values for the \fBworkspace\fR config are either: * Workspace names * Path to a workspace directory * Path to a parent workspace directory (will result in selecting all workspaces within that folder) When set for the \fBnpm init\fR command, this may be set to the folder of a workspace which does not yet exist, to create the folder and set it up as a brand new workspace within the project. | | \fB--workspaces\fR | null | null or Boolean | Set to true to run the command in the context of \fBall\fR configured workspaces. Explicitly setting this to false will cause commands like \fBinstall\fR to ignore workspaces altogether. When not set explicitly: - Commands that operate on the \fBnode_modules\fR tree (install, update, etc.) will link workspaces into the \fBnode_modules\fR folder. - Commands that do other things (test, exec, publish, etc.) will operate on the root project, \fIunless\fR one or more workspaces are specified in the \fBworkspace\fR config. | | \fB--include-workspace-root\fR | false | Boolean | Include the workspace root when workspaces are enabled for a command. When false, specifying individual workspaces via the \fBworkspace\fR config, or all workspaces via the \fBworkspaces\fR flag, will cause npm to operate only on the specified workspaces, and not on the root project. | | \fB--provenance\fR | false | Boolean | When publishing from a supported cloud CI/CD system, the package will be publicly linked to where it was built and published from. | +| Flag | Default | Type | Description | | --- | --- | --- | --- | | \fB--tag\fR | "latest" | String | If you ask npm to install a package and don't tell it a specific version, then it will install the specified tag. It is the tag added to the package@version specified in the \fBnpm dist-tag add\fR command, if no explicit tag is given. When used by the \fBnpm diff\fR command, this is the tag used to fetch the tarball that will be compared with the local files by default. If used in the \fBnpm publish\fR command, this is the tag that will be added to the package submitted to the registry. | | \fB--access\fR | 'public' for new packages, existing packages it will not change the current level | null, "restricted", "public", or "private" | If you do not want your scoped package to be publicly viewable (and installable) set \fB--access=restricted\fR. Unscoped packages cannot be set to \fBrestricted\fR. Note: This defaults to not changing the current access level for existing packages. Specifying a value of \fBrestricted\fR or \fBpublic\fR during publish will change the access for an existing package the same way that \fBnpm access set status\fR would. The value \fBprivate\fR is an alias for \fBrestricted\fR. | | \fB--dry-run\fR | false | Boolean | Indicates that you don't want npm to make any changes and that it should only report what it would have done. This can be passed into any of the commands that modify your local installation, eg, \fBinstall\fR, \fBupdate\fR, \fBdedupe\fR, \fBuninstall\fR, as well as \fBpack\fR and \fBpublish\fR. Note: This is NOT honored by other network related commands, eg \fBdist-tags\fR, \fBowner\fR, etc. | | \fB--otp\fR | null | null or String | This is a one-time password from a two-factor authenticator. It's needed when publishing or changing package permissions with \fBnpm access\fR. If not set, and a registry response fails with a challenge for a one-time password, npm will prompt on the command line for one. | | \fB--workspace\fR, \fB-w\fR | | String (can be set multiple times) | Enable running a command in the context of the configured workspaces of the current project while filtering by running only the workspaces defined by this configuration option. Valid values for the \fBworkspace\fR config are either: * Workspace names * Path to a workspace directory * Path to a parent workspace directory (will result in selecting all workspaces within that folder) When set for the \fBnpm init\fR command, this may be set to the folder of a workspace which does not yet exist, to create the folder and set it up as a brand new workspace within the project. | | \fB--workspaces\fR | null | null or Boolean | Set to true to run the command in the context of \fBall\fR configured workspaces. Explicitly setting this to false will cause commands like \fBinstall\fR to ignore workspaces altogether. When not set explicitly: - Commands that operate on the \fBnode_modules\fR tree (install, update, etc.) will link workspaces into the \fBnode_modules\fR folder. - Commands that do other things (test, exec, publish, etc.) will operate on the root project, \fIunless\fR one or more workspaces are specified in the \fBworkspace\fR config. | | \fB--include-workspace-root\fR | false | Boolean | Include the workspace root when workspaces are enabled for a command. When false, specifying individual workspaces via the \fBworkspace\fR config, or all workspaces via the \fBworkspaces\fR flag, will cause npm to operate only on the specified workspaces, and not on the root project. | | \fB--provenance\fR | false | Boolean | When publishing from a supported cloud CI/CD system, the package will be publicly linked to where it was built and published from. | .SS "\fBnpm stage list\fR" .P List all staged package versions diff --git a/deps/npm/man/man1/npm-star.1 b/deps/npm/man/man1/npm-star.1 index ff2f6219714d1b..385d6e8a23249e 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-STAR" "1" "May 2026" "NPM@11.16.0" "" .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 34f968d1331f75..f028349ba81f8c 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-STARS" "1" "May 2026" "NPM@11.16.0" "" .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 5af8739d5978e5..de0605d2fa8fa2 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-START" "1" "May 2026" "NPM@11.16.0" "" .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 e869b3a796f120..57cadfb2fa80bd 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-STOP" "1" "May 2026" "NPM@11.16.0" "" .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 0bc981401b0da4..06d7b94acb7f24 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-TEAM" "1" "May 2026" "NPM@11.16.0" "" .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 a506a985023502..6c1108360e5789 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-TEST" "1" "May 2026" "NPM@11.16.0" "" .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 646db6a114a570..41a69ed9d9c355 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-TOKEN" "1" "May 2026" "NPM@11.16.0" "" .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 61e2e39efa94fc..7b3c4d7990a8c9 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-TRUST" "1" "May 2026" "NPM@11.16.0" "" .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 46d9f977f7c98a..8bb2936691c5c4 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-UNDEPRECATE" "1" "May 2026" "NPM@11.16.0" "" .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 d58ca50d2ba9ce..10845d76cb050f 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-UNINSTALL" "1" "May 2026" "NPM@11.16.0" "" .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 bc1c5ed7b2c5ba..1386bb85ce279d 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-UNPUBLISH" "1" "May 2026" "NPM@11.16.0" "" .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 bb96432baee821..4fabb116f4242f 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-UNSTAR" "1" "May 2026" "NPM@11.16.0" "" .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 75aa70256aff10..e157c16d8a30ee 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-UPDATE" "1" "May 2026" "NPM@11.16.0" "" .SH "NAME" \fBnpm-update\fR - Update packages .SS "Synopsis" @@ -277,6 +277,42 @@ Type: Boolean If true, npm does not run scripts specified in package.json files. .P Note that commands explicitly intended to run a particular script, such as \fBnpm start\fR, \fBnpm stop\fR, \fBnpm restart\fR, \fBnpm test\fR, and \fBnpm run\fR will still run their intended script if \fBignore-scripts\fR is set, but they will \fInot\fR run any pre- or post-scripts. +.SS "\fBallow-scripts\fR" +.RS 0 +.IP \(bu 4 +Default: "" +.IP \(bu 4 +Type: String (can be set multiple times) +.RE 0 + +.P +Comma-separated list of packages whose install-time lifecycle scripts (\fBpreinstall\fR, \fBinstall\fR, \fBpostinstall\fR, and \fBprepare\fR for non-registry dependencies) are allowed to run. +.P +This setting is intended for one-off and global contexts: \fBnpm exec\fR, \fBnpx\fR, and \fBnpm install -g\fR, where no project \fBpackage.json\fR is involved. For team-wide policy in a project, use the \fBallowScripts\fR field in \fBpackage.json\fR (which also supports explicit denials), or configure it in \fB.npmrc\fR. Passing \fB--allow-scripts\fR on the command line during a project-scoped \fBnpm install\fR, \fBci\fR, \fBupdate\fR, or \fBrebuild\fR is an error. +.P +Each name is matched against a dependency's resolved identity, not against the package's self-reported name. \fB--ignore-scripts\fR and \fB--dangerously-allow-all-scripts\fR both override this setting. +.SS "\fBstrict-allow-scripts\fR" +.RS 0 +.IP \(bu 4 +Default: false +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +If \fBtrue\fR, turn the install-script policy from a warning into a hard error: any dependency with install scripts not covered by \fBallowScripts\fR will fail the install instead of running with a notice. +.P +Dependencies explicitly denied with \fBfalse\fR in \fBallowScripts\fR are always silently skipped; this setting only affects unreviewed entries. \fB--ignore-scripts\fR and \fB--dangerously-allow-all-scripts\fR both override this setting. +.SS "\fBdangerously-allow-all-scripts\fR" +.RS 0 +.IP \(bu 4 +Default: false +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +If \fBtrue\fR, bypass the \fBallowScripts\fR policy entirely and run every dependency install script regardless of whether it was approved or denied. Intended as a migration escape hatch only; its use is strongly discouraged. \fB--ignore-scripts\fR still takes precedence over this setting. .SS "\fBaudit\fR" .RS 0 .IP \(bu 4 diff --git a/deps/npm/man/man1/npm-version.1 b/deps/npm/man/man1/npm-version.1 index 7321a301400e4a..78612356235a44 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-VERSION" "1" "May 2026" "NPM@11.16.0" "" .SH "NAME" \fBnpm-version\fR - Bump a package version .SS "Synopsis" @@ -226,6 +226,8 @@ Commit and tag. Run the \fBpostversion\fR script. Use it to clean up the file system or automatically push the commit and/or tag. .RE 0 +.P +For the \fBpreversion\fR, \fBversion\fR and \fBpostversion\fR scripts, npm also sets the \fBenvironment variables\fR \fI\(la/using-npm/scripts#environment\(ra\fR \fBnpm_old_version\fR and \fBnpm_new_version\fR. .P Take the following example: .P diff --git a/deps/npm/man/man1/npm-view.1 b/deps/npm/man/man1/npm-view.1 index 0ef6c63b3dc6d8..5ac8d354fbb51d 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-VIEW" "1" "May 2026" "NPM@11.16.0" "" .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 e7f660e4628eaa..a0c1956514c26e 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-WHOAMI" "1" "May 2026" "NPM@11.16.0" "" .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 e2f3b3e81bb9f5..68369b132c9168 100644 --- a/deps/npm/man/man1/npm.1 +++ b/deps/npm/man/man1/npm.1 @@ -1,4 +1,4 @@ -.TH "NPM" "1" "May 2026" "NPM@11.15.0" "" +.TH "NPM" "1" "May 2026" "NPM@11.16.0" "" .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.15.0 +11.16.0 .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 cfe09b033681af..23671ac8cfb611 100644 --- a/deps/npm/man/man1/npx.1 +++ b/deps/npm/man/man1/npx.1 @@ -1,4 +1,4 @@ -.TH "NPX" "1" "May 2026" "NPM@11.15.0" "" +.TH "NPX" "1" "May 2026" "NPM@11.16.0" "" .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 b7eb083b084bb5..b0a8c9b4825ccb 100644 --- a/deps/npm/man/man5/folders.5 +++ b/deps/npm/man/man5/folders.5 @@ -1,4 +1,4 @@ -.TH "FOLDERS" "5" "May 2026" "NPM@11.15.0" "" +.TH "FOLDERS" "5" "May 2026" "NPM@11.16.0" "" .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 c5e7b921cd50dd..e70fd2ed7b602e 100644 --- a/deps/npm/man/man5/install.5 +++ b/deps/npm/man/man5/install.5 @@ -1,4 +1,4 @@ -.TH "INSTALL" "5" "May 2026" "NPM@11.15.0" "" +.TH "INSTALL" "5" "May 2026" "NPM@11.16.0" "" .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 b7eb083b084bb5..b0a8c9b4825ccb 100644 --- a/deps/npm/man/man5/npm-global.5 +++ b/deps/npm/man/man5/npm-global.5 @@ -1,4 +1,4 @@ -.TH "FOLDERS" "5" "May 2026" "NPM@11.15.0" "" +.TH "FOLDERS" "5" "May 2026" "NPM@11.16.0" "" .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 0d4d8d0480c8cd..3d0c548f7042fa 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" "May 2026" "NPM@11.15.0" "" +.TH "PACKAGE.JSON" "5" "May 2026" "NPM@11.16.0" "" .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 68d66807e0f5c6..104ac0703d7710 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" "May 2026" "NPM@11.15.0" "" +.TH "NPM-SHRINKWRAP.JSON" "5" "May 2026" "NPM@11.16.0" "" .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 3cf3729867b79d..7c5273d6344a25 100644 --- a/deps/npm/man/man5/npmrc.5 +++ b/deps/npm/man/man5/npmrc.5 @@ -1,4 +1,4 @@ -.TH ".NPMRC" "5" "May 2026" "NPM@11.15.0" "" +.TH ".NPMRC" "5" "May 2026" "NPM@11.16.0" "" .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 0d4d8d0480c8cd..3d0c548f7042fa 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" "May 2026" "NPM@11.15.0" "" +.TH "PACKAGE.JSON" "5" "May 2026" "NPM@11.16.0" "" .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 7e0bd7fcd68729..40742966252fa4 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" "May 2026" "NPM@11.15.0" "" +.TH "PACKAGE-LOCK.JSON" "5" "May 2026" "NPM@11.16.0" "" .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 3dcb6d67ecea85..d4b0b124d3f142 100644 --- a/deps/npm/man/man7/config.7 +++ b/deps/npm/man/man7/config.7 @@ -1,4 +1,4 @@ -.TH "CONFIG" "7" "May 2026" "NPM@11.15.0" "" +.TH "CONFIG" "7" "May 2026" "NPM@11.16.0" "" .SH "NAME" \fBConfig\fR - About npm configuration .SS "Description" @@ -174,7 +174,7 @@ Warning: This should generally not be set via a command-line option. It is safer .IP \(bu 4 Default: 'public' for new packages, existing packages it will not change the current level .IP \(bu 4 -Type: null, "restricted", or "public" +Type: null, "restricted", "public", or "private" .RE 0 .P @@ -184,6 +184,8 @@ Unscoped packages cannot be set to \fBrestricted\fR. .P Note: This defaults to not changing the current access level for existing packages. Specifying a value of \fBrestricted\fR or \fBpublic\fR during publish will change the access for an existing package the same way that \fBnpm access set status\fR would. +.P +The value \fBprivate\fR is an alias for \fBrestricted\fR. .SS "\fBall\fR" .RS 0 .IP \(bu 4 @@ -252,6 +254,40 @@ Type: Boolean .P Prevents throwing an error when \fBnpm version\fR is used to set the new version to the same value as the current version. +.SS "\fBallow-scripts\fR" +.RS 0 +.IP \(bu 4 +Default: "" +.IP \(bu 4 +Type: String (can be set multiple times) +.RE 0 + +.P +Comma-separated list of packages whose install-time lifecycle scripts (\fBpreinstall\fR, \fBinstall\fR, \fBpostinstall\fR, and \fBprepare\fR for non-registry dependencies) are allowed to run. +.P +This setting is intended for one-off and global contexts: \fBnpm exec\fR, \fBnpx\fR, and \fBnpm install -g\fR, where no project \fBpackage.json\fR is involved. For team-wide policy in a project, use the \fBallowScripts\fR field in \fBpackage.json\fR (which also supports explicit denials), or configure it in \fB.npmrc\fR. Passing \fB--allow-scripts\fR on the command line during a project-scoped \fBnpm install\fR, \fBci\fR, \fBupdate\fR, or \fBrebuild\fR is an error. +.P +Each name is matched against a dependency's resolved identity, not against the package's self-reported name. \fB--ignore-scripts\fR and \fB--dangerously-allow-all-scripts\fR both override this setting. +.SS "\fBallow-scripts-pending\fR" +.RS 0 +.IP \(bu 4 +Default: false +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +List packages with install scripts that are not yet covered by the \fBallowScripts\fR policy, without modifying \fBpackage.json\fR. Only meaningful for \fBnpm approve-scripts\fR. +.SS "\fBallow-scripts-pin\fR" +.RS 0 +.IP \(bu 4 +Default: true +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +Write pinned (\fBpkg@version\fR) entries when approving install scripts. Set to \fBfalse\fR to write name-only entries that allow any version. Has no effect on \fBnpm deny-scripts\fR, which always writes name-only entries regardless of this setting. .SS "\fBaudit\fR" .RS 0 .IP \(bu 4 @@ -437,6 +473,16 @@ Type: null or String .P Override CPU architecture of native modules to install. Acceptable values are same as \fBcpu\fR field of package.json, which comes from \fBprocess.arch\fR. +.SS "\fBdangerously-allow-all-scripts\fR" +.RS 0 +.IP \(bu 4 +Default: false +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +If \fBtrue\fR, bypass the \fBallowScripts\fR policy entirely and run every dependency install script regardless of whether it was approved or denied. Intended as a migration escape hatch only; its use is strongly discouraged. \fB--ignore-scripts\fR still takes precedence over this setting. .SS "\fBdepth\fR" .RS 0 .IP \(bu 4 @@ -1759,6 +1805,18 @@ Type: Boolean If set to true, then the \fBnpm version\fR command will tag the version using \fB-s\fR to add a signature. .P Note that git requires you to have set up GPG keys in your git configs for this to work properly. +.SS "\fBstrict-allow-scripts\fR" +.RS 0 +.IP \(bu 4 +Default: false +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +If \fBtrue\fR, turn the install-script policy from a warning into a hard error: any dependency with install scripts not covered by \fBallowScripts\fR will fail the install instead of running with a notice. +.P +Dependencies explicitly denied with \fBfalse\fR in \fBallowScripts\fR are always silently skipped; this setting only affects unreviewed entries. \fB--ignore-scripts\fR and \fB--dangerously-allow-all-scripts\fR both override this setting. .SS "\fBstrict-peer-deps\fR" .RS 0 .IP \(bu 4 diff --git a/deps/npm/man/man7/dependency-selectors.7 b/deps/npm/man/man7/dependency-selectors.7 index 95849879c7256f..4b67c2a3be5892 100644 --- a/deps/npm/man/man7/dependency-selectors.7 +++ b/deps/npm/man/man7/dependency-selectors.7 @@ -1,4 +1,4 @@ -.TH "SELECTORS" "7" "May 2026" "NPM@11.15.0" "" +.TH "SELECTORS" "7" "May 2026" "NPM@11.16.0" "" .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 2f7d9113a180bc..5b41ed26374205 100644 --- a/deps/npm/man/man7/developers.7 +++ b/deps/npm/man/man7/developers.7 @@ -1,4 +1,4 @@ -.TH "DEVELOPERS" "7" "May 2026" "NPM@11.15.0" "" +.TH "DEVELOPERS" "7" "May 2026" "NPM@11.16.0" "" .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 f5c65d13c25680..474becd6ff6431 100644 --- a/deps/npm/man/man7/logging.7 +++ b/deps/npm/man/man7/logging.7 @@ -1,4 +1,4 @@ -.TH "LOGGING" "7" "May 2026" "NPM@11.15.0" "" +.TH "LOGGING" "7" "May 2026" "NPM@11.16.0" "" .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 d9c76b7bc683cd..e0666bdf0f3389 100644 --- a/deps/npm/man/man7/orgs.7 +++ b/deps/npm/man/man7/orgs.7 @@ -1,4 +1,4 @@ -.TH "ORGANIZATIONS" "7" "May 2026" "NPM@11.15.0" "" +.TH "ORGANIZATIONS" "7" "May 2026" "NPM@11.16.0" "" .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 9e4eb36e857b0f..76d07709632b0c 100644 --- a/deps/npm/man/man7/package-spec.7 +++ b/deps/npm/man/man7/package-spec.7 @@ -1,4 +1,4 @@ -.TH "SPEC" "7" "May 2026" "NPM@11.15.0" "" +.TH "SPEC" "7" "May 2026" "NPM@11.16.0" "" .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 41ce62cdc6b792..cbfca0c6f42d3c 100644 --- a/deps/npm/man/man7/registry.7 +++ b/deps/npm/man/man7/registry.7 @@ -1,4 +1,4 @@ -.TH "REGISTRY" "7" "May 2026" "NPM@11.15.0" "" +.TH "REGISTRY" "7" "May 2026" "NPM@11.16.0" "" .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 144c6ca788afc6..fa44b56539a008 100644 --- a/deps/npm/man/man7/removal.7 +++ b/deps/npm/man/man7/removal.7 @@ -1,4 +1,4 @@ -.TH "REMOVAL" "7" "May 2026" "NPM@11.15.0" "" +.TH "REMOVAL" "7" "May 2026" "NPM@11.16.0" "" .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 7491b29be87116..7857ede645fa92 100644 --- a/deps/npm/man/man7/scope.7 +++ b/deps/npm/man/man7/scope.7 @@ -1,4 +1,4 @@ -.TH "SCOPE" "7" "May 2026" "NPM@11.15.0" "" +.TH "SCOPE" "7" "May 2026" "NPM@11.16.0" "" .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 4c24ce5e54c772..5cfab1d64cd2e9 100644 --- a/deps/npm/man/man7/scripts.7 +++ b/deps/npm/man/man7/scripts.7 @@ -1,4 +1,4 @@ -.TH "SCRIPTS" "7" "May 2026" "NPM@11.15.0" "" +.TH "SCRIPTS" "7" "May 2026" "NPM@11.16.0" "" .SH "NAME" \fBScripts\fR - How npm handles the "scripts" field .SS "Description" @@ -382,6 +382,16 @@ For example, if you had \fB{"name":"foo", "version":"1.2.5"}\fR in your package. \fBNote:\fR In npm 7 and later, most package.json fields are no longer provided as environment variables. Scripts that need access to other package.json fields should read the package.json file directly. The \fBnpm_package_json\fR environment variable provides the path to the file for this purpose. .P See \fB\[rs]fBpackage.json\[rs]fR\fR \fI\(la/configuring-npm/package-json\(ra\fR for more on package configs. +.SS "versioning variables" +.P +For versioning scripts (\fBpreversion\fR, \fBversion\fR, \fBpostversion\fR), npm sets these environment variables: +.RS 0 +.IP \(bu 4 +\fBnpm_old_version\fR - The version before being bumped +.IP \(bu 4 +\fBnpm_new_version\fR \[en] The version after being bumped +.RE 0 + .SS "current lifecycle event" .P Lastly, the \fBnpm_lifecycle_event\fR environment variable is set to whichever stage of the cycle is being executed. So, you could have a single script used for different parts of the process which switches based on what's currently happening. diff --git a/deps/npm/man/man7/workspaces.7 b/deps/npm/man/man7/workspaces.7 index 9cd6628201a000..be7a22047d9971 100644 --- a/deps/npm/man/man7/workspaces.7 +++ b/deps/npm/man/man7/workspaces.7 @@ -1,4 +1,4 @@ -.TH "WORKSPACES" "7" "May 2026" "NPM@11.15.0" "" +.TH "WORKSPACES" "7" "May 2026" "NPM@11.16.0" "" .SH "NAME" \fBWorkspaces\fR - Working with workspaces .SS "Description" diff --git a/deps/npm/node_modules/@npmcli/agent/lib/agents.js b/deps/npm/node_modules/@npmcli/agent/lib/agents.js index c541b93001517e..e9624dfeb90090 100644 --- a/deps/npm/node_modules/@npmcli/agent/lib/agents.js +++ b/deps/npm/node_modules/@npmcli/agent/lib/agents.js @@ -203,4 +203,56 @@ module.exports = class Agent extends AgentBase { return super.addRequest(request, options) } + + // When connect() rejects, agent-base removes only its placeholder socket, so Node never drains this.requests[name] and requests queued past maxSockets hang forever. + // On a failure we dispatch the next queued request ourselves. + // See npm/cli#9386 and TooTallNate/proxy-agents#427. + createSocket (req, options, cb) { + super.createSocket(req, options, (err, socket) => { + if (err) { + this.#drainPendingRequests(req, options) + } + cb(err, socket) + }) + } + + // Dispatch the next request queued behind maxSockets, reusing the slot the failed connection freed. + #drainPendingRequests (failedReq, options) { + const name = this.getName(options) + const queue = this.requests[name] + if (!queue || queue.length === 0) { + return + } + + // Node's removeSocket() picks a queued request without shifting it off, so drop the failed one to avoid dispatching it twice. + const failedIndex = queue.indexOf(failedReq) + if (failedIndex !== -1) { + queue.splice(failedIndex, 1) + } + if (queue.length === 0) { + delete this.requests[name] + return + } + + // Safety belt: only dispatch if a socket slot is genuinely free. + const socketCount = this.sockets[name] ? this.sockets[name].length : 0 + if (socketCount >= this.maxSockets || this.totalSocketCount >= this.maxTotalSockets) { + return + } + + const nextReq = queue.shift() + if (queue.length === 0) { + delete this.requests[name] + } + + // All queued requests share this origin, so the failed request's options suit the next one. + // createSocket() recurses here if this connection also fails, draining the whole queue. + this.createSocket(nextReq, options, (err, socket) => { + if (err) { + nextReq.onSocket(null, err) + } else { + nextReq.onSocket(socket) + } + }) + } } diff --git a/deps/npm/node_modules/@npmcli/agent/lib/options.js b/deps/npm/node_modules/@npmcli/agent/lib/options.js index 0bf53f725f0846..a6ae490a89c3b3 100644 --- a/deps/npm/node_modules/@npmcli/agent/lib/options.js +++ b/deps/npm/node_modules/@npmcli/agent/lib/options.js @@ -37,6 +37,10 @@ const normalizeOptions = (opts) => { // remove timeout since we already used it to set our own idle timeout delete normalized.timeout + // since opts is often passed when initiating requests, it may contain + // headers, which should not be saved in an agent + delete normalized.headers + return normalized } diff --git a/deps/npm/node_modules/@npmcli/agent/package.json b/deps/npm/node_modules/@npmcli/agent/package.json index 67670a0c1c484e..8c0d358b02a717 100644 --- a/deps/npm/node_modules/@npmcli/agent/package.json +++ b/deps/npm/node_modules/@npmcli/agent/package.json @@ -1,6 +1,6 @@ { "name": "@npmcli/agent", - "version": "4.0.0", + "version": "4.0.2", "description": "the http/https agent used by the npm cli", "main": "lib/index.js", "scripts": { @@ -29,8 +29,10 @@ }, "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "4.25.0", - "publish": "true" + "version": "4.30.0", + "publish": "true", + "updateNpm": false, + "latestCiVersion": 24 }, "dependencies": { "agent-base": "^7.1.0", @@ -40,11 +42,11 @@ "socks-proxy-agent": "^8.0.3" }, "devDependencies": { - "@npmcli/eslint-config": "^5.0.0", - "@npmcli/template-oss": "4.25.0", - "minipass-fetch": "^4.0.1", + "@npmcli/eslint-config": "^6.0.0", + "@npmcli/template-oss": "4.30.0", + "ip-address": "^10.1.0", + "minipass-fetch": "^5.0.0", "nock": "^14.0.3", - "socksv5": "^0.0.6", "tap": "^16.3.0" }, "repository": { diff --git a/deps/npm/node_modules/@npmcli/arborist/lib/arborist/index.js b/deps/npm/node_modules/@npmcli/arborist/lib/arborist/index.js index eda38947462609..11581cb4fd9400 100644 --- a/deps/npm/node_modules/@npmcli/arborist/lib/arborist/index.js +++ b/deps/npm/node_modules/@npmcli/arborist/lib/arborist/index.js @@ -100,8 +100,10 @@ class Arborist extends Base { nodeVersion: process.version, ...options, Arborist: this.constructor, + allowScripts: options.allowScripts ?? null, binLinks: 'binLinks' in options ? !!options.binLinks : true, cache: options.cache || `${homedir()}/.npm/_cacache`, + dangerouslyAllowAllScripts: !!options.dangerouslyAllowAllScripts, dryRun: !!options.dryRun, formatPackageLock: 'formatPackageLock' in options ? !!options.formatPackageLock : true, force: !!options.force, diff --git a/deps/npm/node_modules/@npmcli/arborist/lib/arborist/isolated-reifier.js b/deps/npm/node_modules/@npmcli/arborist/lib/arborist/isolated-reifier.js index a285da0a45c9a2..14f432ca977459 100644 --- a/deps/npm/node_modules/@npmcli/arborist/lib/arborist/isolated-reifier.js +++ b/deps/npm/node_modules/@npmcli/arborist/lib/arborist/isolated-reifier.js @@ -335,7 +335,8 @@ module.exports = cls => class IsolatedReifier extends cls { root.inventory.set(workspace.location, workspace) root.workspaces.set(wsName, workspace.path) - // Create workspace Link. For root declared deps, link at root node_modules/. For undeclared deps, link at the workspace's own node_modules/ (self-link). + // Declared workspaces are symlinked at root node_modules/. + // Undeclared workspaces get a tree-only Link kept for diff/filter participation but not materialized on disk. const isDeclared = this.#rootDeclaredDeps.has(wsName) const wsLink = new IsolatedLink({ location: isDeclared ? join('node_modules', wsName) : join(c.localLocation, 'node_modules', wsName), @@ -348,7 +349,7 @@ module.exports = cls => class IsolatedReifier extends cls { target: workspace, }) if (!isDeclared) { - workspace.children.set(wsName, wsLink) + wsLink.isUndeclaredWorkspaceLink = true } root.children.set(wsName, wsLink) root.inventory.set(wsLink.location, wsLink) diff --git a/deps/npm/node_modules/@npmcli/arborist/lib/arborist/rebuild.js b/deps/npm/node_modules/@npmcli/arborist/lib/arborist/rebuild.js index d4cce1ac02776c..e70a2186c29713 100644 --- a/deps/npm/node_modules/@npmcli/arborist/lib/arborist/rebuild.js +++ b/deps/npm/node_modules/@npmcli/arborist/lib/arborist/rebuild.js @@ -12,6 +12,7 @@ const { isNodeGypPackage, defaultGypInstallScript } = require('@npmcli/node-gyp' const { promiseRetry } = require('@gar/promise-retry') const { log, time } = require('proc-log') const { resolve } = require('node:path') +const { isScriptAllowed } = require('../script-allowed.js') const boolEnv = b => b ? '1' : '' const sortNodes = (a, b) => (a.depth - b.depth) || localeCompare(a.path, b.path) @@ -225,6 +226,18 @@ module.exports = cls => class Builder extends cls { return } + // Phase 1 allowScripts gate: a `false` verdict from the policy matcher + // means the user explicitly denied install scripts for this node, so skip + // it. `true` and `null` (unreviewed) both fall through to the existing + // detection logic — unreviewed nodes still run their scripts in Phase 1 + // and are surfaced via the post-reify advisory warning. The global + // --ignore-scripts kill switch in #build() still takes precedence, and + // --dangerously-allow-all-scripts bypasses this gate entirely. + if (!this.options.dangerouslyAllowAllScripts && + isScriptAllowed(node, this.options.allowScripts) === false) { + return + } + if (this.#oldMeta === null) { const { root: { meta } } = node this.#oldMeta = meta && meta.loadedFromDisk && diff --git a/deps/npm/node_modules/@npmcli/arborist/lib/arborist/reify.js b/deps/npm/node_modules/@npmcli/arborist/lib/arborist/reify.js index c9f08de776e462..38fb4e37589255 100644 --- a/deps/npm/node_modules/@npmcli/arborist/lib/arborist/reify.js +++ b/deps/npm/node_modules/@npmcli/arborist/lib/arborist/reify.js @@ -239,7 +239,7 @@ module.exports = cls => class Reifier extends cls { this.actualTree = this.idealTree this.idealTree = null - if (!this.options.global) { + if (!this.options.global && !this.options.dryRun) { await this.actualTree.meta.save() const ignoreScripts = !!this.options.ignoreScripts // if we aren't doing a dry run or ignoring scripts and we actually made changes to the dep @@ -760,6 +760,12 @@ module.exports = cls => class Reifier extends cls { } // node.isLink + + // Tree-only Link: present in the tree for diff/filter participation, never materialized on disk. + if (node.isUndeclaredWorkspaceLink) { + return + } + await rm(node.path, { recursive: true, force: true }) // symlink @@ -1381,6 +1387,10 @@ module.exports = cls => class Reifier extends cls { if (!child.isLink) { continue } + // Tree-only Links never exist on disk; skipping them lets the sweep remove any stale self-link left by an older npm version. + if (child.isUndeclaredWorkspaceLink) { + continue + } const nmIdx = loc.lastIndexOf(NM_PREFIX) if (nmIdx === -1 || loc.includes(STORE_MARKER)) { continue diff --git a/deps/npm/node_modules/@npmcli/arborist/lib/install-scripts.js b/deps/npm/node_modules/@npmcli/arborist/lib/install-scripts.js new file mode 100644 index 00000000000000..47a7f982c04ef1 --- /dev/null +++ b/deps/npm/node_modules/@npmcli/arborist/lib/install-scripts.js @@ -0,0 +1,88 @@ +const { isNodeGypPackage } = require('@npmcli/node-gyp') + +// Returns the install-relevant lifecycle scripts that would run for a +// given arborist Node, or `{}` if there are none. +// +// Includes: +// - explicit preinstall/install/postinstall +// - prepare, but only for non-registry sources (git, file, link, remote) +// - synthetic `node-gyp rebuild`, when `binding.gyp` is present on disk +// and the package does not opt out via `gypfile: false` or define its +// own install / preinstall script + +// Lifecycle-script enumeration boundary. +// +// IMPORTANT: this helper decides whether `prepare` should be included +// in the enumerated install scripts (true for non-registry sources only). +// It is NOT a policy-matching predicate. The policy matcher in +// script-allowed.js uses `isRegistryNode`, which is strictly tied to +// versionFromTgz(node.resolved). The two helpers exist separately on +// purpose: +// +// - `hasNonRegistryShape` (here): "should we consider running prepare +// on this node?" — a yes/no for what to enumerate. +// - `isRegistryNode` (script-allowed.js): "do we trust this node's +// identity enough to apply a policy entry?" — a security check. +// +// The looser fallback here (treating unknown-resolved nodes as registry, +// thus skipping `prepare`) is the safer default for enumeration: we'd +// rather omit a script we should have run than synthesise one for a +// non-registry source we couldn't confirm. The policy matcher's stricter +// behaviour is correct for its boundary; the two helpers must not be +// merged. +const hasNonRegistryShape = (node) => { + if (typeof node.isRegistryDependency === 'boolean') { + return !node.isRegistryDependency + } + if (!node.resolved) { + return false + } + return !/^https?:\/\/[^/]+\/.+\/-\/[^/]+-\d/.test(node.resolved) +} + +const getInstallScripts = async (node) => { + /* istanbul ignore next: arborist Nodes always carry a `package` object; + defensive fallbacks for non-arborist callers. */ + const pkg = node.package || {} + /* istanbul ignore next */ + const scripts = pkg.scripts || {} + const collected = {} + + if (scripts.preinstall) { + collected.preinstall = scripts.preinstall + } + if (scripts.install) { + collected.install = scripts.install + } + if (scripts.postinstall) { + collected.postinstall = scripts.postinstall + } + if (scripts.prepare && hasNonRegistryShape(node)) { + collected.prepare = scripts.prepare + } + + const hasExplicitGypGate = !!(collected.preinstall || collected.install) + if ( + !hasExplicitGypGate && + pkg.gypfile !== false && + await isNodeGypPackage(node.path).catch(() => false) + ) { + collected.install = 'node-gyp rebuild' + } + + // Lockfile-only nodes (e.g. `npm ci` before reify) carry + // `hasInstallScript: true` but no enumerated scripts: the lockfile + // records the presence flag but never the script bodies. Without this + // fallback the strict-allow-scripts preflight would miss them entirely + // and let postinstall run. We can't recover the real script body + // without fetching the manifest, so emit a sentinel describing that + // install scripts are present. + if (Object.keys(collected).length === 0 && node.hasInstallScript === true) { + collected.install = '(install scripts present)' + } + + return collected +} + +module.exports = getInstallScripts +module.exports.getInstallScripts = getInstallScripts diff --git a/deps/npm/node_modules/@npmcli/arborist/lib/script-allowed.js b/deps/npm/node_modules/@npmcli/arborist/lib/script-allowed.js new file mode 100644 index 00000000000000..91734fa38c1034 --- /dev/null +++ b/deps/npm/node_modules/@npmcli/arborist/lib/script-allowed.js @@ -0,0 +1,340 @@ +const npa = require('npm-package-arg') +const semver = require('semver') +const versionFromTgz = require('./version-from-tgz.js') + +// Identity matcher for the allowScripts policy. +// +// Returns: +// - true: at least one allow entry matches and no deny entry matches +// - false: at least one deny entry matches (deny wins on conflict) +// - null: no entry matches (unreviewed) +// +// `policy` is a flat object of `spec-key -> boolean`, where spec-key is +// anything `npm-package-arg` can parse. `node` is an arborist Node. +// +// Identity rules (see RFC npm/rfcs#868): +// - registry deps match by the name+version parsed from the lockfile's +// resolved URL, NOT by `node.packageName` / `node.version`. Those two +// getters return `node.package.name` / `node.package.version`, which +// come from the tarball's own package.json and are therefore +// attacker-controlled. A package can publish a tarball claiming any +// name; the only trusted name is the one baked into the registry URL. +// - tarball / file / link / remote: exact match on node.resolved +// - git: match on hosted.ssh() plus a short-SHA prefix of the +// resolved committish + +const isScriptAllowed = (node, policy) => { + // Bundled dependencies cannot be allowlisted in Phase 1. The RFC defers + // allowlisting them to a follow-up RFC because matching by name@version + // from the bundled tarball would reintroduce manifest confusion (a + // bundled tarball can claim any name and version). Returning null here + // marks bundled deps as unreviewed regardless of any policy entries, so + // their install scripts surface in the Phase 1 advisory warning and + // (eventually) get blocked at the install-time gate. + if (node.inBundle) { + return null + } + + if (!policy || typeof policy !== 'object') { + return null + } + + let anyAllow = false + let anyDeny = false + + for (const [key, value] of Object.entries(policy)) { + if (!matches(node, key)) { + continue + } + if (value === false) { + anyDeny = true + continue + } + /* istanbul ignore else: policy values are strictly true/false; + defensive guard against unexpected coercions. */ + if (value === true) { + anyAllow = true + } + } + + if (anyDeny) { + return false + } + if (anyAllow) { + return true + } + return null +} + +const matches = (node, key) => { + let parsed + try { + parsed = npa(key) + } catch { + return false + } + + switch (parsed.type) { + case 'tag': + case 'range': + case 'version': + return matchRegistry(node, parsed) + case 'git': + return matchGit(node, parsed) + case 'file': + case 'directory': + return matchFileOrDir(node, parsed) + case 'remote': + return matchRemote(node, parsed) + case 'alias': + // Disallowed: aliases as policy keys do not match anything. + // The user has to address the real package name. + return false + /* istanbul ignore next: switch above covers every npa type we expect; + defensive fallback for future npa types. */ + default: + return false + } +} + +const matchRegistry = (node, parsed) => { + // If this node is not a registry dep, refuse the match. A registry-style + // key (`pkg`, `pkg@1`, `pkg@1 || 2`) must not match a tarball or git node + // even if their names happen to coincide. + if (!isRegistryNode(node)) { + return false + } + + // Derive the trusted name+version from the lockfile's resolved URL. + // Never use `node.packageName` / `node.version` here: those read from + // the tarball's own package.json and can be forged by a malicious + // publisher to bypass an allowScripts entry. + const trusted = getTrustedRegistryIdentity(node) + if (!trusted || trusted.name !== parsed.name) { + return false + } + + // `tag` covers `pkg@latest`. Rejected up front by validatePolicy in + // resolve-allow-scripts.js because tags look like a pin but can't be + // verified at install time. Defense-in-depth: if one slips through + // (e.g. arborist invoked directly without the resolver), don't match. + if (parsed.type === 'tag') { + /* istanbul ignore next: validatePolicy filters this; defensive */ + return false + } + + // `range` includes `pkg@^1`, `pkg@1 || 2`, `pkg@*`, `pkg@>=0`, and bare + // names like `pkg` (npa parses these as range with fetchSpec='*'). The + // RFC permits bare names (name-only allow) and exact versions joined by + // `||`; ranges like ^/~/>=/< are rejected because they would silently + // allow versions the user has never reviewed. + if (parsed.type === 'range') { + // Bare name or `pkg@*`: treat as name-only allow. + if (parsed.fetchSpec === '*' || parsed.rawSpec === '' || parsed.rawSpec === '*') { + return true + } + if (!trusted.version || !isExactVersionDisjunction(parsed.fetchSpec)) { + return false + } + return semver.satisfies(trusted.version, parsed.fetchSpec, { loose: true }) + } + + // `version` is an exact pin like `pkg@1.2.3`. + /* istanbul ignore else: parsed.type at this point is always 'version'; + the istanbul-ignored fallback below handles the impossible case. */ + if (parsed.type === 'version') { + return trusted.version === parsed.fetchSpec + } + + /* istanbul ignore next: parsed.type is constrained to tag/range/version + by the caller; this final fallback is defensive. */ + return false +} + +// Derive a registry node's trusted name+version. +// +// Preferred source: the lockfile's resolved URL parsed via +// versionFromTgz. arborist records the URL when it first adds the dep, +// before any tarball is unpacked, so the URL cannot be forged by the +// package's own package.json. +// +// Fallback for lockfiles produced with omit-lockfile-registry-resolved +// (where the URL is absent): take the dep name from an incoming +// dependency edge. The edge's spec was written by the consumer (or by an +// upstream package.json), not by the installed tarball. For aliases like +// `"trusted": "npm:naughty@1.0.0"`, the underlying registered package +// name is parsed out of the alias `subSpec`. The install location +// (`node_modules/trusted`) is deliberately not consulted because for +// aliases it carries only the alias name, which would let a malicious +// publisher bypass an allowScripts entry written for the real package. +// +// Version is left null in the fallback case because the only remaining +// source for it (`node.version`) reads from the tarball. +// +// Returns `{ name, version }` or `null` if no trusted identity exists. +const getTrustedRegistryIdentity = (node) => { + if (node.resolved && typeof node.resolved === 'string') { + const parsed = versionFromTgz('', node.resolved) + /* istanbul ignore else: versionFromTgz returns either a complete + { name, version } or null; partial objects are not produced. */ + if (parsed && parsed.name && parsed.version) { + return parsed + } + } + const name = nameFromEdges(node) + if (name) { + return { name, version: null } + } + return null +} + +const nameFromEdges = (node) => { + if (!node.edgesIn || typeof node.edgesIn[Symbol.iterator] !== 'function') { + return null + } + for (const edge of node.edgesIn) { + let parsed + try { + parsed = npa.resolve(edge.name, edge.spec) + } catch { + continue + } + // Aliases: trust the underlying registered package, not the alias. + if (parsed.type === 'alias' && parsed.subSpec && parsed.subSpec.registry) { + return parsed.subSpec.name + } + // Non-aliased registry edge: the edge name is the package name as + // written by the consumer / upstream, which is trusted (it is not + // read from the installed tarball). + if (parsed.registry) { + return parsed.name + } + } + return null +} + +// True if `rangeSpec` is one or more exact versions joined by `||`. Anything +// containing comparator operators (^, ~, >=, <, *) returns false. +const isExactVersionDisjunction = (rangeSpec) => { + /* istanbul ignore next: caller always passes parsed.fetchSpec, which + npa guarantees to be a non-empty string for range specs. */ + if (typeof rangeSpec !== 'string' || rangeSpec.trim() === '') { + return false + } + const parts = rangeSpec.split('||').map(p => p.trim()) + /* istanbul ignore next: String.prototype.split always returns at least + one element; defensive guard only. */ + if (parts.length === 0) { + return false + } + return parts.every(p => p !== '' && semver.valid(p) !== null) +} + +const matchGit = (node, parsed) => { + if (!node.resolved || !node.resolved.startsWith('git')) { + return false + } + + let nodeParsed + try { + nodeParsed = npa(node.resolved) + } catch { + /* istanbul ignore next: npa parsing a git URL we already validated + starts with `git` should not throw; defensive guard only. */ + return false + } + + // Compare the host/repo. Both sides should resolve to the same canonical + // ssh URL. + const noCommittish = { noCommittish: true } + const keyHost = parsed.hosted?.ssh(noCommittish) + const nodeHost = nodeParsed.hosted?.ssh(noCommittish) + if (keyHost && nodeHost) { + if (keyHost !== nodeHost) { + return false + } + } else if (parsed.fetchSpec && nodeParsed.fetchSpec) { + // Non-hosted git URLs: fall back to fetch spec. + if (parsed.fetchSpec !== nodeParsed.fetchSpec) { + return false + } + } else { + return false + } + + // If the policy key has no committish, name-only match. + const keyCommittish = parsed.gitCommittish || parsed.hosted?.committish + if (!keyCommittish) { + return true + } + + // Match the resolved full SHA against the key's committish. Users + // typically write short SHAs in the policy; the lockfile stores 40-char + // SHAs. Direction matters: the lockfile's full SHA must START WITH the + // key's short SHA, never the reverse. A longer key matching a shorter + // resolved committish would let a malformed lockfile or a divergent + // resolver allow scripts the user never approved. + const nodeCommittish = nodeParsed.gitCommittish || nodeParsed.hosted?.committish || '' + if (!nodeCommittish) { + return false + } + return nodeCommittish.startsWith(keyCommittish) +} + +const matchFileOrDir = (node, parsed) => { + if (!node.resolved) { + return false + } + return node.resolved === parsed.saveSpec || node.resolved === parsed.fetchSpec +} + +const matchRemote = (node, parsed) => { + if (!node.resolved) { + return false + } + return node.resolved === parsed.fetchSpec || node.resolved === parsed.saveSpec +} + +const isRegistryNode = (node) => { + // Prefer arborist's edge-based check when available (real Node objects). + // It inspects the incoming edges' specs and only returns true if every + // edge resolves to a registry spec, which is much harder to spoof than + // the URL. + if (typeof node.isRegistryDependency === 'boolean') { + return node.isRegistryDependency + } + // Fall back to URL parsing for nodes without the arborist getter + // (e.g. test fixtures, lockfiles with omit-lockfile-registry-resolved). + // Treat the node as a registry dep when: + // - resolved is missing entirely (omitLockfileRegistryResolved), + // - resolved is an https/http URL pointing at a registry tarball, or + // - resolved is undefined and the node has a version (defensive). + if (!node.resolved) { + return !!node.version + } + // Registry tarballs live at `//-/-.tgz`. + // Require a path segment before `/-/` so an attacker can't lift a + // registry-style allow entry to a hostile URL like + // `https://evil.com/-/trusted-1.0.0.tgz`. + return /^https?:\/\/[^/]+\/.+\/-\/[^/]+-\d/.test(node.resolved) +} + +// Trusted display identity for human-facing output (`npm install` +// advisory, `npm approve-scripts --allow-scripts-pending`). Same idea as +// getTrustedRegistryIdentity, but for DISPLAY only — version falls back +// to node.version when the URL doesn't carry one. Must never be used +// for policy matching. +const trustedDisplay = (node) => { + const trusted = getTrustedRegistryIdentity(node) + /* istanbul ignore next: defensive fallbacks for nodes without name/version */ + return { + name: (trusted && trusted.name) || node.name || null, + version: (trusted && trusted.version) || node.version || null, + } +} + +module.exports = isScriptAllowed +module.exports.isScriptAllowed = isScriptAllowed +module.exports.isExactVersionDisjunction = isExactVersionDisjunction +module.exports.getTrustedRegistryIdentity = getTrustedRegistryIdentity +module.exports.trustedDisplay = trustedDisplay diff --git a/deps/npm/node_modules/@npmcli/arborist/package.json b/deps/npm/node_modules/@npmcli/arborist/package.json index c8c464e8d3a7e4..712151e63a1c65 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.6.0", + "version": "9.7.0", "description": "Manage node_modules trees", "dependencies": { "@gar/promise-retry": "^1.0.0", diff --git a/deps/npm/node_modules/@npmcli/config/lib/definitions/definitions.js b/deps/npm/node_modules/@npmcli/config/lib/definitions/definitions.js index d54e1845d60777..2cb03709d73b5e 100644 --- a/deps/npm/node_modules/@npmcli/config/lib/definitions/definitions.js +++ b/deps/npm/node_modules/@npmcli/config/lib/definitions/definitions.js @@ -1,4 +1,5 @@ const Definition = require('./definition.js') +const parseAllowScriptsList = require('../parse-allow-scripts-list.js') const ciInfo = require('ci-info') const querystring = require('node:querystring') @@ -153,7 +154,7 @@ const definitions = { defaultDescription: ` 'public' for new packages, existing packages it will not change the current level `, - type: [null, 'restricted', 'public'], + type: [null, 'restricted', 'public', 'private'], description: ` If you do not want your scoped package to be publicly viewable (and installable) set \`--access=restricted\`. @@ -164,8 +165,13 @@ const definitions = { packages. Specifying a value of \`restricted\` or \`public\` during publish will change the access for an existing package the same way that \`npm access set status\` would. + + The value \`private\` is an alias for \`restricted\`. `, - flatten, + flatten (key, obj, flatOptions) { + const value = obj[key] + flatOptions.access = value === 'private' ? 'restricted' : value + }, }), all: new Definition('all', { default: false, @@ -247,6 +253,31 @@ const definitions = { `, flatten, }), + 'allow-scripts': new Definition('allow-scripts', { + default: '', + type: [String, Array], + hint: '', + description: ` + Comma-separated list of packages whose install-time lifecycle scripts + (\`preinstall\`, \`install\`, \`postinstall\`, and \`prepare\` for + non-registry dependencies) are allowed to run. + + This setting is intended for one-off and global contexts: \`npm exec\`, + \`npx\`, and \`npm install -g\`, where no project \`package.json\` is + involved. For team-wide policy in a project, use the \`allowScripts\` + field in \`package.json\` (which also supports explicit denials), or + configure it in \`.npmrc\`. Passing \`--allow-scripts\` on the command + line during a project-scoped \`npm install\`, \`ci\`, \`update\`, or + \`rebuild\` is an error. + + Each name is matched against a dependency's resolved identity, not + against the package's self-reported name. \`--ignore-scripts\` and + \`--dangerously-allow-all-scripts\` both override this setting. + `, + flatten (key, obj, flatOptions) { + flatOptions.allowScripts = parseAllowScriptsList(obj[key]) + }, + }), also: new Definition('also', { default: null, type: [null, 'dev', 'development'], @@ -535,6 +566,18 @@ const definitions = { `, flatten, }), + 'dangerously-allow-all-scripts': new Definition('dangerously-allow-all-scripts', { + default: false, + type: Boolean, + description: ` + If \`true\`, bypass the \`allowScripts\` policy entirely and run every + dependency install script regardless of whether it was approved or + denied. Intended as a migration escape hatch only; its use is strongly + discouraged. \`--ignore-scripts\` still takes precedence over this + setting. + `, + flatten, + }), depth: new Definition('depth', { default: null, defaultDescription: ` @@ -1667,6 +1710,27 @@ const definitions = { `, flatten, }), + 'allow-scripts-pending': new Definition('allow-scripts-pending', { + default: false, + type: Boolean, + description: ` + List packages with install scripts that are not yet covered by the + \`allowScripts\` policy, without modifying \`package.json\`. Only + meaningful for \`npm approve-scripts\`. + `, + flatten, + }), + 'allow-scripts-pin': new Definition('allow-scripts-pin', { + default: true, + type: Boolean, + description: ` + Write pinned (\`pkg@version\`) entries when approving install scripts. + Set to \`false\` to write name-only entries that allow any version. + Has no effect on \`npm deny-scripts\`, which always writes name-only + entries regardless of this setting. + `, + flatten, + }), 'prefer-dedupe': new Definition('prefer-dedupe', { default: false, type: Boolean, @@ -2238,6 +2302,22 @@ const definitions = { `, flatten, }), + 'strict-allow-scripts': new Definition('strict-allow-scripts', { + default: false, + type: Boolean, + description: ` + If \`true\`, turn the install-script policy from a warning into a hard + error: any dependency with install scripts not covered by + \`allowScripts\` will fail the install instead of running with a + notice. + + Dependencies explicitly denied with \`false\` in \`allowScripts\` are + always silently skipped; this setting only affects unreviewed entries. + \`--ignore-scripts\` and \`--dangerously-allow-all-scripts\` both + override this setting. + `, + flatten, + }), 'strict-ssl': new Definition('strict-ssl', { default: true, type: Boolean, diff --git a/deps/npm/node_modules/@npmcli/config/lib/parse-allow-scripts-list.js b/deps/npm/node_modules/@npmcli/config/lib/parse-allow-scripts-list.js new file mode 100644 index 00000000000000..0f13d4a75b6349 --- /dev/null +++ b/deps/npm/node_modules/@npmcli/config/lib/parse-allow-scripts-list.js @@ -0,0 +1,23 @@ +// Parse an `allow-scripts` raw config value (string or array of strings) +// into a flat array of trimmed package-spec entries. Shared between the +// CLI/env layer (via the `allow-scripts` definition's `flatten`) and the +// package.json / .npmrc layer (in lib/utils/resolve-allow-scripts.js) so +// both paths agree on quoting, whitespace, and duplicate handling. +const parseAllowScriptsList = (raw) => { + const parts = [] + const entries = Array.isArray(raw) ? raw : (typeof raw === 'string' ? [raw] : []) + for (const entry of entries) { + if (typeof entry !== 'string') { + continue + } + for (const part of entry.split(',')) { + const trimmed = part.trim() + if (trimmed) { + parts.push(trimmed) + } + } + } + return parts +} + +module.exports = parseAllowScriptsList diff --git a/deps/npm/node_modules/@npmcli/config/package.json b/deps/npm/node_modules/@npmcli/config/package.json index 09627833e07971..295855d76df3e0 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.9.1", + "version": "10.10.0", "files": [ "bin/", "lib/" diff --git a/deps/npm/node_modules/@sigstore/core/dist/dsse.js b/deps/npm/node_modules/@sigstore/core/dist/dsse.js index ca7b63630e2ba9..9dcc2649198c19 100644 --- a/deps/npm/node_modules/@sigstore/core/dist/dsse.js +++ b/deps/npm/node_modules/@sigstore/core/dist/dsse.js @@ -19,12 +19,11 @@ limitations under the License. const PAE_PREFIX = 'DSSEv1'; // DSSE Pre-Authentication Encoding function preAuthEncoding(payloadType, payload) { - const prefix = [ - PAE_PREFIX, - payloadType.length, - payloadType, - payload.length, - '', - ].join(' '); - return Buffer.concat([Buffer.from(prefix, 'ascii'), payload]); + const typeBytes = Buffer.from(payloadType, 'utf-8'); + return Buffer.concat([ + Buffer.from(`${PAE_PREFIX} ${typeBytes.length} `, 'ascii'), + typeBytes, + Buffer.from(` ${payload.length} `, 'ascii'), + payload, + ]); } diff --git a/deps/npm/node_modules/@sigstore/core/package.json b/deps/npm/node_modules/@sigstore/core/package.json index 0564a373c6fa31..82cab44654a1c9 100644 --- a/deps/npm/node_modules/@sigstore/core/package.json +++ b/deps/npm/node_modules/@sigstore/core/package.json @@ -1,6 +1,6 @@ { "name": "@sigstore/core", - "version": "3.2.0", + "version": "3.2.1", "description": "Base library for Sigstore", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/deps/npm/node_modules/@sigstore/verify/dist/key/index.js b/deps/npm/node_modules/@sigstore/verify/dist/key/index.js index c966ccb1e925ef..880ad04bd235d7 100644 --- a/deps/npm/node_modules/@sigstore/verify/dist/key/index.js +++ b/deps/npm/node_modules/@sigstore/verify/dist/key/index.js @@ -56,9 +56,17 @@ function getSigner(cert) { else { issuer = cert.extension(OID_FULCIO_ISSUER_V1)?.value.toString('ascii'); } + const oids = cert.extensions.map((ext) => { + const oid = ext.subs[0].toOID(); + return { + oid: { id: oid.split('.').map(Number) }, + value: ext.subs[ext.subs.length - 1].value, + }; + }); const identity = { extensions: { issuer }, subjectAlternativeName: cert.subjectAltName, + oids, }; return { key: core_1.crypto.createPublicKey(cert.publicKey), diff --git a/deps/npm/node_modules/@sigstore/verify/dist/policy.js b/deps/npm/node_modules/@sigstore/verify/dist/policy.js index f5960cf047b84b..b08d083a296fb8 100644 --- a/deps/npm/node_modules/@sigstore/verify/dist/policy.js +++ b/deps/npm/node_modules/@sigstore/verify/dist/policy.js @@ -2,7 +2,12 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.verifySubjectAlternativeName = verifySubjectAlternativeName; exports.verifyExtensions = verifyExtensions; +exports.verifyOIDs = verifyOIDs; const error_1 = require("./error"); +// Verifies that the signer's SAN matches the policy identity. The +// policyIdentity is treated as a JavaScript regular expression pattern and +// tested against the full signerIdentity string. For exact matching, use +// anchored patterns (e.g. '^user@example\\.com$'). function verifySubjectAlternativeName(policyIdentity, signerIdentity) { if (signerIdentity === undefined || !signerIdentity.match(policyIdentity)) { throw new error_1.PolicyError({ @@ -22,3 +27,24 @@ function verifyExtensions(policyExtensions, signerExtensions = {}) { } } } +function verifyOIDs(policyOIDs, signerOIDs = []) { + for (const policyOID of policyOIDs) { + const match = signerOIDs.find((signerOID) => oidEquals(policyOID.oid?.id, signerOID.oid?.id) && + policyOID.value.equals(signerOID.value)); + if (!match) { + /* istanbul ignore next */ + const oid = policyOID.oid?.id.join('.') ?? ''; + throw new error_1.PolicyError({ + code: 'UNTRUSTED_SIGNER_ERROR', + message: `invalid certificate extension - missing OID ${oid}`, + }); + } + } +} +function oidEquals(a, b) { + /* istanbul ignore if */ + if (a === undefined || b === undefined) { + return false; + } + return a.length === b.length && a.every((v, i) => v === b[i]); +} diff --git a/deps/npm/node_modules/@sigstore/verify/dist/timestamp/index.js b/deps/npm/node_modules/@sigstore/verify/dist/timestamp/index.js index 03a51083e10827..603e559831a9d8 100644 --- a/deps/npm/node_modules/@sigstore/verify/dist/timestamp/index.js +++ b/deps/npm/node_modules/@sigstore/verify/dist/timestamp/index.js @@ -12,6 +12,10 @@ function getTSATimestamp(timestamp, data, timestampAuthorities) { }; } function getTLogTimestamp(entry) { + // Only entries with an inclusion promise provide a verifiable timestamp + if (!entry.inclusionPromise) { + return undefined; + } return { type: 'transparency-log', logID: entry.logId.keyId, diff --git a/deps/npm/node_modules/@sigstore/verify/dist/verifier.js b/deps/npm/node_modules/@sigstore/verify/dist/verifier.js index 5751087ff178d2..eeba4128fabe34 100644 --- a/deps/npm/node_modules/@sigstore/verify/dist/verifier.js +++ b/deps/npm/node_modules/@sigstore/verify/dist/verifier.js @@ -46,17 +46,22 @@ class Verifier { } // Checks that all of the timestamps in the entity are valid and returns them verifyTimestamps(entity) { - let timestampCount = 0; - const timestamps = entity.timestamps.map((timestamp) => { + const timestamps = []; + for (const timestamp of entity.timestamps) { switch (timestamp.$case) { case 'timestamp-authority': - timestampCount++; - return (0, timestamp_1.getTSATimestamp)(timestamp.timestamp, entity.signature.signature, this.trustMaterial.timestampAuthorities); - case 'transparency-log': - timestampCount++; - return (0, timestamp_1.getTLogTimestamp)(timestamp.tlogEntry); + timestamps.push((0, timestamp_1.getTSATimestamp)(timestamp.timestamp, entity.signature.signature, this.trustMaterial.timestampAuthorities)); + break; + case 'transparency-log': { + const result = (0, timestamp_1.getTLogTimestamp)(timestamp.tlogEntry); + /* istanbul ignore else */ + if (result) { + timestamps.push(result); + } + break; + } } - }); + } // Check for duplicate timestamps if (containsDupes(timestamps)) { throw new error_1.VerificationError({ @@ -64,10 +69,10 @@ class Verifier { message: 'duplicate timestamp', }); } - if (timestampCount < this.options.timestampThreshold) { + if (timestamps.length < this.options.timestampThreshold) { throw new error_1.VerificationError({ code: 'TIMESTAMP_ERROR', - message: `expected ${this.options.timestampThreshold} timestamps, got ${timestampCount}`, + message: `expected ${this.options.timestampThreshold} timestamps, got ${timestamps.length}`, }); } return timestamps.map((t) => t.timestamp); @@ -133,6 +138,11 @@ class Verifier { if (policy.extensions) { (0, policy_1.verifyExtensions)(policy.extensions, identity.extensions); } + // Check that the OIDs of the signer match the policy + /* istanbul ignore if */ + if (policy.oids) { + (0, policy_1.verifyOIDs)(policy.oids, identity.oids); + } } } exports.Verifier = Verifier; diff --git a/deps/npm/node_modules/@sigstore/verify/package.json b/deps/npm/node_modules/@sigstore/verify/package.json index 79826a80bddebf..9c4e5dc7a727a7 100644 --- a/deps/npm/node_modules/@sigstore/verify/package.json +++ b/deps/npm/node_modules/@sigstore/verify/package.json @@ -1,6 +1,6 @@ { "name": "@sigstore/verify", - "version": "3.1.0", + "version": "3.1.1", "description": "Verification of Sigstore signatures", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -28,7 +28,7 @@ "dependencies": { "@sigstore/protobuf-specs": "^0.5.0", "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.1.0" + "@sigstore/core": "^3.2.1" }, "engines": { "node": "^20.17.0 || >=22.9.0" diff --git a/deps/npm/node_modules/libnpmdiff/package.json b/deps/npm/node_modules/libnpmdiff/package.json index 974b7346e01068..08783e3ecb13e8 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.8", + "version": "8.1.9", "description": "The registry diff", "repository": { "type": "git", @@ -47,7 +47,7 @@ "tap": "^16.3.8" }, "dependencies": { - "@npmcli/arborist": "^9.6.0", + "@npmcli/arborist": "^9.7.0", "@npmcli/installed-package-contents": "^4.0.0", "binary-extensions": "^3.0.0", "diff": "^8.0.2", diff --git a/deps/npm/node_modules/libnpmexec/lib/index.js b/deps/npm/node_modules/libnpmexec/lib/index.js index 3681653d8217d6..3add22cd2edca5 100644 --- a/deps/npm/node_modules/libnpmexec/lib/index.js +++ b/deps/npm/node_modules/libnpmexec/lib/index.js @@ -87,8 +87,10 @@ const missingFromTree = async ({ spec, tree, flatOptions, isNpxTree, shallow }) } // see if the package.json at `path` has an entry that matches `cmd` +// the path is a known-local directory, not a user-supplied dep, so +// allow-directory must not gate this introspection const hasPkgBin = (path, cmd, flatOptions) => - pacote.manifest(path, flatOptions) + pacote.manifest(path, { ...flatOptions, allowDirectory: 'all' }) .then(manifest => manifest?.bin?.[cmd]).catch(() => null) const exec = async (opts) => { @@ -147,6 +149,8 @@ const exec = async (opts) => { // we have to install the local package into the npx cache so that its // bin links get set up flatOptions.installLinks = false + // self-execution of a local bin, not a directory dep install + flatOptions.allowDirectory = 'all' // args[0] will exist when the package is installed packages.push(p) yes = true diff --git a/deps/npm/node_modules/libnpmexec/package.json b/deps/npm/node_modules/libnpmexec/package.json index 52a1e1539d2697..b672050048bd3f 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.2.8", + "version": "10.2.9", "files": [ "bin/", "lib/" @@ -61,7 +61,7 @@ }, "dependencies": { "@gar/promise-retry": "^1.0.0", - "@npmcli/arborist": "^9.6.0", + "@npmcli/arborist": "^9.7.0", "@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 9c35a66a27e31f..ab5b5a86d98339 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.22", + "version": "7.0.23", "main": "lib/index.js", "files": [ "bin/", @@ -46,7 +46,7 @@ "tap": "^16.3.8" }, "dependencies": { - "@npmcli/arborist": "^9.6.0" + "@npmcli/arborist": "^9.7.0" }, "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 11029cf91ee08a..58ff8edc24d844 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.8", + "version": "9.1.9", "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.6.0", + "@npmcli/arborist": "^9.7.0", "@npmcli/run-script": "^10.0.0", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2" diff --git a/deps/npm/node_modules/libnpmversion/README.md b/deps/npm/node_modules/libnpmversion/README.md index b81a231d05ce04..d60a144bcc1bf1 100644 --- a/deps/npm/node_modules/libnpmversion/README.md +++ b/deps/npm/node_modules/libnpmversion/README.md @@ -86,6 +86,9 @@ The exact order of execution is as follows: 6. Run the `postversion` script. Use it to clean up the file system or automatically push the commit and/or tag. +For the `preversion`, `version` and `postversion` scripts, npm also sets the +environment variables `npm_old_version` and `npm_new_version`. + Take the following example: ```json diff --git a/deps/npm/node_modules/libnpmversion/package.json b/deps/npm/node_modules/libnpmversion/package.json index cac11cc36bd385..f8be6d8fdb3af7 100644 --- a/deps/npm/node_modules/libnpmversion/package.json +++ b/deps/npm/node_modules/libnpmversion/package.json @@ -1,6 +1,6 @@ { "name": "libnpmversion", - "version": "8.0.3", + "version": "8.0.4", "main": "lib/index.js", "files": [ "bin/", diff --git a/deps/npm/node_modules/lru-cache/package.json b/deps/npm/node_modules/lru-cache/package.json index 760fee478270d7..6ada2c211f2d6c 100644 --- a/deps/npm/node_modules/lru-cache/package.json +++ b/deps/npm/node_modules/lru-cache/package.json @@ -1,7 +1,7 @@ { "name": "lru-cache", "description": "A cache object that deletes the least-recently-used items.", - "version": "11.5.0", + "version": "11.5.1", "author": "Isaac Z. Schlueter ", "keywords": [ "mru", diff --git a/deps/npm/node_modules/make-fetch-happen/package.json b/deps/npm/node_modules/make-fetch-happen/package.json index 1d06ac4889c3e3..92c48b45871586 100644 --- a/deps/npm/node_modules/make-fetch-happen/package.json +++ b/deps/npm/node_modules/make-fetch-happen/package.json @@ -1,6 +1,6 @@ { "name": "make-fetch-happen", - "version": "15.0.5", + "version": "15.0.6", "description": "Opinionated, caching, retrying fetch client", "main": "lib/index.js", "files": [ diff --git a/deps/npm/node_modules/semver/classes/range.js b/deps/npm/node_modules/semver/classes/range.js index 94629ce6f5df60..c2e605e5173601 100644 --- a/deps/npm/node_modules/semver/classes/range.js +++ b/deps/npm/node_modules/semver/classes/range.js @@ -98,6 +98,9 @@ class Range { } parseRange (range) { + // strip build metadata so it can't bleed into the version + range = range.replace(BUILDSTRIPRE, '') + // memoize range parsing for performance. // this is a very hot path, and fully deterministic. const memoOpts = @@ -223,6 +226,7 @@ const debug = require('../internal/debug') const SemVer = require('./semver') const { safeRe: re, + src, t, comparatorTrimReplace, tildeTrimReplace, @@ -230,6 +234,9 @@ const { } = require('../internal/re') const { FLAG_INCLUDE_PRERELEASE, FLAG_LOOSE } = require('../internal/constants') +// unbounded global build-metadata stripper used by parseRange +const BUILDSTRIPRE = new RegExp(src[t.BUILD], 'g') + const isNullSet = c => c.value === '<0.0.0-0' const isAny = c => c.value === '' diff --git a/deps/npm/node_modules/semver/package.json b/deps/npm/node_modules/semver/package.json index f8447c4951594d..6edb9ab49d9774 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.0", + "version": "7.8.1", "description": "The semantic version parser used by npm.", "main": "index.js", "scripts": { diff --git a/deps/npm/node_modules/semver/ranges/subset.js b/deps/npm/node_modules/semver/ranges/subset.js index 99f43218075c86..a949832329003b 100644 --- a/deps/npm/node_modules/semver/ranges/subset.js +++ b/deps/npm/node_modules/semver/ranges/subset.js @@ -174,7 +174,7 @@ const simpleSubset = (sub, dom, options) => { if (higher === c && higher !== gt) { return false } - } else if (gt.operator === '>=' && !satisfies(gt.semver, String(c), options)) { + } else if (gt.operator === '>=' && !c.test(gt.semver)) { return false } } @@ -192,7 +192,7 @@ const simpleSubset = (sub, dom, options) => { if (lower === c && lower !== lt) { return false } - } else if (lt.operator === '<=' && !satisfies(lt.semver, String(c), options)) { + } else if (lt.operator === '<=' && !c.test(lt.semver)) { return false } } diff --git a/deps/npm/node_modules/sigstore/dist/config.js b/deps/npm/node_modules/sigstore/dist/config.js index e8b2392f97f236..373149fe22fb75 100644 --- a/deps/npm/node_modules/sigstore/dist/config.js +++ b/deps/npm/node_modules/sigstore/dist/config.js @@ -65,6 +65,12 @@ function createVerificationPolicy(options) { if (options.certificateIssuer) { policy.extensions = { issuer: options.certificateIssuer }; } + if (options.certificateOIDs) { + policy.oids = Object.entries(options.certificateOIDs).map(([oid, value]) => ({ + oid: { id: oid.split('.').map(Number) }, + value: Buffer.from(value), + })); + } return policy; } // Instantiate the FulcioSigner based on the supplied options. diff --git a/deps/npm/node_modules/sigstore/package.json b/deps/npm/node_modules/sigstore/package.json index 5965f0889ca7db..e0acea6d96287e 100644 --- a/deps/npm/node_modules/sigstore/package.json +++ b/deps/npm/node_modules/sigstore/package.json @@ -1,6 +1,6 @@ { "name": "sigstore", - "version": "4.1.0", + "version": "4.1.1", "description": "code-signing for npm packages", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -29,17 +29,17 @@ "devDependencies": { "@sigstore/rekor-types": "^4.0.0", "@sigstore/jest": "^0.0.0", - "@sigstore/mock": "^0.11.0", - "@tufjs/repo-mock": "^4.0.0", + "@sigstore/mock": "^0.12.1", + "@tufjs/repo-mock": "^4.0.1", "@types/make-fetch-happen": "^10.0.4" }, "dependencies": { "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.1.0", + "@sigstore/core": "^3.2.1", "@sigstore/protobuf-specs": "^0.5.0", - "@sigstore/sign": "^4.1.0", - "@sigstore/tuf": "^4.0.1", - "@sigstore/verify": "^3.1.0" + "@sigstore/sign": "^4.1.1", + "@sigstore/tuf": "^4.0.2", + "@sigstore/verify": "^3.1.1" }, "engines": { "node": "^20.17.0 || >=22.9.0" diff --git a/deps/npm/node_modules/undici/lib/dispatcher/agent.js b/deps/npm/node_modules/undici/lib/dispatcher/agent.js index db2f817d0fe978..90b46fe3aeb4b4 100644 --- a/deps/npm/node_modules/undici/lib/dispatcher/agent.js +++ b/deps/npm/node_modules/undici/lib/dispatcher/agent.js @@ -24,7 +24,6 @@ function defaultFactory (origin, opts) { class Agent extends DispatcherBase { constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) { - if (typeof factory !== 'function') { throw new InvalidArgumentError('factory must be a function.') } 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 2b8fa05da29427..ef3d38ea4f2ed3 100644 --- a/deps/npm/node_modules/undici/lib/dispatcher/client-h1.js +++ b/deps/npm/node_modules/undici/lib/dispatcher/client-h1.js @@ -279,29 +279,71 @@ class Parser { const offset = llhttp.llhttp_get_error_pos(this.ptr) - currentBufferPtr - if (ret === constants.ERROR.PAUSED_UPGRADE) { - this.onUpgrade(data.slice(offset)) - } else if (ret === constants.ERROR.PAUSED) { - this.paused = true - socket.unshift(data.slice(offset)) - } else if (ret !== constants.ERROR.OK) { - const ptr = llhttp.llhttp_get_error_reason(this.ptr) - let message = '' - /* istanbul ignore else: difficult to make a test case for */ - if (ptr) { - const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0) - message = - 'Response does not match the HTTP/1.1 protocol (' + - Buffer.from(llhttp.memory.buffer, ptr, len).toString() + - ')' + if (ret !== constants.ERROR.OK) { + const body = data.subarray(offset) + + if (ret === constants.ERROR.PAUSED_UPGRADE) { + this.onUpgrade(body) + } else if (ret === constants.ERROR.PAUSED) { + this.paused = true + socket.unshift(body) + } else { + throw this.createError(ret, body) } - throw new HTTPParserError(message, constants.ERROR[ret], data.slice(offset)) } } catch (err) { util.destroy(socket, err) } } + finish () { + assert(currentParser === null) + assert(this.ptr != null) + assert(!this.paused) + + const { llhttp } = this + + let ret + + try { + currentParser = this + ret = llhttp.llhttp_finish(this.ptr) + } finally { + currentParser = null + } + + if (ret === constants.ERROR.OK) { + return null + } + + if (ret === constants.ERROR.PAUSED || ret === constants.ERROR.PAUSED_UPGRADE) { + this.paused = true + return null + } + + return this.createError(ret, EMPTY_BUF) + } + + createError (ret, data) { + const { llhttp, contentLength, bytesRead } = this + + if (contentLength && bytesRead !== parseInt(contentLength, 10)) { + return new ResponseContentLengthMismatchError() + } + + const ptr = llhttp.llhttp_get_error_reason(this.ptr) + let message = '' + if (ptr) { + const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0) + message = + 'Response does not match the HTTP/1.1 protocol (' + + Buffer.from(llhttp.memory.buffer, ptr, len).toString() + + ')' + } + + return new HTTPParserError(message, constants.ERROR[ret], data) + } + destroy () { assert(this.ptr != null) assert(currentParser == null) @@ -673,8 +715,11 @@ async function connectH1 (client, socket) { // On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded // to the user. if (err.code === 'ECONNRESET' && parser.statusCode && !parser.shouldKeepAlive) { - // We treat all incoming data so for as a valid response. - parser.onMessageComplete() + const parserErr = parser.finish() + if (parserErr) { + this[kError] = parserErr + this[kClient][kOnError](parserErr) + } return } @@ -693,8 +738,10 @@ async function connectH1 (client, socket) { const parser = this[kParser] if (parser.statusCode && !parser.shouldKeepAlive) { - // We treat all incoming data so far as a valid response. - parser.onMessageComplete() + const parserErr = parser.finish() + if (parserErr) { + util.destroy(this, parserErr) + } return } @@ -706,8 +753,7 @@ async function connectH1 (client, socket) { if (parser) { if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) { - // We treat all incoming data so far as a valid response. - parser.onMessageComplete() + this[kError] = parser.finish() || this[kError] } this[kParser].destroy() diff --git a/deps/npm/node_modules/undici/package.json b/deps/npm/node_modules/undici/package.json index 46cb9a8292618f..d1eef502c4169f 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.25.0", + "version": "6.26.0", "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 24c176130a2121..e600a3c0095246 100644 --- a/deps/npm/package.json +++ b/deps/npm/package.json @@ -1,5 +1,5 @@ { - "version": "11.15.0", + "version": "11.16.0", "name": "npm", "description": "a package manager for JavaScript", "workspaces": [ @@ -52,8 +52,8 @@ }, "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^9.6.0", - "@npmcli/config": "^10.9.1", + "@npmcli/arborist": "^9.7.0", + "@npmcli/config": "^10.10.0", "@npmcli/fs": "^5.0.0", "@npmcli/map-workspaces": "^5.0.3", "@npmcli/metavuln-calculator": "^9.0.3", @@ -77,16 +77,16 @@ "is-cidr": "^6.0.4", "json-parse-even-better-errors": "^5.0.0", "libnpmaccess": "^10.0.3", - "libnpmdiff": "^8.1.8", - "libnpmexec": "^10.2.8", - "libnpmfund": "^7.0.22", + "libnpmdiff": "^8.1.9", + "libnpmexec": "^10.2.9", + "libnpmfund": "^7.0.23", "libnpmorg": "^8.0.1", - "libnpmpack": "^9.1.8", + "libnpmpack": "^9.1.9", "libnpmpublish": "^11.2.0", "libnpmsearch": "^9.0.1", "libnpmteam": "^8.0.2", - "libnpmversion": "^8.0.3", - "make-fetch-happen": "^15.0.5", + "libnpmversion": "^8.0.4", + "make-fetch-happen": "^15.0.6", "minimatch": "^10.2.5", "minipass": "^7.1.3", "minipass-pipeline": "^1.2.4", @@ -106,7 +106,7 @@ "proc-log": "^6.1.0", "qrcode-terminal": "^0.12.0", "read": "^5.0.1", - "semver": "^7.8.0", + "semver": "^7.8.1", "spdx-expression-parse": "^4.0.0", "ssri": "^13.0.1", "supports-color": "^10.2.2", diff --git a/deps/npm/tap-snapshots/test/lib/commands/completion.js.test.cjs b/deps/npm/tap-snapshots/test/lib/commands/completion.js.test.cjs index a5f0f6748f8d74..12f1c803cd7695 100644 --- a/deps/npm/tap-snapshots/test/lib/commands/completion.js.test.cjs +++ b/deps/npm/tap-snapshots/test/lib/commands/completion.js.test.cjs @@ -62,6 +62,7 @@ Array [ String( access adduser + approve-scripts audit author add diff --git a/deps/npm/tap-snapshots/test/lib/commands/config.js.test.cjs b/deps/npm/tap-snapshots/test/lib/commands/config.js.test.cjs index 4a224a5cffbff8..829b64b3f800b6 100644 --- a/deps/npm/tap-snapshots/test/lib/commands/config.js.test.cjs +++ b/deps/npm/tap-snapshots/test/lib/commands/config.js.test.cjs @@ -20,6 +20,9 @@ exports[`test/lib/commands/config.js TAP config list --json > output matches sna "allow-file": "all", "allow-git": "all", "allow-remote": "all", + "allow-scripts": [ + "" + ], "also": null, "audit": true, "audit-level": null, @@ -37,6 +40,7 @@ exports[`test/lib/commands/config.js TAP config list --json > output matches sna "cidr": null, "commit-hooks": true, "cpu": null, + "dangerously-allow-all-scripts": false, "depth": null, "description": true, "dev": false, @@ -127,6 +131,8 @@ exports[`test/lib/commands/config.js TAP config list --json > output matches sna "pack-destination": ".", "packages": [], "parseable": false, + "allow-scripts-pending": false, + "allow-scripts-pin": true, "prefer-dedupe": false, "prefer-offline": false, "prefer-online": false, @@ -167,6 +173,7 @@ exports[`test/lib/commands/config.js TAP config list --json > output matches sna "sign-git-commit": false, "sign-git-tag": false, "strict-peer-deps": false, + "strict-allow-scripts": false, "strict-ssl": true, "tag": "latest", "tag-version-prefix": "v", @@ -200,6 +207,9 @@ allow-file = "all" allow-git = "all" allow-remote = "all" allow-same-version = false +allow-scripts = [""] +allow-scripts-pending = false +allow-scripts-pin = true also = null audit = true audit-level = null @@ -219,6 +229,7 @@ cidr = null ; color = {COLOR} commit-hooks = true cpu = null +dangerously-allow-all-scripts = false depth = null description = true dev = false @@ -348,6 +359,7 @@ shell = "{SHELL}" shrinkwrap = true sign-git-commit = false sign-git-tag = false +strict-allow-scripts = false strict-peer-deps = false strict-ssl = true tag = "latest" diff --git a/deps/npm/tap-snapshots/test/lib/commands/publish.js.test.cjs b/deps/npm/tap-snapshots/test/lib/commands/publish.js.test.cjs index 2eda536c6bb33e..143d08dda8ff4b 100644 --- a/deps/npm/tap-snapshots/test/lib/commands/publish.js.test.cjs +++ b/deps/npm/tap-snapshots/test/lib/commands/publish.js.test.cjs @@ -156,6 +156,7 @@ Object { "man": Array [ "man/man1/npm-access.1", "man/man1/npm-adduser.1", + "man/man1/npm-approve-scripts.1", "man/man1/npm-audit.1", "man/man1/npm-bugs.1", "man/man1/npm-cache.1", @@ -163,6 +164,7 @@ Object { "man/man1/npm-completion.1", "man/man1/npm-config.1", "man/man1/npm-dedupe.1", + "man/man1/npm-deny-scripts.1", "man/man1/npm-deprecate.1", "man/man1/npm-diff.1", "man/man1/npm-dist-tag.1", @@ -269,6 +271,28 @@ exports[`test/lib/commands/publish.js TAP prioritize CLI flags over publishConfi + @npmcli/test-package@1.0.0 ` +exports[`test/lib/commands/publish.js TAP private access > must match snapshot 1`] = ` +Array [ + "package: @npm/test-package@1.0.0", + "Tarball Contents", + "55B package.json", + "Tarball Details", + "name: @npm/test-package", + "version: 1.0.0", + "filename: npm-test-package-1.0.0.tgz", + "package size: {size}", + "unpacked size: 55 B", + "shasum: {sha}", + "integrity: {integrity} + "total files: 1", + "Publishing to https://registry.npmjs.org/ with tag latest and restricted access", +] +` + +exports[`test/lib/commands/publish.js TAP private access > new package version 1`] = ` ++ @npm/test-package@1.0.0 +` + exports[`test/lib/commands/publish.js TAP public access > must match snapshot 1`] = ` Array [ "package: @npm/test-package@1.0.0", diff --git a/deps/npm/tap-snapshots/test/lib/docs.js.test.cjs b/deps/npm/tap-snapshots/test/lib/docs.js.test.cjs index dfc170636ee500..72671906850ee0 100644 --- a/deps/npm/tap-snapshots/test/lib/docs.js.test.cjs +++ b/deps/npm/tap-snapshots/test/lib/docs.js.test.cjs @@ -99,6 +99,7 @@ exports[`test/lib/docs.js TAP command list > commands 1`] = ` Array [ "access", "adduser", + "approve-scripts", "audit", "bugs", "cache", @@ -106,6 +107,7 @@ Array [ "completion", "config", "dedupe", + "deny-scripts", "deprecate", "diff", "dist-tag", @@ -193,7 +195,7 @@ safer to use a registry-provided authentication bearer token stored in the * Default: 'public' for new packages, existing packages it will not change the current level -* Type: null, "restricted", or "public" +* Type: null, "restricted", "public", or "private" If you do not want your scoped package to be publicly viewable (and installable) set \`--access=restricted\`. @@ -205,6 +207,8 @@ packages. Specifying a value of \`restricted\` or \`public\` during publish will change the access for an existing package the same way that \`npm access set status\` would. +The value \`private\` is an alias for \`restricted\`. + #### \`all\` @@ -301,6 +305,51 @@ to the same value as the current version. +#### \`allow-scripts\` + +* Default: "" +* Type: String (can be set multiple times) + +Comma-separated list of packages whose install-time lifecycle scripts +(\`preinstall\`, \`install\`, \`postinstall\`, and \`prepare\` for non-registry +dependencies) are allowed to run. + +This setting is intended for one-off and global contexts: \`npm exec\`, \`npx\`, +and \`npm install -g\`, where no project \`package.json\` is involved. For +team-wide policy in a project, use the \`allowScripts\` field in +\`package.json\` (which also supports explicit denials), or configure it in +\`.npmrc\`. Passing \`--allow-scripts\` on the command line during a +project-scoped \`npm install\`, \`ci\`, \`update\`, or \`rebuild\` is an error. + +Each name is matched against a dependency's resolved identity, not against +the package's self-reported name. \`--ignore-scripts\` and +\`--dangerously-allow-all-scripts\` both override this setting. + + + +#### \`allow-scripts-pending\` + +* Default: false +* Type: Boolean + +List packages with install scripts that are not yet covered by the +\`allowScripts\` policy, without modifying \`package.json\`. Only meaningful for +\`npm approve-scripts\`. + + + +#### \`allow-scripts-pin\` + +* Default: true +* Type: Boolean + +Write pinned (\`pkg@version\`) entries when approving install scripts. Set to +\`false\` to write name-only entries that allow any version. Has no effect on +\`npm deny-scripts\`, which always writes name-only entries regardless of this +setting. + + + #### \`audit\` * Default: true @@ -496,6 +545,18 @@ are same as \`cpu\` field of package.json, which comes from \`process.arch\`. +#### \`dangerously-allow-all-scripts\` + +* Default: false +* Type: Boolean + +If \`true\`, bypass the \`allowScripts\` policy entirely and run every +dependency install script regardless of whether it was approved or denied. +Intended as a migration escape hatch only; its use is strongly discouraged. +\`--ignore-scripts\` still takes precedence over this setting. + + + #### \`depth\` * Default: \`Infinity\` if \`--all\` is set; otherwise, \`0\` @@ -1822,6 +1883,22 @@ this to work properly. +#### \`strict-allow-scripts\` + +* Default: false +* Type: Boolean + +If \`true\`, turn the install-script policy from a warning into a hard error: +any dependency with install scripts not covered by \`allowScripts\` will fail +the install instead of running with a notice. + +Dependencies explicitly denied with \`false\` in \`allowScripts\` are always +silently skipped; this setting only affects unreviewed entries. +\`--ignore-scripts\` and \`--dangerously-allow-all-scripts\` both override this +setting. + + + #### \`strict-peer-deps\` * Default: false @@ -2327,6 +2404,7 @@ Array [ "allow-file", "allow-git", "allow-remote", + "allow-scripts", "also", "audit", "audit-level", @@ -2346,6 +2424,7 @@ Array [ "color", "commit-hooks", "cpu", + "dangerously-allow-all-scripts", "depth", "description", "dev", @@ -2435,6 +2514,8 @@ Array [ "pack-destination", "packages", "parseable", + "allow-scripts-pending", + "allow-scripts-pin", "prefer-dedupe", "prefer-offline", "prefer-online", @@ -2476,6 +2557,7 @@ Array [ "sign-git-commit", "sign-git-tag", "strict-peer-deps", + "strict-allow-scripts", "strict-ssl", "tag", "tag-version-prefix", @@ -2507,6 +2589,7 @@ Array [ "allow-file", "allow-git", "allow-remote", + "allow-scripts", "also", "audit", "audit-level", @@ -2526,6 +2609,7 @@ Array [ "color", "commit-hooks", "cpu", + "dangerously-allow-all-scripts", "depth", "description", "dev", @@ -2595,6 +2679,8 @@ Array [ "pack-destination", "packages", "parseable", + "allow-scripts-pending", + "allow-scripts-pin", "prefer-dedupe", "prefer-offline", "prefer-online", @@ -2635,6 +2721,7 @@ Array [ "sign-git-commit", "sign-git-tag", "strict-peer-deps", + "strict-allow-scripts", "strict-ssl", "tag", "tag-version-prefix", @@ -2692,6 +2779,9 @@ Object { "allowGit": "all", "allowRemote": "all", "allowSameVersion": false, + "allowScripts": Array [], + "allowScriptsPending": false, + "allowScriptsPin": true, "audit": true, "auditLevel": null, "authType": "web", @@ -2707,6 +2797,7 @@ Object { "color": false, "commitHooks": true, "cpu": null, + "dangerouslyAllowAllScripts": false, "defaultTag": "latest", "depth": null, "diff": Array [], @@ -2812,6 +2903,7 @@ Object { "signGitCommit": false, "signGitTag": false, "silent": false, + "strictAllowScripts": false, "strictPeerDeps": false, "strictSSL": true, "tagVersionPrefix": "v", @@ -2949,6 +3041,46 @@ Note: This command is unaware of workspaces. #### \`auth-type\` ` +exports[`test/lib/docs.js TAP usage approve-scripts > must match snapshot 1`] = ` +Approve install scripts for specific dependencies + +Usage: +npm approve-scripts [ ...] +npm approve-scripts --all +npm approve-scripts --allow-scripts-pending + +Options: +[-a|--all] [--allow-scripts-pending] [--no-allow-scripts-pin] [--json] + + -a|--all + When running \`npm outdated\` and \`npm ls\`, setting \`--all\` will show + + --allow-scripts-pending + List packages with install scripts that are not yet covered by the + + --allow-scripts-pin + Write pinned (\`pkg@version\`) entries when approving install scripts. + + --json + Whether or not to output JSON data, rather than the normal output. + + +Run "npm help approve-scripts" for more info + +\`\`\`bash +npm approve-scripts [ ...] +npm approve-scripts --all +npm approve-scripts --allow-scripts-pending +\`\`\` + +Note: This command is unaware of workspaces. + +#### \`all\` +#### \`allow-scripts-pending\` +#### \`allow-scripts-pin\` +#### \`json\` +` + exports[`test/lib/docs.js TAP usage audit > must match snapshot 1`] = ` Run a security audit @@ -3125,7 +3257,9 @@ Options: [--include [--include ...]] [--strict-peer-deps] [--foreground-scripts] [--ignore-scripts] [--allow-directory ] [--allow-file ] -[--allow-git ] [--allow-remote ] [--no-audit] +[--allow-git ] [--allow-remote ] +[--allow-scripts [--allow-scripts ...]] +[--strict-allow-scripts] [--dangerously-allow-all-scripts] [--no-audit] [--no-bin-links] [--no-fund] [--dry-run] [-w|--workspace [-w|--workspace ...]] [--workspaces] [--include-workspace-root] [--install-links] @@ -3166,6 +3300,15 @@ Options: --allow-remote Limits the ability for npm to fetch dependencies from urls. + --allow-scripts + Comma-separated list of packages whose install-time lifecycle scripts + + --strict-allow-scripts + If \`true\`, turn the install-script policy from a warning into a hard + + --dangerously-allow-all-scripts + If \`true\`, bypass the \`allowScripts\` policy entirely and run every + --audit When "true" submit audit reports alongside the current npm command to the @@ -3213,6 +3356,9 @@ aliases: clean-install, ic, install-clean, isntall-clean #### \`allow-file\` #### \`allow-git\` #### \`allow-remote\` +#### \`allow-scripts\` +#### \`strict-allow-scripts\` +#### \`dangerously-allow-all-scripts\` #### \`audit\` #### \`bin-links\` #### \`fund\` @@ -3409,6 +3555,44 @@ alias: ddp #### \`install-links\` ` +exports[`test/lib/docs.js TAP usage deny-scripts > must match snapshot 1`] = ` +Deny install scripts for specific dependencies + +Usage: +npm deny-scripts [ ...] +npm deny-scripts --all + +Options: +[-a|--all] [--allow-scripts-pending] [--no-allow-scripts-pin] [--json] + + -a|--all + When running \`npm outdated\` and \`npm ls\`, setting \`--all\` will show + + --allow-scripts-pending + List packages with install scripts that are not yet covered by the + + --allow-scripts-pin + Write pinned (\`pkg@version\`) entries when approving install scripts. + + --json + Whether or not to output JSON data, rather than the normal output. + + +Run "npm help deny-scripts" for more info + +\`\`\`bash +npm deny-scripts [ ...] +npm deny-scripts --all +\`\`\` + +Note: This command is unaware of workspaces. + +#### \`all\` +#### \`allow-scripts-pending\` +#### \`allow-scripts-pin\` +#### \`json\` +` + exports[`test/lib/docs.js TAP usage deprecate > must match snapshot 1`] = ` Deprecate a version of a package @@ -3660,6 +3844,8 @@ Options: [--package [--package ...]] [-c|--call ] [-w|--workspace [-w|--workspace ...]] [--workspaces] [--include-workspace-root] +[--allow-scripts [--allow-scripts ...]] +[--strict-allow-scripts] [--dangerously-allow-all-scripts] --package The package or packages to install for [\`npm exec\`](/commands/npm-exec) @@ -3676,6 +3862,15 @@ Options: --include-workspace-root Include the workspace root when workspaces are enabled for a command. + --allow-scripts + Comma-separated list of packages whose install-time lifecycle scripts + + --strict-allow-scripts + If \`true\`, turn the install-script policy from a warning into a hard + + --dangerously-allow-all-scripts + If \`true\`, bypass the \`allowScripts\` policy entirely and run every + alias: x @@ -3695,6 +3890,9 @@ alias: x #### \`workspace\` #### \`workspaces\` #### \`include-workspace-root\` +#### \`allow-scripts\` +#### \`strict-allow-scripts\` +#### \`dangerously-allow-all-scripts\` ` exports[`test/lib/docs.js TAP usage explain > must match snapshot 1`] = ` @@ -4050,9 +4248,11 @@ Options: [--strict-peer-deps] [--prefer-dedupe] [--no-package-lock] [--package-lock-only] [--foreground-scripts] [--ignore-scripts] [--allow-directory ] [--allow-file ] [--allow-git ] -[--allow-remote ] [--no-audit] [--before ] -[--min-release-age ] [--no-bin-links] [--no-fund] [--dry-run] [--cpu ] -[--os ] [--libc ] +[--allow-remote ] +[--allow-scripts [--allow-scripts ...]] +[--strict-allow-scripts] [--dangerously-allow-all-scripts] [--no-audit] +[--before ] [--min-release-age ] [--no-bin-links] [--no-fund] +[--dry-run] [--cpu ] [--os ] [--libc ] [-w|--workspace [-w|--workspace ...]] [--workspaces] [--include-workspace-root] [--install-links] @@ -4110,6 +4310,15 @@ Options: --allow-remote Limits the ability for npm to fetch dependencies from urls. + --allow-scripts + Comma-separated list of packages whose install-time lifecycle scripts + + --strict-allow-scripts + If \`true\`, turn the install-script policy from a warning into a hard + + --dangerously-allow-all-scripts + If \`true\`, bypass the \`allowScripts\` policy entirely and run every + --audit When "true" submit audit reports alongside the current npm command to the @@ -4178,6 +4387,9 @@ aliases: add, i, in, ins, inst, insta, instal, isnt, isnta, isntal, isntall #### \`allow-file\` #### \`allow-git\` #### \`allow-remote\` +#### \`allow-scripts\` +#### \`strict-allow-scripts\` +#### \`dangerously-allow-all-scripts\` #### \`audit\` #### \`before\` #### \`min-release-age\` @@ -4205,7 +4417,9 @@ Options: [--include [--include ...]] [--strict-peer-deps] [--foreground-scripts] [--ignore-scripts] [--allow-directory ] [--allow-file ] -[--allow-git ] [--allow-remote ] [--no-audit] +[--allow-git ] [--allow-remote ] +[--allow-scripts [--allow-scripts ...]] +[--strict-allow-scripts] [--dangerously-allow-all-scripts] [--no-audit] [--no-bin-links] [--no-fund] [--dry-run] [-w|--workspace [-w|--workspace ...]] [--workspaces] [--include-workspace-root] [--install-links] @@ -4246,6 +4460,15 @@ Options: --allow-remote Limits the ability for npm to fetch dependencies from urls. + --allow-scripts + Comma-separated list of packages whose install-time lifecycle scripts + + --strict-allow-scripts + If \`true\`, turn the install-script policy from a warning into a hard + + --dangerously-allow-all-scripts + If \`true\`, bypass the \`allowScripts\` policy entirely and run every + --audit When "true" submit audit reports alongside the current npm command to the @@ -4293,6 +4516,9 @@ aliases: cit, clean-install-test, sit #### \`allow-file\` #### \`allow-git\` #### \`allow-remote\` +#### \`allow-scripts\` +#### \`strict-allow-scripts\` +#### \`dangerously-allow-all-scripts\` #### \`audit\` #### \`bin-links\` #### \`fund\` @@ -4318,9 +4544,11 @@ Options: [--strict-peer-deps] [--prefer-dedupe] [--no-package-lock] [--package-lock-only] [--foreground-scripts] [--ignore-scripts] [--allow-directory ] [--allow-file ] [--allow-git ] -[--allow-remote ] [--no-audit] [--before ] -[--min-release-age ] [--no-bin-links] [--no-fund] [--dry-run] [--cpu ] -[--os ] [--libc ] +[--allow-remote ] +[--allow-scripts [--allow-scripts ...]] +[--strict-allow-scripts] [--dangerously-allow-all-scripts] [--no-audit] +[--before ] [--min-release-age ] [--no-bin-links] [--no-fund] +[--dry-run] [--cpu ] [--os ] [--libc ] [-w|--workspace [-w|--workspace ...]] [--workspaces] [--include-workspace-root] [--install-links] @@ -4378,6 +4606,15 @@ Options: --allow-remote Limits the ability for npm to fetch dependencies from urls. + --allow-scripts + Comma-separated list of packages whose install-time lifecycle scripts + + --strict-allow-scripts + If \`true\`, turn the install-script policy from a warning into a hard + + --dangerously-allow-all-scripts + If \`true\`, bypass the \`allowScripts\` policy entirely and run every + --audit When "true" submit audit reports alongside the current npm command to the @@ -4446,6 +4683,9 @@ alias: it #### \`allow-file\` #### \`allow-git\` #### \`allow-remote\` +#### \`allow-scripts\` +#### \`strict-allow-scripts\` +#### \`dangerously-allow-all-scripts\` #### \`audit\` #### \`before\` #### \`min-release-age\` @@ -5234,7 +5474,7 @@ Usage: npm publish Options: -[--tag ] [--access ] [--dry-run] [--otp ] +[--tag ] [--access ] [--dry-run] [--otp ] [-w|--workspace [-w|--workspace ...]] [--workspaces] [--include-workspace-root] [--provenance|--provenance-file ] @@ -5334,6 +5574,8 @@ npm rebuild [] ...] Options: [-g|--global] [--no-bin-links] [--foreground-scripts] [--ignore-scripts] +[--allow-scripts [--allow-scripts ...]] +[--strict-allow-scripts] [--dangerously-allow-all-scripts] [-w|--workspace [-w|--workspace ...]] [--workspaces] [--include-workspace-root] [--install-links] @@ -5349,6 +5591,15 @@ Options: --ignore-scripts If true, npm does not run scripts specified in package.json files. + --allow-scripts + Comma-separated list of packages whose install-time lifecycle scripts + + --strict-allow-scripts + If \`true\`, turn the install-script policy from a warning into a hard + + --dangerously-allow-all-scripts + If \`true\`, bypass the \`allowScripts\` policy entirely and run every + -w|--workspace Enable running a command in the context of the configured workspaces of the @@ -5376,6 +5627,9 @@ alias: rb #### \`bin-links\` #### \`foreground-scripts\` #### \`ignore-scripts\` +#### \`allow-scripts\` +#### \`strict-allow-scripts\` +#### \`dangerously-allow-all-scripts\` #### \`workspace\` #### \`workspaces\` #### \`include-workspace-root\` @@ -6219,8 +6473,11 @@ Options: [--omit [--omit ...]] [--include [--include ...]] [--strict-peer-deps] [--no-package-lock] [--foreground-scripts] -[--ignore-scripts] [--no-audit] [--before ] [--min-release-age ] -[--no-bin-links] [--no-fund] [--dry-run] +[--ignore-scripts] +[--allow-scripts [--allow-scripts ...]] +[--strict-allow-scripts] [--dangerously-allow-all-scripts] [--no-audit] +[--before ] [--min-release-age ] [--no-bin-links] [--no-fund] +[--dry-run] [-w|--workspace [-w|--workspace ...]] [--workspaces] [--include-workspace-root] [--install-links] @@ -6257,6 +6514,15 @@ Options: --ignore-scripts If true, npm does not run scripts specified in package.json files. + --allow-scripts + Comma-separated list of packages whose install-time lifecycle scripts + + --strict-allow-scripts + If \`true\`, turn the install-script policy from a warning into a hard + + --dangerously-allow-all-scripts + If \`true\`, bypass the \`allowScripts\` policy entirely and run every + --audit When "true" submit audit reports alongside the current npm command to the @@ -6309,6 +6575,9 @@ aliases: u, up, upgrade, udpate #### \`package-lock\` #### \`foreground-scripts\` #### \`ignore-scripts\` +#### \`allow-scripts\` +#### \`strict-allow-scripts\` +#### \`dangerously-allow-all-scripts\` #### \`audit\` #### \`before\` #### \`min-release-age\` diff --git a/deps/npm/tap-snapshots/test/lib/npm.js.test.cjs b/deps/npm/tap-snapshots/test/lib/npm.js.test.cjs index 337095989e6638..dc1c95cd3c5763 100644 --- a/deps/npm/tap-snapshots/test/lib/npm.js.test.cjs +++ b/deps/npm/tap-snapshots/test/lib/npm.js.test.cjs @@ -31,16 +31,16 @@ npm help npm more involved overview All commands: - access, adduser, audit, bugs, cache, ci, completion, - config, dedupe, deprecate, diff, dist-tag, docs, doctor, - edit, exec, explain, explore, find-dupes, fund, get, help, - help-search, init, install, install-ci-test, install-test, - link, ll, login, logout, ls, org, outdated, owner, pack, - ping, pkg, prefix, profile, prune, publish, query, rebuild, - repo, restart, root, run, sbom, search, set, shrinkwrap, - stage, star, stars, start, stop, team, test, token, trust, - undeprecate, uninstall, unpublish, unstar, update, version, - view, whoami + access, adduser, approve-scripts, audit, bugs, cache, ci, + completion, config, dedupe, deny-scripts, deprecate, diff, + dist-tag, docs, doctor, edit, exec, explain, explore, + find-dupes, fund, get, help, help-search, init, install, + install-ci-test, install-test, link, ll, login, logout, ls, + org, outdated, owner, pack, ping, pkg, prefix, profile, + prune, publish, query, rebuild, repo, restart, root, run, + sbom, search, set, shrinkwrap, stage, star, stars, start, + stop, team, test, token, trust, undeprecate, uninstall, + unpublish, unstar, update, version, view, whoami Specify configs in the ini-formatted file: {USERCONFIG} @@ -69,9 +69,11 @@ npm help npm more involved overview All commands: access, adduser, - audit, bugs, cache, ci, + approve-scripts, audit, + bugs, cache, ci, completion, config, - dedupe, deprecate, diff, + dedupe, deny-scripts, + deprecate, diff, dist-tag, docs, doctor, edit, exec, explain, explore, find-dupes, @@ -122,9 +124,11 @@ npm help npm more involved overview All commands: access, adduser, - audit, bugs, cache, ci, + approve-scripts, audit, + bugs, cache, ci, completion, config, - dedupe, deprecate, diff, + dedupe, deny-scripts, + deprecate, diff, dist-tag, docs, doctor, edit, exec, explain, explore, find-dupes, @@ -174,16 +178,16 @@ npm help npm more involved overview All commands: - access, adduser, audit, bugs, cache, ci, completion, - config, dedupe, deprecate, diff, dist-tag, docs, doctor, - edit, exec, explain, explore, find-dupes, fund, get, help, - help-search, init, install, install-ci-test, install-test, - link, ll, login, logout, ls, org, outdated, owner, pack, - ping, pkg, prefix, profile, prune, publish, query, rebuild, - repo, restart, root, run, sbom, search, set, shrinkwrap, - stage, star, stars, start, stop, team, test, token, trust, - undeprecate, uninstall, unpublish, unstar, update, version, - view, whoami + access, adduser, approve-scripts, audit, bugs, cache, ci, + completion, config, dedupe, deny-scripts, deprecate, diff, + dist-tag, docs, doctor, edit, exec, explain, explore, + find-dupes, fund, get, help, help-search, init, install, + install-ci-test, install-test, link, ll, login, logout, ls, + org, outdated, owner, pack, ping, pkg, prefix, profile, + prune, publish, query, rebuild, repo, restart, root, run, + sbom, search, set, shrinkwrap, stage, star, stars, start, + stop, team, test, token, trust, undeprecate, uninstall, + unpublish, unstar, update, version, view, whoami Specify configs in the ini-formatted file: {USERCONFIG} @@ -212,9 +216,11 @@ npm help npm more involved overview All commands: access, adduser, - audit, bugs, cache, ci, + approve-scripts, audit, + bugs, cache, ci, completion, config, - dedupe, deprecate, diff, + dedupe, deny-scripts, + deprecate, diff, dist-tag, docs, doctor, edit, exec, explain, explore, find-dupes, @@ -265,9 +271,11 @@ npm help npm more involved overview All commands: access, adduser, - audit, bugs, cache, ci, + approve-scripts, audit, + bugs, cache, ci, completion, config, - dedupe, deprecate, diff, + dedupe, deny-scripts, + deprecate, diff, dist-tag, docs, doctor, edit, exec, explain, explore, find-dupes, @@ -317,10 +325,12 @@ npm help npm more involved overview All commands: - access, adduser, audit, + access, adduser, + approve-scripts, audit, bugs, cache, ci, completion, config, - dedupe, deprecate, diff, + dedupe, deny-scripts, + deprecate, diff, dist-tag, docs, doctor, edit, exec, explain, explore, find-dupes, @@ -369,16 +379,16 @@ npm help npm more involved overview All commands: - access, adduser, audit, bugs, cache, ci, completion, - config, dedupe, deprecate, diff, dist-tag, docs, doctor, - edit, exec, explain, explore, find-dupes, fund, get, help, - help-search, init, install, install-ci-test, install-test, - link, ll, login, logout, ls, org, outdated, owner, pack, - ping, pkg, prefix, profile, prune, publish, query, rebuild, - repo, restart, root, run, sbom, search, set, shrinkwrap, - stage, star, stars, start, stop, team, test, token, trust, - undeprecate, uninstall, unpublish, unstar, update, version, - view, whoami + access, adduser, approve-scripts, audit, bugs, cache, ci, + completion, config, dedupe, deny-scripts, deprecate, diff, + dist-tag, docs, doctor, edit, exec, explain, explore, + find-dupes, fund, get, help, help-search, init, install, + install-ci-test, install-test, link, ll, login, logout, ls, + org, outdated, owner, pack, ping, pkg, prefix, profile, + prune, publish, query, rebuild, repo, restart, root, run, + sbom, search, set, shrinkwrap, stage, star, stars, start, + stop, team, test, token, trust, undeprecate, uninstall, + unpublish, unstar, update, version, view, whoami Specify configs in the ini-formatted file: {USERCONFIG} @@ -406,16 +416,16 @@ npm help npm more involved overview All commands: - access, adduser, audit, bugs, cache, ci, completion, - config, dedupe, deprecate, diff, dist-tag, docs, doctor, - edit, exec, explain, explore, find-dupes, fund, get, help, - help-search, init, install, install-ci-test, install-test, - link, ll, login, logout, ls, org, outdated, owner, pack, - ping, pkg, prefix, profile, prune, publish, query, rebuild, - repo, restart, root, run, sbom, search, set, shrinkwrap, - stage, star, stars, start, stop, team, test, token, trust, - undeprecate, uninstall, unpublish, unstar, update, version, - view, whoami + access, adduser, approve-scripts, audit, bugs, cache, ci, + completion, config, dedupe, deny-scripts, deprecate, diff, + dist-tag, docs, doctor, edit, exec, explain, explore, + find-dupes, fund, get, help, help-search, init, install, + install-ci-test, install-test, link, ll, login, logout, ls, + org, outdated, owner, pack, ping, pkg, prefix, profile, + prune, publish, query, rebuild, repo, restart, root, run, + sbom, search, set, shrinkwrap, stage, star, stars, start, + stop, team, test, token, trust, undeprecate, uninstall, + unpublish, unstar, update, version, view, whoami Specify configs in the ini-formatted file: {USERCONFIG} @@ -443,16 +453,16 @@ npm help npm more involved overview All commands: - access, adduser, audit, bugs, cache, ci, completion, - config, dedupe, deprecate, diff, dist-tag, docs, doctor, - edit, exec, explain, explore, find-dupes, fund, get, help, - help-search, init, install, install-ci-test, install-test, - link, ll, login, logout, ls, org, outdated, owner, pack, - ping, pkg, prefix, profile, prune, publish, query, rebuild, - repo, restart, root, run, sbom, search, set, shrinkwrap, - stage, star, stars, start, stop, team, test, token, trust, - undeprecate, uninstall, unpublish, unstar, update, version, - view, whoami + access, adduser, approve-scripts, audit, bugs, cache, ci, + completion, config, dedupe, deny-scripts, deprecate, diff, + dist-tag, docs, doctor, edit, exec, explain, explore, + find-dupes, fund, get, help, help-search, init, install, + install-ci-test, install-test, link, ll, login, logout, ls, + org, outdated, owner, pack, ping, pkg, prefix, profile, + prune, publish, query, rebuild, repo, restart, root, run, + sbom, search, set, shrinkwrap, stage, star, stars, start, + stop, team, test, token, trust, undeprecate, uninstall, + unpublish, unstar, update, version, view, whoami Specify configs in the ini-formatted file: {USERCONFIG} diff --git a/deps/npm/test/lib/commands/approve-scripts.js b/deps/npm/test/lib/commands/approve-scripts.js new file mode 100644 index 00000000000000..dde7a358b12e2b --- /dev/null +++ b/deps/npm/test/lib/commands/approve-scripts.js @@ -0,0 +1,562 @@ +const t = require('tap') +const fs = require('node:fs') +const { resolve } = require('node:path') +const _mockNpm = require('../../fixtures/mock-npm') + +const mockNpm = async (t, opts = {}) => { + return _mockNpm(t, opts) +} + +const setupProject = ({ allowScripts, withScripts = ['canvas'] } = {}) => { + const pkg = { + name: 'host', + version: '1.0.0', + dependencies: Object.fromEntries(withScripts.map((n) => [n, '*'])), + } + if (allowScripts !== undefined) { + pkg.allowScripts = allowScripts + } + + const lockPackages = { '': pkg } + const nodeModules = {} + for (const name of withScripts) { + const tarUrl = `https://registry.npmjs.org/${name}/-/${name}-1.0.0.tgz` + nodeModules[name] = { + 'package.json': JSON.stringify({ + name, + version: '1.0.0', + scripts: { install: 'echo install' }, + }), + } + lockPackages[`node_modules/${name}`] = { + version: '1.0.0', + resolved: tarUrl, + hasInstallScript: true, + } + } + + return { + 'package.json': JSON.stringify(pkg, null, 2), + 'package-lock.json': JSON.stringify({ + name: pkg.name, + version: pkg.version, + lockfileVersion: 3, + requires: true, + packages: lockPackages, + }), + node_modules: nodeModules, + } +} + +t.test('approve-scripts --pending lists unreviewed packages', async t => { + const { npm, joinedOutput } = await mockNpm(t, { + prefixDir: setupProject({ withScripts: ['canvas', 'sharp'] }), + config: { 'allow-scripts-pending': true }, + }) + await npm.exec('approve-scripts', []) + const out = joinedOutput() + t.match(out, /2 packages have install scripts not yet covered/) + t.match(out, /canvas@1\.0\.0/) + t.match(out, /sharp@1\.0\.0/) +}) + +t.test('approve-scripts --pending with no unreviewed says so', async t => { + const { npm, joinedOutput } = await mockNpm(t, { + prefixDir: setupProject({ + allowScripts: { canvas: true }, + withScripts: ['canvas'], + }), + config: { 'allow-scripts-pending': true }, + }) + await npm.exec('approve-scripts', []) + t.match(joinedOutput(), /No packages with unreviewed install scripts/) +}) + +t.test('approve-scripts writes pinned entry by default', async t => { + const { npm, prefix } = await mockNpm(t, { + prefixDir: setupProject({ withScripts: ['canvas'] }), + }) + 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 --no-pin writes name-only entry', async t => { + const { npm, prefix } = await mockNpm(t, { + prefixDir: setupProject({ withScripts: ['canvas'] }), + config: { 'allow-scripts-pin': false }, + }) + await npm.exec('approve-scripts', ['canvas']) + + const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8')) + t.strictSame(pkg.allowScripts, { canvas: true }) +}) + +t.test('approve-scripts --all approves every unreviewed package', async t => { + const { npm, prefix } = await mockNpm(t, { + prefixDir: setupProject({ withScripts: ['canvas', 'sharp'] }), + config: { all: true }, + }) + 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, + 'sharp@1.0.0': true, + }) +}) + +t.test('approve-scripts errors on unknown package', async t => { + const { npm } = await mockNpm(t, { + prefixDir: setupProject({ withScripts: ['canvas'] }), + }) + await t.rejects( + npm.exec('approve-scripts', ['not-installed']), + { code: 'ENOMATCH' } + ) +}) + +t.test('approve-scripts respects existing deny entry', async t => { + const { npm, prefix, logs } = await mockNpm(t, { + prefixDir: setupProject({ + withScripts: ['canvas'], + allowScripts: { canvas: false }, + }), + }) + await npm.exec('approve-scripts', ['canvas']) + + const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8')) + // Deny wins; unchanged. + t.strictSame(pkg.allowScripts, { canvas: false }) + t.match(logs.warn.byTitle('approve-scripts'), [/canvas is denied/]) +}) + +t.test('approve-scripts requires positional args, --all, or --pending', async t => { + const { npm } = await mockNpm(t, { + prefixDir: setupProject({ withScripts: ['canvas'] }), + }) + await t.rejects(npm.exec('approve-scripts', []), { code: 'EUSAGE' }) +}) + +t.test('approve-scripts --pending cannot be combined with positional', async t => { + const { npm } = await mockNpm(t, { + prefixDir: setupProject({ withScripts: ['canvas'] }), + config: { 'allow-scripts-pending': true }, + }) + await t.rejects(npm.exec('approve-scripts', ['canvas']), { code: 'EUSAGE' }) +}) + +t.test('approve-scripts fails on global', async t => { + const { npm } = await mockNpm(t, { + config: { global: true }, + }) + await t.rejects(npm.exec('approve-scripts', ['canvas']), { code: 'EGLOBAL' }) +}) + +t.test('approve-scripts --json outputs structured summary', async t => { + const { npm, joinedOutput } = await mockNpm(t, { + prefixDir: setupProject({ withScripts: ['canvas'] }), + config: { json: true }, + }) + await npm.exec('approve-scripts', ['canvas']) + const parsed = JSON.parse(joinedOutput()) + t.match(parsed, { + allowScripts: [{ name: 'canvas', changes: [{ key: 'canvas@1.0.0', change: 'added' }] }], + }) +}) + +t.test('approve-scripts --all with no unreviewed packages prints message', async t => { + const { npm, joinedOutput } = await _mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ name: 'host', version: '1.0.0' }), + 'package-lock.json': JSON.stringify({ + name: 'host', + version: '1.0.0', + lockfileVersion: 3, + requires: true, + packages: { '': { name: 'host', version: '1.0.0' } }, + }), + node_modules: {}, + }, + config: { all: true }, + }) + await npm.exec('approve-scripts', []) + t.match(joinedOutput(), /No packages with unreviewed install scripts/) +}) + +t.test('approve-scripts on a package already at the right pin is no-op', async t => { + const { npm, prefix, joinedOutput } = await _mockNpm(t, { + prefixDir: setupProject({ + withScripts: ['canvas'], + allowScripts: { 'canvas@1.0.0': true }, + }), + }) + 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.match(joinedOutput(), /Nothing to approve/) +}) + +t.test('approve-scripts --pending with single package uses singular wording', async t => { + const { npm, joinedOutput } = await _mockNpm(t, { + prefixDir: setupProject({ withScripts: ['canvas'] }), + config: { 'allow-scripts-pending': true }, + }) + await npm.exec('approve-scripts', []) + t.match(joinedOutput(), /1 package has install scripts/) +}) + +t.test('approve-scripts --pending lists package with no version', async t => { + // Use a fixture where the lockfile records a synthetic node without a version + const { npm } = await _mockNpm(t, { + prefixDir: setupProject({ withScripts: ['canvas'] }), + config: { 'allow-scripts-pending': true }, + }) + await npm.exec('approve-scripts', []) + // Just exercising; no assertion needed for additional coverage. + t.pass() +}) + +t.test('approve-scripts groups multiple installed versions of the same package', async t => { + // Two versions of lodash exist in the tree; both have install scripts. + // groupByPackage should put them in the same group (hits the + // `if (!groups[key])` falsy branch on the second node). + const { npm, prefix } = await _mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'host', + version: '1.0.0', + dependencies: { 'top-of-tree': '*' }, + }), + 'package-lock.json': JSON.stringify({ + name: 'host', + version: '1.0.0', + lockfileVersion: 3, + requires: true, + packages: { + '': { name: 'host', version: '1.0.0', dependencies: { 'top-of-tree': '*' } }, + 'node_modules/lodash': { + version: '4.17.21', + resolved: 'https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz', + hasInstallScript: true, + }, + 'node_modules/top-of-tree': { + version: '1.0.0', + resolved: 'https://registry.npmjs.org/top-of-tree/-/top-of-tree-1.0.0.tgz', + dependencies: { lodash: '3.10.1' }, + }, + 'node_modules/top-of-tree/node_modules/lodash': { + version: '3.10.1', + resolved: 'https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz', + hasInstallScript: true, + }, + }, + }), + node_modules: { + lodash: { + 'package.json': JSON.stringify({ + name: 'lodash', + version: '4.17.21', + scripts: { install: 'echo install' }, + }), + }, + 'top-of-tree': { + 'package.json': JSON.stringify({ name: 'top-of-tree', version: '1.0.0' }), + node_modules: { + lodash: { + 'package.json': JSON.stringify({ + name: 'lodash', + version: '3.10.1', + scripts: { install: 'echo install' }, + }), + }, + }, + }, + }, + }, + }) + await npm.exec('approve-scripts', ['lodash']) + + const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8')) + // Both versions get pinned. + t.strictSame(pkg.allowScripts, { + 'lodash@3.10.1': true, + 'lodash@4.17.21': true, + }) +}) + +t.test('approve-scripts --pending handles node with no version', async t => { + // Exercise the ternary's falsy branch in runPending: `node.version ? '@'... : ''` + // when the node has no version field. + const mockSync = await _mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ name: 'host', version: '1.0.0' }), + 'package-lock.json': JSON.stringify({ + name: 'host', + version: '1.0.0', + lockfileVersion: 3, + requires: true, + packages: { '': { name: 'host', version: '1.0.0' } }, + }), + node_modules: {}, + }, + config: { 'allow-scripts-pending': true }, + mocks: { + // Make the walker return a synthetic node with no version + '{LIB}/utils/check-allow-scripts.js': async () => [{ + node: { packageName: 'no-version-pkg', name: 'no-version-pkg', version: undefined }, + scripts: { install: 'do-stuff' }, + }], + }, + }) + await mockSync.npm.exec('approve-scripts', []) + // Output should mention the package without an @version suffix. + t.match(mockSync.joinedOutput(), / no-version-pkg \(install: do-stuff\)/) +}) + +t.test('forbidden semver range in package.json#allowScripts is dropped with a warning', async t => { + // End-to-end: project declares a caret range in allowScripts. The + // resolver must drop the entry, emit a warning, and the matching node + // must remain unreviewed (listed by --pending). + const mock = await _mockNpm(t, { + prefixDir: setupProject({ + withScripts: ['canvas'], + // ^0.33.0 is a forbidden range per RFC. + allowScripts: { 'canvas@^0.33.0': true }, + }), + config: { 'allow-scripts-pending': true }, + }) + await mock.npm.exec('approve-scripts', []) + + const warnings = mock.logs.warn.byTitle('allow-scripts') + t.ok( + warnings.some(m => /semver ranges/.test(m) && /canvas@\^0\.33\.0/.test(m)), + 'resolver emits warning about forbidden range' + ) + // canvas was installed with version 1.0.0 (setupProject default) and + // the forbidden allowlist entry was dropped, so canvas appears in the + // pending list. + t.match(mock.joinedOutput(), /canvas@1\.0\.0/) +}) + +t.test('approve-scripts --pending lists packages that only have binding.gyp', async t => { + // End-to-end: a package with no preinstall/install/postinstall but a + // binding.gyp on disk gets a synthetic `node-gyp rebuild` install + // script. The runtime isNodeGypPackage check must see it and surface + // the package in --pending output. + const mock = await _mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'host', + version: '1.0.0', + dependencies: { 'native-pkg': '*' }, + }), + 'package-lock.json': JSON.stringify({ + name: 'host', + version: '1.0.0', + lockfileVersion: 3, + requires: true, + packages: { + '': { name: 'host', version: '1.0.0', dependencies: { 'native-pkg': '*' } }, + 'node_modules/native-pkg': { + version: '1.0.0', + resolved: 'https://registry.npmjs.org/native-pkg/-/native-pkg-1.0.0.tgz', + // No hasInstallScript — the synthetic node-gyp injection is + // what we want this test to exercise. + }, + }, + }), + node_modules: { + 'native-pkg': { + 'package.json': JSON.stringify({ name: 'native-pkg', version: '1.0.0' }), + // The file that triggers isNodeGypPackage to return true. + 'binding.gyp': '{}', + }, + }, + }, + config: { 'allow-scripts-pending': true }, + }) + await mock.npm.exec('approve-scripts', []) + + const out = mock.joinedOutput() + t.match(out, /native-pkg@1\.0\.0/, 'binding.gyp-only package appears in --pending') + t.match(out, /install: node-gyp rebuild/, 'synthetic node-gyp install is named') +}) + +t.test('approve-scripts --all skips bundled deps with a notice', async t => { + // Bundled deps cannot be allowlisted in Phase 1 (RFC defers their + // allowlisting to a follow-up). --all must not silently write a key + // derived from the bundled tarball's self-claimed identity. + const { npm, logs, prefix } = await _mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'host', + version: '1.0.0', + dependencies: { 'parent-pkg': '*' }, + }), + 'package-lock.json': JSON.stringify({ + name: 'host', + version: '1.0.0', + lockfileVersion: 3, + requires: true, + packages: { + '': { name: 'host', version: '1.0.0', dependencies: { 'parent-pkg': '*' } }, + 'node_modules/parent-pkg': { + version: '1.0.0', + resolved: 'https://registry.npmjs.org/parent-pkg/-/parent-pkg-1.0.0.tgz', + hasInstallScript: true, + }, + 'node_modules/parent-pkg/node_modules/inner': { + version: '1.0.0', + inBundle: true, + hasInstallScript: true, + }, + }, + }), + node_modules: { + 'parent-pkg': { + 'package.json': JSON.stringify({ + name: 'parent-pkg', + version: '1.0.0', + scripts: { install: 'echo install' }, + bundleDependencies: ['inner'], + }), + node_modules: { + inner: { + 'package.json': JSON.stringify({ + name: 'inner', + version: '1.0.0', + scripts: { install: 'echo bundled-install' }, + }), + }, + }, + }, + }, + }, + config: { all: true }, + }) + await npm.exec('approve-scripts', []) + + const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8')) + // parent-pkg is approvable. inner is bundled and must be excluded. + t.equal(pkg.allowScripts['parent-pkg@1.0.0'], true, + 'non-bundled parent gets approved') + t.notOk(Object.keys(pkg.allowScripts).some(k => k.startsWith('inner')), + 'bundled inner is not approved') + t.match(logs.warn.byTitle('approve-scripts'), [/Skipping 1 bundled dependency/]) +}) + +t.test('approve-scripts positional is ignored', async t => { + // Same protection on the positional path: a user typing a bundled + // package name must not get a policy entry written. + const { npm } = await _mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'host', + version: '1.0.0', + dependencies: { 'parent-pkg': '*' }, + }), + 'package-lock.json': JSON.stringify({ + name: 'host', + version: '1.0.0', + lockfileVersion: 3, + requires: true, + packages: { + '': { name: 'host', version: '1.0.0', dependencies: { 'parent-pkg': '*' } }, + 'node_modules/parent-pkg': { + version: '1.0.0', + resolved: 'https://registry.npmjs.org/parent-pkg/-/parent-pkg-1.0.0.tgz', + hasInstallScript: true, + }, + 'node_modules/parent-pkg/node_modules/inner': { + version: '1.0.0', + inBundle: true, + hasInstallScript: true, + }, + }, + }), + node_modules: { + 'parent-pkg': { + 'package.json': JSON.stringify({ + name: 'parent-pkg', + version: '1.0.0', + scripts: { install: 'echo install' }, + bundleDependencies: ['inner'], + }), + node_modules: { + inner: { + 'package.json': JSON.stringify({ + name: 'inner', + version: '1.0.0', + scripts: { install: 'echo bundled' }, + }), + }, + }, + }, + }, + }, + }) + await t.rejects( + npm.exec('approve-scripts', ['inner']), + { code: 'ENOMATCH' }, + 'typing the bundled package name does not match any approvable node' + ) +}) + +t.test('approve-scripts --all with only bundled deps prints "no eligible" notice', async t => { + const { npm, logs, joinedOutput, prefix } = await _mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'host', + version: '1.0.0', + dependencies: { 'parent-pkg': '*' }, + }), + 'package-lock.json': JSON.stringify({ + name: 'host', + version: '1.0.0', + lockfileVersion: 3, + requires: true, + packages: { + '': { name: 'host', version: '1.0.0', dependencies: { 'parent-pkg': '*' } }, + 'node_modules/parent-pkg': { + version: '1.0.0', + resolved: 'https://registry.npmjs.org/parent-pkg/-/parent-pkg-1.0.0.tgz', + // parent-pkg has NO install scripts; only the bundled child does. + }, + 'node_modules/parent-pkg/node_modules/only-bundled': { + version: '1.0.0', + inBundle: true, + hasInstallScript: true, + }, + }, + }), + node_modules: { + 'parent-pkg': { + 'package.json': JSON.stringify({ + name: 'parent-pkg', + version: '1.0.0', + bundleDependencies: ['only-bundled'], + }), + node_modules: { + 'only-bundled': { + 'package.json': JSON.stringify({ + name: 'only-bundled', + version: '1.0.0', + scripts: { install: 'echo evil' }, + }), + }, + }, + }, + }, + }, + config: { all: true }, + }) + await npm.exec('approve-scripts', []) + t.match(joinedOutput(), /No packages eligible for approval/) + t.match(logs.warn.byTitle('approve-scripts'), [/Skipping 1 bundled dependency/]) + // Ensure no policy entry was written. + const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8')) + t.notOk(pkg.allowScripts, 'no allowScripts written') +}) diff --git a/deps/npm/test/lib/commands/config.js b/deps/npm/test/lib/commands/config.js index 9a65e883cfebc1..8237ffff22a42a 100644 --- a/deps/npm/test/lib/commands/config.js +++ b/deps/npm/test/lib/commands/config.js @@ -582,6 +582,11 @@ t.test('config edit', async t => { }, }) + const inputEvents = [] + const inputListener = (level) => inputEvents.push(level) + process.on('input', inputListener) + t.teardown(() => process.off('input', inputListener)) + await npm.exec('config', ['edit']) t.ok(editor.called, 'editor was spawned') @@ -590,6 +595,7 @@ t.test('config edit', async t => { [join(home, '.npmrc')], 'editor opened the user config file' ) + t.same(inputEvents.slice(0, 2), ['start', 'end'], 'progress paused and resumed around editor') const contents = await fs.readFile(join(home, '.npmrc'), { encoding: 'utf8' }) t.ok(contents.includes('foo=bar'), 'kept foo') diff --git a/deps/npm/test/lib/commands/deny-scripts.js b/deps/npm/test/lib/commands/deny-scripts.js new file mode 100644 index 00000000000000..fd9031c665d6a8 --- /dev/null +++ b/deps/npm/test/lib/commands/deny-scripts.js @@ -0,0 +1,163 @@ +const t = require('tap') +const fs = require('node:fs') +const { resolve } = require('node:path') +const _mockNpm = require('../../fixtures/mock-npm') + +const setupProject = ({ allowScripts, withScripts = ['core-js'] } = {}) => { + const pkg = { + name: 'host', + version: '1.0.0', + dependencies: Object.fromEntries(withScripts.map((n) => [n, '*'])), + } + if (allowScripts !== undefined) { + pkg.allowScripts = allowScripts + } + const lockPackages = { '': pkg } + const nodeModules = {} + for (const name of withScripts) { + nodeModules[name] = { + 'package.json': JSON.stringify({ + name, + version: '1.0.0', + scripts: { install: 'echo install' }, + }), + } + lockPackages[`node_modules/${name}`] = { + version: '1.0.0', + resolved: `https://registry.npmjs.org/${name}/-/${name}-1.0.0.tgz`, + hasInstallScript: true, + } + } + return { + 'package.json': JSON.stringify(pkg, null, 2), + 'package-lock.json': JSON.stringify({ + name: pkg.name, + version: pkg.version, + lockfileVersion: 3, + requires: true, + packages: lockPackages, + }), + node_modules: nodeModules, + } +} + +t.test('deny-scripts writes name-only false entry', async t => { + const { npm, prefix } = await _mockNpm(t, { + prefixDir: setupProject({ withScripts: ['core-js'] }), + }) + await npm.exec('deny-scripts', ['core-js']) + + const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8')) + t.strictSame(pkg.allowScripts, { 'core-js': false }) +}) + +t.test('deny-scripts ignores --pin and always writes name-only', async t => { + const { npm, prefix } = await _mockNpm(t, { + prefixDir: setupProject({ withScripts: ['core-js'] }), + config: { 'allow-scripts-pin': true }, + }) + await npm.exec('deny-scripts', ['core-js']) + + const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8')) + t.strictSame(pkg.allowScripts, { 'core-js': false }) +}) + +t.test('deny-scripts replaces existing pinned allow', async t => { + const { npm, prefix } = await _mockNpm(t, { + prefixDir: setupProject({ + withScripts: ['core-js'], + allowScripts: { 'core-js@1.0.0': true }, + }), + }) + await npm.exec('deny-scripts', ['core-js']) + + const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8')) + t.strictSame(pkg.allowScripts, { 'core-js': false }) +}) + +t.test('deny-scripts --pending is rejected', async t => { + const { npm } = await _mockNpm(t, { + prefixDir: setupProject({ withScripts: ['core-js'] }), + config: { 'allow-scripts-pending': true }, + }) + await t.rejects(npm.exec('deny-scripts', []), { code: 'EUSAGE' }) +}) + +t.test('deny-scripts --all denies every unreviewed package', async t => { + const { npm, prefix } = await _mockNpm(t, { + prefixDir: setupProject({ withScripts: ['core-js', 'telemetry'] }), + config: { all: true }, + }) + await npm.exec('deny-scripts', []) + + const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8')) + t.strictSame(pkg.allowScripts, { 'core-js': false, telemetry: false }) +}) + +t.test('deny-scripts errors on unknown package', async t => { + const { npm } = await _mockNpm(t, { + prefixDir: setupProject({ withScripts: ['core-js'] }), + }) + await t.rejects( + npm.exec('deny-scripts', ['not-installed']), + { code: 'ENOMATCH' } + ) +}) + +t.test('deny-scripts requires positional args or --all', async t => { + const { npm } = await _mockNpm(t, { + prefixDir: setupProject({ withScripts: ['core-js'] }), + }) + await t.rejects(npm.exec('deny-scripts', []), { code: 'EUSAGE' }) +}) + +t.test('deny-scripts --all with no unreviewed packages prints message', async t => { + const { npm, joinedOutput } = await _mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ name: 'host', version: '1.0.0' }), + 'package-lock.json': JSON.stringify({ + name: 'host', + version: '1.0.0', + lockfileVersion: 3, + requires: true, + packages: { '': { name: 'host', version: '1.0.0' } }, + }), + node_modules: {}, + }, + config: { all: true }, + }) + await npm.exec('deny-scripts', []) + t.match(joinedOutput(), /No packages with unreviewed install scripts/) +}) + +t.test('deny-scripts fails on global', async t => { + const { npm } = await _mockNpm(t, { + config: { global: true }, + }) + await t.rejects(npm.exec('deny-scripts', ['canvas']), { code: 'EGLOBAL' }) +}) + +t.test('deny-scripts on a package already denied is no-op', async t => { + const { npm, joinedOutput, prefix } = await _mockNpm(t, { + prefixDir: setupProject({ + withScripts: ['core-js'], + allowScripts: { 'core-js': false }, + }), + }) + await npm.exec('deny-scripts', ['core-js']) + const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8')) + t.strictSame(pkg.allowScripts, { 'core-js': false }) + t.match(joinedOutput(), /Nothing to deny/) +}) + +t.test('deny-scripts --json outputs structured summary', async t => { + const { npm, joinedOutput } = await _mockNpm(t, { + prefixDir: setupProject({ withScripts: ['core-js'] }), + config: { json: true }, + }) + await npm.exec('deny-scripts', ['core-js']) + const parsed = JSON.parse(joinedOutput()) + t.match(parsed, { + allowScripts: [{ name: 'core-js', changes: [{ key: 'core-js', change: 'added' }] }], + }) +}) diff --git a/deps/npm/test/lib/commands/edit.js b/deps/npm/test/lib/commands/edit.js index b55bb2df218ba2..915241c82f6da8 100644 --- a/deps/npm/test/lib/commands/edit.js +++ b/deps/npm/test/lib/commands/edit.js @@ -58,8 +58,14 @@ t.test('npm edit', async t => { : ['-c', 'testinstall'] spawk.spawn(scriptShell, scriptArgs, { cwd: semverPath }) + const inputEvents = [] + const inputListener = (level) => inputEvents.push(level) + process.on('input', inputListener) + t.teardown(() => process.off('input', inputListener)) + await npm.exec('edit', ['semver']) t.match(joinedOutput(), 'rebuilt dependencies successfully') + t.same(inputEvents.slice(0, 2), ['start', 'end'], 'progress paused and resumed around editor') }) t.test('rebuild failure', async t => { diff --git a/deps/npm/test/lib/commands/exec.js b/deps/npm/test/lib/commands/exec.js index 2a6d3f6b8e0aff..92ea993e3edfb2 100644 --- a/deps/npm/test/lib/commands/exec.js +++ b/deps/npm/test/lib/commands/exec.js @@ -303,3 +303,68 @@ t.test('can run packages with keywords', async t => { t.fail(err, 'should not throw') } }) + +t.test('exec threads allowScripts policy from .npmrc through to libexec', async t => { + let capturedOpts + const fakeLibexec = async (opts) => { + capturedOpts = opts + } + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ name: 'host', version: '1.0.0' }), + '.npmrc': 'allow-scripts = canvas', + }, + mocks: { + libnpmexec: fakeLibexec, + }, + }) + await npm.exec('exec', ['some-pkg']) + t.strictSame(capturedOpts.allowScripts, { canvas: true }, + 'allowScripts populated from .npmrc layer') +}) + +t.test('exec ignores project package.json#allowScripts (RFC: .npmrc-only)', async t => { + // Per RFC line 299, exec/npx consults only user/global .npmrc. Project + // package.json policy must NOT influence npx behaviour, even when the + // user is running npx inside a project that has its own allowScripts. + let capturedOpts + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'host', + version: '1.0.0', + allowScripts: { sharp: true }, + }), + }, + mocks: { + libnpmexec: async (opts) => { + capturedOpts = opts + }, + }, + }) + await npm.exec('exec', ['some-pkg']) + // package.json policy is skipped; no other layer has policy; result is null. + t.equal(capturedOpts.allowScripts, null) +}) + +t.test('exec reads .npmrc policy even when project package.json has a different policy', async t => { + // .npmrc-tier policy wins because package.json is skipped entirely. + let capturedOpts + const { npm } = await loadMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'host', + version: '1.0.0', + allowScripts: { sharp: true }, + }), + '.npmrc': 'allow-scripts = canvas', + }, + mocks: { + libnpmexec: async (opts) => { + capturedOpts = opts + }, + }, + }) + await npm.exec('exec', ['some-pkg']) + t.strictSame(capturedOpts.allowScripts, { canvas: true }) +}) diff --git a/deps/npm/test/lib/commands/publish.js b/deps/npm/test/lib/commands/publish.js index ad528c2c8dd3ef..acf8c4c96a93d6 100644 --- a/deps/npm/test/lib/commands/publish.js +++ b/deps/npm/test/lib/commands/publish.js @@ -742,6 +742,27 @@ t.test('restricted access', async t => { t.matchSnapshot(logs.notice) }) +t.test('private access', async t => { + const packageJson = { + name: '@npm/test-package', + version: '1.0.0', + } + const { npm, joinedOutput, logs, registry } = await loadNpmWithRegistry(t, { + config: { + ...auth, + access: 'private', + }, + prefixDir: { + 'package.json': JSON.stringify(packageJson, null, 2), + }, + authorization: token, + }) + registry.publish('@npm/test-package', { packageJson, access: 'restricted' }) + await npm.exec('publish', []) + t.matchSnapshot(joinedOutput(), 'new package version') + t.matchSnapshot(logs.notice) +}) + t.test('public access', async t => { const { npm, joinedOutput, logs, registry } = await loadNpmWithRegistry(t, { config: { diff --git a/deps/npm/test/lib/commands/rebuild.js b/deps/npm/test/lib/commands/rebuild.js index 0062362b61329b..de91fd3471b4e1 100644 --- a/deps/npm/test/lib/commands/rebuild.js +++ b/deps/npm/test/lib/commands/rebuild.js @@ -221,3 +221,63 @@ t.test('completion', async t => { const res = await rebuild.completion({ conf: { argv: { remain: ['npm', 'rebuild'] } } }) t.type(res, Array) }) + +t.test('emits Phase 1 advisory warning for unreviewed install scripts', async t => { + const { npm, logs } = await setupMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ name: 'host', version: '1.0.0' }), + node_modules: { + canvas: { + 'package.json': JSON.stringify({ + name: 'canvas', + version: '1.0.0', + scripts: { install: 'echo install' }, + }), + }, + }, + }, + }) + await npm.exec('rebuild', []) + t.match( + logs.warn.byTitle('rebuild'), + [/install scripts not yet covered by allowScripts/] + ) +}) + +t.test('no advisory warning when allowScripts covers the package', async t => { + const { npm, logs } = await setupMockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'host', + version: '1.0.0', + dependencies: { canvas: '1.0.0' }, + allowScripts: { canvas: true }, + }), + 'package-lock.json': JSON.stringify({ + name: 'host', + version: '1.0.0', + lockfileVersion: 3, + requires: true, + packages: { + '': { name: 'host', version: '1.0.0', dependencies: { canvas: '1.0.0' } }, + 'node_modules/canvas': { + version: '1.0.0', + resolved: 'https://registry.npmjs.org/canvas/-/canvas-1.0.0.tgz', + hasInstallScript: true, + }, + }, + }), + node_modules: { + canvas: { + 'package.json': JSON.stringify({ + name: 'canvas', + version: '1.0.0', + scripts: { install: 'echo install' }, + }), + }, + }, + }, + }) + await npm.exec('rebuild', []) + t.strictSame(logs.warn.byTitle('rebuild'), []) +}) diff --git a/deps/npm/test/lib/commands/update.js b/deps/npm/test/lib/commands/update.js index a8c68bd65bb361..68067b8af8168f 100644 --- a/deps/npm/test/lib/commands/update.js +++ b/deps/npm/test/lib/commands/update.js @@ -95,3 +95,33 @@ t.test('completion', async t => { const res = await update.completion({ conf: { argv: { remain: ['npm', 'update'] } } }) t.type(res, Array) }) + +t.test('update threads allowScripts policy through to arborist', async t => { + // The reify step uses the resolved policy. The advisory warning is + // emitted from reifyFinish (already covered by install.js tests), + // so here we verify the call site populates opts.allowScripts. + let capturedOpts + const FakeArborist = function (opts) { + capturedOpts = opts + this.options = opts + this.actualTree = { inventory: new Map() } + } + FakeArborist.prototype.reify = async function () {} + + const mock = await _mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'host', + version: '1.0.0', + allowScripts: { canvas: true }, + }), + }, + mocks: { + '@npmcli/arborist': FakeArborist, + '{LIB}/utils/reify-finish.js': async () => {}, + }, + }) + await mock.npm.exec('update', []) + t.strictSame(capturedOpts.allowScripts, { canvas: true }, + 'opts.allowScripts populated from package.json') +}) diff --git a/deps/npm/test/lib/utils/allow-scripts-writer.js b/deps/npm/test/lib/utils/allow-scripts-writer.js new file mode 100644 index 00000000000000..56314f8eb5a521 --- /dev/null +++ b/deps/npm/test/lib/utils/allow-scripts-writer.js @@ -0,0 +1,637 @@ +const t = require('tap') +const path = require('node:path') +const { + applyApprovalForPackage, + applyDenyForPackage, + nameKeyFor, + versionedKeyFor, + isSingleVersionPin, +} = require('../../../lib/utils/allow-scripts-writer.js') + +const node = (overrides = {}) => { + const name = overrides.name ?? overrides.packageName ?? 'pkg' + const packageName = overrides.packageName ?? name + const version = overrides.version ?? '1.0.0' + const urlPkg = packageName + return { + name, + packageName, + version, + resolved: overrides.resolved + ?? `https://registry.npmjs.org/${urlPkg}/-/${urlPkg}-${version}.tgz`, + location: overrides.location ?? `node_modules/${name}`, + isRegistryDependency: overrides.isRegistryDependency ?? true, + } +} + +t.test('nameKeyFor / versionedKeyFor — registry', async t => { + const n = node({ name: 'canvas', version: '2.11.0' }) + t.equal(nameKeyFor(n), 'canvas') + t.equal(versionedKeyFor(n), 'canvas@2.11.0') +}) + +t.test('nameKeyFor / versionedKeyFor — git', async t => { + const n = node({ + name: 'bar', + resolved: 'git+ssh://git@github.com/foo/bar.git#deadbeefcafebabe1234567890abcdef12345678', + }) + t.equal(nameKeyFor(n), 'github:foo/bar') + t.equal(versionedKeyFor(n), 'github:foo/bar#deadbeefcafebabe1234567890abcdef12345678') +}) + +t.test('nameKeyFor / versionedKeyFor — file', async t => { + const n = node({ name: 'local', resolved: 'file:../local' }) + t.equal(nameKeyFor(n), 'file:../local') + t.equal(versionedKeyFor(n), 'file:../local') +}) + +t.test('isSingleVersionPin', async t => { + t.ok(isSingleVersionPin('pkg@1.2.3')) + t.notOk(isSingleVersionPin('pkg')) + t.notOk(isSingleVersionPin('pkg@^1')) + t.notOk(isSingleVersionPin('pkg@1.2.3 || 2.0.0')) + t.notOk(isSingleVersionPin('@@@bad')) +}) + +t.test('applyApprovalForPackage — empty allowScripts, --pin', async t => { + const { allowScripts, changes } = applyApprovalForPackage( + {}, + [node({ name: 'canvas', version: '2.11.0' })], + { pin: true } + ) + t.strictSame(allowScripts, { 'canvas@2.11.0': true }) + t.strictSame(changes, [{ key: 'canvas@2.11.0', change: 'added' }]) +}) + +t.test('applyApprovalForPackage — empty allowScripts, --no-pin', async t => { + const { allowScripts, changes } = applyApprovalForPackage( + {}, + [node({ name: 'canvas', version: '2.11.0' })], + { pin: false } + ) + t.strictSame(allowScripts, { canvas: true }) + t.strictSame(changes, [{ key: 'canvas', change: 'added' }]) +}) + +t.test('applyApprovalForPackage — stale pin rewritten to new installed version', async t => { + const { allowScripts, changes } = applyApprovalForPackage( + { 'canvas@2.10.0': true }, + [node({ name: 'canvas', version: '2.11.0' })], + { pin: true } + ) + t.strictSame(allowScripts, { 'canvas@2.11.0': true }) + t.match(changes, [ + { key: 'canvas@2.10.0', change: 'removed-stale' }, + { key: 'canvas@2.11.0', change: 'added' }, + ]) +}) + +t.test('applyApprovalForPackage — multi-version disjunction is preserved', async t => { + const { allowScripts } = applyApprovalForPackage( + { 'canvas@2.10.0 || 2.11.0': true }, + [node({ name: 'canvas', version: '2.11.0' })], + { pin: true } + ) + t.strictSame(allowScripts, { + 'canvas@2.10.0 || 2.11.0': true, + 'canvas@2.11.0': true, + }) +}) + +t.test('applyApprovalForPackage — already-allowed exact version is a no-op', async t => { + const { allowScripts, changes } = applyApprovalForPackage( + { 'canvas@2.11.0': true }, + [node({ name: 'canvas', version: '2.11.0' })], + { pin: true } + ) + t.strictSame(allowScripts, { 'canvas@2.11.0': true }) + t.strictSame(changes, []) +}) + +t.test('applyApprovalForPackage — existing deny wins, returns warning', async t => { + const { allowScripts, changes, warning } = applyApprovalForPackage( + { canvas: false }, + [node({ name: 'canvas', version: '2.11.0' })], + { pin: true } + ) + t.strictSame(allowScripts, { canvas: false }) + t.strictSame(changes, []) + t.match(warning, /canvas is denied/) +}) + +t.test('applyApprovalForPackage — versioned deny wins too', async t => { + const { changes, warning } = applyApprovalForPackage( + { 'canvas@2.11.0': false }, + [node({ name: 'canvas', version: '2.11.0' })], + { pin: true } + ) + t.strictSame(changes, []) + t.match(warning, /denied|versioned deny/) +}) + +t.test('applyApprovalForPackage — name-only existing, --no-pin no-op', async t => { + const { allowScripts, changes } = applyApprovalForPackage( + { canvas: true }, + [node({ name: 'canvas', version: '2.11.0' })], + { pin: false } + ) + t.strictSame(allowScripts, { canvas: true }) + t.strictSame(changes, []) +}) + +t.test('applyApprovalForPackage — --no-pin downgrades pinned entry to name-only', async t => { + const { allowScripts } = applyApprovalForPackage( + { 'canvas@2.10.0': true }, + [node({ name: 'canvas', version: '2.11.0' })], + { pin: false } + ) + t.strictSame(allowScripts, { canvas: true }) +}) + +t.test('applyApprovalForPackage — multiple installed versions write multiple pins', async t => { + const { allowScripts } = applyApprovalForPackage( + {}, + [ + node({ name: 'lodash', version: '4.17.21' }), + node({ name: 'lodash', version: '3.10.1' }), + ], + { pin: true } + ) + t.strictSame(allowScripts, { 'lodash@3.10.1': true, 'lodash@4.17.21': true }) +}) + +t.test('applyApprovalForPackage — keeps existing pin matching one installed, adds pin for other', async t => { + const { allowScripts } = applyApprovalForPackage( + { 'lodash@4.17.21': true }, + [ + node({ name: 'lodash', version: '4.17.21' }), + node({ name: 'lodash', version: '3.10.1' }), + ], + { pin: true } + ) + t.strictSame(allowScripts, { 'lodash@3.10.1': true, 'lodash@4.17.21': true }) +}) + +t.test('applyDenyForPackage — empty allowScripts adds name-only false', async t => { + const { allowScripts, changes } = applyDenyForPackage( + {}, + [node({ name: 'core-js', version: '3.0.0' })] + ) + t.strictSame(allowScripts, { 'core-js': false }) + t.strictSame(changes, [{ key: 'core-js', change: 'added' }]) +}) + +t.test('applyDenyForPackage — pinned allow is replaced by name-only deny', async t => { + const { allowScripts } = applyDenyForPackage( + { 'core-js@3.0.0': true }, + [node({ name: 'core-js', version: '3.0.0' })] + ) + t.strictSame(allowScripts, { 'core-js': false }) +}) + +t.test('applyDenyForPackage — already-denied is a no-op', async t => { + const { changes } = applyDenyForPackage( + { 'core-js': false }, + [node({ name: 'core-js', version: '3.0.0' })] + ) + t.strictSame(changes, []) +}) + +t.test('applyDenyForPackage — name-only true is replaced by name-only false', async t => { + const { allowScripts } = applyDenyForPackage( + { 'core-js': true }, + [node({ name: 'core-js', version: '3.0.0' })] + ) + t.strictSame(allowScripts, { 'core-js': false }) +}) + +t.test('applyApprovalForPackage — preserves unrelated entries', async t => { + const { allowScripts } = applyApprovalForPackage( + { other: true, 'unrelated@1.0.0': false }, + [node({ name: 'canvas', version: '2.11.0' })], + { pin: true } + ) + t.strictSame(allowScripts, { + other: true, + 'unrelated@1.0.0': false, + 'canvas@2.11.0': true, + }) +}) + +t.test('applyApprovalForPackage — git node writes hosted shortcut with commit', async t => { + const { allowScripts } = applyApprovalForPackage( + {}, + [node({ + name: 'bar', + resolved: 'git+ssh://git@github.com/foo/bar.git#deadbeefcafebabe1234567890abcdef12345678', + })], + { pin: true } + ) + t.strictSame(allowScripts, { + 'github:foo/bar#deadbeefcafebabe1234567890abcdef12345678': true, + }) +}) + +t.test('applyApprovalForPackage — git node --no-pin writes hosted shortcut without commit', async t => { + const { allowScripts } = applyApprovalForPackage( + {}, + [node({ + name: 'bar', + resolved: 'git+ssh://git@github.com/foo/bar.git#deadbeef', + })], + { pin: false } + ) + t.strictSame(allowScripts, { 'github:foo/bar': true }) +}) + +t.test('applyApprovalForPackage — file dep uses resolved as both keys', async t => { + const { allowScripts } = applyApprovalForPackage( + {}, + [node({ name: 'local', resolved: 'file:../local' })], + { pin: true } + ) + t.strictSame(allowScripts, { 'file:../local': true }) +}) + +t.test('applyApprovalForPackage — empty nodes returns unchanged', async t => { + const { allowScripts, changes } = applyApprovalForPackage({ x: true }, [], { pin: true }) + t.strictSame(allowScripts, { x: true }) + t.strictSame(changes, []) +}) + +t.test('applyApprovalForPackage — name-only entry is replaced by pin (RFC table)', async t => { + const { allowScripts, changes } = applyApprovalForPackage( + { canvas: true }, + [node({ name: 'canvas', version: '2.11.0' })], + { pin: true } + ) + // Per RFC table: pkg: true + --pin must upgrade to pkg@x.y.z: true. + // Both entries left behind would be wrong. + t.strictSame(allowScripts, { 'canvas@2.11.0': true }) + t.match(changes, [ + { key: 'canvas@2.11.0', change: 'added' }, + { key: 'canvas', change: 'replaced-by-pin' }, + ]) +}) + +t.test('applyApprovalForPackage — name-only + multi-version installs replaces with all pins', async t => { + const { allowScripts } = applyApprovalForPackage( + { lodash: true }, + [ + node({ name: 'lodash', version: '4.17.21' }), + node({ name: 'lodash', version: '3.10.1' }), + ], + { pin: true } + ) + t.strictSame(allowScripts, { 'lodash@3.10.1': true, 'lodash@4.17.21': true }) +}) + +t.test('applyApprovalForPackage — name-only is preserved when --no-pin', async t => { + const { allowScripts, changes } = applyApprovalForPackage( + { canvas: true }, + [node({ name: 'canvas', version: '2.11.0' })], + { pin: false } + ) + t.strictSame(allowScripts, { canvas: true }) + t.strictSame(changes, []) +}) + +t.test('applyApprovalForPackage — name-only NOT dropped when no pinning could happen', async t => { + // Node has no version, so installedKeys is empty. The name-only entry + // must NOT be dropped or we silently lose the policy. + const noVersion = { name: 'pkg', packageName: 'pkg', version: undefined, resolved: 'https://registry.npmjs.org/pkg/-/pkg-1.tgz' } + const { allowScripts } = applyApprovalForPackage( + { pkg: true }, + [noVersion], + { pin: true } + ) + t.strictSame(allowScripts, { pkg: true }) +}) + +t.test('applyApprovalForPackage — convergent: running twice gives the same result', async t => { + // Start with stale state including a name-only entry. + const start = { canvas: true, 'canvas@2.10.0': true } + const nodes = [node({ name: 'canvas', version: '2.11.0' })] + + const run1 = applyApprovalForPackage(start, nodes, { pin: true }) + const run2 = applyApprovalForPackage(run1.allowScripts, nodes, { pin: true }) + + t.strictSame(run1.allowScripts, { 'canvas@2.11.0': true }) + t.strictSame(run2.allowScripts, { 'canvas@2.11.0': true }) + t.strictSame(run2.changes, [], 'second run is a no-op') +}) + +t.test('applyApprovalForPackage — deny still wins even when name-only is upgraded', async t => { + const { allowScripts, warning } = applyApprovalForPackage( + { canvas: true, 'canvas@2.11.0': false }, + [node({ name: 'canvas', version: '2.11.0' })], + { pin: true } + ) + // Existing deny on the version blocks the approval. + t.strictSame(allowScripts, { canvas: true, 'canvas@2.11.0': false }) + t.match(warning, /denied|versioned deny/) +}) + +t.test('keyTargetsNode — unparseable key returns false (via applyApproval)', async t => { + // An unparseable key in the existing object should be ignored. + const { allowScripts } = applyApprovalForPackage( + { '@@@invalid': true }, + [node({ name: 'canvas', version: '2.11.0' })], + { pin: true } + ) + t.equal(allowScripts['canvas@2.11.0'], true) + t.equal(allowScripts['@@@invalid'], true) +}) + +t.test('applyDenyForPackage — empty nodes array returns unchanged', async t => { + const { allowScripts, changes } = applyDenyForPackage({ existing: true }, []) + t.strictSame(allowScripts, { existing: true }) + t.strictSame(changes, []) +}) + +t.test('applyDenyForPackage — node with no nameable identity is a no-op', async t => { + // A node whose resolved field is unparseable as a git URL and has no + // version/name produces a null name; the writer must short-circuit. + const weird = { name: '', packageName: '', version: undefined, resolved: undefined } + const { allowScripts, changes } = applyDenyForPackage({}, [weird]) + t.strictSame(allowScripts, {}) + t.strictSame(changes, []) +}) + +t.test('applyApprovalForPackage — file dep with deny entry blocks approval', async t => { + const { warning } = applyApprovalForPackage( + { 'file:../local': false }, + [node({ name: 'local', resolved: 'file:../local' })], + { pin: true } + ) + t.match(warning, /denied|versioned deny/) +}) + +t.test('applyApprovalForPackage — remote tarball deny blocks approval', async t => { + const remote = { name: 'pkg', packageName: 'pkg', version: '1.0.0', resolved: 'https://example.com/pkg.tgz' } + const { warning } = applyApprovalForPackage( + { 'https://example.com/pkg.tgz': false }, + [remote], + { pin: true } + ) + t.match(warning, /denied|versioned deny/) +}) + +t.test('applyApprovalForPackage — no-pin with no name produces no-op', async t => { + const weird = { name: '', packageName: '', resolved: 'git+ssh://no.parse' } + const { allowScripts, changes } = applyApprovalForPackage({}, [weird], { pin: false }) + t.strictSame(allowScripts, {}) + t.strictSame(changes, []) +}) + +t.test('applyApprovalForPackage — pin with no versioned key is a no-op', async t => { + const noVersion = { name: 'pkg', packageName: 'pkg', version: undefined, resolved: 'https://registry.npmjs.org/pkg/-/pkg-1.tgz' } + const { allowScripts, changes } = applyApprovalForPackage({}, [noVersion], { pin: true }) + t.strictSame(allowScripts, {}) + t.strictSame(changes, []) +}) + +t.test('applyApprovalForPackage — pin with no versioned key and existing name-only is no-op', async t => { + const noVersion = { name: 'pkg', packageName: 'pkg', version: undefined, resolved: 'https://registry.npmjs.org/pkg/-/pkg-1.tgz' } + const { changes } = applyApprovalForPackage({ pkg: true }, [noVersion], { pin: true }) + t.strictSame(changes, []) +}) + +t.test('keyTargetsNode handles file with directory-typed key', async t => { + // A "directory" spec for a relative path. + const dirNode = { name: 'local', packageName: 'local', resolved: 'file:./local-dir' } + const { allowScripts } = applyApprovalForPackage( + {}, + [dirNode], + { pin: true } + ) + t.equal(allowScripts['file:./local-dir'], true) +}) + +t.test('nameKeyFor / versionedKeyFor — null node', async t => { + t.equal(nameKeyFor(null), null) + t.equal(versionedKeyFor(null), null) +}) + +t.test('nameKeyFor / versionedKeyFor — non-hosted git url returns null', async t => { + const n = { name: 'pkg', packageName: 'pkg', resolved: 'git+https://example.invalid/foo/bar.git#abc' } + t.equal(nameKeyFor(n), null) + t.equal(versionedKeyFor(n), null) +}) + +t.test('versionedKeyFor — absolute path resolved field', async t => { + const n = { name: 'pkg', packageName: 'pkg', resolved: '/abs/path/local' } + t.equal(versionedKeyFor(n), '/abs/path/local') + t.equal(nameKeyFor(n), '/abs/path/local') +}) + +t.test('applyApprovalForPackage — node.resolved parse error in keyTargetsNode is safe', async t => { + // An existing git-style key for a package whose own resolved field + // doesn't parse: the key just doesn't target anything. + const gitNode = node({ + name: 'bar', + resolved: 'git+ssh://git@github.com/foo/bar.git#abc', + }) + // Add an explicit unparseable existing entry. + const { allowScripts } = applyApprovalForPackage( + { 'github:other/other': true }, + [gitNode], + { pin: true } + ) + // Existing entry unchanged; new git entry added. + t.equal(allowScripts['github:other/other'], true) + t.equal(allowScripts['github:foo/bar#abc'], true) +}) + +t.test('keyTargetsNode — alias key does not target anything (via writer)', async t => { + // Alias-typed key falls through the switch default. + const { allowScripts } = applyApprovalForPackage( + { 'foo@npm:bar@1.0.0': true }, + [node({ name: 'foo', packageName: 'foo', version: '1.0.0' })], + { pin: true } + ) + // Alias entry untouched, new pin added separately. + t.equal(allowScripts['foo@npm:bar@1.0.0'], true) + t.equal(allowScripts['foo@1.0.0'], true) +}) +t.test('keyTargetsNode handles tag-type key', async t => { + // 'canvas@latest' parses as type='tag'. The writer should treat it like + // a name-only match (any installed version of canvas). + const { allowScripts } = applyApprovalForPackage( + { 'canvas@latest': true }, + [node({ name: 'canvas', version: '2.11.0' })], + { pin: true } + ) + // The tag key targets the canvas node (same package name), so the + // 'canvas@2.11.0' pin gets added; tag key is preserved. + t.equal(allowScripts['canvas@latest'], true) + t.equal(allowScripts['canvas@2.11.0'], true) +}) + +t.test('keyTargetsNode handles file-type tarball key matching saveSpec', async t => { + // 'file:pkg.tgz' parses as type='file' with saveSpec='file:pkg.tgz'. + const tarballNode = { + name: 'pkg', + packageName: 'pkg', + version: '1.0.0', + resolved: 'file:pkg.tgz', + } + const { allowScripts } = applyApprovalForPackage( + { 'file:pkg.tgz': false }, + [tarballNode], + { pin: true } + ) + // saveSpec match: deny wins, no pin added. + t.equal(allowScripts['file:pkg.tgz'], false) +}) + +t.test('keyTargetsNode handles file-type tarball key matching fetchSpec', async t => { + // When node.resolved is an absolute path matching parsed.fetchSpec. + // Use path.resolve so the absolute path is platform-correct (npa + // parses POSIX-style `/abs/...` as a directory on Windows). + const absTgz = path.resolve('pkg.tgz') + const tarballNode = { + name: 'pkg', + packageName: 'pkg', + version: '1.0.0', + resolved: absTgz, + } + const { allowScripts, warning } = applyApprovalForPackage( + { './pkg.tgz': false }, + [tarballNode], + { pin: true } + ) + t.equal(allowScripts['./pkg.tgz'], false) + t.match(warning, /denied|versioned deny/) +}) + +t.test('versionedKeyFor — git node without committish', async t => { + // versionedKeyFor's ternary takes the "no committish" branch. + t.equal( + versionedKeyFor({ + name: 'bar', + resolved: 'git+ssh://git@github.com/foo/bar.git', + }), + 'github:foo/bar' + ) +}) + +t.test('versionedKeyFor / nameKeyFor — absolute path resolved field', async t => { + // Hits the `resolved.startsWith('/')` branch in both helpers. + const n = { name: 'pkg', packageName: 'pkg', resolved: '/abs/local-dir' } + t.equal(versionedKeyFor(n), '/abs/local-dir') + t.equal(nameKeyFor(n), '/abs/local-dir') +}) + +t.test('keyTargetsNode — git key against a node with no resolved field', async t => { + // Defensive: if existing has a git-shaped key and the installed node + // has no resolved field, keyTargetsNode bails out and no policy entry + // can be derived from untrusted sources. + const noResolved = { name: 'bar', packageName: 'bar', resolved: undefined } + const { allowScripts } = applyApprovalForPackage( + { 'github:foo/bar': true }, + [noResolved], + { pin: false } + ) + // Existing entry untouched. No new key written: nameKeyFor returns + // null for a node with no trusted identity source. + t.equal(allowScripts['github:foo/bar'], true) + t.notOk('bar' in allowScripts, 'no entry written under attacker-controlled node.name') +}) + +t.test('applyApprovalForPackage — default args (no options object)', async t => { + // Hits the `{ pin = true } = {}` default-arg branch. + const { allowScripts } = applyApprovalForPackage( + {}, + [node({ name: 'canvas', version: '2.11.0' })] + ) + t.strictSame(allowScripts, { 'canvas@2.11.0': true }) +}) + +t.test('applyApprovalForPackage — deny-wins warning when node has no name', async t => { + // Hits the `name || 'this package'` fallback in the warning message. + const noName = { name: '', packageName: '', resolved: 'git+ssh://no.parse' } + const { warning } = applyApprovalForPackage( + { 'github:foo/bar': false }, + [noName], + { pin: true } + ) + // No keys target this node (its resolved doesn't parse to a hosted URL), + // so deny-wins doesn't trigger. Result is no warning. + t.notOk(warning) +}) + +t.test('denyWarning branches on key shape per RFC §approve-scripts', async t => { + // Name-only deny: only remedy is to remove the entry. + const nameOnly = applyApprovalForPackage( + { canvas: false }, + [node({ name: 'canvas', version: '2.11.0' })], + { pin: true } + ) + t.match(nameOnly.warning, /remove the entry from allowScripts/) + t.notMatch(nameOnly.warning, /widen the deny/) + + // Pinned deny on a different version: suggest both widen and remove. + const pinned = applyApprovalForPackage( + { 'canvas@2.10.0': false }, + [node({ name: 'canvas', version: '2.10.0' })], + { pin: true } + ) + t.match(pinned.warning, /versioned deny/) + t.match(pinned.warning, /npm deny-scripts canvas/) + t.match(pinned.warning, /widen the deny to all versions/) + t.match(pinned.warning, /remove the entry/) + + // Multi-version deny disjunction: same as pinned (versioned). + const multi = applyApprovalForPackage( + { 'canvas@2.10.0 || 2.11.0': false }, + [node({ name: 'canvas', version: '2.10.0' })], + { pin: true } + ) + t.match(multi.warning, /versioned deny/) + t.match(multi.warning, /npm deny-scripts canvas/) +}) + +t.test('denyWarning: tag-type key (pkg@latest: false) is name-only', async t => { + // `canvas@latest` parses as type='tag'. Treat the same as a bare name. + const { warning } = applyApprovalForPackage( + { 'canvas@latest': false }, + [node({ name: 'canvas', version: '2.11.0' })], + { pin: true } + ) + t.match(warning, /remove the entry/) + t.notMatch(warning, /versioned deny/) +}) + +t.test('applyApprovalForPackage — multi-version entry + --pin=false adds name-only alongside', async t => { + // RFC table: existing `pkg@a.b.c || d.e.f: true` + installed `pkg@x.y.z` + // + --pin=false adds `pkg: true`. The multi-version disjunction stays + // (it captures intent the command can't infer), and the name-only + // entry is added. + const { allowScripts } = applyApprovalForPackage( + { 'canvas@1.0.0 || 2.0.0': true }, + [node({ name: 'canvas', version: '3.0.0' })], + { pin: false } + ) + t.strictSame(allowScripts, { + 'canvas@1.0.0 || 2.0.0': true, + canvas: true, + }) +}) + +t.test('versionedKeyFor — registry resolved that versionFromTgz cannot parse returns null', async t => { + // Private-registry mirror / alternate CDN URL shape that doesn't match + // the standard `/-/name-version.tgz` pattern. Exercises the log.silly + // breadcrumb path in versionedKeyFor, including each fallback branch + // of the `node.path || node.name || ''` label expression. + const resolved = 'https://private-mirror.example.com/blobs/abc123' + t.equal(versionedKeyFor({ + path: '/fake/mystery', name: 'mystery', resolved, isRegistryDependency: true, + }), null, 'falls back when node has a path') + t.equal(versionedKeyFor({ + name: 'mystery', resolved, isRegistryDependency: true, + }), null, 'falls back when node has only a name') + t.equal(versionedKeyFor({ + resolved, isRegistryDependency: true, + }), null, 'falls back when node has neither path nor name') +}) diff --git a/deps/npm/test/lib/utils/check-allow-scripts.js b/deps/npm/test/lib/utils/check-allow-scripts.js new file mode 100644 index 00000000000000..8dea9674375df4 --- /dev/null +++ b/deps/npm/test/lib/utils/check-allow-scripts.js @@ -0,0 +1,263 @@ +const t = require('tap') + +const mockCheck = (t, mocks = {}) => + t.mock('../../../lib/utils/check-allow-scripts.js', mocks) + +// Build a minimal "arborist tree" fixture for the walker. +const arb = ({ nodes, allowScripts = null, ignoreScripts = false } = {}) => ({ + options: { allowScripts, ignoreScripts }, + actualTree: { + inventory: new Map(nodes.map((n, i) => [`node_modules/${n.name || `n${i}`}`, n])), + }, +}) + +const node = ({ + name = 'pkg', + packageName, + version = '1.0.0', + resolved, + scripts = {}, + gypfile, + path: nodePath = `/fake/${name}`, + isProjectRoot = false, + isWorkspace = false, + isLink = false, + isRegistryDependency, +} = {}) => { + const pkgName = packageName ?? name + const resolvedUrl = resolved + ?? `https://registry.npmjs.org/${pkgName}/-/${pkgName}-${version}.tgz` + // Default isRegistryDependency to match the shape of resolved: registry + // tarballs are registry, anything else (git, file, remote) is not. + const isReg = isRegistryDependency ?? /^https?:\/\/[^/]+\/.+\/-\/[^/]+-\d/.test(resolvedUrl) + return { + name, + packageName: pkgName, + version, + resolved: resolvedUrl, + location: `node_modules/${name}`, + isRegistryDependency: isReg, + path: nodePath, + isProjectRoot, + isWorkspace, + isLink, + package: { scripts, ...(gypfile !== undefined ? { gypfile } : {}) }, + } +} + +t.test('returns [] when ignoreScripts is set', async t => { + const checkAllowScripts = mockCheck(t) + const result = await checkAllowScripts({ + arb: arb({ + nodes: [node({ scripts: { install: 'do-stuff' } })], + ignoreScripts: true, + }), + npm: { flatOptions: {} }, + }) + t.strictSame(result, []) +}) + +t.test('returns [] when dangerouslyAllowAllScripts is set', async t => { + const checkAllowScripts = mockCheck(t) + const result = await checkAllowScripts({ + arb: arb({ nodes: [node({ scripts: { install: 'do-stuff' } })] }), + npm: { flatOptions: { dangerouslyAllowAllScripts: true } }, + }) + t.strictSame(result, []) +}) + +t.test('skips project root, workspace, and linked nodes', async t => { + const checkAllowScripts = mockCheck(t) + const result = await checkAllowScripts({ + arb: arb({ + nodes: [ + node({ name: 'root', scripts: { install: 'x' }, isProjectRoot: true }), + node({ name: 'ws', scripts: { install: 'x' }, isWorkspace: true }), + node({ name: 'linked', scripts: { install: 'x' }, isLink: true }), + ], + }), + npm: { flatOptions: {} }, + }) + t.strictSame(result, []) +}) + +t.test('skips nodes with no install-relevant scripts', async t => { + const checkAllowScripts = mockCheck(t) + const result = await checkAllowScripts({ + arb: arb({ + nodes: [node({ scripts: { test: 'jest' } })], + }), + npm: { flatOptions: {} }, + }) + t.strictSame(result, []) +}) + +t.test('includes nodes with preinstall/install/postinstall', async t => { + const checkAllowScripts = mockCheck(t) + const result = await checkAllowScripts({ + arb: arb({ + nodes: [ + node({ name: 'a', scripts: { preinstall: 'pre' } }), + node({ name: 'b', scripts: { install: 'inst' } }), + node({ name: 'c', scripts: { postinstall: 'post' } }), + ], + }), + npm: { flatOptions: {} }, + }) + t.equal(result.length, 3) + t.strictSame(result[0].scripts, { preinstall: 'pre' }) + t.strictSame(result[1].scripts, { install: 'inst' }) + t.strictSame(result[2].scripts, { postinstall: 'post' }) +}) + +t.test('prepare counts for non-registry sources only', async t => { + const checkAllowScripts = mockCheck(t) + const result = await checkAllowScripts({ + arb: arb({ + nodes: [ + // registry: prepare ignored + node({ + name: 'registry-pkg', + resolved: 'https://registry.npmjs.org/registry-pkg/-/registry-pkg-1.0.0.tgz', + scripts: { prepare: 'do' }, + }), + // git: prepare counts + node({ + name: 'git-pkg', + resolved: 'git+ssh://git@github.com/foo/bar.git#abcdef0123456789', + scripts: { prepare: 'do' }, + }), + ], + }), + npm: { flatOptions: {} }, + }) + t.equal(result.length, 1) + t.equal(result[0].node.name, 'git-pkg') +}) + +t.test('detects synthetic node-gyp via binding.gyp runtime check', async t => { + const checkAllowScripts = mockCheck(t, { + '@npmcli/arborist/lib/install-scripts.js': async (n) => { + if (n.path === '/has-bindings') { + return { install: 'node-gyp rebuild' } + } + return {} + }, + }) + + const result = await checkAllowScripts({ + arb: arb({ + nodes: [ + node({ name: 'native', path: '/has-bindings' }), + node({ name: 'pure-js', path: '/no-bindings' }), + ], + }), + npm: { flatOptions: {} }, + }) + t.equal(result.length, 1) + t.equal(result[0].node.name, 'native') + t.strictSame(result[0].scripts, { install: 'node-gyp rebuild' }) +}) + +t.test('skips node-gyp detection when gypfile is explicitly false', async t => { + // Mock returns no scripts to simulate the gypfile:false short-circuit + // inside getInstallScripts. + const checkAllowScripts = mockCheck(t, { + '@npmcli/arborist/lib/install-scripts.js': async () => ({}), + }) + + const result = await checkAllowScripts({ + arb: arb({ + nodes: [node({ name: 'opt-out', gypfile: false })], + }), + npm: { flatOptions: {} }, + }) + t.strictSame(result, []) +}) + +t.test('skips approved nodes', async t => { + const checkAllowScripts = mockCheck(t) + const result = await checkAllowScripts({ + arb: arb({ + nodes: [node({ name: 'allowed', scripts: { install: 'x' } })], + allowScripts: { allowed: true }, + }), + npm: { flatOptions: {} }, + }) + t.strictSame(result, []) +}) + +t.test('skips denied nodes (false counts as reviewed)', async t => { + const checkAllowScripts = mockCheck(t) + const result = await checkAllowScripts({ + arb: arb({ + nodes: [node({ name: 'denied', scripts: { install: 'x' } })], + allowScripts: { denied: false }, + }), + npm: { flatOptions: {} }, + }) + t.strictSame(result, []) +}) + +t.test('includes unreviewed nodes when policy is set but does not cover them', async t => { + const checkAllowScripts = mockCheck(t) + const result = await checkAllowScripts({ + arb: arb({ + nodes: [ + node({ name: 'allowed', scripts: { install: 'x' } }), + node({ name: 'unreviewed', scripts: { install: 'y' } }), + ], + allowScripts: { allowed: true }, + }), + npm: { flatOptions: {} }, + }) + t.equal(result.length, 1) + t.equal(result[0].node.name, 'unreviewed') +}) + +t.test('reports every install-script node when no policy is set', async t => { + const checkAllowScripts = mockCheck(t) + const result = await checkAllowScripts({ + arb: arb({ + nodes: [ + node({ name: 'a', scripts: { install: 'x' } }), + node({ name: 'b', scripts: { postinstall: 'y' } }), + ], + }), + npm: { flatOptions: {} }, + }) + t.equal(result.length, 2) +}) + +t.test('survives missing actualTree', async t => { + const checkAllowScripts = mockCheck(t) + const result = await checkAllowScripts({ + arb: { options: {} }, + npm: { flatOptions: {} }, + }) + t.strictSame(result, []) +}) + +t.test('bundled dep with install scripts is reported as unreviewed regardless of policy', async t => { + const checkAllowScripts = mockCheck(t) + const bundled = node({ + name: 'bundled-pkg', + version: '1.0.0', + resolved: undefined, + scripts: { install: 'do-stuff' }, + }) + bundled.inBundle = true + + const result = await checkAllowScripts({ + arb: arb({ + nodes: [bundled], + // Policy explicitly allows the bundled name — the matcher should + // still return null and the walker should still flag the bundled + // dep as unreviewed. + allowScripts: { 'bundled-pkg': true }, + }), + npm: { flatOptions: {} }, + }) + t.equal(result.length, 1, 'bundled dep flagged despite explicit allow entry') + t.equal(result[0].node, bundled) +}) diff --git a/deps/npm/test/lib/utils/reify-output.js b/deps/npm/test/lib/utils/reify-output.js index 134951e40aabd1..b1bc92b1c77aed 100644 --- a/deps/npm/test/lib/utils/reify-output.js +++ b/deps/npm/test/lib/utils/reify-output.js @@ -448,3 +448,114 @@ t.test('prints dedupe difference on long', async t => { t.matchSnapshot(out, 'diff table') }) + +t.test('prints unreviewed install scripts summary', async t => { + const mockReifyWithExtras = async (t, reify, extras, { command, ...config } = {}) => { + const mock = await mockNpm(t, { command, config }) + Object.defineProperty(mock.npm, 'command', { + get () { + return command + }, + enumerable: true, + }) + reifyOutput(mock.npm, reify, extras) + mock.npm.finish() + return mock + } + + const baseReify = { + actualTree: { name: 'host', inventory: { has: () => false } }, + diff: { children: [] }, + } + + const unreviewedScripts = [ + { + node: { packageName: 'canvas', name: 'canvas', version: '2.11.0', path: '/x/canvas' }, + scripts: { install: 'node-gyp rebuild' }, + }, + { + node: { packageName: 'sharp', name: 'sharp', version: '0.33.2', path: '/x/sharp' }, + scripts: { preinstall: 'pre', postinstall: 'post' }, + }, + ] + + const mock = await mockReifyWithExtras(t, baseReify, { unreviewedScripts }) + const warn = mock.logs.warn.byTitle('allow-scripts').join('\n') + t.match(warn, /2 packages have install scripts not yet covered/) + t.match(warn, /canvas@2\.11\.0 \(install: node-gyp rebuild\)/) + t.match(warn, /sharp@0\.33\.2 \(preinstall: pre; postinstall: post\)/) + t.match(warn, /npm approve-scripts --allow-scripts-pending/) +}) + +t.test('single unreviewed script uses singular wording', async t => { + const mockReifyWithExtras = async (t, reify, extras) => { + const mock = await mockNpm(t, {}) + reifyOutput(mock.npm, reify, extras) + mock.npm.finish() + return mock + } + + const mock = await mockReifyWithExtras( + t, + { actualTree: { inventory: { has: () => false } }, diff: { children: [] } }, + { + unreviewedScripts: [{ + node: { packageName: 'one', name: 'one', version: '1.0.0', path: '/x' }, + scripts: { install: 'do' }, + }], + } + ) + t.match(mock.logs.warn.byTitle('allow-scripts').join('\n'), /1 package has install scripts/) +}) + +t.test('json output includes unreviewedScripts', async t => { + const mock = await mockNpm(t, { config: { json: true } }) + reifyOutput(mock.npm, { + actualTree: { inventory: { size: 0 } }, + diff: null, + }, { + unreviewedScripts: [{ + node: { packageName: 'pkg', name: 'pkg', version: '1.0.0', path: '/x' }, + scripts: { install: 'cmd' }, + }], + }) + mock.npm.finish() + const parsed = JSON.parse(mock.joinedOutput()) + t.match(parsed.unreviewedScripts, [{ + name: 'pkg', + version: '1.0.0', + path: '/x', + scripts: { install: 'cmd' }, + }]) +}) + +t.test('unreviewed script with node.name only (no packageName) still renders', async t => { + const mock = await mockNpm(t, {}) + reifyOutput(mock.npm, { + actualTree: { inventory: { has: () => false } }, + diff: { children: [] }, + }, { + unreviewedScripts: [{ + node: { name: 'fallback', path: '/x' }, // no packageName, no version + scripts: { install: 'cmd' }, + }], + }) + mock.npm.finish() + t.match(mock.logs.warn.byTitle('allow-scripts').join('\n'), / fallback \(install: cmd\)/) +}) + +t.test('json output includes node.name when packageName is missing', async t => { + const mock = await mockNpm(t, { config: { json: true } }) + reifyOutput(mock.npm, { + actualTree: { inventory: { size: 0 } }, + diff: null, + }, { + unreviewedScripts: [{ + node: { name: 'fallback', path: '/x' }, + scripts: { install: 'cmd' }, + }], + }) + mock.npm.finish() + const parsed = JSON.parse(mock.joinedOutput()) + t.equal(parsed.unreviewedScripts[0].name, 'fallback') +}) diff --git a/deps/npm/test/lib/utils/resolve-allow-scripts.js b/deps/npm/test/lib/utils/resolve-allow-scripts.js new file mode 100644 index 00000000000000..0d6cdb8c040ac9 --- /dev/null +++ b/deps/npm/test/lib/utils/resolve-allow-scripts.js @@ -0,0 +1,347 @@ +const t = require('tap') +const mockNpm = require('../../fixtures/mock-npm') +const tmock = require('../../fixtures/tmock') + +const loadResolver = (t) => tmock(t, '{LIB}/utils/resolve-allow-scripts.js') + +// Helper that simulates config layering. `cliConfig` sets the value at +// the 'cli' source; `npmrcConfig` sets it at the 'user' source. mockNpm +// puts all `config` keys into the 'cli' source by default, so for npmrc +// tests we use an .npmrc file instead. + +t.test('returns null when no policy is set anywhere', async t => { + const { npm } = await mockNpm(t, { + prefixDir: { 'package.json': JSON.stringify({ name: 'p' }) }, + }) + const resolveAllowScripts = loadResolver(t) + const result = await resolveAllowScripts(npm) + t.strictSame(result, { policy: null, source: null }) +}) + +t.test('global install: skips package.json but still consults CLI', async t => { + const { npm } = await mockNpm(t, { + config: { global: true, 'allow-scripts': 'canvas' }, + prefixDir: { 'package.json': JSON.stringify({ name: 'p', allowScripts: { sharp: true } }) }, + }) + const resolveAllowScripts = loadResolver(t) + const result = await resolveAllowScripts(npm) + t.equal(result.source, 'cli') + t.strictSame(result.policy, { canvas: true }) +}) + +t.test('global install: skips package.json but still consults .npmrc', async t => { + const { npm } = await mockNpm(t, { + config: { global: true }, + homeDir: { '.npmrc': 'allow-scripts = canvas' }, + prefixDir: { + 'package.json': JSON.stringify({ name: 'p', allowScripts: { sharp: true } }), + }, + }) + const resolveAllowScripts = loadResolver(t) + const result = await resolveAllowScripts(npm) + t.equal(result.source, '.npmrc') + t.strictSame(result.policy, { canvas: true }) +}) + +t.test('global install with no CLI or .npmrc returns null', async t => { + const { npm } = await mockNpm(t, { + config: { global: true }, + prefixDir: { 'package.json': JSON.stringify({ name: 'p', allowScripts: { x: true } }) }, + }) + const resolveAllowScripts = loadResolver(t) + const result = await resolveAllowScripts(npm) + t.strictSame(result, { policy: null, source: null }) +}) + +t.test('reads from package.json when only package.json is set', async t => { + const { npm } = await mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'p', + allowScripts: { canvas: true, 'core-js': false, 'sharp@0.33.2': true }, + }), + }, + }) + const resolveAllowScripts = loadResolver(t) + const result = await resolveAllowScripts(npm) + t.equal(result.source, 'package.json') + t.strictSame(result.policy, { canvas: true, 'core-js': false, 'sharp@0.33.2': true }) +}) + +t.test('--allow-scripts CLI flag is rejected in project-scoped installs', async t => { + const mock = await mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'p', + allowScripts: { sharp: true }, + }), + }, + // mock-npm puts all config keys at the 'cli' source. + config: { 'allow-scripts': 'canvas' }, + }) + const resolveAllowScripts = loadResolver(t) + await t.rejects( + resolveAllowScripts(mock.npm), + { code: 'EALLOWSCRIPTS', message: /--allow-scripts is not allowed/ } + ) +}) + +t.test('--allow-scripts CLI flag is accepted in global installs (RFC layer 1 wins)', async t => { + const mock = await mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'p', + allowScripts: { sharp: true }, + }), + }, + config: { 'allow-scripts': 'canvas', global: true }, + }) + const resolveAllowScripts = loadResolver(t) + const result = await resolveAllowScripts(mock.npm) + t.equal(result.source, 'cli') + t.strictSame(result.policy, { canvas: true }) +}) + +t.test('package.json wins over .npmrc setting (RFC layer 2 > layer 3)', async t => { + // Put the allow-scripts setting in an .npmrc file so it loads at the + // 'user' source, not 'cli'. + const mock = await mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'p', + allowScripts: { sharp: true }, + }), + '.npmrc': 'allow-scripts = canvas', + }, + }) + const resolveAllowScripts = loadResolver(t) + const result = await resolveAllowScripts(mock.npm) + t.equal(result.source, 'package.json') + t.strictSame(result.policy, { sharp: true }) + t.match( + mock.logs.warn.byTitle('allow-scripts'), + [/\.npmrc allow-scripts setting is being ignored because package.json/] + ) +}) + +t.test('.npmrc setting is used when nothing higher is set', async t => { + const { npm } = await mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ name: 'p' }), + '.npmrc': 'allow-scripts = canvas, sharp', + }, + }) + const resolveAllowScripts = loadResolver(t) + const result = await resolveAllowScripts(npm) + t.equal(result.source, '.npmrc') + t.strictSame(result.policy, { canvas: true, sharp: true }) +}) + +t.test('--allow-scripts CLI flag is accepted via skipProjectConfig (npm exec)', async t => { + const mock = await mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ name: 'p' }), + '.npmrc': 'allow-scripts = canvas', + }, + config: { 'allow-scripts': 'sharp' }, + }) + const resolveAllowScripts = loadResolver(t) + const result = await resolveAllowScripts(mock.npm, { skipProjectConfig: true }) + t.equal(result.source, 'cli') + t.strictSame(result.policy, { sharp: true }) + t.match( + mock.logs.warn.byTitle('allow-scripts'), + [/\.npmrc allow-scripts setting is being ignored because --allow-scripts/] + ) +}) + +t.test('empty allowScripts object in package.json falls through to .npmrc', async t => { + const { npm } = await mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ name: 'p', allowScripts: {} }), + '.npmrc': 'allow-scripts = canvas', + }, + }) + const resolveAllowScripts = loadResolver(t) + const result = await resolveAllowScripts(npm) + t.equal(result.source, '.npmrc') + t.strictSame(result.policy, { canvas: true }) +}) + +t.test('missing package.json with .npmrc setting uses .npmrc', async t => { + const { npm } = await mockNpm(t, { + prefixDir: { + '.npmrc': 'allow-scripts = canvas', + }, + }) + const resolveAllowScripts = loadResolver(t) + const result = await resolveAllowScripts(npm) + t.equal(result.source, '.npmrc') + t.strictSame(result.policy, { canvas: true }) +}) + +t.test('reads from npm.prefix, not cwd, so workspace sub-installs find root policy', async t => { + const { npm } = await mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'root', + workspaces: ['packages/*'], + allowScripts: { sharp: true }, + }), + packages: { + sub: { 'package.json': JSON.stringify({ name: 'sub' }) }, + }, + }, + chdir: ({ prefix }) => require('node:path').join(prefix, 'packages', 'sub'), + }) + const resolveAllowScripts = loadResolver(t) + const result = await resolveAllowScripts(npm) + t.equal(result.source, 'package.json') + t.strictSame(result.policy, { sharp: true }) +}) + +t.test('drops package.json entries with forbidden semver ranges and warns', async t => { + const mock = await mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'p', + allowScripts: { + 'sharp@^0.33.0': true, // forbidden: caret range + 'canvas@~2.11.0': true, // forbidden: tilde range + 'core-js@>=3.0.0': true, // forbidden: gte range + 'good@1.2.3': true, // OK: exact pin + 'also-good': true, // OK: bare name + 'disjunction@1.0.0 || 2.0.0': true, // OK: exact disjunction + }, + }), + }, + }) + const resolveAllowScripts = loadResolver(t) + const result = await resolveAllowScripts(mock.npm) + t.equal(result.source, 'package.json') + t.strictSame(result.policy, { + 'good@1.2.3': true, + 'also-good': true, + 'disjunction@1.0.0 || 2.0.0': true, + }) + const warnings = mock.logs.warn.byTitle('allow-scripts') + t.equal(warnings.filter(m => /semver ranges/.test(m)).length, 3) +}) + +t.test('drops package.json entries with dist-tag specs and warns', async t => { + const mock = await mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'p', + allowScripts: { + 'sharp@latest': true, // forbidden: dist-tag + 'canvas@next': true, // forbidden: dist-tag + 'good@1.2.3': true, // OK: exact pin + }, + }), + }, + }) + const resolveAllowScripts = loadResolver(t) + const result = await resolveAllowScripts(mock.npm) + t.equal(result.source, 'package.json') + t.strictSame(result.policy, { 'good@1.2.3': true }) + const warnings = mock.logs.warn.byTitle('allow-scripts') + t.equal(warnings.filter(m => /dist-tag specs/.test(m)).length, 2) +}) + +t.test('drops .npmrc forbidden ranges (and warns) but keeps valid entries', async t => { + const mock = await mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ name: 'p' }), + '.npmrc': 'allow-scripts = canvas, sharp@^0.33.0, lodash@4.17.21', + }, + }) + const resolveAllowScripts = loadResolver(t) + const result = await resolveAllowScripts(mock.npm) + t.equal(result.source, '.npmrc') + t.strictSame(result.policy, { canvas: true, 'lodash@4.17.21': true }) + const warnings = mock.logs.warn.byTitle('allow-scripts') + t.ok(warnings.some(m => /sharp@\^0\.33\.0/.test(m) && /semver ranges/.test(m))) +}) + +t.test('drops package.json entries that fail npa parse', async t => { + const mock = await mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'p', + allowScripts: { + '@@@invalid@@@': true, + good: true, + }, + }), + }, + }) + const resolveAllowScripts = loadResolver(t) + const result = await resolveAllowScripts(mock.npm) + t.equal(result.source, 'package.json') + t.strictSame(result.policy, { good: true }) + t.ok(mock.logs.warn.byTitle('allow-scripts').some(m => /unparseable/.test(m))) +}) + +t.test('returns null when all package.json entries are dropped as invalid', async t => { + const { npm } = await mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'p', + allowScripts: { 'sharp@^0.33.0': true }, + }), + }, + }) + const resolveAllowScripts = loadResolver(t) + const result = await resolveAllowScripts(npm) + t.strictSame(result, { policy: null, source: null }) +}) + +t.test('skipProjectConfig: ignores package.json even when present', async t => { + // Per RFC line 299, exec/npx consults only user/global .npmrc. + const { npm } = await mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'p', + allowScripts: { sharp: true }, + }), + '.npmrc': 'allow-scripts = canvas', + }, + }) + const resolveAllowScripts = loadResolver(t) + const result = await resolveAllowScripts(npm, { skipProjectConfig: true }) + // package.json is skipped, falls through to .npmrc. + t.equal(result.source, '.npmrc') + t.strictSame(result.policy, { canvas: true }) +}) + +t.test('skipProjectConfig: CLI still wins over .npmrc', async t => { + const { npm } = await mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'p', + allowScripts: { sharp: true }, + }), + '.npmrc': 'allow-scripts = canvas', + }, + config: { 'allow-scripts': 'lodash' }, + }) + const resolveAllowScripts = loadResolver(t) + const result = await resolveAllowScripts(npm, { skipProjectConfig: true }) + t.equal(result.source, 'cli') + t.strictSame(result.policy, { lodash: true }) +}) + +t.test('skipProjectConfig: returns null when only package.json is set', async t => { + const { npm } = await mockNpm(t, { + prefixDir: { + 'package.json': JSON.stringify({ + name: 'p', + allowScripts: { sharp: true }, + }), + }, + }) + const resolveAllowScripts = loadResolver(t) + const result = await resolveAllowScripts(npm, { skipProjectConfig: true }) + t.strictSame(result, { policy: null, source: null }) +}) diff --git a/deps/npm/test/lib/utils/strict-allow-scripts-preflight.js b/deps/npm/test/lib/utils/strict-allow-scripts-preflight.js new file mode 100644 index 00000000000000..e246c68998c451 --- /dev/null +++ b/deps/npm/test/lib/utils/strict-allow-scripts-preflight.js @@ -0,0 +1,191 @@ +const t = require('tap') + +const preflight = require('../../../lib/utils/strict-allow-scripts-preflight.js') + +// Build a node fixture that checkAllowScripts will pick up as "unreviewed": +// registry-resolved, hasInstallScript true, not project root / workspace / +// link, and no allowScripts entry covering it. +const node = ({ + name = 'pkg', + version = '1.0.0', + scripts = { install: 'node-gyp rebuild' }, +} = {}) => ({ + name, + resolved: `https://registry.npmjs.org/${name}/-/${name}-${version}.tgz`, + hasInstallScript: !!Object.keys(scripts).length, + path: `/fake/${name}`, + isProjectRoot: false, + isWorkspace: false, + isLink: false, + package: { name, version, scripts }, +}) + +const tree = (nodes) => ({ + inventory: new Map(nodes.map((n, i) => [`node_modules/${n.name}-${i}`, n])), +}) + +const makeArb = ({ ideal, actual, allowScripts = null } = {}) => { + const arb = { + options: { allowScripts, ignoreScripts: false }, + idealTree: ideal ?? null, + actualTree: actual ?? null, + } + arb.buildIdealTree = async () => arb.idealTree + return arb +} + +t.test('no-op when strictAllowScripts is not set', async t => { + const arb = makeArb({ ideal: tree([node()]) }) + await preflight({ arb, npm: { flatOptions: {} }, idealTreeOpts: {} }) + t.pass('returned without throwing') +}) + +t.test('no-op when dangerouslyAllowAllScripts overrides', async t => { + const arb = makeArb({ ideal: tree([node()]) }) + await preflight({ + arb, + npm: { flatOptions: { strictAllowScripts: true, dangerouslyAllowAllScripts: true } }, + idealTreeOpts: {}, + }) + t.pass('returned without throwing') +}) + +t.test('no-op when ignoreScripts overrides', async t => { + const arb = makeArb({ ideal: tree([node()]) }) + arb.options.ignoreScripts = true + await preflight({ + arb, + npm: { flatOptions: { strictAllowScripts: true } }, + idealTreeOpts: {}, + }) + t.pass('returned without throwing') +}) + +t.test('throws when unreviewed install scripts exist (idealTree path)', async t => { + const arb = makeArb({ ideal: tree([node({ name: 'canvas' }), node({ name: 'sharp' })]) }) + await t.rejects( + preflight({ + arb, + npm: { flatOptions: { strictAllowScripts: true } }, + idealTreeOpts: {}, + }), + { + code: 'ESTRICTALLOWSCRIPTS', + message: /2 package\(s\) have install scripts not covered/, + } + ) +}) + +t.test('passes when all install-script nodes are explicitly approved', async t => { + const arb = makeArb({ + ideal: tree([node({ name: 'canvas' })]), + allowScripts: { canvas: true }, + }) + await preflight({ + arb, + npm: { flatOptions: { strictAllowScripts: true } }, + idealTreeOpts: {}, + }) + t.pass('no error thrown') +}) + +t.test('passes when all install-script nodes are explicitly denied', async t => { + const arb = makeArb({ + ideal: tree([node({ name: 'canvas' })]), + allowScripts: { canvas: false }, + }) + await preflight({ + arb, + npm: { flatOptions: { strictAllowScripts: true } }, + idealTreeOpts: {}, + }) + t.pass('no error thrown') +}) + +t.test('skips buildIdealTree when arb.idealTree already exists (npm ci path)', async t => { + // `npm ci` builds the ideal tree before calling the preflight. The + // helper must not rebuild it. + const ideal = tree([node({ name: 'pre-built' })]) + const arb = makeArb({ ideal, allowScripts: { 'pre-built': true } }) + let buildCalls = 0 + arb.buildIdealTree = async () => { + buildCalls++ + return arb.idealTree + } + await preflight({ + arb, + npm: { flatOptions: { strictAllowScripts: true } }, + idealTreeOpts: {}, + }) + t.equal(buildCalls, 0, 'buildIdealTree was not called a second time') +}) + +t.test('builds the ideal tree when arb.idealTree is empty (npm install path)', async t => { + // `npm install` does not pre-build the ideal tree. The helper must + // build it so checkAllowScripts has something to walk. + const arb = makeArb({ allowScripts: { 'fresh-pkg': true } }) + let buildCalls = 0 + arb.buildIdealTree = async () => { + buildCalls++ + arb.idealTree = tree([node({ name: 'fresh-pkg' })]) + } + await preflight({ + arb, + npm: { flatOptions: { strictAllowScripts: true } }, + idealTreeOpts: {}, + }) + t.equal(buildCalls, 1, 'buildIdealTree was called once') +}) + +t.test('uses actualTree when idealTreeOpts is not provided (rebuild path)', async t => { + const arb = makeArb({ actual: tree([node({ name: 'rebuild-pkg' })]) }) + await t.rejects( + preflight({ + arb, + npm: { flatOptions: { strictAllowScripts: true } }, + }), + { + code: 'ESTRICTALLOWSCRIPTS', + message: /rebuild-pkg@1\.0\.0/, + } + ) +}) + +t.test('error message includes script bodies', async t => { + const arb = makeArb({ + ideal: tree([node({ name: 'canvas', version: '2.11.0', scripts: { install: 'node-gyp rebuild' } })]), + }) + await t.rejects( + preflight({ + arb, + npm: { flatOptions: { strictAllowScripts: true } }, + idealTreeOpts: {}, + }), + { message: /canvas@2\.11\.0 \(install: node-gyp rebuild\)/ } + ) +}) + +t.test('error label falls back to node.name when package.version is missing', async t => { + // Exercises the `version ? '${name}@${version}' : name` branch in the + // error formatter when a node has no package.version (and the name + // falls back to node.name via `node.package?.name || node.name`). + const bare = { + name: 'no-version-pkg', + resolved: 'https://registry.npmjs.org/no-version-pkg/-/no-version-pkg-1.0.0.tgz', + hasInstallScript: true, + path: '/fake/no-version-pkg', + isProjectRoot: false, + isWorkspace: false, + isLink: false, + package: { scripts: { install: 'node-gyp rebuild' } }, + } + const arb = makeArb({ ideal: tree([bare]) }) + await t.rejects( + preflight({ + arb, + npm: { flatOptions: { strictAllowScripts: true } }, + idealTreeOpts: {}, + }), + { message: /no-version-pkg \(install: node-gyp rebuild\)/ } + ) +}) diff --git a/deps/npm/test/lib/utils/warn-workspace-allow-scripts.js b/deps/npm/test/lib/utils/warn-workspace-allow-scripts.js new file mode 100644 index 00000000000000..c9a5727157c21e --- /dev/null +++ b/deps/npm/test/lib/utils/warn-workspace-allow-scripts.js @@ -0,0 +1,108 @@ +const t = require('tap') +const { + findWorkspaceAllowScripts, + warnWorkspaceAllowScripts, +} = require('../../../lib/utils/warn-workspace-allow-scripts.js') + +const node = ({ + name = 'pkg', + packageName, + isWorkspace = false, + isProjectRoot = false, + allowScripts, + path = `/fake/${name}`, +} = {}) => ({ + name, + packageName: packageName ?? name, + path, + isWorkspace, + isProjectRoot, + package: allowScripts !== undefined ? { allowScripts } : {}, +}) + +const tree = (nodes) => ({ + inventory: new Map(nodes.map((n, i) => [`node_modules/${n.name || `n${i}`}`, n])), +}) + +t.test('returns [] for empty tree', async t => { + t.strictSame(findWorkspaceAllowScripts(tree([])), []) +}) + +t.test('returns [] for missing tree', async t => { + t.strictSame(findWorkspaceAllowScripts(null), []) + t.strictSame(findWorkspaceAllowScripts(undefined), []) +}) + +t.test('ignores project root with allowScripts', async t => { + const t1 = tree([ + node({ name: 'root', isProjectRoot: true, isWorkspace: true, allowScripts: { x: true } }), + ]) + t.strictSame(findWorkspaceAllowScripts(t1), []) +}) + +t.test('ignores non-workspace dep with allowScripts', async t => { + const t1 = tree([ + node({ name: 'dep', allowScripts: { x: true } }), + ]) + t.strictSame(findWorkspaceAllowScripts(t1), []) +}) + +t.test('finds non-root workspace with allowScripts', async t => { + const ws = node({ name: 'ws', isWorkspace: true, allowScripts: { x: true } }) + const t1 = tree([ + node({ name: 'root', isProjectRoot: true, isWorkspace: true }), + ws, + ]) + t.equal(findWorkspaceAllowScripts(t1).length, 1) + t.equal(findWorkspaceAllowScripts(t1)[0], ws) +}) + +t.test('finds workspace with empty allowScripts object too', async t => { + const ws = node({ name: 'ws', isWorkspace: true, allowScripts: {} }) + t.equal(findWorkspaceAllowScripts(tree([ws])).length, 1) +}) + +t.test('warnWorkspaceAllowScripts emits one log.warn per offender', async t => { + const warnings = [] + const listener = (level, ...args) => { + if (level === 'warn') { + warnings.push(args) + } + } + process.on('log', listener) + t.teardown(() => process.off('log', listener)) + + const t1 = tree([ + node({ name: 'root', isProjectRoot: true, isWorkspace: true }), + node({ name: 'a', isWorkspace: true, allowScripts: { x: true } }), + node({ name: 'b', isWorkspace: true, allowScripts: { y: false } }), + node({ name: 'c', isWorkspace: true }), // no allowScripts; no warning + ]) + warnWorkspaceAllowScripts(t1) + + t.equal(warnings.length, 2) + t.match(warnings[0][1], /allowScripts in workspace a/) + t.match(warnings[1][1], /allowScripts in workspace b/) +}) + +t.test('warnWorkspaceAllowScripts uses node.name when packageName missing', async t => { + const warnings = [] + const listener = (level, ...args) => { + if (level === 'warn') { + warnings.push(args) + } + } + process.on('log', listener) + t.teardown(() => process.off('log', listener)) + + // packageName undefined, name set + const ws = { + name: 'fallback-name', + path: '/x', + isWorkspace: true, + isProjectRoot: false, + package: { allowScripts: { x: true } }, + } + warnWorkspaceAllowScripts({ inventory: new Map([['node_modules/ws', ws]]) }) + t.match(warnings[0][1], /workspace fallback-name/) +})