@pnpm/exec.lifecycle 1100.1.1 → 1100.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1670 -0
- package/lib/index.d.ts +2 -1
- package/lib/index.js +2 -1
- package/lib/trackChildProcess.d.ts +23 -0
- package/lib/trackChildProcess.js +72 -0
- package/package.json +16 -16
package/lib/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { runLifecycleHook, type RunLifecycleHookOptions } from './runLifecycleHook.js';
|
|
2
2
|
import { runLifecycleHooksConcurrently, type RunLifecycleHooksConcurrentlyOptions } from './runLifecycleHooksConcurrently.js';
|
|
3
|
+
import { killTrackedProcessTrees, type TrackableChildProcess, trackChildProcess } from './trackChildProcess.js';
|
|
3
4
|
export declare function makeNodeRequireOption(modulePath: string, env?: Record<string, string | undefined>): {
|
|
4
5
|
NODE_OPTIONS: string;
|
|
5
6
|
};
|
|
6
7
|
export declare function makeNodePackageMapOption(packageMapPath: string, env?: Record<string, string | undefined>): {
|
|
7
8
|
NODE_OPTIONS: string;
|
|
8
9
|
};
|
|
9
|
-
export { runLifecycleHook, type RunLifecycleHookOptions, runLifecycleHooksConcurrently, type RunLifecycleHooksConcurrentlyOptions, };
|
|
10
|
+
export { killTrackedProcessTrees, runLifecycleHook, type RunLifecycleHookOptions, runLifecycleHooksConcurrently, type RunLifecycleHooksConcurrentlyOptions, type TrackableChildProcess, trackChildProcess, };
|
|
10
11
|
export declare function runPostinstallHooks(opts: RunLifecycleHookOptions): Promise<boolean>;
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { safeReadPackageJsonFromDir } from '@pnpm/pkg-manifest.reader';
|
|
2
2
|
import { runLifecycleHook } from './runLifecycleHook.js';
|
|
3
3
|
import { runLifecycleHooksConcurrently } from './runLifecycleHooksConcurrently.js';
|
|
4
|
+
import { killTrackedProcessTrees, trackChildProcess } from './trackChildProcess.js';
|
|
4
5
|
export function makeNodeRequireOption(modulePath, env) {
|
|
5
6
|
let { NODE_OPTIONS } = env ?? process.env;
|
|
6
7
|
NODE_OPTIONS = `${NODE_OPTIONS ?? process.env.NODE_OPTIONS ?? ''} --require=${quotePathIfNeeded(modulePath)}`.trim();
|
|
@@ -30,7 +31,7 @@ function removeNodePackageMapOption(nodeOptions) {
|
|
|
30
31
|
.replace(/(?:^|\s)--experimental-package-map\s+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\S+)/g, '')
|
|
31
32
|
.trim();
|
|
32
33
|
}
|
|
33
|
-
export { runLifecycleHook, runLifecycleHooksConcurrently, };
|
|
34
|
+
export { killTrackedProcessTrees, runLifecycleHook, runLifecycleHooksConcurrently, trackChildProcess, };
|
|
34
35
|
export async function runPostinstallHooks(opts) {
|
|
35
36
|
const pkg = await safeReadPackageJsonFromDir(opts.pkgRoot);
|
|
36
37
|
if (pkg == null)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface TrackableChildProcess {
|
|
2
|
+
pid?: number;
|
|
3
|
+
once: (event: 'close' | 'error', listener: () => void) => unknown;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Registers a child process spawned for a user command so that
|
|
7
|
+
* killTrackedProcessTrees() can terminate its process tree if pnpm exits
|
|
8
|
+
* while the command is still running.
|
|
9
|
+
*/
|
|
10
|
+
export declare function trackChildProcess(child: TrackableChildProcess): void;
|
|
11
|
+
/**
|
|
12
|
+
* Kills the process trees of the still-running child processes registered
|
|
13
|
+
* with trackChildProcess(). Best-effort: children that exited concurrently
|
|
14
|
+
* are skipped silently.
|
|
15
|
+
*
|
|
16
|
+
* On Windows the tree is killed with `taskkill /T`, which terminates every
|
|
17
|
+
* descendant of a known PID without enumerating the system process list (an
|
|
18
|
+
* enumeration needs `wmic` or PowerShell there and can take tens of seconds).
|
|
19
|
+
* On POSIX only the tracked child itself is signalled; descendants are
|
|
20
|
+
* expected to be handled by the caller (the error handler enumerates them
|
|
21
|
+
* cheaply with one `ps` call).
|
|
22
|
+
*/
|
|
23
|
+
export declare function killTrackedProcessTrees(): Promise<void>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
const trackedChildPids = new Set();
|
|
4
|
+
/**
|
|
5
|
+
* Registers a child process spawned for a user command so that
|
|
6
|
+
* killTrackedProcessTrees() can terminate its process tree if pnpm exits
|
|
7
|
+
* while the command is still running.
|
|
8
|
+
*/
|
|
9
|
+
export function trackChildProcess(child) {
|
|
10
|
+
const pid = child.pid;
|
|
11
|
+
if (pid == null)
|
|
12
|
+
return;
|
|
13
|
+
trackedChildPids.add(pid);
|
|
14
|
+
const untrack = () => {
|
|
15
|
+
trackedChildPids.delete(pid);
|
|
16
|
+
};
|
|
17
|
+
child.once('close', untrack);
|
|
18
|
+
child.once('error', untrack);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Kills the process trees of the still-running child processes registered
|
|
22
|
+
* with trackChildProcess(). Best-effort: children that exited concurrently
|
|
23
|
+
* are skipped silently.
|
|
24
|
+
*
|
|
25
|
+
* On Windows the tree is killed with `taskkill /T`, which terminates every
|
|
26
|
+
* descendant of a known PID without enumerating the system process list (an
|
|
27
|
+
* enumeration needs `wmic` or PowerShell there and can take tens of seconds).
|
|
28
|
+
* On POSIX only the tracked child itself is signalled; descendants are
|
|
29
|
+
* expected to be handled by the caller (the error handler enumerates them
|
|
30
|
+
* cheaply with one `ps` call).
|
|
31
|
+
*/
|
|
32
|
+
export async function killTrackedProcessTrees() {
|
|
33
|
+
await Promise.all(Array.from(trackedChildPids, killProcessTree));
|
|
34
|
+
}
|
|
35
|
+
async function killProcessTree(pid) {
|
|
36
|
+
if (process.platform === 'win32') {
|
|
37
|
+
// Resolve taskkill to its absolute System32 location so a taskkill.exe
|
|
38
|
+
// planted in the current directory or on PATH can't be run in its place
|
|
39
|
+
// during error cleanup. `process.env` is case-insensitive on Windows, so
|
|
40
|
+
// `SystemRoot` also matches the SYSTEMROOT/systemroot spellings.
|
|
41
|
+
const taskkillPath = path.join(process.env.SystemRoot ?? process.env.windir ?? 'C:\\Windows', 'System32', 'taskkill.exe');
|
|
42
|
+
await new Promise((resolve) => {
|
|
43
|
+
const taskkill = spawn(taskkillPath, ['/pid', pid.toString(), '/T', '/F'], { stdio: 'ignore', windowsHide: true });
|
|
44
|
+
// pnpm is on its error-exit path and must not hang, so the wait is
|
|
45
|
+
// bounded by a timer that resolves even if taskkill never emits 'exit'
|
|
46
|
+
// or 'error' (and reaps a stuck taskkill). The kill is best-effort
|
|
47
|
+
// either way. The timer is unref'd so it can't keep the process alive.
|
|
48
|
+
const timer = setTimeout(() => {
|
|
49
|
+
taskkill.kill();
|
|
50
|
+
resolve();
|
|
51
|
+
}, 10_000);
|
|
52
|
+
timer.unref();
|
|
53
|
+
const done = () => {
|
|
54
|
+
clearTimeout(timer);
|
|
55
|
+
resolve();
|
|
56
|
+
};
|
|
57
|
+
// A non-zero exit code (128 when the process is already gone, 1 when
|
|
58
|
+
// access is denied) is deliberately ignored.
|
|
59
|
+
taskkill.once('error', done);
|
|
60
|
+
taskkill.once('exit', done);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
try {
|
|
65
|
+
process.kill(pid);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// the process exited before it could be signalled
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=trackChildProcess.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pnpm/exec.lifecycle",
|
|
3
|
-
"version": "1100.1.
|
|
3
|
+
"version": "1100.1.3",
|
|
4
4
|
"description": "Package lifecycle hook runner",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -29,34 +29,34 @@
|
|
|
29
29
|
"!*.map"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
+
"@pnpm/bins.linker": "1100.0.17",
|
|
33
|
+
"@pnpm/core-loggers": "1100.2.2",
|
|
34
|
+
"@pnpm/error": "1100.0.1",
|
|
35
|
+
"@pnpm/fetching.directory-fetcher": "1100.0.20",
|
|
32
36
|
"@pnpm/npm-lifecycle": "^1100.0.0",
|
|
37
|
+
"@pnpm/pkg-manifest.reader": "1100.0.10",
|
|
38
|
+
"@pnpm/store.cafs-types": "1100.0.1",
|
|
39
|
+
"@pnpm/store.controller-types": "1100.1.8",
|
|
40
|
+
"@pnpm/types": "1101.4.0",
|
|
33
41
|
"chalk": "^5.6.2",
|
|
34
42
|
"is-windows": "^1.0.2",
|
|
35
43
|
"path-exists": "^5.0.0",
|
|
36
44
|
"run-groups": "^5.0.0",
|
|
37
|
-
"shlex": "^3.0.0"
|
|
38
|
-
"@pnpm/bins.linker": "1100.0.16",
|
|
39
|
-
"@pnpm/error": "1100.0.1",
|
|
40
|
-
"@pnpm/core-loggers": "1100.2.1",
|
|
41
|
-
"@pnpm/store.cafs-types": "1100.0.1",
|
|
42
|
-
"@pnpm/pkg-manifest.reader": "1100.0.9",
|
|
43
|
-
"@pnpm/store.controller-types": "1100.1.6",
|
|
44
|
-
"@pnpm/fetching.directory-fetcher": "1100.0.18",
|
|
45
|
-
"@pnpm/types": "1101.3.2"
|
|
45
|
+
"shlex": "^3.0.0"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"@pnpm/logger": "^1100.0.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@jest/globals": "30.4.1",
|
|
52
|
-
"@
|
|
53
|
-
"@zkochan/rimraf": "^4.0.0",
|
|
54
|
-
"load-json-file": "^7.0.1",
|
|
52
|
+
"@pnpm/exec.lifecycle": "1100.1.3",
|
|
55
53
|
"@pnpm/logger": "1100.0.0",
|
|
56
|
-
"@pnpm/
|
|
57
|
-
"@pnpm/test-ipc-server": "1100.0.0",
|
|
54
|
+
"@pnpm/prepare": "1100.0.19",
|
|
58
55
|
"@pnpm/test-fixtures": "1100.0.0",
|
|
59
|
-
"@pnpm/
|
|
56
|
+
"@pnpm/test-ipc-server": "1100.0.0",
|
|
57
|
+
"@types/is-windows": "^1.0.2",
|
|
58
|
+
"@zkochan/rimraf": "^4.0.0",
|
|
59
|
+
"load-json-file": "^7.0.1"
|
|
60
60
|
},
|
|
61
61
|
"engines": {
|
|
62
62
|
"node": ">=22.13"
|