pnpm
155 строк · 4.9 Кб
1import path from 'path'
2import { calcDepState, type DepsStateCache } from '@pnpm/calc-dep-state'
3import {
4progressLogger,
5removalLogger,
6statsLogger,
7} from '@pnpm/core-loggers'
8import {
9type DepHierarchy,
10type DependenciesGraph,
11} from '@pnpm/deps.graph-builder'
12import { linkBins } from '@pnpm/link-bins'
13import { logger } from '@pnpm/logger'
14import {
15type PackageFilesResponse,
16type StoreController,
17} from '@pnpm/store-controller-types'
18import pLimit from 'p-limit'
19import difference from 'ramda/src/difference'
20import isEmpty from 'ramda/src/isEmpty'
21import rimraf from '@zkochan/rimraf'
22
23const limitLinking = pLimit(16)
24
25export async function linkHoistedModules (
26storeController: StoreController,
27graph: DependenciesGraph,
28prevGraph: DependenciesGraph,
29hierarchy: DepHierarchy,
30opts: {
31depsStateCache: DepsStateCache
32disableRelinkLocalDirDeps?: boolean
33force: boolean
34ignoreScripts: boolean
35lockfileDir: string
36preferSymlinkedExecutables?: boolean
37sideEffectsCacheRead: boolean
38}
39): Promise<void> {
40// TODO: remove nested node modules first
41const dirsToRemove = difference(
42Object.keys(prevGraph),
43Object.keys(graph)
44)
45statsLogger.debug({
46prefix: opts.lockfileDir,
47removed: dirsToRemove.length,
48})
49// We should avoid removing unnecessary directories while simultaneously adding new ones.
50// Doing so can sometimes lead to a race condition when linking commands to `node_modules/.bin`.
51await Promise.all(dirsToRemove.map((dir) => tryRemoveDir(dir)))
52await Promise.all(
53Object.entries(hierarchy)
54.map(([parentDir, depsHierarchy]) => {
55function warn (message: string) {
56logger.info({
57message,
58prefix: parentDir,
59})
60}
61return linkAllPkgsInOrder(storeController, graph, prevGraph, depsHierarchy, parentDir, {
62...opts,
63warn,
64})
65})
66)
67}
68
69async function tryRemoveDir (dir: string): Promise<void> {
70removalLogger.debug(dir)
71try {
72await rimraf(dir)
73} catch (err: any) { // eslint-disable-line
74/* Just ignoring for now. Not even logging.
75logger.warn({
76error: err,
77message: `Failed to remove "${pathToRemove}"`,
78prefix: lockfileDir,
79})
80*/
81}
82}
83
84async function linkAllPkgsInOrder (
85storeController: StoreController,
86graph: DependenciesGraph,
87prevGraph: DependenciesGraph,
88hierarchy: DepHierarchy,
89parentDir: string,
90opts: {
91depsStateCache: DepsStateCache
92disableRelinkLocalDirDeps?: boolean
93force: boolean
94ignoreScripts: boolean
95lockfileDir: string
96preferSymlinkedExecutables?: boolean
97sideEffectsCacheRead: boolean
98warn: (message: string) => void
99}
100): Promise<void> {
101const _calcDepState = calcDepState.bind(null, graph, opts.depsStateCache)
102await Promise.all(
103Object.entries(hierarchy).map(async ([dir, deps]) => {
104const depNode = graph[dir]
105if (depNode.fetching) {
106let filesResponse!: PackageFilesResponse
107try {
108filesResponse = (await depNode.fetching()).files
109} catch (err: any) { // eslint-disable-line
110if (depNode.optional) return
111throw err
112}
113
114depNode.requiresBuild = filesResponse.requiresBuild
115let sideEffectsCacheKey: string | undefined
116if (opts.sideEffectsCacheRead && filesResponse.sideEffects && !isEmpty(filesResponse.sideEffects)) {
117sideEffectsCacheKey = _calcDepState(dir, {
118isBuilt: !opts.ignoreScripts && depNode.requiresBuild,
119patchFileHash: depNode.patchFile?.hash,
120})
121}
122// Limiting the concurrency here fixes an out of memory error.
123// It is not clear why it helps as importing is also limited inside fs.indexed-pkg-importer.
124// The out of memory error was reproduced on the teambit/bit repository with the "rootComponents" feature turned on
125await limitLinking(async () => {
126const { importMethod, isBuilt } = await storeController.importPackage(depNode.dir, {
127filesResponse,
128force: true,
129disableRelinkLocalDirDeps: opts.disableRelinkLocalDirDeps,
130keepModulesDir: true,
131requiresBuild: depNode.patchFile != null || depNode.requiresBuild,
132sideEffectsCacheKey,
133})
134if (importMethod) {
135progressLogger.debug({
136method: importMethod,
137requester: opts.lockfileDir,
138status: 'imported',
139to: depNode.dir,
140})
141}
142depNode.isBuilt = isBuilt
143})
144}
145return linkAllPkgsInOrder(storeController, graph, prevGraph, deps, dir, opts)
146})
147)
148const modulesDir = path.join(parentDir, 'node_modules')
149const binsDir = path.join(modulesDir, '.bin')
150await linkBins(modulesDir, binsDir, {
151allowExoticManifests: true,
152preferSymlinkedExecutables: opts.preferSymlinkedExecutables,
153warn: opts.warn,
154})
155}
156