@pnpm/installing.commands 1100.3.0 → 1100.4.0

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/add.js CHANGED
@@ -10,6 +10,7 @@ import { pick } from 'ramda';
10
10
  import { renderHelp } from 'render-help';
11
11
  import { getFetchFullMetadata } from './getFetchFullMetadata.js';
12
12
  import { installDeps } from './installDeps.js';
13
+ import { createGlobalPolicyCallbacks } from './resolutionPolicyManifest.js';
13
14
  export const shorthands = {
14
15
  'save-catalog': '--save-catalog-name=default',
15
16
  d: '--save-dev',
@@ -228,7 +229,10 @@ export async function handler(opts, params, commands) {
228
229
  if (params.includes('pnpm') || params.includes('@pnpm/exe')) {
229
230
  throw new PnpmError('GLOBAL_PNPM_INSTALL', 'Use the "pnpm self-update" command to install or update pnpm');
230
231
  }
231
- return handleGlobalAdd(opts, params, commands ?? {});
232
+ return handleGlobalAdd({
233
+ ...opts,
234
+ ...createGlobalPolicyCallbacks(opts),
235
+ }, params, commands ?? {});
232
236
  }
233
237
  const include = {
234
238
  dependencies: opts.production !== false,
@@ -19,6 +19,7 @@ import { getSaveType } from './getSaveType.js';
19
19
  import { handleIgnoredBuilds } from './handleIgnoredBuilds.js';
20
20
  import { setupPolicyHandlers } from './policyHandlers.js';
21
21
  import { createMatcher, makeIgnorePatterns, matchDependencies, recursive, } from './recursive.js';
22
+ import { makeRunPacquet } from './runPacquet.js';
22
23
  import { createWorkspaceSpecs, updateToWorkspacePackagesFromManifest } from './updateWorkspaceDependencies.js';
23
24
  const OVERWRITE_UPDATE_OPTIONS = {
24
25
  allowNew: true,
@@ -55,6 +56,29 @@ export async function installDeps(opts, params) {
55
56
  opts['preserveWorkspaceProtocol'] = !opts.linkWorkspacePackages;
56
57
  }
57
58
  const store = await createStoreController(opts);
59
+ // When `configDependencies` declares pacquet, build the alternative
60
+ // install engine the deps-installer delegates to. The CLI layer owns
61
+ // the construction so the installer doesn't need to know about
62
+ // pacquet's binary path, CLI surface, or any settings that only
63
+ // pacquet consumes. Threaded through both the workspace recursive
64
+ // path and the single-project path below. Two declaration names are
65
+ // accepted: the original unscoped `pacquet` and the official scoped
66
+ // `@pnpm/pacquet` mirror. Both packages ship the same JS shim and
67
+ // optional `@pacquet/<plat>-<arch>` binary sub-packages, so the
68
+ // resolved \`node_modules/.pnpm-config/<name>\` layout pacquet's
69
+ // wrapper expects is identical either way.
70
+ const pacquetConfigDepName = opts.configDependencies?.['@pnpm/pacquet'] != null
71
+ ? '@pnpm/pacquet'
72
+ : opts.configDependencies?.pacquet != null
73
+ ? 'pacquet'
74
+ : undefined;
75
+ const runPacquet = pacquetConfigDepName != null
76
+ ? makeRunPacquet({
77
+ lockfileDir: opts.lockfileDir ?? opts.dir,
78
+ packageName: pacquetConfigDepName,
79
+ argv: opts.argv.original,
80
+ })
81
+ : undefined;
58
82
  const includeDirect = opts.includeDirect ?? {
59
83
  dependencies: true,
60
84
  devDependencies: true,
@@ -95,6 +119,7 @@ export async function installDeps(opts, params) {
95
119
  selectedProjectsGraph,
96
120
  storeControllerAndDir: store,
97
121
  workspaceDir: opts.workspaceDir,
122
+ runPacquet,
98
123
  }, opts.update ? 'update' : (params.length === 0 ? 'install' : 'add'));
99
124
  return;
100
125
  }
@@ -138,6 +163,7 @@ export async function installDeps(opts, params) {
138
163
  workspacePackages,
139
164
  preferredVersions: opts.packageVulnerabilityAudit ? preferNonvulnerablePackageVersions(opts.packageVulnerabilityAudit) : undefined,
140
165
  handleResolutionPolicyViolations: policyHandlers?.handleResolutionPolicyViolations,
166
+ runPacquet,
141
167
  };
142
168
  let updateMatch;
143
169
  let updatePackageManifest = opts.updatePackageManifest;
@@ -273,6 +299,7 @@ export async function installDeps(opts, params) {
273
299
  allProjectsGraph: opts.allProjectsGraph,
274
300
  selectedProjectsGraph,
275
301
  workspaceDir: opts.workspaceDir, // Otherwise TypeScript doesn't understand that is not undefined
302
+ runPacquet,
276
303
  }, 'install');
277
304
  if (opts.ignoreScripts)
278
305
  return;
@@ -30,6 +30,13 @@ export type RecursiveOptions = CreateStoreControllerOptions & Pick<Config, 'bail
30
30
  resolutionVerifiers: ResolutionVerifier[];
31
31
  };
