cross-packer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +151 -0
  3. package/README.zh-CN.md +151 -0
  4. package/dist/archive/ar.d.ts +12 -0
  5. package/dist/archive/seven-za.d.ts +32 -0
  6. package/dist/archive/tar.d.ts +30 -0
  7. package/dist/archive/zip-reader.d.ts +17 -0
  8. package/dist/asar/dependency/collect.d.ts +2 -0
  9. package/dist/asar/dependency/detector.d.ts +8 -0
  10. package/dist/asar/dependency/hoister.d.ts +27 -0
  11. package/dist/asar/dependency/utils.d.ts +59 -0
  12. package/dist/asar/fileset/matcher.d.ts +12 -0
  13. package/dist/asar/fileset/platform-fileset.d.ts +36 -0
  14. package/dist/asar/fileset/transformer.d.ts +2 -0
  15. package/dist/asar/fileset/unpack-detector.d.ts +10 -0
  16. package/dist/asar/fileset/walker.d.ts +14 -0
  17. package/dist/asar/pack.d.ts +23 -0
  18. package/dist/asar/packer/app-collector.d.ts +2 -0
  19. package/dist/asar/packer/collector.d.ts +18 -0
  20. package/dist/asar/packer/concurrency.d.ts +1 -0
  21. package/dist/asar/packer/integrity.d.ts +8 -0
  22. package/dist/asar/packer/nm-collector.d.ts +7 -0
  23. package/dist/asar/packer/stream-builder.d.ts +18 -0
  24. package/dist/cli.d.ts +2 -0
  25. package/dist/config/index.d.ts +97 -0
  26. package/dist/electron/dist-zip.d.ts +18 -0
  27. package/dist/electron/download.d.ts +21 -0
  28. package/dist/index.d.ts +11 -0
  29. package/dist/packer/common/app-update-yml.d.ts +5 -0
  30. package/dist/packer/common/extra-resources.d.ts +39 -0
  31. package/dist/packer/common/staging.d.ts +3 -0
  32. package/dist/packer/common/template.d.ts +2 -0
  33. package/dist/packer/linux/linux.d.ts +2 -0
  34. package/dist/packer/mac/mac.d.ts +23 -0
  35. package/dist/packer/win/nsis.d.ts +24 -0
  36. package/dist/packer/win/resedit.d.ts +5 -0
  37. package/dist/packer/win/toolchain.d.ts +8 -0
  38. package/dist/packer/win/win.d.ts +12 -0
  39. package/dist/pipeline/pipeline.d.ts +41 -0
  40. package/dist/pipeline/targets.d.ts +28 -0
  41. package/dist/shared/hash.d.ts +2 -0
  42. package/dist/shared/logger.d.ts +39 -0
  43. package/dist/shared/platform.d.ts +26 -0
  44. package/dist/shared/types.d.ts +68 -0
  45. package/dist/update/blockmap.d.ts +13 -0
  46. package/dist/update/metadata.d.ts +20 -0
  47. package/dist/update/version.d.ts +7 -0
  48. package/package.json +71 -0
  49. package/src/archive/ar.ts +41 -0
  50. package/src/archive/seven-za.ts +160 -0
  51. package/src/archive/tar.ts +142 -0
  52. package/src/archive/zip-reader.ts +61 -0
  53. package/src/asar/dependency/collect.ts +437 -0
  54. package/src/asar/dependency/detector.ts +73 -0
  55. package/src/asar/dependency/hoister.ts +714 -0
  56. package/src/asar/dependency/utils.ts +225 -0
  57. package/src/asar/fileset/matcher.ts +101 -0
  58. package/src/asar/fileset/platform-fileset.ts +416 -0
  59. package/src/asar/fileset/transformer.ts +154 -0
  60. package/src/asar/fileset/unpack-detector.ts +38 -0
  61. package/src/asar/fileset/walker.ts +115 -0
  62. package/src/asar/pack.ts +186 -0
  63. package/src/asar/packer/app-collector.ts +74 -0
  64. package/src/asar/packer/collector.ts +134 -0
  65. package/src/asar/packer/concurrency.ts +24 -0
  66. package/src/asar/packer/integrity.ts +53 -0
  67. package/src/asar/packer/nm-collector.ts +194 -0
  68. package/src/asar/packer/stream-builder.ts +723 -0
  69. package/src/cli.ts +182 -0
  70. package/src/config/index.ts +365 -0
  71. package/src/electron/dist-zip.ts +65 -0
  72. package/src/electron/download.ts +121 -0
  73. package/src/index.ts +14 -0
  74. package/src/packer/common/app-update-yml.ts +21 -0
  75. package/src/packer/common/extra-resources.ts +83 -0
  76. package/src/packer/common/staging.ts +33 -0
  77. package/src/packer/common/template.ts +24 -0
  78. package/src/packer/linux/linux.ts +329 -0
  79. package/src/packer/mac/mac.ts +495 -0
  80. package/src/packer/win/nsis.ts +244 -0
  81. package/src/packer/win/resedit.ts +60 -0
  82. package/src/packer/win/toolchain.ts +63 -0
  83. package/src/packer/win/win.ts +421 -0
  84. package/src/pipeline/pipeline.ts +149 -0
  85. package/src/pipeline/targets.ts +67 -0
  86. package/src/shared/hash.ts +15 -0
  87. package/src/shared/logger.ts +91 -0
  88. package/src/shared/platform.ts +41 -0
  89. package/src/shared/types.ts +82 -0
  90. package/src/update/blockmap.ts +247 -0
  91. package/src/update/metadata.ts +97 -0
  92. package/src/update/version.ts +59 -0
  93. package/templates/linux/app.desktop +11 -0
  94. package/templates/linux/control +9 -0
  95. package/templates/linux/postinst +10 -0
  96. package/templates/linux/postrm +8 -0
  97. package/templates/linux/preinst +49 -0
  98. package/templates/linux/prerm +43 -0
  99. package/templates/mac/Info.plist +18 -0
  100. package/templates/mac/app-update.yml +4 -0
  101. package/templates/win/nsis/README.md +51 -0
  102. package/templates/win/nsis/THIRD-PARTY-NOTICES.md +20 -0
  103. package/templates/win/nsis/assistedInstaller.nsh +154 -0
  104. package/templates/win/nsis/assistedMessages.yml +368 -0
  105. package/templates/win/nsis/common.nsh +147 -0
  106. package/templates/win/nsis/empty-license.txt +1 -0
  107. package/templates/win/nsis/include/FileAssociation.nsh +123 -0
  108. package/templates/win/nsis/include/StdUtils.nsh +496 -0
  109. package/templates/win/nsis/include/StrContains.nsh +48 -0
  110. package/templates/win/nsis/include/UAC.nsh +299 -0
  111. package/templates/win/nsis/include/allowOnlyOneInstallerInstance.nsh +164 -0
  112. package/templates/win/nsis/include/extractAppPackage.nsh +138 -0
  113. package/templates/win/nsis/include/getProcessInfo.nsh +157 -0
  114. package/templates/win/nsis/include/installUtil.nsh +248 -0
  115. package/templates/win/nsis/include/installer.nsh +247 -0
  116. package/templates/win/nsis/include/nsProcess.nsh +28 -0
  117. package/templates/win/nsis/include/webPackage.nsh +64 -0
  118. package/templates/win/nsis/installSection.nsh +110 -0
  119. package/templates/win/nsis/installer.nsi +132 -0
  120. package/templates/win/nsis/messages.yml +240 -0
  121. package/templates/win/nsis/multiUser.nsh +134 -0
  122. package/templates/win/nsis/multiUserUi.nsh +230 -0
  123. package/templates/win/nsis/oneClick.nsh +19 -0
  124. package/templates/win/nsis/portable.nsi +91 -0
  125. package/templates/win/nsis/uninstaller.nsh +263 -0
  126. package/templates/win/nsis-header.nsh +57 -0
