@pnpm/installing.commands 1100.2.2 → 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,
package/lib/fetch.js CHANGED
@@ -58,6 +58,7 @@ export async function handler(opts) {
58
58
  pruneStore: true,
59
59
  storeController: store.ctrl,
60
60
  storeDir: store.dir,
61
+ resolutionVerifiers: store.resolutionVerifiers,
61
62
  // Hoisting is skipped anyway,
62
63
  // so we store these empty patterns in node_modules/.modules.yaml
63
64
  // to let the subsequent install know that hoisting should be performed.
@@ -104,6 +104,7 @@ export async function handler(opts, params) {
104
104
  preferredVersions,
105
105
  storeController: store.ctrl,
106
106
  storeDir: store.dir,
107
+ resolutionVerifiers: store.resolutionVerifiers,
107
108
  };
108
109
  await install(manifest, installOpts);
109
110
  }
package/lib/install.js CHANGED
@@ -79,6 +79,11 @@ export const cliOptionsTypes = () => ({
79
79
  'fix-lockfile': Boolean,
80
80
  'resolution-only': Boolean,
81
81
  recursive: Boolean,
82
+ // `--no-save` lets `pnpm install` skip writing to package.json /
83
+ // pnpm-workspace.yaml. Without registering it here, nopt drops the
84
+ // flag, `opts.save` stays undefined, and the auto-add path treats
85
+ // it as "save enabled".
86
+ save: Boolean,
82
87
  });