32
32
  pnpmfile: string[];
33
+ /**
34
+ * Alternative install engine (today: pacquet) the deps-installer
35
+ * delegates the materialization phase to. Built in `installDeps`
36
+ * when `configDependencies.pacquet` is declared, threaded through
37
+ * here so the recursive workspace path picks it up too.
38
+ */
39
+ runPacquet?: () => Promise<void>;
33
40
  } & Partial<Pick<Config, 'ci' | 'sort' | 'strictDepBuilds' | 'workspaceConcurrency'>> & Required<Pick<Config, 'workspaceDir'>>;
34
41
  export type CommandFullName = 'install' | 'add' | 'remove' | 'update' | 'import';
35
42
  export declare function recursive(allProjects: Project[], params: string[], opts: RecursiveOptions, cmdFullName: CommandFullName): Promise<boolean | string>;
@@ -0,0 +1,6 @@
1
+ import { type PolicyHandlersOptions, type PolicyViolation } from './policyHandlers.js';
2
+ export interface GlobalPolicyCallbacks {
3
+ handleResolutionPolicyViolations?: (violations: readonly PolicyViolation[]) => Promise<void>;
4
+ updateResolutionPolicyManifest?: (violations: readonly PolicyViolation[], dir: string) => Promise<void>;
5
+ }
6
+ export declare function createGlobalPolicyCallbacks(opts: PolicyHandlersOptions): GlobalPolicyCallbacks;
@@ -0,0 +1,17 @@
1
+ import { updateWorkspaceManifest } from '@pnpm/workspace.workspace-manifest-writer';
2
+ import { setupPolicyHandlers, } from './policyHandlers.js';
3
+ export function createGlobalPolicyCallbacks(opts) {
4
+ const policyHandlers = setupPolicyHandlers(opts);
5
+ if (policyHandlers == null)
6
+ return {};
7
+ return {
8
+ handleResolutionPolicyViolations: policyHandlers.handleResolutionPolicyViolations,
9
+ updateResolutionPolicyManifest: async (violations, dir) => {
10
+ const policyUpdates = policyHandlers.pickManifestUpdates(violations);
11
+ if (policyUpdates != null) {
12
+ await updateWorkspaceManifest(dir, policyUpdates);
13
+ }
14
+ },
15
+ };
16
+ }
17
+ //# sourceMappingURL=resolutionPolicyManifest.js.map
@@ -0,0 +1,48 @@
1
+ export interface MakeRunPacquetOpts {
2
+ lockfileDir: string;
3
+ /**
4
+ * Which `configDependencies` entry installed pacquet: either the
5
+ * original unscoped `pacquet` or the official scoped
6
+ * `@pnpm/pacquet` mirror. Drives the directory we look in under
7
+ * `node_modules/.pnpm-config/<packageName>/`. Both packages ship
8
+ * the same shim and the same `@pacquet/<plat>-<arch>` binary
9
+ * sub-packages, so the rest of the lookup is identical.
10
+ */
11
+ packageName: 'pacquet' | '@pnpm/pacquet';
12
+ /**
13
+ * The user's original `pnpm` argv (`process.argv.slice(2)`). Not
14
+ * forwarded to pacquet — we only inspect it to warn about flags
15
+ * pacquet won't see.
16
+ */
17
+ argv: string[];
18
+ }
19
+ /**
20
+ * Build the install-engine callback `mutateModules` invokes when
21
+ * `configDependencies` declares pacquet. Returns `undefined` when no
22
+ * pacquet binary is on disk — the caller falls back to the JS path in
23
+ * that case.
24
+ *
25
+ * The callback spawns the pacquet binary installed under
26
+ * `node_modules/.pnpm-config/pacquet` and forwards the user's own
27
+ * pnpm CLI flags. Pacquet's NDJSON stderr is parsed line-by-line and
28
+ * the valid JSON records are re-emitted on pnpm's global
29
+ * `streamParser` so `@pnpm/cli.default-reporter` renders pacquet's
30
+ * events the same way it renders pnpm's own. Non-JSON stderr lines
31
+ * (panic backtraces, unexpected diagnostics) are forwarded to the
32
+ * real stderr verbatim so they reach the user.
33
+ */
34
+ /** Args the deps-installer passes per pacquet invocation. */
35
+ export interface RunPacquetCallOpts {
36
+ /**
37
+ * `true` when pnpm has already run a lockfileOnly resolve pass and
38
+ * the reporter has already accumulated one `pnpm:progress
39
+ * status:resolved` per package. Pacquet's own `resolved` events
40
+ * (emitted for wire-format parity as it walks the lockfile) are
41
+ * dropped on the way back through the reader so the reporter
42
+ * doesn't double-count. The frozen-install path passes `false`:
43
+ * pnpm did no resolution there, so pacquet's events are the only
44
+ * source.
45
+ */
46
+ filterResolvedProgress?: boolean;
47
+ }
48
+ export declare function makeRunPacquet(opts: MakeRunPacquetOpts): (callOpts?: RunPacquetCallOpts) => Promise<void>;
@@ -0,0 +1,126 @@
1
+ import { spawn } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import { createRequire } from 'node:module';
4
+ import path from 'node:path';
5
+ import readline from 'node:readline';
6
+ import { PnpmError } from '@pnpm/error';
7
+ import { logger, streamParser } from '@pnpm/logger';
8
+ import chalk from 'chalk';
9
+ // The runtime `streamParser` is a `Transform` stream (split2 + JSON.parse).
10
+ // Its public typing only exposes `on`/`removeListener`, so we narrow to the
11
+ // writable side here to feed pacquet's NDJSON lines back through the same
12
+ // parser that `@pnpm/cli.default-reporter` listens on.
13
+ const streamParserWritable = streamParser;
14
+ export function makeRunPacquet(opts) {
15
+ return async (callOpts) => {
16
+ const pacquetBin = resolvePacquetBin(opts.lockfileDir, opts.packageName);
17
+ // Always the same fixed args. We don't forward pnpm's CLI flags
18
+ // even though pacquet's `install` subcommand mirrors most of them:
19
+ // pnpm has commands like `add` and `update` that carry flags
20
+ // pacquet's `install` doesn't recognize (e.g., `--save-dev`,
21
+ // `--save-peer`), and clap would reject them. The settings users
22
+ // care about live in `pnpm-workspace.yaml` / `.npmrc`, which
23
+ // pacquet reads on its own.
24
+ const args = ['--reporter=ndjson', 'install', '--frozen-lockfile'];
25
+ const droppedFlags = collectDroppedFlags(opts.argv);
26
+ if (droppedFlags.length > 0) {
27
+ logger.warn({
28
+ message: `The following CLI flags are not forwarded to pacquet and may not be honored: ${droppedFlags.join(' ')}. Move the equivalent settings into pnpm-workspace.yaml (or .npmrc for auth/registry) if pacquet needs them.`,
29
+ prefix: opts.lockfileDir,
30
+ });
31
+ }
32
+ // Banner so users can tell at a glance their install is going
33
+ // through the Rust engine rather than the JS path. Chalk is the
34
+ // same dependency the default reporter uses for the "+ pkg
35
+ // version" summary, so colorization respects the user's TTY
36
+ // settings consistently.
37
+ const banner = [
38
+ chalk.magentaBright('▶ Using pacquet for this install'),
39
+ chalk.gray(' pacquet is pnpm\'s Rust install engine (preview); declared in configDependencies.'),
40
+ ].join('\n');
41
+ logger.info({ message: banner, prefix: opts.lockfileDir });
42
+ const child = spawn(pacquetBin, args, {
43
+ cwd: opts.lockfileDir,
44
+ stdio: ['ignore', 'inherit', 'pipe'],
45
+ });
46
+ const filterResolved = callOpts?.filterResolvedProgress === true;
47
+ const rl = readline.createInterface({ input: child.stderr, crlfDelay: Infinity });
48
+ rl.on('line', (line) => {
49
+ if (!line)
50
+ return;
51
+ let parsed;
52
+ try {
53
+ parsed = JSON.parse(line);
54
+ }
55
+ catch {
56
+ process.stderr.write(`${line}\n`);
57
+ return;
58
+ }
59
+ if (filterResolved &&
60
+ typeof parsed === 'object' && parsed !== null &&
61
+ parsed.name === 'pnpm:progress' &&
62
+ parsed.status === 'resolved') {
63
+ return;
64
+ }
65
+ streamParserWritable.write(`${line}\n`);
66
+ });
67
+ await new Promise((resolve, reject) => {
68
+ child.once('error', reject);
69
+ child.once('close', (code) => {
70
+ rl.close();
71
+ if (code === 0) {
72
+ resolve();
73
+ return;
74
+ }
75
+ reject(new PnpmError('PACQUET_INSTALL_FAILED', `pacquet exited with code ${code ?? 'null'}`));
76
+ });
77
+ });
78
+ };
79
+ }
80
+ /**
81
+ * Path of the platform-specific native pacquet binary for the host. The
82
+ * pacquet npm package ships a Node wrapper at `bin/pacquet` that uses
83
+ * `require.resolve('@pacquet/<platform>-<arch>/pacquet[.exe]')` to find
84
+ * the binary — so the platform package lands as a *sibling* of pacquet,
85
+ * not inside its own `node_modules` (pacquet's own `node_modules` is
86
+ * empty after configDependencies install). Use Node's resolver rooted
87
+ * at pacquet's own `package.json` so we follow the same path the
88
+ * wrapper would have.
89
+ *
90
+ * The `realpathSync` is required: `.pnpm-config/pacquet` is a symlink
91
+ * into the global virtual store, and Node's `createRequire` builds its
92
+ * search paths from the *literal* ancestors of the path it's given —
93
+ * it won't follow the symlink up into the store dir where the
94
+ * `@pacquet/<plat>-<arch>` sibling actually lives.
95
+ */
96
+ function resolvePacquetBin(lockfileDir, packageName) {
97
+ const ext = process.platform === 'win32' ? '.exe' : '';
98
+ const pacquetPkg = fs.realpathSync(path.join(lockfileDir, 'node_modules/.pnpm-config', packageName, 'package.json'));
99
+ return createRequire(pacquetPkg).resolve(`@pacquet/${process.platform}-${process.arch}/pacquet${ext}`);
100
+ }
101
+ /**
102
+ * Pull the CLI flags out of pnpm's argv so we can warn about them
103
+ * before pacquet runs. We don't forward any of them — pacquet always
104
+ * gets `install --frozen-lockfile --reporter=ndjson` — but most are
105
+ * handled by pnpm itself before delegation (`--save-dev` rewrites
106
+ * `package.json`, `--filter` selects projects, etc.) so listing them
107
+ * to the user makes the "not forwarded" surface concrete.
108
+ *
109
+ * Flags we explicitly emit ourselves (`--frozen-lockfile`,
110
+ * `--reporter=ndjson`) are filtered out: they're honored, so warning
111
+ * about them would be misleading. `--config.*` is filtered too —
112
+ * those configure pnpm's runtime and aren't intended for the install
113
+ * engine.
114
+ */
115
+ function collectDroppedFlags(argv) {
116
+ return argv.filter((arg) => {
117
+ if (!arg.startsWith('-'))
118
+ return false;
119
+ if (arg === '--frozen-lockfile' || arg === '--reporter=ndjson')
120
+ return false;
121
+ if (arg.startsWith('--config.'))
122
+ return false;
123
+ return true;
124
+ });
125
+ }
126
+ //# sourceMappingURL=runPacquet.js.map
@@ -12,6 +12,7 @@ import { pick, pluck, unnest } from 'ramda';
12
12
  import { renderHelp } from 'render-help';
