@pnpm/installing.deps-installer 1101.8.0 → 1102.0.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.
@@ -18,6 +18,7 @@ export interface StrictInstallOptions {
18
18
  cleanupUnusedCatalogs: boolean;
19
19
  frozenLockfile: boolean;
20
20
  frozenLockfileIfExists: boolean;
21
+ frozenStore: boolean;
21
22
  enableGlobalVirtualStore: boolean;
22
23
  enablePnp: boolean;
23
24
  extraBinPaths: string[];
@@ -205,23 +206,33 @@ export interface StrictInstallOptions {
205
206
  packageVulnerabilityAudit?: PackageVulnerabilityAudit;
206
207
  blockExoticSubdeps?: boolean;
207
208
  /**
208
- * Optional alternative install engine. When set, the frozen-install
209
- * path invokes this callback instead of `headlessInstall`. The CLI
210
- * layer constructs it (today: spawning the pacquet binary installed
211
- * via `configDependencies` and forwarding pnpm's own CLI argv); the
212
- * installer treats it as an opaque "do the install" hook so it
213
- * doesn't need to know about pacquet's binary path, CLI surface, or
214
- * any settings that only pacquet consumes.
209
+ * Optional alternative install engine. When set, the installer
210
+ * delegates the install to `run` instead of calling `headlessInstall`.
211
+ * The CLI layer constructs it (today: the pacquet binary installed via
212
+ * `configDependencies`, forwarding pnpm's own CLI argv); the installer
213
+ * treats it as an opaque "do the install" hook so it doesn't need to
214
+ * know about pacquet's binary path, CLI surface, or any settings that
215
+ * only pacquet consumes.
215
216
  *
216
- * `filterResolvedProgress` tells the helper to drop the engine's
217
- * own `pnpm:progress status:resolved` events because pnpm already
218
- * emitted one per package during a preceding lockfileOnly resolve
219
- * pass. The frozen-install path passes `false` (or nothing): no
220
- * resolve pass ran, so the engine's events are the only source.
217
+ * `supportsResolution` is `true` when the engine can resolve
218
+ * dependencies itself (pacquet >= 0.11.7). When `false` the installer
219
+ * runs its own resolve pass first and the engine only materializes the
220
+ * written lockfile.
221
+ *
222
+ * `run`'s `filterResolvedProgress` tells the helper to drop the
223
+ * engine's own `pnpm:progress status:resolved` events because pnpm
224
+ * already emitted one per package during a preceding lockfileOnly
225
+ * resolve pass. `resolve` tells the engine to do the resolution
226
+ * itself (non-frozen install). The frozen/materialize paths leave
227
+ * both unset.
221
228
  */
222
- runPacquet?: (opts?: {
223
- filterResolvedProgress?: boolean;
224
- }) => Promise<void>;
229
+ runPacquet?: {
230
+ supportsResolution: boolean;
231
+ run: (opts?: {
232
+ filterResolvedProgress?: boolean;
233
+ resolve?: boolean;
234
+ }) => Promise<void>;
235
+ };
225
236
  /**
226
237
  * If true, `mutateModules` does not emit the per-install `summary` log
227
238
  * event. Used by `pnpm add -g` when it runs multiple isolated installs
@@ -27,6 +27,7 @@ const defaults = (opts) => {
27
27
  force: false,
28
28
  forceFullResolution: false,
29
29
  frozenLockfile: false,
30
+ frozenStore: false,
30
31
  hoistPattern: undefined,
31
32
  publicHoistPattern: undefined,
32
33
  hooks: {},
@@ -144,6 +145,17 @@ export function extendOptions(opts) {
144
145
  throw new PnpmError('CONFIG_CONFLICT_LOCKFILE_ONLY_WITH_NO_LOCKFILE', `Cannot generate a ${WANTED_LOCKFILE} because lockfile is set to false`);
145
146
  }
146
147
  }
148
+ if (extendedOpts.frozenStore && extendedOpts.force) {
149
+ throw new PnpmError('CONFIG_CONFLICT_FROZEN_STORE_WITH_FORCE', 'Cannot use force together with frozenStore: --force re-imports packages into the store, which is opened read-only when frozenStore is enabled');
150
+ }
151
+ if (extendedOpts.frozenStore) {
152
+ // The side-effects cache is written into the store, which frozenStore opens
153
+ // read-only. Caching is an optimization, not a correctness requirement, so
154
+ // force it off rather than failing (the writable seed-build already
155
+ // populated it). Without this, a build under frozenStore (e.g. with the
156
+ // global virtual store disabled) would attempt a store write.
157
+ extendedOpts.sideEffectsCacheWrite = false;
158
+ }
147
159
  if (extendedOpts.userAgent.startsWith('npm/')) {
148
160
  extendedOpts.userAgent = `${extendedOpts.packageManager.name}/${extendedOpts.packageManager.version} ${extendedOpts.userAgent}`;
149
161
  }
@@ -15,7 +15,7 @@ import { getContext } from '@pnpm/installing.context';
15
15
  import { getWantedDependencies, resolveDependencies, } from '@pnpm/installing.deps-resolver';
16
16
  import { extendProjectsWithTargetDirs, headlessInstall } from '@pnpm/installing.deps-restorer';
17
17
  import { writeModulesManifest } from '@pnpm/installing.modules-yaml';
18
- import { cleanGitBranchLockfiles, getWantedLockfileName, isEmptyLockfile, readWantedLockfileFile, writeCurrentLockfile, writeLockfiles, writeWantedLockfile, } from '@pnpm/lockfile.fs';
18
+ import { cleanGitBranchLockfiles, getWantedLockfileName, isEmptyLockfile, readEnvLockfile, readWantedLockfile, readWantedLockfileFile, writeCurrentLockfile, writeEnvLockfile, writeLockfiles, writeWantedLockfile, } from '@pnpm/lockfile.fs';
19
19
  import { getPreferredVersionsFromLockfileAndManifests } from '@pnpm/lockfile.preferred-versions';
20
20
  import { calcPatchHashes, createOverridesMapFromParsed, getOutdatedLockfileSetting, } from '@pnpm/lockfile.settings-checker';
21
21
  import { writePnpFile } from '@pnpm/lockfile.to-pnp';
@@ -168,15 +168,24 @@ export async function mutateModules(projects, maybeOpts) {
168
168
  // attaching their own verifiers). The threat model is a lockfile that
169
169
  // someone else resolved — committed to the repo, restored from a CI
170
170
  // cache, etc. — bypassing the local resolver's policy filters; the local
171
- // resolver's own filters already cover fresh resolution. We run this
172
- // exactly once, right after the lockfile is loaded from disk, before any
173
- // path branches.
171
+ // resolver's own filters already cover fresh resolution.
174
172
  //
175
- // Skipped when we already know pacquet will run the install: pacquet's
176
- // frozen-install path applies the same resolver-policy gate (port of
177
- // this function), so re-running here would duplicate the work and
178
- // for `minimumReleaseAge` in strict mode each lockfile entry is an
179
- // HTTP probe.
173
+ // The verification is kicked off here, right after the lockfile is loaded,
174
+ // but not awaited inline it would otherwise block every later install
175
+ // stage on per-entry registry round trips. Its synchronous prologue (cache
176
+ // lookup, lockfile hashing, candidate collection) runs now against the
177
+ // pristine lockfile, so the async fan-out reads a stable snapshot even
178
+ // while the install mutates `ctx.wantedLockfile` concurrently. The verdict
179
+ // is reconciled with the install in `settleInstall`: a failure aborts the
180
+ // install even mid-flight, and an install that finishes first is held back
181
+ // until the verdict arrives.
182
+ //
183
+ // Skipped when we already know pacquet will run the install: pacquet
184
+ // applies the same resolver-policy gate (port of this function) whether
185
+ // it materializes a frozen lockfile or re-resolves from the manifests,
186
+ // so re-running here would duplicate the work — and for
187
+ // `minimumReleaseAge` in strict mode each lockfile entry is an HTTP
188
+ // probe.
180
189
  //
181
190
  // The predicate mirrors every short-circuit `tryFrozenInstall` checks
182
191
  // before reaching the pacquet branch: anything that would make it
@@ -186,13 +195,28 @@ export async function mutateModules(projects, maybeOpts) {
186
195
  // isn't known here — so verification still runs in that window, the
187
196
  // duplicate is bounded to it.
188
197
  const willDelegateToPacquet = opts.runPacquet != null &&
198
+ opts.useLockfile &&
199
+ !opts.useGitBranchLockfile &&
200
+ !opts.mergeGitBranchLockfiles &&
201
+ opts.lockfileCheck == null &&
202
+ opts.enableModulesDir &&
189
203
  installsOnly &&
190
204
  !opts.lockfileOnly &&
191
205
  !opts.fixLockfile &&
192
206
  !opts.dedupe &&
193
207
  !ctx.lockfileHadConflicts &&
194
- ctx.existsNonEmptyWantedLockfile &&
195
- (opts.frozenLockfile === true || opts.frozenLockfileIfExists === true);
208
+ (
209
+ // Frozen materialization: pacquet reads the existing lockfile and
210
+ // re-applies the resolver-policy gate as it walks it.
211
+ (ctx.existsNonEmptyWantedLockfile &&
212
+ (opts.frozenLockfile === true || opts.frozenLockfileIfExists === true)) ||
213
+ // Resolving install: pacquet (>= 0.11.7) re-resolves from the
214
+ // manifests itself — applying the policy during fresh resolution —
215
+ // so the existing lockfile entries verified here would just be
216
+ // discarded. If a policy handler is active, keep resolution in pnpm
217
+ // so violations can be returned to the command layer.
218
+ (opts.saveLockfile && opts.runPacquet.supportsResolution && opts.frozenLockfile !== true && opts.nodeLinker !== 'hoisted' && opts.handleResolutionPolicyViolations == null));
219
+ let verifyLockfilePromise;
196
220
  if (!willDelegateToPacquet && !opts.trustLockfile) {
197
221
  const cacheActive = opts.cacheDir != null && opts.resolutionVerifiers.length > 0;
198
222
  const wantedLockfilePath = cacheActive
@@ -201,21 +225,21 @@ export async function mutateModules(projects, maybeOpts) {
201
225
  mergeGitBranchLockfiles: opts.mergeGitBranchLockfiles,
202
226
  }))
203
227
  : undefined;
204
- try {
205
- await verifyLockfileResolutions(ctx.wantedLockfile, opts.resolutionVerifiers, {
206
- cacheDir: opts.cacheDir,
207
- lockfilePath: wantedLockfilePath,
208
- });
209
- }
210
- catch (err) {
211
- // verifyLockfileResolutions is the one throw site in this function
212
- // that's part of normal user-facing operation (a rejected lockfile);
213
- // other throws here are unexpected. Detach the reporter listener so
214
- // long-lived processes don't leak it on every rejected install.
215
- detachReporter();
216
- throw err;
217
- }
228
+ verifyLockfilePromise = verifyLockfileResolutions(ctx.wantedLockfile, opts.resolutionVerifiers, {
229
+ cacheDir: opts.cacheDir,
230
+ lockfilePath: wantedLockfilePath,
231
+ });
232
+ // Keep the rejection from going unhandled in the window before
233
+ // `settleInstall` awaits the verdict — a preResolution hook or the
234
+ // install kickoff below could throw and bail out before we get there.
235
+ verifyLockfilePromise.catch(() => { });
218
236
  }
237
+ // Gate passed down to the build phase: fetching and linking overlap with
238
+ // verification, but no dependency lifecycle script may run until the verdict
239
+ // is in. Awaiting the promise here throws if verification failed, aborting
240
+ // before any script executes. `settleInstall` is the catch-all that still
241
+ // reconciles the verdict on paths that never reach the build phase.
242
+ const verifyLockfile = verifyLockfilePromise && (() => verifyLockfilePromise);
219
243
  if (opts.hooks.preResolution) {
220
244
  for (const preResolution of opts.hooks.preResolution) {
221
245
  // eslint-disable-next-line no-await-in-loop
@@ -251,7 +275,7 @@ export async function mutateModules(projects, maybeOpts) {
251
275
  }
252
276
  }
253
277
  }
254
- const result = await _install();
278
+ const result = await settleInstall(_install(), verifyLockfilePromise);
255
279
  // @ts-expect-error
256
280
  if (global['verifiedFileIntegrity'] > 1000) {
257
281
  // @ts-expect-error
@@ -302,6 +326,31 @@ export async function mutateModules(projects, maybeOpts) {
302
326
  ignoredBuilds,
303
327
  resolutionPolicyViolations: result.resolutionPolicyViolations ?? [],
304
328
  };
329
+ // Reconcile the install with the lockfile verification that runs alongside
330
+ // it. The verification verdict is awaited first so it takes precedence and
331
+ // aborts as soon as it fails, even while the install is still in flight —
332
+ // matching the original sequencing where verification gated the install, so
333
+ // a rejected lockfile surfaces its own error rather than whatever the
334
+ // concurrent install happened to throw. Only once verification passes is the
335
+ // install's result (or error) surfaced. detachReporter mirrors the success
336
+ // path's cleanup so a long-lived process doesn't leak the stream listener on
337
+ // a rejected install.
338
+ async function settleInstall(install, verification) {
339
+ if (verification == null)
340
+ return install;
341
+ // Handle the install's eventual rejection up front so a fail-fast
342
+ // verification throw below doesn't leave the still-running install
343
+ // unhandled.
344
+ install.catch(() => { });
345
+ try {
346
+ await verification;
347
+ return await install;
348
+ }
349
+ catch (err) {
350
+ detachReporter();
351
+ throw err;
352
+ }
353
+ }
305
354
  async function _install() {
306
355
  const scriptsOpts = {
307
356
  extraBinPaths: opts.extraBinPaths,
@@ -589,6 +638,7 @@ export async function mutateModules(projects, maybeOpts) {
589
638
  scriptsOpts,
590
639
  updateLockfileMinorVersion: true,
591
640
  patchedDependencies: patchGroups,
641
+ verifyLockfile,
592
642
  });
593
643
  return {
594
644
  updatedCatalogs: result.updatedCatalogs,
@@ -717,9 +767,9 @@ Note that in CI environments, this setting is enabled by default.`,
717
767
  else {
718
768
  logger.info({ message: 'Lockfile is up to date, resolution step is skipped', prefix: opts.lockfileDir });
719
769
  }
720
- if (opts.runPacquet != null) {
770
+ if (opts.runPacquet != null && opts.useLockfile && !opts.useGitBranchLockfile && !opts.mergeGitBranchLockfiles && opts.lockfileCheck == null && opts.enableModulesDir) {
721
771
  try {
722
- await opts.runPacquet();
772
+ await opts.runPacquet.run();
723
773
  }
724
774
  catch (err) {
725
775
  // Same reasoning as the verifyLockfileResolutions catch above: this
@@ -755,6 +805,7 @@ Note that in CI environments, this setting is enabled by default.`,
755
805
  pruneVirtualStore,
756
806
  wantedLockfile: maybeOpts.ignorePackageManifest ? undefined : ctx.wantedLockfile,
757
807
  useLockfile: opts.useLockfile && ctx.wantedLockfileIsModified,
808
+ verifyLockfile,
758
809
  });
759
810
  if (opts.useLockfile && opts.saveLockfile && opts.mergeGitBranchLockfiles ||
760
811
  !upToDateLockfileMajorVersion && !opts.frozenLockfile) {
@@ -1189,6 +1240,8 @@ const _installInContext = async (projects, ctx, opts) => {
1189
1240
  ...makeNodeRequireOption(path.join(opts.lockfileDir, '.pnp.cjs')),
1190
1241
  };
1191
1242
  }
1243
+ // Dependency lifecycle scripts must not run on an unverified lockfile.
1244
+ await opts.verifyLockfile?.();
1192
1245
  ignoredBuilds = (await buildModules(dependenciesGraph, rootNodes, {
1193
1246
  allowBuild: opts.allowBuild,
1194
1247
  childConcurrency: opts.childConcurrency,
@@ -1210,6 +1263,7 @@ const _installInContext = async (projects, ctx, opts) => {
1210
1263
  unsafePerm: opts.unsafePerm,
1211
1264
  userAgent: opts.userAgent,
1212
1265
  enableGlobalVirtualStore: opts.enableGlobalVirtualStore,
1266
+ frozenStore: opts.frozenStore,
1213
1267
  })).ignoredBuilds;
1214
1268
  if (ctx.modulesFile?.ignoredBuilds?.size) {
1215
1269
  ignoredBuilds ??= new Set();
@@ -1332,6 +1386,10 @@ const _installInContext = async (projects, ctx, opts) => {
1332
1386
  };
1333
1387
  }
1334
1388
  const projectsToBeBuilt = projectsWithTargetDirs.filter(({ mutation }) => mutation === 'install');
1389
+ // The projects' own lifecycle scripts import dependency code linked
1390
+ // from the lockfile, so they are held to the same gate as dependency
1391
+ // builds — also when no new dep paths made the buildModules branch run.
1392
+ await opts.verifyLockfile?.();
1335
1393
  await runLifecycleHooksConcurrently(['preinstall', 'install', 'postinstall', 'preprepare', 'prepare', 'postprepare'], projectsToBeBuilt, opts.childConcurrency, opts.scriptsOpts);
1336
1394
  }
1337
1395
  }
@@ -1413,6 +1471,28 @@ const _installInContext = async (projects, ctx, opts) => {
1413
1471
  function allMutationsAreInstalls(projects) {
1414
1472
  return projects.every((project) => project.mutation === 'install' && !project.update && !project.updateMatching);
1415
1473
  }
1474
+ /**
1475
+ * The `InstallFunctionResult` for an install pacquet resolved and
1476
+ * materialized end-to-end. pacquet wrote `pnpm-lock.yaml` and the
1477
+ * `node_modules` tree itself. `ctx.wantedLockfile` has already been
1478
+ * refreshed from disk, and pacquet reports its own stats / ignored-builds
1479
+ * via NDJSON, so the structured `stats` / `ignoredBuilds` fall back to
1480
+ * their no-op defaults. Resolution-policy handlers are guarded out before
1481
+ * this path, so there are no command-layer policy violations to return.
1482
+ * Manifests are returned unchanged — this path only runs for plain
1483
+ * installs, which don't rewrite `package.json`.
1484
+ */
1485
+ function pacquetResolveResult(projects, ctx) {
1486
+ return {
1487
+ newLockfile: ctx.wantedLockfile,
1488
+ projects: projects.map((project) => ({
1489
+ manifest: project.originalManifest ?? project.manifest,
1490
+ rootDir: project.rootDir,
1491
+ })),
1492
+ depsRequiringBuild: [],
1493
+ resolutionPolicyViolations: [],
1494
+ };
1495
+ }
1416
1496
  /**
1417
1497
  * Run the pacquet binary if it's configured, otherwise run the JS
1418
1498
  * `headlessInstall`. Callers can hand off any code path that materializes
@@ -1429,13 +1509,17 @@ function allMutationsAreInstalls(projects) {
1429
1509
  * stats record and a no-op ignoredBuilds iteration).
1430
1510
  */
1431
1511
  async function materializeOrDelegate(opts, runHeadlessInstall) {
1432
- if (opts.runPacquet != null) {
1512
+ if (opts.runPacquet != null &&
1513
+ opts.useLockfile !== false &&
1514
+ opts.saveLockfile !== false &&
1515
+ opts.useGitBranchLockfile !== true &&
1516
+ opts.mergeGitBranchLockfiles !== true) {
1433
1517
  // Reached only from the resolve-then-materialize call sites
1434
1518
  // (workspace-partial, hoisted-linker, pnpr server install). Each ran a
1435
1519
  // lockfileOnly resolve pass that emitted one
1436
1520
  // `pnpm:progress status:resolved` per package, so pacquet's
1437
1521
  // duplicate `resolved` events would double the reporter's count.
1438
- await opts.runPacquet({ filterResolvedProgress: true });
1522
+ await opts.runPacquet.run({ filterResolvedProgress: true });
1439
1523
  return {};
1440
1524
  }
1441
1525
  return runHeadlessInstall();
@@ -1446,7 +1530,7 @@ const installInContext = async (projects, ctx, opts) => {
1446
1530
  if (!opts.frozenLockfile && opts.useLockfile) {
1447
1531
  const allProjectsLocatedInsideWorkspace = Object.values(ctx.projects)
1448
1532
  .filter((project) => isPathInsideWorkspace(project.rootDirRealPath ?? project.rootDir));
1449
- if (allProjectsLocatedInsideWorkspace.length > projects.length) {
1533
+ if (allProjectsLocatedInsideWorkspace.length > projects.length && opts.lockfileCheck == null && opts.enableModulesDir) {
1450
1534
  const newProjects = [...projects];
1451
1535
  const getWantedDepsOpts = {
1452
1536
  autoInstallPeers: opts.autoInstallPeers,
@@ -1498,7 +1582,7 @@ const installInContext = async (projects, ctx, opts) => {
1498
1582
  };
1499
1583
  }
1500
1584
  }
1501
- if (opts.nodeLinker === 'hoisted' && !opts.lockfileOnly) {
1585
+ if (opts.nodeLinker === 'hoisted' && !opts.lockfileOnly && opts.lockfileCheck == null && opts.enableModulesDir) {
1502
1586
  const result = await _installInContext(projects, ctx, {
1503
1587
  ...opts,
1504
1588
  lockfileOnly: true,
@@ -1524,20 +1608,69 @@ const installInContext = async (projects, ctx, opts) => {
1524
1608
  ignoredBuilds,
1525
1609
  };
1526
1610
  }
1527
- // Isolated `nodeLinker` (the default) with a non-frozen install:
1528
- // pacquet doesn't ship a resolver yet, so split the install in two —
1529
- // ask `_installInContext` for a `lockfileOnly` resolve pass (writes
1530
- // `pnpm-lock.yaml`), then hand the freshly-written lockfile to
1531
- // pacquet for the fetch / import / link / build phases. The frozen
1532
- // branch is handled earlier in `tryFrozenInstall`; the hoisted
1533
- // branch above already runs the same resolve-then-materialize
1534
- // sequence (it had to even before pacquet existed). When no pacquet
1535
- // is configured this falls through to the full single-pass install.
1536
- if (opts.runPacquet != null && !opts.lockfileOnly) {
1611
+ // Isolated `nodeLinker` (the default) with a non-frozen install.
1612
+ // The frozen branch is handled earlier in `tryFrozenInstall`; the
1613
+ // 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) {
1615
+ // pacquet >= 0.11.7 resolves itself: hand it the whole install
1616
+ // (resolve + fetch + import + link + build, writing the lockfile)
1617
+ // in a single non-frozen pass. Only for plain installs — `add` /
1618
+ // `update` / `remove` need pnpm to mutate the manifests and
1619
+ // resolve the new specs first (pacquet's `install` reads
1620
+ // package.json from disk, which pnpm hasn't rewritten yet).
1621
+ if (opts.runPacquet.supportsResolution && !opts.frozenLockfile && opts.handleResolutionPolicyViolations == null && allMutationsAreInstalls(projects)) {
1622
+ // `configDependencies` are recorded in a YAML document prepended
1623
+ // to `pnpm-lock.yaml` — purely a pnpm concept that pacquet doesn't
1624
+ // model. Capture it before pacquet rewrites the lockfile and
1625
+ // restore it afterwards (`writeEnvLockfile` re-reads pacquet's main
1626
+ // document and re-prepends the env document), otherwise the next
1627
+ // `--frozen-lockfile` install fails its config-deps freshness gate.
1628
+ // The restore runs even if pacquet fails partway: a non-zero exit can
1629
+ // still leave a rewritten lockfile behind, so the env document must be
1630
+ // put back regardless.
1631
+ const envLockfile = await readEnvLockfile(ctx.lockfileDir);
1632
+ let pacquetError;
1633
+ try {
1634
+ await opts.runPacquet.run({ resolve: true });
1635
+ }
1636
+ catch (err) {
1637
+ pacquetError = err;
1638
+ throw err;
1639
+ }
1640
+ finally {
1641
+ if (envLockfile != null) {
1642
+ await writeEnvLockfile(ctx.lockfileDir, envLockfile).catch((restoreErr) => {
1643
+ if (pacquetError == null) {
1644
+ throw restoreErr;
1645
+ }
1646
+ logger.warn({
1647
+ error: restoreErr,
1648
+ message: `Failed to restore the configDependencies document in pnpm-lock.yaml: ${restoreErr.message}`,
1649
+ prefix: ctx.lockfileDir,
1650
+ });
1651
+ });
1652
+ }
1653
+ }
1654
+ const wantedLockfile = await readWantedLockfile(ctx.lockfileDir, {
1655
+ ignoreIncompatible: opts.force || opts.ci === true,
1656
+ mergeGitBranchLockfiles: opts.mergeGitBranchLockfiles,
1657
+ useGitBranchLockfile: opts.useGitBranchLockfile,
1658
+ wantedVersions: [LOCKFILE_VERSION],
1659
+ });
1660
+ if (wantedLockfile == null) {
1661
+ throw new PnpmError('PACQUET_LOCKFILE_READ_FAILED', `pacquet did not write a readable ${WANTED_LOCKFILE}`);
1662
+ }
1663
+ ctx.wantedLockfile = wantedLockfile;
1664
+ return pacquetResolveResult(projects, ctx);
1665
+ }
1666
+ // Older pacquet can only materialize: split the install in two —
1667
+ // ask `_installInContext` for a `lockfileOnly` resolve pass (writes
1668
+ // `pnpm-lock.yaml`), then hand the freshly-written lockfile to
1669
+ // pacquet for the fetch / import / link / build phases. The resolve
1670
+ // pass emitted a `pnpm:progress status:resolved` per package; ask
1671
+ // pacquet to drop its own duplicates.
1537
1672
  const result = await _installInContext(projects, ctx, { ...opts, lockfileOnly: true });
1538
- // The resolve pass above emitted a `pnpm:progress status:resolved`
1539
- // per package; ask pacquet to drop its own duplicates.
1540
- await opts.runPacquet({ filterResolvedProgress: true });
1673
+ await opts.runPacquet.run({ filterResolvedProgress: true });
1541
1674
  return result;
1542
1675
  }
1543
1676
  return await _installInContext(projects, ctx, opts);
@@ -1811,6 +1944,15 @@ async function mutateModulesViaPnpr(projects, opts) {
1811
1944
  * and links packages into node_modules — like a normal install.
1812
1945
  */
1813
1946
  async function installViaPnprServer(manifest, rootDir, opts, allInstallProjects) {
1947
+ // The pnpr server path re-resolves and persists new `index.db` entries plus a
1948
+ // freshly written lockfile, so it inherently writes the store. `frozenStore`
1949
+ // promises the store is complete and read-only, so the two are mutually
1950
+ // exclusive — and the unconditional pnpr gate means this path runs even under
1951
+ // `--offline --frozen-lockfile`, so refuse up front with guidance instead of
1952
+ // crashing later on the read-only `index.db` open.
1953
+ if (opts.frozenStore) {
1954
+ throw new PnpmError('FROZEN_STORE_INCOMPATIBLE_WITH_PNPR', 'The pnpr server resolves dependencies and writes new entries into the store, which is opened read-only when frozenStore is enabled.', { hint: 'Disable the pnpr server (unset `--pnpr-server` / `pnprServer` in pnpm-workspace.yaml) so the install reads from the existing store, or unset `frozenStore` to allow store writes.' });
1955
+ }
1814
1956
  // The pnpr server path skips client-side resolution, so resolver-side policies
1815
1957
  // can't be enforced locally. `minimumReleaseAge` is forwarded to the
1816
1958
  // pnpr server and enforced server-side. `trustPolicy` has no server-side
@@ -1820,7 +1962,7 @@ async function installViaPnprServer(manifest, rootDir, opts, allInstallProjects)
1820
1962
  throw new PnpmError('TRUST_POLICY_INCOMPATIBLE_WITH_PNPR', 'The pnpr server does not yet enforce `trustPolicy: no-downgrade`, so running an install through it under this policy would produce a lockfile that the local verifier rejects.', { hint: 'Unset `trustPolicy` for this install, or disable the pnpr server (unset `--pnpr-server` / `pnprServer` in pnpm-workspace.yaml) so resolution runs locally and the trust check applies.' });
1821
1963
  }
1822
1964
  const { resolveViaPnprServer } = await import('@pnpm/pnpr.client');
1823
- const { createGetAuthHeaderByURI, getAuthHeadersFromCreds } = await import('@pnpm/network.auth-header');
1965
+ const { createGetAuthHeaderByURI, getAuthHeadersByScope, getAuthHeadersFromCreds } = await import('@pnpm/network.auth-header');
1824
1966
  // Forward the whole credential map (the registries a graph touches
1825
1967
  // aren't known up front), so the server attaches the right token per
1826
1968
  // URL. `authorization` also identifies the caller to pnpr's gate.
@@ -1856,7 +1998,7 @@ async function installViaPnprServer(manifest, rootDir, opts, allInstallProjects)
1856
1998
  projects: projectsList,
1857
1999
  registry: opts.registries?.default,
1858
2000
  namedRegistries: opts.namedRegistries,
1859
- authHeaders: forwardedAuthHeaders,
2001
+ authHeaders: getAuthHeadersByScope(forwardedAuthHeaders),
1860
2002
  authorization: pnprAuthorization,
1861
2003
  overrides: opts.overrides,
1862
2004
  minimumReleaseAge: opts.minimumReleaseAge,
@@ -1,5 +1,5 @@
1
1
  import { hashObject } from '@pnpm/crypto.object-hasher';
2
- import { withResolutionShapeCacheIdentity } from './verifyLockfileResolutions.js';
2
+ import { withOfflineCheckCacheIdentities } from './verifyLockfileResolutions.js';
3
3
  import { recordVerification } from './verifyLockfileResolutionsCache.js';
4
4
  /**
5
5
  * Records the post-resolution lockfile as verified so the next install
@@ -18,7 +18,7 @@ export function recordLockfileVerified(opts) {
18
18
  return;
19
19
  recordVerification(opts.cacheDir, {
20
20
  lockfilePath: opts.lockfilePath,
21
- verifiers: withResolutionShapeCacheIdentity(opts.resolutionVerifiers),
21
+ verifiers: withOfflineCheckCacheIdentities(opts.resolutionVerifiers),
22
22
  hashLockfile: () => hashObject(opts.lockfile),
23
23
  });
24
24
  }
@@ -3,15 +3,19 @@ import type { ResolutionPolicyViolation, ResolutionVerifier } from '@pnpm/resolv
3
3
  import { type VerifierCacheIdentity } from './verifyLockfileResolutionsCache.js';
4
4
  export type { ResolutionPolicyViolation };
5
5
  export declare const RESOLUTION_SHAPE_MISMATCH_VIOLATION_CODE = "RESOLUTION_SHAPE_MISMATCH";
6
+ export declare const INVALID_DEPENDENCY_ALIAS_CODE = "INVALID_DEPENDENCY_NAME";
6
7
  /**
7
8
  * Every verifier list that flows into the verification cache must carry
8
- * the resolution-shape identity, so records written before the shape rule
9
- * existed cannot stat-fast-path around it. Used by the gate itself and by
10
- * {@link recordLockfileVerified}, whose freshly-resolved lockfile satisfies
11
- * the shape invariant by construction (the writer derives every key from
12
- * the resolution it just produced).
9
+ * the always-on offline structural checks' identities, so a record
10
+ * written before one of those rules existed cannot stat-fast-path around
11
+ * it its missing flag fails `canTrustPastCheck`, forcing a
12
+ * re-verification that runs the new check. Used by the gate itself and by
13
+ * {@link recordLockfileVerified}, whose freshly-resolved lockfile
14
+ * satisfies these invariants by construction (the resolver validates
15
+ * aliases at manifest-read time and derives every resolution key from the
16
+ * resolution it just produced).
13
17
  */
14
- export declare function withResolutionShapeCacheIdentity(verifiers: readonly VerifierCacheIdentity[]): VerifierCacheIdentity[];
18
+ export declare function withOfflineCheckCacheIdentities(verifiers: readonly VerifierCacheIdentity[]): VerifierCacheIdentity[];
15
19
  export interface VerifyLockfileResolutionsOptions {
16
20
  concurrency?: number;
17
21
  /**
@@ -1,6 +1,7 @@
1
1
  import { lockfileVerificationLogger } from '@pnpm/core-loggers';
2
2
  import { hashObject } from '@pnpm/crypto.object-hasher';
3
3
  import { PnpmError } from '@pnpm/error';
4
+ import { isValidDependencyAlias } from '@pnpm/installing.deps-resolver';
4
5
  import { isGitHostedTarballUrl, nameVerFromPkgSnapshot } from '@pnpm/lockfile.utils';
5
6
  import pLimit from 'p-limit';
6
7
  import { recordVerification, tryLockfileVerificationCache, } from './verifyLockfileResolutionsCache.js';
@@ -8,25 +9,34 @@ import { recordVerification, tryLockfileVerificationCache, } from './verifyLockf
8
9
  // (e.g. a poisoned lockfile) doesn't flood the terminal / CI log; the full
9
10
  // count is in the header and the remainder is summarized at the end.
10
11
  const MAX_VIOLATIONS_TO_PRINT = 20;
11
- // 16 mirrors the floor of pnpm's package-requester network-concurrency
12
- // (Math.min(64, Math.max(workers*3, 16))); keep them aligned so the
12
+ // 64 mirrors the floor of pnpm's package-requester network-concurrency
13
+ // (Math.min(96, Math.max(workers*3, 64))); keep them aligned so the
13
14
  // verification pass doesn't push past what the rest of the install respects.
14
- const DEFAULT_CONCURRENCY = 16;
15
+ const DEFAULT_CONCURRENCY = 64;
15
16
  export const RESOLUTION_SHAPE_MISMATCH_VIOLATION_CODE = 'RESOLUTION_SHAPE_MISMATCH';
17
+ // Same code the sink-level guards (`safeJoinModulesDir`) throw.
18
+ export const INVALID_DEPENDENCY_ALIAS_CODE = 'INVALID_DEPENDENCY_NAME';
16
19
  const RESOLUTION_SHAPE_CACHE_IDENTITY = {
17
20
  policy: { resolutionShapeCheck: true },
18
21
  canTrustPastCheck: (cached) => cached.resolutionShapeCheck === true,
19
22
  };
23
+ const DEPENDENCY_ALIAS_CACHE_IDENTITY = {
24
+ policy: { dependencyAliasCheck: true },
25
+ canTrustPastCheck: (cached) => cached.dependencyAliasCheck === true,
26
+ };
20
27
  /**
21
28
  * Every verifier list that flows into the verification cache must carry
22
- * the resolution-shape identity, so records written before the shape rule
23
- * existed cannot stat-fast-path around it. Used by the gate itself and by
24
- * {@link recordLockfileVerified}, whose freshly-resolved lockfile satisfies
25
- * the shape invariant by construction (the writer derives every key from
26
- * the resolution it just produced).
29
+ * the always-on offline structural checks' identities, so a record
30
+ * written before one of those rules existed cannot stat-fast-path around
31
+ * it its missing flag fails `canTrustPastCheck`, forcing a
32
+ * re-verification that runs the new check. Used by the gate itself and by
33
+ * {@link recordLockfileVerified}, whose freshly-resolved lockfile
34
+ * satisfies these invariants by construction (the resolver validates
35
+ * aliases at manifest-read time and derives every resolution key from the
36
+ * resolution it just produced).
27
37
  */
28
- export function withResolutionShapeCacheIdentity(verifiers) {
29
- return [...verifiers, RESOLUTION_SHAPE_CACHE_IDENTITY];
38
+ export function withOfflineCheckCacheIdentities(verifiers) {
39
+ return [...verifiers, RESOLUTION_SHAPE_CACHE_IDENTITY, DEPENDENCY_ALIAS_CACHE_IDENTITY];
30
40
  }
31
41
  /**
32
42
  * Policy-neutral pass that asks every resolver-supplied
@@ -65,7 +75,7 @@ export async function verifyLockfileResolutions(lockfile, verifiers, options) {
65
75
  const cache = options?.cacheDir && options?.lockfilePath
66
76
  ? { cacheDir: options.cacheDir, lockfilePath: options.lockfilePath }
67
77
  : undefined;
68
- const cacheVerifiers = withResolutionShapeCacheIdentity(verifiers);
78
+ const cacheVerifiers = withOfflineCheckCacheIdentities(verifiers);
69
79
  let cachePrecomputed;
70
80
  // hashObject streams and is key-order-stable, unlike JSON.stringify.
71
81
  let cachedHash;
@@ -80,18 +90,34 @@ export async function verifyLockfileResolutions(lockfile, verifiers, options) {
80
90
  verifiers: cacheVerifiers,
81
91
  hashLockfile,
82
92
  });
83
- if (result.hit)
93
+ if (result.hit) {
94
+ // A silent short-circuit looks like the policy gate never ran
95
+ // (pnpm/pnpm#12324), so surface the reused verdict — but only
96
+ // when policy verifiers are active; the shape-only run that
97
+ // every install performs stays quiet.
98
+ if (verifiers.length > 0) {
99
+ lockfileVerificationLogger.debug({
100
+ status: 'cached',
101
+ verifiedAt: result.verifiedAt,
102
+ lockfilePath: options?.lockfilePath,
103
+ });
104
+ }
84
105
  return;
106
+ }
85
107
  cachePrecomputed = result.precomputed;
86
108
  }
87
109
  // Emit started/done around the actual verification pass — the
88
110
  // round-trip can be slow on a cold registry cache, and the cached
89
- // short-circuit above doesn't reach this branch, so a user only
90
- // sees these messages on installs that are doing real work.
111
+ // short-circuit above announces itself with its own `cached` event,
112
+ // so a user only sees these messages on installs that are doing
113
+ // real work.
91
114
  // A degenerate lockfile where every snapshot fails the
92
115
  // name/version extraction (so candidates is empty) skips emission
93
116
  // entirely — no work, no noise.
94
- const { candidates, shapeViolations } = collectCandidates(lockfile);
117
+ const { candidates, shapeViolations, invalidAliases } = collectCandidates(lockfile);
118
+ if (invalidAliases.length > 0) {
119
+ throw buildInvalidAliasError(invalidAliases);
120
+ }
95
121
  if (shapeViolations.length > 0) {
96
122
  throw buildVerificationError(shapeViolations);
97
123
  }
@@ -145,6 +171,19 @@ export async function verifyLockfileResolutions(lockfile, verifiers, options) {
145
171
  });
146
172
  }
147
173
  }
174
+ function buildInvalidAliasError(aliases) {
175
+ const sorted = [...aliases].sort();
176
+ const visible = sorted.slice(0, MAX_VIOLATIONS_TO_PRINT);
177
+ const omitted = sorted.length - visible.length;
178
+ const breakdown = visible.map((alias) => ` ${JSON.stringify(alias)}`).join('\n');
179
+ const details = omitted > 0 ? `${breakdown}\n …and ${omitted} more` : breakdown;
180
+ const plural = aliases.length === 1 ? 'alias' : 'aliases';
181
+ return new PnpmError(INVALID_DEPENDENCY_ALIAS_CODE, `The lockfile contains ${aliases.length} dependency ${plural} that are not valid package names:\n${details}`, {
182
+ hint: 'A dependency alias becomes a directory under node_modules, so it must be a valid npm package name — a single `name` or `@scope/name` with no leading `.` or `_`, and not a reserved name such as `node_modules`. ' +
183
+ 'An alias containing path-traversal segments or a reserved name such as `.bin` or `.pnpm` could make an install write outside the intended directory or overwrite pnpm-owned layout. ' +
184
+ 'This usually means the lockfile was tampered with — inspect recent changes to pnpm-lock.yaml before trusting it.',
185
+ });
186
+ }
148
187
  function buildVerificationError(violations) {
149
188
  // Stable order so the error output is deterministic.
150
189
  violations.sort((a, b) => `${a.name}@${a.version}`.localeCompare(`${b.name}@${b.version}`));
@@ -253,7 +292,17 @@ function isRegistryShapedResolution(resolution) {
253
292
  function collectCandidates(lockfile) {
254
293
  const candidates = new Map();
255
294
  const shapeViolations = [];
295
+ // The importer alias maps are the one source not reached by the
296
+ // package loop below, so they're scanned here.
297
+ const invalidAliases = new Set();
298
+ for (const importer of Object.values(lockfile.importers ?? {})) {
299
+ pushInvalidAliases(importer.dependencies, invalidAliases);
300
+ pushInvalidAliases(importer.devDependencies, invalidAliases);
301
+ pushInvalidAliases(importer.optionalDependencies, invalidAliases);
302
+ }
256
303
  for (const [depPath, snapshot] of Object.entries(lockfile.packages ?? {})) {
304
+ pushInvalidAliases(snapshot.dependencies, invalidAliases);
305
+ pushInvalidAliases(snapshot.optionalDependencies, invalidAliases);
257
306
  const { name, version, nonSemverVersion } = nameVerFromPkgSnapshot(depPath, snapshot);
258
307
  if (!name || !version)
259
308
  continue;
@@ -279,7 +328,21 @@ function collectCandidates(lockfile) {
279
328
  resolution: snapshot.resolution,
280
329
  });
281
330
  }
282
- return { candidates, shapeViolations };
331
+ return { candidates, shapeViolations, invalidAliases: Array.from(invalidAliases) };
332
+ }
333
+ /**
334
+ * Add every key of `deps` that is not a valid {@link isValidDependencyAlias}
335
+ * to `invalid`. Only pass maps whose keys become `node_modules/<alias>`
336
+ * directories — not `overrides` (`foo>bar` selectors) or
337
+ * `patchedDependencies` (`name@version` keys).
338
+ */
339
+ function pushInvalidAliases(deps, invalid) {
340
+ if (deps == null)
341
+ return;
342
+ for (const alias of Object.keys(deps)) {
343
+ if (!isValidDependencyAlias(alias))
344
+ invalid.add(alias);
345
+ }
283
346
  }
284
347
  async function iterateLockfileViolations(candidates, verifiers, concurrency) {
285
348
  const violations = [];
@@ -9,6 +9,13 @@ import type { ResolutionVerifier } from '@pnpm/resolving.resolver-base';
9
9
  export type VerifierCacheIdentity = Pick<ResolutionVerifier, 'policy' | 'canTrustPastCheck'>;
10
10
  export interface CacheLookupResult {
11
11
  hit: boolean;
12
+ /**
13
+ * ISO-8601 timestamp of the verification run the hit is reusing.
14
+ * Set only on a hit, and only when the record carries a usable
15
+ * timestamp (records written before the field existed normalize to
16
+ * an empty string and surface as `undefined` here).
17
+ */
18
+ verifiedAt?: string;
12
19
  /**
13
20
  * stat + hash already computed during the lookup. When the caller
14
21
  * follows up with {@link recordVerification} after running the gate,
@@ -159,8 +159,10 @@ export function tryLockfileVerificationCache(cacheDir, key) {
159
159
  // hash without reading the file. Microseconds.
160
160
  const byPathRecord = indexes.byPath.get(key.lockfilePath);
161
161
  if (byPathRecord && statMatches(stat, byPathRecord.lockfile)) {
162
+ const hit = everyVerifierTrustsCachedRun(byPathRecord, key.verifiers);
162
163
  return {
163
- hit: everyVerifierTrustsCachedRun(byPathRecord, key.verifiers),
164
+ hit,
165
+ verifiedAt: hit ? byPathRecord.verifiedAt || undefined : undefined,
164
166
  // The stat-match implies the file content is unchanged since the
165
167
  // cached record was written, so its hash is still correct. Pass
166
168
  // it through to skip hashing on the miss-then-record path.
@@ -190,7 +192,7 @@ export function tryLockfileVerificationCache(cacheDir, key) {
190
192
  ...byHashRecord,
191
193
  lockfile: { ...byHashRecord.lockfile, path: key.lockfilePath, size: stat.size, mtimeNs: stat.mtimeNs, inode: stat.inode },
192
194
  });
193
- return { hit: true, precomputed: { stat, hash } };
195
+ return { hit: true, verifiedAt: byHashRecord.verifiedAt || undefined, precomputed: { stat, hash } };
194
196
  }
195
197
  function everyVerifierTrustsCachedRun(record, verifiers) {
196
198
  for (const verifier of verifiers) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/installing.deps-installer",
3
- "version": "1101.8.0",
3
+ "version": "1102.0.0",
4
4
  "description": "Fast, disk space efficient installation engine",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -52,7 +52,7 @@
52
52
  "dependencies": {
53
53
  "@inquirer/prompts": "^8.4.3",
54
54
  "@pnpm/npm-package-arg": "^2.0.0",
55
- "@pnpm/util.lex-comparator": "^3.0.2",
55
+ "@pnpm/util.lex-comparator": "^4.0.1",
56
56
  "@zkochan/rimraf": "^4.0.0",
57
57
  "is-inner-link": "^5.0.0",
58
58
  "is-subdir": "^2.0.0",
@@ -64,104 +64,104 @@
64
64
  "path-exists": "^5.0.0",
65
65
  "ramda": "npm:@pnpm/ramda@0.28.1",
66
66
  "run-groups": "^5.0.0",
67
- "semver": "^7.8.1",
68
- "@pnpm/bins.linker": "1100.0.12",
69
- "@pnpm/building.after-install": "1101.0.20",
67
+ "semver": "^7.8.4",
68
+ "@pnpm/building.after-install": "1102.0.0",
69
+ "@pnpm/building.during-install": "1102.0.0",
70
70
  "@pnpm/catalogs.protocol-parser": "1100.0.0",
71
- "@pnpm/building.during-install": "1101.0.17",
72
- "@pnpm/building.policy": "1100.0.9",
73
- "@pnpm/catalogs.types": "1100.0.0",
71
+ "@pnpm/bins.remover": "1100.0.10",
74
72
  "@pnpm/catalogs.resolver": "1100.0.0",
75
- "@pnpm/config.normalize-registries": "1100.0.7",
76
- "@pnpm/config.matcher": "1100.0.1",
77
- "@pnpm/core-loggers": "1100.1.4",
73
+ "@pnpm/config.normalize-registries": "1100.0.8",
74
+ "@pnpm/config.parse-overrides": "1100.0.1",
75
+ "@pnpm/catalogs.types": "1100.0.0",
76
+ "@pnpm/core-loggers": "1100.2.1",
78
77
  "@pnpm/constants": "1100.0.0",
79
- "@pnpm/crypto.hash": "1100.0.1",
80
- "@pnpm/deps.graph-hasher": "1100.2.4",
81
- "@pnpm/crypto.object-hasher": "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
81
  "@pnpm/deps.graph-sequencer": "1100.0.0",
83
- "@pnpm/config.parse-overrides": "1100.0.1",
82
+ "@pnpm/building.policy": "1100.0.10",
83
+ "@pnpm/bins.linker": "1100.0.14",
84
+ "@pnpm/crypto.object-hasher": "1100.0.0",
85
+ "@pnpm/exec.lifecycle": "1100.0.18",
86
+ "@pnpm/hooks.read-package-hook": "1100.0.8",
87
+ "@pnpm/fs.symlink-dependency": "1100.0.10",
88
+ "@pnpm/hooks.types": "1100.0.12",
84
89
  "@pnpm/error": "1100.0.0",
90
+ "@pnpm/installing.context": "1100.0.18",
91
+ "@pnpm/installing.linking.direct-dep-linker": "1100.0.10",
92
+ "@pnpm/installing.deps-resolver": "1100.2.3",
93
+ "@pnpm/crypto.hash": "1100.0.1",
85
94
  "@pnpm/fs.read-modules-dir": "1100.0.1",
86
- "@pnpm/exec.lifecycle": "1100.0.16",
87
- "@pnpm/fs.symlink-dependency": "1100.0.8",
88
- "@pnpm/hooks.read-package-hook": "1100.0.7",
89
- "@pnpm/installing.deps-restorer": "1101.1.10",
90
- "@pnpm/hooks.types": "1100.0.11",
91
- "@pnpm/installing.context": "1100.0.16",
92
- "@pnpm/installing.deps-resolver": "1100.2.1",
93
- "@pnpm/installing.linking.direct-dep-linker": "1100.0.8",
94
- "@pnpm/deps.path": "1100.0.7",
95
- "@pnpm/installing.linking.modules-cleaner": "1100.1.6",
96
- "@pnpm/installing.linking.hoist": "1100.0.12",
97
- "@pnpm/bins.remover": "1100.0.8",
98
- "@pnpm/installing.modules-yaml": "1100.0.8",
99
- "@pnpm/lockfile.fs": "1100.1.4",
100
- "@pnpm/installing.package-requester": "1101.0.12",
101
- "@pnpm/lockfile.pruner": "1100.0.10",
102
- "@pnpm/lockfile.settings-checker": "1100.0.16",
103
- "@pnpm/lockfile.preferred-versions": "1100.0.14",
104
- "@pnpm/lockfile.filtering": "1100.1.5",
105
- "@pnpm/lockfile.to-pnp": "1100.0.13",
106
- "@pnpm/lockfile.verification": "1100.0.16",
107
- "@pnpm/lockfile.utils": "1100.0.12",
108
- "@pnpm/lockfile.walker": "1100.0.10",
109
- "@pnpm/network.auth-header": "1101.1.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",
102
+ "@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",
110
109
  "@pnpm/resolving.parse-wanted-dependency": "1100.0.1",
111
- "@pnpm/pkg-manifest.utils": "1100.2.3",
112
- "@pnpm/patching.config": "1100.0.7",
113
- "@pnpm/pnpr.client": "1.2.0",
114
- "@pnpm/resolving.resolver-base": "1100.4.1",
115
- "@pnpm/store.controller-types": "1100.1.4",
116
- "@pnpm/workspace.project-manifest-reader": "1100.0.11",
117
- "@pnpm/store.index": "1100.1.0",
118
- "@pnpm/types": "1101.3.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"
119
119
  },
120
120
  "peerDependencies": {
121
- "@pnpm/logger": "^1001.0.1",
122
- "@pnpm/worker": "^1100.1.10"
121
+ "@pnpm/logger": "^1100.0.0",
122
+ "@pnpm/worker": "^1100.2.0"
123
123
  },
124
124
  "devDependencies": {
125
- "@jest/globals": "30.3.0",
125
+ "@jest/globals": "30.4.1",
126
126
  "@types/fs-extra": "^11.0.4",
127
127
  "@types/is-windows": "^1.0.2",
128
128
  "@types/normalize-path": "^3.0.2",
129
129
  "@types/ramda": "0.31.1",
130
130
  "@types/semver": "7.7.1",
131
- "@yarnpkg/core": "4.5.0",
131
+ "@yarnpkg/core": "4.8.0",
132
132
  "ci-info": "^4.4.0",
133
133
  "deep-require-cwd": "1.0.0",
134
134
  "execa": "npm:safe-execa@0.3.0",
135
135
  "exists-link": "2.0.0",
136
136
  "is-windows": "^1.0.2",
137
- "nock": "13.3.4",
138
137
  "path-name": "^1.0.0",
139
138
  "read-yaml-file": "^3.0.0",
140
139
  "resolve-link-target": "^3.0.0",
141
140
  "symlink-dir": "^10.0.1",
142
141
  "write-json-file": "^7.0.0",
143
142
  "write-yaml-file": "^6.0.0",
144
- "@pnpm/assert-project": "1100.0.14",
145
- "@pnpm/lockfile.types": "1100.0.10",
146
- "@pnpm/installing.deps-installer": "1101.8.0",
147
- "@pnpm/assert-store": "1100.0.14",
148
- "@pnpm/network.git-utils": "1100.0.1",
143
+ "@pnpm/assert-project": "1100.0.16",
144
+ "@pnpm/installing.deps-installer": "1102.0.0",
145
+ "@pnpm/assert-store": "1100.0.16",
149
146
  "@pnpm/logger": "1100.0.0",
150
- "@pnpm/pkg-manifest.reader": "1100.0.7",
151
- "@pnpm/resolving.registry.types": "1100.1.2",
152
- "@pnpm/prepare": "1100.0.14",
153
- "@pnpm/test-fixtures": "1100.0.0",
154
- "@pnpm/store.cafs": "1100.1.9",
155
- "@pnpm/testing.mock-agent": "1101.0.1",
156
- "@pnpm/test-ipc-server": "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",
157
151
  "@pnpm/store.path": "1100.0.1",
158
- "@pnpm/testing.temp-store": "1100.1.7",
159
- "@pnpm/testing.registry-mock": "1100.0.4"
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",
157
+ "@pnpm/resolving.registry.types": "1100.1.3",
158
+ "@pnpm/lockfile.types": "1100.0.11"
160
159
  },
161
160
  "engines": {
162
161
  "node": ">=22.13"
163
162
  },
164
163
  "jest": {
164
+ "collectCoverage": false,
165
165
  "preset": "@pnpm/jest-config/with-registry"
166
166
  },
167
167
  "scripts": {
@@ -170,6 +170,8 @@
170
170
  "test-with-preview": "preview && pnpm run test:e2e",
171
171
  "test": "pn compile && pn .test",
172
172
  "compile": "tsgo --build && pn lint --fix",
173
- ".test": "cross-env PNPM_REGISTRY_MOCK_PORT=7769 NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169\" jest"
173
+ ".test": "pn .test:heavy && pn .test:rest",
174
+ ".test:heavy": "cross-env PNPM_REGISTRY_MOCK_PORT=7769 NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169\" jest test/install/deepRecursive.ts",
175
+ ".test:rest": "cross-env PNPM_REGISTRY_MOCK_PORT=7769 NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169\" jest \"^(?!.*deepRecursive)\""
174
176
  }
175
177
  }