Skip to content

Commit ac37843

Browse files
committed
fix: process exit race condition and Windows signal handling
Remove preemptive process.exitCode=1 that caused wrong exit codes. Add Windows-specific signal handling for SIGTERM/SIGINT. Guard against deleted directory with cwd fallback logic.
1 parent d64be3e commit ac37843

File tree

3 files changed

+146
-61
lines changed

3 files changed

+146
-61
lines changed

src/npm-cli.mts

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,57 @@
33
/** @fileoverview npm CLI wrapper entry point. */
44

55
void (async () => {
6-
process.exitCode = 1
6+
try {
7+
// Use require to load from built dist path to avoid creating shadow-npm-bin files.
8+
const shadowNpmBin = require('../dist/shadow-npm-bin.js')
79

8-
// Use require to load from built dist path to avoid creating shadow-npm-bin files.
9-
const shadowNpmBin = require('../dist/shadow-npm-bin.js')
10+
// Safely get current working directory, fall back if deleted
11+
let cwd
12+
try {
13+
cwd = process.cwd()
14+
} catch {
15+
// If cwd is deleted, use home or temp directory
16+
const os = require('node:os')
17+
cwd = process.env['HOME'] || process.env['USERPROFILE'] || os.tmpdir()
18+
}
1019

11-
const { spawnPromise } = await shadowNpmBin(process.argv.slice(2), {
12-
stdio: 'inherit',
13-
cwd: process.cwd(),
14-
env: { ...process.env },
15-
})
20+
const { spawnPromise } = await shadowNpmBin(process.argv.slice(2), {
21+
stdio: 'inherit',
22+
cwd,
23+
env: { ...process.env },
24+
})
1625

17-
// See https://nodejs.org/api/child_process.html#event-exit.
18-
spawnPromise.process.on(
19-
'exit',
20-
(code: number | null, signalName: NodeJS.Signals | null) => {
21-
if (signalName) {
22-
process.kill(process.pid, signalName)
23-
} else if (typeof code === 'number') {
24-
// eslint-disable-next-line n/no-process-exit
25-
process.exit(code)
26-
}
27-
},
28-
)
26+
// See https://nodejs.org/api/child_process.html#event-exit.
27+
spawnPromise.process.on(
28+
'exit',
29+
(code: number | null, signalName: NodeJS.Signals | null) => {
30+
if (signalName) {
31+
// On Windows, only certain signals are supported
32+
// Fallback to exit code 128 + signal number for unsupported signals
33+
if (process.platform === 'win32') {
34+
// Windows supports SIGTERM and SIGINT but not others like SIGHUP
35+
if (signalName === 'SIGTERM' || signalName === 'SIGINT') {
36+
process.kill(process.pid, signalName)
37+
} else {
38+
// Use conventional exit code for signal termination
39+
// Default to SIGTERM-like exit code
40+
// eslint-disable-next-line n/no-process-exit
41+
process.exit(128 + 15)
42+
}
43+
} else {
44+
process.kill(process.pid, signalName)
45+
}
46+
} else if (typeof code === 'number') {
47+
// eslint-disable-next-line n/no-process-exit
48+
process.exit(code)
49+
}
50+
},
51+
)
2952

30-
await spawnPromise
53+
await spawnPromise
54+
} catch (error) {
55+
// Only set exit code on actual failure
56+
process.exitCode = 1
57+
throw error
58+
}
3159
})()

src/npx-cli.mts

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,56 @@
33
/** @fileoverview npx CLI wrapper entry point. */
44