13
13
  import { installDeps } from '../installDeps.js';
14
14
  import { parseUpdateParam } from '../recursive.js';
15
+ import { createGlobalPolicyCallbacks } from '../resolutionPolicyManifest.js';
15
16
  import { getUpdateChoices } from './getUpdateChoices.js';
16
17
  export function rcOptionsTypes() {
17
18
  return pick([
@@ -151,7 +152,10 @@ export async function handler(opts, params = [], commands) {
151
152
  hint: 'Run "pnpm setup" to create it automatically, or set the global-bin-dir setting, or the PNPM_HOME env variable. The global bin directory should be in the PATH.',
152
153
  });
153
154
  }
154
- return handleGlobalUpdate(opts, params, commands ?? {});
155
+ return handleGlobalUpdate({
156
+ ...opts,
157
+ ...createGlobalPolicyCallbacks(opts),
158
+ }, params, commands ?? {});
155
159
  }
156
160
  const rebuildHandler = commands?.rebuild;
157
161
  if (opts.interactive) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/installing.commands",
3
- "version": "1100.3.0",
3
+ "version": "1100.4.0",
4
4
  "description": "Commands for installation",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -44,46 +44,46 @@
44
44
  "ramda": "npm:@pnpm/ramda@0.28.1",
45
45
  "render-help": "^2.0.0",
