From b8c0479ebe9f63a1ef89e99ecd78ce84b2fe51cf Mon Sep 17 00:00:00 2001 From: Isaac Levy Date: Sat, 16 Aug 2025 12:45:35 -0400 Subject: [PATCH] Restore npm SIGINT behavior (reverts #41) This reverts the behavioral change from #41. This change caused pnpm to improperly handle interactive shell Ctrl-C, trapping the received signal and also ignoring the abnormal exit of the script. This often results still in a quick exit (because the script has terminated), but with exit code 0. PR #41 resulted in incorrect behavior, which also diverges from npm (as well as `node --run`). As another problem, it made pnpm internally inconsistent as Ctrl-C results in non-zero in situations when lifecycles scripts are not actively running. Even in the context of pnpm run, a very quick Ctrl-C, prior to spawn, results in a non-zero pnpm exit. For additional discussion see: #51 Fixes: pnpm/pnpm#9626 --- index.js | 2 +- test/index.js | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 8078fe9..fdbd805 100644 --- a/index.js +++ b/index.js @@ -281,7 +281,7 @@ function runCmd_ (cmd, pkg, env, wd, opts, stage, unsafe, uid, gid, cb_) { proc.on('close', (code, signal) => { opts.log.silly('lifecycle', logid(pkg, stage), 'Returned: code:', code, ' signal:', signal) let err - if (signal && signal !== 'SIGINT') { + if (signal) { err = new PnpmError('CHILD_PROCESS_FAILED', `Command failed with signal "${signal}"`) process.kill(process.pid, signal) } else if (code) { diff --git a/test/index.js b/test/index.js index 2fdc5b1..387f00a 100644 --- a/test/index.js +++ b/test/index.js @@ -205,7 +205,7 @@ test('throw error signal kills child', async function (t) { stubProcessExit.restore() }) -test('no error on INT signal from child', async function (t) { +test('exit with error on INT signal from child', async function (t) { if (isWindows()) { // On Windows there is no way to get the INT signal return @@ -233,7 +233,7 @@ test('no error on INT signal from child', async function (t) { const dir = fixture const pkg = require(path.join(fixture, 'package.json')) - await t.resolves(async () => { + await t.rejects(async () => { await lifecycle(pkg, 'signal-int', fixture, { stdio: 'pipe', log, @@ -243,14 +243,15 @@ test('no error on INT signal from child', async function (t) { }) stubProcessExit.restore() + stubProcessExit.calledOnceWith(process.pid, 'SIGINT') t.ok( - !info.calledWithMatch( + info.calledWithMatch( 'lifecycle', 'undefined~signal-int:', 'Failed to exec signal-int script' ), - 'INT signal intercepted incorrectly' + 'INT signal not intercepted' ) t.ok(