@pnpm/installing.deps-installer 1101.6.1 → 1101.7.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.
@@ -229,8 +229,11 @@ export interface StrictInstallOptions {
229
229
  * summary at the very end instead of one summary per install.
230
230
  */
231
231
  omitSummaryLog: boolean;
232
- /** URL of a pnpm agent server. See the pnpm-agent README. */
233
- agent?: string;
232
+ /**
233
+ * URL of a pnpr server that resolves dependencies server-side and serves
234
+ * only the files missing from the client's store.
235
+ */
236
+ pnprServer?: string;
234
237
  }
235
238
  export type InstallOptions = Partial<StrictInstallOptions> & Pick<StrictInstallOptions, 'storeDir' | 'storeController'>;
236
239
  export interface ProcessedInstallOptions extends StrictInstallOptions {
@@ -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, readWantedLockfile, writeCurrentLockfile, writeLockfiles, writeWantedLockfile, } from '@pnpm/lockfile.fs';
18
+ import { cleanGitBranchLockfiles, getWantedLockfileName, isEmptyLockfile, readWantedLockfileFile, writeCurrentLockfile, 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';
@@ -55,9 +55,9 @@ const BROKEN_LOCKFILE_INTEGRITY_ERRORS = new Set([
55
55
  const DEV_PREINSTALL = 'pnpm:devPreinstall';
56
56
  export async function install(manifest, opts) {
57
57
  const rootDir = (opts.dir ?? process.cwd());
58
- // When a pnpm agent is configured, use server-side resolution
58
+ // When a pnpr server is configured, use server-side resolution
59
59
  // instead of the normal resolution flow.
60
- if (opts.agent) {
60
+ if (opts.pnprServer) {
61
61
  return installFromPnpmRegistry(manifest, rootDir, opts);
62
62
  }
63
63
  const { updatedCatalogs, updatedProjects: projects, ignoredBuilds, resolutionPolicyViolations } = await mutateModules([
@@ -120,14 +120,14 @@ export async function mutateModules(projects, maybeOpts) {
120
120
  streamParser.on('data', reporter);
121
121
  }
122
122
  const opts = extendOptions(maybeOpts);
123
- // When a pnpm agent is configured, use server-side resolution. The agent
123
+ // When a pnpr server is configured, use server-side resolution. The pnpr server
124
124
  // path supports `install`, `installSome` (pnpm add), and `uninstallSome`
125
125
  // (pnpm remove). Mutations that need full client-side resolution (update
126
126
  // flags) still fall through to the normal flow.
127
- if (opts.agent && canUseAgentForMutations(projects)) {
128
- const agentResult = await mutateModulesViaAgent(projects, opts);
129
- if (agentResult)
130
- return agentResult;
127
+ if (opts.pnprServer && canUsePnprForMutations(projects)) {
128
+ const pnprResult = await mutateModulesViaPnpr(projects, opts);
129
+ if (pnprResult)
130
+ return pnprResult;
131
131
  }
132
132
  const allowBuild = createAllowBuildFunction(opts);
133
133
  if (!opts.include.dependencies && opts.include.optionalDependencies) {
@@ -536,14 +536,21 @@ export async function mutateModules(projects, maybeOpts) {
536
536
  });
537
537
  if (opts.catalogMode !== 'manual') {
538
538
  for (const wantedDep of wantedDeps) {
539
+ // A `runtime:` specifier (e.g. node from `devEngines.runtime` or
540
+ // `pnpm runtime set`) round-trips to `devEngines.runtime` through the
541
+ // manifest writer, which only recognizes the `runtime:` protocol.
542
+ // Promoting it into a catalog rewrites the entry to `catalog:`, which
543
+ // breaks that round-trip and strands it in `devDependencies`.
544
+ if (wantedDep.bareSpecifier?.startsWith('runtime:'))
545
+ continue;
539
546
  const perDepCatalogName = getPerDepCatalogName(wantedDep, opts.saveCatalogName);
540
547
  const catalogBareSpecifier = `catalog:${perDepCatalogName === 'default' ? '' : perDepCatalogName}`;
541
548
  const catalog = resolveFromCatalog(opts.catalogs, { ...wantedDep, bareSpecifier: catalogBareSpecifier });
542
549
  const catalogDepSpecifier = matchCatalogResolveResult(catalog, pickCatalogSpecifier);
543
550
  if (!catalogDepSpecifier ||
544
551
  wantedDep.bareSpecifier === catalogBareSpecifier ||
545
- semver.validRange(wantedDep.bareSpecifier) &&
546
- semver.validRange(catalogDepSpecifier) &&
552
+ semver.valid(wantedDep.bareSpecifier) &&
553
+ semver.valid(catalogDepSpecifier) &&
547
554
  semver.eq(wantedDep.bareSpecifier, catalogDepSpecifier)) {
548
555
  wantedDep.saveCatalogName = perDepCatalogName;
549
556
  continue;
@@ -1409,7 +1416,7 @@ function allMutationsAreInstalls(projects) {
1409
1416
  * Run the pacquet binary if it's configured, otherwise run the JS
1410
1417
  * `headlessInstall`. Callers can hand off any code path that materializes
1411
1418
  * an already-resolved lockfile (workspace partial install, hoisted
1412
- * linker, agent-server install, frozen install) without restating the
1419
+ * linker, pnpr server install, frozen install) without restating the
1413
1420
  * delegation choice.
1414
1421
  *
1415
1422
  * Pacquet reads the wanted lockfile from disk and produces its own
@@ -1423,7 +1430,7 @@ function allMutationsAreInstalls(projects) {
1423
1430
  async function materializeOrDelegate(opts, runHeadlessInstall) {
1424
1431
  if (opts.runPacquet != null) {
1425
1432
  // Reached only from the resolve-then-materialize call sites
1426
- // (workspace-partial, hoisted-linker, agent install). Each ran a
1433
+ // (workspace-partial, hoisted-linker, pnpr server install). Each ran a
1427
1434
  // lockfileOnly resolve pass that emitted one
1428
1435
  // `pnpm:progress status:resolved` per package, so pacquet's
1429
1436
  // duplicate `resolved` events would double the reporter's count.
@@ -1588,13 +1595,13 @@ function getProjectsWithTargetDirs(projects, lockfile, dependenciesGraph) {
1588
1595
  return extendProjectsWithTargetDirs(projects, injectionTargetsByDepPath);
1589
1596
  }
1590
1597
  /**
1591
- * Whether the agent path can handle this batch of mutations. The agent flow
1598
+ * Whether the pnpr server path can handle this batch of mutations. The pnpr server flow
1592
1599
  * supports installing the manifest as-is (`install`), adding new deps
1593
1600
  * (`installSome`), and removing deps (`uninstallSome`). It cannot model the
1594
1601
  * client-side update-flag behavior (`update`/`updateMatching`/`updateToLatest`)
1595
1602
  * yet, so those still go through the normal client-side resolver.
1596
1603
  */
1597
- function canUseAgentForMutations(projects) {
1604
+ function canUsePnprForMutations(projects) {
1598
1605
  if (projects.length === 0)
1599
1606
  return false;
1600
1607
  return projects.every((p) => {
@@ -1607,26 +1614,26 @@ function canUseAgentForMutations(projects) {
1607
1614
  });
1608
1615
  }
1609
1616
  /**
1610
- * Pre-process projects for the agent flow:
1617
+ * Pre-process projects for the pnpr server flow:
1611
1618
  * - `install`: send the manifest as-is.
1612
1619
  * - `uninstallSome`: drop the named deps from the manifest before sending,
1613
- * so the agent's resolution naturally produces a lockfile without them.
1620
+ * so the pnpr server's resolution naturally produces a lockfile without them.
1614
1621
  * - `installSome`: parse selectors and merge them into the manifest. The
1615
- * agent server then resolves the merged manifest, and we read the resolved
1622
+ * pnpr server then resolves the merged manifest, and we read the resolved
1616
1623
  * specifiers (with the right save-prefix applied server-side) back from
1617
1624
  * the lockfile importer entries to update the client-side manifest.
1618
1625
  *
1619
1626
  * Returns null if the projects don't map cleanly to allProjects (caller
1620
1627
  * should fall through to the normal flow).
1621
1628
  */
1622
- async function prepareAgentProjects(projects, opts) {
1629
+ async function preparePnprProjects(projects, opts) {
1623
1630
  const allProjects = opts.allProjects ?? [];
1624
1631
  const mutationByRootDir = new Map();
1625
1632
  for (const p of projects) {
1626
1633
  mutationByRootDir.set(p.rootDir, p);
1627
1634
  }
1628
1635
  // Include every workspace project, not just the mutated ones — otherwise
1629
- // the agent's resulting lockfile would only contain the targeted importer
1636
+ // the pnpr server's resulting lockfile would only contain the targeted importer
1630
1637
  // and `headlessInstall` (or a later install) would crash on the missing
1631
1638
  // entries for the other workspace projects. Projects without a mutation
1632
1639
  // are sent with their current manifest (no-op for resolution).
@@ -1685,7 +1692,7 @@ async function prepareAgentProjects(projects, opts) {
1685
1692
  * dependency field per the mutation's `targetDependenciesField` (or the
1686
1693
  * existing field if the dep is already in the manifest, defaulting to
1687
1694
  * `dependencies`). Selectors without a version use `'latest'` so the
1688
- * agent's resolver picks the newest matching release.
1695
+ * pnpr server's resolver picks the newest matching release.
1689
1696
  */
1690
1697
  function mergeInstallSelectors(manifest, mutation) {
1691
1698
  const target = mutation.targetDependenciesField;
@@ -1729,10 +1736,10 @@ function findExistingSpec(alias, manifest) {
1729
1736
  manifest.optionalDependencies?.[alias];
1730
1737
  }
1731
1738
  /**
1732
- * After the agent resolves, copy the lockfile importer's per-dep specifier
1739
+ * After the pnpr server resolves, copy the lockfile importer's per-dep specifier
1733
1740
  * (which the server's resolver computed with the right save-prefix) back
1734
1741
  * into the client manifest for any newly added aliases. We rely on the
1735
- * lockfile because the agent server applies catalog substitution,
1742
+ * lockfile because the pnpr server applies catalog substitution,
1736
1743
  * normalizedBareSpecifier, and save-prefix logic during resolution.
1737
1744
  */
1738
1745
  function applyResolvedSpecsFromLockfile(manifest, importerSnapshot, newDeps, pinnedVersion) {
@@ -1751,7 +1758,7 @@ function applyResolvedSpecsFromLockfile(manifest, importerSnapshot, newDeps, pin
1751
1758
  const resolvedVersion = importerSnapshot[field]?.[dep.alias];
1752
1759
  if (!resolvedVersion || manifest[field]?.[dep.alias] == null)
1753
1760
  continue;
1754
- // The agent server resolved the tree but, on the plain-install path, it
1761
+ // The pnpr server resolved the tree but, on the plain-install path, it
1755
1762
  // writes the user's raw spec (`'latest'`) into the lockfile specifier
1756
1763
  // rather than normalizing to a save-prefix range. Compute the
1757
1764
  // save-prefix spec client-side from the resolved version.
@@ -1762,24 +1769,24 @@ function applyResolvedSpecsFromLockfile(manifest, importerSnapshot, newDeps, pin
1762
1769
  return manifest;
1763
1770
  }
1764
1771
  /**
1765
- * Drives the agent path for a `mutateModules` call across one or more
1766
- * projects. Returns null if the call can't be served by the agent (e.g. one
1772
+ * Drives the pnpr server path for a `mutateModules` call across one or more
1773
+ * projects. Returns null if the call can't be served by the pnpr server (e.g. one
1767
1774
  * of the projects isn't in `allProjects`).
1768
1775
  */
1769
- async function mutateModulesViaAgent(projects, opts) {
1770
- const agentProjects = await prepareAgentProjects(projects, opts);
1771
- if (!agentProjects)
1776
+ async function mutateModulesViaPnpr(projects, opts) {
1777
+ const pnprProjects = await preparePnprProjects(projects, opts);
1778
+ if (!pnprProjects)
1772
1779
  return null;
1773
1780
  // installFromPnpmRegistry runs the headless install for the first
1774
1781
  // project's root and the workspace path for the rest. Pass the
1775
1782
  // pre-processed manifests so resolution sees the post-mutation state.
1776
- const result = await installFromPnpmRegistry(agentProjects[0].manifest, agentProjects[0].rootDir, opts, agentProjects.map((p) => ({ rootDir: p.rootDir, manifest: p.manifest })));
1783
+ const result = await installFromPnpmRegistry(pnprProjects[0].manifest, pnprProjects[0].rootDir, opts, pnprProjects.map((p) => ({ rootDir: p.rootDir, manifest: p.manifest })));
1777
1784
  // For installSome projects, copy resolved specs from the lockfile importer
1778
1785
  // entries back into the client manifest so save-prefix/catalog/etc. take
1779
1786
  // effect (the server applies these during its resolution step).
1780
1787
  const lockfileDir = opts.lockfileDir ?? projects[0].rootDir;
1781
1788
  const mutatedRootDirs = new Set(projects.map((p) => p.rootDir));
1782
- const updatedProjects = agentProjects
1789
+ const updatedProjects = pnprProjects
1783
1790
  .filter((p) => mutatedRootDirs.has(p.rootDir))
1784
1791
  .map((p) => {
1785
1792
  if (p.mutation === 'installSome' && p.newDeps.length > 0) {
@@ -1798,63 +1805,79 @@ async function mutateModulesViaAgent(projects, opts) {
1798
1805
  };
1799
1806
  }
1800
1807
  /**
1801
- * When a pnpm agent is configured, resolve dependencies server-side
1808
+ * When a pnpr server is configured, resolve dependencies server-side
1802
1809
  * and download only the missing files. Then run a headless install to link
1803
1810
  * packages into node_modules.
1804
1811
  */
1805
1812
  async function installFromPnpmRegistry(manifest, rootDir, opts, allInstallProjects) {
1806
- // The agent path skips client-side resolution, so resolver-side policies
1813
+ // The pnpr server path skips client-side resolution, so resolver-side policies
1807
1814
  // can't be enforced locally. `minimumReleaseAge` is forwarded to the
1808
- // agent and enforced server-side. `trustPolicy` has no server-side
1815
+ // pnpr server and enforced server-side. `trustPolicy` has no server-side
1809
1816
  // counterpart yet, so refuse to run under it instead of silently
1810
1817
  // letting through a lockfile the local verifier would reject.
1811
1818
  if (opts.trustPolicy === 'no-downgrade') {
1812
- throw new PnpmError('TRUST_POLICY_INCOMPATIBLE_WITH_AGENT', 'The pnpm agent does not yet enforce `trustPolicy: no-downgrade`, so running an install through the agent under this policy would produce a lockfile that the local verifier rejects.', { hint: 'Unset `trustPolicy` for this install, or disable the agent (unset `--agent` / `agent` in pnpm-workspace.yaml) so resolution runs locally and the trust check applies.' });
1819
+ 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.' });
1813
1820
  }
1814
- const { fetchFromPnpmRegistry } = await import('@pnpm/agent.client');
1821
+ const { fetchFromPnpmRegistry } = await import('@pnpm/pnpr.client');
1822
+ const { createGetAuthHeaderByURI, getAuthHeadersFromCreds } = await import('@pnpm/network.auth-header');
1815
1823
  const { StoreIndex } = await import('@pnpm/store.index');
1816
1824
  const { setImportConcurrency } = await import('@pnpm/worker');
1817
- // Raise import concurrency for this install only — the agent path has no
1825
+ // Forward the whole credential map (the registries a graph touches
1826
+ // aren't known up front), so the server attaches the right token per
1827
+ // URL. `authorization` also identifies the caller to pnpr's gate.
1828
+ const configByUri = opts.configByUri ?? {};
1829
+ const forwardedAuthHeaders = getAuthHeadersFromCreds(configByUri);
1830
+ const pnprAuthorization = createGetAuthHeaderByURI(configByUri)(opts.pnprServer);
1831
+ // Raise import concurrency for this install only — the pnpr server path has no
1818
1832
  // concurrent fetching competing for workers. Restore afterwards so we
1819
1833
  // don't leak a process-wide mutation to other installs (e.g. tests).
1820
1834
  const restoreImportConcurrency = setImportConcurrency(6);
1821
1835
  try {
1822
1836
  const lockfileDir = opts.lockfileDir ?? rootDir;
1823
- // Read existing lockfile if available
1824
- const existingLockfile = await readWantedLockfile(lockfileDir, {
1837
+ // Read the existing lockfile (if any) in its on-disk shape — that's
1838
+ // what the pnpr server protocol carries, so no conversion is needed before
1839
+ // sending it.
1840
+ const existingLockfile = await readWantedLockfileFile(lockfileDir, {
1825
1841
  ignoreIncompatible: true,
1826
1842
  }).catch(() => null);
1827
- logger.info({ message: 'Resolving dependencies via pnpm agent', prefix: rootDir });
1843
+ logger.info({ message: 'Resolving dependencies via the pnpr server', prefix: rootDir });
1828
1844
  // Open the store index to read integrities and write new entries.
1829
1845
  // Close it in a finally so a failure in fetchFromPnpmRegistry doesn't
1830
1846
  // leak an open SQLite handle (on Windows that also blocks store cleanup).
1831
1847
  const storeIndex = new StoreIndex(opts.storeDir);
1832
- let lockfile, agentStats, fileDownloads, indexEntries;
1848
+ let lockfile, pnprStats, fileDownloads, indexEntries;
1833
1849
  try {
1834
1850
  // Build projects list for workspace support.
1835
1851
  // Normalize separators to POSIX — on Windows `path.relative` returns
1836
- // backslashes, which the agent server rejects (it treats `\` as an
1852
+ // backslashes, which the pnpr server rejects (it treats `\` as an
1837
1853
  // unsafe/YAML-injection character and normalizes paths as POSIX).
1838
1854
  const projectsList = allInstallProjects && allInstallProjects.length > 1
1839
1855
  ? allInstallProjects.map(p => ({
1840
1856
  dir: (path.relative(lockfileDir, p.rootDir) || '.').split(path.sep).join('/'),
1841
1857
  dependencies: p.manifest.dependencies,
1842
1858
  devDependencies: p.manifest.devDependencies,
1859
+ optionalDependencies: p.manifest.optionalDependencies,
1843
1860
  }))
1844
1861
  : undefined;
1845
- ({ lockfile, stats: agentStats, fileDownloads, indexEntries } = await fetchFromPnpmRegistry({
1846
- registryUrl: opts.agent,
1862
+ ({ lockfile, stats: pnprStats, fileDownloads, indexEntries } = await fetchFromPnpmRegistry({
1863
+ registryUrl: opts.pnprServer,
1847
1864
  storeDir: opts.storeDir,
1848
1865
  storeIndex,
1849
1866
  dependencies: projectsList ? undefined : manifest.dependencies,
1850
1867
  devDependencies: projectsList ? undefined : manifest.devDependencies,
1868
+ optionalDependencies: projectsList ? undefined : manifest.optionalDependencies,
1851
1869
  projects: projectsList,
1870
+ registry: opts.registries?.default,
1871
+ namedRegistries: opts.namedRegistries,
1872
+ authHeaders: forwardedAuthHeaders,
1873
+ authorization: pnprAuthorization,
1852
1874
  overrides: opts.overrides,
1853
1875
  minimumReleaseAge: opts.minimumReleaseAge,
1854
1876
  lockfile: existingLockfile ?? undefined,
1877
+ lockfileOnly: opts.lockfileOnly,
1855
1878
  }));
1856
1879
  // Write store index entries so headless install finds them.
1857
- const { writeRawIndexEntries } = await import('@pnpm/agent.client');
1880
+ const { writeRawIndexEntries } = await import('@pnpm/pnpr.client');
1858
1881
  writeRawIndexEntries(indexEntries, storeIndex);
1859
1882
  storeIndex.checkpoint();
1860
1883
  }
@@ -1870,12 +1893,29 @@ async function installFromPnpmRegistry(manifest, rootDir, opts, allInstallProjec
1870
1893
  mergeGitBranchLockfiles: opts.mergeGitBranchLockfiles,
1871
1894
  });
1872
1895
  logger.info({
1873
- message: `Resolved ${agentStats.totalPackages} packages: ${agentStats.alreadyInStore} cached, ${agentStats.filesToDownload} files to download`,
1896
+ message: `Resolved ${pnprStats.totalPackages} packages: ${pnprStats.alreadyInStore} cached, ${pnprStats.filesToDownload} files to download`,
1874
1897
  prefix: rootDir,
1875
1898
  });
1899
+ // `--lockfile-only`: the pnpr server resolved and we wrote the lockfile, but
1900
+ // pnpm fetches nothing and links nothing in this mode — stop before the
1901
+ // headless install. See https://github.com/pnpm/pnpm/issues/12146.
1902
+ if (opts.lockfileOnly) {
1903
+ // Nothing is downloaded in this mode, but the lockfile arrives before
1904
+ // the stream closes — observe `fileDownloads` so a stream error after
1905
+ // the `L` frame doesn't surface as an unhandled rejection.
1906
+ void fileDownloads.catch(() => { });
1907
+ return {
1908
+ updatedCatalogs: undefined,
1909
+ updatedManifest: manifest,
1910
+ ignoredBuilds: undefined,
1911
+ stats: { added: 0, removed: 0, linkedToRoot: 0 },
1912
+ lockfile,
1913
+ resolutionPolicyViolations: [],
1914
+ };
1915
+ }
1876
1916
  // Wrap fetchPackage to:
1877
- // 1. Wait for agent file downloads before checking the store
1878
- // 2. Skip integrity verification — files just written from the agent
1917
+ // 1. Wait for pnpr server file downloads before checking the store
1918
+ // 2. Skip integrity verification — files just written from the pnpr server
1879
1919
  // are guaranteed correct (server verified, no rehashing needed)
1880
1920
  const { readPkgFromCafs } = await import('@pnpm/worker');
1881
1921
  const { storeIndexKey: _storeIndexKey } = await import('@pnpm/store.index');
@@ -1887,7 +1927,7 @@ async function installFromPnpmRegistry(manifest, rootDir, opts, allInstallProjec
1887
1927
  const integrity = resolution?.integrity;
1888
1928
  // Fall through to the regular store controller for git-hosted tarballs.
1889
1929
  // Their cached entry lives under gitHostedStoreIndexKey (preserves the
1890
- // built/not-built dimension), not the integrity-keyed path the agent
1930
+ // built/not-built dimension), not the integrity-keyed path the pnpr server
1891
1931
  // uses for npm tarballs. See @pnpm/store.pkg-finder for the rationale.
1892
1932
  if (integrity && !resolution?.gitHosted) {
1893
1933
  const filesIndexFile = _storeIndexKey(integrity, fetchOpts.pkg.id);
@@ -1906,7 +1946,7 @@ async function installFromPnpmRegistry(manifest, rootDir, opts, allInstallProjec
1906
1946
  };
1907
1947
  const headlessOpts = {
1908
1948
  ...opts,
1909
- // Skip re-verifying files just written from the agent — they're
1949
+ // Skip re-verifying files just written from the pnpr server — they're
1910
1950
  // guaranteed correct (server verified, no rehashing needed).
1911
1951
  verifyStoreIntegrity: false,
1912
1952
  storeController: wrappedStoreController,
@@ -1948,13 +1988,13 @@ async function installFromPnpmRegistry(manifest, rootDir, opts, allInstallProjec
1948
1988
  updatedManifest: manifest,
1949
1989
  ignoredBuilds,
1950
1990
  // Pacquet doesn't surface a structured stats return; default to
1951
- // zeros so the agent-path's non-optional `stats` slot is filled.
1991
+ // zeros so the pnpr server's non-optional `stats` slot is filled.
1952
1992
  // The reporter still renders accurate counts from pacquet's
1953
1993
  // `pnpm:stats` log events.
1954
1994
  stats: stats ?? { added: 0, removed: 0, linkedToRoot: 0 },
1955
1995
  lockfile,
1956
- // Server-side resolution (pnpm agent) enforces `minimumReleaseAge`
1957
- // itself — the agent picks only mature versions and the lockfile
1996
+ // Server-side resolution (pnpr server) enforces `minimumReleaseAge`
1997
+ // itself — the pnpr server picks only mature versions and the lockfile
1958
1998
  // can't contain immature entries to auto-collect. `trustPolicy` is
1959
1999
  // guarded above (we refuse to enter this path when it's set), so
1960
2000
  // there's nothing for the install command to react to here.
@@ -181,22 +181,25 @@ export async function collectResolutionPolicyViolations(lockfile, verifiers, opt
181
181
  // therefore appear multiple times. Dedupe so we issue at most one
182
182
  // verification per package version.
183
183
  //
184
- // Include a serialization of `resolution` in the key so two entries that
185
- // share a (name, version) but differ in *what* was resolved (e.g. one
186
- // pinned via npm, another via a git URL under the same alias) don't
187
- // collapse: if the wrong shape wins the dedup, a protocol-scoped
188
- // verifier short-circuits on the surviving entry and the real one is
184
+ // Include a serialization of `resolution` and `nonSemverVersion` in the key
185
+ // so two entries that share a (name, version) but differ in *what* was
186
+ // resolved (e.g. one pinned via npm, another a URL-keyed tarball whose
187
+ // snapshot copied the same semver `version` from its manifest) don't
188
+ // collapse: `nonSemverVersion` flips whether the npm verifier enforces or
189
+ // skips the tarball/policy checks, so if the wrong shape wins the dedup the
190
+ // surviving entry is verified under the wrong rules and the real one is
189
191
  // never checked.
190
192
  function collectCandidates(lockfile) {
191
193
  const candidates = new Map();
192
194
  for (const [depPath, snapshot] of Object.entries(lockfile.packages ?? {})) {
193
- const { name, version } = nameVerFromPkgSnapshot(depPath, snapshot);
195
+ const { name, version, nonSemverVersion } = nameVerFromPkgSnapshot(depPath, snapshot);
194
196
  if (!name || !version)
195
197
  continue;
196
- const key = `${name}@${version}@${JSON.stringify(snapshot.resolution)}`;
198
+ const key = `${name}@${version}@${nonSemverVersion ?? ''}@${JSON.stringify(snapshot.resolution)}`;
197
199
  candidates.set(key, {
198
200
  name,
199
201
  version,
202
+ nonSemverVersion,
200
203
  resolution: snapshot.resolution,
201
204
  });
202
205
  }
@@ -205,7 +208,7 @@ function collectCandidates(lockfile) {
205
208
  async function iterateLockfileViolations(candidates, verifiers, concurrency) {
206
209
  const violations = [];
207
210
  const limit = pLimit(concurrency ?? DEFAULT_CONCURRENCY);
208
- await Promise.all(Array.from(candidates.values(), ({ name, version, resolution }) => limit(async () => {
211
+ await Promise.all(Array.from(candidates.values(), ({ name, version, nonSemverVersion, resolution }) => limit(async () => {
209
212
  // Fan out across every active verifier; each handles its own
210
213
  // protocol short-circuit (e.g. the npm verifier returns ok:true for
211
214
  // git resolutions). We stop at the first failure per entry so a
@@ -213,7 +216,7 @@ async function iterateLockfileViolations(candidates, verifiers, concurrency) {
213
216
  // same (name, version).
214
217
  for (const verifier of verifiers) {
215
218
  // eslint-disable-next-line no-await-in-loop
216
- const result = await verifier.verify(resolution, { name, version });
219
+ const result = await verifier.verify(resolution, { name, version, nonSemverVersion });
217
220
  if (!result.ok) {
218
221
  violations.push({ name, version, resolution, code: result.code, reason: result.reason });
219
222
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/installing.deps-installer",
3
- "version": "1101.6.1",
3
+ "version": "1101.7.0",
4
4
  "description": "Fast, disk space efficient installation engine",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -28,7 +28,10 @@
28
28
  ],
29
29
  "license": "MIT",
30
30
  "funding": "https://opencollective.com/pnpm",
31
- "repository": "https://github.com/pnpm/pnpm/tree/main/installing/deps-installer",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/pnpm/pnpm/tree/main/installing/deps-installer"
34
+ },
32
35
  "homepage": "https://github.com/pnpm/pnpm/tree/main/installing/deps-installer#readme",
33
36
  "bugs": {
34
37
  "url": "https://github.com/pnpm/pnpm/issues"
@@ -62,60 +65,61 @@
62
65
  "ramda": "npm:@pnpm/ramda@0.28.1",
63
66
  "run-groups": "^5.0.0",
64
67
  "semver": "^7.8.1",
65
- "@pnpm/agent.client": "1.0.8",
66
- "@pnpm/bins.remover": "1100.0.6",
67
- "@pnpm/building.after-install": "1101.0.18",
68
- "@pnpm/bins.linker": "1100.0.10",
69
- "@pnpm/building.during-install": "1101.0.15",
70
- "@pnpm/catalogs.protocol-parser": "1100.0.0",
71
- "@pnpm/building.policy": "1100.0.7",
68
+ "@pnpm/building.after-install": "1101.0.19",
69
+ "@pnpm/bins.linker": "1100.0.11",
70
+ "@pnpm/building.policy": "1100.0.8",
72
71
  "@pnpm/catalogs.resolver": "1100.0.0",
73
72
  "@pnpm/config.matcher": "1100.0.1",
74
- "@pnpm/config.normalize-registries": "1100.0.5",
73
+ "@pnpm/catalogs.protocol-parser": "1100.0.0",
74
+ "@pnpm/building.during-install": "1101.0.16",
75
+ "@pnpm/config.normalize-registries": "1100.0.6",
76
+ "@pnpm/core-loggers": "1100.1.3",
77
+ "@pnpm/bins.remover": "1100.0.7",
75
78
  "@pnpm/catalogs.types": "1100.0.0",
76
- "@pnpm/config.parse-overrides": "1100.0.1",
77
- "@pnpm/deps.graph-hasher": "1100.2.2",
78
- "@pnpm/crypto.hash": "1100.0.1",
79
- "@pnpm/crypto.object-hasher": "1100.0.0",
80
79
  "@pnpm/deps.graph-sequencer": "1100.0.0",
81
- "@pnpm/deps.path": "1100.0.5",
82
- "@pnpm/exec.lifecycle": "1100.0.14",
83
- "@pnpm/hooks.read-package-hook": "1100.0.5",
84
- "@pnpm/fs.symlink-dependency": "1100.0.6",
85
- "@pnpm/fs.read-modules-dir": "1100.0.1",
86
- "@pnpm/installing.deps-resolver": "1100.1.6",
87
- "@pnpm/installing.deps-restorer": "1101.1.8",
88
- "@pnpm/error": "1100.0.0",
89
80
  "@pnpm/constants": "1100.0.0",
90
- "@pnpm/hooks.types": "1100.0.9",
91
- "@pnpm/installing.context": "1100.0.14",
92
- "@pnpm/installing.linking.direct-dep-linker": "1100.0.6",
93
- "@pnpm/installing.modules-yaml": "1100.0.6",
94
- "@pnpm/installing.linking.modules-cleaner": "1100.1.4",
95
- "@pnpm/lockfile.filtering": "1100.1.3",
96
- "@pnpm/installing.package-requester": "1101.0.10",
97
- "@pnpm/installing.linking.hoist": "1100.0.10",
98
- "@pnpm/lockfile.fs": "1100.1.2",
99
- "@pnpm/lockfile.pruner": "1100.0.8",
100
- "@pnpm/lockfile.settings-checker": "1100.0.14",
101
- "@pnpm/lockfile.preferred-versions": "1100.0.12",
102
- "@pnpm/lockfile.to-pnp": "1100.0.11",
103
- "@pnpm/lockfile.verification": "1100.0.14",
104
- "@pnpm/lockfile.walker": "1100.0.8",
105
- "@pnpm/lockfile.utils": "1100.0.10",
106
- "@pnpm/patching.config": "1100.0.5",
107
- "@pnpm/core-loggers": "1100.1.2",
108
- "@pnpm/resolving.parse-wanted-dependency": "1100.0.1",
109
- "@pnpm/pkg-manifest.utils": "1100.2.1",
110
- "@pnpm/resolving.resolver-base": "1100.3.1",
81
+ "@pnpm/crypto.object-hasher": "1100.0.0",
82
+ "@pnpm/deps.path": "1100.0.6",
83
+ "@pnpm/deps.graph-hasher": "1100.2.3",
84
+ "@pnpm/error": "1100.0.0",
85
+ "@pnpm/config.parse-overrides": "1100.0.1",
86
+ "@pnpm/exec.lifecycle": "1100.0.15",
87
+ "@pnpm/hooks.read-package-hook": "1100.0.6",
88
+ "@pnpm/fs.read-modules-dir": "1100.0.1",
89
+ "@pnpm/hooks.types": "1100.0.10",
90
+ "@pnpm/installing.context": "1100.0.15",
91
+ "@pnpm/crypto.hash": "1100.0.1",
92
+ "@pnpm/installing.deps-resolver": "1100.2.0",
93
+ "@pnpm/installing.deps-restorer": "1101.1.9",
94
+ "@pnpm/installing.linking.direct-dep-linker": "1100.0.7",
95
+ "@pnpm/installing.linking.hoist": "1100.0.11",
96
+ "@pnpm/installing.modules-yaml": "1100.0.7",
97
+ "@pnpm/installing.package-requester": "1101.0.11",
98
+ "@pnpm/lockfile.fs": "1100.1.3",
99
+ "@pnpm/lockfile.filtering": "1100.1.4",
100
+ "@pnpm/lockfile.to-pnp": "1100.0.12",
101
+ "@pnpm/lockfile.preferred-versions": "1100.0.13",
102
+ "@pnpm/lockfile.utils": "1100.0.11",
103
+ "@pnpm/lockfile.walker": "1100.0.9",
104
+ "@pnpm/network.auth-header": "1101.1.0",
105
+ "@pnpm/pkg-manifest.utils": "1100.2.2",
106
+ "@pnpm/patching.config": "1100.0.6",
107
+ "@pnpm/lockfile.pruner": "1100.0.9",
111
108
  "@pnpm/store.index": "1100.1.0",
112
- "@pnpm/types": "1101.2.0",
113
- "@pnpm/workspace.project-manifest-reader": "1100.0.9",
114
- "@pnpm/store.controller-types": "1100.1.2"
109
+ "@pnpm/resolving.resolver-base": "1100.4.0",
110
+ "@pnpm/resolving.parse-wanted-dependency": "1100.0.1",
111
+ "@pnpm/workspace.project-manifest-reader": "1100.0.10",
112
+ "@pnpm/pnpr.client": "1.1.0",
113
+ "@pnpm/types": "1101.3.0",
114
+ "@pnpm/lockfile.settings-checker": "1100.0.15",
115
+ "@pnpm/installing.linking.modules-cleaner": "1100.1.5",
116
+ "@pnpm/store.controller-types": "1100.1.3",
117
+ "@pnpm/lockfile.verification": "1100.0.15",
118
+ "@pnpm/fs.symlink-dependency": "1100.0.7"
115
119
  },
116
120
  "peerDependencies": {
117
121
  "@pnpm/logger": "^1001.0.1",
118
- "@pnpm/worker": "^1100.1.8"
122
+ "@pnpm/worker": "^1100.1.9"
119
123
  },
120
124
  "devDependencies": {
121
125
  "@jest/globals": "30.3.0",
@@ -137,22 +141,22 @@
137
141
  "symlink-dir": "^10.0.1",
138
142
  "write-json-file": "^7.0.0",
139
143
  "write-yaml-file": "^6.0.0",
140
- "@pnpm/assert-project": "1100.0.12",
141
- "@pnpm/installing.deps-installer": "1101.6.1",
142
- "@pnpm/assert-store": "1100.0.12",
143
- "@pnpm/lockfile.types": "1100.0.8",
144
+ "@pnpm/assert-project": "1100.0.13",
145
+ "@pnpm/lockfile.types": "1100.0.9",
146
+ "@pnpm/installing.deps-installer": "1101.7.0",
147
+ "@pnpm/prepare": "1100.0.13",
148
+ "@pnpm/pkg-manifest.reader": "1100.0.6",
144
149
  "@pnpm/network.git-utils": "1100.0.1",
145
- "@pnpm/pkg-manifest.reader": "1100.0.5",
146
150
  "@pnpm/logger": "1100.0.0",
147
- "@pnpm/prepare": "1100.0.12",
151
+ "@pnpm/resolving.registry.types": "1100.1.1",
152
+ "@pnpm/store.cafs": "1100.1.8",
153
+ "@pnpm/testing.mock-agent": "1101.0.0",
154
+ "@pnpm/test-ipc-server": "1100.0.0",
148
155
  "@pnpm/store.path": "1100.0.1",
149
- "@pnpm/resolving.registry.types": "1100.1.0",
150
156
  "@pnpm/test-fixtures": "1100.0.0",
151
- "@pnpm/store.cafs": "1100.1.7",
152
- "@pnpm/testing.mock-agent": "1100.0.8",
153
- "@pnpm/testing.temp-store": "1100.1.5",
154
- "@pnpm/test-ipc-server": "1100.0.0",
155
- "@pnpm/testing.registry-mock": "1100.0.2"
157
+ "@pnpm/testing.registry-mock": "1100.0.3",
158
+ "@pnpm/testing.temp-store": "1100.1.6",
159
+ "@pnpm/assert-store": "1100.0.13"
156
160
  },
157
161
  "engines": {
158
162
  "node": ">=22.13"