@pnpm/installing.deps-installer 1012.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +259 -0
  3. package/lib/api.d.ts +3 -0
  4. package/lib/api.js +4 -0
  5. package/lib/getPeerDependencyIssues.d.ts +5 -0
  6. package/lib/getPeerDependencyIssues.js +70 -0
  7. package/lib/index.d.ts +9 -0
  8. package/lib/index.js +5 -0
  9. package/lib/install/checkCompatibility/BreakingChangeError.d.ts +12 -0
  10. package/lib/install/checkCompatibility/BreakingChangeError.js +13 -0
  11. package/lib/install/checkCompatibility/CatalogVersionMismatchError.d.ts +9 -0
  12. package/lib/install/checkCompatibility/CatalogVersionMismatchError.js +11 -0
  13. package/lib/install/checkCompatibility/ErrorRelatedSources.d.ts +5 -0
  14. package/lib/install/checkCompatibility/ErrorRelatedSources.js +2 -0
  15. package/lib/install/checkCompatibility/ModulesBreakingChangeError.d.ts +9 -0
  16. package/lib/install/checkCompatibility/ModulesBreakingChangeError.js +15 -0
  17. package/lib/install/checkCompatibility/UnexpectedStoreError.d.ts +11 -0
  18. package/lib/install/checkCompatibility/UnexpectedStoreError.js +13 -0
  19. package/lib/install/checkCompatibility/UnexpectedVirtualStoreDirError.d.ts +11 -0
  20. package/lib/install/checkCompatibility/UnexpectedVirtualStoreDirError.js +13 -0
  21. package/lib/install/checkCompatibility/index.d.ts +6 -0
  22. package/lib/install/checkCompatibility/index.js +32 -0
  23. package/lib/install/checkCustomResolverForceResolve.d.ts +10 -0
  24. package/lib/install/checkCustomResolverForceResolve.js +47 -0
  25. package/lib/install/extendInstallOptions.d.ts +163 -0
  26. package/lib/install/extendInstallOptions.js +156 -0
  27. package/lib/install/index.d.ts +110 -0
  28. package/lib/install/index.js +1346 -0
  29. package/lib/install/link.d.ts +51 -0
  30. package/lib/install/link.js +382 -0
  31. package/lib/install/reportPeerDependencyIssues.d.ts +12 -0
  32. package/lib/install/reportPeerDependencyIssues.js +116 -0
  33. package/lib/install/validateModules.d.ts +26 -0
  34. package/lib/install/validateModules.js +135 -0
  35. package/lib/parseWantedDependencies.d.ts +17 -0
  36. package/lib/parseWantedDependencies.js +51 -0
  37. package/lib/pnpmPkgJson.d.ts +3 -0
  38. package/lib/pnpmPkgJson.js +14 -0
  39. package/lib/types.d.ts +2 -0
  40. package/lib/types.js +2 -0
  41. package/lib/uninstall/removeDeps.d.ts +5 -0
  42. package/lib/uninstall/removeDeps.js +36 -0
  43. package/package.json +168 -0