@@ -0,0 +1,714 @@
1
+ import { parseNameVersion } from './utils.ts'
2
+
3
+ const HoisterDependencyKind = { REGULAR: 0, WORKSPACE: 1, EXTERNAL_SOFT_LINK: 2 } as const
4
+ const Hoistable = { YES: 0, NO: 1, DEPENDS: 2 } as const
5
+ const DebugLevel = { NONE: -1, PERF: 0, CHECK: 1, REASONS: 2, INTENSIVE_CHECK: 9 } as const
6
+
7
+ /** Compose a locator string from name and reference. */
8
+ const makeLocator = (name: string, reference: string): string => `${name}@${reference}`
9
+
10
+ /** Compose an ident string, stripping any hash portion from the reference. */
11
+ const makeIdent = (name: string, reference: string): string => {
12
+ const hashIdx = reference.indexOf('#')
13
+ const realReference = hashIdx >= 0 ? reference.substring(hashIdx + 1) : reference
14
+ return makeLocator(name, realReference)
15
+ }
16
+
17
+ /** Input tree produced by transformToHoisterTree. */
18
+ export interface HoisterTreeNode {
19
+ name: string
20
+ identName: string
21
+ reference: string
22
+ peerNames: Set<string>
23
+ dependencies: Set<HoisterTreeNode>
24
+ hoistPriority?: number
25
+ dependencyKind?: number
26
+ }
27
+
28
+ /** Mutable work node used during hoisting rounds. */
29
+ interface WorkNode {
30
+ name: string
31
+ references: Set<string>
32
+ ident: string
33
+ locator: string
34
+ dependencies: Map<string, WorkNode>
35
+ originalDependencies: Map<string, WorkNode>
36
+ hoistedDependencies: Map<string, WorkNode>
37
+ peerNames: Set<string>
38
+ reasons: Map<string, string | null>
39
+ decoupled: boolean
40
+ isHoistBorder: boolean
41
+ hoistPriority: number
42
+ dependencyKind: number
43
+ hoistedFrom: Map<string, Set<string>>
44
+ hoistedTo: Map<string, Set<string>>
45
+ }
46
+
47
+ /** Immutable shrunk tree returned by hoist. */
48
+ export interface ShrunkNode {
49
+ name: string
50
+ identName: string
51
+ references: Set<string>
52
+ dependencies: Set<ShrunkNode>
53
+ }
54
+
55
+ interface PreferenceEntry {
56
+ dependents: Set<string>
57
+ peerDependents: Set<string>
58
+ hoistPriority: number
59
+ }
60
+
61
+ interface HoistInfo {
62
+ isHoistable: (typeof Hoistable)[keyof typeof Hoistable]
63
+ dependsOn?: Set<WorkNode> | null
64
+ reason: string | null
65
+ }
66
+
67
+ interface HoistOptions {
68
+ debugLevel?: number
69
+ check?: boolean
70
+ hoistingLimits?: Map<string, Set<string>>
71
+ }
72
+
73
+ interface HoistToOptions {
74
+ check: boolean
75
+ debugLevel: number
76
+ hoistingLimits: Map<string, Set<string>>
77
+ fastLookupPossible: boolean
78
+ }
79
+
80
+ interface RoundResult {
81
+ anotherRoundNeeded: boolean
82
+ isGraphChanged: boolean
83
+ }
84
+
85
+ export function hoist(tree: HoisterTreeNode, opts: HoistOptions = {}): ShrunkNode {
86
+ const debugLevel = opts.debugLevel || DebugLevel.NONE
87
+ const check = opts.check || debugLevel >= DebugLevel.INTENSIVE_CHECK
88
+ const hoistingLimits = opts.hoistingLimits || new Map<string, Set<string>>()
89
+ const options: HoistToOptions = { check, debugLevel, hoistingLimits, fastLookupPossible: true }
90
+
91
+ const treeCopy = cloneTree(tree, options)
92
+ let anotherRoundNeeded = false
93
+ do {
94
+ const result = hoistTo(treeCopy, [treeCopy], new Set([treeCopy.locator]), new Map(), options)
95
+ anotherRoundNeeded = result.anotherRoundNeeded || result.isGraphChanged
96
+ options.fastLookupPossible = false
97
+ } while (anotherRoundNeeded)
98
+
99
+ return shrinkTree(treeCopy)
100
+ }
101
+
102
+ export function transformToHoisterTree(
103
+ productionGraph: Record<string, { dependencies?: string[] }>,
104
+ key: string,
105
+ nodes: Map<string, HoisterTreeNode> = new Map(),
106
+ ): HoisterTreeNode {
107
+ let node = nodes.get(key)
108
+ const { name, version } = parseNameVersion(key)
109
+ if (!node) {
110
+ node = {
111
+ name,
112
+ identName: name,
113
+ reference: version,
114
+ peerNames: new Set(),
115
+ dependencies: new Set(),
116
+ }
117
+ nodes.set(key, node)
118
+ const deps = productionGraph[key]?.dependencies || []
119
+ for (const dep of deps) {
120
+ const child = transformToHoisterTree(productionGraph, dep, nodes)
121
+ node.dependencies.add(child)
122
+ }
123
+ }
124
+ return node
125
+ }
126
+ /** Collect dependencies used before the first hoisting round. */
127
+ const getZeroRoundUsedDependencies = (rootNodePath: WorkNode[]): Map<string, WorkNode> => {
128
+ const rootNode = rootNodePath[rootNodePath.length - 1]
129
+ const usedDependencies = new Map<string, WorkNode>()
130
+ const seenNodes = new Set<WorkNode>()
131
+ // Collect the zero-round reachable dependencies of the root node.
132
+ const addUsedDependencies = (node: WorkNode): void => {
133
+ if (seenNodes.has(node)) return
134
+ seenNodes.add(node)
135
+ for (const dep of node.hoistedDependencies.values()) usedDependencies.set(dep.name, dep)
136
+ for (const dep of node.dependencies.values()) {
137
+ if (!node.peerNames.has(dep.name)) addUsedDependencies(dep)
138
+ }
139
+ }
140
+ addUsedDependencies(rootNode)
141
+ return usedDependencies
142
+ }
143
+
144
+ /** Collect the dependencies reachable from the root node path. */
145
+ const getUsedDependencies = (rootNodePath: WorkNode[]): Map<string, WorkNode> => {
146
+ const rootNode = rootNodePath[rootNodePath.length - 1]
147
+ const usedDependencies = new Map<string, WorkNode>()
148
+ const seenNodes = new Set<WorkNode>()
149
+ // Collect reachable dependencies, skipping names in hiddenDependencies.
150
+ const addUsedDependencies = (node: WorkNode, hiddenDependencies: Set<string>): void => {
151
+ if (seenNodes.has(node)) return
152
+ seenNodes.add(node)
153
+ for (const dep of node.hoistedDependencies.values()) {
154
+ if (!hiddenDependencies.has(dep.name)) {
155
+ for (const pathNode of rootNodePath) {
156
+ const reachableDependency = pathNode.dependencies.get(dep.name)
157
+ if (reachableDependency)
158
+ usedDependencies.set(reachableDependency.name, reachableDependency)
159
+ }
160
+ }
161
+ }
162
+ const childrenHiddenDependencies = new Set<string>()
163
+ for (const dep of node.dependencies.values()) childrenHiddenDependencies.add(dep.name)
164
+ for (const dep of node.dependencies.values()) {
165
+ if (!node.peerNames.has(dep.name)) addUsedDependencies(dep, childrenHiddenDependencies)
166
+ }
167
+ }
168
+ addUsedDependencies(rootNode, new Set())
169
+ return usedDependencies
170
+ }
171
+
172
+ /** Clone a graph node so it can be re-parented without side effects. */
173
+ const decoupleGraphNode = (parent: WorkNode, node: WorkNode): WorkNode => {
174
+ if (node.decoupled) return node
175
+ const clone: WorkNode = {
176
+ name: node.name,
177
+ references: new Set(node.references),
178
+ ident: node.ident,
179
+ locator: node.locator,
180
+ dependencies: new Map(node.dependencies),
181
+ originalDependencies: new Map(node.originalDependencies),
182
+ hoistedDependencies: new Map(node.hoistedDependencies),
183
+ peerNames: new Set(node.peerNames),
184
+ reasons: new Map(node.reasons),
185
+ decoupled: true,
186
+ isHoistBorder: node.isHoistBorder,
187
+ hoistPriority: node.hoistPriority,
188
+ dependencyKind: node.dependencyKind,
189
+ hoistedFrom: new Map(node.hoistedFrom),
190
+ hoistedTo: new Map(node.hoistedTo),
191
+ }
192
+ const selfDep = clone.dependencies.get(node.name)
193
+ if (selfDep && selfDep.ident === clone.ident) clone.dependencies.set(node.name, clone)
194
+ parent.dependencies.set(clone.name, clone)
195
+ return clone
196
+ }
197
+
198
+ /** Map each dependency name to its list of acceptable idents, ordered by hoist priority. */
199
+ const getHoistIdentMap = (
200
+ rootNode: WorkNode,
201
+ preferenceMap: Map<string, PreferenceEntry>,
202
+ ): Map<string, string[]> => {
203
+ const identMap = new Map<string, string[]>([[rootNode.name, [rootNode.ident]]])
204
+ for (const dep of rootNode.dependencies.values()) {
205
+ if (!rootNode.peerNames.has(dep.name)) identMap.set(dep.name, [dep.ident])
206
+ }
207
+ const keyList = Array.from(preferenceMap.keys())
208
+ keyList.sort((key1, key2) => {
209
+ const entry1 = preferenceMap.get(key1)
210
+ const entry2 = preferenceMap.get(key2)
211
+ if (!entry1 || !entry2) return 0
212
+ if (entry2.hoistPriority !== entry1.hoistPriority)
213
+ return entry2.hoistPriority - entry1.hoistPriority
214
+ const entry1Usages = entry1.dependents.size + entry1.peerDependents.size
215
+ const entry2Usages = entry2.dependents.size + entry2.peerDependents.size
216
+ return entry2Usages - entry1Usages
217
+ })
218
+ for (const key of keyList) {
219
+ const name = key.substring(0, key.indexOf('@', 1))
220
+ const ident = key.substring(name.length + 1)
221
+ if (!rootNode.peerNames.has(name)) {
222
+ let idents = identMap.get(name)
223
+ if (!idents) {
224
+ idents = []
225
+ identMap.set(name, idents)
226
+ }
227
+ if (idents.indexOf(ident) < 0) idents.push(ident)
228
+ }
229
+ }
230
+ return identMap
231
+ }
232
+
233
+ /** Sort the regular (non-peer) dependencies of a node into a Set. */
234
+ const getSortedRegularDependencies = (node: WorkNode): Set<WorkNode> => {
235
+ const dependencies = new Set<WorkNode>()
236
+ // Add a dependency and its transitive non-shadowed peers.
237
+ const addDep = (dep: WorkNode, seenDeps: Set<WorkNode> = new Set()): void => {
238
+ if (seenDeps.has(dep)) return
239
+ seenDeps.add(dep)
240
+ for (const peerName of dep.peerNames) {
241
+ if (!node.peerNames.has(peerName)) {
242
+ const peerDep = node.dependencies.get(peerName)
243
+ if (peerDep && !dependencies.has(peerDep)) addDep(peerDep, seenDeps)
244
+ }
245
+ }
246
+ dependencies.add(dep)
247
+ }
248
+ for (const dep of node.dependencies.values()) {
249
+ if (!node.peerNames.has(dep.name)) addDep(dep)
250
+ }
251
+ return dependencies
252
+ }
253
+
254
+ const hoistTo = (
255
+ tree: WorkNode,
256
+ rootNodePath: WorkNode[],
257
+ rootNodePathLocators: Set<string>,
258
+ parentShadowedNodes: Map<WorkNode, Set<string>>,
259
+ options: HoistToOptions,
260
+ seenNodes: Set<WorkNode> = new Set(),
261
+ ): RoundResult => {
262
+ const rootNode = rootNodePath[rootNodePath.length - 1]
263
+ if (seenNodes.has(rootNode)) return { anotherRoundNeeded: false, isGraphChanged: false }
264
+ seenNodes.add(rootNode)
265
+ const preferenceMap = buildPreferenceMap(rootNode)
266
+ const hoistIdentMap = getHoistIdentMap(rootNode, preferenceMap)
267
+ const usedDependencies =
268
+ tree === rootNode
269
+ ? new Map<string, WorkNode>()
270
+ : options.fastLookupPossible
271
+ ? getZeroRoundUsedDependencies(rootNodePath)
272
+ : getUsedDependencies(rootNodePath)
273
+ let wasStateChanged: boolean
274
+ let anotherRoundNeeded = false
275
+ let isGraphChanged = false
276
+ const hoistIdents = new Map(
277
+ Array.from(hoistIdentMap.entries()).map(([k, v]) => [k, v[0]] as const),
278
+ )
279
+ const shadowedNodes = new Map<WorkNode, Set<string>>()
280
+ do {
281
+ const result = hoistGraph(
282
+ tree,
283
+ rootNodePath,
284
+ rootNodePathLocators,
285
+ usedDependencies,
286
+ hoistIdents,
287
+ hoistIdentMap,
288
+ parentShadowedNodes,
289
+ shadowedNodes,
290
+ options,
291
+ )
292
+ if (result.isGraphChanged) isGraphChanged = true
293
+ if (result.anotherRoundNeeded) anotherRoundNeeded = true
294
+ wasStateChanged = false
295
+ for (const [name, idents] of hoistIdentMap) {
296
+ if (idents.length > 1 && !rootNode.dependencies.has(name)) {
297
+ hoistIdents.delete(name)
298
+ idents.shift()
299
+ hoistIdents.set(name, idents[0])
300
+ wasStateChanged = true
301
+ }
302
+ }
303
+ } while (wasStateChanged)
304
+ for (const dependency of rootNode.dependencies.values()) {
305
+ if (!rootNode.peerNames.has(dependency.name) && !rootNodePathLocators.has(dependency.locator)) {
306
+ rootNodePathLocators.add(dependency.locator)
307
+ const result = hoistTo(
308
+ tree,
309
+ [...rootNodePath, dependency],
310
+ rootNodePathLocators,
311
+ shadowedNodes,
312
+ options,
313
+ )
314
+ if (result.isGraphChanged) isGraphChanged = true
315
+ if (result.anotherRoundNeeded) anotherRoundNeeded = true
316
+ rootNodePathLocators.delete(dependency.locator)
317
+ }
318
+ }
319
+ return { anotherRoundNeeded, isGraphChanged }
320
+ }
321
+
322
+ /** Check whether a node still has unhoisted non-peer dependencies. */
323
+ const hasUnhoistedDependencies = (node: WorkNode): boolean => {
324
+ for (const [subName, subDependency] of node.dependencies) {
325
+ if (!node.peerNames.has(subName) && subDependency.ident !== node.ident) return true
326
+ }
327
+ return false
328
+ }
329
+
330
+ const getNodeHoistInfo = (
331
+ rootNode: WorkNode,
332
+ _rootNodePathLocators: Set<string>,
333
+ nodePath: WorkNode[],
334
+ node: WorkNode,
335
+ usedDependencies: Map<string, WorkNode>,
336
+ hoistIdents: Map<string, string>,
337
+ _hoistIdentMap: Map<string, string[]>,
338
+ shadowedNodes: Map<WorkNode, Set<string>>,
339
+ { fastLookupPossible }: { fastLookupPossible: boolean },
340
+ ): HoistInfo => {
341
+ const reason: string | null = null
342
+ let dependsOn: Set<WorkNode> | null = new Set()
343
+ const parentNode = nodePath[nodePath.length - 1]
344
+ const isSelfReference = node.ident === parentNode.ident
345
+ let isHoistable = !isSelfReference
346
+ if (isHoistable) isHoistable = node.dependencyKind !== HoisterDependencyKind.WORKSPACE
347
+ if (isHoistable && node.dependencyKind === HoisterDependencyKind.EXTERNAL_SOFT_LINK) {
348
+ isHoistable = !hasUnhoistedDependencies(node)
349
+ }
350
+ if (isHoistable) isHoistable = !rootNode.peerNames.has(node.name)
351
+ if (isHoistable) {
352
+ const usedDep = usedDependencies.get(node.name)
353
+ const isNameAvailable = !usedDep || usedDep.ident === node.ident
354
+ isHoistable = isNameAvailable
355
+ }
356
+ if (isHoistable && isNameShadowed(node, nodePath, shadowedNodes)) {
357
+ isHoistable = false
358
+ }
359
+ if (isHoistable) {
360
+ const hoistedIdent = hoistIdents.get(node.name)
361
+ isHoistable = hoistedIdent === node.ident
362
+ }
363
+ if (isHoistable) {
364
+ const peerCheck = checkPeerDeps(node, rootNode, nodePath)
365
+ dependsOn = peerCheck.dependsOn
366
+ isHoistable = peerCheck.arePeerDepsSatisfied
367
+ }
368
+ if (isHoistable && !fastLookupPossible) {
369
+ for (const origDep of node.hoistedDependencies.values()) {
370
+ const usedDep = usedDependencies.get(origDep.name) || rootNode.dependencies.get(origDep.name)
371
+ if (!usedDep || origDep.ident !== usedDep.ident) {
372
+ isHoistable = false
373
+ break
374
+ }
375
+ }
376
+ }
377
+ if (dependsOn !== null && dependsOn.size > 0)
378
+ return { isHoistable: Hoistable.DEPENDS, dependsOn, reason }
379
+ return { isHoistable: isHoistable ? Hoistable.YES : Hoistable.NO, reason }
380
+ }
381
+
382
+ /** Compose the alias locator for a node (name@locator). */
383
+ const getAliasedLocator = (node: WorkNode): string => `${node.name}@${node.locator}`
384
+
385
+ const hoistGraph = (
386
+ _tree: WorkNode,
387
+ rootNodePath: WorkNode[],
388
+ rootNodePathLocators: Set<string>,
389
+ usedDependencies: Map<string, WorkNode>,
390
+ hoistIdents: Map<string, string>,
391
+ hoistIdentMap: Map<string, string[]>,
392
+ parentShadowedNodes: Map<WorkNode, Set<string>>,
393
+ shadowedNodes: Map<WorkNode, Set<string>>,
394
+ options: HoistToOptions,
395
+ ): RoundResult => {
396
+ const rootNode = rootNodePath[rootNodePath.length - 1]
397
+ const seenNodes = new Set<WorkNode>()
398
+ let anotherRoundNeeded = false
399
+ let isGraphChanged = false
400
+ const hoistNodeDependencies = (
401
+ nodePath: WorkNode[],
402
+ locatorPath: string[],
403
+ aliasedLocatorPath: string[],
404
+ parentNode: WorkNode,
405
+ newNodes: Set<WorkNode>,
406
+ ): void => {
407
+ if (seenNodes.has(parentNode)) return
408
+ const nextLocatorPath = [...locatorPath, getAliasedLocator(parentNode)]
409
+ const nextAliasedLocatorPath = [...aliasedLocatorPath, getAliasedLocator(parentNode)]
410
+ const dependantTree = new Map<string, Set<string>>()
411
+ const hoistInfos = new Map<WorkNode, HoistInfo>()
412
+ for (const subDependency of getSortedRegularDependencies(parentNode)) {
413
+ const hoistInfo = getNodeHoistInfo(
414
+ rootNode,
415
+ rootNodePathLocators,
416
+ [rootNode, ...nodePath, parentNode],
417
+ subDependency,
418
+ usedDependencies,
419
+ hoistIdents,
420
+ hoistIdentMap,
421
+ shadowedNodes,
422
+ { fastLookupPossible: options.fastLookupPossible },
423
+ )
424
+ hoistInfos.set(subDependency, hoistInfo)
425
+ if (hoistInfo.isHoistable === Hoistable.DEPENDS) {
426
+ for (const node of hoistInfo.dependsOn || []) {
427
+ const nodeDependants = dependantTree.get(node.name) || new Set<string>()
428
+ nodeDependants.add(subDependency.name)
429
+ dependantTree.set(node.name, nodeDependants)
430
+ }
431
+ }
432
+ }
433
+ const unhoistableNodes = new Set<WorkNode>()
434
+ // Mark a node unhoistable and cascade to its dependants.
435
+ const addUnhoistableNode = (node: WorkNode | undefined, hoistInfo: HoistInfo): void => {
436
+ if (!node || unhoistableNodes.has(node)) return
437
+ unhoistableNodes.add(node)
438
+ hoistInfos.set(node, { isHoistable: Hoistable.NO, reason: hoistInfo.reason })
439
+ for (const dependantName of dependantTree.get(node.name) || []) {
440
+ addUnhoistableNode(parentNode.dependencies.get(dependantName), hoistInfo)
441
+ }
442
+ }
443
+ for (const [node, hoistInfo] of hoistInfos)
444
+ if (hoistInfo.isHoistable === Hoistable.NO) addUnhoistableNode(node, hoistInfo)
445
+ let wereNodesHoisted = false
446
+ for (const node of hoistInfos.keys()) {
447
+ if (!unhoistableNodes.has(node)) {
448
+ isGraphChanged = true
449
+ const shadowedNames = parentShadowedNodes.get(parentNode)
450
+ if (shadowedNames?.has(node.name)) anotherRoundNeeded = true
451
+ wereNodesHoisted = true
452
+ parentNode.dependencies.delete(node.name)
453
+ parentNode.hoistedDependencies.set(node.name, node)
454
+ parentNode.reasons.delete(node.name)
455
+ const hoistedNode = rootNode.dependencies.get(node.name)
456
+ if (!hoistedNode) {
457
+ if (rootNode.ident !== node.ident) {
458
+ rootNode.dependencies.set(node.name, node)
459
+ newNodes.add(node)
460
+ }
461
+ } else {
462
+ for (const reference of node.references) hoistedNode.references.add(reference)
463
+ }
464
+ }
465
+ }
466
+ if (parentNode.dependencyKind === HoisterDependencyKind.EXTERNAL_SOFT_LINK && wereNodesHoisted)
467
+ anotherRoundNeeded = true
468
+ const children = getSortedRegularDependencies(parentNode)
469
+ for (const node of children) {
470
+ if (unhoistableNodes.has(node)) {
471
+ const hoistInfo = hoistInfos.get(node)
472
+ if (!hoistInfo) continue
473
+ const hoistableIdent = hoistIdents.get(node.name)
474
+ if (
475
+ (hoistableIdent === node.ident || !parentNode.reasons.has(node.name)) &&
476
+ hoistInfo.isHoistable !== Hoistable.YES
477
+ )
478
+ parentNode.reasons.set(node.name, hoistInfo.reason)
479
+ if (!node.isHoistBorder && nextAliasedLocatorPath.indexOf(getAliasedLocator(node)) < 0) {
480
+ seenNodes.add(parentNode)
481
+ const decoupledNode = decoupleGraphNode(parentNode, node)
482
+ hoistNodeDependencies(
483
+ [...nodePath, parentNode],
484
+ nextLocatorPath,
485
+ nextAliasedLocatorPath,
486
+ decoupledNode,
487
+ nextNewNodes,
488
+ )
489
+ seenNodes.delete(parentNode)
490
+ }
491
+ }
492
+ }
493
+ }
494
+ let newNodes: Set<WorkNode>
495
+ let nextNewNodes = new Set<WorkNode>(getSortedRegularDependencies(rootNode))
496
+ const aliasedRootNodePathLocators = Array.from(rootNodePath).map((x) => getAliasedLocator(x))
497
+ do {
498
+ newNodes = nextNewNodes
499
+ nextNewNodes = new Set<WorkNode>()
500
+ for (const dep of newNodes) {
501
+ if (dep.locator === rootNode.locator || dep.isHoistBorder) continue
502
+ const decoupledDependency = decoupleGraphNode(rootNode, dep)
503
+ hoistNodeDependencies(
504
+ [],
505
+ Array.from(rootNodePathLocators),
506
+ aliasedRootNodePathLocators,
507
+ decoupledDependency,
508
+ nextNewNodes,
509
+ )
510
+ }
511
+ } while (nextNewNodes.size > 0)
512
+ return { anotherRoundNeeded, isGraphChanged }
513
+ }
514
+
515
+ const cloneTree = (tree: HoisterTreeNode, options: HoistToOptions): WorkNode => {
516
+ const treeCopy: WorkNode = {
517
+ name: tree.name,
518
+ references: new Set([tree.reference]),
519
+ locator: makeLocator(tree.identName, tree.reference),
520
+ ident: makeIdent(tree.identName, tree.reference),
521
+ dependencies: new Map(),
522
+ originalDependencies: new Map(),
523
+ hoistedDependencies: new Map(),
524
+ peerNames: new Set(tree.peerNames),
525
+ reasons: new Map(),
526
+ decoupled: true,
527
+ isHoistBorder: true,
528
+ hoistPriority: 0,
529
+ dependencyKind: HoisterDependencyKind.WORKSPACE,
530
+ hoistedFrom: new Map(),
531
+ hoistedTo: new Map(),
532
+ }
533
+ const seenNodes = new Map<HoisterTreeNode, WorkNode>([[tree, treeCopy]])
534
+ // Recursively copy a node and its dependencies into work nodes.
535
+ const addNode = (node: HoisterTreeNode, parentNode: WorkNode): void => {
536
+ const existing = seenNodes.get(node)
537
+ const isSeen = existing !== undefined
538
+ let workNode: WorkNode
539
+ if (!isSeen) {
540
+ const dependenciesNmHoistingLimits = options.hoistingLimits.get(parentNode.locator)
541
+ workNode = {
542
+ name: node.name,
543
+ references: new Set([node.reference]),
544
+ locator: makeLocator(node.identName, node.reference),
545
+ ident: makeIdent(node.identName, node.reference),
546
+ dependencies: new Map(),
547
+ originalDependencies: new Map(),
548
+ hoistedDependencies: new Map(),
549
+ peerNames: new Set(node.peerNames),
550
+ reasons: new Map(),
551
+ decoupled: true,
552
+ isHoistBorder: dependenciesNmHoistingLimits
553
+ ? dependenciesNmHoistingLimits.has(node.name)
554
+ : false,
555
+ hoistPriority: node.hoistPriority || 0,
556
+ dependencyKind: node.dependencyKind || HoisterDependencyKind.REGULAR,
557
+ hoistedFrom: new Map(),
558
+ hoistedTo: new Map(),
559
+ }
560
+ seenNodes.set(node, workNode)
561
+ } else {
562
+ workNode = existing
563
+ }
564
+ parentNode.dependencies.set(node.name, workNode)
565
+ parentNode.originalDependencies.set(node.name, workNode)
566
+ if (!isSeen) {
567
+ for (const dep of node.dependencies) addNode(dep, workNode)
568
+ } else {
569
+ const seenCoupledNodes = new Set<WorkNode>()
570
+ // Mark a work node and its dependencies as coupled (not decoupled).
571
+ const markNodeCoupled = (node: WorkNode): void => {
572
+ if (seenCoupledNodes.has(node)) return
573
+ seenCoupledNodes.add(node)
574
+ node.decoupled = false
575
+ for (const dep of node.dependencies.values()) {
576
+ if (!node.peerNames.has(dep.name)) markNodeCoupled(dep)
577
+ }
578
+ }
579
+ markNodeCoupled(workNode)
580
+ }
581
+ }
582
+ for (const dep of tree.dependencies) addNode(dep, treeCopy)
583
+ return treeCopy
584
+ }
585
+
586
+ /** Extract the ident name from a locator string. */
587
+ const getIdentName = (locator: string): string => locator.substring(0, locator.indexOf('@', 1))
588
+
589
+ /** Build a shrunk copy of the dependency tree for hoisting. */
590
+ const shrinkTree = (tree: WorkNode): ShrunkNode => {
591
+ const treeCopy: ShrunkNode = {
592
+ name: tree.name,
593
+ identName: getIdentName(tree.locator),
594
+ references: new Set(tree.references),
595
+ dependencies: new Set(),
596
+ }
597
+ const seenNodes = new Set<WorkNode>([tree])
598
+ // Recursively add a node to the shrunk tree, reusing parent on self-reference.
599
+ const addNode = (node: WorkNode, parentWorkNode: WorkNode, parentNode: ShrunkNode): void => {
600
+ const isSeen = seenNodes.has(node)
601
+ let resultNode: ShrunkNode
602
+ if (parentWorkNode === node) {
603
+ resultNode = parentNode
604
+ } else {
605
+ resultNode = {
606
+ name: node.name,
607
+ identName: getIdentName(node.locator),
608
+ references: node.references,
609
+ dependencies: new Set(),
610
+ }
611
+ }
612
+ parentNode.dependencies.add(resultNode)
613
+ if (!isSeen) {
614
+ seenNodes.add(node)
615
+ for (const dep of node.dependencies.values()) {
616
+ if (!node.peerNames.has(dep.name)) addNode(dep, node, resultNode)
617
+ }
618
+ seenNodes.delete(node)
619
+ }
620
+ }
621
+ for (const dep of tree.dependencies.values()) addNode(dep, tree, treeCopy)
622
+ return treeCopy
623
+ }
624
+
625
+ /** Build a preference map (dependents / peer dependents / hoist priority). */
626
+ const buildPreferenceMap = (rootNode: WorkNode): Map<string, PreferenceEntry> => {
627
+ const preferenceMap = new Map<string, PreferenceEntry>()
628
+ const seenNodes = new Set<WorkNode>([rootNode])
629
+ // Derive the preference map key for a node.
630
+ const getPreferenceKey = (node: WorkNode): string => `${node.name}@${node.ident}`
631
+ // Get or create the preference entry for a node.
632
+ const getOrCreatePreferenceEntry = (node: WorkNode): PreferenceEntry => {
633
+ const key = getPreferenceKey(node)
634
+ let entry = preferenceMap.get(key)
635
+ if (!entry) {
636
+ entry = { dependents: new Set(), peerDependents: new Set(), hoistPriority: 0 }
637
+ preferenceMap.set(key, entry)
638
+ }
639
+ return entry
640
+ }
641
+ // Register a dependent and recurse into the node's dependencies.
642
+ const addDependent = (dependent: WorkNode, node: WorkNode): void => {
643
+ const isSeen = !!seenNodes.has(node)
644
+ const entry = getOrCreatePreferenceEntry(node)
645
+ entry.dependents.add(dependent.ident)
646
+ if (!isSeen) {
647
+ seenNodes.add(node)
648
+ for (const dep of node.dependencies.values()) {
649
+ const depEntry = getOrCreatePreferenceEntry(dep)
650
+ depEntry.hoistPriority = Math.max(depEntry.hoistPriority, dep.hoistPriority)
651
+ if (node.peerNames.has(dep.name)) {
652
+ depEntry.peerDependents.add(node.ident)
653
+ } else {
654
+ addDependent(node, dep)
655
+ }
656
+ }
657
+ }
658
+ }
659
+ for (const dep of rootNode.dependencies.values())
660
+ if (!rootNode.peerNames.has(dep.name)) addDependent(rootNode, dep)
661
+ return preferenceMap
662
+ }
663
+
664
+ /** Check whether the node name is shadowed by an ancestor's different-ident dependency. */
665
+ function isNameShadowed(
666
+ node: WorkNode,
667
+ nodePath: WorkNode[],
668
+ shadowedNodes: Map<WorkNode, Set<string>>,
669
+ ): boolean {
670
+ for (let idx = nodePath.length - 1; idx >= 1; idx--) {
671
+ const parent = nodePath[idx]
672
+ const parentDep = parent.dependencies.get(node.name)
673
+ if (parentDep && parentDep.ident !== node.ident) {
674
+ const parentNode = nodePath[nodePath.length - 1]
675
+ let shadowedNames = shadowedNodes.get(parentNode)
676
+ if (!shadowedNames) {
677
+ shadowedNames = new Set<string>()
678
+ shadowedNodes.set(parentNode, shadowedNames)
679
+ }
680
+ shadowedNames.add(node.name)
681
+ return true
682
+ }
683
+ }
684
+ return false
685
+ }
686
+
687
+ /** Check peer-dependency satisfaction along the node path. */
688
+ function checkPeerDeps(
689
+ node: WorkNode,
690
+ rootNode: WorkNode,
691
+ nodePath: WorkNode[],
692
+ ): { dependsOn: Set<WorkNode> | null; arePeerDepsSatisfied: boolean } {
693
+ let dependsOn: Set<WorkNode> | null = new Set()
694
+ let arePeerDepsSatisfied = true
695
+ const checkList = new Set(node.peerNames)
696
+ for (let idx = nodePath.length - 1; idx >= 1; idx--) {
697
+ const parent = nodePath[idx]
698
+ for (const name of checkList) {
699
+ if (parent.peerNames.has(name) && parent.originalDependencies.has(name)) continue
700
+ const parentDepNode = parent.dependencies.get(name)
701
+ if (parentDepNode && rootNode.dependencies.get(name) !== parentDepNode) {
702
+ if (idx === nodePath.length - 1) {
703
+ if (dependsOn) dependsOn.add(parentDepNode)
704
+ } else {
705
+ dependsOn = null
706
+ arePeerDepsSatisfied = false
707
+ }
708
+ }
709
+ checkList.delete(name)
710
+ }
711
+ if (!arePeerDepsSatisfied) break
712
+ }
713
+ return { dependsOn, arePeerDepsSatisfied }
714
+ }