@pnpm/installing.deps-installer 1100.0.2 → 1100.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/install/extendInstallOptions.d.ts +2 -0
- package/lib/install/index.js +370 -2
- package/lib/install/link.d.ts +2 -1
- package/lib/install/link.js +3 -0
- package/package.json +44 -43
|
@@ -151,6 +151,8 @@ export interface StrictInstallOptions {
|
|
|
151
151
|
trustPolicyIgnoreAfter?: number;
|
|
152
152
|
packageVulnerabilityAudit?: PackageVulnerabilityAudit;
|
|
153
153
|
blockExoticSubdeps?: boolean;
|
|
154
|
+
/** URL of a pnpm agent server. See the pnpm-agent README. */
|
|
155
|
+
agent?: string;
|
|
154
156
|
}
|
|
155
157
|
export type InstallOptions = Partial<StrictInstallOptions> & Pick<StrictInstallOptions, 'storeDir' | 'storeController'>;
|
|
156
158
|
export interface ProcessedInstallOptions extends StrictInstallOptions {
|
package/lib/install/index.js
CHANGED
|
@@ -15,14 +15,15 @@ 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, writeCurrentLockfile, writeLockfiles, writeWantedLockfile, } from '@pnpm/lockfile.fs';
|
|
18
|
+
import { cleanGitBranchLockfiles, readWantedLockfile, 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';
|
|
22
22
|
import { allProjectsAreUpToDate, satisfiesPackageManifest } from '@pnpm/lockfile.verification';
|
|
23
23
|
import { globalInfo, logger, streamParser } from '@pnpm/logger';
|
|
24
24
|
import { groupPatchedDependencies } from '@pnpm/patching.config';
|
|
25
|
-
import { getAllDependenciesFromManifest, getAllUniqueSpecs } from '@pnpm/pkg-manifest.utils';
|
|
25
|
+
import { createVersionSpecFromResolvedVersion, getAllDependenciesFromManifest, getAllUniqueSpecs } from '@pnpm/pkg-manifest.utils';
|
|
26
|
+
import { parseWantedDependency } from '@pnpm/resolving.parse-wanted-dependency';
|
|
26
27
|
import { lexCompare } from '@pnpm/util.lex-comparator';
|
|
27
28
|
import { safeReadProjectManifestOnly } from '@pnpm/workspace.project-manifest-reader';
|
|
28
29
|
import { isSubdir } from 'is-subdir';
|
|
@@ -51,6 +52,11 @@ const BROKEN_LOCKFILE_INTEGRITY_ERRORS = new Set([
|
|
|
51
52
|
const DEV_PREINSTALL = 'pnpm:devPreinstall';
|
|
52
53
|
export async function install(manifest, opts) {
|
|
53
54
|
const rootDir = (opts.dir ?? process.cwd());
|
|
55
|
+
// When a pnpm agent is configured, use server-side resolution
|
|
56
|
+
// instead of the normal resolution flow.
|
|
57
|
+
if (opts.agent) {
|
|
58
|
+
return installFromPnpmRegistry(manifest, rootDir, opts);
|
|
59
|
+
}
|
|
54
60
|
const { updatedCatalogs, updatedProjects: projects, ignoredBuilds } = await mutateModules([
|
|
55
61
|
{
|
|
56
62
|
mutation: 'install',
|
|
@@ -105,6 +111,15 @@ export async function mutateModules(projects, maybeOpts) {
|
|
|
105
111
|
streamParser.on('data', reporter);
|
|
106
112
|
}
|
|
107
113
|
const opts = extendOptions(maybeOpts);
|
|
114
|
+
// When a pnpm agent is configured, use server-side resolution. The agent
|
|
115
|
+
// path supports `install`, `installSome` (pnpm add), and `uninstallSome`
|
|
116
|
+
// (pnpm remove). Mutations that need full client-side resolution (update
|
|
117
|
+
// flags) still fall through to the normal flow.
|
|
118
|
+
if (opts.agent && canUseAgentForMutations(projects)) {
|
|
119
|
+
const agentResult = await mutateModulesViaAgent(projects, opts);
|
|
120
|
+
if (agentResult)
|
|
121
|
+
return agentResult;
|
|
122
|
+
}
|
|
108
123
|
const allowBuild = createAllowBuildFunction(opts);
|
|
109
124
|
if (!opts.include.dependencies && opts.include.optionalDependencies) {
|
|
110
125
|
throw new PnpmError('OPTIONAL_DEPS_REQUIRE_PROD_DEPS', 'Optional dependencies cannot be installed without production dependencies');
|
|
@@ -1005,6 +1020,7 @@ const _installInContext = async (projects, ctx, opts) => {
|
|
|
1005
1020
|
wantedToBeSkippedPackageIds,
|
|
1006
1021
|
hoistWorkspacePackages: opts.hoistWorkspacePackages,
|
|
1007
1022
|
virtualStoreOnly: opts.virtualStoreOnly,
|
|
1023
|
+
supportedArchitectures: opts.supportedArchitectures,
|
|
1008
1024
|
});
|
|
1009
1025
|
stats = result.stats;
|
|
1010
1026
|
if (opts.enablePnp) {
|
|
@@ -1374,4 +1390,356 @@ function getProjectsWithTargetDirs(projects, lockfile, dependenciesGraph) {
|
|
|
1374
1390
|
}
|
|
1375
1391
|
return extendProjectsWithTargetDirs(projects, injectionTargetsByDepPath);
|
|
1376
1392
|
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Whether the agent path can handle this batch of mutations. The agent flow
|
|
1395
|
+
* supports installing the manifest as-is (`install`), adding new deps
|
|
1396
|
+
* (`installSome`), and removing deps (`uninstallSome`). It cannot model the
|
|
1397
|
+
* client-side update-flag behavior (`update`/`updateMatching`/`updateToLatest`)
|
|
1398
|
+
* yet, so those still go through the normal client-side resolver.
|
|
1399
|
+
*/
|
|
1400
|
+
function canUseAgentForMutations(projects) {
|
|
1401
|
+
if (projects.length === 0)
|
|
1402
|
+
return false;
|
|
1403
|
+
return projects.every((p) => {
|
|
1404
|
+
if (p.mutation === 'uninstallSome')
|
|
1405
|
+
return true;
|
|
1406
|
+
if (p.mutation !== 'install' && p.mutation !== 'installSome')
|
|
1407
|
+
return false;
|
|
1408
|
+
const m = p;
|
|
1409
|
+
return !m.update && !m.updateToLatest && m.updateMatching == null;
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* Pre-process projects for the agent flow:
|
|
1414
|
+
* - `install`: send the manifest as-is.
|
|
1415
|
+
* - `uninstallSome`: drop the named deps from the manifest before sending,
|
|
1416
|
+
* so the agent's resolution naturally produces a lockfile without them.
|
|
1417
|
+
* - `installSome`: parse selectors and merge them into the manifest. The
|
|
1418
|
+
* agent server then resolves the merged manifest, and we read the resolved
|
|
1419
|
+
* specifiers (with the right save-prefix applied server-side) back from
|
|
1420
|
+
* the lockfile importer entries to update the client-side manifest.
|
|
1421
|
+
*
|
|
1422
|
+
* Returns null if the projects don't map cleanly to allProjects (caller
|
|
1423
|
+
* should fall through to the normal flow).
|
|
1424
|
+
*/
|
|
1425
|
+
async function prepareAgentProjects(projects, opts) {
|
|
1426
|
+
const allProjects = opts.allProjects ?? [];
|
|
1427
|
+
const mutationByRootDir = new Map();
|
|
1428
|
+
for (const p of projects) {
|
|
1429
|
+
mutationByRootDir.set(p.rootDir, p);
|
|
1430
|
+
}
|
|
1431
|
+
// Include every workspace project, not just the mutated ones — otherwise
|
|
1432
|
+
// the agent's resulting lockfile would only contain the targeted importer
|
|
1433
|
+
// and `headlessInstall` (or a later install) would crash on the missing
|
|
1434
|
+
// entries for the other workspace projects. Projects without a mutation
|
|
1435
|
+
// are sent with their current manifest (no-op for resolution).
|
|
1436
|
+
const targetSet = allProjects.length > 0
|
|
1437
|
+
? allProjects.map((ap) => ({
|
|
1438
|
+
rootDir: ap.rootDir,
|
|
1439
|
+
manifest: ap.manifest,
|
|
1440
|
+
mutation: mutationByRootDir.get(ap.rootDir),
|
|
1441
|
+
}))
|
|
1442
|
+
: projects.map((p) => {
|
|
1443
|
+
const proj = allProjects.find((ap) => ap.rootDir === p.rootDir);
|
|
1444
|
+
return {
|
|
1445
|
+
rootDir: p.rootDir,
|
|
1446
|
+
manifest: proj?.manifest ?? {},
|
|
1447
|
+
mutation: p,
|
|
1448
|
+
};
|
|
1449
|
+
});
|
|
1450
|
+
// Bail to the normal flow if any mutated project isn't in allProjects —
|
|
1451
|
+
// we can't pre-process its manifest correctly.
|
|
1452
|
+
for (const p of projects) {
|
|
1453
|
+
if (!targetSet.some((t) => t.rootDir === p.rootDir))
|
|
1454
|
+
return null;
|
|
1455
|
+
}
|
|
1456
|
+
return Promise.all(targetSet.map(async (t) => {
|
|
1457
|
+
let manifest = clone(t.manifest);
|
|
1458
|
+
const newDeps = [];
|
|
1459
|
+
const mutation = t.mutation;
|
|
1460
|
+
let pinnedVersion;
|
|
1461
|
+
if (mutation?.mutation === 'uninstallSome') {
|
|
1462
|
+
manifest = await removeDeps(manifest, mutation.dependencyNames, {
|
|
1463
|
+
prefix: mutation.rootDir,
|
|
1464
|
+
saveType: mutation.targetDependenciesField,
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
else if (mutation?.mutation === 'installSome') {
|
|
1468
|
+
manifest = mergeInstallSelectors(manifest, mutation);
|
|
1469
|
+
pinnedVersion = mutation.pinnedVersion;
|
|
1470
|
+
for (const sel of mutation.dependencySelectors) {
|
|
1471
|
+
const parsed = parseWantedDependency(sel);
|
|
1472
|
+
if (parsed.alias) {
|
|
1473
|
+
newDeps.push({ alias: parsed.alias, userSpecified: parsed.bareSpecifier != null });
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
return {
|
|
1478
|
+
rootDir: t.rootDir,
|
|
1479
|
+
manifest,
|
|
1480
|
+
mutation: mutation?.mutation ?? 'install',
|
|
1481
|
+
newDeps,
|
|
1482
|
+
pinnedVersion,
|
|
1483
|
+
};
|
|
1484
|
+
}));
|
|
1485
|
+
}
|
|
1486
|
+
/**
|
|
1487
|
+
* Merge `installSome` selectors into the manifest, choosing the target
|
|
1488
|
+
* dependency field per the mutation's `targetDependenciesField` (or the
|
|
1489
|
+
* existing field if the dep is already in the manifest, defaulting to
|
|
1490
|
+
* `dependencies`). Selectors without a version use `'latest'` so the
|
|
1491
|
+
* agent's resolver picks the newest matching release.
|
|
1492
|
+
*/
|
|
1493
|
+
function mergeInstallSelectors(manifest, mutation) {
|
|
1494
|
+
const target = mutation.targetDependenciesField;
|
|
1495
|
+
const fieldsToClear = ['dependencies', 'devDependencies', 'optionalDependencies'];
|
|
1496
|
+
for (const sel of mutation.dependencySelectors) {
|
|
1497
|
+
const parsed = parseWantedDependency(sel);
|
|
1498
|
+
if (!parsed.alias)
|
|
1499
|
+
continue;
|
|
1500
|
+
const alias = parsed.alias;
|
|
1501
|
+
const field = target ?? guessDepField(alias, manifest) ?? 'dependencies';
|
|
1502
|
+
const spec = parsed.bareSpecifier ?? findExistingSpec(alias, manifest) ?? 'latest';
|
|
1503
|
+
manifest[field] = manifest[field] ?? {};
|
|
1504
|
+
manifest[field][alias] = spec;
|
|
1505
|
+
// If `targetDependenciesField` is set, also remove the alias from the
|
|
1506
|
+
// other fields — matches the normal flow's behavior.
|
|
1507
|
+
if (target) {
|
|
1508
|
+
for (const other of fieldsToClear) {
|
|
1509
|
+
if (other !== target)
|
|
1510
|
+
delete manifest[other]?.[alias];
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
if (mutation.peer) {
|
|
1514
|
+
manifest.peerDependencies = manifest.peerDependencies ?? {};
|
|
1515
|
+
manifest.peerDependencies[alias] = manifest.peerDependencies[alias] ?? spec;
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
return manifest;
|
|
1519
|
+
}
|
|
1520
|
+
function guessDepField(alias, manifest) {
|
|
1521
|
+
if (manifest.dependencies?.[alias] != null)
|
|
1522
|
+
return 'dependencies';
|
|
1523
|
+
if (manifest.devDependencies?.[alias] != null)
|
|
1524
|
+
return 'devDependencies';
|
|
1525
|
+
if (manifest.optionalDependencies?.[alias] != null)
|
|
1526
|
+
return 'optionalDependencies';
|
|
1527
|
+
return undefined;
|
|
1528
|
+
}
|
|
1529
|
+
function findExistingSpec(alias, manifest) {
|
|
1530
|
+
return manifest.dependencies?.[alias] ??
|
|
1531
|
+
manifest.devDependencies?.[alias] ??
|
|
1532
|
+
manifest.optionalDependencies?.[alias];
|
|
1533
|
+
}
|
|
1534
|
+
/**
|
|
1535
|
+
* After the agent resolves, copy the lockfile importer's per-dep specifier
|
|
1536
|
+
* (which the server's resolver computed with the right save-prefix) back
|
|
1537
|
+
* into the client manifest for any newly added aliases. We rely on the
|
|
1538
|
+
* lockfile because the agent server applies catalog substitution,
|
|
1539
|
+
* normalizedBareSpecifier, and save-prefix logic during resolution.
|
|
1540
|
+
*/
|
|
1541
|
+
function applyResolvedSpecsFromLockfile(manifest, importerSnapshot, newDeps, pinnedVersion) {
|
|
1542
|
+
if (!importerSnapshot || newDeps.length === 0)
|
|
1543
|
+
return manifest;
|
|
1544
|
+
// In-memory ProjectSnapshot stores resolved versions in `dependencies`
|
|
1545
|
+
// (alias → resolved version) and original specs in `specifiers` (alias →
|
|
1546
|
+
// user spec). The on-disk YAML shape pairs them per entry — the reader
|
|
1547
|
+
// splits them. Read both and compute the save-prefix spec client-side.
|
|
1548
|
+
for (const dep of newDeps) {
|
|
1549
|
+
// User explicitly specified a spec (e.g. `foo@^2`) — the merged manifest
|
|
1550
|
+
// already has the right value, don't touch it.
|
|
1551
|
+
if (dep.userSpecified)
|
|
1552
|
+
continue;
|
|
1553
|
+
for (const field of ['dependencies', 'devDependencies', 'optionalDependencies']) {
|
|
1554
|
+
const resolvedVersion = importerSnapshot[field]?.[dep.alias];
|
|
1555
|
+
if (!resolvedVersion || manifest[field]?.[dep.alias] == null)
|
|
1556
|
+
continue;
|
|
1557
|
+
// The agent server resolved the tree but, on the plain-install path, it
|
|
1558
|
+
// writes the user's raw spec (`'latest'`) into the lockfile specifier
|
|
1559
|
+
// rather than normalizing to a save-prefix range. Compute the
|
|
1560
|
+
// save-prefix spec client-side from the resolved version.
|
|
1561
|
+
const savePrefixSpec = createVersionSpecFromResolvedVersion(resolvedVersion, pinnedVersion);
|
|
1562
|
+
manifest[field][dep.alias] = savePrefixSpec ?? resolvedVersion;
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
return manifest;
|
|
1566
|
+
}
|
|
1567
|
+
/**
|
|
1568
|
+
* Drives the agent path for a `mutateModules` call across one or more
|
|
1569
|
+
* projects. Returns null if the call can't be served by the agent (e.g. one
|
|
1570
|
+
* of the projects isn't in `allProjects`).
|
|
1571
|
+
*/
|
|
1572
|
+
async function mutateModulesViaAgent(projects, opts) {
|
|
1573
|
+
const agentProjects = await prepareAgentProjects(projects, opts);
|
|
1574
|
+
if (!agentProjects)
|
|
1575
|
+
return null;
|
|
1576
|
+
// installFromPnpmRegistry runs the headless install for the first
|
|
1577
|
+
// project's root and the workspace path for the rest. Pass the
|
|
1578
|
+
// pre-processed manifests so resolution sees the post-mutation state.
|
|
1579
|
+
const result = await installFromPnpmRegistry(agentProjects[0].manifest, agentProjects[0].rootDir, opts, agentProjects.map((p) => ({ rootDir: p.rootDir, manifest: p.manifest })));
|
|
1580
|
+
// For installSome projects, copy resolved specs from the lockfile importer
|
|
1581
|
+
// entries back into the client manifest so save-prefix/catalog/etc. take
|
|
1582
|
+
// effect (the server applies these during its resolution step).
|
|
1583
|
+
const lockfileDir = opts.lockfileDir ?? projects[0].rootDir;
|
|
1584
|
+
const mutatedRootDirs = new Set(projects.map((p) => p.rootDir));
|
|
1585
|
+
const updatedProjects = agentProjects
|
|
1586
|
+
.filter((p) => mutatedRootDirs.has(p.rootDir))
|
|
1587
|
+
.map((p) => {
|
|
1588
|
+
if (p.mutation === 'installSome' && p.newDeps.length > 0) {
|
|
1589
|
+
// Lockfile importer keys are POSIX-normalized paths.
|
|
1590
|
+
const relative = path.relative(lockfileDir, p.rootDir).split(path.sep).join('/');
|
|
1591
|
+
const importerId = (relative || '.');
|
|
1592
|
+
const snapshot = result.lockfile?.importers?.[importerId];
|
|
1593
|
+
p.manifest = applyResolvedSpecsFromLockfile(p.manifest, snapshot, p.newDeps, p.pinnedVersion);
|
|
1594
|
+
}
|
|
1595
|
+
return { rootDir: p.rootDir, manifest: p.manifest };
|
|
1596
|
+
});
|
|
1597
|
+
return {
|
|
1598
|
+
updatedProjects,
|
|
1599
|
+
stats: result.stats,
|
|
1600
|
+
ignoredBuilds: result.ignoredBuilds,
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1603
|
+
/**
|
|
1604
|
+
* When a pnpm agent is configured, resolve dependencies server-side
|
|
1605
|
+
* and download only the missing files. Then run a headless install to link
|
|
1606
|
+
* packages into node_modules.
|
|
1607
|
+
*/
|
|
1608
|
+
async function installFromPnpmRegistry(manifest, rootDir, opts, allInstallProjects) {
|
|
1609
|
+
const { fetchFromPnpmRegistry } = await import('@pnpm/agent.client');
|
|
1610
|
+
const { StoreIndex } = await import('@pnpm/store.index');
|
|
1611
|
+
const { setImportConcurrency } = await import('@pnpm/worker');
|
|
1612
|
+
// Raise import concurrency for this install only — the agent path has no
|
|
1613
|
+
// concurrent fetching competing for workers. Restore afterwards so we
|
|
1614
|
+
// don't leak a process-wide mutation to other installs (e.g. tests).
|
|
1615
|
+
const restoreImportConcurrency = setImportConcurrency(6);
|
|
1616
|
+
try {
|
|
1617
|
+
const lockfileDir = opts.lockfileDir ?? rootDir;
|
|
1618
|
+
// Read existing lockfile if available
|
|
1619
|
+
const existingLockfile = await readWantedLockfile(lockfileDir, {
|
|
1620
|
+
ignoreIncompatible: true,
|
|
1621
|
+
}).catch(() => null);
|
|
1622
|
+
logger.info({ message: 'Resolving dependencies via pnpm agent', prefix: rootDir });
|
|
1623
|
+
// Open the store index to read integrities and write new entries.
|
|
1624
|
+
// Close it in a finally so a failure in fetchFromPnpmRegistry doesn't
|
|
1625
|
+
// leak an open SQLite handle (on Windows that also blocks store cleanup).
|
|
1626
|
+
const storeIndex = new StoreIndex(opts.storeDir);
|
|
1627
|
+
let lockfile, agentStats, fileDownloads, indexEntries;
|
|
1628
|
+
try {
|
|
1629
|
+
// Build projects list for workspace support.
|
|
1630
|
+
// Normalize separators to POSIX — on Windows `path.relative` returns
|
|
1631
|
+
// backslashes, which the agent server rejects (it treats `\` as an
|
|
1632
|
+
// unsafe/YAML-injection character and normalizes paths as POSIX).
|
|
1633
|
+
const projectsList = allInstallProjects && allInstallProjects.length > 1
|
|
1634
|
+
? allInstallProjects.map(p => ({
|
|
1635
|
+
dir: (path.relative(lockfileDir, p.rootDir) || '.').split(path.sep).join('/'),
|
|
1636
|
+
dependencies: p.manifest.dependencies,
|
|
1637
|
+
devDependencies: p.manifest.devDependencies,
|
|
1638
|
+
}))
|
|
1639
|
+
: undefined;
|
|
1640
|
+
({ lockfile, stats: agentStats, fileDownloads, indexEntries } = await fetchFromPnpmRegistry({
|
|
1641
|
+
registryUrl: opts.agent,
|
|
1642
|
+
storeDir: opts.storeDir,
|
|
1643
|
+
storeIndex,
|
|
1644
|
+
dependencies: projectsList ? undefined : manifest.dependencies,
|
|
1645
|
+
devDependencies: projectsList ? undefined : manifest.devDependencies,
|
|
1646
|
+
projects: projectsList,
|
|
1647
|
+
overrides: opts.overrides,
|
|
1648
|
+
minimumReleaseAge: opts.minimumReleaseAge,
|
|
1649
|
+
lockfile: existingLockfile ?? undefined,
|
|
1650
|
+
}));
|
|
1651
|
+
// Write store index entries so headless install finds them.
|
|
1652
|
+
const { writeRawIndexEntries } = await import('@pnpm/agent.client');
|
|
1653
|
+
writeRawIndexEntries(indexEntries, storeIndex);
|
|
1654
|
+
storeIndex.checkpoint();
|
|
1655
|
+
}
|
|
1656
|
+
finally {
|
|
1657
|
+
storeIndex.close();
|
|
1658
|
+
}
|
|
1659
|
+
await writeWantedLockfile(lockfileDir, lockfile);
|
|
1660
|
+
logger.info({
|
|
1661
|
+
message: `Resolved ${agentStats.totalPackages} packages: ${agentStats.alreadyInStore} cached, ${agentStats.filesToDownload} files to download`,
|
|
1662
|
+
prefix: rootDir,
|
|
1663
|
+
});
|
|
1664
|
+
// Wrap fetchPackage to:
|
|
1665
|
+
// 1. Wait for agent file downloads before checking the store
|
|
1666
|
+
// 2. Skip integrity verification — files just written from the agent
|
|
1667
|
+
// are guaranteed correct (server verified, no rehashing needed)
|
|
1668
|
+
const { readPkgFromCafs } = await import('@pnpm/worker');
|
|
1669
|
+
const { storeIndexKey: _storeIndexKey } = await import('@pnpm/store.index');
|
|
1670
|
+
const wrappedStoreController = {
|
|
1671
|
+
...opts.storeController,
|
|
1672
|
+
fetchPackage: async (fetchOpts) => {
|
|
1673
|
+
await fileDownloads;
|
|
1674
|
+
const resolution = fetchOpts.pkg.resolution;
|
|
1675
|
+
const integrity = resolution?.integrity;
|
|
1676
|
+
if (integrity) {
|
|
1677
|
+
const filesIndexFile = _storeIndexKey(integrity, fetchOpts.pkg.id);
|
|
1678
|
+
const result = await readPkgFromCafs({ storeDir: opts.storeDir, verifyStoreIntegrity: false }, filesIndexFile, { readManifest: true, expectedPkg: { name: fetchOpts.pkg.name, version: fetchOpts.pkg.version } });
|
|
1679
|
+
return {
|
|
1680
|
+
fetching: () => Promise.resolve({
|
|
1681
|
+
files: result.files,
|
|
1682
|
+
bundledManifest: result.bundledManifest,
|
|
1683
|
+
integrity,
|
|
1684
|
+
}),
|
|
1685
|
+
filesIndexFile,
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1688
|
+
return opts.storeController.fetchPackage(fetchOpts);
|
|
1689
|
+
},
|
|
1690
|
+
};
|
|
1691
|
+
const headlessOpts = {
|
|
1692
|
+
...opts,
|
|
1693
|
+
// Skip re-verifying files just written from the agent — they're
|
|
1694
|
+
// guaranteed correct (server verified, no rehashing needed).
|
|
1695
|
+
verifyStoreIntegrity: false,
|
|
1696
|
+
storeController: wrappedStoreController,
|
|
1697
|
+
dir: rootDir,
|
|
1698
|
+
lockfileDir,
|
|
1699
|
+
engineStrict: opts.engineStrict ?? false,
|
|
1700
|
+
ignoreScripts: opts.ignoreScripts ?? false,
|
|
1701
|
+
sideEffectsCacheRead: opts.sideEffectsCacheRead ?? false,
|
|
1702
|
+
sideEffectsCacheWrite: opts.sideEffectsCacheWrite ?? false,
|
|
1703
|
+
symlink: opts.symlink ?? true,
|
|
1704
|
+
enableModulesDir: opts.enableModulesDir ?? true,
|
|
1705
|
+
include: opts.include ?? { dependencies: true, devDependencies: true, optionalDependencies: true },
|
|
1706
|
+
currentEngine: {
|
|
1707
|
+
nodeVersion: opts.nodeVersion,
|
|
1708
|
+
pnpmVersion: opts.packageManager?.version ?? '',
|
|
1709
|
+
},
|
|
1710
|
+
selectedProjectDirs: (allInstallProjects ?? [{ rootDir }]).map(p => p.rootDir),
|
|
1711
|
+
allProjects: Object.fromEntries((allInstallProjects ?? [{ rootDir, manifest }]).map((p, i) => [
|
|
1712
|
+
p.rootDir,
|
|
1713
|
+
{
|
|
1714
|
+
binsDir: path.join(p.rootDir, 'node_modules', '.bin'),
|
|
1715
|
+
buildIndex: i,
|
|
1716
|
+
id: (path.relative(lockfileDir, p.rootDir) || '.'),
|
|
1717
|
+
manifest: p.manifest,
|
|
1718
|
+
modulesDir: path.join(p.rootDir, 'node_modules'),
|
|
1719
|
+
rootDir: p.rootDir,
|
|
1720
|
+
},
|
|
1721
|
+
])),
|
|
1722
|
+
hoistedDependencies: {},
|
|
1723
|
+
pendingBuilds: [],
|
|
1724
|
+
skipped: new Set(),
|
|
1725
|
+
wantedLockfile: lockfile,
|
|
1726
|
+
};
|
|
1727
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1728
|
+
const { ignoredBuilds, stats } = await headlessInstall(headlessOpts);
|
|
1729
|
+
return {
|
|
1730
|
+
updatedCatalogs: undefined,
|
|
1731
|
+
updatedManifest: manifest,
|
|
1732
|
+
ignoredBuilds,
|
|
1733
|
+
stats,
|
|
1734
|
+
lockfile,
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
finally {
|
|
1738
|
+
// Close the storeController to flush queued StoreIndex writes — the
|
|
1739
|
+
// normal install path does the same; skipping it here would leave
|
|
1740
|
+
// pending writes on disk and diverge from lifecycle expectations.
|
|
1741
|
+
await opts.storeController.close();
|
|
1742
|
+
restoreImportConcurrency();
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1377
1745
|
//# sourceMappingURL=index.js.map
|
package/lib/install/link.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { InstallationResultStats } from '@pnpm/installing.deps-restorer';
|
|
|
4
4
|
import type { IncludedDependencies } from '@pnpm/installing.modules-yaml';
|
|
5
5
|
import type { LockfileObject } from '@pnpm/lockfile.fs';
|
|
6
6
|
import type { StoreController } from '@pnpm/store.controller-types';
|
|
7
|
-
import type { AllowBuild, DepPath, HoistedDependencies, Registries } from '@pnpm/types';
|
|
7
|
+
import type { AllowBuild, DepPath, HoistedDependencies, Registries, SupportedArchitectures } from '@pnpm/types';
|
|
8
8
|
import type { ImporterToUpdate } from './index.js';
|
|
9
9
|
export interface LinkPackagesOptions {
|
|
10
10
|
allowBuild?: AllowBuild;
|
|
@@ -40,6 +40,7 @@ export interface LinkPackagesOptions {
|
|
|
40
40
|
wantedToBeSkippedPackageIds: Set<string>;
|
|
41
41
|
hoistWorkspacePackages?: boolean;
|
|
42
42
|
virtualStoreOnly: boolean;
|
|
43
|
+
supportedArchitectures?: SupportedArchitectures;
|
|
43
44
|
}
|
|
44
45
|
export interface LinkPackagesResult {
|
|
45
46
|
currentLockfile: LockfileObject;
|
package/lib/install/link.js
CHANGED
|
@@ -83,6 +83,7 @@ export async function linkPackages(projects, depGraph, opts) {
|
|
|
83
83
|
symlink: opts.symlink,
|
|
84
84
|
skipped: opts.skipped,
|
|
85
85
|
storeController: opts.storeController,
|
|
86
|
+
supportedArchitectures: opts.supportedArchitectures,
|
|
86
87
|
virtualStoreDir: opts.virtualStoreDir,
|
|
87
88
|
});
|
|
88
89
|
stageLogger.debug({
|
|
@@ -284,6 +285,7 @@ async function linkNewPackages(currentLockfile, wantedLockfile, depGraph, opts)
|
|
|
284
285
|
ignoreScripts: opts.ignoreScripts,
|
|
285
286
|
lockfileDir: opts.lockfileDir,
|
|
286
287
|
sideEffectsCacheRead: opts.sideEffectsCacheRead,
|
|
288
|
+
supportedArchitectures: opts.supportedArchitectures,
|
|
287
289
|
}),
|
|
288
290
|
]);
|
|
289
291
|
return { newDepPaths, added };
|
|
@@ -323,6 +325,7 @@ async function linkAllPkgs(storeController, depNodes, opts) {
|
|
|
323
325
|
sideEffectsCacheKey = calcDepState(opts.depGraph, opts.depsStateCache, depNode.depPath, {
|
|
324
326
|
includeDepGraphHash: !opts.ignoreScripts && depNode.requiresBuild, // true when is built
|
|
325
327
|
patchFileHash: depNode.patch?.hash,
|
|
328
|
+
supportedArchitectures: opts.supportedArchitectures,
|
|
326
329
|
});
|
|
327
330
|
}
|
|
328
331
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pnpm/installing.deps-installer",
|
|
3
|
-
"version": "1100.0.
|
|
3
|
+
"version": "1100.0.3",
|
|
4
4
|
"description": "Fast, disk space efficient installation engine",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -61,58 +61,60 @@
|
|
|
61
61
|
"ramda": "npm:@pnpm/ramda@0.28.1",
|
|
62
62
|
"run-groups": "^5.0.0",
|
|
63
63
|
"semver": "^7.7.2",
|
|
64
|
-
"@pnpm/
|
|
64
|
+
"@pnpm/agent.client": "0.0.1",
|
|
65
|
+
"@pnpm/bins.remover": "1100.0.1",
|
|
66
|
+
"@pnpm/building.after-install": "1100.0.3",
|
|
67
|
+
"@pnpm/building.during-install": "1100.0.3",
|
|
65
68
|
"@pnpm/bins.linker": "1100.0.2",
|
|
66
69
|
"@pnpm/building.policy": "1100.0.1",
|
|
67
|
-
"@pnpm/building.during-install": "1100.0.2",
|
|
68
|
-
"@pnpm/bins.remover": "1100.0.1",
|
|
69
70
|
"@pnpm/catalogs.protocol-parser": "1100.0.0",
|
|
70
|
-
"@pnpm/config.matcher": "1100.0.0",
|
|
71
|
-
"@pnpm/catalogs.types": "1100.0.0",
|
|
72
|
-
"@pnpm/config.normalize-registries": "1100.0.1",
|
|
73
71
|
"@pnpm/catalogs.resolver": "1100.0.0",
|
|
72
|
+
"@pnpm/config.matcher": "1100.0.0",
|
|
74
73
|
"@pnpm/config.parse-overrides": "1100.0.0",
|
|
74
|
+
"@pnpm/config.normalize-registries": "1100.0.1",
|
|
75
|
+
"@pnpm/catalogs.types": "1100.0.0",
|
|
75
76
|
"@pnpm/constants": "1100.0.0",
|
|
76
77
|
"@pnpm/crypto.hash": "1100.0.0",
|
|
78
|
+
"@pnpm/core-loggers": "1100.0.1",
|
|
77
79
|
"@pnpm/crypto.object-hasher": "1100.0.0",
|
|
78
|
-
"@pnpm/deps.graph-hasher": "1100.0
|
|
80
|
+
"@pnpm/deps.graph-hasher": "1100.1.0",
|
|
79
81
|
"@pnpm/deps.graph-sequencer": "1100.0.0",
|
|
80
|
-
"@pnpm/core-loggers": "1100.0.1",
|
|
81
82
|
"@pnpm/deps.path": "1100.0.1",
|
|
82
|
-
"@pnpm/
|
|
83
|
+
"@pnpm/exec.lifecycle": "1100.0.3",
|
|
83
84
|
"@pnpm/fs.read-modules-dir": "1100.0.0",
|
|
84
85
|
"@pnpm/fs.symlink-dependency": "1100.0.1",
|
|
85
|
-
"@pnpm/
|
|
86
|
+
"@pnpm/hooks.types": "1100.0.2",
|
|
87
|
+
"@pnpm/error": "1100.0.0",
|
|
86
88
|
"@pnpm/hooks.read-package-hook": "1100.0.1",
|
|
87
|
-
"@pnpm/installing.context": "1100.0.
|
|
88
|
-
"@pnpm/
|
|
89
|
-
"@pnpm/installing.deps-
|
|
90
|
-
"@pnpm/installing.linking.direct-dep-linker": "1100.0.1",
|
|
91
|
-
"@pnpm/installing.deps-restorer": "1100.0.2",
|
|
89
|
+
"@pnpm/installing.context": "1100.0.2",
|
|
90
|
+
"@pnpm/installing.deps-resolver": "1100.0.3",
|
|
91
|
+
"@pnpm/installing.deps-restorer": "1100.0.3",
|
|
92
92
|
"@pnpm/installing.linking.hoist": "1100.0.2",
|
|
93
|
-
"@pnpm/installing.linking.modules-cleaner": "1100.0.
|
|
94
|
-
"@pnpm/installing.
|
|
95
|
-
"@pnpm/lockfile.filtering": "1100.0.1",
|
|
96
|
-
"@pnpm/lockfile.fs": "1100.0.1",
|
|
97
|
-
"@pnpm/lockfile.preferred-versions": "1100.0.2",
|
|
93
|
+
"@pnpm/installing.linking.modules-cleaner": "1100.0.2",
|
|
94
|
+
"@pnpm/installing.linking.direct-dep-linker": "1100.0.1",
|
|
98
95
|
"@pnpm/installing.modules-yaml": "1100.0.1",
|
|
99
|
-
"@pnpm/lockfile.
|
|
100
|
-
"@pnpm/lockfile.
|
|
101
|
-
"@pnpm/lockfile.
|
|
102
|
-
"@pnpm/lockfile.
|
|
103
|
-
"@pnpm/lockfile.
|
|
96
|
+
"@pnpm/lockfile.filtering": "1100.0.2",
|
|
97
|
+
"@pnpm/lockfile.fs": "1100.0.2",
|
|
98
|
+
"@pnpm/lockfile.preferred-versions": "1100.0.3",
|
|
99
|
+
"@pnpm/lockfile.settings-checker": "1100.0.2",
|
|
100
|
+
"@pnpm/lockfile.pruner": "1100.0.2",
|
|
101
|
+
"@pnpm/installing.package-requester": "1100.0.2",
|
|
102
|
+
"@pnpm/lockfile.to-pnp": "1100.0.2",
|
|
103
|
+
"@pnpm/lockfile.utils": "1100.0.2",
|
|
104
|
+
"@pnpm/lockfile.walker": "1100.0.2",
|
|
104
105
|
"@pnpm/patching.config": "1100.0.1",
|
|
105
|
-
"@pnpm/
|
|
106
|
-
"@pnpm/
|
|
106
|
+
"@pnpm/resolving.resolver-base": "1100.1.0",
|
|
107
|
+
"@pnpm/lockfile.verification": "1100.0.2",
|
|
107
108
|
"@pnpm/resolving.parse-wanted-dependency": "1100.0.0",
|
|
108
|
-
"@pnpm/
|
|
109
|
+
"@pnpm/store.controller-types": "1100.0.2",
|
|
110
|
+
"@pnpm/pkg-manifest.utils": "1100.1.0",
|
|
111
|
+
"@pnpm/store.index": "1100.0.0",
|
|
109
112
|
"@pnpm/types": "1101.0.0",
|
|
110
|
-
"@pnpm/workspace.project-manifest-reader": "1100.0.2"
|
|
111
|
-
"@pnpm/store.controller-types": "1100.0.1"
|
|
113
|
+
"@pnpm/workspace.project-manifest-reader": "1100.0.2"
|
|
112
114
|
},
|
|
113
115
|
"peerDependencies": {
|
|
114
116
|
"@pnpm/logger": ">=1001.0.0 <1002.0.0",
|
|
115
|
-
"@pnpm/worker": "^1100.0.
|
|
117
|
+
"@pnpm/worker": "^1100.0.2"
|
|
116
118
|
},
|
|
117
119
|
"devDependencies": {
|
|
118
120
|
"@jest/globals": "30.3.0",
|
|
@@ -135,22 +137,21 @@
|
|
|
135
137
|
"symlink-dir": "^10.0.1",
|
|
136
138
|
"write-json-file": "^7.0.0",
|
|
137
139
|
"write-yaml-file": "^6.0.0",
|
|
138
|
-
"@pnpm/assert-project": "1100.0.
|
|
139
|
-
"@pnpm/installing.deps-installer": "1100.0.
|
|
140
|
-
"@pnpm/assert-store": "1100.0.
|
|
141
|
-
"@pnpm/lockfile.types": "1100.0.1",
|
|
140
|
+
"@pnpm/assert-project": "1100.0.2",
|
|
141
|
+
"@pnpm/installing.deps-installer": "1100.0.3",
|
|
142
|
+
"@pnpm/assert-store": "1100.0.2",
|
|
142
143
|
"@pnpm/logger": "1100.0.0",
|
|
144
|
+
"@pnpm/lockfile.types": "1100.0.2",
|
|
145
|
+
"@pnpm/prepare": "1100.0.2",
|
|
146
|
+
"@pnpm/resolving.registry.types": "1100.0.1",
|
|
143
147
|
"@pnpm/network.git-utils": "1100.0.0",
|
|
144
|
-
"@pnpm/prepare": "1100.0.1",
|
|
145
148
|
"@pnpm/pkg-manifest.reader": "1100.0.1",
|
|
146
|
-
"@pnpm/store.cafs": "1100.0.
|
|
147
|
-
"@pnpm/store.index": "1100.0.0",
|
|
148
|
-
"@pnpm/test-fixtures": "1100.0.0",
|
|
149
|
-
"@pnpm/resolving.registry.types": "1100.0.1",
|
|
149
|
+
"@pnpm/store.cafs": "1100.0.2",
|
|
150
150
|
"@pnpm/store.path": "1100.0.0",
|
|
151
151
|
"@pnpm/test-ipc-server": "1100.0.0",
|
|
152
|
-
"@pnpm/
|
|
153
|
-
"@pnpm/testing.mock-agent": "1100.0.1"
|
|
152
|
+
"@pnpm/test-fixtures": "1100.0.0",
|
|
153
|
+
"@pnpm/testing.mock-agent": "1100.0.1",
|
|
154
|
+
"@pnpm/testing.temp-store": "1100.0.3"
|
|
154
155
|
},
|
|
155
156
|
"engines": {
|
|
156
157
|
"node": ">=22.13"
|