@pnpm/installing.deps-installer 1102.0.0 → 1102.1.1

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.
@@ -46,6 +46,13 @@ export interface StrictInstallOptions {
46
46
  preferFrozenLockfile: boolean;
47
47
  saveWorkspaceProtocol: boolean | 'rolling';
48
48
  lockfileCheck?: (prev: LockfileObject, next: LockfileObject) => void;
49
+ /**
50
+ * When true, resolve fully but write nothing to disk (no lockfile, no
51
+ * `node_modules`). The before/after wanted lockfiles are returned in the
52
+ * install result's `dryRunResult` so the caller can report what an install
53
+ * would change. Powers `pnpm install --dry-run`.
54
+ */
55
+ dryRun?: boolean;
49
56
  lockfileIncludeTarballUrl?: boolean;
50
57
  preferWorkspacePackages: boolean;
51
58
  preserveWorkspaceProtocol: boolean;
@@ -65,6 +72,8 @@ export interface StrictInstallOptions {
65
72
  engineStrict: boolean;
66
73
  allowBuilds?: Record<string, boolean | string>;
67
74
  nodeLinker: 'isolated' | 'hoisted' | 'pnp';
75
+ nodeExperimentalPackageMap: boolean;
76
+ nodePackageMapType: 'standard' | 'loose';
68
77
  nodeVersion?: string;
69
78
  packageExtensions: Record<string, PackageExtension>;
70
79
  ignoredOptionalDependencies: string[];
@@ -48,6 +48,8 @@ const defaults = (opts) => {
48
48
  updateChecksums: false,
49
49
  nodeVersion: opts.nodeVersion,
50
50
  nodeLinker: 'isolated',
51
+ nodeExperimentalPackageMap: false,
52
+ nodePackageMapType: 'standard',
51
53
  overrides: {},
52
54
  ownLifecycleHooksStdio: 'inherit',
53
55
  ignoreCompatibilityDb: false,
@@ -2,6 +2,7 @@ import type { Catalogs } from '@pnpm/catalogs.types';
2
2
  import { PnpmError } from '@pnpm/error';
3
3
  import { type PinnedVersion, type UpdateMatchingFunction, type WantedDependency } from '@pnpm/installing.deps-resolver';
4
4
  import { type InstallationResultStats } from '@pnpm/installing.deps-restorer';
5
+ import { type LockfileObject } from '@pnpm/lockfile.fs';
5
6
  import type { PreferredVersions, ResolutionPolicyViolation } from '@pnpm/resolving.resolver-base';
6
7
  import type { DependenciesField, DepPath, IgnoredBuilds, PeerDependencyIssues, ProjectId, ProjectManifest, ProjectRootDir, ReadPackageHook } from '@pnpm/types';
7
8
  import { type InstallOptions } from './extendInstallOptions.js';
@@ -47,6 +48,8 @@ export interface InstallResult {
47
48
  ignoredBuilds: IgnoredBuilds | undefined;
48
49
  /** Forwarded from {@link MutateModulesResult.resolutionPolicyViolations}. */
49
50
  resolutionPolicyViolations: ResolutionPolicyViolation[];
51
+ /** Forwarded from {@link MutateModulesResult.dryRunResult}. */
52
+ dryRunResult?: DryRunInstallResult;
50
53
  }
51
54
  export declare function install(manifest: ProjectManifest, opts: Opts): Promise<InstallResult>;
52
55
  export type MutatedProject = DependenciesMutation & {
@@ -64,6 +67,8 @@ export interface MutateModulesInSingleProjectResult {
64
67
  ignoredBuilds: IgnoredBuilds | undefined;
65
68
  /** Forwarded from {@link MutateModulesResult.resolutionPolicyViolations}. */
66
69
  resolutionPolicyViolations: ResolutionPolicyViolation[];
70
+ /** Forwarded from {@link MutateModulesResult.dryRunResult}. */
71
+ dryRunResult?: DryRunInstallResult;
67
72
  }
68
73
  export declare function mutateModulesInSingleProject(project: MutatedProject & {
69
74
  binsDir?: string;
@@ -86,6 +91,11 @@ export interface MutateModulesResult {
86
91
  * verifier reported a violation or no policy was active.
87
92
  */
88
93
  resolutionPolicyViolations: ResolutionPolicyViolation[];
94
+ /**
95
+ * Present only for a `dryRun` install: the before/after wanted lockfiles
96
+ * the resolve produced without writing, for the caller to diff.
97
+ */
98
+ dryRunResult?: DryRunInstallResult;
89
99
  }
90
100
  export declare function mutateModules(projects: MutatedProject[], maybeOpts: MutateModulesOptions): Promise<MutateModulesResult>;
91
101
  export declare function addDependenciesToPackage(manifest: ProjectManifest, dependencySelectors: string[], opts: Omit<InstallOptions, 'allProjects'> & {
@@ -117,6 +127,14 @@ export interface UpdatedProject {
117
127
  peerDependencyIssues?: PeerDependencyIssues;
118
128
  rootDir: ProjectRootDir;
119
129
  }
130
+ /**
131
+ * The before/after wanted lockfiles a `dryRun` install resolved without
132
+ * writing. The caller diffs them to report what a real install would change.
133
+ */
134
+ export interface DryRunInstallResult {
135
+ originalLockfile: LockfileObject;
136
+ wantedLockfile: LockfileObject;
137
+ }
120
138
  export declare class IgnoredBuildsError extends PnpmError {
121
139
  constructor(ignoredBuilds: IgnoredBuilds);
122
140
  }
@@ -3,22 +3,24 @@ import { linkBins, linkBinsOfPackages } from '@pnpm/bins.linker';
3
3
  import { buildSelectedPkgs } from '@pnpm/building.after-install';
4
4
  import { buildModules, linkBinsOfDependencies } from '@pnpm/building.during-install';
5
5
  import { createAllowBuildFunction, isBuildExplicitlyDisallowed } from '@pnpm/building.policy';
6
+ import { mergeCatalogs } from '@pnpm/catalogs.config';
6
7
  import { parseCatalogProtocol } from '@pnpm/catalogs.protocol-parser';
7
8
  import { matchCatalogResolveResult, resolveFromCatalog } from '@pnpm/catalogs.resolver';
9
+ import { parseOverrides } from '@pnpm/config.parse-overrides';
8
10
  import { LAYOUT_VERSION, LOCKFILE_MAJOR_VERSION, LOCKFILE_VERSION, WANTED_LOCKFILE, } from '@pnpm/constants';
9
11
  import { ignoredScriptsLogger, stageLogger, summaryLogger, } from '@pnpm/core-loggers';
10
12
  import { hashObjectNullableWithPrefix } from '@pnpm/crypto.object-hasher';
11
13
  import * as dp from '@pnpm/deps.path';
12
14
  import { PnpmError } from '@pnpm/error';
13
- import { makeNodeRequireOption, runLifecycleHook, runLifecycleHooksConcurrently, } from '@pnpm/exec.lifecycle';
15
+ import { makeNodePackageMapOption, makeNodeRequireOption, runLifecycleHook, runLifecycleHooksConcurrently, } from '@pnpm/exec.lifecycle';
14
16
  import { getContext } from '@pnpm/installing.context';
15
17
  import { getWantedDependencies, resolveDependencies, } from '@pnpm/installing.deps-resolver';
16
18
  import { extendProjectsWithTargetDirs, headlessInstall } from '@pnpm/installing.deps-restorer';
17
- import { writeModulesManifest } from '@pnpm/installing.modules-yaml';
19
+ import { readModulesManifest, writeModulesManifest } from '@pnpm/installing.modules-yaml';
18
20
  import { cleanGitBranchLockfiles, getWantedLockfileName, isEmptyLockfile, readEnvLockfile, readWantedLockfile, readWantedLockfileFile, writeCurrentLockfile, writeEnvLockfile, writeLockfiles, writeWantedLockfile, } from '@pnpm/lockfile.fs';
19
21
  import { getPreferredVersionsFromLockfileAndManifests } from '@pnpm/lockfile.preferred-versions';
20
22
  import { calcPatchHashes, createOverridesMapFromParsed, getOutdatedLockfileSetting, } from '@pnpm/lockfile.settings-checker';
21
- import { writePnpFile } from '@pnpm/lockfile.to-pnp';
23
+ import { PACKAGE_MAP_FILENAME, writePackageMap, writePnpFile } from '@pnpm/lockfile.to-pnp';
22
24
  import { allProjectsAreUpToDate, satisfiesPackageManifest } from '@pnpm/lockfile.verification';
23
25
  import { globalInfo, logger, streamParser } from '@pnpm/logger';
24
26
  import { groupPatchedDependencies } from '@pnpm/patching.config';
@@ -60,7 +62,7 @@ export async function install(manifest, opts) {
60
62
  if (opts.pnprServer) {
61
63
  return installViaPnprServer(manifest, rootDir, opts);
62
64
  }
63
- const { updatedCatalogs, updatedProjects: projects, ignoredBuilds, resolutionPolicyViolations } = await mutateModules([
65
+ const { updatedCatalogs, updatedProjects: projects, ignoredBuilds, resolutionPolicyViolations, dryRunResult } = await mutateModules([
64
66
  {
65
67
  mutation: 'install',
66
68
  pruneDirectDependencies: opts.pruneDirectDependencies,
@@ -79,7 +81,7 @@ export async function install(manifest, opts) {
79
81
  binsDir: opts.binsDir,
80
82
  }],
81
83
  });
82
- return { updatedCatalogs, updatedManifest: projects[0].manifest, ignoredBuilds, resolutionPolicyViolations };
84
+ return { updatedCatalogs, updatedManifest: projects[0].manifest, ignoredBuilds, resolutionPolicyViolations, dryRunResult };
83
85
  }
84
86
  export async function mutateModulesInSingleProject(project, maybeOpts) {
85
87
  const result = await mutateModules([
@@ -102,6 +104,7 @@ export async function mutateModulesInSingleProject(project, maybeOpts) {
102
104
  updatedProject: result.updatedProjects[0],
103
105
  ignoredBuilds: result.ignoredBuilds,
104
106
  resolutionPolicyViolations: result.resolutionPolicyViolations,
107
+ dryRunResult: result.dryRunResult,
105
108
  };
106
109
  }
107
110
  const pickCatalogSpecifier = {
@@ -198,7 +201,7 @@ export async function mutateModules(projects, maybeOpts) {
198
201
  opts.useLockfile &&
199
202
  !opts.useGitBranchLockfile &&
200
203
  !opts.mergeGitBranchLockfiles &&
201
- opts.lockfileCheck == null &&
204
+ !isCheckOnlyInstall(opts) &&
202
205
  opts.enableModulesDir &&
203
206
  installsOnly &&
204
207
  !opts.lockfileOnly &&
@@ -288,6 +291,7 @@ export async function mutateModules(projects, maybeOpts) {
288
291
  if (!opts.ignoreScripts && ignoredBuilds?.size) {
289
292
  ignoredBuilds = await runUnignoredDependencyBuilds(opts, ignoredBuilds, ctx.wantedLockfile, allowBuild);
290
293
  }
294
+ let revokedBuilds = false;
291
295
  // Detect packages whose build approval was revoked between the previous
292
296
  // and current install. A package is considered revoked when it was
293
297
  // previously allowed (true) but is now undecided (undefined). Packages
@@ -310,10 +314,28 @@ export async function mutateModules(projects, maybeOpts) {
310
314
  if (allowBuild?.(depPath) === undefined) {
311
315
  ignoredBuilds ??= new Set();
312
316
  ignoredBuilds.add(depPath);
317
+ revokedBuilds = true;
313
318
  }
314
319
  }
315
320
  }
316
321
  }
322
+ if (revokedBuilds && !opts.lockfileOnly && opts.enableModulesDir) {
323
+ // The install path already wrote .modules.yaml with the current
324
+ // install's state, but it captured ignoredBuilds before the revocation
325
+ // scan above added to it. Re-read the manifest from disk so we only
326
+ // update ignoredBuilds and don't clobber fields (hoistedDependencies,
327
+ // pendingBuilds, etc.) the install just wrote. The current computed
328
+ // set is authoritative — runUnignoredDependencyBuilds may have removed
329
+ // entries (for packages it successfully rebuilt) that the on-disk
330
+ // manifest still records, and those must not be re-introduced.
331
+ const writtenManifest = await readModulesManifest(ctx.rootModulesDir);
332
+ if (writtenManifest) {
333
+ // writeModulesManifest converts ignoredBuilds to an array before
334
+ // serializing, so a Set is fine here.
335
+ writtenManifest.ignoredBuilds = ignoredBuilds;
336
+ await writeModulesManifest(ctx.rootModulesDir, writtenManifest);
337
+ }
338
+ }
317
339
  ignoredScriptsLogger.debug({
318
340
  packageNames: ignoredBuilds ? dedupePackageNamesFromIgnoredBuilds(ignoredBuilds) : [],
319
341
  });
@@ -325,6 +347,7 @@ export async function mutateModules(projects, maybeOpts) {
325
347
  depsRequiringBuild: result.depsRequiringBuild,
326
348
  ignoredBuilds,
327
349
  resolutionPolicyViolations: result.resolutionPolicyViolations ?? [],
350
+ dryRunResult: result.dryRunResult,
328
351
  };
329
352
  // Reconcile the install with the lockfile verification that runs alongside
330
353
  // it. The verification verdict is awaited first so it takes precedence and
@@ -647,6 +670,7 @@ export async function mutateModules(projects, maybeOpts) {
647
670
  depsRequiringBuild: result.depsRequiringBuild,
648
671
  ignoredBuilds: result.ignoredBuilds,
649
672
  resolutionPolicyViolations: result.resolutionPolicyViolations,
673
+ dryRunResult: result.dryRunResult,
650
674
  };
651
675
  }
652
676
  /**
@@ -680,6 +704,11 @@ export async function mutateModules(projects, maybeOpts) {
680
704
  !ctx.lockfileHadConflicts &&
681
705
  !opts.fixLockfile &&
682
706
  !opts.dedupe &&
707
+ // A check-only install (`lockfileCheck`, used by `--dry-run` and
708
+ // `dedupe --check`) must always run a full resolution so the wanted
709
+ // lockfile can be compared, and must never materialize anything. The
710
+ // frozen path would skip resolution and/or perform a real install.
711
+ !isCheckOnlyInstall(opts) &&
683
712
  installsOnly &&
684
713
  (
685
714
  // If the user explicitly requested a frozen lockfile install, attempt
@@ -767,7 +796,7 @@ Note that in CI environments, this setting is enabled by default.`,
767
796
  else {
768
797
  logger.info({ message: 'Lockfile is up to date, resolution step is skipped', prefix: opts.lockfileDir });
769
798
  }
770
- if (opts.runPacquet != null && opts.useLockfile && !opts.useGitBranchLockfile && !opts.mergeGitBranchLockfiles && opts.lockfileCheck == null && opts.enableModulesDir) {
799
+ if (opts.runPacquet != null && opts.useLockfile && !opts.useGitBranchLockfile && !opts.mergeGitBranchLockfiles && !isCheckOnlyInstall(opts) && opts.enableModulesDir) {
771
800
  try {
772
801
  await opts.runPacquet.run();
773
802
  }
@@ -989,18 +1018,29 @@ export async function addDependenciesToPackage(manifest, dependencySelectors, op
989
1018
  });
990
1019
  return { updatedCatalogs, updatedManifest: projects[0].manifest, ignoredBuilds, resolutionPolicyViolations };
991
1020
  }
1021
+ /**
1022
+ * A "check-only" install resolves fully but writes nothing: `dryRun`
1023
+ * (`pnpm install --dry-run`) and `lockfileCheck` (`pnpm dedupe --check`)
1024
+ * both take this path. The shared flag suppresses every write and forces a
1025
+ * full resolution (the frozen/headless fast paths are skipped) so the wanted
1026
+ * lockfile can always be compared.
1027
+ */
1028
+ function isCheckOnlyInstall(opts) {
1029
+ return opts.lockfileCheck != null || opts.dryRun === true;
1030
+ }
992
1031
  const _installInContext = async (projects, ctx, opts) => {
1032
+ // Aliasing for clarity in boolean expressions below. True for both
1033
+ // `--dry-run` and `dedupe --check`: resolve fully, write nothing.
1034
+ const isInstallationOnlyForLockfileCheck = isCheckOnlyInstall(opts);
993
1035
  // The wanted lockfile is mutated during installation. To compare changes, a
994
1036
  // deep copy before installation is needed. This copy should represent the
995
1037
  // original wanted lockfile on disk as close as possible.
996
1038
  //
997
1039
  // This object can be quite large. Intentionally avoiding an expensive copy
998
- // if no lockfileCheck option was passed in.
999
- const originalLockfileForCheck = opts.lockfileCheck != null
1040
+ // unless this is a check-only install that needs the comparison.
1041
+ const originalLockfileForCheck = isInstallationOnlyForLockfileCheck
1000
1042
  ? clone(ctx.wantedLockfile)
1001
1043
  : null;
1002
- // Aliasing for clarity in boolean expressions below.
1003
- const isInstallationOnlyForLockfileCheck = opts.lockfileCheck != null;
1004
1044
  ctx.wantedLockfile.importers = ctx.wantedLockfile.importers || {};
1005
1045
  for (const { id } of projects) {
1006
1046
  if (!ctx.wantedLockfile.importers[id]) {
@@ -1159,6 +1199,17 @@ const _installInContext = async (projects, ctx, opts) => {
1159
1199
  prefix: ctx.lockfileDir,
1160
1200
  stage: 'resolution_done',
1161
1201
  });
1202
+ // `pnpm update` may bump catalog entries during resolution. Overrides that
1203
+ // reference a catalog (e.g. `overrides: { foo: 'catalog:' }`) were resolved
1204
+ // against the pre-update catalog when the install options were extended, so
1205
+ // re-resolve them against the updated catalog. Done before `afterAllResolved`
1206
+ // so that hook still sees (and can amend) the final overrides. Otherwise
1207
+ // lockfile `overrides` keeps pointing at the old version while `catalogs`
1208
+ // advances, and a later `--frozen-lockfile` install fails with
1209
+ // ERR_PNPM_LOCKFILE_CONFIG_MISMATCH.
1210
+ if (updatedCatalogs != null && opts.overrides != null && Object.keys(opts.overrides).length > 0) {
1211
+ newLockfile.overrides = createOverridesMapFromParsed(parseOverrides(opts.overrides, mergeCatalogs(opts.catalogs, updatedCatalogs)));
1212
+ }
1162
1213
  newLockfile = ((opts.hooks?.afterAllResolved) != null)
1163
1214
  ? await pipeWith(async (f, res) => f(await res), opts.hooks.afterAllResolved)(newLockfile) // eslint-disable-line
1164
1215
  : newLockfile;
@@ -1172,6 +1223,7 @@ const _installInContext = async (projects, ctx, opts) => {
1172
1223
  };
1173
1224
  let stats;
1174
1225
  let ignoredBuilds;
1226
+ const shouldWritePackageMap = opts.enableModulesDir !== false && opts.nodeLinker === 'isolated' && !opts.virtualStoreOnly;
1175
1227
  if (!opts.lockfileOnly && !isInstallationOnlyForLockfileCheck && opts.enableModulesDir) {
1176
1228
  const result = await linkPackages(projects, dependenciesGraph, {
1177
1229
  allowBuild: opts.allowBuild,
@@ -1211,6 +1263,20 @@ const _installInContext = async (projects, ctx, opts) => {
1211
1263
  supportedArchitectures: opts.supportedArchitectures,
1212
1264
  });
1213
1265
  stats = result.stats;
1266
+ if (shouldWritePackageMap) {
1267
+ // Omit the importer self-mapping when a project has no name (see the
1268
+ // deps-restorer write): a non-package-name key like `.` would be invalid.
1269
+ const importerNames = Object.fromEntries(projects.map(({ manifest, id }) => [id, manifest.name]));
1270
+ await writePackageMap(result.currentLockfile, {
1271
+ importerNames,
1272
+ lockfileDir: ctx.lockfileDir,
1273
+ locationByDepPath: Object.fromEntries(Object.values(dependenciesGraph).map((node) => [node.depPath, node.dir])),
1274
+ packageMapType: opts.nodePackageMapType,
1275
+ rootModulesDir: ctx.rootModulesDir,
1276
+ virtualStoreDir: ctx.virtualStoreDir,
1277
+ virtualStoreDirMaxLength: ctx.virtualStoreDirMaxLength,
1278
+ });
1279
+ }
1214
1280
  if (opts.enablePnp) {
1215
1281
  const importerNames = Object.fromEntries(projects.map(({ manifest, id }) => [id, manifest.name ?? id]));
1216
1282
  await writePnpFile(result.currentLockfile, {
@@ -1237,7 +1303,13 @@ const _installInContext = async (projects, ctx, opts) => {
1237
1303
  if (opts.enablePnp) {
1238
1304
  extraEnv = {
1239
1305
  ...extraEnv,
1240
- ...makeNodeRequireOption(path.join(opts.lockfileDir, '.pnp.cjs')),
1306
+ ...makeNodeRequireOption(path.join(opts.lockfileDir, '.pnp.cjs'), extraEnv),
1307
+ };
1308
+ }
1309
+ if (opts.nodeExperimentalPackageMap && shouldWritePackageMap) {
1310
+ extraEnv = {
1311
+ ...extraEnv,
1312
+ ...makeNodePackageMapOption(path.join(ctx.rootModulesDir, PACKAGE_MAP_FILENAME), extraEnv),
1241
1313
  };
1242
1314
  }
1243
1315
  // Dependency lifecycle scripts must not run on an unverified lockfile.
@@ -1382,7 +1454,13 @@ const _installInContext = async (projects, ctx, opts) => {
1382
1454
  if (opts.enablePnp) {
1383
1455
  opts.scriptsOpts.extraEnv = {
1384
1456
  ...opts.scriptsOpts.extraEnv,
1385
- ...makeNodeRequireOption(path.join(opts.lockfileDir, '.pnp.cjs')),
1457
+ ...makeNodeRequireOption(path.join(opts.lockfileDir, '.pnp.cjs'), opts.scriptsOpts.extraEnv),
1458
+ };
1459
+ }
1460
+ if (opts.nodeExperimentalPackageMap && shouldWritePackageMap) {
1461
+ opts.scriptsOpts.extraEnv = {
1462
+ ...opts.scriptsOpts.extraEnv,
1463
+ ...makeNodePackageMapOption(path.join(ctx.rootModulesDir, PACKAGE_MAP_FILENAME), opts.scriptsOpts.extraEnv),
1386
1464
  };
1387
1465
  }
1388
1466
  const projectsToBeBuilt = projectsWithTargetDirs.filter(({ mutation }) => mutation === 'install');
@@ -1466,6 +1544,9 @@ const _installInContext = async (projects, ctx, opts) => {
1466
1544
  depsRequiringBuild,
1467
1545
  ignoredBuilds,
1468
1546
  resolutionPolicyViolations,
1547
+ dryRunResult: (opts.dryRun && originalLockfileForCheck != null)
1548
+ ? { originalLockfile: originalLockfileForCheck, wantedLockfile: newLockfile }
1549
+ : undefined,
1469
1550
  };
1470
1551
  };
1471
1552
  function allMutationsAreInstalls(projects) {
@@ -1530,7 +1611,7 @@ const installInContext = async (projects, ctx, opts) => {
1530
1611
  if (!opts.frozenLockfile && opts.useLockfile) {
1531
1612
  const allProjectsLocatedInsideWorkspace = Object.values(ctx.projects)
1532
1613
  .filter((project) => isPathInsideWorkspace(project.rootDirRealPath ?? project.rootDir));
1533
- if (allProjectsLocatedInsideWorkspace.length > projects.length && opts.lockfileCheck == null && opts.enableModulesDir) {
1614
+ if (allProjectsLocatedInsideWorkspace.length > projects.length && !isCheckOnlyInstall(opts) && opts.enableModulesDir) {
1534
1615
  const newProjects = [...projects];
1535
1616
  const getWantedDepsOpts = {
1536
1617
  autoInstallPeers: opts.autoInstallPeers,
@@ -1582,7 +1663,7 @@ const installInContext = async (projects, ctx, opts) => {
1582
1663
  };
1583
1664
  }
1584
1665
  }
1585
- if (opts.nodeLinker === 'hoisted' && !opts.lockfileOnly && opts.lockfileCheck == null && opts.enableModulesDir) {
1666
+ if (opts.nodeLinker === 'hoisted' && !opts.lockfileOnly && !isCheckOnlyInstall(opts) && opts.enableModulesDir) {
1586
1667
  const result = await _installInContext(projects, ctx, {
1587
1668
  ...opts,
1588
1669
  lockfileOnly: true,
@@ -1611,7 +1692,7 @@ const installInContext = async (projects, ctx, opts) => {
1611
1692
  // Isolated `nodeLinker` (the default) with a non-frozen install.
1612
1693
  // The frozen branch is handled earlier in `tryFrozenInstall`; the
1613
1694
  // hoisted branch above runs a resolve-then-materialize sequence.
1614
- if (opts.runPacquet != null && opts.useLockfile && opts.saveLockfile && !opts.useGitBranchLockfile && !opts.mergeGitBranchLockfiles && !opts.lockfileOnly && opts.lockfileCheck == null && opts.enableModulesDir) {
1695
+ if (opts.runPacquet != null && opts.useLockfile && opts.saveLockfile && !opts.useGitBranchLockfile && !opts.mergeGitBranchLockfiles && !opts.lockfileOnly && !isCheckOnlyInstall(opts) && opts.enableModulesDir) {
1615
1696
  // pacquet >= 0.11.7 resolves itself: hand it the whole install
1616
1697
  // (resolve + fetch + import + link + build, writing the lockfile)
1617
1698
  // in a single non-frozen pass. Only for plain installs — `add` /
@@ -1,5 +1,5 @@
1
1
  import { type DepsStateCache } from '@pnpm/deps.graph-hasher';
2
- import type { DependenciesGraph, LinkedDependency } from '@pnpm/installing.deps-resolver';
2
+ import { type DependenciesGraph, type LinkedDependency } from '@pnpm/installing.deps-resolver';
3
3
  import type { InstallationResultStats } from '@pnpm/installing.deps-restorer';
4
4
  import type { IncludedDependencies } from '@pnpm/installing.modules-yaml';
5
5
  import type { LockfileObject } from '@pnpm/lockfile.fs';
@@ -2,13 +2,16 @@ import { promises as fs } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { progressLogger, stageLogger, statsLogger, } from '@pnpm/core-loggers';
4
4
  import { calcDepState, findRuntimeNodeVersion } from '@pnpm/deps.graph-hasher';
5
+ import { readModulesDir } from '@pnpm/fs.read-modules-dir';
5
6
  import { symlinkDependency } from '@pnpm/fs.symlink-dependency';
7
+ import { isValidDependencyAlias, } from '@pnpm/installing.deps-resolver';
6
8
  import { linkDirectDeps } from '@pnpm/installing.linking.direct-dep-linker';
7
9
  import { hoist } from '@pnpm/installing.linking.hoist';
8
10
  import { prune } from '@pnpm/installing.linking.modules-cleaner';
9
11
  import { filterLockfileByImporters, } from '@pnpm/lockfile.filtering';
10
12
  import { logger } from '@pnpm/logger';
11
13
  import { symlinkAllModules } from '@pnpm/worker';
14
+ import { rimraf } from '@zkochan/rimraf';
12
15
  import pLimit from 'p-limit';
13
16
  import { pathExists } from 'path-exists';
14
17
  import { difference, equals, isEmpty, pick, pickBy, props } from 'ramda';
@@ -250,20 +253,47 @@ async function linkNewPackages(currentLockfile, wantedLockfile, depGraph, opts)
250
253
  });
251
254
  const existingWithUpdatedDeps = [];
252
255
  if (!opts.force && (currentLockfile.packages != null) && (wantedLockfile.packages != null)) {
256
+ const currentPackages = currentLockfile.packages;
257
+ const wantedPackages = wantedLockfile.packages;
253
258
  // add subdependencies that have been updated
254
- // TODO: no need to relink everything. Can be relinked only what was changed
255
- for (const depPath of wantedRelDepPaths) {
256
- if (currentLockfile.packages[depPath] &&
257
- (!equals(currentLockfile.packages[depPath].dependencies, wantedLockfile.packages[depPath].dependencies) ||
258
- !isEmpty(currentLockfile.packages[depPath].optionalDependencies ?? {}) ||
259
- !isEmpty(wantedLockfile.packages[depPath].optionalDependencies ?? {}))) {
259
+ await Promise.all(wantedRelDepPaths.map((depPath) => limitModulesDirReads(async () => {
260
+ if (currentPackages[depPath] &&
261
+ (!equals(currentPackages[depPath].dependencies, wantedPackages[depPath].dependencies) ||
262
+ !isEmpty(currentPackages[depPath].optionalDependencies ?? {}) ||
263
+ !isEmpty(wantedPackages[depPath].optionalDependencies ?? {}))) {
260
264
  // TODO: come up with a test that triggers the usecase of depGraph[depPath] undefined
261
265
  // see related issue: https://github.com/pnpm/pnpm/issues/870
262
266
  if (depGraph[depPath] && !newDepPathsSet.has(depPath)) {
263
- existingWithUpdatedDeps.push(depGraph[depPath]);
267
+ const { actualChildrenChanged, removedAliases: actualRemovedAliases } = await getActualChildrenDiff(depGraph[depPath], depGraph, opts.lockfileDir, opts.optional);
268
+ if (actualChildrenChanged) {
269
+ existingWithUpdatedDeps.push({
270
+ children: depGraph[depPath].children,
271
+ modules: depGraph[depPath].modules,
272
+ name: depGraph[depPath].name,
273
+ optionalDependencies: depGraph[depPath].optionalDependencies,
274
+ removedAliases: actualRemovedAliases,
275
+ });
276
+ return;
277
+ }
278
+ const { changedChildren, removedAliases } = getChangedChildren({
279
+ currentDependencies: currentPackages[depPath].dependencies,
280
+ currentOptionalDependencies: currentPackages[depPath].optionalDependencies,
281
+ wantedDependencies: wantedPackages[depPath].dependencies,
282
+ wantedOptionalDependencies: wantedPackages[depPath].optionalDependencies,
283
+ allChildren: depGraph[depPath].children,
284
+ });
285
+ if (!isEmpty(changedChildren) || removedAliases.length > 0) {
286
+ existingWithUpdatedDeps.push({
287
+ children: changedChildren,
288
+ modules: depGraph[depPath].modules,
289
+ name: depGraph[depPath].name,
290
+ optionalDependencies: depGraph[depPath].optionalDependencies,
291
+ removedAliases,
292
+ });
293
+ }
264
294
  }
265
295
  }
266
- }
296
+ })));
267
297
  }
268
298
  if (!newDepPathsSet.size && (existingWithUpdatedDeps.length === 0))
269
299
  return { newDepPaths: [], added };
@@ -317,6 +347,7 @@ async function selectNewFromWantedDeps(wantedRelDepPaths, currentLockfile, depGr
317
347
  return newDeps;
318
348
  }
319
349
  const limitLinking = pLimit(16);
350
+ const limitModulesDirReads = pLimit(16);
320
351
  async function linkAllPkgs(storeController, depNodes, opts) {
321
352
  // Resolved `engines.runtime` Node version (when present) so the
322
353
  // side-effects-cache key prefix tracks the script-runner Node
@@ -365,29 +396,83 @@ async function linkAllPkgs(storeController, depNodes, opts) {
365
396
  }));
366
397
  }
367
398
  async function linkAllModules(depNodes, depGraph, opts) {
399
+ await Promise.all(depNodes.flatMap((depNode) => (depNode.removedAliases ?? []).map(async (alias) => limitModulesDirReads(async () => removeObsoleteChild(depNode.modules, alias)))));
368
400
  await symlinkAllModules({
369
401
  deps: depNodes.map((depNode) => {
370
- const children = opts.optional
371
- ? depNode.children
372
- : pickBy((_, childAlias) => !depNode.optionalDependencies.has(childAlias), depNode.children);
373
- const childrenPaths = {};
374
- for (const [alias, childDepPath] of Object.entries(children ?? {})) {
375
- if (childDepPath.startsWith('link:')) {
376
- childrenPaths[alias] = path.resolve(opts.lockfileDir, childDepPath.slice(5));
377
- }
378
- else {
379
- const pkg = depGraph[childDepPath];
380
- if (!pkg || !pkg.installable && pkg.optional || alias === depNode.name)
381
- continue;
382
- childrenPaths[alias] = pkg.dir;
383
- }
384
- }
385
402
  return {
386
- children: childrenPaths,
403
+ children: getChildrenPaths(depNode, depGraph, opts.lockfileDir, opts.optional),
387
404
  modules: depNode.modules,
388
405
  name: depNode.name,
389
406
  };
390
407
  }),
391
408
  });
392
409
  }
410
+ function getChangedChildren(opts) {
411
+ const { currentOptionalDependencies, wantedOptionalDependencies, allChildren } = opts;
412
+ // Use null-prototype maps so a child literally named `constructor`,
413
+ // `toString`, `__proto__`, etc. is treated as a normal alias instead
414
+ // of colliding with an inherited `Object.prototype` key during the
415
+ // `in`/index lookups below.
416
+ const currentChildren = Object.assign(Object.create(null), opts.currentDependencies, currentOptionalDependencies);
417
+ const wantedChildren = Object.assign(Object.create(null), opts.wantedDependencies, wantedOptionalDependencies);
418
+ const changedChildren = {};
419
+ const removedAliases = [];
420
+ for (const [alias, wantedChildDepPath] of Object.entries(wantedChildren)) {
421
+ const optionalityChanged = hasOwn(wantedOptionalDependencies, alias) !== hasOwn(currentOptionalDependencies, alias);
422
+ if (currentChildren[alias] !== wantedChildDepPath || optionalityChanged) {
423
+ const resolvedChildDepPath = hasOwn(allChildren, alias) ? allChildren[alias] : undefined;
424
+ if (resolvedChildDepPath != null) {
425
+ changedChildren[alias] = resolvedChildDepPath;
426
+ }
427
+ }
428
+ }
429
+ for (const alias of Object.keys(currentChildren)) {
430
+ if (!hasOwn(wantedChildren, alias)) {
431
+ removedAliases.push(alias);
432
+ }
433
+ }
434
+ return { changedChildren, removedAliases };
435
+ }
436
+ function hasOwn(obj, key) {
437
+ return obj != null && Object.hasOwn(obj, key);
438
+ }
439
+ async function getActualChildrenDiff(depNode, depGraph, lockfileDir, optional) {
440
+ if (depNode.optionalDependencies.size === 0) {
441
+ return { actualChildrenChanged: false, removedAliases: [] };
442
+ }
443
+ const currentAliases = new Set((await readModulesDir(depNode.modules) ?? []).filter((alias) => alias !== depNode.name));
444
+ const nextAliases = new Set(Object.keys(getChildrenPaths(depNode, depGraph, lockfileDir, optional)));
445
+ const removedAliases = Array.from(currentAliases).filter((alias) => !nextAliases.has(alias));
446
+ const actualChildrenChanged = removedAliases.length > 0 ||
447
+ Array.from(nextAliases).some((alias) => !currentAliases.has(alias));
448
+ return { actualChildrenChanged, removedAliases };
449
+ }
450
+ async function removeObsoleteChild(modulesDir, alias) {
451
+ // Guard against an alias that would escape the modules directory (e.g. `../../x`).
452
+ if (!isValidDependencyAlias(alias))
453
+ return;
454
+ await rimraf(path.join(modulesDir, alias));
455
+ if (alias[0] === '@') {
456
+ await fs.rmdir(path.join(modulesDir, alias.split('/')[0])).catch(() => { });
457
+ }
458
+ }
459
+ function getChildrenPaths(depNode, depGraph, lockfileDir, optional) {
460
+ const children = optional
461
+ ? depNode.children
462
+ : pickBy((_, childAlias) => !depNode.optionalDependencies.has(childAlias), depNode.children);
463
+ const childrenPaths = {};
464
+ for (const [alias, childDepPath] of Object.entries(children ?? {})) {
465
+ if (alias === depNode.name)
466
+ continue;
467
+ if (childDepPath.startsWith('link:')) {
468
+ childrenPaths[alias] = path.resolve(lockfileDir, childDepPath.slice(5));
469
+ continue;
470
+ }
471
+ const pkg = depGraph[childDepPath];
472
+ if (!pkg || !pkg.installable && pkg.optional)
473
+ continue;
474
+ childrenPaths[alias] = pkg.dir;
475
+ }
476
+ return childrenPaths;
477
+ }
393
478
  //# sourceMappingURL=link.js.map
@@ -205,6 +205,10 @@ function buildVerificationError(violations) {
205
205
  const details = omitted > 0
206
206
  ? `${breakdown}\n …and ${omitted} more`
207
207
  : breakdown;
208
+ // Registry fetch failures (auth/network/5xx) don't reach this batch — the
209
+ // verifier throws the registry's own error and the gate aborts with it. So
210
+ // every violation here is a genuine policy rejection, and the hint points at
211
+ // the lockfile rather than at connectivity.
208
212
  return new PnpmError(errorCode, `${violations.length} lockfile entries failed verification:\n${details}`, {
209
213
  hint: 'The lockfile contains entries that the active policies reject. ' +
210
214
  'This can mean the lockfile is stale, or that someone committed a ' +
@@ -346,22 +350,41 @@ function pushInvalidAliases(deps, invalid) {
346
350
  }
347
351
  async function iterateLockfileViolations(candidates, verifiers, concurrency) {
348
352
  const violations = [];
353
+ // A verifier may throw rather than return a violation when it can't reach the
354
+ // registry to verify an entry (auth/network/5xx) — that's not a per-entry
355
+ // policy pick, it's an incomplete verification, so the registry's own error
356
+ // should abort the install. Capture the first such error and rethrow it after
357
+ // the fan-out settles: rethrowing straight into Promise.all would leave the
358
+ // sibling tasks (all failing against the same dead registry) as unhandled
359
+ // rejections once Promise.all rejects on the first.
360
+ let fetchError;
349
361
  const limit = pLimit(concurrency ?? DEFAULT_CONCURRENCY);
350
362
  await Promise.all(Array.from(candidates.values(), ({ name, version, nonSemverVersion, resolution }) => limit(async () => {
351
- // Fan out across every active verifier; each handles its own
352
- // protocol short-circuit (e.g. the npm verifier returns ok:true for
353
- // git resolutions). We stop at the first failure per entry so a
354
- // multi-verifier setup doesn't produce duplicate violations for the
355
- // same (name, version).
356
- for (const verifier of verifiers) {
357
- // eslint-disable-next-line no-await-in-loop
358
- const result = await verifier.verify(resolution, { name, version, nonSemverVersion });
359
- if (!result.ok) {
360
- violations.push({ name, version, resolution, code: result.code, reason: result.reason });
361
- break;
363
+ try {
364
+ // Fan out across every active verifier; each handles its own
365
+ // protocol short-circuit (e.g. the npm verifier returns ok:true for
366
+ // git resolutions). We stop at the first failure per entry so a
367
+ // multi-verifier setup doesn't produce duplicate violations for the
368
+ // same (name, version).
369
+ for (const verifier of verifiers) {
370
+ // eslint-disable-next-line no-await-in-loop
371
+ const result = await verifier.verify(resolution, { name, version, nonSemverVersion });
372
+ if (!result.ok) {
373
+ violations.push({ name, version, resolution, code: result.code, reason: result.reason });
374
+ break;
375
+ }
362
376
  }
363
377
  }
378
+ catch (err) {
379
+ fetchError ??= err;
380
+ }
364
381
  })));
382
+ // A registry that couldn't be reached takes precedence over collected
383
+ // violations: the run never finished verifying, so the batch is incomplete
384
+ // and the actionable failure is the transport error. Once it's resolved the
385
+ // re-run surfaces any remaining violations.
386
+ if (fetchError != null)
387
+ throw fetchError;
365
388
  return violations;
366
389
  }
367
390
  //# sourceMappingURL=verifyLockfileResolutions.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/installing.deps-installer",
3
- "version": "1102.0.0",
3
+ "version": "1102.1.1",
4
4
  "description": "Fast, disk space efficient installation engine",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -30,9 +30,9 @@
30
30
  "funding": "https://opencollective.com/pnpm",
31
31
  "repository": {
32
32
  "type": "git",
33
- "url": "https://github.com/pnpm/pnpm/tree/main/installing/deps-installer"
33
+ "url": "https://github.com/pnpm/pnpm/tree/main/pnpm11/installing/deps-installer"
34
34
  },
35
- "homepage": "https://github.com/pnpm/pnpm/tree/main/installing/deps-installer#readme",
35
+ "homepage": "https://github.com/pnpm/pnpm/tree/main/pnpm11/installing/deps-installer#readme",
36
36
  "bugs": {
37
37
  "url": "https://github.com/pnpm/pnpm/issues"
38
38
  },
@@ -65,61 +65,62 @@
65
65
  "ramda": "npm:@pnpm/ramda@0.28.1",
66
66
  "run-groups": "^5.0.0",
67
67
  "semver": "^7.8.4",
68
- "@pnpm/building.after-install": "1102.0.0",
69
- "@pnpm/building.during-install": "1102.0.0",
70
- "@pnpm/catalogs.protocol-parser": "1100.0.0",
71
- "@pnpm/bins.remover": "1100.0.10",
68
+ "@pnpm/bins.remover": "1100.0.11",
69
+ "@pnpm/building.during-install": "1102.0.2",
70
+ "@pnpm/bins.linker": "1100.0.16",
71
+ "@pnpm/catalogs.config": "1100.0.2",
72
+ "@pnpm/building.policy": "1100.0.11",
73
+ "@pnpm/building.after-install": "1102.0.2",
74
+ "@pnpm/catalogs.types": "1100.0.0",
72
75
  "@pnpm/catalogs.resolver": "1100.0.0",
76
+ "@pnpm/catalogs.protocol-parser": "1100.0.0",
77
+ "@pnpm/config.parse-overrides": "1100.0.2",
73
78
  "@pnpm/config.normalize-registries": "1100.0.8",
74
- "@pnpm/config.parse-overrides": "1100.0.1",
75
- "@pnpm/catalogs.types": "1100.0.0",
79
+ "@pnpm/config.matcher": "1100.0.1",
76
80
  "@pnpm/core-loggers": "1100.2.1",
77
81
  "@pnpm/constants": "1100.0.0",
78
- "@pnpm/config.matcher": "1100.0.1",
79
- "@pnpm/deps.graph-hasher": "1100.2.5",
80
- "@pnpm/deps.path": "1100.0.8",
82
+ "@pnpm/crypto.hash": "1100.0.1",
83
+ "@pnpm/deps.graph-hasher": "1100.2.6",
81
84
  "@pnpm/deps.graph-sequencer": "1100.0.0",
82
- "@pnpm/building.policy": "1100.0.10",
83
- "@pnpm/bins.linker": "1100.0.14",
84
85
  "@pnpm/crypto.object-hasher": "1100.0.0",
85
- "@pnpm/exec.lifecycle": "1100.0.18",
86
- "@pnpm/hooks.read-package-hook": "1100.0.8",
86
+ "@pnpm/error": "1100.0.1",
87
+ "@pnpm/fs.read-modules-dir": "1100.0.1",
88
+ "@pnpm/deps.path": "1100.0.8",
89
+ "@pnpm/exec.lifecycle": "1100.1.1",
87
90
  "@pnpm/fs.symlink-dependency": "1100.0.10",
88
- "@pnpm/hooks.types": "1100.0.12",
89
- "@pnpm/error": "1100.0.0",
90
- "@pnpm/installing.context": "1100.0.18",
91
+ "@pnpm/hooks.types": "1100.1.0",
92
+ "@pnpm/installing.context": "1100.0.20",
93
+ "@pnpm/installing.deps-resolver": "1100.2.5",
94
+ "@pnpm/installing.deps-restorer": "1102.1.1",
95
+ "@pnpm/hooks.read-package-hook": "1100.0.9",
91
96
  "@pnpm/installing.linking.direct-dep-linker": "1100.0.10",
92
- "@pnpm/installing.deps-resolver": "1100.2.3",
93
- "@pnpm/crypto.hash": "1100.0.1",
94
- "@pnpm/fs.read-modules-dir": "1100.0.1",
95
- "@pnpm/installing.linking.hoist": "1100.0.14",
96
- "@pnpm/installing.package-requester": "1102.0.0",
97
- "@pnpm/installing.deps-restorer": "1102.0.0",
98
- "@pnpm/lockfile.fs": "1100.1.5",
99
- "@pnpm/lockfile.pruner": "1100.0.11",
100
- "@pnpm/installing.linking.modules-cleaner": "1100.1.8",
101
- "@pnpm/lockfile.preferred-versions": "1100.0.16",
97
+ "@pnpm/installing.linking.hoist": "1100.0.16",
102
98
  "@pnpm/installing.modules-yaml": "1100.0.9",
103
- "@pnpm/lockfile.settings-checker": "1100.0.18",
104
- "@pnpm/lockfile.utils": "1100.0.13",
105
- "@pnpm/lockfile.to-pnp": "1100.0.14",
106
- "@pnpm/network.auth-header": "1101.1.2",
107
- "@pnpm/lockfile.walker": "1100.0.11",
108
- "@pnpm/pnpr.client": "1.2.1",
99
+ "@pnpm/installing.linking.modules-cleaner": "1100.1.9",
100
+ "@pnpm/installing.package-requester": "1102.1.0",
101
+ "@pnpm/lockfile.fs": "1100.1.7",
102
+ "@pnpm/lockfile.preferred-versions": "1100.0.17",
103
+ "@pnpm/lockfile.filtering": "1100.1.8",
104
+ "@pnpm/lockfile.pruner": "1100.0.12",
105
+ "@pnpm/lockfile.settings-checker": "1100.0.20",
106
+ "@pnpm/lockfile.verification": "1100.0.20",
107
+ "@pnpm/lockfile.to-pnp": "1100.1.1",
108
+ "@pnpm/patching.config": "1100.0.9",
109
+ "@pnpm/lockfile.utils": "1100.1.0",
110
+ "@pnpm/lockfile.walker": "1100.0.12",
111
+ "@pnpm/network.auth-header": "1101.1.3",
112
+ "@pnpm/pkg-manifest.utils": "1100.2.6",
109
113
  "@pnpm/resolving.parse-wanted-dependency": "1100.0.1",
110
- "@pnpm/pkg-manifest.utils": "1100.2.5",
111
- "@pnpm/resolving.resolver-base": "1100.4.2",
112
- "@pnpm/lockfile.filtering": "1100.1.7",
113
- "@pnpm/types": "1101.3.2",
114
- "@pnpm/patching.config": "1100.0.8",
115
- "@pnpm/workspace.project-manifest-reader": "1100.0.13",
116
- "@pnpm/store.controller-types": "1100.1.5",
117
- "@pnpm/lockfile.verification": "1100.0.18",
118
- "@pnpm/store.index": "1100.2.0"
114
+ "@pnpm/pnpr.client": "1.2.3",
115
+ "@pnpm/store.index": "1100.2.1",
116
+ "@pnpm/resolving.resolver-base": "1100.5.0",
117
+ "@pnpm/workspace.project-manifest-reader": "1100.0.14",
118
+ "@pnpm/store.controller-types": "1100.1.6",
119
+ "@pnpm/types": "1101.3.2"
119
120
  },
120
121
  "peerDependencies": {
121
122
  "@pnpm/logger": "^1100.0.0",
122
- "@pnpm/worker": "^1100.2.0"
123
+ "@pnpm/worker": "^1100.2.2"
123
124
  },
124
125
  "devDependencies": {
125
126
  "@jest/globals": "30.4.1",
@@ -140,22 +141,22 @@
140
141
  "symlink-dir": "^10.0.1",
141
142
  "write-json-file": "^7.0.0",
142
143
  "write-yaml-file": "^6.0.0",
143
- "@pnpm/assert-project": "1100.0.16",
144
- "@pnpm/installing.deps-installer": "1102.0.0",
145
- "@pnpm/assert-store": "1100.0.16",
144
+ "@pnpm/assert-project": "1100.0.17",
145
+ "@pnpm/assert-store": "1100.0.17",
146
+ "@pnpm/installing.deps-installer": "1102.1.1",
147
+ "@pnpm/lockfile.types": "1100.0.12",
146
148
  "@pnpm/logger": "1100.0.0",
147
- "@pnpm/network.git-utils": "1100.0.1",
148
- "@pnpm/pkg-manifest.reader": "1100.0.8",
149
- "@pnpm/store.cafs": "1100.1.10",
150
- "@pnpm/prepare": "1100.0.16",
151
- "@pnpm/store.path": "1100.0.1",
152
- "@pnpm/test-ipc-server": "1100.0.0",
153
- "@pnpm/testing.registry-mock": "1100.0.6",
154
- "@pnpm/testing.mock-agent": "1101.0.3",
155
- "@pnpm/test-fixtures": "1100.0.0",
156
- "@pnpm/testing.temp-store": "1100.1.9",
149
+ "@pnpm/prepare": "1100.0.17",
157
150
  "@pnpm/resolving.registry.types": "1100.1.3",
158
- "@pnpm/lockfile.types": "1100.0.11"
151
+ "@pnpm/store.cafs": "1100.1.11",
152
+ "@pnpm/pkg-manifest.reader": "1100.0.9",
153
+ "@pnpm/store.path": "1100.0.2",
154
+ "@pnpm/test-fixtures": "1100.0.0",
155
+ "@pnpm/test-ipc-server": "1100.0.0",
156
+ "@pnpm/testing.mock-agent": "1101.0.4",
157
+ "@pnpm/testing.temp-store": "1100.1.11",
158
+ "@pnpm/network.git-utils": "1100.0.2",
159
+ "@pnpm/testing.registry-mock": "1100.0.7"
159
160
  },
160
161
  "engines": {
161
162
  "node": ">=22.13"