55
void (async () => {
6-
process.exitCode = 1
6+
try {
7+
// Use require to load from built dist path to avoid creating shadow-npx-bin files.
8+
const shadowNpxBin = require('../dist/shadow-npx-bin.js')
79

8-
// Use require to load from built dist path to avoid creating shadow-npx-bin files.
9-
const shadowNpxBin = require('../dist/shadow-npx-bin.js')
10+
// Safely get current working directory, fall back if deleted
11+
let cwd
12+
try {
13+
cwd = process.cwd()
14+
} catch {
15+
// If cwd is deleted, use home or temp directory
16+
const os = require('node:os')
17+
cwd = process.env['HOME'] || process.env['USERPROFILE'] || os.tmpdir()
18+
}
1019

11-
const { spawnPromise } = await shadowNpxBin(process.argv.slice(2), {
12-
stdio: 'inherit',
13-
})
20+
const { spawnPromise } = await shadowNpxBin(process.argv.slice(2), {
21+
stdio: 'inherit',
22+
cwd,
23+
})
1424

15-
// See https://nodejs.org/api/child_process.html#event-exit.
16-
spawnPromise.process.on(
17-
'exit',
18-
(code: number | null, signalName: NodeJS.Signals | null) => {
19-
if (signalName) {
20-
process.kill(process.pid, signalName)
21-
} else if (typeof code === 'number') {
22-
// eslint-disable-next-line n/no-process-exit
23-
process.exit(code)
24-
}
25-
},
26-
)
25+
// See https://nodejs.org/api/child_process.html#event-exit.
26+
spawnPromise.process.on(
27+
'exit',
28+
(code: number | null, signalName: NodeJS.Signals | null) => {
29+
if (signalName) {
30+
// On Windows, only certain signals are supported
31+
// Fallback to exit code 128 + signal number for unsupported signals
32+
if (process.platform === 'win32') {
33+
// Windows supports SIGTERM and SIGINT but not others like SIGHUP
34+
if (signalName === 'SIGTERM' || signalName === 'SIGINT') {
35+
process.kill(process.pid, signalName)
36+
} else {
37+
// Use conventional exit code for signal termination
38+
// Default to SIGTERM-like exit code
39+
// eslint-disable-next-line n/no-process-exit
40+
process.exit(128 + 15)
41+
}
42+
} else {
43+
process.kill(process.pid, signalName)
44+
}
45+
} else if (typeof code === 'number') {
46+
// eslint-disable-next-line n/no-process-exit
47+
process.exit(code)
48+
}
49+
},
50+
)
2751

28-
await spawnPromise
52+
await spawnPromise
53+
} catch (error) {
54+
// Only set exit code on actual failure
55+
process.exitCode = 1
56+
throw error
57+
}
2958
})()

src/pnpm-cli.mts

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,57 @@
33
/** @fileoverview pnpm CLI wrapper entry point. */
44

55
void (async () => {
6-
process.exitCode = 1
6+
try {
7+
// Use require to load from built dist path to avoid creating shadow-pnpm-bin files.
8+
const shadowPnpmBin = require('../dist/shadow-pnpm-bin.js')
79

8-
// Use require to load from built dist path to avoid creating shadow-pnpm-bin files.
9-
const shadowPnpmBin = require('../dist/shadow-pnpm-bin.js')
10+
// Safely get current working directory, fall back if deleted
11+
let cwd
12+
try {
13+
cwd = process.cwd()
14+
} catch {
15+
// If cwd is deleted, use home or temp directory
16+
const os = require('node:os')
17+
cwd = process.env['HOME'] || process.env['USERPROFILE'] || os.tmpdir()
18+
}
1019

11-
const { spawnPromise } = await shadowPnpmBin(process.argv.slice(2), {
12-
stdio: 'inherit',
13-
cwd: process.cwd(),
14-
env: { ...process.env },
15-
})
20+
const { spawnPromise } = await shadowPnpmBin(process.argv.slice(2), {
21+
stdio: 'inherit',
22+
cwd,
23+
env: { ...process.env },
24+
})
1625

17-
// See https://nodejs.org/api/child_process.html#event-exit.
18-
spawnPromise.process.on(
19-
'exit',
20-
(code: number | null, signalName: NodeJS.Signals | null) => {
21-
if (signalName) {
22-
process.kill(process.pid, signalName)
23-
} else if (typeof code === 'number') {
24-
// eslint-disable-next-line n/no-process-exit
25-
process.exit(code)
26-
}
27-
},
28-
)
26+
// See https://nodejs.org/api/child_process.html#event-exit.
27+
spawnPromise.process.on(
28+
'exit',
29+
(code: number | null, signalName: NodeJS.Signals | null) => {
30+
if (signalName) {
31+
// On Windows, only certain signals are supported
32+
// Fallback to exit code 128 + signal number for unsupported signals
33+
if (process.platform === 'win32') {
34+
// Windows supports SIGTERM and SIGINT but not others like SIGHUP
35+
if (signalName === 'SIGTERM' || signalName === 'SIGINT') {
36+
process.kill(process.pid, signalName)
37+
} else {
38+
// Use conventional exit code for signal termination
39+
// Default to SIGTERM-like exit code
40+
// eslint-disable-next-line n/no-process-exit
41+
process.exit(128 + 15)
42+
}
43+
} else {
44+
process.kill(process.pid, signalName)
45+
}
46+
} else if (typeof code === 'number') {
47+
// eslint-disable-next-line n/no-process-exit
48+
process.exit(code)
49+
}
50+
},
51+
)
2952

30-
await spawnPromise
53+
await spawnPromise
54+
} catch (error) {
55+
// Only set exit code on actual failure
56+
process.exitCode = 1
57+
throw error
58+
}
3159
})()

0 commit comments

Comments
 (0)