46
46
  "version-selector-type": "^3.0.0",
47
- "@pnpm/building.after-install": "1101.0.13",
47
+ "@pnpm/building.after-install": "1101.0.14",
48
48
  "@pnpm/catalogs.types": "1100.0.0",
49
- "@pnpm/cli.command": "1100.0.1",
50
49
  "@pnpm/cli.common-cli-options-help": "1100.0.1",
51
- "@pnpm/config.pick-registry-for-package": "1100.0.3",
50
+ "@pnpm/cli.utils": "1101.0.6",
51
+ "@pnpm/config.pick-registry-for-package": "1100.0.4",
52
52
  "@pnpm/config.matcher": "1100.0.1",
53
- "@pnpm/cli.utils": "1101.0.5",
54
- "@pnpm/config.writer": "1100.0.8",
55
- "@pnpm/deps.status": "1100.0.16",
53
+ "@pnpm/config.reader": "1101.3.3",
54
+ "@pnpm/cli.command": "1100.0.1",
55
+ "@pnpm/config.writer": "1100.0.9",
56
56
  "@pnpm/constants": "1100.0.0",
57
- "@pnpm/deps.path": "1100.0.3",
57
+ "@pnpm/deps.inspection.outdated": "1100.1.0",
58
+ "@pnpm/deps.path": "1100.0.4",
59
+ "@pnpm/deps.status": "1100.0.17",
60
+ "@pnpm/error": "1100.0.0",
58
61
  "@pnpm/fs.graceful-fs": "1100.1.0",
