@pnpm/exec.lifecycle 1100.1.2 → 1100.1.4

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/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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/exec.lifecycle",
3
- "version": "1100.1.2",
3
+ "version": "1100.1.4",
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.18",
33
+ "@pnpm/core-loggers": "1100.2.3",
34
+ "@pnpm/error": "1100.0.1",
35
+ "@pnpm/fetching.directory-fetcher": "1100.0.21",
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/core-loggers": "1100.2.1",
40
- "@pnpm/error": "1100.0.1",
41
- "@pnpm/fetching.directory-fetcher": "1100.0.19",
42
- "@pnpm/pkg-manifest.reader": "1100.0.9",
43
- "@pnpm/store.cafs-types": "1100.0.1",
44
- "@pnpm/store.controller-types": "1100.1.7",
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
- "@types/is-windows": "^1.0.2",
53
- "@zkochan/rimraf": "^4.0.0",
54
- "load-json-file": "^7.0.1",
55
- "@pnpm/exec.lifecycle": "1100.1.2",
52
+ "@pnpm/exec.lifecycle": "1100.1.4",
56
53
  "@pnpm/logger": "1100.0.0",
57
- "@pnpm/prepare": "1100.0.18",
54
+ "@pnpm/prepare": "1100.0.20",
55
+ "@pnpm/test-fixtures": "1100.0.0",
58
56
  "@pnpm/test-ipc-server": "1100.0.0",
59
- "@pnpm/test-fixtures": "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"
package/lib/index.d.ts DELETED
@@ -1,10 +0,0 @@
1
- import { runLifecycleHook, type RunLifecycleHookOptions } from './runLifecycleHook.js';
2
- import { runLifecycleHooksConcurrently, type RunLifecycleHooksConcurrentlyOptions } from './runLifecycleHooksConcurrently.js';
3
- export declare function makeNodeRequireOption(modulePath: string, env?: Record<string, string | undefined>): {
4
- NODE_OPTIONS: string;
5
- };
6
- export declare function makeNodePackageMapOption(packageMapPath: string, env?: Record<string, string | undefined>): {
7
- NODE_OPTIONS: string;
8
- };
9
- export { runLifecycleHook, type RunLifecycleHookOptions, runLifecycleHooksConcurrently, type RunLifecycleHooksConcurrentlyOptions, };
10
- export declare function runPostinstallHooks(opts: RunLifecycleHookOptions): Promise<boolean>;
@@ -1,19 +0,0 @@
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>;
@@ -1,168 +0,0 @@
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
@@ -1,18 +0,0 @@
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>;
@@ -1,67 +0,0 @@
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