From b45ae69f45f280882484795cdece657f0c76df19 Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Thu, 3 Sep 2026 22:49:49 +0200 Subject: [PATCH] fix: accept --ignore-scripts on update (#14513) Pacquet omitted the lifecycle-script flags from its update argument set even though the TypeScript CLI accepts them. Add both boolean forms and merge them into the update config before dispatch, matching the existing install-family behavior. Fixes pnpm/pnpm#14512. --- .changeset/update-ignore-scripts.md | 5 +++ pnpm/crates/cli/src/cli_args/update.rs | 14 ++++++ pnpm/crates/cli/src/cli_args/update/tests.rs | 17 +++++++ pnpm/crates/cli/tests/suite/update.rs | 22 +++++++++ .../installing/commands/test/update/update.ts | 45 ++++++++++++++++++- 5 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 .changeset/update-ignore-scripts.md diff --git a/.changeset/update-ignore-scripts.md b/.changeset/update-ignore-scripts.md new file mode 100644 index 0000000000..3c2e48b46d --- /dev/null +++ b/.changeset/update-ignore-scripts.md @@ -0,0 +1,5 @@ +--- +"pacquet": patch +--- + +`pnpm update` now accepts `--ignore-scripts` and skips lifecycle scripts during the update [pnpm/pnpm#14512](https://github.com/pnpm/pnpm/issues/14512). diff --git a/pnpm/crates/cli/src/cli_args/update.rs b/pnpm/crates/cli/src/cli_args/update.rs index 259223efc4..b80a8c5fad 100644 --- a/pnpm/crates/cli/src/cli_args/update.rs +++ b/pnpm/crates/cli/src/cli_args/update.rs @@ -1,6 +1,7 @@ use crate::{ State, cli_args::{ + install::resolve_bool_override, lockfile_dir::LockfileDirArg, pipelines::InstallFamilySelection, recursive, @@ -148,6 +149,14 @@ pub struct UpdateArgs { #[clap(long = "ignore-pnpmfile")] pub ignore_pnpmfile: bool, + /// Don't run lifecycle scripts of the project or its dependencies. + #[clap(long = "ignore-scripts", overrides_with = "no_ignore_scripts")] + pub ignore_scripts: bool, + + /// Run lifecycle scripts even when the configuration disables them. + #[clap(long = "no-ignore-scripts", overrides_with = "ignore_scripts")] + pub no_ignore_scripts: bool, + /// URL of a pnpr server to offload revision refresh resolution to. #[clap(long = "pnpr-server")] pub pnpr_server: Option, @@ -179,6 +188,11 @@ struct PatchesWithSelectorError; impl UpdateArgs { pub(crate) fn apply_cli_config(&self, config: &mut Config) { + config.ignore_scripts = resolve_bool_override( + self.ignore_scripts, + self.no_ignore_scripts, + config.ignore_scripts, + ); config.ignore_pnpmfile = self.ignore_pnpmfile || config.ignore_pnpmfile; if let Some(pnpr_server) = self.pnpr_server.clone() { config.pnpr_server = Some(pnpr_server); diff --git a/pnpm/crates/cli/src/cli_args/update/tests.rs b/pnpm/crates/cli/src/cli_args/update/tests.rs index b3ae22299d..e0a936ef73 100644 --- a/pnpm/crates/cli/src/cli_args/update/tests.rs +++ b/pnpm/crates/cli/src/cli_args/update/tests.rs @@ -130,6 +130,23 @@ fn ignore_pnpmfile_flag_applies_to_config() { assert!(config.ignore_pnpmfile, "flag present → config set"); } +#[test] +fn ignore_scripts_flags_apply_to_config() { + let mut config = Config::default(); + assert!(!config.ignore_scripts); + + config.ignore_scripts = true; + update_args(&[]).apply_cli_config(&mut config); + assert!(config.ignore_scripts, "flags absent -> config unchanged"); + + config.ignore_scripts = false; + update_args(&["--ignore-scripts"]).apply_cli_config(&mut config); + assert!(config.ignore_scripts, "--ignore-scripts enables the setting"); + + update_args(&["--no-ignore-scripts"]).apply_cli_config(&mut config); + assert!(!config.ignore_scripts, "--no-ignore-scripts disables the setting"); +} + #[test] fn pnpr_server_flag_applies_to_config() { let mut config = Config::default(); diff --git a/pnpm/crates/cli/tests/suite/update.rs b/pnpm/crates/cli/tests/suite/update.rs index 765a904e38..7254ae98af 100644 --- a/pnpm/crates/cli/tests/suite/update.rs +++ b/pnpm/crates/cli/tests/suite/update.rs @@ -972,6 +972,28 @@ fn update_workspace_that_links_nothing_still_runs_project_scripts() { drop((root, anchor)); } +#[test] +fn update_ignore_scripts_skips_project_scripts() { + let root = TempDir::new().expect("create temp directory"); + let workspace = root.path().to_path_buf(); + + fs::write( + workspace.join("package.json"), + r#"{ "name": "test-update", "version": "1.0.0", + "scripts": { "postinstall": "node -e \"require('fs').writeFileSync('postinstall-ran', '')\"" } }"#, + ) + .expect("write package.json"); + + pacquet(&workspace, ["update", "--ignore-scripts"]).assert().success(); + + assert!( + !workspace.join("postinstall-ran").exists(), + "--ignore-scripts should skip the project's lifecycle scripts", + ); + + drop(root); +} + /// Naming a dependency that no workspace project publishes fails, since /// there is nothing to link it to. #[test] diff --git a/pnpm11/installing/commands/test/update/update.ts b/pnpm11/installing/commands/test/update/update.ts index b00169c46b..bc8d25853c 100644 --- a/pnpm11/installing/commands/test/update/update.ts +++ b/pnpm11/installing/commands/test/update/update.ts @@ -1,15 +1,22 @@ +import { execFile } from 'node:child_process' import path from 'node:path' +import { promisify } from 'node:util' import { beforeAll, describe, expect, it, test } from '@jest/globals' import type { PnpmError } from '@pnpm/error' import { install, update } from '@pnpm/installing.commands' import { prepare, preparePackages } from '@pnpm/prepare' -import { addDistTag } from '@pnpm/testing.registry-mock' +import { createTestIpcServer } from '@pnpm/test-ipc-server' +import { addDistTag, REGISTRY_MOCK_PORT } from '@pnpm/testing.registry-mock' import type { ProjectManifest } from '@pnpm/types' import { loadJsonFileSync } from 'load-json-file' import { DEFAULT_OPTS } from '../utils/index.js' +const execFileAsync = promisify(execFile) +const pnpmBin = path.join(import.meta.dirname, '../../../../pnpm/bin/pnpm.mjs') +const registry = `http://localhost:${REGISTRY_MOCK_PORT}/` + test.each([ { dependencies: ['is-positive'], options: { patches: true } }, { dependencies: [], options: { latest: true, patches: true } }, @@ -25,6 +32,42 @@ test.each([ }) }) +test('update ignores lifecycle scripts when --ignore-scripts is used', async () => { + await using server = await createTestIpcServer() + + const project = prepare({ + dependencies: { + '@pnpm.e2e/foo': '1.0.0', + }, + }) + + await execFileAsync(process.execPath, [ + pnpmBin, + 'install', + `--registry=${registry}`, + ]) + + project.writePackageJson({ + dependencies: { + '@pnpm.e2e/foo': '1.0.0', + }, + scripts: { + postinstall: server.sendLineScript('postinstall'), + }, + }) + + await execFileAsync(process.execPath, [ + pnpmBin, + 'update', + '@pnpm.e2e/foo@2.0.0', + '--ignore-scripts', + `--registry=${registry}`, + ]) + + expect(loadJsonFileSync('package.json').dependencies?.['@pnpm.e2e/foo']).toBe('2.0.0') + expect(server.getLines()).toStrictEqual([]) +}) + test('update with "*" pattern', async () => { await addDistTag({ package: '@pnpm.e2e/peer-a', version: '1.0.1', distTag: 'latest' }) await addDistTag({ package: '@pnpm.e2e/peer-c', version: '2.0.0', distTag: 'latest' })