59
62
  "@pnpm/fs.read-modules-dir": "1100.0.1",
60
- "@pnpm/config.reader": "1101.3.2",
61
- "@pnpm/error": "1100.0.0",
62
- "@pnpm/installing.context": "1100.0.11",
63
- "@pnpm/global.commands": "1100.0.18",
64
- "@pnpm/hooks.pnpmfile": "1100.0.9",
65
- "@pnpm/installing.dedupe.check": "1100.0.6",
66
- "@pnpm/installing.deps-installer": "1101.2.0",
67
- "@pnpm/installing.env-installer": "1101.0.10",
68
- "@pnpm/lockfile.types": "1100.0.6",
69
- "@pnpm/pkg-manifest.reader": "1100.0.3",
63
+ "@pnpm/global.commands": "1100.0.19",
64
+ "@pnpm/hooks.pnpmfile": "1100.0.10",
65
+ "@pnpm/installing.context": "1100.0.12",
66
+ "@pnpm/installing.dedupe.check": "1100.0.7",
67
+ "@pnpm/installing.env-installer": "1101.1.0",
68
+ "@pnpm/installing.deps-installer": "1101.3.0",
69
+ "@pnpm/lockfile.types": "1100.0.7",
70
+ "@pnpm/pkg-manifest.utils": "1100.2.0",
71
+ "@pnpm/pkg-manifest.reader": "1100.0.4",
70
72
  "@pnpm/resolving.parse-wanted-dependency": "1100.0.1",
