@pnpm/exec.lifecycle 1100.1.7 → 1100.1.9
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 +28 -0
- package/lib/index.d.ts +11 -0
- package/lib/runLifecycleHook.d.ts +19 -0
- package/lib/runLifecycleHook.js +168 -0
- package/lib/runLifecycleHooksConcurrently.d.ts +18 -0
- package/lib/runLifecycleHooksConcurrently.js +67 -0
- package/lib/trackChildProcess.d.ts +23 -0
- package/lib/trackChildProcess.js +72 -0
- package/package.json +13 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# @pnpm/lifecycle
|
|
2
2
|
|
|
3
|
+
## 1100.1.9
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies:
|
|
8
|
+
- @pnpm/bins.linker@1100.0.23
|
|
9
|
+
- @pnpm/core-loggers@1100.3.0
|
|
10
|
+
- @pnpm/fetching.directory-fetcher@1100.0.26
|
|
11
|
+
- @pnpm/pkg-manifest.reader@1100.0.13
|
|
12
|
+
- @pnpm/store.controller-types@1100.1.11
|
|
13
|
+
- @pnpm/types@1101.7.0
|
|
14
|
+
|
|
15
|
+
## 1100.1.8
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Republished every package: the tarballs published by the v11.13.1 through v11.16.0 releases were missing most of their compiled files due to a packing bug [#13164](https://github.com/pnpm/pnpm/issues/13164).
|
|
20
|
+
|
|
21
|
+
- Updated dependencies:
|
|
22
|
+
- @pnpm/bins.linker@1100.0.22
|
|
23
|
+
- @pnpm/core-loggers@1100.2.5
|
|
24
|
+
- @pnpm/error@1100.1.0
|
|
25
|
+
- @pnpm/fetching.directory-fetcher@1100.0.25
|
|
26
|
+
- @pnpm/pkg-manifest.reader@1100.0.12
|
|
27
|
+
- @pnpm/store.cafs-types@1100.0.2
|
|
28
|
+
- @pnpm/store.controller-types@1100.1.10
|
|
29
|
+
- @pnpm/types@1101.6.0
|
|
30
|
+
|
|
3
31
|
## 1100.1.7
|
|
4
32
|
|
|
5
33
|
### Patch Changes
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { runLifecycleHook, type RunLifecycleHookOptions } from './runLifecycleHook.js';
|
|
2
|
+
import { runLifecycleHooksConcurrently, type RunLifecycleHooksConcurrentlyOptions } from './runLifecycleHooksConcurrently.js';
|
|
3
|
+
import { killTrackedProcessTrees, type TrackableChildProcess, trackChildProcess } from './trackChildProcess.js';
|
|
4
|
+
export declare function makeNodeRequireOption(modulePath: string, env?: Record<string, string | undefined>): {
|
|
5
|
+
NODE_OPTIONS: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function makeNodePackageMapOption(packageMapPath: string, env?: Record<string, string | undefined>): {
|
|
8
|
+
NODE_OPTIONS: string;
|
|
9
|
+
};
|
|
10
|
+
export { killTrackedProcessTrees, runLifecycleHook, type RunLifecycleHookOptions, runLifecycleHooksConcurrently, type RunLifecycleHooksConcurrentlyOptions, type TrackableChildProcess, trackChildProcess, };
|
|
11
|
+
export declare function runPostinstallHooks(opts: RunLifecycleHookOptions): Promise<boolean>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { DependencyManifest, ProjectManifest } from '@pnpm/types';
|
|
2
|
+
export interface RunLifecycleHookOptions {
|
|
3
|
+
args?: string[];
|
|
4
|
+
depPath: string;
|
|
5
|
+
extraBinPaths?: string[];
|
|
6
|
+
extraEnv?: Record<string, string>;
|
|
7
|
+
initCwd?: string;
|
|
8
|
+
optional?: boolean;
|
|
9
|
+
pkgRoot: string;
|
|
10
|
+
rootModulesDir: string;
|
|
11
|
+
scriptShell?: string;
|
|
12
|
+
silent?: boolean;
|
|
13
|
+
scriptsPrependNodePath?: boolean | 'warn-only';
|
|
14
|
+
shellEmulator?: boolean;
|
|
15
|
+
stdio?: string;
|
|
16
|
+
unsafePerm: boolean;
|
|
17
|
+
userAgent?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function runLifecycleHook(stage: string, manifest: ProjectManifest | DependencyManifest, opts: RunLifecycleHookOptions): Promise<boolean>;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { lifecycleLogger } from '@pnpm/core-loggers';
|
|
4
|
+
import { PnpmError } from '@pnpm/error';
|
|
5
|
+
import { globalWarn } from '@pnpm/logger';
|
|
6
|
+
import { lifecycle } from '@pnpm/npm-lifecycle';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import isWindows from 'is-windows';
|
|
9
|
+
import { join as shellQuote } from 'shlex';
|
|
10
|
+
function noop() { } // eslint-disable-line:no-empty
|
|
11
|
+
export async function runLifecycleHook(stage, manifest, opts) {
|
|
12
|
+
const optional = opts.optional === true;
|
|
13
|
+
// To remediate CVE_2024_27980, Node.js does not allow .bat or .cmd files to
|
|
14
|
+
// be spawned without the "shell: true" option.
|
|
15
|
+
//
|
|
16
|
+
// https://nodejs.org/api/child_process.html#spawning-bat-and-cmd-files-on-windows
|
|
17
|
+
//
|
|
18
|
+
// Unfortunately, setting spawn's shell option also causes arguments to be
|
|
19
|
+
// evaluated before they're passed to the shell, resulting in a surprising
|
|
20
|
+
// behavior difference only with .bat/.cmd files.
|
|
21
|
+
//
|
|
22
|
+
// Instead of showing a "spawn EINVAL" error, let's throw a clearer error that
|
|
23
|
+
// this isn't supported.
|
|
24
|
+
//
|
|
25
|
+
// If this behavior needs to be supported in the future, the arguments would
|
|
26
|
+
// need to be escaped before they're passed to the .bat/.cmd file. For
|
|
27
|
+
// example, scripts such as "echo %PATH%" should be passed verbatim rather
|
|
28
|
+
// than expanded. This is difficult to do correctly. Other open source tools
|
|
29
|
+
// (e.g. Rust) attempted and introduced bugs. The Rust blog has a good
|
|
30
|
+
// high-level explanation of the same security vulnerability Node.js patched.
|
|
31
|
+
//
|
|
32
|
+
// https://blog.rust-lang.org/2024/04/09/cve-2024-24576.html#overview
|
|
33
|
+
//
|
|
34
|
+
// Note that npm (as of version 10.5.0) doesn't support setting script-shell
|
|
35
|
+
// to a .bat or .cmd file either.
|
|
36
|
+
if (opts.scriptShell != null && typeof opts.scriptShell === 'string' && isWindowsBatchFile(opts.scriptShell)) {
|
|
37
|
+
throw new PnpmError('ERR_PNPM_INVALID_SCRIPT_SHELL_WINDOWS', 'Cannot spawn .bat or .cmd as a script shell.', {
|
|
38
|
+
hint: `\
|
|
39
|
+
The pnpm-workspace.yaml scriptShell option was configured to a .bat or .cmd file. These cannot be used as a script shell reliably.
|
|
40
|
+
|
|
41
|
+
Please unset the scriptShell option, or configure it to a .exe instead.
|
|
42
|
+
`,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
const m = { _id: getId(manifest), ...manifest };
|
|
46
|
+
m.scripts = { ...m.scripts };
|
|
47
|
+
switch (stage) {
|
|
48
|
+
case 'start':
|
|
49
|
+
if (!m.scripts.start) {
|
|
50
|
+
if (!existsSync('server.js')) {
|
|
51
|
+
throw new PnpmError('NO_SCRIPT_OR_SERVER', 'Missing script start or file server.js');
|
|
52
|
+
}
|
|
53
|
+
m.scripts.start = 'node server.js';
|
|
54
|
+
}
|
|
55
|
+
break;
|
|
56
|
+
case 'install':
|
|
57
|
+
if (!m.scripts.install && !m.scripts.preinstall) {
|
|
58
|
+
checkBindingGyp(opts.pkgRoot, m.scripts);
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
if (opts.args?.length && m.scripts?.[stage]) {
|
|
63
|
+
// It is impossible to quote a command line argument that contains newline for Windows cmd.
|
|
64
|
+
const escapedArgs = isWindows()
|
|
65
|
+
? opts.args.map((arg) => JSON.stringify(arg)).join(' ')
|
|
66
|
+
: shellQuote(opts.args);
|
|
67
|
+
m.scripts[stage] = `${m.scripts[stage]} ${escapedArgs}`;
|
|
68
|
+
}
|
|
69
|
+
// This script is used to prevent the usage of npm or Yarn.
|
|
70
|
+
// It does nothing, when pnpm is used, so we may skip its execution.
|
|
71
|
+
if (m.scripts[stage] === 'npx only-allow pnpm' || !m.scripts[stage])
|
|
72
|
+
return false;
|
|
73
|
+
if (opts.stdio !== 'inherit') {
|
|
74
|
+
lifecycleLogger.debug({
|
|
75
|
+
depPath: opts.depPath,
|
|
76
|
+
optional,
|
|
77
|
+
script: m.scripts[stage],
|
|
78
|
+
stage,
|
|
79
|
+
wd: opts.pkgRoot,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
else if (!opts.silent) {
|
|
83
|
+
process.stderr.write(chalk.dim(`$ ${m.scripts[stage]}`) + '\n');
|
|
84
|
+
}
|
|
85
|
+
const logLevel = (opts.stdio !== 'inherit' || opts.silent)
|
|
86
|
+
? 'silent'
|
|
87
|
+
: undefined;
|
|
88
|
+
await lifecycle(m, stage, opts.pkgRoot, {
|
|
89
|
+
config: {},
|
|
90
|
+
dir: opts.rootModulesDir,
|
|
91
|
+
extraBinPaths: opts.extraBinPaths,
|
|
92
|
+
extraEnv: {
|
|
93
|
+
...opts.extraEnv,
|
|
94
|
+
INIT_CWD: opts.initCwd ?? process.cwd(),
|
|
95
|
+
PNPM_SCRIPT_SRC_DIR: opts.pkgRoot,
|
|
96
|
+
...(opts.userAgent ? { npm_config_user_agent: opts.userAgent } : {}),
|
|
97
|
+
},
|
|
98
|
+
log: {
|
|
99
|
+
clearProgress: noop,
|
|
100
|
+
info: noop,
|
|
101
|
+
level: logLevel,
|
|
102
|
+
pause: noop,
|
|
103
|
+
resume: noop,
|
|
104
|
+
showProgress: noop,
|
|
105
|
+
silly: npmLog,
|
|
106
|
+
verbose: npmLog,
|
|
107
|
+
warn: (...msg) => {
|
|
108
|
+
globalWarn(msg.join(' '));
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
runConcurrently: true,
|
|
112
|
+
scriptsPrependNodePath: opts.scriptsPrependNodePath,
|
|
113
|
+
scriptShell: opts.scriptShell,
|
|
114
|
+
shellEmulator: opts.shellEmulator,
|
|
115
|
+
stdio: opts.stdio ?? 'pipe',
|
|
116
|
+
unsafePerm: opts.unsafePerm,
|
|
117
|
+
});
|
|
118
|
+
return true;
|
|
119
|
+
function npmLog(prefix, logId, stdtype, line) {
|
|
120
|
+
switch (stdtype) {
|
|
121
|
+
case 'stdout':
|
|
122
|
+
case 'stderr':
|
|
123
|
+
lifecycleLogger.debug({
|
|
124
|
+
depPath: opts.depPath,
|
|
125
|
+
line: (line ?? 0).toString(),
|
|
126
|
+
stage,
|
|
127
|
+
stdio: stdtype,
|
|
128
|
+
wd: opts.pkgRoot,
|
|
129
|
+
});
|
|
130
|
+
return;
|
|
131
|
+
case 'Returned: code:': {
|
|
132
|
+
if (opts.stdio === 'inherit') {
|
|
133
|
+
// Preventing the pnpm reporter from overriding the project's script output
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const code = line ?? 1;
|
|
137
|
+
lifecycleLogger.debug({
|
|
138
|
+
depPath: opts.depPath,
|
|
139
|
+
exitCode: code,
|
|
140
|
+
optional,
|
|
141
|
+
stage,
|
|
142
|
+
wd: opts.pkgRoot,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Run node-gyp when binding.gyp is available. Only do this when there are no
|
|
150
|
+
* `install` and `preinstall` scripts (see `npm help scripts`).
|
|
151
|
+
*/
|
|
152
|
+
function checkBindingGyp(root, scripts) {
|
|
153
|
+
if (existsSync(path.join(root, 'binding.gyp'))) {
|
|
154
|
+
scripts.install = 'node-gyp rebuild';
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function getId(manifest) {
|
|
158
|
+
return `${manifest.name ?? ''}@${manifest.version ?? ''}`;
|
|
159
|
+
}
|
|
160
|
+
function isWindowsBatchFile(scriptShell) {
|
|
161
|
+
// Node.js performs a similar check to determine whether it should throw
|
|
162
|
+
// EINVAL when spawning a .cmd/.bat file.
|
|
163
|
+
//
|
|
164
|
+
// https://github.com/nodejs/node/commit/6627222409#diff-1e725bfa950eda4d4b5c0c00a2bb6be3e5b83d819872a1adf2ef87c658273903
|
|
165
|
+
const scriptShellLower = scriptShell.toLowerCase();
|
|
166
|
+
return isWindows() && (scriptShellLower.endsWith('.cmd') || scriptShellLower.endsWith('.bat'));
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=runLifecycleHook.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { StoreController } from '@pnpm/store.controller-types';
|
|
2
|
+
import type { ProjectManifest, ProjectRootDir } from '@pnpm/types';
|
|
3
|
+
import { type RunLifecycleHookOptions } from './runLifecycleHook.js';
|
|
4
|
+
export type RunLifecycleHooksConcurrentlyOptions = Omit<RunLifecycleHookOptions, 'depPath' | 'pkgRoot' | 'rootModulesDir'> & {
|
|
5
|
+
resolveSymlinksInInjectedDirs?: boolean;
|
|
6
|
+
storeController: StoreController;
|
|
7
|
+
extraNodePaths?: string[];
|
|
8
|
+
preferSymlinkedExecutables?: boolean;
|
|
9
|
+
};
|
|
10
|
+
export interface Importer {
|
|
11
|
+
buildIndex: number;
|
|
12
|
+
manifest: ProjectManifest;
|
|
13
|
+
rootDir: ProjectRootDir;
|
|
14
|
+
modulesDir: string;
|
|
15
|
+
stages?: string[];
|
|
16
|
+
targetDirs?: string[];
|
|
17
|
+
}
|
|
18
|
+
export declare function runLifecycleHooksConcurrently(stages: string[], importers: Importer[], childConcurrency: number, opts: RunLifecycleHooksConcurrentlyOptions): Promise<void>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { linkBins } from '@pnpm/bins.linker';
|
|
3
|
+
import { fetchFromDir } from '@pnpm/fetching.directory-fetcher';
|
|
4
|
+
import { logger } from '@pnpm/logger';
|
|
5
|
+
import { runGroups } from 'run-groups';
|
|
6
|
+
import { runLifecycleHook } from './runLifecycleHook.js';
|
|
7
|
+
export async function runLifecycleHooksConcurrently(stages, importers, childConcurrency, opts) {
|
|
8
|
+
const importersByBuildIndex = new Map();
|
|
9
|
+
for (const importer of importers) {
|
|
10
|
+
if (!importersByBuildIndex.has(importer.buildIndex)) {
|
|
11
|
+
importersByBuildIndex.set(importer.buildIndex, [importer]);
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
importersByBuildIndex.get(importer.buildIndex).push(importer);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const sortedBuildIndexes = Array.from(importersByBuildIndex.keys()).sort((a, b) => a - b);
|
|
18
|
+
const groups = sortedBuildIndexes.map((buildIndex) => {
|
|
19
|
+
const importers = importersByBuildIndex.get(buildIndex);
|
|
20
|
+
return importers.map(({ manifest, modulesDir, rootDir, stages: importerStages, targetDirs }) => async () => {
|
|
21
|
+
// We are linking the bin files, in case they were created by lifecycle scripts of other workspace packages.
|
|
22
|
+
await linkBins(modulesDir, path.join(modulesDir, '.bin'), {
|
|
23
|
+
extraNodePaths: opts.extraNodePaths,
|
|
24
|
+
allowExoticManifests: true,
|
|
25
|
+
preferSymlinkedExecutables: opts.preferSymlinkedExecutables,
|
|
26
|
+
projectManifest: manifest,
|
|
27
|
+
warn: (message) => {
|
|
28
|
+
logger.warn({ message, prefix: rootDir });
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
const runLifecycleHookOpts = {
|
|
32
|
+
...opts,
|
|
33
|
+
depPath: rootDir,
|
|
34
|
+
pkgRoot: rootDir,
|
|
35
|
+
rootModulesDir: modulesDir,
|
|
36
|
+
};
|
|
37
|
+
let isBuilt = false;
|
|
38
|
+
for (const stage of (importerStages ?? stages)) {
|
|
39
|
+
if (await runLifecycleHook(stage, manifest, runLifecycleHookOpts)) { // eslint-disable-line no-await-in-loop
|
|
40
|
+
isBuilt = true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (targetDirs == null || targetDirs.length === 0 || !isBuilt)
|
|
44
|
+
return;
|
|
45
|
+
// Re-import only the freshly-built source — fetchFromDir already
|
|
46
|
+
// excludes the source's node_modules/. `keepModulesDir: true` makes
|
|
47
|
+
// importIndexedDir skip the destructive makeEmptyDir fast path
|
|
48
|
+
// (#11088) and preserve the target's existing node_modules (bin
|
|
49
|
+
// symlinks + transitive deps from the initial install) via its
|
|
50
|
+
// staging/move path. Replaces the old scanDir-into-filesMap
|
|
51
|
+
// workaround (#4299) that the fast path then wiped, causing ENOENT
|
|
52
|
+
// on .bin/<tool>. Stays on storeController.importPackage so source
|
|
53
|
+
// files keep their hardlinks (no copy-loop).
|
|
54
|
+
const filesResponse = await fetchFromDir(rootDir, { resolveSymlinks: opts.resolveSymlinksInInjectedDirs });
|
|
55
|
+
await Promise.all(targetDirs.map(async (targetDir) => opts.storeController.importPackage(targetDir, {
|
|
56
|
+
filesResponse: {
|
|
57
|
+
resolvedFrom: 'local-dir',
|
|
58
|
+
...filesResponse,
|
|
59
|
+
},
|
|
60
|
+
force: false,
|
|
61
|
+
keepModulesDir: true,
|
|
62
|
+
})));
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
await runGroups(childConcurrency, groups);
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=runLifecycleHooksConcurrently.js.map
|
|
@@ -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.9",
|
|
4
4
|
"description": "Package lifecycle hook runner",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -29,16 +29,16 @@
|
|
|
29
29
|
"!*.map"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@pnpm/bins.linker": "1100.0.
|
|
33
|
-
"@pnpm/core-loggers": "1100.
|
|
34
|
-
"@pnpm/error": "1100.0
|
|
35
|
-
"@pnpm/fetching.directory-fetcher": "1100.0.
|
|
32
|
+
"@pnpm/bins.linker": "1100.0.23",
|
|
33
|
+
"@pnpm/core-loggers": "1100.3.0",
|
|
34
|
+
"@pnpm/error": "1100.1.0",
|
|
35
|
+
"@pnpm/fetching.directory-fetcher": "1100.0.26",
|
|
36
36
|
"@pnpm/npm-lifecycle": "^1100.0.0",
|
|
37
|
-
"@pnpm/pkg-manifest.reader": "1100.0.
|
|
38
|
-
"@pnpm/store.cafs-types": "1100.0.
|
|
39
|
-
"@pnpm/store.controller-types": "1100.1.
|
|
40
|
-
"@pnpm/types": "1101.
|
|
41
|
-
"chalk": "^
|
|
37
|
+
"@pnpm/pkg-manifest.reader": "1100.0.13",
|
|
38
|
+
"@pnpm/store.cafs-types": "1100.0.2",
|
|
39
|
+
"@pnpm/store.controller-types": "1100.1.11",
|
|
40
|
+
"@pnpm/types": "1101.7.0",
|
|
41
|
+
"chalk": "^6.0.0",
|
|
42
42
|
"is-windows": "^1.0.2",
|
|
43
43
|
"path-exists": "^5.0.0",
|
|
44
44
|
"run-groups": "^5.0.0",
|
|
@@ -49,10 +49,10 @@
|
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@jest/globals": "30.4.1",
|
|
52
|
-
"@pnpm/exec.lifecycle": "1100.1.
|
|
52
|
+
"@pnpm/exec.lifecycle": "1100.1.9",
|
|
53
53
|
"@pnpm/logger": "1100.0.0",
|
|
54
|
-
"@pnpm/prepare": "1100.0.
|
|
55
|
-
"@pnpm/test-fixtures": "1100.0.
|
|
54
|
+
"@pnpm/prepare": "1100.0.23",
|
|
55
|
+
"@pnpm/test-fixtures": "1100.0.1",
|
|
56
56
|
"@pnpm/test-ipc-server": "1100.0.0",
|
|
57
57
|
"@types/is-windows": "^1.0.2",
|
|
58
58
|
"@zkochan/rimraf": "^4.0.0",
|