@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
|
-
/**
|
|
233
|
-
|
|
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 {
|
package/lib/install/index.js
CHANGED
|
@@ -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,
|
|
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
|
|
58
|
+
// When a pnpr server is configured, use server-side resolution
|
|
59
59
|
// instead of the normal resolution flow.
|
|
60
|
-
if (opts.
|
|
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
|
|
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.
|
|
128
|
-
const
|
|
129
|
-
if (
|
|
130
|
-
return
|
|
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.
|
|
546
|
-
semver.
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1766
|
-
* projects. Returns null if the call can't be served by the
|
|
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
|
|
1770
|
-
const
|
|
1771
|
-
if (!
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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('
|
|
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/
|
|
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
|
-
//
|
|
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
|
|
1824
|
-
|
|
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
|
|
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,
|
|
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
|
|
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:
|
|
1846
|
-
registryUrl: opts.
|
|
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/
|
|
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 ${
|
|
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
|
|
1878
|
-
// 2. Skip integrity verification — files just written from the
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
1957
|
-
// itself — the
|
|
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
|
|
185
|
-
// share a (name, version) but differ in *what* was
|
|
186
|
-
// pinned via npm, another
|
|
187
|
-
//
|
|
188
|
-
//
|
|
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.
|
|
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":
|
|
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/
|
|
66
|
-
"@pnpm/bins.
|
|
67
|
-
"@pnpm/building.
|
|
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/
|
|
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/
|
|
91
|
-
"@pnpm/
|
|
92
|
-
"@pnpm/
|
|
93
|
-
"@pnpm/
|
|
94
|
-
"@pnpm/
|
|
95
|
-
"@pnpm/
|
|
96
|
-
"@pnpm/
|
|
97
|
-
"@pnpm/
|
|
98
|
-
"@pnpm/
|
|
99
|
-
"@pnpm/
|
|
100
|
-
"@pnpm/
|
|
101
|
-
"@pnpm/
|
|
102
|
-
"@pnpm/
|
|
103
|
-
"@pnpm/
|
|
104
|
-
"@pnpm/
|
|
105
|
-
"@pnpm/
|
|
106
|
-
"@pnpm/
|
|
107
|
-
"@pnpm/
|
|
108
|
-
"@pnpm/
|
|
109
|
-
"@pnpm/
|
|
110
|
-
"@pnpm/
|
|
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/
|
|
113
|
-
"@pnpm/
|
|
114
|
-
"@pnpm/
|
|
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.
|
|
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.
|
|
141
|
-
"@pnpm/
|
|
142
|
-
"@pnpm/
|
|
143
|
-
"@pnpm/
|
|
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/
|
|
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/
|
|
152
|
-
"@pnpm/testing.
|
|
153
|
-
"@pnpm/
|
|
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"
|