@@ -0,0 +1,1346 @@
1
+ import path from 'node:path';
2
+ import { linkBins, linkBinsOfPackages } from '@pnpm/bins.linker';
3
+ import { buildSelectedPkgs } from '@pnpm/building.after-install';
4
+ import { buildModules, linkBinsOfDependencies } from '@pnpm/building.during-install';
5
+ import { createAllowBuildFunction } from '@pnpm/building.policy';
6
+ import { parseCatalogProtocol } from '@pnpm/catalogs.protocol-parser';
7
+ import { matchCatalogResolveResult, resolveFromCatalog } from '@pnpm/catalogs.resolver';
8
+ import { LAYOUT_VERSION, LOCKFILE_MAJOR_VERSION, LOCKFILE_VERSION, WANTED_LOCKFILE, } from '@pnpm/constants';
9
+ import { ignoredScriptsLogger, stageLogger, summaryLogger, } from '@pnpm/core-loggers';
10
+ import { hashObjectNullableWithPrefix } from '@pnpm/crypto.object-hasher';
11
+ import * as dp from '@pnpm/deps.path';
12
+ import { PnpmError } from '@pnpm/error';
13
+ import { makeNodeRequireOption, runLifecycleHook, runLifecycleHooksConcurrently, } from '@pnpm/exec.lifecycle';
14
+ import { getContext } from '@pnpm/installing.context';
15
+ import { getWantedDependencies, resolveDependencies, } from '@pnpm/installing.deps-resolver';
16
+ import { extendProjectsWithTargetDirs, headlessInstall } from '@pnpm/installing.deps-restorer';
17
+ import { writeModulesManifest } from '@pnpm/installing.modules-yaml';
18
+ import { cleanGitBranchLockfiles, writeCurrentLockfile, writeLockfiles, writeWantedLockfile, } from '@pnpm/lockfile.fs';
19
+ import { getPreferredVersionsFromLockfileAndManifests } from '@pnpm/lockfile.preferred-versions';
20
+ import { calcPatchHashes, createOverridesMapFromParsed, getOutdatedLockfileSetting, } from '@pnpm/lockfile.settings-checker';
21
+ import { writePnpFile } from '@pnpm/lockfile.to-pnp';
22
+ import { allProjectsAreUpToDate, satisfiesPackageManifest } from '@pnpm/lockfile.verification';
23
+ import { globalInfo, logger, streamParser } from '@pnpm/logger';
24
+ import { groupPatchedDependencies } from '@pnpm/patching.config';
25
+ import { getAllDependenciesFromManifest, getAllUniqueSpecs } from '@pnpm/pkg-manifest.utils';
26
+ import { lexCompare } from '@pnpm/util.lex-comparator';
27
+ import { safeReadProjectManifestOnly } from '@pnpm/workspace.project-manifest-reader';
28
+ import { isSubdir } from 'is-subdir';
29
+ import pLimit from 'p-limit';
30
+ import { clone, isEmpty, map as mapValues, pipeWith, props } from 'ramda';
31
+ import semver from 'semver';
32
+ import { parseWantedDependencies } from '../parseWantedDependencies.js';
33
+ import { removeDeps } from '../uninstall/removeDeps.js';
34
+ import { CatalogVersionMismatchError } from './checkCompatibility/CatalogVersionMismatchError.js';
35
+ import { checkCustomResolverForceResolve } from './checkCustomResolverForceResolve.js';
36
+ import { extendOptions, } from './extendInstallOptions.js';
37
+ import { linkPackages } from './link.js';
38
+ import { reportPeerDependencyIssues } from './reportPeerDependencyIssues.js';
39
+ import { validateModules } from './validateModules.js';
40
+ class LockfileConfigMismatchError extends PnpmError {
41
+ constructor(outdatedLockfileSettingName) {
42
+ super('LOCKFILE_CONFIG_MISMATCH', `Cannot proceed with the frozen installation. The current "${outdatedLockfileSettingName}" configuration doesn't match the value found in the lockfile`, {
43
+ hint: 'Update your lockfile using "pnpm install --no-frozen-lockfile"',
44
+ });
45
+ }
46
+ }
47
+ const BROKEN_LOCKFILE_INTEGRITY_ERRORS = new Set([
48
+ 'ERR_PNPM_UNEXPECTED_PKG_CONTENT_IN_STORE',
49
+ 'ERR_PNPM_TARBALL_INTEGRITY',
50
+ ]);
51
+ const DEV_PREINSTALL = 'pnpm:devPreinstall';
52
+ export async function install(manifest, opts) {
53
+ const rootDir = (opts.dir ?? process.cwd());
54
+ const { updatedCatalogs, updatedProjects: projects, ignoredBuilds } = await mutateModules([
55
+ {
56
+ mutation: 'install',
57
+ pruneDirectDependencies: opts.pruneDirectDependencies,
58
+ rootDir,
59
+ update: opts.update,
60
+ updateMatching: opts.updateMatching,
61
+ updateToLatest: opts.updateToLatest,
62
+ updatePackageManifest: opts.updatePackageManifest,
63
+ },
64
+ ], {
65
+ ...opts,
66
+ allProjects: [{
67
+ buildIndex: 0,
68
+ manifest,
69
+ rootDir,
70
+ binsDir: opts.binsDir,
71
+ }],
72
+ });
73
+ return { updatedCatalogs, updatedManifest: projects[0].manifest, ignoredBuilds };
74
+ }
75
+ export async function mutateModulesInSingleProject(project, maybeOpts) {
76
+ const result = await mutateModules([
77
+ {
78
+ ...project,
79
+ update: maybeOpts.update,
80
+ updateToLatest: maybeOpts.updateToLatest,
81
+ updateMatching: maybeOpts.updateMatching,
82
+ updatePackageManifest: maybeOpts.updatePackageManifest,
83
+ },
84
+ ], {
85
+ ...maybeOpts,
86
+ allProjects: [{
87
+ buildIndex: 0,
88
+ ...project,
89
+ }],
90
+ });
91
+ return {
92
+ updatedCatalogs: result.updatedCatalogs,
93
+ updatedProject: result.updatedProjects[0],
94
+ ignoredBuilds: result.ignoredBuilds,
95
+ };
96
+ }
97
+ const pickCatalogSpecifier = {
98
+ found: (found) => found.resolution.specifier,
99
+ misconfiguration: () => undefined,
100
+ unused: () => undefined,
101
+ };
102
+ export async function mutateModules(projects, maybeOpts) {
103
+ const reporter = maybeOpts?.reporter;
104
+ if ((reporter != null) && typeof reporter === 'function') {
105
+ streamParser.on('data', reporter);
106
+ }
107
+ const opts = extendOptions(maybeOpts);
108
+ const allowBuild = createAllowBuildFunction(opts);
109
+ if (!opts.include.dependencies && opts.include.optionalDependencies) {
110
+ throw new PnpmError('OPTIONAL_DEPS_REQUIRE_PROD_DEPS', 'Optional dependencies cannot be installed without production dependencies');
111
+ }
112
+ const installsOnly = allMutationsAreInstalls(projects);
113
+ if (!installsOnly)
114
+ opts.strictPeerDependencies = false;
115
+ const rootProjectManifest = opts.allProjects.find(({ rootDir }) => rootDir === opts.lockfileDir)?.manifest ??
116
+ // When running install/update on a subset of projects, the root project might not be included,
117
+ // so reading its manifest explicitly here.
118
+ await safeReadProjectManifestOnly(opts.lockfileDir);
119
+ let ctx = await getContext(opts);
120
+ if (!opts.lockfileOnly && ctx.modulesFile != null) {
121
+ const { purged } = await validateModules(ctx.modulesFile, Object.values(ctx.projects), {
122
+ forceNewModules: installsOnly,
123
+ include: opts.include,
124
+ lockfileDir: opts.lockfileDir,
125
+ modulesDir: opts.modulesDir ?? 'node_modules',
126
+ registries: opts.registries,
127
+ storeDir: opts.storeDir,
128
+ virtualStoreDir: ctx.virtualStoreDir,
129
+ virtualStoreDirMaxLength: opts.virtualStoreDirMaxLength,
130
+ confirmModulesPurge: opts.confirmModulesPurge && !opts.ci,
131
+ forceHoistPattern: opts.forceHoistPattern,
132
+ hoistPattern: opts.hoistPattern,
133
+ currentHoistPattern: ctx.currentHoistPattern,
134
+ forcePublicHoistPattern: opts.forcePublicHoistPattern,
135
+ publicHoistPattern: opts.publicHoistPattern,
136
+ currentPublicHoistPattern: ctx.currentPublicHoistPattern,
137
+ global: opts.global,
138
+ });
139
+ if (purged) {
140
+ ctx = await getContext(opts);
141
+ }
142
+ }
143
+ if (opts.hooks.preResolution) {
144
+ for (const preResolution of opts.hooks.preResolution) {
145
+ // eslint-disable-next-line no-await-in-loop
146
+ await preResolution({
147
+ currentLockfile: ctx.currentLockfile,
148
+ wantedLockfile: ctx.wantedLockfile,
149
+ existsCurrentLockfile: ctx.existsCurrentLockfile,
150
+ existsNonEmptyWantedLockfile: ctx.existsNonEmptyWantedLockfile,
151
+ lockfileDir: ctx.lockfileDir,
152
+ storeDir: ctx.storeDir,
153
+ registries: ctx.registries,
154
+ });
155
+ }
156
+ }
157
+ // Check if any custom resolvers want to force resolution for specific dependencies
158
+ // Skip this check when not saving the lockfile (e.g., during deploy) since there's no point
159
+ // in forcing re-resolution if we're not going to persist the results
160
+ let forceResolutionFromHook = false;
161
+ const shouldCheckCustomResolverForceResolve = opts.hooks.customResolvers &&
162
+ ctx.existsNonEmptyWantedLockfile &&
163
+ !opts.frozenLockfile &&
164
+ opts.saveLockfile;
165
+ if (shouldCheckCustomResolverForceResolve) {
166
+ forceResolutionFromHook = await checkCustomResolverForceResolve(opts.hooks.customResolvers, ctx.wantedLockfile);
167
+ }
168
+ const pruneVirtualStore = !opts.enableGlobalVirtualStore && (ctx.modulesFile?.prunedAt && opts.modulesCacheMaxAge > 0
169
+ ? cacheExpired(ctx.modulesFile.prunedAt, opts.modulesCacheMaxAge)
170
+ : true);
171
+ if (!maybeOpts.ignorePackageManifest) {
172
+ for (const { manifest, rootDir } of Object.values(ctx.projects)) {
173
+ if (!manifest) {
174
+ throw new Error(`No package.json found in "${rootDir}"`);
175
+ }
176
+ }
177
+ }
178
+ const result = await _install();
179
+ // @ts-expect-error
180
+ if (global['verifiedFileIntegrity'] > 1000) {
181
+ // @ts-expect-error
182
+ globalInfo(`The integrity of ${global['verifiedFileIntegrity']} files was checked. This might have caused installation to take longer.`);
183
+ }
184
+ if (opts.mergeGitBranchLockfiles) {
185
+ await cleanGitBranchLockfiles(ctx.lockfileDir);
186
+ }
187
+ let ignoredBuilds = result.ignoredBuilds;
188
+ if (!opts.ignoreScripts && ignoredBuilds?.size) {
189
+ ignoredBuilds = await runUnignoredDependencyBuilds(opts, ignoredBuilds, allowBuild);
190
+ }
191
+ ignoredScriptsLogger.debug({
192
+ packageNames: ignoredBuilds ? dedupePackageNamesFromIgnoredBuilds(ignoredBuilds) : [],
193
+ });
194
+ if ((reporter != null) && typeof reporter === 'function') {
195
+ streamParser.removeListener('data', reporter);
196
+ }
197
+ return {
198
+ updatedCatalogs: result.updatedCatalogs,
199
+ updatedProjects: result.updatedProjects,
200
+ stats: result.stats ?? { added: 0, removed: 0, linkedToRoot: 0 },
201
+ depsRequiringBuild: result.depsRequiringBuild,
202
+ ignoredBuilds,
203
+ };
204
+ async function _install() {
205
+ const scriptsOpts = {
206
+ extraBinPaths: opts.extraBinPaths,
207
+ extraNodePaths: ctx.extraNodePaths,
208
+ extraEnv: opts.extraEnv,
209
+ preferSymlinkedExecutables: opts.preferSymlinkedExecutables,
210
+ rawConfig: opts.rawConfig,
211
+ resolveSymlinksInInjectedDirs: opts.resolveSymlinksInInjectedDirs,
212
+ scriptsPrependNodePath: opts.scriptsPrependNodePath,
213
+ scriptShell: opts.scriptShell,
214
+ shellEmulator: opts.shellEmulator,
215
+ stdio: opts.ownLifecycleHooksStdio,
216
+ storeController: opts.storeController,
217
+ unsafePerm: opts.unsafePerm || false,
218
+ };
219
+ if (!opts.ignoreScripts && !opts.ignorePackageManifest && rootProjectManifest?.scripts?.[DEV_PREINSTALL]) {
220
+ await runLifecycleHook(DEV_PREINSTALL, rootProjectManifest, {
221
+ ...scriptsOpts,
222
+ depPath: opts.lockfileDir,
223
+ pkgRoot: opts.lockfileDir,
224
+ rootModulesDir: ctx.rootModulesDir,
225
+ });
226
+ }
227
+ const packageExtensionsChecksum = hashObjectNullableWithPrefix(opts.packageExtensions);
228
+ const pnpmfileChecksum = await opts.hooks.calculatePnpmfileChecksum?.();
229
+ const patchedDependencies = opts.ignorePackageManifest
230
+ ? ctx.wantedLockfile.patchedDependencies
231
+ : (opts.patchedDependencies ? await calcPatchHashes(opts.patchedDependencies) : {});
232
+ const patchGroupInput = opts.patchedDependencies
233
+ ? Object.fromEntries(Object.entries(patchedDependencies ?? {}).map(([key, hash]) => {
234
+ const patchFilePath = opts.patchedDependencies[key]
235
+ ? path.resolve(opts.lockfileDir, opts.patchedDependencies[key])
236
+ : undefined;
237
+ return [key, { hash, patchFilePath }];
238
+ }))
239
+ : patchedDependencies;
240
+ const patchGroups = patchGroupInput ? groupPatchedDependencies(patchGroupInput) : undefined;
241
+ const frozenLockfile = opts.frozenLockfile ||
242
+ opts.frozenLockfileIfExists && ctx.existsNonEmptyWantedLockfile;
243
+ let outdatedLockfileSettings = false;
244
+ const overridesMap = createOverridesMapFromParsed(opts.parsedOverrides);
245
+ if (!opts.ignorePackageManifest) {
246
+ const outdatedLockfileSettingName = getOutdatedLockfileSetting(ctx.wantedLockfile, {
247
+ autoInstallPeers: opts.autoInstallPeers,
248
+ catalogs: opts.catalogs,
249
+ injectWorkspacePackages: opts.injectWorkspacePackages,
250
+ excludeLinksFromLockfile: opts.excludeLinksFromLockfile,
251
+ peersSuffixMaxLength: opts.peersSuffixMaxLength,
252
+ overrides: overridesMap,
253
+ ignoredOptionalDependencies: opts.ignoredOptionalDependencies?.sort(),
254
+ packageExtensionsChecksum,
255
+ patchedDependencies,
256
+ pnpmfileChecksum,
257
+ });
258
+ outdatedLockfileSettings = outdatedLockfileSettingName != null;
259
+ if (frozenLockfile && outdatedLockfileSettings) {
260
+ throw new LockfileConfigMismatchError(outdatedLockfileSettingName);
261
+ }
262
+ }
263
+ const _isWantedDepBareSpecifierSame = isWantedDepBareSpecifierSame.bind(null, ctx.wantedLockfile.catalogs, opts.catalogs);
264
+ const upToDateLockfileMajorVersion = ctx.wantedLockfile.lockfileVersion.toString().startsWith(`${LOCKFILE_MAJOR_VERSION}.`);
265
+ let needsFullResolution = outdatedLockfileSettings ||
266
+ opts.fixLockfile ||
267
+ !upToDateLockfileMajorVersion ||
268
+ opts.forceFullResolution ||
269
+ forceResolutionFromHook;
270
+ if (needsFullResolution) {
271
+ ctx.wantedLockfile.settings = {
272
+ autoInstallPeers: opts.autoInstallPeers,
273
+ excludeLinksFromLockfile: opts.excludeLinksFromLockfile,
274
+ peersSuffixMaxLength: opts.peersSuffixMaxLength,
275
+ injectWorkspacePackages: opts.injectWorkspacePackages,
276
+ };
277
+ ctx.wantedLockfile.overrides = overridesMap;
278
+ ctx.wantedLockfile.packageExtensionsChecksum = packageExtensionsChecksum;
279
+ ctx.wantedLockfile.ignoredOptionalDependencies = opts.ignoredOptionalDependencies;
280
+ ctx.wantedLockfile.pnpmfileChecksum = pnpmfileChecksum;
281
+ ctx.wantedLockfile.patchedDependencies = patchedDependencies;
282
+ }
283
+ else if (!frozenLockfile) {
284
+ ctx.wantedLockfile.settings = {
285
+ autoInstallPeers: opts.autoInstallPeers,
286
+ excludeLinksFromLockfile: opts.excludeLinksFromLockfile,
287
+ peersSuffixMaxLength: opts.peersSuffixMaxLength,
288
+ injectWorkspacePackages: opts.injectWorkspacePackages,
289
+ };
290
+ }
291
+ const frozenInstallResult = await tryFrozenInstall({
292
+ frozenLockfile,
293
+ needsFullResolution,
294
+ patchGroups,
295
+ upToDateLockfileMajorVersion,
296
+ });
297
+ if (frozenInstallResult !== null) {
298
+ if ('needsFullResolution' in frozenInstallResult) {
299
+ needsFullResolution = frozenInstallResult.needsFullResolution;
300
+ }
301
+ else {
302
+ return frozenInstallResult;
303
+ }
304
+ }
305
+ const projectsToInstall = [];
306
+ let preferredSpecs = null;
307
+ // TODO: make it concurrent
308
+ /* eslint-disable no-await-in-loop */
309
+ for (const project of projects) {
310
+ const projectOpts = {
311
+ ...project,
312
+ ...ctx.projects[project.rootDir],
313
+ };
314
+ switch (project.mutation) {
315
+ case 'uninstallSome':
316
+ projectsToInstall.push({
317
+ pruneDirectDependencies: false,
318
+ ...projectOpts,
319
+ removePackages: project.dependencyNames,
320
+ updatePackageManifest: true,
321
+ wantedDependencies: [],
322
+ });
323
+ break;
324
+ case 'install': {
325
+ await installCase({
326
+ ...projectOpts,
327
+ updatePackageManifest: projectOpts.updatePackageManifest ?? projectOpts.update,
328
+ });
329
+ break;
330
+ }
331
+ case 'installSome': {
332
+ await installSome({
333
+ ...projectOpts,
334
+ updatePackageManifest: projectOpts.updatePackageManifest !== false,
335
+ });
336
+ break;
337
+ }
338
+ }
339
+ }
340
+ async function installCase(project) {
341
+ const wantedDependencies = getWantedDependencies(project.manifest, {
342
+ autoInstallPeers: opts.autoInstallPeers,
343
+ includeDirect: opts.includeDirect,
344
+ })
345
+ .map((wantedDependency) => ({ ...wantedDependency, updateSpec: true }));
346
+ if (opts.packageVulnerabilityAudit) {
347
+ for (const dep of wantedDependencies) {
348
+ let specifier = dep.bareSpecifier;
349
+ const catalogName = specifier ? parseCatalogProtocol(specifier) : null;
350
+ if (catalogName != null) {
351
+ const catalogResult = resolveFromCatalog(opts.catalogs, { alias: dep.alias, bareSpecifier: specifier });
352
+ specifier = matchCatalogResolveResult(catalogResult, pickCatalogSpecifier);
353
+ }
354
+ const validVersion = semver.valid(specifier);
355
+ // Only proceed if the specifier is a pinned version, not a range
356
+ if (!validVersion)
357
+ continue;
358
+ if (opts.packageVulnerabilityAudit.isVulnerable(dep.alias, validVersion)) {
359
+ // If the current version is pinned and vulnerable, expand the specifier to a range
360
+ // that will allow updating to a non-vulnerable, semver-compatible version, if available.
361
+ if (catalogName != null && opts.catalogs?.[catalogName]) {
362
+ // If a catalog is used, update the catalog entry so the resolver can find a
363
+ // non-vulnerable version. The package.json keeps "catalog:" and the workspace manifest
364
+ // gets updated.
365
+ opts.catalogs = {
366
+ ...opts.catalogs,
367
+ [catalogName]: {
368
+ ...opts.catalogs[catalogName],
369
+ [dep.alias]: '^' + validVersion,
370
+ },
371
+ };
372
+ // Set prevSpecifier to the original catalog specifier so the resolver
373
+ // preserves the original pinning style (i.e. pinned stays pinned).
374
+ dep.prevSpecifier = specifier;
375
+ }
376
+ else {
377
+ // If no catalog is used, we directly update the specifier.
378
+ dep.bareSpecifier = '^' + validVersion;
379
+ }
380
+ }
381
+ }
382
+ }
383
+ if (ctx.wantedLockfile?.importers) {
384
+ forgetResolutionsOfPrevWantedDeps(ctx.wantedLockfile.importers[project.id], wantedDependencies, _isWantedDepBareSpecifierSame);
385
+ }
386
+ if (opts.ignoreScripts && project.manifest?.scripts &&
387
+ (project.manifest.scripts.preinstall != null ||
388
+ project.manifest.scripts.install != null ||
389
+ project.manifest.scripts.postinstall != null ||
390
+ project.manifest.scripts.prepare)) {
391
+ ctx.pendingBuilds.push(project.id);
392
+ }
393
+ projectsToInstall.push({
394
+ pruneDirectDependencies: false,
395
+ ...project,
396
+ wantedDependencies,
397
+ });
398
+ }
399
+ async function installSome(project) {
400
+ const currentBareSpecifiers = opts.ignoreCurrentSpecifiers ? {} : getAllDependenciesFromManifest(project.manifest);
401
+ const optionalDependencies = project.targetDependenciesField ? {} : project.manifest.optionalDependencies ?? {};
402
+ const devDependencies = project.targetDependenciesField ? {} : project.manifest.devDependencies ?? {};
403
+ if (preferredSpecs == null) {
404
+ const manifests = [];
405
+ for (const versions of ctx.workspacePackages.values()) {
406
+ for (const { manifest } of versions.values()) {
407
+ manifests.push(manifest);
408
+ }
409
+ }
410
+ preferredSpecs = getAllUniqueSpecs(manifests);
411
+ }
412
+ const wantedDeps = parseWantedDependencies(project.dependencySelectors, {
413
+ allowNew: project.allowNew !== false,
414
+ currentBareSpecifiers,
415
+ defaultTag: opts.tag,
416
+ dev: project.targetDependenciesField === 'devDependencies',
417
+ devDependencies,
418
+ optional: project.targetDependenciesField === 'optionalDependencies',
419
+ optionalDependencies,
420
+ updateWorkspaceDependencies: project.update,
421
+ preferredSpecs,
422
+ saveCatalogName: opts.saveCatalogName,
423
+ overrides: opts.overrides,
424
+ defaultCatalog: opts.catalogs?.default,
425
+ });
426
+ if (opts.catalogMode !== 'manual') {
427
+ const catalogBareSpecifier = `catalog:${opts.saveCatalogName == null || opts.saveCatalogName === 'default' ? '' : opts.saveCatalogName}`;
428
+ for (const wantedDep of wantedDeps) {
429
+ const catalog = resolveFromCatalog(opts.catalogs, { ...wantedDep, bareSpecifier: catalogBareSpecifier });
430
+ const catalogDepSpecifier = matchCatalogResolveResult(catalog, pickCatalogSpecifier);
431
+ if (!catalogDepSpecifier ||
432
+ wantedDep.bareSpecifier === catalogBareSpecifier ||
433
+ semver.validRange(wantedDep.bareSpecifier) &&
434
+ semver.validRange(catalogDepSpecifier) &&
435
+ semver.eq(wantedDep.bareSpecifier, catalogDepSpecifier)) {
436
+ wantedDep.saveCatalogName = opts.saveCatalogName ?? 'default';
437
+ continue;
438
+ }
439
+ switch (opts.catalogMode) {
440
+ case 'strict':
441
+ throw new CatalogVersionMismatchError({ catalogDep: `${wantedDep.alias}@${catalogDepSpecifier}`, wantedDep: `${wantedDep.alias}@${wantedDep.bareSpecifier}` });
442
+ case 'prefer':
443
+ logger.warn({
444
+ message: `Catalog version mismatch for "${wantedDep.alias}": using direct version "${wantedDep.bareSpecifier}" instead of catalog version "${catalogDepSpecifier}".`,
445
+ prefix: opts.lockfileDir,
446
+ });
447
+ }
448
+ }
449
+ }
450
+ projectsToInstall.push({
451
+ pruneDirectDependencies: false,
452
+ ...project,
453
+ wantedDependencies: wantedDeps.map(wantedDep => ({ ...wantedDep, isNew: !currentBareSpecifiers[wantedDep.alias], updateSpec: true })),
454
+ });
455
+ }
456
+ // Unfortunately, the private lockfile may differ from the public one.
457
+ // A user might run named installations on a project that has a pnpm-lock.yaml file before running a noop install
458
+ const makePartialCurrentLockfile = !installsOnly && (ctx.existsNonEmptyWantedLockfile && !ctx.existsCurrentLockfile ||
459
+ !ctx.currentLockfileIsUpToDate);
460
+ const result = await installInContext(projectsToInstall, ctx, {
461
+ ...opts,
462
+ allowBuild,
463
+ currentLockfileIsUpToDate: !ctx.existsNonEmptyWantedLockfile || ctx.currentLockfileIsUpToDate,
464
+ makePartialCurrentLockfile,
465
+ needsFullResolution,
466
+ pruneVirtualStore,
467
+ scriptsOpts,
468
+ updateLockfileMinorVersion: true,
469
+ patchedDependencies: patchGroups,
470
+ });
471
+ return {
472
+ updatedCatalogs: result.updatedCatalogs,
473
+ updatedProjects: result.projects,
474
+ stats: result.stats,
475
+ depsRequiringBuild: result.depsRequiringBuild,
476
+ ignoredBuilds: result.ignoredBuilds,
477
+ };
478
+ }
479
+ /**
480
+ * Attempt to perform a "frozen install".
481
+ *
482
+ * A "frozen install" will be performed if:
483
+ *
484
+ * 1. The --frozen-lockfile flag was explicitly specified or evaluates to
485
+ * true based on conditions like running on CI.
486
+ * 2. No workspace modifications have been made that would invalidate the
487
+ * pnpm-lock.yaml file. In other words, the pnpm-lock.yaml file is
488
+ * known to be "up-to-date".
489
+ *
490
+ * A frozen install is significantly faster since the pnpm-lock.yaml file
491
+ * can treated as immutable, skipping expensive lookups to acquire new
492
+ * dependencies. For this reason, a frozen install should be performed even
493
+ * if --frozen-lockfile wasn't explicitly specified. This allows users to
494
+ * benefit from the increased performance of a frozen install automatically.
495
+ *
496
+ * If a frozen install is not possible, this function will return null.
497
+ * This indicates a standard mutable install needs to be performed.
498
+ *
499
+ * Note this function may update the pnpm-lock.yaml file if the lockfile was
500
+ * on a different major version, needs to be merged due to git conflicts,
501
+ * etc. These changes update the format of the pnpm-lock.yaml file, but do
502
+ * not change recorded dependency resolutions.
503
+ */
504
+ async function tryFrozenInstall({ frozenLockfile, needsFullResolution, patchGroups, upToDateLockfileMajorVersion, }) {
505
+ const isFrozenInstallPossible =
506
+ // A frozen install is never possible when any of these are true:
507
+ !ctx.lockfileHadConflicts &&
508
+ !opts.fixLockfile &&
509
+ !opts.dedupe &&
510
+ installsOnly &&
511
+ (
512
+ // If the user explicitly requested a frozen lockfile install, attempt
513
+ // to perform one. An error will be thrown if updates are required.
514
+ frozenLockfile ||
515
+ // Otherwise, check if a frozen-like install is possible for
516
+ // performance. This will be the case if all projects are up-to-date.
517
+ opts.ignorePackageManifest ||
518
+ !needsFullResolution &&
519
+ opts.preferFrozenLockfile &&
520
+ (!opts.pruneLockfileImporters || Object.keys(ctx.wantedLockfile.importers).length === Object.keys(ctx.projects).length) &&
521
+ ctx.existsNonEmptyWantedLockfile &&
522
+ ctx.wantedLockfile.lockfileVersion === LOCKFILE_VERSION &&
523
+ await allProjectsAreUpToDate(Object.values(ctx.projects), {
524
+ catalogs: opts.catalogs,
525
+ autoInstallPeers: opts.autoInstallPeers,
526
+ excludeLinksFromLockfile: opts.excludeLinksFromLockfile,
527
+ linkWorkspacePackages: opts.linkWorkspacePackagesDepth >= 0,
528
+ wantedLockfile: ctx.wantedLockfile,
529
+ workspacePackages: ctx.workspacePackages,
530
+ lockfileDir: opts.lockfileDir,
531
+ }));
532
+ if (!isFrozenInstallPossible) {
533
+ return null;
534
+ }
535
+ if (needsFullResolution) {
536
+ throw new PnpmError('FROZEN_LOCKFILE_WITH_OUTDATED_LOCKFILE', 'Cannot perform a frozen installation because the version of the lockfile is incompatible with this version of pnpm', {
537
+ hint: `Try either:
538
+ 1. Aligning the version of pnpm that generated the lockfile with the version that installs from it, or
539
+ 2. Migrating the lockfile so that it is compatible with the newer version of pnpm, or
540
+ 3. Using "pnpm install --no-frozen-lockfile".
541
+ Note that in CI environments, this setting is enabled by default.`,
542
+ });
543
+ }
544
+ if (!opts.ignorePackageManifest) {
545
+ const _satisfiesPackageManifest = satisfiesPackageManifest.bind(null, {
546
+ autoInstallPeers: opts.autoInstallPeers,
547
+ excludeLinksFromLockfile: opts.excludeLinksFromLockfile,
548
+ });
549
+ for (const { id, manifest, rootDir } of Object.values(ctx.projects)) {
550
+ const { satisfies, detailedReason } = _satisfiesPackageManifest(ctx.wantedLockfile.importers[id], manifest);
551
+ if (!satisfies) {
552
+ if (!ctx.existsWantedLockfile) {
553
+ throw new PnpmError('NO_LOCKFILE', `Cannot install with "frozen-lockfile" because ${WANTED_LOCKFILE} is absent`, {
554
+ hint: 'Note that in CI environments this setting is true by default. If you still need to run install in such cases, use "pnpm install --no-frozen-lockfile"',
555
+ });
556
+ }
557
+ throw new PnpmError('OUTDATED_LOCKFILE', `Cannot install with "frozen-lockfile" because ${WANTED_LOCKFILE} is not up to date with ` +
558
+ path.join('<ROOT>', path.relative(opts.lockfileDir, path.join(rootDir, 'package.json'))), {
559
+ hint: `Note that in CI environments this setting is true by default. If you still need to run install in such cases, use "pnpm install --no-frozen-lockfile"
560
+
561
+ Failure reason:
562
+ ${detailedReason ?? ''}`,
563
+ });
564
+ }
565
+ }
566
+ }
567
+ if (opts.lockfileOnly) {
568
+ // The lockfile will only be changed if the workspace will have new projects with no dependencies.
569
+ await writeWantedLockfile(ctx.lockfileDir, ctx.wantedLockfile);
570
+ return {
571
+ updatedProjects: projects.map((mutatedProject) => ctx.projects[mutatedProject.rootDir]),
572
+ ignoredBuilds: undefined,
573
+ };
574
+ }
575
+ if (!ctx.existsNonEmptyWantedLockfile) {
576
+ if (Object.values(ctx.projects).some((project) => pkgHasDependencies(project.manifest))) {
577
+ throw new Error(`Headless installation requires a ${WANTED_LOCKFILE} file`);
578
+ }
579
+ return null;
580
+ }
581
+ if (maybeOpts.ignorePackageManifest) {
582
+ logger.info({ message: 'Importing packages to virtual store', prefix: opts.lockfileDir });
583
+ }
584
+ else {
585
+ logger.info({ message: 'Lockfile is up to date, resolution step is skipped', prefix: opts.lockfileDir });
586
+ }
587
+ try {
588
+ const { stats, ignoredBuilds } = await headlessInstall({
589
+ ...ctx,
590
+ ...opts,
591
+ currentEngine: {
592
+ nodeVersion: opts.nodeVersion,
593
+ pnpmVersion: opts.packageManager.name === 'pnpm' ? opts.packageManager.version : '',
594
+ },
595
+ currentHoistedLocations: ctx.modulesFile?.hoistedLocations,
596
+ patchedDependencies: patchGroups,
597
+ selectedProjectDirs: projects.map((project) => project.rootDir),
598
+ allProjects: ctx.projects,
599
+ prunedAt: ctx.modulesFile?.prunedAt,
600
+ pruneVirtualStore,
601
+ wantedLockfile: maybeOpts.ignorePackageManifest ? undefined : ctx.wantedLockfile,
602
+ useLockfile: opts.useLockfile && ctx.wantedLockfileIsModified,
603
+ });
604
+ if (opts.useLockfile && opts.saveLockfile && opts.mergeGitBranchLockfiles ||
605
+ !upToDateLockfileMajorVersion && !opts.frozenLockfile) {
606
+ const currentLockfileDir = path.join(ctx.rootModulesDir, '.pnpm');
607
+ await writeLockfiles({
608
+ currentLockfile: ctx.currentLockfile,
609
+ currentLockfileDir,
610
+ wantedLockfile: ctx.wantedLockfile,
611
+ wantedLockfileDir: ctx.lockfileDir,
612
+ useGitBranchLockfile: opts.useGitBranchLockfile,
613
+ mergeGitBranchLockfiles: opts.mergeGitBranchLockfiles,
614
+ });
615
+ }
616
+ return {
617
+ updatedProjects: projects.map((mutatedProject) => {
618
+ const project = ctx.projects[mutatedProject.rootDir];
619
+ return {
620
+ ...project,
621
+ manifest: project.originalManifest ?? project.manifest,
622
+ };
623
+ }),
624
+ stats,
625
+ ignoredBuilds,
626
+ };
627
+ }
628
+ catch (error) { // eslint-disable-line
629
+ if (frozenLockfile ||
630
+ (error.code !== 'ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY' &&
631
+ !BROKEN_LOCKFILE_INTEGRITY_ERRORS.has(error.code)) ||
632
+ (!ctx.existsNonEmptyWantedLockfile && !ctx.existsCurrentLockfile))
633
+ throw error;
634
+ if (BROKEN_LOCKFILE_INTEGRITY_ERRORS.has(error.code)) {
635
+ needsFullResolution = true;
636
+ // Ideally, we would not update but currently there is no other way to redownload the integrity of the package
637
+ for (const project of projects) {
638
+ project.update = true;
639
+ }
640
+ }
641
+ // A broken lockfile may be caused by a badly resolved Git conflict
642
+ logger.warn({
643
+ error,
644
+ message: error.message,
645
+ prefix: ctx.lockfileDir,
646
+ });
647
+ logger.error(new PnpmError(error.code, 'The lockfile is broken! Resolution step will be performed to fix it.'));
648
+ return { needsFullResolution };
649
+ }
650
+ }
651
+ }
652
+ async function runUnignoredDependencyBuilds(opts, previousIgnoredBuilds, allowBuild) {
653
+ if (!allowBuild) {
654
+ return previousIgnoredBuilds;
655
+ }
656
+ const pkgsToBuild = [];
657
+ for (const ignoredPkg of previousIgnoredBuilds) {
658
+ const parsed = dp.parse(ignoredPkg);
659
+ if (!parsed.name || !parsed.version)
660
+ continue;
661
+ const allowed = allowBuild(parsed.name, parsed.version);
662
+ if (allowed === true) {
663
+ // Package is explicitly allowed - rebuild it
664
+ pkgsToBuild.push(`${parsed.name}@${parsed.version}`);
665
+ }
666
+ }
667
+ if (pkgsToBuild.length) {
668
+ return (await buildSelectedPkgs(opts.allProjects, pkgsToBuild, {
669
+ ...opts,
670
+ reporter: undefined, // We don't want to attach the reporter again, it was already attached.
671
+ rootProjectManifestDir: opts.lockfileDir,
672
+ })).ignoredBuilds ?? previousIgnoredBuilds;
673
+ }
674
+ return previousIgnoredBuilds;
675
+ }
676
+ function cacheExpired(prunedAt, maxAgeInMinutes) {
677
+ return ((Date.now() - new Date(prunedAt).valueOf()) / (1000 * 60)) > maxAgeInMinutes;
678
+ }
679
+ function pkgHasDependencies(manifest) {
680
+ return Boolean((Object.keys(manifest.dependencies ?? {}).length > 0) ||
681
+ Object.keys(manifest.devDependencies ?? {}).length ||
682
+ Object.keys(manifest.optionalDependencies ?? {}).length);
683
+ }
684
+ // If the specifier is new, the old resolution probably does not satisfy it anymore.
685
+ // By removing these resolutions we ensure that they are resolved again using the new specs.
686
+ function forgetResolutionsOfPrevWantedDeps(importer, wantedDeps, isWantedDepBareSpecifierSame) {
687
+ if (!importer.specifiers)
688
+ return;
689
+ importer.dependencies = importer.dependencies ?? {};
690
+ importer.devDependencies = importer.devDependencies ?? {};
691
+ importer.optionalDependencies = importer.optionalDependencies ?? {};
692
+ for (const { alias, bareSpecifier } of wantedDeps) {
693
+ if (alias && !isWantedDepBareSpecifierSame(alias, importer.specifiers[alias], bareSpecifier)) {
694
+ if (!importer.dependencies[alias]?.startsWith('link:')) {
695
+ delete importer.dependencies[alias];
696
+ }
697
+ delete importer.devDependencies[alias];
698
+ delete importer.optionalDependencies[alias];
699
+ }
700
+ }
701
+ }
702
+ function forgetResolutionsOfAllPrevWantedDeps(wantedLockfile) {
703
+ // Similar to the forgetResolutionsOfPrevWantedDeps function above, we can
704
+ // delete existing resolutions in importers to make sure they're resolved
705
+ // again.
706
+ if ((wantedLockfile.importers != null) && !isEmpty(wantedLockfile.importers)) {
707
+ wantedLockfile.importers = mapValues(({ dependencies: _dependencies, devDependencies: _devDependencies, optionalDependencies: _optionalDependencies, ...rest }) => rest, wantedLockfile.importers);
708
+ }
709
+ // The resolveDependencies function looks at previous PackageSnapshot
710
+ // dependencies/optionalDependencies blocks and merges them with new resolved
711
+ // deps. Clear the previous PackageSnapshot fields so the newly resolved deps
712
+ // are always used.
713
+ if ((wantedLockfile.packages != null) && !isEmpty(wantedLockfile.packages)) {
714
+ wantedLockfile.packages = mapValues(({ dependencies: _dependencies, optionalDependencies: _optionalDependencies, ...rest }) => rest, wantedLockfile.packages);
715
+ }
716
+ // Also clear the resolutions in catalogs so they're re-resolved and deduped.
717
+ if ((wantedLockfile.catalogs != null) && !isEmpty(wantedLockfile.catalogs)) {
718
+ wantedLockfile.catalogs = undefined;
719
+ }
720
+ }
721
+ /**
722
+ * Check if a wanted bareSpecifier is the same.
723
+ *
724
+ * It would be different if the user modified a dependency in package.json or a
725
+ * catalog entry in pnpm-workspace.yaml. This is normally a simple check to see
726
+ * if the specifier strings match, but catalogs make this more involved since we
727
+ * also have to check if the catalog config in pnpm-workspace.yaml is the same.
728
+ */
729
+ function isWantedDepBareSpecifierSame(prevCatalogs, catalogsConfig, alias, prevBareSpecifier, nextBareSpecifier) {
730
+ if (prevBareSpecifier !== nextBareSpecifier) {
731
+ return false;
732
+ }
733
+ // When pnpm catalogs are used, the specifiers can be the same (e.g.
734
+ // "catalog:default"), but the wanted versions for the dependency can be
735
+ // different after resolution if the catalog config was just edited.
736
+ const catalogName = parseCatalogProtocol(prevBareSpecifier);
737
+ // If there's no catalog name, the catalog protocol was not used and we
738
+ // can assume the bareSpecifier is the same since prevBareSpecifier and nextBareSpecifier match.
739
+ if (catalogName === null) {
740
+ return true;
741
+ }
742
+ const prevCatalogEntrySpec = prevCatalogs?.[catalogName]?.[alias]?.specifier;
743
+ const nextCatalogEntrySpec = catalogsConfig?.[catalogName]?.[alias];
744
+ return prevCatalogEntrySpec === nextCatalogEntrySpec;
745
+ }
746
+ export async function addDependenciesToPackage(manifest, dependencySelectors, opts) {
747
+ const rootDir = (opts.dir ?? process.cwd());
748
+ const { updatedCatalogs, updatedProjects: projects, ignoredBuilds } = await mutateModules([
749
+ {
750
+ allowNew: opts.allowNew,
751
+ dependencySelectors,
752
+ mutation: 'installSome',
753
+ peer: opts.peer,
754
+ pinnedVersion: opts.pinnedVersion,
755
+ rootDir,
756
+ targetDependenciesField: opts.targetDependenciesField,
757
+ update: opts.update,
758
+ updateMatching: opts.updateMatching,
759
+ updatePackageManifest: opts.updatePackageManifest,
760
+ updateToLatest: opts.updateToLatest,
761
+ },
762
+ ], {
763
+ ...opts,
764
+ lockfileDir: opts.lockfileDir ?? opts.dir,
765
+ allProjects: [
766
+ {
767
+ buildIndex: 0,
768
+ binsDir: opts.bin,
769
+ manifest,
770
+ rootDir,
771
+ },
772
+ ],
773
+ });
774
+ return { updatedCatalogs, updatedManifest: projects[0].manifest, ignoredBuilds };
775
+ }
776
+ const _installInContext = async (projects, ctx, opts) => {
777
+ // The wanted lockfile is mutated during installation. To compare changes, a
778
+ // deep copy before installation is needed. This copy should represent the
779
+ // original wanted lockfile on disk as close as possible.
780
+ //
781
+ // This object can be quite large. Intentionally avoiding an expensive copy
782
+ // if no lockfileCheck option was passed in.
783
+ const originalLockfileForCheck = opts.lockfileCheck != null
784
+ ? clone(ctx.wantedLockfile)
785
+ : null;
786
+ // Aliasing for clarity in boolean expressions below.
787
+ const isInstallationOnlyForLockfileCheck = opts.lockfileCheck != null;
788
+ ctx.wantedLockfile.importers = ctx.wantedLockfile.importers || {};
789
+ for (const { id } of projects) {
790
+ if (!ctx.wantedLockfile.importers[id]) {
791
+ ctx.wantedLockfile.importers[id] = { specifiers: {} };
792
+ }
793
+ }
794
+ if (opts.pruneLockfileImporters) {
795
+ const projectIds = new Set(projects.map(({ id }) => id));
796
+ for (const wantedImporter of Object.keys(ctx.wantedLockfile.importers)) {
797
+ if (!projectIds.has(wantedImporter)) {
798
+ delete ctx.wantedLockfile.importers[wantedImporter];
799
+ }
800
+ }
801
+ }
802
+ await Promise.all(projects
803
+ .map(async (project) => {
804
+ if (project.mutation !== 'uninstallSome')
805
+ return;
806
+ const _removeDeps = async (manifest) => removeDeps(manifest, project.dependencyNames, { prefix: project.rootDir, saveType: project.targetDependenciesField });
807
+ project.manifest = await _removeDeps(project.manifest);
808
+ if (project.originalManifest != null) {
809
+ project.originalManifest = await _removeDeps(project.originalManifest);
810
+ }
811
+ }));
812
+ stageLogger.debug({
813
+ prefix: ctx.lockfileDir,
814
+ stage: 'resolution_started',
815
+ });
816
+ const update = projects.some((project) => project.update);
817
+ const preferredVersions = opts.preferredVersions ?? (!update
818
+ ? getPreferredVersionsFromLockfileAndManifests(ctx.wantedLockfile.packages, Object.values(ctx.projects).map(({ manifest }) => manifest))
819
+ : undefined);
820
+ const forceFullResolution = ctx.wantedLockfile.lockfileVersion !== LOCKFILE_VERSION ||
821
+ !opts.currentLockfileIsUpToDate ||
822
+ opts.force ||
823
+ opts.needsFullResolution ||
824
+ ctx.lockfileHadConflicts ||
825
+ opts.dedupePeerDependents;
826
+ // Ignore some fields when fixing lockfile, so these fields can be regenerated
827
+ // and make sure it's up to date
828
+ if (opts.fixLockfile &&
829
+ (ctx.wantedLockfile.packages != null) &&
830
+ !isEmpty(ctx.wantedLockfile.packages)) {
831
+ ctx.wantedLockfile.packages = mapValues(({ dependencies, optionalDependencies, resolution }) => ({
832
+ // These fields are needed to avoid losing information of the locked dependencies if these fields are not broken
833
+ // If these fields are broken, they will also be regenerated
834
+ dependencies,
835
+ optionalDependencies,
836
+ resolution,
837
+ }), ctx.wantedLockfile.packages);
838
+ }
839
+ if (opts.dedupe) {
840
+ // Deleting recorded version resolutions from importers and packages. These
841
+ // fields will be regenerated using the preferred versions computed above.
842
+ //
843
+ // This is a bit different from a "full resolution", which completely
844
+ // ignores preferred versions from the lockfile.
845
+ forgetResolutionsOfAllPrevWantedDeps(ctx.wantedLockfile);
846
+ }
847
+ let { dependenciesGraph, dependenciesByProjectId, linkedDependenciesByProjectId, updatedCatalogs, newLockfile, outdatedDependencies, peerDependencyIssuesByProjects, wantedToBeSkippedPackageIds, waitTillAllFetchingsFinish, } = await resolveDependencies(projects, {
848
+ allowBuild: opts.allowBuild,
849
+ allowedDeprecatedVersions: opts.allowedDeprecatedVersions,
850
+ allowUnusedPatches: opts.allowUnusedPatches,
851
+ autoInstallPeers: opts.autoInstallPeers,
852
+ autoInstallPeersFromHighestMatch: opts.autoInstallPeersFromHighestMatch,
853
+ catalogs: opts.catalogs,
854
+ currentLockfile: ctx.currentLockfile,
855
+ defaultUpdateDepth: opts.depth,
856
+ dedupeDirectDeps: opts.dedupeDirectDeps,
857
+ dedupeInjectedDeps: opts.dedupeInjectedDeps,
858
+ dedupePeerDependents: opts.dedupePeerDependents,
859
+ dryRun: opts.lockfileOnly,
860
+ enableGlobalVirtualStore: opts.enableGlobalVirtualStore,
861
+ engineStrict: opts.engineStrict,
862
+ excludeLinksFromLockfile: opts.excludeLinksFromLockfile,
863
+ force: opts.force,
864
+ forceFullResolution,
865
+ ignoreScripts: opts.ignoreScripts,
866
+ hooks: {
867
+ readPackage: opts.readPackageHook,
868
+ },
869
+ linkWorkspacePackagesDepth: opts.linkWorkspacePackagesDepth ?? (opts.saveWorkspaceProtocol ? 0 : -1),
870
+ lockfileDir: opts.lockfileDir,
871
+ nodeVersion: opts.nodeVersion,
872
+ pnpmVersion: opts.packageManager.name === 'pnpm' ? opts.packageManager.version : '',
873
+ preferWorkspacePackages: opts.preferWorkspacePackages,
874
+ preferredVersions,
875
+ preserveWorkspaceProtocol: opts.preserveWorkspaceProtocol,
876
+ registries: ctx.registries,
877
+ resolutionMode: opts.resolutionMode,
878
+ saveWorkspaceProtocol: opts.saveWorkspaceProtocol,
879
+ storeController: opts.storeController,
880
+ tag: opts.tag,
881
+ globalVirtualStoreDir: opts.globalVirtualStoreDir,
882
+ virtualStoreDir: ctx.virtualStoreDir,
883
+ virtualStoreDirMaxLength: ctx.virtualStoreDirMaxLength,
884
+ wantedLockfile: ctx.wantedLockfile,
885
+ workspacePackages: ctx.workspacePackages,
886
+ patchedDependencies: opts.patchedDependencies,
887
+ lockfileIncludeTarballUrl: opts.lockfileIncludeTarballUrl,
888
+ resolvePeersFromWorkspaceRoot: opts.resolvePeersFromWorkspaceRoot,
889
+ supportedArchitectures: opts.supportedArchitectures,
890
+ peersSuffixMaxLength: opts.peersSuffixMaxLength,
891
+ injectWorkspacePackages: opts.injectWorkspacePackages,
892
+ minimumReleaseAge: opts.minimumReleaseAge,
893
+ minimumReleaseAgeExclude: opts.minimumReleaseAgeExclude,
894
+ trustPolicy: opts.trustPolicy,
895
+ trustPolicyExclude: opts.trustPolicyExclude,
896
+ trustPolicyIgnoreAfter: opts.trustPolicyIgnoreAfter,
897
+ blockExoticSubdeps: opts.blockExoticSubdeps,
898
+ allProjectIds: Object.values(ctx.projects).map((p) => p.id),
899
+ });
900
+ if (!opts.include.optionalDependencies || !opts.include.devDependencies || !opts.include.dependencies) {
901
+ linkedDependenciesByProjectId = mapValues((linkedDeps) => linkedDeps.filter((linkedDep) => !(linkedDep.dev && !opts.include.devDependencies ||
902
+ linkedDep.optional && !opts.include.optionalDependencies ||
903
+ !linkedDep.dev && !linkedDep.optional && !opts.include.dependencies)), linkedDependenciesByProjectId ?? {});
904
+ for (const { id, manifest } of projects) {
905
+ for (const [alias, depPath] of dependenciesByProjectId[id].entries()) {
906
+ let include;
907
+ const dep = dependenciesGraph[depPath];
908
+ if (!dep) {
909
+ include = false;
910
+ }
911
+ else {
912
+ const isDev = Boolean(manifest.devDependencies?.[dep.name]);
913
+ const isOptional = Boolean(manifest.optionalDependencies?.[dep.name]);
914
+ include = !(isDev && !opts.include.devDependencies ||
915
+ isOptional && !opts.include.optionalDependencies ||
916
+ !isDev && !isOptional && !opts.include.dependencies);
917
+ }
918
+ if (!include) {
919
+ dependenciesByProjectId[id].delete(alias);
920
+ }
921
+ }
922
+ }
923
+ }
924
+ stageLogger.debug({
925
+ prefix: ctx.lockfileDir,
926
+ stage: 'resolution_done',
927
+ });
928
+ newLockfile = ((opts.hooks?.afterAllResolved) != null)
929
+ ? await pipeWith(async (f, res) => f(await res), opts.hooks.afterAllResolved)(newLockfile) // eslint-disable-line
930
+ : newLockfile;
931
+ if (opts.updateLockfileMinorVersion) {
932
+ newLockfile.lockfileVersion = LOCKFILE_VERSION;
933
+ }
934
+ const depsStateCache = {};
935
+ const lockfileOpts = {
936
+ useGitBranchLockfile: opts.useGitBranchLockfile,
937
+ mergeGitBranchLockfiles: opts.mergeGitBranchLockfiles,
938
+ };
939
+ let stats;
940
+ let ignoredBuilds;
941
+ if (!opts.lockfileOnly && !isInstallationOnlyForLockfileCheck && opts.enableModulesDir) {
942
+ const result = await linkPackages(projects, dependenciesGraph, {
943
+ allowBuild: opts.allowBuild,
944
+ currentLockfile: ctx.currentLockfile,
945
+ dedupeDirectDeps: opts.dedupeDirectDeps,
946
+ dependenciesByProjectId,
947
+ depsStateCache,
948
+ disableRelinkLocalDirDeps: opts.disableRelinkLocalDirDeps,
949
+ enableGlobalVirtualStore: opts.enableGlobalVirtualStore,
950
+ extraNodePaths: ctx.extraNodePaths,
951
+ force: opts.force,
952
+ hoistedDependencies: ctx.hoistedDependencies,
953
+ hoistedModulesDir: ctx.hoistedModulesDir,
954
+ hoistPattern: ctx.hoistPattern,
955
+ ignoreScripts: opts.ignoreScripts,
956
+ include: opts.include,
957
+ linkedDependenciesByProjectId,
958
+ lockfileDir: opts.lockfileDir,
959
+ makePartialCurrentLockfile: opts.makePartialCurrentLockfile,
960
+ outdatedDependencies,
961
+ pruneStore: opts.pruneStore,
962
+ pruneVirtualStore: opts.pruneVirtualStore,
963
+ publicHoistPattern: ctx.publicHoistPattern,
964
+ registries: ctx.registries,
965
+ rootModulesDir: ctx.rootModulesDir,
966
+ sideEffectsCacheRead: opts.sideEffectsCacheRead,
967
+ symlink: opts.symlink,
968
+ skipped: ctx.skipped,
969
+ storeController: opts.storeController,
970
+ virtualStoreDir: ctx.virtualStoreDir,
971
+ virtualStoreDirMaxLength: ctx.virtualStoreDirMaxLength,
972
+ wantedLockfile: newLockfile,
973
+ wantedToBeSkippedPackageIds,
974
+ hoistWorkspacePackages: opts.hoistWorkspacePackages,
975
+ virtualStoreOnly: opts.virtualStoreOnly,
976
+ });
977
+ stats = result.stats;
978
+ if (opts.enablePnp) {
979
+ const importerNames = Object.fromEntries(projects.map(({ manifest, id }) => [id, manifest.name ?? id]));
980
+ await writePnpFile(result.currentLockfile, {
981
+ importerNames,
982
+ lockfileDir: ctx.lockfileDir,
983
+ virtualStoreDir: ctx.virtualStoreDir,
984
+ virtualStoreDirMaxLength: ctx.virtualStoreDirMaxLength,
985
+ registries: ctx.registries,
986
+ });
987
+ }
988
+ ctx.pendingBuilds = ctx.pendingBuilds
989
+ .filter((relDepPath) => !result.removedDepPaths.has(relDepPath));
990
+ if (result.newDepPaths?.length) {
991
+ if (opts.ignoreScripts) {
992
+ // we can use concat here because we always only append new packages, which are guaranteed to not be there by definition
993
+ ctx.pendingBuilds = ctx.pendingBuilds
994
+ .concat(result.newDepPaths.filter((depPath) => dependenciesGraph[depPath].requiresBuild));
995
+ }
996
+ if (!opts.ignoreScripts || Object.keys(opts.patchedDependencies ?? {}).length > 0) {
997
+ // postinstall hooks
998
+ const depPaths = Object.keys(dependenciesGraph);
999
+ const rootNodes = depPaths.filter((depPath) => dependenciesGraph[depPath].depth === 0);
1000
+ let extraEnv = opts.scriptsOpts.extraEnv;
1001
+ if (opts.enablePnp) {
1002
+ extraEnv = {
1003
+ ...extraEnv,
1004
+ ...makeNodeRequireOption(path.join(opts.lockfileDir, '.pnp.cjs')),
1005
+ };
1006
+ }
1007
+ ignoredBuilds = (await buildModules(dependenciesGraph, rootNodes, {
1008
+ allowBuild: opts.allowBuild,
1009
+ childConcurrency: opts.childConcurrency,
1010
+ depsStateCache,
1011
+ depsToBuild: new Set(result.newDepPaths),
1012
+ extraBinPaths: ctx.extraBinPaths,
1013
+ extraNodePaths: ctx.extraNodePaths,
1014
+ extraEnv,
1015
+ ignoreScripts: opts.ignoreScripts || opts.ignoreDepScripts,
1016
+ lockfileDir: ctx.lockfileDir,
1017
+ optional: opts.include.optionalDependencies,
1018
+ preferSymlinkedExecutables: opts.preferSymlinkedExecutables,
1019
+ rawConfig: opts.rawConfig,
1020
+ rootModulesDir: ctx.virtualStoreDir,
1021
+ scriptsPrependNodePath: opts.scriptsPrependNodePath,
1022
+ scriptShell: opts.scriptShell,
1023
+ shellEmulator: opts.shellEmulator,
1024
+ sideEffectsCacheWrite: opts.sideEffectsCacheWrite,
1025
+ storeController: opts.storeController,
1026
+ unsafePerm: opts.unsafePerm,
1027
+ userAgent: opts.userAgent,
1028
+ enableGlobalVirtualStore: opts.enableGlobalVirtualStore,
1029
+ })).ignoredBuilds;
1030
+ if (ctx.modulesFile?.ignoredBuilds?.size) {
1031
+ ignoredBuilds ??= new Set();
1032
+ for (const ignoredBuild of ctx.modulesFile.ignoredBuilds.values()) {
1033
+ if (result.currentLockfile.packages?.[ignoredBuild]) {
1034
+ ignoredBuilds.add(ignoredBuild);
1035
+ }
1036
+ }
1037
+ }
1038
+ }
1039
+ }
1040
+ const binWarn = (prefix, message) => {
1041
+ logger.info({ message, prefix });
1042
+ };
1043
+ if (result.newDepPaths?.length && !opts.virtualStoreOnly) {
1044
+ const newPkgs = props(result.newDepPaths, dependenciesGraph);
1045
+ await linkAllBins(newPkgs, dependenciesGraph, {
1046
+ extraNodePaths: ctx.extraNodePaths,
1047
+ optional: opts.include.optionalDependencies,
1048
+ warn: binWarn.bind(null, opts.lockfileDir),
1049
+ });
1050
+ }
1051
+ if (!opts.virtualStoreOnly)
1052
+ await Promise.all(projects.map(async (project, index) => {
1053
+ let linkedPackages;
1054
+ if (ctx.publicHoistPattern?.length && path.relative(project.rootDir, opts.lockfileDir) === '') {
1055
+ linkedPackages = await linkBins(project.modulesDir, project.binsDir, {
1056
+ allowExoticManifests: true,
1057
+ preferSymlinkedExecutables: opts.preferSymlinkedExecutables,
1058
+ projectManifest: project.manifest,
1059
+ extraNodePaths: ctx.extraNodePaths,
1060
+ warn: binWarn.bind(null, project.rootDir),
1061
+ });
1062
+ }
1063
+ else {
1064
+ const directPkgs = [
1065
+ ...props(Array.from(dependenciesByProjectId[project.id].values()).filter((depPath) => !ctx.skipped.has(depPath)), dependenciesGraph),
1066
+ ...linkedDependenciesByProjectId[project.id].map(({ pkgId }) => ({
1067
+ dir: path.join(project.rootDir, pkgId.substring(5)),
1068
+ fetching: undefined,
1069
+ })),
1070
+ ];
1071
+ linkedPackages = await linkBinsOfPackages((await Promise.all(directPkgs.map(async (dep) => {
1072
+ const manifest = (await dep.fetching?.())?.bundledManifest ?? await safeReadProjectManifestOnly(dep.dir);
1073
+ return {
1074
+ location: dep.dir,
1075
+ manifest,
1076
+ };
1077
+ })))
1078
+ .filter(({ manifest }) => manifest != null), project.binsDir, {
1079
+ extraNodePaths: ctx.extraNodePaths,
1080
+ preferSymlinkedExecutables: opts.preferSymlinkedExecutables,
1081
+ });
1082
+ }
1083
+ const projectToInstall = projects[index];
1084
+ if (opts.global && projectToInstall.mutation.includes('install')) {
1085
+ for (const pkg of projectToInstall.wantedDependencies) {
1086
+ // This warning is never printed currently during "pnpm link --global"
1087
+ // due to the following issue: https://github.com/pnpm/pnpm/issues/4761
1088
+ if (pkg.alias && !linkedPackages?.includes(pkg.alias)) {
1089
+ logger.warn({ message: `${pkg.alias} has no binaries`, prefix: opts.lockfileDir });
1090
+ }
1091
+ }
1092
+ }
1093
+ }));
1094
+ const projectsWithTargetDirs = getProjectsWithTargetDirs(projects, newLockfile, dependenciesGraph);
1095
+ const currentLockfileDir = path.join(ctx.rootModulesDir, '.pnpm');
1096
+ await Promise.all([
1097
+ opts.useLockfile && opts.saveLockfile
1098
+ ? writeLockfiles({
1099
+ currentLockfile: result.currentLockfile,
1100
+ currentLockfileDir,
1101
+ wantedLockfile: newLockfile,
1102
+ wantedLockfileDir: ctx.lockfileDir,
1103
+ ...lockfileOpts,
1104
+ })
1105
+ : writeCurrentLockfile(ctx.virtualStoreDir, result.currentLockfile),
1106
+ (async () => {
1107
+ if (result.currentLockfile.packages === undefined && result.removedDepPaths.size === 0) {
1108
+ return Promise.resolve();
1109
+ }
1110
+ const injectedDeps = {};
1111
+ for (const project of projectsWithTargetDirs) {
1112
+ if (project.targetDirs.length > 0) {
1113
+ injectedDeps[project.id] = project.targetDirs.map((targetDir) => path.relative(opts.lockfileDir, targetDir));
1114
+ }
1115
+ }
1116
+ return writeModulesManifest(ctx.rootModulesDir, {
1117
+ ...ctx.modulesFile,
1118
+ hoistedDependencies: result.newHoistedDependencies,
1119
+ hoistPattern: ctx.hoistPattern,
1120
+ included: ctx.include,
1121
+ injectedDeps,
1122
+ ignoredBuilds,
1123
+ layoutVersion: LAYOUT_VERSION,
1124
+ nodeLinker: opts.nodeLinker,
1125
+ packageManager: `${opts.packageManager.name}@${opts.packageManager.version}`,
1126
+ pendingBuilds: ctx.pendingBuilds,
1127
+ publicHoistPattern: ctx.publicHoistPattern,
1128
+ prunedAt: opts.pruneVirtualStore || ctx.modulesFile == null
1129
+ ? new Date().toUTCString()
1130
+ : ctx.modulesFile.prunedAt,
1131
+ registries: ctx.registries,
1132
+ skipped: Array.from(ctx.skipped),
1133
+ storeDir: ctx.storeDir,
1134
+ virtualStoreDir: ctx.virtualStoreDir,
1135
+ virtualStoreDirMaxLength: ctx.virtualStoreDirMaxLength,
1136
+ allowBuilds: opts.allowBuilds,
1137
+ });
1138
+ })(),
1139
+ ]);
1140
+ if (!opts.ignoreScripts && !opts.virtualStoreOnly) {
1141
+ if (opts.enablePnp) {
1142
+ opts.scriptsOpts.extraEnv = {
1143
+ ...opts.scriptsOpts.extraEnv,
1144
+ ...makeNodeRequireOption(path.join(opts.lockfileDir, '.pnp.cjs')),
1145
+ };
1146
+ }
1147
+ const projectsToBeBuilt = projectsWithTargetDirs.filter(({ mutation }) => mutation === 'install');
1148
+ await runLifecycleHooksConcurrently(['preinstall', 'install', 'postinstall', 'preprepare', 'prepare', 'postprepare'], projectsToBeBuilt, opts.childConcurrency, opts.scriptsOpts);
1149
+ }
1150
+ }
1151
+ else {
1152
+ if (opts.useLockfile && opts.saveLockfile && !isInstallationOnlyForLockfileCheck) {
1153
+ await writeWantedLockfile(ctx.lockfileDir, newLockfile, lockfileOpts);
1154
+ }
1155
+ if (opts.nodeLinker !== 'hoisted') {
1156
+ // This is only needed because otherwise the reporter will hang
1157
+ stageLogger.debug({
1158
+ prefix: opts.lockfileDir,
1159
+ stage: 'importing_done',
1160
+ });
1161
+ }
1162
+ }
1163
+ await waitTillAllFetchingsFinish();
1164
+ const depsRequiringBuild = [];
1165
+ if (opts.returnListOfDepsRequiringBuild) {
1166
+ await Promise.all(Object.entries(dependenciesGraph).map(async ([depPath, node]) => {
1167
+ if (node?.fetching == null)
1168
+ return; // We cannot detect if a skipped optional dependency requires build
1169
+ const { files } = await node.fetching();
1170
+ if (files.requiresBuild) {
1171
+ depsRequiringBuild.push(depPath);
1172
+ }
1173
+ }));
1174
+ }
1175
+ reportPeerDependencyIssues(peerDependencyIssuesByProjects, {
1176
+ lockfileDir: opts.lockfileDir,
1177
+ strictPeerDependencies: opts.strictPeerDependencies,
1178
+ rules: opts.peerDependencyRules,
1179
+ });
1180
+ summaryLogger.debug({ prefix: opts.lockfileDir });
1181
+ // Similar to the sequencing for when the original wanted lockfile is
1182
+ // copied, the new lockfile passed here should be as close as possible to
1183
+ // what will eventually be written to disk. Ex: peers should be resolved,
1184
+ // the afterAllResolved hook has been applied, etc.
1185
+ if (originalLockfileForCheck != null) {
1186
+ opts.lockfileCheck?.(originalLockfileForCheck, newLockfile);
1187
+ }
1188
+ return {
1189
+ updatedCatalogs,
1190
+ newLockfile,
1191
+ projects: projects.map(({ id, manifest, rootDir }) => ({
1192
+ manifest,
1193
+ peerDependencyIssues: peerDependencyIssuesByProjects[id],
1194
+ rootDir,
1195
+ })),
1196
+ stats,
1197
+ depsRequiringBuild,
1198
+ ignoredBuilds,
1199
+ };
1200
+ };
1201
+ function allMutationsAreInstalls(projects) {
1202
+ return projects.every((project) => project.mutation === 'install' && !project.update && !project.updateMatching);
1203
+ }
1204
+ const installInContext = async (projects, ctx, opts) => {
1205
+ try {
1206
+ const isPathInsideWorkspace = isSubdir.bind(null, opts.lockfileDir);
1207
+ if (!opts.frozenLockfile && opts.useLockfile) {
1208
+ const allProjectsLocatedInsideWorkspace = Object.values(ctx.projects)
1209
+ .filter((project) => isPathInsideWorkspace(project.rootDirRealPath ?? project.rootDir));
1210
+ if (allProjectsLocatedInsideWorkspace.length > projects.length) {
1211
+ const newProjects = [...projects];
1212
+ const getWantedDepsOpts = {
1213
+ autoInstallPeers: opts.autoInstallPeers,
1214
+ includeDirect: opts.includeDirect,
1215
+ updateWorkspaceDependencies: false,
1216
+ injectWorkspacePackages: opts.injectWorkspacePackages,
1217
+ };
1218
+ const _isWantedDepBareSpecifierSame = isWantedDepBareSpecifierSame.bind(null, ctx.wantedLockfile.catalogs, opts.catalogs);
1219
+ for (const project of allProjectsLocatedInsideWorkspace) {
1220
+ if (!newProjects.some(({ rootDir }) => rootDir === project.rootDir)) {
1221
+ // This code block mirrors the installCase() function in
1222
+ // mutateModules(). Consider a refactor that combines this logic to
1223
+ // deduplicate code.
1224
+ const wantedDependencies = getWantedDependencies(project.manifest, getWantedDepsOpts)
1225
+ .map((wantedDependency) => ({ ...wantedDependency, updateSpec: true, preserveNonSemverVersionSpec: true }));
1226
+ forgetResolutionsOfPrevWantedDeps(ctx.wantedLockfile.importers[project.id], wantedDependencies, _isWantedDepBareSpecifierSame);
1227
+ newProjects.push({
1228
+ mutation: 'install',
1229
+ ...project,
1230
+ wantedDependencies,
1231
+ pruneDirectDependencies: false,
1232
+ updatePackageManifest: false,
1233
+ });
1234
+ }
1235
+ }
1236
+ const result = await installInContext(newProjects, ctx, {
1237
+ ...opts,
1238
+ lockfileOnly: true,
1239
+ });
1240
+ const { stats, ignoredBuilds } = await headlessInstall({
1241
+ ...ctx,
1242
+ ...opts,
1243
+ currentEngine: {
1244
+ nodeVersion: opts.nodeVersion,
1245
+ pnpmVersion: opts.packageManager.name === 'pnpm' ? opts.packageManager.version : '',
1246
+ },
1247
+ currentHoistedLocations: ctx.modulesFile?.hoistedLocations,
1248
+ selectedProjectDirs: projects.map((project) => project.rootDir),
1249
+ allProjects: ctx.projects,
1250
+ prunedAt: ctx.modulesFile?.prunedAt,
1251
+ wantedLockfile: result.newLockfile,
1252
+ useLockfile: opts.useLockfile && ctx.wantedLockfileIsModified,
1253
+ hoistWorkspacePackages: opts.hoistWorkspacePackages,
1254
+ });
1255
+ return {
1256
+ ...result,
1257
+ stats,
1258
+ ignoredBuilds,
1259
+ };
1260
+ }
1261
+ }
1262
+ if (opts.nodeLinker === 'hoisted' && !opts.lockfileOnly) {
1263
+ const result = await _installInContext(projects, ctx, {
1264
+ ...opts,
1265
+ lockfileOnly: true,
1266
+ });
1267
+ const { stats, ignoredBuilds } = await headlessInstall({
1268
+ ...ctx,
1269
+ ...opts,
1270
+ currentEngine: {
1271
+ nodeVersion: opts.nodeVersion,
1272
+ pnpmVersion: opts.packageManager.name === 'pnpm' ? opts.packageManager.version : '',
1273
+ },
1274
+ currentHoistedLocations: ctx.modulesFile?.hoistedLocations,
1275
+ selectedProjectDirs: projects.map((project) => project.rootDir),
1276
+ allProjects: ctx.projects,
1277
+ prunedAt: ctx.modulesFile?.prunedAt,
1278
+ wantedLockfile: result.newLockfile,
1279
+ useLockfile: opts.useLockfile && ctx.wantedLockfileIsModified,
1280
+ hoistWorkspacePackages: opts.hoistWorkspacePackages,
1281
+ });
1282
+ return {
1283
+ ...result,
1284
+ stats,
1285
+ ignoredBuilds,
1286
+ };
1287
+ }
1288
+ return await _installInContext(projects, ctx, opts);
1289
+ }
1290
+ catch (error) { // eslint-disable-line
1291
+ if (!BROKEN_LOCKFILE_INTEGRITY_ERRORS.has(error.code) ||
1292
+ (!ctx.existsNonEmptyWantedLockfile && !ctx.existsCurrentLockfile))
1293
+ throw error;
1294
+ opts.needsFullResolution = true;
1295
+ // Ideally, we would not update but currently there is no other way to redownload the integrity of the package
1296
+ for (const project of projects) {
1297
+ project.update = true;
1298
+ }
1299
+ logger.warn({
1300
+ error,
1301
+ message: error.message,
1302
+ prefix: ctx.lockfileDir,
1303
+ });
1304
+ logger.error(new PnpmError(error.code, 'The lockfile is broken! A full installation will be performed in an attempt to fix it.'));
1305
+ return _installInContext(projects, ctx, opts);
1306
+ }
1307
+ finally {
1308
+ await opts.storeController.close();
1309
+ }
1310
+ };
1311
+ const limitLinking = pLimit(16);
1312
+ async function linkAllBins(depNodes, depGraph, opts) {
1313
+ await Promise.all(depNodes.map(async depNode => limitLinking(async () => linkBinsOfDependencies(depNode, depGraph, opts))));
1314
+ }
1315
+ export class IgnoredBuildsError extends PnpmError {
1316
+ constructor(ignoredBuilds) {
1317
+ const packageNames = dedupePackageNamesFromIgnoredBuilds(ignoredBuilds);
1318
+ super('IGNORED_BUILDS', `Ignored build scripts: ${packageNames.join(', ')}`, {
1319
+ hint: 'Run "pnpm approve-builds" to pick which dependencies should be allowed to run scripts.',
1320
+ });
1321
+ }
1322
+ }
1323
+ function dedupePackageNamesFromIgnoredBuilds(ignoredBuilds) {
1324
+ return Array.from(new Set(Array.from(ignoredBuilds ?? []).map(dp.removeSuffix))).sort(lexCompare);
1325
+ }
1326
+ /**
1327
+ * Build injectionTargetsByDepPath from the dependenciesGraph for injected workspace packages
1328
+ * and extend projects with their target directories.
1329
+ * The dependenciesGraph already has the correct `dir` values after `extendGraph` is applied
1330
+ * (which uses the correct hash-based paths when global virtual store is enabled).
1331
+ */
1332
+ function getProjectsWithTargetDirs(projects, lockfile, dependenciesGraph) {
1333
+ const injectionTargetsByDepPath = new Map();
1334
+ if (lockfile.packages) {
1335
+ for (const [depPath, { resolution }] of Object.entries(lockfile.packages)) {
1336
+ if (resolution?.type === 'directory') {
1337
+ const graphNode = dependenciesGraph[depPath];
1338
+ if (graphNode?.dir) {
1339
+ injectionTargetsByDepPath.set(depPath, [graphNode.dir]);
1340
+ }
1341
+ }
1342
+ }
1343
+ }
1344
+ return extendProjectsWithTargetDirs(projects, injectionTargetsByDepPath);
1345
+ }
1346
+ //# sourceMappingURL=index.js.map