83
88
  export const shorthands = {
84
89
  D: '--dev',
@@ -4,7 +4,7 @@ import { type UpdateMatchingFunction } from '@pnpm/installing.deps-installer';
4
4
  import type { LockfileObject } from '@pnpm/lockfile.types';
5
5
  import { type CreateStoreControllerOptions } from '@pnpm/store.connection-manager';
6
6
  import type { IncludedDependencies, PackageVulnerabilityAudit } from '@pnpm/types';
7
- export type InstallDepsOptions = Pick<Config, 'autoInstallPeers' | 'bail' | 'bin' | 'catalogs' | 'catalogMode' | 'cleanupUnusedCatalogs' | 'dedupePeerDependents' | 'dedupePeers' | 'depth' | 'dev' | 'enableGlobalVirtualStore' | 'virtualStoreOnly' | 'engineStrict' | 'excludeLinksFromLockfile' | 'global' | 'globalPnpmfile' | 'ignoreCurrentSpecifiers' | 'ignorePnpmfile' | 'ignoreScripts' | 'optimisticRepeatInstall' | 'linkWorkspacePackages' | 'lockfileDir' | 'lockfileOnly' | 'agent' | 'production' | 'preferWorkspacePackages' | 'registries' | 'runtime' | 'runtimeOnFail' | 'save' | 'saveDev' | 'saveExact' | 'saveOptional' | 'savePeer' | 'savePrefix' | 'saveProd' | 'saveWorkspaceProtocol' | 'lockfileIncludeTarballUrl' | 'scriptsPrependNodePath' | 'scriptShell' | 'sideEffectsCache' | 'sideEffectsCacheReadonly' | 'sort' | 'sharedWorkspaceLockfile' | 'shellEmulator' | 'tag' | 'allowBuilds' | 'optional' | 'workspaceConcurrency' | 'workspaceDir' | 'workspacePackagePatterns' | 'extraEnv' | 'ignoreWorkspaceCycles' | 'disallowWorkspaceCycles' | 'configDependencies' | 'packageExtensions' | 'updateConfig'> & Pick<ConfigContext, 'allProjects' | 'allProjectsGraph' | 'cliOptions' | 'hooks' | 'rootProjectManifestDir' | 'rootProjectManifest' | 'selectedProjectsGraph'> & CreateStoreControllerOptions & {
7
+ export type InstallDepsOptions = Pick<Config, 'autoInstallPeers' | 'bail' | 'bin' | 'catalogs' | 'catalogMode' | 'cleanupUnusedCatalogs' | 'dedupePeerDependents' | 'dedupePeers' | 'depth' | 'dev' | 'enableGlobalVirtualStore' | 'virtualStoreOnly' | 'engineStrict' | 'excludeLinksFromLockfile' | 'global' | 'globalPnpmfile' | 'ignoreCurrentSpecifiers' | 'ignorePnpmfile' | 'ignoreScripts' | 'optimisticRepeatInstall' | 'linkWorkspacePackages' | 'lockfileDir' | 'lockfileOnly' | 'agent' | 'production' | 'preferWorkspacePackages' | 'registries' | 'runtime' | 'runtimeOnFail' | 'save' | 'saveDev' | 'saveExact' | 'saveOptional' | 'savePeer' | 'savePrefix' | 'saveProd' | 'saveWorkspaceProtocol' | 'lockfileIncludeTarballUrl' | 'scriptsPrependNodePath' | 'scriptShell' | 'sideEffectsCache' | 'sideEffectsCacheReadonly' | 'sort' | 'sharedWorkspaceLockfile' | 'shellEmulator' | 'tag' | 'allowBuilds' | 'optional' | 'workspaceConcurrency' | 'workspaceDir' | 'workspacePackagePatterns' | 'extraEnv' | 'ignoreWorkspaceCycles' | 'disallowWorkspaceCycles' | 'configDependencies' | 'packageExtensions' | 'updateConfig'> & Pick<ConfigContext, 'allProjects' | 'allProjectsGraph' | 'cliOptions' | 'hooks' | 'rootProjectManifestDir' | 'rootProjectManifest' | 'selectedProjectsGraph'> & Partial<Pick<Config, 'ci'>> & CreateStoreControllerOptions & {
8
8
  argv: {
9
9
  original: string[];
10
10
  };
@@ -17,7 +17,9 @@ import { updateWorkspaceManifest } from '@pnpm/workspace.workspace-manifest-writ
17
17
  import { getPinnedVersion } from './getPinnedVersion.js';
18
18
  import { getSaveType } from './getSaveType.js';
19
19
  import { handleIgnoredBuilds } from './handleIgnoredBuilds.js';
20
+ import { setupPolicyHandlers } from './policyHandlers.js';
20
21
  import { createMatcher, makeIgnorePatterns, matchDependencies, recursive, } from './recursive.js';
22
+ import { makeRunPacquet } from './runPacquet.js';
21
23
  import { createWorkspaceSpecs, updateToWorkspacePackagesFromManifest } from './updateWorkspaceDependencies.js';
22
24
  const OVERWRITE_UPDATE_OPTIONS = {
23
25
  allowNew: true,
@@ -54,6 +56,29 @@ export async function installDeps(opts, params) {
54
56
  opts['preserveWorkspaceProtocol'] = !opts.linkWorkspacePackages;
55
57
  }
56
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;
57
82
  const includeDirect = opts.includeDirect ?? {
58
83
  dependencies: true,
59
84
  devDependencies: true,
@@ -94,6 +119,7 @@ export async function installDeps(opts, params) {
94
119
  selectedProjectsGraph,
95
120
  storeControllerAndDir: store,
96
121
  workspaceDir: opts.workspaceDir,
122
+ runPacquet,
97
123
  }, opts.update ? 'update' : (params.length === 0 ? 'install' : 'add'));
98
124
  return;
99
125
  }
@@ -115,6 +141,12 @@ export async function installDeps(opts, params) {
115
141
  else if (opts.runtimeOnFail) {
116
142
  applyRuntimeOnFailOverride(manifest, opts.runtimeOnFail);
117
143
  }
144
+ // `setupPolicyHandlers` composes the per-policy handlers the install
145
+ // needs for the current opts (today: minimumReleaseAge; future:
146
+ // trustPolicy UX, license policy, etc.). Returns `undefined` when no
147
+ // handler is active so the install skips the empty no-op call at
148
+ // every checkpoint when no policies are configured.
149
+ const policyHandlers = setupPolicyHandlers(opts);
118
150
  const installOpts = {
119
151
  ...opts,
120
152
  // In case installation is done in a multi-package repository
@@ -127,8 +159,11 @@ export async function installDeps(opts, params) {
127
159
  skipRuntimes: opts.runtime === false,
128
160
  storeController: store.ctrl,
129
161
  storeDir: store.dir,
162
+ resolutionVerifiers: store.resolutionVerifiers,
130
163
  workspacePackages,
131
164
  preferredVersions: opts.packageVulnerabilityAudit ? preferNonvulnerablePackageVersions(opts.packageVulnerabilityAudit) : undefined,
165
+ handleResolutionPolicyViolations: policyHandlers?.handleResolutionPolicyViolations,
166
+ runPacquet,
132
167
  };
133
168
  let updateMatch;
134
169
  let updatePackageManifest = opts.updatePackageManifest;
@@ -187,14 +222,20 @@ export async function installDeps(opts, params) {
187
222
  rootDir: opts.dir,
188
223
  targetDependenciesField: getSaveType(opts),
189
224
  };
190
- const { updatedCatalogs, updatedProject, ignoredBuilds } = await mutateModulesInSingleProject(mutatedProject, installOpts);
225
+ const { updatedCatalogs, updatedProject, ignoredBuilds, resolutionPolicyViolations } = await mutateModulesInSingleProject(mutatedProject, installOpts);
191
226
  if (opts.save !== false) {
227
+ // Only pick entries when we'll actually persist. Otherwise the
228
+ // info log would claim we added entries the workspace manifest
229
+ // never saw, and the next install would re-prompt or fail
230
+ // verification.
231
+ const policyUpdates = policyHandlers?.pickManifestUpdates(resolutionPolicyViolations);
192
232
  await Promise.all([
193
233
  writeProjectManifest(updatedProject.manifest),
194
234
  updateWorkspaceManifest(opts.workspaceDir ?? opts.dir, {
195
235
  updatedCatalogs,
196
236
  cleanupUnusedCatalogs: opts.cleanupUnusedCatalogs,
197
237
  allProjects: opts.allProjects,
238
+ ...policyUpdates,
198
239
  }),
199
240
  ]);
200
241
  }
@@ -211,20 +252,35 @@ export async function installDeps(opts, params) {
211
252
  await handleIgnoredBuilds(opts, ignoredBuilds);
212
253
  return;
213
254
  }
214
- const { updatedCatalogs, updatedManifest, ignoredBuilds } = await install(manifest, {
255
+ const { updatedCatalogs, updatedManifest, ignoredBuilds, resolutionPolicyViolations } = await install(manifest, {
215
256
  ...installOpts,
216
257
  updatePackageManifest,
217
258
  updateMatching,
218
259
  });
219
- if (opts.update === true && opts.save !== false) {
220
- await Promise.all([
221
- writeProjectManifest(updatedManifest),
222
- updateWorkspaceManifest(opts.workspaceDir ?? opts.dir, {
223
- updatedCatalogs,
224
- cleanupUnusedCatalogs: opts.cleanupUnusedCatalogs,
225
- allProjects,
226
- }),
227
- ]);
260
+ // `opts.save === false` (e.g. `--no-save`) means "don't persist anything
261
+ // from this install" — both package.json and the workspace manifest.
262
+ // Skip the pick so the info log doesn't claim entries were added that
263
+ // were never written; the next install will resurface them.
264
+ if (opts.save !== false) {
265
+ const policyUpdates = policyHandlers?.pickManifestUpdates(resolutionPolicyViolations);
266
+ if (opts.update === true) {
267
+ await Promise.all([
268
+ writeProjectManifest(updatedManifest),
269
+ updateWorkspaceManifest(opts.workspaceDir ?? opts.dir, {
270
+ updatedCatalogs,
271
+ cleanupUnusedCatalogs: opts.cleanupUnusedCatalogs,
272
+ allProjects,
273
+ ...policyUpdates,
274
+ }),
275
+ ]);
276
+ }
277
+ else if (policyUpdates != null) {
278
+ // Plain `pnpm install` (no --update, no params) wouldn't otherwise touch
279
+ // the workspace manifest. Persist the auto-policy patches anyway so any
280
+ // loose bypass (today: minimumReleaseAgeExclude) remains explicit on
281
+ // subsequent installs.
282
+ await updateWorkspaceManifest(opts.workspaceDir ?? opts.dir, policyUpdates);
283
+ }
228
284
  }
229
285
  await handleIgnoredBuilds(opts, ignoredBuilds);
230
286
  if (opts.linkWorkspacePackages && opts.workspaceDir) {
@@ -243,6 +299,7 @@ export async function installDeps(opts, params) {
243
299
  allProjectsGraph: opts.allProjectsGraph,
244
300
  selectedProjectsGraph,
245
301
  workspaceDir: opts.workspaceDir, // Otherwise TypeScript doesn't understand that is not undefined
302
+ runPacquet,
246
303
  }, 'install');
247
304
  if (opts.ignoreScripts)
248
305
  return;
package/lib/link.js CHANGED
@@ -69,6 +69,9 @@ This might cause issues in your project. To resolve this, you may use the "file:
69
69
  }
70
70
  }
71
71
  export async function handler(opts, params) {
72
+ if ((params == null) || (params.length === 0)) {
73
+ throw new PnpmError('LINK_BAD_PARAMS', 'You must provide a parameter. Usage: pnpm link <dir>');
74
+ }
72
75
  let workspacePackagesArr;
73
76
  let workspacePackages;
74
77
  if (opts.workspaceDir) {
@@ -87,9 +90,6 @@ export async function handler(opts, params) {
87
90
  binsDir: opts.bin,
88
91
  });
89
92
  const writeProjectManifest = await createProjectManifestWriter(opts.rootProjectManifestDir);
90
- if ((params == null) || (params.length === 0)) {
91
- throw new PnpmError('LINK_BAD_PARAMS', 'You must provide a parameter. Usage: pnpm link <dir>');
92
- }
93
93
  const [pkgPaths, pkgNames] = partition((inp) => isFilespec.test(inp), params);
94
94
  if (pkgNames.length > 0) {
95
95
  throw new PnpmError('LINK_BAD_PARAMS', `Cannot link by package name. Use a relative or absolute path instead, e.g. "pnpm link ./${pkgNames[0]}"`);
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Shape returned by `installing/deps-installer`'s
3
+ * `collectResolutionPolicyViolations` and the inline accumulator on
4
+ * the resolveDependencies result. Re-declared locally so the commands
5
+ * layer can react without depending on the deps-installer's private
6
+ * install types.
7
+ *
8
+ * Verifier codes (today: `MINIMUM_RELEASE_AGE_VIOLATION` and
9
+ * `TRUST_DOWNGRADE`) are the contract surface for downstream UX.
10
+ * Each `PolicyHandler` below filters violations by code to decide
11
+ * what to do with them (prompt, persist to an exclude list, log,
12
+ * abort).
13
+ */
14
+ export interface PolicyViolation {
15
+ name: string;
16
+ version: string;
17
+ code: string;
18
+ reason: string;
19
+ }
20
+ /**
21
+ * Workspace-manifest patch a per-policy handler can request. Each
22
+ * field maps to a `pnpm-workspace.yaml` exclude-list array; the
23
+ * install command forwards these to `updateWorkspaceManifest` so the
24
+ * workspace writer dedupes and appends them in one pass.
25
+ *
26
+ * New policies that want auto-persistence add their field here AND
27
+ * teach `updateWorkspaceManifest` how to honor it.
28
+ */
29
+ export interface WorkspaceManifestPolicyUpdates {
30
+ addedMinimumReleaseAgeExcludes?: string[];
31
+ }
32
+ /**
33
+ * Aggregated plan the install command consumes. The `handleResolutionPolicyViolations`
34
+ * call fans out across every registered handler in registration order;
35
+ * any handler can throw to abort. `pickManifestUpdates` merges the
36
+ * per-handler patches into one bag so the workspace writer runs once.
37
+ */
38
+ export interface PolicyHandlersPlan {
39
+ handleResolutionPolicyViolations: (violations: readonly PolicyViolation[]) => Promise<void>;
40
+ pickManifestUpdates: (violations: readonly PolicyViolation[]) => WorkspaceManifestPolicyUpdates | undefined;
41
+ }
42
+ export interface PolicyHandlersOptions {
43
+ minimumReleaseAge?: number;
44
+ minimumReleaseAgeStrict?: boolean;
45
+ /**
46
+ * Pass `false` for `--no-save` installs. Handlers that would
47
+ * persist to the workspace manifest refuse to enter modes where
48
+ * approval is durably required (today: strict minimumReleaseAge)
49
+ * so the prompt never offers an action it can't honor.
50
+ */
51
+ save?: boolean;
52
+ /**
53
+ * Override for CI detection. Defaults to `ci-info`'s `isCI` flag.
54
+ */
55
+ ci?: boolean;
56
+ }
57
+ /**
58
+ * Composes the per-policy handlers the install command needs for the
59
+ * current opts. Returns `undefined` only when no handler reports
60
+ * activity — saves the install command an empty no-op call at every
61
+ * checkpoint when no policies are configured.
62
+ *
63
+ * Today only the minimumReleaseAge handler is registered. Future
64
+ * policies (trustPolicy UX, license policy, etc.) plug in by
65
+ * exporting a sibling `create<Name>PolicyHandler(opts)` and getting
66
+ * pushed into the `handlers` list below.
67
+ */
68
+ export declare function setupPolicyHandlers(opts: PolicyHandlersOptions): PolicyHandlersPlan | undefined;
@@ -0,0 +1,162 @@
1
+ import { PnpmError } from '@pnpm/error';
2
+ import { globalInfo } from '@pnpm/logger';
3
+ import { MINIMUM_RELEASE_AGE_VIOLATION_CODE } from '@pnpm/resolving.npm-resolver';
4
+ import { isCI } from 'ci-info';
5
+ import enquirer from 'enquirer';
6
+ /**
7
+ * Composes the per-policy handlers the install command needs for the
8
+ * current opts. Returns `undefined` only when no handler reports
9
+ * activity — saves the install command an empty no-op call at every
10
+ * checkpoint when no policies are configured.
11
+ *
12
+ * Today only the minimumReleaseAge handler is registered. Future
13
+ * policies (trustPolicy UX, license policy, etc.) plug in by
14
+ * exporting a sibling `create<Name>PolicyHandler(opts)` and getting
15
+ * pushed into the `handlers` list below.
16
+ */
17
+ export function setupPolicyHandlers(opts) {
18
+ const handlers = [];
19
+ const minimumReleaseAge = createMinimumReleaseAgeHandler(opts);
20
+ if (minimumReleaseAge)
21
+ handlers.push(minimumReleaseAge);
22
+ if (handlers.length === 0)
23
+ return undefined;
24
+ return {
25
+ handleResolutionPolicyViolations: async (violations) => {
26
+ // Sequential, not parallel: a TTY prompt from handler N would
27
+ // race with a different prompt from N+1, and we want a clean
28
+ // throw to short-circuit before later handlers ask for input.
29
+ for (const handler of handlers) {
30
+ if (handler.handleResolutionPolicyViolations) {
31
+ // eslint-disable-next-line no-await-in-loop
32
+ await handler.handleResolutionPolicyViolations(violations);
33
+ }
34
+ }
35
+ },
36
+ pickManifestUpdates: (violations) => {
37
+ const merged = {};
38
+ let any = false;
39
+ for (const handler of handlers) {
40
+ if (!handler.pickManifestUpdates)
41
+ continue;
42
+ const patch = handler.pickManifestUpdates(violations);
43
+ if (patch == null)
44
+ continue;
45
+ // Shallow merge — handlers own disjoint fields by convention,
46
+ // so there's no collision policy to encode here yet.
47
+ for (const [key, value] of Object.entries(patch)) {
48
+ if (value == null)
49
+ continue;
50
+ merged[key] = value;
51
+ any = true;
52
+ }
53
+ }
54
+ return any ? merged : undefined;
55
+ },
56
+ };
57
+ }
58
+ /**
59
+ * minimumReleaseAge policy handler.
60
+ *
61
+ * Loose mode (`minimumReleaseAgeStrict: false`) lets the resolver
62
+ * install versions newer than the cutoff and auto-persists them to
63
+ * `minimumReleaseAgeExclude`. Strict mode + an interactive TTY
64
+ * surfaces the full set of immature picks (direct AND transitive) at
65
+ * once via a confirm prompt — the install proceeds if the user
66
+ * approves, otherwise it aborts before touching the lockfile or
67
+ * package.json (#10488). Strict mode in CI or any other non-TTY
68
+ * context aborts hard with the same violation list so the failure
69
+ * pinpoints every offending entry, not just the first one the
70
+ * resolver picked.
71
+ *
72
+ * Strict mode combined with `--no-save` is rejected up-front — the
73
+ * approval prompt promises persistence the install command's
74
+ * `opts.save !== false` gate would block, leaving the lockfile
75
+ * holding approved-but-unlisted immature picks that the next install
76
+ * would reject.
77
+ *
78
+ * Returns `undefined` when minimumReleaseAge is not active.
79
+ */
80
+ function createMinimumReleaseAgeHandler(opts) {
81
+ if (!opts.minimumReleaseAge)
82
+ return undefined;
83
+ const strictMode = opts.minimumReleaseAgeStrict === true;
84
+ const persistenceEnabled = opts.save !== false;
85
+ const inCi = opts.ci ?? isCI;
86
+ const canPrompt = !inCi && Boolean(process.stdin.isTTY);
87
+ return {
88
+ handleResolutionPolicyViolations: async (violations) => {
89
+ if (!strictMode)
90
+ return;
91
+ const immature = filterImmatureViolations(violations);
92
+ if (immature.length === 0)
93
+ return;
94
+ if (!persistenceEnabled) {
95
+ throw new PnpmError('STRICT_MIN_RELEASE_AGE_REQUIRES_SAVE', 'minimumReleaseAgeStrict cannot be combined with --no-save: ' +
96
+ 'approval would require writing to minimumReleaseAgeExclude in pnpm-workspace.yaml, ' +
97
+ 'which --no-save prevents.', {
98
+ hint: 'Drop --no-save so the exclude list can be persisted, or set ' +
99
+ 'minimumReleaseAgeStrict: false to let the install proceed without prompting ' +
100
+ '(the lockfile would still trigger the auto-collect on the next normal install).',
101
+ });
102
+ }
103
+ if (canPrompt) {
104
+ await promptForApproval(immature);
105
+ }
106
+ else {
107
+ throw failOnImmature(immature);
108
+ }
109
+ },
110
+ pickManifestUpdates: (violations) => {
111
+ const entries = pickImmatureEntries(violations, strictMode);
112
+ return entries ? { addedMinimumReleaseAgeExcludes: entries } : undefined;
113
+ },
114
+ };
115
+ }
116
+ function filterImmatureViolations(violations) {
117
+ return violations.filter((v) => v.code === MINIMUM_RELEASE_AGE_VIOLATION_CODE);
118
+ }
119
+ function pickImmatureEntries(violations, promptRequired) {
120
+ const immature = filterImmatureViolations(violations);
121
+ if (immature.length === 0)
122
+ return undefined;
123
+ const sorted = [...new Set(immature.map((v) => `${v.name}@${v.version}`))].sort();
124
+ // Strict-mode picks already passed through the approval prompt, so
125
+ // the log here only confirms what was persisted. Loose-mode picks
126
+ // haven't been announced anywhere else, so the same log doubles as
127
+ // the discovery notice.
128
+ const reason = promptRequired
129
+ ? '(approved at the prompt)'
130
+ : '(loose mode allowed these immature versions)';
131
+ globalInfo(`Added ${sorted.length} ${sorted.length === 1 ? 'entry' : 'entries'} to minimumReleaseAgeExclude in pnpm-workspace.yaml ` +
132
+ `${reason}:\n ${sorted.join('\n ')}`);
133
+ return sorted;
134
+ }
135
+ function failOnImmature(immature) {
136
+ const sorted = [...immature].sort((a, b) => `${a.name}@${a.version}`.localeCompare(`${b.name}@${b.version}`));
137
+ const list = sorted.map((v) => ` ${v.name}@${v.version} ${v.reason}`).join('\n');
138
+ return new PnpmError('NO_MATURE_MATCHING_VERSION', `${sorted.length} ${sorted.length === 1 ? 'version does' : 'versions do'} not meet the minimumReleaseAge constraint:\n${list}`, {
139
+ hint: 'Run the install interactively to approve these picks, or add them to ' +
140
+ 'minimumReleaseAgeExclude in pnpm-workspace.yaml, or wait for the packages ' +
141
+ 'to mature past the configured cutoff.',
142
+ });
143
+ }
144
+ async function promptForApproval(immature) {
145
+ const sorted = [...immature].sort((a, b) => `${a.name}@${a.version}`.localeCompare(`${b.name}@${b.version}`));
146
+ const message = `${sorted.length} ${sorted.length === 1 ? 'version does' : 'versions do'} not meet the minimumReleaseAge constraint:\n` +
147
+ sorted.map((v) => ` ${v.name}@${v.version}`).join('\n') + '\n' +
148
+ 'Add to minimumReleaseAgeExclude in pnpm-workspace.yaml and proceed with the install?';
149
+ const answer = await enquirer.prompt({
150
+ type: 'confirm',
151
+ name: 'confirmed',
152
+ message,
153
+ initial: false,
154
+ });
155
+ if (!answer.confirmed) {
156
+ throw new PnpmError('MINIMUM_RELEASE_AGE_DENIED', 'Aborted: the immature versions were not approved.', {
157
+ hint: 'Re-run the install without `minimumReleaseAgeStrict: true` to allow these versions, ' +
158
+ 'or wait for the packages to mature past the configured cutoff.',
159
+ });
160
+ }
161
+ }
162
+ //# sourceMappingURL=policyHandlers.js.map
@@ -2,6 +2,7 @@ import type { CommandHandler } from '@pnpm/cli.command';
2
2
  import { type Config, type ConfigContext } from '@pnpm/config.reader';
3
3
  import { type UpdateMatchingFunction } from '@pnpm/installing.deps-installer';
4
4
  import type { PreferredVersions } from '@pnpm/resolving.resolver-base';
5
+ import type { ResolutionVerifier } from '@pnpm/resolving.resolver-base';
5
6
  import { type CreateStoreControllerOptions } from '@pnpm/store.connection-manager';
6
7
  import type { StoreController } from '@pnpm/store.controller';
7
8
  import type { IncludedDependencies, Project, ProjectManifest, ProjectsGraph } from '@pnpm/types';
@@ -26,9 +27,17 @@ export type RecursiveOptions = CreateStoreControllerOptions & Pick<Config, 'bail
26
27
  storeControllerAndDir?: {
27
28
  ctrl: StoreController;
28
29
  dir: string;
30
+ resolutionVerifiers: ResolutionVerifier[];
29
31
  };
30
32
  pnpmfile: string[];
31
- } & Partial<Pick<Config, 'sort' | 'strictDepBuilds' | 'workspaceConcurrency'>> & Required<Pick<Config, 'workspaceDir'>>;
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>;
40
+ } & Partial<Pick<Config, 'ci' | 'sort' | 'strictDepBuilds' | 'workspaceConcurrency'>> & Required<Pick<Config, 'workspaceDir'>>;
32
41
  export type CommandFullName = 'install' | 'add' | 'remove' | 'update' | 'import';
33
42
  export declare function recursive(allProjects: Project[], params: string[], opts: RecursiveOptions, cmdFullName: CommandFullName): Promise<boolean | string>;
34
43
  export declare function matchDependencies(match: (input: string) => string | null, manifest: ProjectManifest, include: IncludedDependencies): string[];
package/lib/recursive.js CHANGED
@@ -18,6 +18,7 @@ import pLimit from 'p-limit';
18
18
  import { getPinnedVersion } from './getPinnedVersion.js';
19
19
  import { getSaveType } from './getSaveType.js';
20
20
  import { handleIgnoredBuilds } from './handleIgnoredBuilds.js';
21
+ import { setupPolicyHandlers } from './policyHandlers.js';
21
22
  import { createWorkspaceSpecs, updateToWorkspacePackagesFromManifest } from './updateWorkspaceDependencies.js';
22
23
  export async function recursive(allProjects, params, opts, cmdFullName) {
23
24
  if (allProjects.length === 0) {
@@ -33,6 +34,12 @@ export async function recursive(allProjects, params, opts, cmdFullName) {
33
34
  const store = opts.storeControllerAndDir ?? await createStoreController(opts);
34
35
  const workspacePackages = arrayOfWorkspacePackagesToMap(allProjects);
35
36
  const targetDependenciesField = getSaveType(opts);
37
+ // See `installDeps.ts` for context; mirrored here so workspace-recursive
38
+ // installs also surface immature picks (loose-mode auto-persist or
39
+ // strict-mode prompt). The workspace manifest writer dedupes against the
40
+ // existing list, so a single drain at the end captures additions across
41
+ // every project.
42
+ const policyHandlers = setupPolicyHandlers(opts);
36
43
  const installOpts = Object.assign(opts, {
37
44
  allProjects: getAllProjects(manifestsByPath, opts.allProjectsGraph, opts.sort),
38
45
  linkWorkspacePackagesDepth: opts.linkWorkspacePackages === 'deep' ? Infinity : opts.linkWorkspacePackages ? 0 : -1,
@@ -46,7 +53,9 @@ export async function recursive(allProjects, params, opts, cmdFullName) {
46
53
  storeController: store.ctrl,
47
54
  storeDir: store.dir,
48
55
  targetDependenciesField,
56
+ resolutionVerifiers: store.resolutionVerifiers,
49
57
  workspacePackages,
58
+ handleResolutionPolicyViolations: policyHandlers?.handleResolutionPolicyViolations,
50
59
  });
51
60
  const result = {};
52
61
  const projectConfigRecord = createProjectConfigRecord(opts);
@@ -167,11 +176,17 @@ export async function recursive(allProjects, params, opts, cmdFullName) {
167
176
  if ((mutatedImporters.length === 0) && cmdFullName === 'update' && opts.depth === 0) {
168
177
  throw new PnpmError('NO_PACKAGE_IN_DEPENDENCIES', 'None of the specified packages were found in the dependencies of any of the projects.');
169
178
  }
170
- const { updatedCatalogs, updatedProjects: mutatedPkgs, ignoredBuilds, } = await mutateModules(mutatedImporters, {
179
+ const { updatedCatalogs, updatedProjects: mutatedPkgs, ignoredBuilds, resolutionPolicyViolations, } = await mutateModules(mutatedImporters, {
171
180
  ...installOpts,
172
181
  storeController: store.ctrl,
182
+ resolutionVerifiers: store.resolutionVerifiers,
173
183
  });
174
184
  if (opts.save !== false) {
185
+ // Only pick entries when we'll actually persist. Otherwise the
186
+ // info log would claim entries were added that the workspace
187
+ // manifest never saw, and the next install would re-prompt or
188
+ // fail verification.
189
+ const policyUpdates = policyHandlers?.pickManifestUpdates(resolutionPolicyViolations);
175
190
  const promises = mutatedPkgs.map(async ({ originalManifest, manifest, rootDir }) => {
176
191
  return manifestsByPath[rootDir].writeProjectManifest(originalManifest ?? manifest);
177
192
  });
@@ -179,6 +194,7 @@ export async function recursive(allProjects, params, opts, cmdFullName) {
179
194
  updatedCatalogs,
180
195
  cleanupUnusedCatalogs: opts.cleanupUnusedCatalogs,
181
196
  allProjects,
197
+ ...policyUpdates,
182
198
  }));
183
199
  await Promise.all(promises);
184
200
  }
@@ -188,6 +204,10 @@ export async function recursive(allProjects, params, opts, cmdFullName) {
188
204
  const pkgPaths = Object.keys(opts.selectedProjectsGraph).sort();
189
205
  let updatedCatalogs;
190
206
  const allIgnoredBuilds = new Set();
207
+ // Each per-project install returns its own slice of lockfile-resolution
208
+ // violations; accumulate them here so the post-loop persist step can
209
+ // dedup and write a single batch to the workspace manifest.
210
+ const allResolutionPolicyViolations = [];
191
211
  const limitInstallation = pLimit(getWorkspaceConcurrency(opts.workspaceConcurrency));
192
212
  await Promise.all(pkgPaths.map(async (rootDir) => limitInstallation(async () => {
193
213
  const hooks = opts.ignorePnpmfile
@@ -239,6 +259,7 @@ export async function recursive(allProjects, params, opts, cmdFullName) {
239
259
  updatedCatalogs: undefined, // there's no reason to add new or update catalogs on `pnpm remove`
240
260
  updatedManifest: mutationResult.updatedProjects[0].manifest,
241
261
  ignoredBuilds: mutationResult.ignoredBuilds,
262
+ resolutionPolicyViolations: mutationResult.resolutionPolicyViolations,
242
263
  };
243
264
  };
244
265
  break;
@@ -249,7 +270,7 @@ export async function recursive(allProjects, params, opts, cmdFullName) {
249
270
  break;
250
271
  }
251
272
  const localConfig = getProjectConfig(manifest) ?? {};
252
- const { updatedCatalogs: newCatalogsAddition, updatedManifest: newManifest, ignoredBuilds, } = await action(manifest, {
273
+ const { updatedCatalogs: newCatalogsAddition, updatedManifest: newManifest, ignoredBuilds, resolutionPolicyViolations, } = await action(manifest, {
253
274
  ...installOpts,
254
275
  ...localConfig,
255
276
  ...opts.allProjectsGraph[rootDir]?.package,
@@ -263,6 +284,7 @@ export async function recursive(allProjects, params, opts, cmdFullName) {
263
284
  }),
264
285
  configByUri: installOpts.configByUri,
265
286
  storeController: store.ctrl,
287
+ resolutionVerifiers: store.resolutionVerifiers,
266
288
  });
267
289
  if (opts.save !== false) {
268
290
  await writeProjectManifest(newManifest);
@@ -276,6 +298,11 @@ export async function recursive(allProjects, params, opts, cmdFullName) {
276
298
  allIgnoredBuilds.add(depPath);
277
299
  }
278
300
  }
301
+ if (resolutionPolicyViolations?.length) {
302
+ for (const violation of resolutionPolicyViolations) {
303
+ allResolutionPolicyViolations.push(violation);
304
+ }
305
+ }
279
306
  result[rootDir].status = 'passed';
280
307
  }
281
308
  catch (err) { // eslint-disable-line
@@ -294,11 +321,18 @@ export async function recursive(allProjects, params, opts, cmdFullName) {
294
321
  }
295
322
  })));
296
323
  await handleIgnoredBuilds(opts, allIgnoredBuilds.size ? allIgnoredBuilds : undefined);
297
- await updateWorkspaceManifest(opts.workspaceDir, {
298
- updatedCatalogs,
299
- cleanupUnusedCatalogs: opts.cleanupUnusedCatalogs,
300
- allProjects,
301
- });
324
+ if (opts.save !== false) {
325
+ // Only pick entries when we'll actually persist. Otherwise the
326
+ // info log would claim entries were added that the workspace
327
+ // manifest never saw, mirroring the gate the shared-lockfile
328
+ // branch + installDeps already apply.
329
+ await updateWorkspaceManifest(opts.workspaceDir, {
330
+ updatedCatalogs,
331
+ cleanupUnusedCatalogs: opts.cleanupUnusedCatalogs,
332
+ allProjects,
333
+ ...policyHandlers?.pickManifestUpdates(allResolutionPolicyViolations),
334
+ });
335
+ }
302
336
  if (!opts.lockfileOnly && !opts.ignoreScripts && (cmdFullName === 'add' ||
303
337
  cmdFullName === 'install' ||
304
338
  cmdFullName === 'update')) {
package/lib/remove.js CHANGED
@@ -135,6 +135,7 @@ export async function handler(opts, params) {
135
135
  linkWorkspacePackagesDepth: opts.linkWorkspacePackages === 'deep' ? Infinity : opts.linkWorkspacePackages ? 0 : -1,
136
136
  storeController: store.ctrl,
137
137
  storeDir: store.dir,
138
+ resolutionVerifiers: store.resolutionVerifiers,
138
139
  include,
139
140
  });
140
141
  const allProjects = opts.allProjects ?? (opts.workspaceDir
@@ -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.2.2",
3
+ "version": "1100.4.0",
4
4
  "description": "Commands for installation",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -33,6 +33,7 @@
33
33
  "@zkochan/rimraf": "^4.0.0",
34
34
  "@zkochan/table": "^2.0.1",
35
35
  "chalk": "^5.6.0",
36
+ "ci-info": "^4.3.0",
36
37
  "enquirer": "^2.4.1",
37
38
  "get-npm-tarball-url": "^2.1.0",
38
39
  "is-subdir": "^2.0.0",
@@ -43,45 +44,46 @@
43
44
  "ramda": "npm:@pnpm/ramda@0.28.1",
44
45
  "render-help": "^2.0.0",
45
46
  "version-selector-type": "^3.0.0",
46
- "@pnpm/building.after-install": "1101.0.12",
47
+ "@pnpm/building.after-install": "1101.0.14",
47
48
  "@pnpm/catalogs.types": "1100.0.0",
48
- "@pnpm/cli.command": "1100.0.1",
49
49
  "@pnpm/cli.common-cli-options-help": "1100.0.1",
50
- "@pnpm/cli.utils": "1101.0.4",
50
+ "@pnpm/cli.utils": "1101.0.6",
51
+ "@pnpm/config.pick-registry-for-package": "1100.0.4",
51
52
  "@pnpm/config.matcher": "1100.0.1",
52
- "@pnpm/config.pick-registry-for-package": "1100.0.3",
53
- "@pnpm/config.writer": "1100.0.7",
53
+ "@pnpm/config.reader": "1101.3.3",
54
+ "@pnpm/cli.command": "1100.0.1",
55
+ "@pnpm/config.writer": "1100.0.9",
54
56
  "@pnpm/constants": "1100.0.0",
55
- "@pnpm/deps.inspection.outdated": "1100.0.15",
56
- "@pnpm/deps.path": "1100.0.3",
57
- "@pnpm/deps.status": "1100.0.15",
58
- "@pnpm/fs.graceful-fs": "1100.1.0",
57
+ "@pnpm/deps.inspection.outdated": "1100.1.0",
58
+ "@pnpm/deps.path": "1100.0.4",
59
+ "@pnpm/deps.status": "1100.0.17",
59
60
  "@pnpm/error": "1100.0.0",
61
+ "@pnpm/fs.graceful-fs": "1100.1.0",
60
62
  "@pnpm/fs.read-modules-dir": "1100.0.1",
61
- "@pnpm/global.commands": "1100.0.17",
62
- "@pnpm/installing.context": "1100.0.10",
63
- "@pnpm/hooks.pnpmfile": "1100.0.8",
64
- "@pnpm/installing.dedupe.check": "1100.0.5",
65
- "@pnpm/installing.env-installer": "1101.0.9",
66
- "@pnpm/installing.deps-installer": "1101.1.2",
67
- "@pnpm/lockfile.types": "1100.0.5",
68
- "@pnpm/pkg-manifest.reader": "1100.0.3",
69
- "@pnpm/pkg-manifest.utils": "1100.1.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/resolving.resolver-base": "1100.1.3",
72
- "@pnpm/store.controller": "1101.0.6",
73
- "@pnpm/types": "1101.1.0",
74
- "@pnpm/workspace.projects-filter": "1100.0.11",
75
- "@pnpm/workspace.project-manifest-reader": "1100.0.5",
76
- "@pnpm/workspace.project-manifest-writer": "1100.0.3",
77
- "@pnpm/store.connection-manager": "1100.1.2",
78
- "@pnpm/workspace.projects-reader": "1101.0.4",
79
- "@pnpm/workspace.state": "1100.0.12",
80
- "@pnpm/workspace.workspace-manifest-writer": "1100.0.7",
81
- "@pnpm/workspace.projects-sorter": "1100.0.2",
82
- "@pnpm/config.reader": "1101.3.1",
83
- "@pnpm/workspace.projects-graph": "1100.0.8",
84
- "@pnpm/workspace.root-finder": "1100.0.1"
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",
83
+ "@pnpm/workspace.root-finder": "1100.0.1",
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"
85
87
  },
86
88
  "peerDependencies": {
87
89
  "@pnpm/logger": ">=1001.0.0 <1002.0.0"
@@ -94,7 +96,6 @@
94
96
  "@types/ramda": "0.31.1",
95
97
  "@types/yarnpkg__lockfile": "^1.1.9",
96
98
  "@types/zkochan__table": "npm:@types/table@6.0.0",
97
- "ci-info": "^4.3.0",
98
99
  "delay": "^7.0.0",
99
100
  "jest-diff": "^30.3.0",
100
101
  "path-name": "^1.0.0",
@@ -105,18 +106,18 @@
105
106
  "write-json-file": "^7.0.0",
106
107
  "write-package": "7.2.0",
107
108
  "write-yaml-file": "^6.0.0",
108
- "@pnpm/installing.commands": "1100.2.2",
109
- "@pnpm/assert-project": "1100.0.8",
110
- "@pnpm/prepare": "1100.0.8",
111
- "@pnpm/store.index": "1100.1.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",
112
113
  "@pnpm/logger": "1100.0.0",
113
114
  "@pnpm/test-fixtures": "1100.0.0",
115
+ "@pnpm/store.index": "1100.1.0",
114
116
  "@pnpm/test-ipc-server": "1100.0.0",
115
- "@pnpm/installing.modules-yaml": "1100.0.4",
117
+ "@pnpm/testing.mock-agent": "1100.0.6",
116
118
  "@pnpm/testing.command-defaults": "1100.0.1",
117
- "@pnpm/testing.mock-agent": "1100.0.4",
118
- "@pnpm/worker": "1100.1.5",
119
- "@pnpm/workspace.projects-filter": "1100.0.11"
119
+ "@pnpm/worker": "1100.1.7",
120
+ "@pnpm/workspace.projects-filter": "1100.0.13"
120
121
  },
121
122
  "engines": {
122
123
  "node": ">=22.13"