diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/cli_helper_message/snapshots/cli_helper_message.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/cli_helper_message/snapshots/cli_helper_message.md index aa0b6e0fe5..a5a37e1b89 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/cli_helper_message/snapshots/cli_helper_message.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/cli_helper_message/snapshots/cli_helper_message.md @@ -130,6 +130,7 @@ Options: -g, --global Install globally (requires package names) --node Node.js version to use for global installation (only with -g) --concurrency Number of global package installs to run in parallel (only with -g) + --run-scripts Run all lifecycle scripts (only with -g) -h, --help Print help Documentation: https://viteplus.dev/guide/install @@ -176,6 +177,7 @@ Options: -g, --global Install globally --node Node.js version to use for global installation (only with -g) --concurrency Number of global package installs to run in parallel (only with -g) + --run-scripts Run all lifecycle scripts (only with -g) -h, --help Print help Documentation: https://viteplus.dev/guide/install @@ -228,6 +230,7 @@ Arguments: Options: -L, --latest Update to latest version (ignore semver range) -g, --global Update global packages + --run-scripts Run all lifecycle scripts (only with -g) --concurrency Number of global package updates to run in parallel (only with -g) --reinstall-node-mismatch Reinstall up-to-date global packages installed with a different Node.js version --ignore-node-mismatch Skip up-to-date global packages installed with a different Node.js version diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_bun/snapshots/command_add_bun.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_bun/snapshots/command_add_bun.md index 65acc370c9..8cc89cc278 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_bun/snapshots/command_add_bun.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_bun/snapshots/command_add_bun.md @@ -41,6 +41,7 @@ Options: -g, --global Install globally --node Node.js version to use for global installation (only with -g) --concurrency Number of global package installs to run in parallel (only with -g) + --run-scripts Run all lifecycle scripts (only with -g) -h, --help Print help Documentation: https://viteplus.dev/guide/install diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_npm10/snapshots/command_add_npm10.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_npm10/snapshots/command_add_npm10.md index fa89053e35..9f46b1e117 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_npm10/snapshots/command_add_npm10.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_npm10/snapshots/command_add_npm10.md @@ -41,6 +41,7 @@ Options: -g, --global Install globally --node Node.js version to use for global installation (only with -g) --concurrency Number of global package installs to run in parallel (only with -g) + --run-scripts Run all lifecycle scripts (only with -g) -h, --help Print help Documentation: https://viteplus.dev/guide/install diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_npm11/snapshots/command_add_npm11.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_npm11/snapshots/command_add_npm11.md index 6994f3ef11..52fb1be760 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_npm11/snapshots/command_add_npm11.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_npm11/snapshots/command_add_npm11.md @@ -41,6 +41,7 @@ Options: -g, --global Install globally --node Node.js version to use for global installation (only with -g) --concurrency Number of global package installs to run in parallel (only with -g) + --run-scripts Run all lifecycle scripts (only with -g) -h, --help Print help Documentation: https://viteplus.dev/guide/install diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm10/snapshots/command_add_pnpm10.global.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm10/snapshots/command_add_pnpm10.global.md index 1d8c2acc5d..b5f0cb67fc 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm10/snapshots/command_add_pnpm10.global.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm10/snapshots/command_add_pnpm10.global.md @@ -41,6 +41,7 @@ Options: -g, --global Install globally --node Node.js version to use for global installation (only with -g) --concurrency Number of global package installs to run in parallel (only with -g) + --run-scripts Run all lifecycle scripts (only with -g) -h, --help Print help Documentation: https://viteplus.dev/guide/install diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm10/snapshots/command_add_pnpm10.local.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm10/snapshots/command_add_pnpm10.local.md index 2c76ae81bf..27d5b4ad97 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm10/snapshots/command_add_pnpm10.local.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm10/snapshots/command_add_pnpm10.local.md @@ -64,6 +64,8 @@ Options: Node.js version to use for global installation (only with -g) --concurrency Number of global package installs to run in parallel (only with -g) + --run-scripts + Run all lifecycle scripts (only with -g) -h, --help Print help ``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm11/snapshots/command_add_pnpm11.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm11/snapshots/command_add_pnpm11.md index f605e1480e..fd8f450d36 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm11/snapshots/command_add_pnpm11.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm11/snapshots/command_add_pnpm11.md @@ -41,6 +41,7 @@ Options: -g, --global Install globally --node Node.js version to use for global installation (only with -g) --concurrency Number of global package installs to run in parallel (only with -g) + --run-scripts Run all lifecycle scripts (only with -g) -h, --help Print help Documentation: https://viteplus.dev/guide/install diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm12/snapshots/command_add_pnpm12.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm12/snapshots/command_add_pnpm12.md index c1c4688e28..d2d0f0dba1 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm12/snapshots/command_add_pnpm12.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm12/snapshots/command_add_pnpm12.md @@ -41,6 +41,7 @@ Options: -g, --global Install globally --node Node.js version to use for global installation (only with -g) --concurrency Number of global package installs to run in parallel (only with -g) + --run-scripts Run all lifecycle scripts (only with -g) -h, --help Print help Documentation: https://viteplus.dev/guide/install diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm9/snapshots/command_add_pnpm9.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm9/snapshots/command_add_pnpm9.md index 597153f2f6..324728ba22 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm9/snapshots/command_add_pnpm9.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_pnpm9/snapshots/command_add_pnpm9.md @@ -41,6 +41,7 @@ Options: -g, --global Install globally --node Node.js version to use for global installation (only with -g) --concurrency Number of global package installs to run in parallel (only with -g) + --run-scripts Run all lifecycle scripts (only with -g) -h, --help Print help Documentation: https://viteplus.dev/guide/install diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_yarn4/snapshots/command_add_yarn4.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_yarn4/snapshots/command_add_yarn4.md index 6e70fbed1c..4f0d4f22bd 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_yarn4/snapshots/command_add_yarn4.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_add_yarn4/snapshots/command_add_yarn4.md @@ -41,6 +41,7 @@ Options: -g, --global Install globally --node Node.js version to use for global installation (only with -g) --concurrency Number of global package installs to run in parallel (only with -g) + --run-scripts Run all lifecycle scripts (only with -g) -h, --help Print help Documentation: https://viteplus.dev/guide/install diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_bun/snapshots/command_update_bun.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_bun/snapshots/command_update_bun.md index 41f16db801..9a698b1f5d 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_bun/snapshots/command_update_bun.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_bun/snapshots/command_update_bun.md @@ -18,6 +18,7 @@ Arguments: Options: -L, --latest Update to latest version (ignore semver range) -g, --global Update global packages + --run-scripts Run all lifecycle scripts (only with -g) --concurrency Number of global package updates to run in parallel (only with -g) --reinstall-node-mismatch Reinstall up-to-date global packages installed with a different Node.js version --ignore-node-mismatch Skip up-to-date global packages installed with a different Node.js version diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_pnpm10/snapshots/command_update_pnpm10.global.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_pnpm10/snapshots/command_update_pnpm10.global.md index ffc46aac1c..989307569d 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_pnpm10/snapshots/command_update_pnpm10.global.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_pnpm10/snapshots/command_update_pnpm10.global.md @@ -18,6 +18,7 @@ Arguments: Options: -L, --latest Update to latest version (ignore semver range) -g, --global Update global packages + --run-scripts Run all lifecycle scripts (only with -g) --concurrency Number of global package updates to run in parallel (only with -g) --reinstall-node-mismatch Reinstall up-to-date global packages installed with a different Node.js version --ignore-node-mismatch Skip up-to-date global packages installed with a different Node.js version diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_pnpm10/snapshots/command_update_pnpm10.local.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_pnpm10/snapshots/command_update_pnpm10.local.md index ea57f581d1..a99838e161 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_pnpm10/snapshots/command_update_pnpm10.local.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_pnpm10/snapshots/command_update_pnpm10.local.md @@ -16,6 +16,7 @@ Arguments: Options: -L, --latest Update to latest version (ignore semver range) -g, --global Update global packages + --run-scripts Run all lifecycle scripts (only with -g) --concurrency Number of global package updates to run in parallel (only with -g) --reinstall-node-mismatch Reinstall up-to-date global packages installed with a different Node.js version --ignore-node-mismatch Skip up-to-date global packages installed with a different Node.js version diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_pnpm11/snapshots/command_update_pnpm11.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_pnpm11/snapshots/command_update_pnpm11.md index af2231ad83..e2895a0fcb 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_pnpm11/snapshots/command_update_pnpm11.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_update_pnpm11/snapshots/command_update_pnpm11.md @@ -18,6 +18,7 @@ Arguments: Options: -L, --latest Update to latest version (ignore semver range) -g, --global Update global packages + --run-scripts Run all lifecycle scripts (only with -g) --concurrency Number of global package updates to run in parallel (only with -g) --reinstall-node-mismatch Reinstall up-to-date global packages installed with a different Node.js version --ignore-node-mismatch Skip up-to-date global packages installed with a different Node.js version diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/env_install_interrupt/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/env_install_interrupt/snapshots.toml index 59012dcaf0..d3b23cb9b3 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/env_install_interrupt/snapshots.toml +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/env_install_interrupt/snapshots.toml @@ -2,7 +2,7 @@ name = "env_install_interrupt" vp = "global" steps = [ - { argv = ["vp", "install", "-g", "./long-time-install-package"], continue-on-failure = true }, + { argv = ["vp", "install", "-g", "./long-time-install-package", "--run-scripts"], continue-on-failure = true }, { argv = ["long-time-install-package"], continue-on-failure = true }, # snapshot = false: the killed install's partial screen is timing-dependent # (which spinner frame was live when the process tree was terminated). ConPTY @@ -11,7 +11,7 @@ steps = [ { argv = ["node", "test-reinstall-interrupt.js"], comment = "Reinstall but interrupt", snapshot = false, continue-on-failure = true }, { argv = ["long-time-install-package"], comment = "Original package should be still runnable", continue-on-failure = true }, { argv = ["node", "check-stale-packages.js", "--expect-stale"], comment = "Interrupted reinstall should leave stale package", continue-on-failure = true }, - { argv = ["vp", "install", "-g", "./long-time-install-package"], comment = "Successful reinstall should clean stale packages", continue-on-failure = true }, + { argv = ["vp", "install", "-g", "./long-time-install-package", "--run-scripts"], comment = "Successful reinstall should clean stale packages", continue-on-failure = true }, { argv = ["node", "check-stale-packages.js"], continue-on-failure = true }, ] after = [ diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/env_install_interrupt/snapshots/env_install_interrupt.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/env_install_interrupt/snapshots/env_install_interrupt.md index 550c62a6f0..872a3cc452 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/env_install_interrupt/snapshots/env_install_interrupt.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/env_install_interrupt/snapshots/env_install_interrupt.md @@ -1,6 +1,6 @@ # env_install_interrupt -## `vp install -g ./long-time-install-package` +## `vp install -g ./long-time-install-package --run-scripts` ``` VITE+ - The Unified Toolchain for the Web @@ -37,7 +37,7 @@ Interrupted reinstall should leave stale package interrupted stale package exists ``` -## `vp install -g ./long-time-install-package` +## `vp install -g ./long-time-install-package --run-scripts` Successful reinstall should clean stale packages diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/env_install_interrupt/test-reinstall-interrupt.js b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/env_install_interrupt/test-reinstall-interrupt.js index 823e20dfa9..6323aa8227 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/env_install_interrupt/test-reinstall-interrupt.js +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/env_install_interrupt/test-reinstall-interrupt.js @@ -25,7 +25,7 @@ const vpBinary = path.join( 'bin', process.platform === 'win32' ? 'vp.exe' : 'vp', ); -const child = spawn(vpBinary, ['install', '-g', './long-time-install-package'], { +const child = spawn(vpBinary, ['install', '-g', './long-time-install-package', '--run-scripts'], { env: { ...process.env, VP_TEST_INTERRUPT_INSTALL: '1' }, stdio: 'inherit', }); diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/.node-version b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/.node-version new file mode 100644 index 0000000000..c94711948a --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/.node-version @@ -0,0 +1 @@ +22.23.2 diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/local-cli/cli.cjs b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/local-cli/cli.cjs new file mode 100644 index 0000000000..9a15abe108 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/local-cli/cli.cjs @@ -0,0 +1,2 @@ +#!/usr/bin/env node +console.log('local-cli works'); diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/local-cli/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/local-cli/package.json new file mode 100644 index 0000000000..f2b69c6716 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/local-cli/package.json @@ -0,0 +1,10 @@ +{ + "name": "local-cli", + "version": "1.0.0", + "bin": { + "local-cli": "cli.cjs" + }, + "scripts": { + "postinstall": "node -e \"require('node:fs').writeFileSync('postinstall-ran', '')\"" + } +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/mock-manifest.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/mock-manifest.json new file mode 100644 index 0000000000..918973bce2 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/mock-manifest.json @@ -0,0 +1,60 @@ +{ + "my-cli": { + "name": "my-cli", + "dist-tags": { + "latest": "2.0.0" + }, + "versions": { + "1.0.0": { + "name": "my-cli", + "version": "1.0.0", + "bin": { + "my-cli": "check.cjs" + }, + "scripts": { + "postinstall": "node -e \"require('node:fs').writeFileSync('postinstall-ran', process.version)\"" + }, + "dependencies": { + "native-addon": "1.0.0" + }, + "dist": { + "tarball": "{REGISTRY}/my-cli/-/my-cli-1.0.0.tgz" + } + }, + "2.0.0": { + "name": "my-cli", + "version": "2.0.0", + "bin": { + "my-cli": "check.cjs" + }, + "scripts": { + "postinstall": "node -e \"require('node:fs').writeFileSync('postinstall-ran', process.version)\"" + }, + "dependencies": { + "native-addon": "1.0.0" + }, + "dist": { + "tarball": "{REGISTRY}/my-cli/-/my-cli-2.0.0.tgz" + } + } + } + }, + "native-addon": { + "name": "native-addon", + "dist-tags": { + "latest": "1.0.0" + }, + "versions": { + "1.0.0": { + "name": "native-addon", + "version": "1.0.0", + "scripts": { + "postinstall": "node -e \"require('node:fs').writeFileSync('postinstall-ran', process.version)\"" + }, + "dist": { + "tarball": "{REGISTRY}/native-addon/-/native-addon-1.0.0.tgz" + } + } + } + } +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/my-cli/check.cjs b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/my-cli/check.cjs new file mode 100644 index 0000000000..e49596584b --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/my-cli/check.cjs @@ -0,0 +1,13 @@ +#!/usr/bin/env node +const assert = require('node:assert/strict'); +const { existsSync, readFileSync } = require('node:fs'); +const { dirname, join } = require('node:path'); + +// Exercise the installed CLI, including a transitive package's build output. +const parent = existsSync(join(__dirname, 'postinstall-ran')) ? 'ran' : 'skipped'; +const child = existsSync(join(dirname(require.resolve('native-addon/package.json')), 'postinstall-ran')) ? 'ran' : 'skipped'; +assert.equal(parent, process.argv[2]); +assert.equal(child, process.argv[3]); +if (parent === 'ran') assert.equal(readFileSync(join(__dirname, 'postinstall-ran'), 'utf8'), process.version); +if (child === 'ran') assert.equal(readFileSync(join(dirname(require.resolve('native-addon/package.json')), 'postinstall-ran'), 'utf8'), process.version); +console.log(`my-cli: ${parent}; native-addon: ${child}`); diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/my-cli/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/my-cli/package.json new file mode 100644 index 0000000000..35d1965a9d --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/my-cli/package.json @@ -0,0 +1,13 @@ +{ + "name": "my-cli", + "version": "1.0.0", + "bin": { + "my-cli": "check.cjs" + }, + "scripts": { + "postinstall": "node -e \"require('node:fs').writeFileSync('postinstall-ran', process.version)\"" + }, + "dependencies": { + "native-addon": "1.0.0" + } +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/native-addon/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/native-addon/package.json new file mode 100644 index 0000000000..6b56aeb8ef --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/native-addon/package.json @@ -0,0 +1,7 @@ +{ + "name": "native-addon", + "version": "1.0.0", + "scripts": { + "postinstall": "node -e \"require('node:fs').writeFileSync('postinstall-ran', process.version)\"" + } +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/package.json new file mode 100644 index 0000000000..2036978713 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/package.json @@ -0,0 +1,5 @@ +{ + "name": "global-install-scripts", + "private": true, + "packageManager": "npm@12.0.2" +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/setup-npm.cjs b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/setup-npm.cjs new file mode 100644 index 0000000000..e3a77d3298 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/setup-npm.cjs @@ -0,0 +1,11 @@ +const assert = require('node:assert/strict'); +const { spawnSync } = require('node:child_process'); +const { dirname, join, relative } = require('node:path'); + +// Replace npm only inside this unseeded case's runtime, using the same Node.js. +const prefix = process.platform === 'win32' ? dirname(process.execPath) : dirname(dirname(process.execPath)); +assert.ok(!relative(process.env.VP_HOME, prefix).startsWith('..')); +const npm = join(prefix, process.platform === 'win32' ? 'node_modules/npm' : 'lib/node_modules/npm'); +const result = spawnSync(process.execPath, [join(npm, 'bin/npm-cli.js'), 'install', '-g', 'npm@12.0.2', '--prefix', prefix, '--ignore-scripts'], { stdio: 'inherit' }); +assert.equal(result.status, 0); +assert.equal(require(join(npm, 'package.json')).version, '12.0.2'); diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/snapshots.toml new file mode 100644 index 0000000000..9f0c054cbf --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/snapshots.toml @@ -0,0 +1,57 @@ +[[case]] +name = "global_scripts_dangling_dependency" +vp = "global" +comment = "A dangling dependency link in a local package must not prevent global installation or skipped-script warnings." +steps = [ + { argv = ["vpt", "mkdir", "-p", "local-cli/node_modules"], snapshot = false }, + { argv = ["node", "-e", "require('node:fs').symlinkSync(require('node:path').resolve('removed-package'), 'local-cli/node_modules/unused-link', process.platform === 'win32' ? 'junction' : 'dir')"], snapshot = false }, + ["vp", "install", "-g", "./local-cli"], + ["local-cli"], + ["vpt", "stat-file", "local-cli/postinstall-ran", "--assert", "missing"], +] + +[[case]] +name = "global_scripts_default_and_enable" +vp = "global" +local-registry = true +comment = "Global installs and updates skip direct and transitive scripts by default and warn last. --run-scripts runs all scripts with the selected Node.js, including Node 20." +env = { npm_config_ignore_scripts = "false", npm_config_dangerously_allow_all_scripts = "true", npm_config_audit = "false", npm_config_fund = "false" } +steps = [ + { argv = ["vpt", "mkdir", "-p", "tarballs"], snapshot = false }, + { argv = ["npm", "pack", "./native-addon", "--ignore-scripts", "--pack-destination", "tarballs"], snapshot = false }, + { argv = ["npm", "pack", "./my-cli", "--ignore-scripts", "--pack-destination", "tarballs"], snapshot = false }, + { argv = ["vpt", "json-edit", "my-cli/package.json", "version", "2.0.0"], snapshot = false }, + { argv = ["npm", "pack", "./my-cli", "--ignore-scripts", "--pack-destination", "tarballs"], snapshot = false }, + ["vp", "install", "-g", "my-cli@1.0.0", "native-addon@1.0.0"], + ["my-cli", "skipped", "skipped"], + { argv = ["vp", "install", "-g", "my-cli@1.0.0", "--run-scripts"], envs = [["npm_config_ignore_scripts", "true"], ["npm_config_dangerously_allow_all_scripts", "false"]] }, + ["my-cli", "ran", "ran"], + { argv = ["vp", "env", "install", "node@20.19.6"], snapshot = false }, + ["vp", "add", "-g", "my-cli@1.0.0", "--node", "20.19.6", "--ignore-scripts"], + ["my-cli", "skipped", "skipped"], + ["vp", "add", "-g", "my-cli@1.0.0", "--node", "20.19.6", "--run-scripts"], + ["my-cli", "ran", "ran"], + ["vp", "update", "-g", "my-cli@2.0.0"], + ["my-cli", "skipped", "skipped"], + ["vp", "update", "-g", "my-cli@1.0.0", "--run-scripts"], + ["my-cli", "ran", "ran"], +] + +[[case]] +name = "global_scripts_npm12" +vp = "global" +seed-runtime = false +local-registry = true +comment = "A case-owned Node runtime with bundled npm 12 still runs all scripts only when explicitly enabled, without changing Node.js." +env = { npm_config_ignore_scripts = "true", npm_config_dangerously_allow_all_scripts = "false", npm_config_audit = "false", npm_config_fund = "false" } +steps = [ + { argv = ["node", "setup-npm.cjs"], snapshot = false, timeout = 120000 }, + ["node", "-p", "require(require('node:path').join(require('node:path').dirname(process.execPath), process.platform === 'win32' ? 'node_modules/npm/package.json' : '../lib/node_modules/npm/package.json')).version"], + { argv = ["vpt", "mkdir", "-p", "tarballs"], snapshot = false }, + { argv = ["npm", "pack", "./native-addon", "--ignore-scripts", "--pack-destination", "tarballs"], snapshot = false }, + { argv = ["npm", "pack", "./my-cli", "--ignore-scripts", "--pack-destination", "tarballs"], snapshot = false }, + ["vp", "install", "-g", "my-cli@1.0.0"], + ["my-cli", "skipped", "skipped"], + ["vp", "install", "-g", "my-cli@1.0.0", "--run-scripts"], + ["my-cli", "ran", "ran"], +] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/snapshots/global_scripts_dangling_dependency.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/snapshots/global_scripts_dangling_dependency.md new file mode 100644 index 0000000000..cd1a29c0a3 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/snapshots/global_scripts_dangling_dependency.md @@ -0,0 +1,34 @@ +# global_scripts_dangling_dependency + +A dangling dependency link in a local package must not prevent global installation or skipped-script warnings. + +## `vpt mkdir -p local-cli/node_modules` + + +## `node -e 'require('\''node:fs'\'').symlinkSync(require('\''node:path'\'').resolve('\''removed-package'\''), '\''local-cli/node_modules/unused-link'\'', process.platform === '\''win32'\'' ? '\''junction'\'' : '\''dir'\'')'` + + +## `vp install -g ./local-cli` + +``` +VITE+ - The Unified Toolchain for the Web + +info: Installing 1 global package with Node.js +✓ Installed local-cli 1.0.0 + Bins: local-cli +warning: Lifecycle scripts were skipped for: local-cli. +To allow them, reinstall with: + vp install -g ./local-cli --run-scripts +``` + +## `local-cli` + +``` +local-cli works +``` + +## `vpt stat-file local-cli/postinstall-ran --assert missing` + +``` +local-cli/postinstall-ran: missing +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/snapshots/global_scripts_default_and_enable.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/snapshots/global_scripts_default_and_enable.md new file mode 100644 index 0000000000..676aff1fb4 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/snapshots/global_scripts_default_and_enable.md @@ -0,0 +1,123 @@ +# global_scripts_default_and_enable + +Global installs and updates skip direct and transitive scripts by default and warn last. --run-scripts runs all scripts with the selected Node.js, including Node 20. + +## `vpt mkdir -p tarballs` + + +## `npm pack ./native-addon --ignore-scripts --pack-destination tarballs` + + +## `npm pack ./my-cli --ignore-scripts --pack-destination tarballs` + + +## `vpt json-edit my-cli/package.json version 2.0.0` + + +## `npm pack ./my-cli --ignore-scripts --pack-destination tarballs` + + +## `vp install -g my-cli@1.0.0 native-addon@1.0.0` + +``` +VITE+ - The Unified Toolchain for the Web + +info: Installing 2 global packages with Node.js +✓ Installed my-cli 1.0.0 + Bins: my-cli + +✓ Installed native-addon 1.0.0 +warning: Lifecycle scripts were skipped for: my-cli, native-addon. +To allow them, reinstall with: + vp install -g my-cli@1.0.0 --run-scripts +warning: Lifecycle scripts were skipped for: native-addon. +To allow them, reinstall with: + vp install -g native-addon@1.0.0 --run-scripts +``` + +## `my-cli skipped skipped` + +``` +my-cli: skipped; native-addon: skipped +``` + +## `npm_config_ignore_scripts=true npm_config_dangerously_allow_all_scripts=false vp install -g my-cli@1.0.0 --run-scripts` + +``` +VITE+ - The Unified Toolchain for the Web + +info: Installing 1 global package with Node.js +✓ Installed my-cli 1.0.0 + Bins: my-cli +``` + +## `my-cli ran ran` + +``` +my-cli: ran; native-addon: ran +``` + +## `vp env install node@20.19.6` + + +## `vp add -g my-cli@1.0.0 --node 20.19.6 --ignore-scripts` + +``` +info: Installing 1 global package with Node.js +✓ Installed my-cli 1.0.0 + Bins: my-cli +warning: Lifecycle scripts were skipped for: my-cli, native-addon. +To allow them, reinstall with: + vp install -g my-cli@1.0.0 --node --run-scripts +``` + +## `my-cli skipped skipped` + +``` +my-cli: skipped; native-addon: skipped +``` + +## `vp add -g my-cli@1.0.0 --node 20.19.6 --run-scripts` + +``` +info: Installing 1 global package with Node.js +✓ Installed my-cli 1.0.0 + Bins: my-cli +``` + +## `my-cli ran ran` + +``` +my-cli: ran; native-addon: ran +``` + +## `vp update -g my-cli@2.0.0` + +``` +info: Updating 1 global package with Node.js +✓ Updated my-cli to 2.0.0 + Bins: my-cli +warning: Lifecycle scripts were skipped for: my-cli, native-addon. +To allow them, reinstall with: + vp install -g my-cli@2.0.0 --run-scripts +``` + +## `my-cli skipped skipped` + +``` +my-cli: skipped; native-addon: skipped +``` + +## `vp update -g my-cli@1.0.0 --run-scripts` + +``` +info: Updating 1 global package with Node.js +✓ Updated my-cli to 1.0.0 + Bins: my-cli +``` + +## `my-cli ran ran` + +``` +my-cli: ran; native-addon: ran +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/snapshots/global_scripts_npm12.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/snapshots/global_scripts_npm12.md new file mode 100644 index 0000000000..1eae8f7066 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/global_install_scripts/snapshots/global_scripts_npm12.md @@ -0,0 +1,56 @@ +# global_scripts_npm12 + +A case-owned Node runtime with bundled npm 12 still runs all scripts only when explicitly enabled, without changing Node.js. + +## `node setup-npm.cjs` + + +## `node -p 'require(require('\''node:path'\'').join(require('\''node:path'\'').dirname(process.execPath), process.platform === '\''win32'\'' ? '\''node_modules/npm/package.json'\'' : '\''../lib/node_modules/npm/package.json'\'')).version'` + +``` +12.0.2 +``` + +## `vpt mkdir -p tarballs` + + +## `npm pack ./native-addon --ignore-scripts --pack-destination tarballs` + + +## `npm pack ./my-cli --ignore-scripts --pack-destination tarballs` + + +## `vp install -g my-cli@1.0.0` + +``` +VITE+ - The Unified Toolchain for the Web + +info: Installing 1 global package with Node.js +✓ Installed my-cli 1.0.0 + Bins: my-cli +warning: Lifecycle scripts were skipped for: my-cli, native-addon. +To allow them, reinstall with: + vp install -g my-cli@1.0.0 --run-scripts +``` + +## `my-cli skipped skipped` + +``` +my-cli: skipped; native-addon: skipped +``` + +## `vp install -g my-cli@1.0.0 --run-scripts` + +``` +VITE+ - The Unified Toolchain for the Web + +info: Installing 1 global package with Node.js +✓ Installed my-cli 1.0.0 + Bins: my-cli +``` + +## `my-cli ran ran` + +``` +my-cli: ran; native-addon: ran +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_ignore_scripts/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_ignore_scripts/snapshots.toml index 15b5570dbf..ea854f7d23 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_ignore_scripts/snapshots.toml +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_ignore_scripts/snapshots.toml @@ -33,7 +33,7 @@ steps = [ [[case]] name = "global_install_ignore_scripts" vp = "global" -comment = "Managed global install and add skip dependency lifecycle scripts when requested. Without the flag, scripts still run. The installed binary works in either case." +comment = "Managed global install and add skip dependency lifecycle scripts, including by default. The installed binary works in either case." env = { npm_config_ignore_scripts = "false", npm_config_audit = "false", npm_config_fund = "false" } steps = [ { argv = ["npm", "pack", "./scripted-dep", "--ignore-scripts"], snapshot = false }, @@ -43,8 +43,8 @@ steps = [ { argv = ["vp", "add", "-g", "--ignore-scripts", "./scripted-dep-1.0.0.tgz"], snapshot = false }, ["scripted-dep", "skipped"], { argv = ["vp", "remove", "-g", "scripted-dep"], snapshot = false }, - { argv = ["vp", "install", "-g", "./scripted-dep-1.0.0.tgz"], snapshot = false }, - ["scripted-dep", "ran"], + ["vp", "install", "-g", "./scripted-dep-1.0.0.tgz"], + ["scripted-dep", "skipped"], ] after = [ { argv = ["vp", "remove", "-g", "scripted-dep"], continue-on-failure = true }, diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_ignore_scripts/snapshots/global_install_ignore_scripts.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_ignore_scripts/snapshots/global_install_ignore_scripts.md index 7830dadfdd..265c5a0c82 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_ignore_scripts/snapshots/global_install_ignore_scripts.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/install_ignore_scripts/snapshots/global_install_ignore_scripts.md @@ -1,6 +1,6 @@ # global_install_ignore_scripts -Managed global install and add skip dependency lifecycle scripts when requested. Without the flag, scripts still run. The installed binary works in either case. +Managed global install and add skip dependency lifecycle scripts, including by default. The installed binary works in either case. ## `npm pack ./scripted-dep --ignore-scripts` @@ -31,9 +31,19 @@ postinstall: skipped ## `vp install -g ./scripted-dep-1.0.0.tgz` +``` +VITE+ - The Unified Toolchain for the Web + +info: Installing 1 global package with Node.js +✓ Installed scripted-dep 1.0.0 + Bins: scripted-dep +warning: Lifecycle scripts were skipped for: scripted-dep. +To allow them, reinstall with: + vp install -g ./scripted-dep-1.0.0.tgz --run-scripts +``` -## `scripted-dep ran` +## `scripted-dep skipped` ``` -postinstall: ran +postinstall: skipped ``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/npm_install_with_options/snapshots/npm_install_with_options.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/npm_install_with_options/snapshots/npm_install_with_options.md index 22974d0ba2..ef08af3c50 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/npm_install_with_options/snapshots/npm_install_with_options.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/npm_install_with_options/snapshots/npm_install_with_options.md @@ -38,6 +38,7 @@ Options: -g, --global Install globally (requires package names) --node Node.js version to use for global installation (only with -g) --concurrency Number of global package installs to run in parallel (only with -g) + --run-scripts Run all lifecycle scripts (only with -g) -h, --help Print help ``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/npm_update_notifier/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/npm_update_notifier/snapshots.toml index 6862462f47..d8181f7a50 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/npm_update_notifier/snapshots.toml +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/npm_update_notifier/snapshots.toml @@ -11,7 +11,7 @@ steps = [ name = "npm_global_update_notifier" vp = "global" comment = "Global installs disable npm update notifications in the child process." -steps = [["vp", "install", "-g", "./global-package"]] +steps = [["vp", "install", "-g", "./global-package", "--run-scripts"]] [[case]] name = "npm12_update_notifier" diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/npm_update_notifier/snapshots/npm_global_update_notifier.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/npm_update_notifier/snapshots/npm_global_update_notifier.md index 8ab0abf4bc..c075c049a6 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/npm_update_notifier/snapshots/npm_global_update_notifier.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/npm_update_notifier/snapshots/npm_global_update_notifier.md @@ -2,7 +2,7 @@ Global installs disable npm update notifications in the child process. -## `vp install -g ./global-package` +## `vp install -g ./global-package --run-scripts` ``` VITE+ - The Unified Toolchain for the Web diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/yarn_install_with_options/snapshots/yarn_install_with_options.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/yarn_install_with_options/snapshots/yarn_install_with_options.md index f5b9f60078..c6ce592109 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/yarn_install_with_options/snapshots/yarn_install_with_options.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/yarn_install_with_options/snapshots/yarn_install_with_options.md @@ -38,6 +38,7 @@ Options: -g, --global Install globally (requires package names) --node Node.js version to use for global installation (only with -g) --concurrency Number of global package installs to run in parallel (only with -g) + --run-scripts Run all lifecycle scripts (only with -g) -h, --help Print help ``` diff --git a/crates/vp_global_cli/src/cli.rs b/crates/vp_global_cli/src/cli.rs index 6216fef327..8fd6dbbed0 100644 --- a/crates/vp_global_cli/src/cli.rs +++ b/crates/vp_global_cli/src/cli.rs @@ -654,6 +654,7 @@ async fn run_package_manager_command( } Some(ManagedGlobalCommand::Update { packages, + ignore_scripts, latest, concurrency, reinstall_node_mismatch, @@ -667,6 +668,7 @@ async fn run_package_manager_command( } return managed_update( packages, + ignore_scripts, latest, concurrency, reinstall_node_mismatch, @@ -813,6 +815,7 @@ struct NodeMismatchPackage { async fn managed_update( packages: &[String], + ignore_scripts: bool, latest: bool, concurrency: Option, reinstall_node_mismatch: bool, @@ -966,7 +969,7 @@ async fn managed_update( global::install::InstallOptions { node_version: Some(¤t_node_version), force: false, - ignore_scripts: false, + ignore_scripts, concurrency, update: true, }, diff --git a/crates/vp_global_cli/src/commands/global/install.rs b/crates/vp_global_cli/src/commands/global/install.rs index 5a70953c69..4ff7405474 100644 --- a/crates/vp_global_cli/src/commands/global/install.rs +++ b/crates/vp_global_cli/src/commands/global/install.rs @@ -1,7 +1,9 @@ //! Global package installation handling. +#![deny(clippy::print_stdout)] + use std::{ - collections::{HashMap, HashSet}, + collections::{BTreeSet, HashMap, HashSet}, fs::{File, OpenOptions, TryLockError}, io::{Read, Write}, process::Stdio, @@ -44,6 +46,7 @@ struct InstalledPackage { js_bins: HashSet, install_id: String, install_dir: AbsolutePathBuf, + skipped_scripts: Vec, } type InstallError = (Option, Box); @@ -134,6 +137,7 @@ pub async fn install( options: InstallOptions<'_>, ) -> Result<(), InstallError> { let InstallOptions { node_version, force, ignore_scripts, concurrency, update } = options; + let explicit_node = node_version.is_some() && !update; if package_specs.is_empty() { return Ok(()); } @@ -275,6 +279,7 @@ pub async fn install( // 5. Finalize installed packages. let mut bin_owners = HashMap::::new(); + let mut script_warnings = Vec::new(); for (index, (package_name, Package { spec, install })) in packages.into_iter().enumerate() { let lock_file = install_locks.remove(&package_name); let Some(InstalledPackage { @@ -283,6 +288,7 @@ pub async fn install( mut js_bins, install_id, install_dir, + skipped_scripts, }) = install else { continue; @@ -540,11 +546,36 @@ pub async fn install( if index + 1 < packages_count { output::raw(""); } + if !skipped_scripts.is_empty() { + script_warnings.push((spec.to_string(), skipped_scripts)); + } + } + + // Keep recovery instructions after every package's installation summary. + let node_argument = + if explicit_node { format!(" --node {node_version}") } else { String::new() }; + for (spec, skipped) in script_warnings { + let spec = quote_install_argument(&spec); + output::raw_stderr(&format!( + "{} Lifecycle scripts were skipped for: {}.\nTo allow them, reinstall with:\n vp install -g {spec}{node_argument} --run-scripts", + style("warning:").for_stderr().yellow().bold(), + skipped.join(", "), + )); } if let Some(error) = first_error { Err(error) } else { Ok(()) } } +fn quote_install_argument(argument: &str) -> String { + if argument.bytes().all(|byte| byte.is_ascii_alphanumeric() || b"@/.,:_-".contains(&byte)) { + argument.to_string() + } else if cfg!(windows) { + format!("\"{}\"", argument.replace('"', "\\\"")) + } else { + format!("'{}'", argument.replace('\'', "'\\''")) + } +} + /// Install one package into a unique final prefix. async fn install_one( package_name: &str, @@ -565,8 +596,10 @@ async fn install_one( env.prepend(node_bin_dir, &["node", "npm", "npx"], PrependOptions::default())?; let mut command = Command::new(npm_path.as_path()); command.args(["install", "-g", "--no-fund", &package_spec]); - if ignore_scripts { - command.arg("--ignore-scripts"); + command.arg(if ignore_scripts { "--ignore-scripts" } else { "--ignore-scripts=false" }); + if !ignore_scripts { + // npm 12 also gates scripts by package approval; older npm ignores this setting. + command.env("npm_config_dangerously_allow_all_scripts", "true"); } let output = command .env("npm_config_prefix", install_dir.as_path()) @@ -591,7 +624,21 @@ async fn install_one( return Err(Error::Other(format!("npm install failed with {}", output.status).into())); } + // 3. Inspect the installed packages for skipped scripts and binary metadata. let node_modules_dir = get_node_modules_dir(&install_dir, package_name); + let skipped_scripts = if ignore_scripts { + // --ignore-scripts suppresses npm's blocked-script report, so inspect the installed tree. + collect_skipped_scripts(&get_node_modules_dir(&install_dir, "")).await + } else { + Ok(Vec::new()) + }; + let skipped_scripts = match skipped_scripts { + Ok(skipped) => skipped, + Err(error) => { + cleanup_failed_install(&install_dir).await?; + return Err(error); + } + }; let package_json_path = node_modules_dir.join("package.json"); if !tokio::fs::try_exists(&package_json_path).await.unwrap_or(false) { @@ -634,11 +681,84 @@ async fn install_one( } Ok(( - InstalledPackage { installed_version, bin_names, js_bins, install_id, install_dir }, + InstalledPackage { + installed_version, + bin_names, + js_bins, + install_id, + install_dir, + skipped_scripts, + }, lock_file, )) } +async fn collect_skipped_scripts(node_modules: &AbsolutePath) -> Result, Error> { + let mut pending = vec![node_modules.to_absolute_path_buf()]; + let mut visited = HashSet::new(); + let mut skipped = BTreeSet::new(); + while let Some(directory) = pending.pop() { + let mut entries = match tokio::fs::read_dir(&directory).await { + Ok(entries) => entries, + Err(error) if error.kind() == std::io::ErrorKind::NotFound => continue, + Err(error) => return Err(error.into()), + }; + while let Some(entry) = entries.next_entry().await? { + let file_type = entry.file_type().await?; + if !file_type.is_dir() && !file_type.is_symlink() { + continue; + } + let name = entry.file_name(); + let name = name.to_string_lossy(); + if name.starts_with('.') { + continue; + } + let package_dir = directory.join(name.as_ref()); + if name.starts_with('@') { + pending.push(package_dir); + continue; + } + // Linked local packages can form cycles through their node_modules. + let real_path = match tokio::fs::canonicalize(&package_dir).await { + Ok(real_path) => real_path, + // Local packages can retain dangling dependency links unrelated to this install. + Err(error) if error.kind() == std::io::ErrorKind::NotFound => continue, + Err(error) => return Err(error.into()), + }; + if !visited.insert(real_path) { + continue; + } + let manifest = match tokio::fs::read(package_dir.join("package.json")).await { + Ok(manifest) => manifest, + Err(error) if error.kind() == std::io::ErrorKind::NotFound => continue, + Err(error) => return Err(error.into()), + }; + let manifest: serde_json::Value = + serde_json::from_slice(&manifest).map_err(|error| { + Error::Other(format!("Failed to read installed package: {error}").into()) + })?; + let scripts = &manifest["scripts"]; + let has_script = + |event: &str| scripts[event].as_str().is_some_and(|script| !script.is_empty()); + let lifecycle = + ["preinstall", "install", "postinstall"].iter().any(|event| has_script(event)); + let prepare = file_type.is_symlink() && has_script("prepare"); + // npm implicitly runs node-gyp when there is no custom preinstall/install hook. + let node_gyp = manifest["gypfile"] != false + && !has_script("preinstall") + && !has_script("install") + && tokio::fs::try_exists(package_dir.join("binding.gyp")).await?; + if (lifecycle || prepare || node_gyp) + && let Some(name) = manifest["name"].as_str() + { + skipped.insert(name.to_string()); + } + pending.push(package_dir.join("node_modules")); + } + } + Ok(skipped.into_iter().collect()) +} + fn new_install_id() -> String { Uuid::new_v4().to_string() } diff --git a/crates/vp_pm_cli/src/cli.rs b/crates/vp_pm_cli/src/cli.rs index 37d0a3ca02..8bb575b93e 100644 --- a/crates/vp_pm_cli/src/cli.rs +++ b/crates/vp_pm_cli/src/cli.rs @@ -178,6 +178,7 @@ pub enum ManagedGlobalCommand<'a> { /// Update packages in the managed global store. Update { packages: &'a [String], + ignore_scripts: bool, latest: bool, concurrency: Option, reinstall_node_mismatch: bool, @@ -239,14 +240,14 @@ impl PackageManagerCommand { packages: &args.packages, node: args.node.as_deref(), force: args.force, - ignore_scripts: args.ignore_scripts, + ignore_scripts: !args.run_scripts, concurrency: args.concurrency, }), Self::Add(args) if args.global => Some(ManagedGlobalCommand::Install { packages: &args.packages, node: args.node.as_deref(), force: false, - ignore_scripts: args.ignore_scripts, + ignore_scripts: !args.run_scripts, concurrency: args.concurrency, }), Self::Remove(args) if args.global => Some(ManagedGlobalCommand::Remove { @@ -255,6 +256,7 @@ impl PackageManagerCommand { }), Self::Update(args) if args.global => Some(ManagedGlobalCommand::Update { packages: &args.packages, + ignore_scripts: !args.run_scripts, latest: args.latest, concurrency: args.concurrency, reinstall_node_mismatch: args.reinstall_node_mismatch, @@ -595,6 +597,8 @@ mod tests { vec![command, "-g", "react"], vec![command, "-g", "--ignore-scripts", "react"], vec![command, "-g", "react", "--ignore-scripts"], + vec![command, "-g", "react", "--run-scripts"], + vec![command, "--run-scripts", "-g", "react"], ] { let parsed = parse(&input).unwrap(); let Some(ManagedGlobalCommand::Install { ignore_scripts, .. }) = @@ -602,7 +606,7 @@ mod tests { else { panic!("expected managed install command: {input:?}"); }; - assert_eq!(ignore_scripts, input.contains(&"--ignore-scripts"), "{input:?}"); + assert_eq!(ignore_scripts, !input.contains(&"--run-scripts"), "{input:?}"); } } } @@ -630,6 +634,13 @@ mod tests { fn validates_managed_global_options() { assert!(parse(&["install", "-g"]).is_err()); assert!(parse(&["add", "--node", "22", "react"]).is_err()); + for command in ["install", "add", "update"] { + assert!(parse(&[command, "--run-scripts", "react"]).is_err()); + assert!(parse(&[command, "-g", "--ignore-scripts=false", "react"]).is_err()); + } + for command in ["install", "add"] { + assert!(parse(&[command, "-g", "--run-scripts", "--ignore-scripts", "react"]).is_err()); + } let install = parse(&["install", "-g", "--node", "22", "--concurrency", "2", "tsx"]).unwrap(); diff --git a/crates/vp_pm_cli/src/resolution/commands/add.rs b/crates/vp_pm_cli/src/resolution/commands/add.rs index 5c15e00757..c619764a58 100644 --- a/crates/vp_pm_cli/src/resolution/commands/add.rs +++ b/crates/vp_pm_cli/src/resolution/commands/add.rs @@ -105,6 +105,10 @@ pub struct AddArgs { #[arg(long, requires = "global", value_parser = parse_positive_usize)] pub(crate) concurrency: Option, + /// Run all lifecycle scripts (only with -g) + #[arg(long, requires = "global", conflicts_with = "ignore_scripts")] + pub(crate) run_scripts: bool, + /// Packages to add #[arg(required = true)] pub(crate) packages: Vec, diff --git a/crates/vp_pm_cli/src/resolution/commands/install.rs b/crates/vp_pm_cli/src/resolution/commands/install.rs index afa5b93f40..0a3e3c82c2 100644 --- a/crates/vp_pm_cli/src/resolution/commands/install.rs +++ b/crates/vp_pm_cli/src/resolution/commands/install.rs @@ -108,6 +108,10 @@ pub struct InstallArgs { #[arg(long, requires = "global", value_parser = parse_positive_usize)] pub(crate) concurrency: Option, + /// Run all lifecycle scripts (only with -g) + #[arg(long, requires = "global", conflicts_with = "ignore_scripts")] + pub(crate) run_scripts: bool, + /// Packages to add (if provided, acts as `vp add`) pub(crate) packages: Vec, @@ -208,6 +212,7 @@ impl InstallArgs { save_catalog: self.save_catalog, allow_build: None, ignore_scripts: self.ignore_scripts, + run_scripts: self.run_scripts, no_optional: self.no_optional, frozen_lockfile: self.frozen_lockfile, no_frozen_lockfile: self.no_frozen_lockfile, diff --git a/crates/vp_pm_cli/src/resolution/commands/update.rs b/crates/vp_pm_cli/src/resolution/commands/update.rs index c9e49e2616..2abf4e8770 100644 --- a/crates/vp_pm_cli/src/resolution/commands/update.rs +++ b/crates/vp_pm_cli/src/resolution/commands/update.rs @@ -16,6 +16,10 @@ pub struct UpdateArgs { #[arg(short = 'g', long)] pub(crate) global: bool, + /// Run all lifecycle scripts (only with -g) + #[arg(long, requires = "global")] + pub(crate) run_scripts: bool, + /// Number of global package updates to run in parallel (only with -g) #[arg(long, requires = "global", value_parser = parse_positive_usize)] pub(crate) concurrency: Option, diff --git a/docs/guide/install.md b/docs/guide/install.md index 0b763ccc50..64769202e2 100644 --- a/docs/guide/install.md +++ b/docs/guide/install.md @@ -126,6 +126,22 @@ Use these commands when you want package-manager-managed tools available outside - `vp list -g` - `vp outdated -g` +::: details Lifecycle scripts for global packages + +Global installs and updates disable lifecycle scripts by default, including scripts of transitive dependencies. If scripts were skipped, Vite+ prints a warning at the end with a reinstall command. + +Use `--run-scripts` to enable all lifecycle scripts for that invocation, including scripts of transitive dependencies: + +```bash +vp install -g my-cli --run-scripts +vp add -g my-cli --run-scripts +vp update -g my-cli --run-scripts +``` + +This uses the selected Node.js runtime and its bundled npm. The setting is not saved, so repeat it when updating. To repair an already-installed package, use `vp install -g --run-scripts` again, since `vp update -g` skips packages that are already up to date. + +::: + #### Add and Remove Use `vp add` and `vp remove` for day-to-day dependency edits instead of editing `package.json` by hand.