71
- "@pnpm/pkg-manifest.utils": "1100.1.4",
72
- "@pnpm/resolving.resolver-base": "1100.2.0",
73
- "@pnpm/types": "1101.1.0",
74
- "@pnpm/resolving.npm-resolver": "1101.2.0",
75
- "@pnpm/deps.inspection.outdated": "1100.0.16",
76
- "@pnpm/store.connection-manager": "1100.2.0",
77
- "@pnpm/workspace.project-manifest-reader": "1100.0.6",
78
- "@pnpm/workspace.projects-filter": "1100.0.12",
79
- "@pnpm/workspace.project-manifest-writer": "1100.0.3",
80
- "@pnpm/store.controller": "1101.0.7",
81
- "@pnpm/workspace.projects-graph": "1100.0.9",
82
- "@pnpm/workspace.projects-sorter": "1100.0.2",
83
- "@pnpm/workspace.projects-reader": "1101.0.5",
84
- "@pnpm/workspace.workspace-manifest-writer": "1100.0.8",
73
+ "@pnpm/resolving.resolver-base": "1100.3.0",
74
+ "@pnpm/store.connection-manager": "1100.2.1",
75
+ "@pnpm/resolving.npm-resolver": "1101.3.0",
76
+ "@pnpm/store.controller": "1101.0.8",
77
+ "@pnpm/types": "1101.1.1",
78
+ "@pnpm/workspace.projects-filter": "1100.0.13",
79
+ "@pnpm/workspace.projects-graph": "1100.0.10",
80
+ "@pnpm/workspace.project-manifest-reader": "1100.0.7",
81
+ "@pnpm/workspace.projects-sorter": "1100.0.3",
82
+ "@pnpm/workspace.projects-reader": "1101.0.6",
85
83
  "@pnpm/workspace.root-finder": "1100.0.1",
86
- "@pnpm/workspace.state": "1100.0.13"
84
+ "@pnpm/workspace.project-manifest-writer": "1100.0.4",
85
+ "@pnpm/workspace.state": "1100.0.14",
86
+ "@pnpm/workspace.workspace-manifest-writer": "1100.0.9"
87
87
  },
88
88
  "peerDependencies": {
89
89
  "@pnpm/logger": ">=1001.0.0 <1002.0.0"
@@ -106,18 +106,18 @@
106
106
  "write-json-file": "^7.0.0",
107
107
  "write-package": "7.2.0",
108
108
  "write-yaml-file": "^6.0.0",
109
- "@pnpm/assert-project": "1100.0.9",
110
- "@pnpm/prepare": "1100.0.9",
111
- "@pnpm/installing.commands": "1100.3.0",
112
- "@pnpm/test-fixtures": "1100.0.0",
109
+ "@pnpm/assert-project": "1100.0.10",
110
+ "@pnpm/installing.commands": "1100.4.0",
111
+ "@pnpm/installing.modules-yaml": "1100.0.5",
112
+ "@pnpm/prepare": "1100.0.10",
113
113
  "@pnpm/logger": "1100.0.0",
114
+ "@pnpm/test-fixtures": "1100.0.0",
115
+ "@pnpm/store.index": "1100.1.0",
114
116
  "@pnpm/test-ipc-server": "1100.0.0",
117
+ "@pnpm/testing.mock-agent": "1100.0.6",
115
118
  "@pnpm/testing.command-defaults": "1100.0.1",
116
- "@pnpm/testing.mock-agent": "1100.0.5",
117
- "@pnpm/store.index": "1100.1.0",
118
- "@pnpm/worker": "1100.1.6",
119
- "@pnpm/workspace.projects-filter": "1100.0.12",
120
- "@pnpm/installing.modules-yaml": "1100.0.4"
119
+ "@pnpm/worker": "1100.1.7",
120
+ "@pnpm/workspace.projects-filter": "1100.0.13"
121
121
  },
122
122
  "engines": {
123
123
  "node": ">=22.13"