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,416 @@
1
+ import { existsSync } from 'node:fs'
2
+ import path from 'node:path'
3
+ import { logger } from '../../shared/logger.ts'
4
+ import { type Arch, NodePlatform } from '../../shared/platform.ts'
5
+ import { EntryKind, type FileSet, type FileStat } from '../../shared/types.ts'
6
+ import { isLibOrExe } from './unpack-detector.ts'
7
+ import { walkDirectory } from './walker.ts'
8
+
9
+ /**
10
+ * A prebuilt native-module replacement rule: replace the node_modules copy
11
+ * of `moduleName` with files from `<projectDir>/<from>` in the packed fileset,
12
+ * optionally renaming the module directory.
13
+ */
14
+ export interface PrebuiltModuleRule {
15
+ /** Module name as it appears in node_modules (may be scoped). */
16
+ moduleName: string
17
+ /** Source directory (relative to projectDir) holding the prebuilt files. */
18
+ from: string
19
+ /**
20
+ * Rename the module directory in the packed fileset (e.g. `@napi-rs/canvas-darwin-arm64`
21
+ * → `@napi-rs/canvas-<suffix>`); omit to keep the original name.
22
+ */
23
+ renameTo?: string
24
+ }
25
+
26
+ /**
27
+ * Resolver producing the replacement rules for a platform/arch. Returning
28
+ * undefined/null skips prebuilt replacement and node_modules copies are
29
+ * used as-is.
30
+ */
31
+ export type PrebuiltModuleRuleResolver = (
32
+ platform: NodePlatform,
33
+ arch: Arch,
34
+ ) => ReadonlyArray<PrebuiltModuleRule> | null | undefined
35
+
36
+ let prebuiltRuleResolver: PrebuiltModuleRuleResolver | null = null
37
+
38
+ /**
39
+ * Install the resolver used to derive prebuilt module replacement rules.
40
+ * Hosts inject their own rules through config.nativeModules.prebuiltRules.
41
+ */
42
+ export function setPrebuiltDirResolver(resolver: PrebuiltModuleRuleResolver | null): void {
43
+ prebuiltRuleResolver = typeof resolver === 'function' ? resolver : null
44
+ }
45
+
46
+ /** Compare module names independent of the host path separator. */
47
+ function normalizeModuleName(name: string): string {
48
+ return path.toNamespacedPath(name).split(path.sep).join('/')
49
+ }
50
+
51
+ export function derivePlatformFileSet(
52
+ fileSet: FileSet,
53
+ platform: NodePlatform,
54
+ arch: Arch,
55
+ projectDir: string,
56
+ ): FileSet {
57
+ const { files, metadata, src, destination } = fileSet
58
+
59
+ // Rules from the host-injected resolver; moduleName may end with "-*" to
60
+ // match a family of platform-variant modules (e.g. "@napi-rs/canvas-*").
61
+ const rules = (prebuiltRuleResolver?.(platform, arch) ?? []).filter((r) => r.from)
62
+ const exactRules = rules.filter((r) => !r.moduleName.endsWith('-*'))
63
+ const prefixRules = rules.filter((r) => r.moduleName.endsWith('-*'))
64
+ const prefixes = prefixRules.map((r) => ({
65
+ rule: r,
66
+ prefix: normalizeModuleName(r.moduleName.slice(0, -1)),
67
+ }))
68
+
69
+ // First pass: find module roots to replace.
70
+ const exactModuleRoots = new Map<string, PrebuiltModuleRule[]>()
71
+ let prefixMatch: {
72
+ rule: PrebuiltModuleRule
73
+ moduleRootDir: string
74
+ originalModuleName: string
75
+ originalSegment: string
76
+ } | null = null
77
+
78
+ const seenModuleNames = new Set<string>()
79
+
80
+ for (const file of files) {
81
+ const info = parseModuleInfo(file)
82
+ if (!info) continue
83
+ const normalizedModuleName = normalizeModuleName(info.moduleName)
84
+ seenModuleNames.add(normalizedModuleName)
85
+
86
+ const exact = exactRules.filter(
87
+ (r) => normalizeModuleName(r.moduleName) === normalizedModuleName,
88
+ )
89
+ if (exact.length > 0) {
90
+ const list = exactModuleRoots.get(info.moduleRootPath) ?? []
91
+ list.push(...exact)
92
+ exactModuleRoots.set(info.moduleRootPath, list)
93
+ continue
94
+ }
95
+ for (const { rule, prefix } of prefixes) {
96
+ if (normalizedModuleName.startsWith(prefix)) {
97
+ // Only the first platform variant encountered drives the rename.
98
+ if (!prefixMatch) {
99
+ const segments = normalizedModuleName.split('/')
100
+ prefixMatch = {
101
+ rule,
102
+ moduleRootDir: path.dirname(info.moduleRootPath),
103
+ originalModuleName: info.moduleName,
104
+ originalSegment: segments[segments.length - 1] ?? info.moduleName,
105
+ }
106
+ }
107
+ break
108
+ }
109
+ }
110
+ }
111
+
112
+ for (const rule of exactRules) {
113
+ const normalizedRuleName = normalizeModuleName(rule.moduleName)
114
+ if (
115
+ [...seenModuleNames].some(
116
+ (name) => name !== normalizedRuleName && name.startsWith(`${normalizedRuleName}-`),
117
+ )
118
+ ) {
119
+ logger.warn(
120
+ `prebuiltRules: rule "${rule.moduleName}" is exact, but platform variants of the same family exist in node_modules; consider "${rule.moduleName}-*"`,
121
+ )
122
+ }
123
+ }
124
+
125
+ const exactReplacements = new Map<
126
+ string,
127
+ {
128
+ entries: Array<{ rule: PrebuiltModuleRule; content: PrebuiltContent }>
129
+ replacedRelPaths: Set<string>
130
+ }
131
+ >()
132
+ for (const [moduleRootPath, ruleList] of exactModuleRoots) {
133
+ const entries: Array<{ rule: PrebuiltModuleRule; content: PrebuiltContent }> = []
134
+ const replacedRelPaths = new Set<string>()
135
+ for (const rule of ruleList) {
136
+ const content = collectPrebuiltContent(projectDir, rule)
137
+ if (!content) continue
138
+ entries.push({ rule, content })
139
+ for (const pf of content.files) {
140
+ replacedRelPaths.add(path.relative(content.prebuiltBase, pf))
141
+ }
142
+ }
143
+ if (entries.length > 0) exactReplacements.set(moduleRootPath, { entries, replacedRelPaths })
144
+ }
145
+
146
+ // Second pass: drop files belonging to replaced modules, keep the rest.
147
+ const newFiles: string[] = []
148
+ const newMetadata = new Map<string, FileStat>()
149
+ for (const file of files) {
150
+ const meta = metadata.get(file)
151
+ if (!meta) continue
152
+
153
+ const info = parseModuleInfo(file)
154
+ if (info) {
155
+ const replacement = exactReplacements.get(info.moduleRootPath)
156
+ if (replacement) {
157
+ const relPath = path.relative(info.moduleRootPath, file)
158
+ if (replacement.replacedRelPaths.has(relPath) || isLibOrExe(file)) continue
159
+ }
160
+ const normalizedModuleName = normalizeModuleName(info.moduleName)
161
+ let matchedPrefix = false
162
+ for (const { prefix } of prefixes) {
163
+ if (normalizedModuleName.startsWith(prefix)) {
164
+ matchedPrefix = true
165
+ break
166
+ }
167
+ }
168
+ if (matchedPrefix) continue
169
+ }
170
+
171
+ if (info && isExcludedPlatformPath(file, info.moduleRootPath, platform, info.moduleName)) {
172
+ continue
173
+ }
174
+
175
+ newFiles.push(file)
176
+ newMetadata.set(file, meta)
177
+ }
178
+
179
+ let adjustedSrc = src
180
+ let adjustedDestination = destination
181
+
182
+ // Emit prebuilt files for exact-name rules into each original module root.
183
+ for (const [moduleRootPath, { entries }] of exactReplacements) {
184
+ for (const { rule, content } of entries) {
185
+ emitPrebuiltContent({
186
+ platform,
187
+ content,
188
+ targetModuleRoot: moduleRootPath,
189
+ targetModuleName: rule.moduleName,
190
+ newFiles,
191
+ newMetadata,
192
+ })
193
+ }
194
+ }
195
+
196
+ // Emit prebuilt files for the platform-variant rule, renaming the module
197
+ // directory in the packed fileset.
198
+ if (prefixMatch) {
199
+ const { rule, moduleRootDir, originalModuleName, originalSegment } = prefixMatch
200
+ const renamedSegment = rule.renameTo ?? originalSegment
201
+ const renamedModuleName = rule.renameTo
202
+ ? originalModuleName.replace(originalSegment, rule.renameTo)
203
+ : originalModuleName
204
+ const targetRoot = path.join(moduleRootDir, renamedSegment)
205
+
206
+ const content = collectPrebuiltContent(projectDir, rule)
207
+ if (content) {
208
+ emitPrebuiltContent({
209
+ platform,
210
+ content,
211
+ targetModuleRoot: targetRoot,
212
+ targetModuleName: renamedModuleName,
213
+ newFiles,
214
+ newMetadata,
215
+ })
216
+
217
+ // When the whole fileset src was the renamed module (sub-fileset case),
218
+ // rebase src/destination onto the renamed directory.
219
+ if (newFiles.length > 0 && !newFiles.some((f) => f.startsWith(src))) {
220
+ adjustedSrc = targetRoot
221
+ adjustedDestination = destination.replace(originalSegment, renamedSegment)
222
+ }
223
+ }
224
+ }
225
+
226
+ newFiles.sort()
227
+ return {
228
+ src: adjustedSrc,
229
+ destination: adjustedDestination,
230
+ files: newFiles,
231
+ metadata: newMetadata,
232
+ }
233
+ }
234
+
235
+ /** Walked content of one prebuilt source directory. */
236
+ interface PrebuiltContent {
237
+ prebuiltBase: string
238
+ files: string[]
239
+ metadata: Map<string, FileStat>
240
+ }
241
+
242
+ function collectPrebuiltContent(
243
+ projectDir: string,
244
+ rule: PrebuiltModuleRule,
245
+ ): PrebuiltContent | null {
246
+ const prebuiltBase = path.join(projectDir, rule.from)
247
+ if (!existsSync(prebuiltBase)) return null
248
+ const files: string[] = []
249
+ const metadata = new Map<string, FileStat>()
250
+ walkDirectory(prebuiltBase, files, metadata, prebuiltBase)
251
+ return { prebuiltBase, files, metadata }
252
+ }
253
+
254
+ /** Copy prebuilt directory files into a target module root with override metadata. */
255
+ function emitPrebuiltContent({
256
+ platform,
257
+ content,
258
+ targetModuleRoot,
259
+ targetModuleName,
260
+ newFiles,
261
+ newMetadata,
262
+ }: {
263
+ platform: NodePlatform
264
+ content: PrebuiltContent
265
+ targetModuleRoot: string
266
+ targetModuleName: string
267
+ newFiles: string[]
268
+ newMetadata: Map<string, FileStat>
269
+ }): void {
270
+ for (const pf of content.files) {
271
+ const pm = content.metadata.get(pf)
272
+ if (!pm) continue
273
+
274
+ const relFromPrebuilt = path.relative(content.prebuiltBase, pf)
275
+ const targetPath = path.join(targetModuleRoot, relFromPrebuilt)
276
+
277
+ const overrideMeta: FileStat = {
278
+ ...pm,
279
+ realFilePath: pf,
280
+ moduleRootPath: targetModuleRoot,
281
+ moduleName: targetModuleName,
282
+ }
283
+ if (pm.type === EntryKind.FILE && pf.endsWith('.node')) {
284
+ if (platform === NodePlatform.DARWIN || platform === NodePlatform.LINUX) {
285
+ overrideMeta.mode = 0o100755
286
+ }
287
+ }
288
+ newFiles.push(targetPath)
289
+ newMetadata.set(targetPath, overrideMeta)
290
+ }
291
+ }
292
+
293
+ /** Parse a file path inside node_modules into module name / subpath info. */
294
+ export function parseModuleInfo(
295
+ filePath: string,
296
+ ): { moduleName: string; moduleRootPath: string } | null {
297
+ const nmIdx = filePath.indexOf(`${path.sep}node_modules${path.sep}`)
298
+ if (nmIdx !== -1) {
299
+ const after = filePath.slice(nmIdx + path.sep.length + 'node_modules'.length + path.sep.length)
300
+ const parts = after.split(path.sep)
301
+ if (parts[0].startsWith('@') && parts.length >= 2) {
302
+ const moduleName = parts[0] + path.sep + parts[1]
303
+ const moduleRootPath = filePath.slice(
304
+ 0,
305
+ nmIdx + path.sep.length + 'node_modules'.length + path.sep.length + moduleName.length,
306
+ )
307
+ return { moduleName, moduleRootPath }
308
+ }
309
+ if (parts.length >= 1) {
310
+ const moduleName = parts[0]
311
+ const moduleRootPath = filePath.slice(
312
+ 0,
313
+ nmIdx + path.sep.length + 'node_modules'.length + path.sep.length + moduleName.length,
314
+ )
315
+ return { moduleName, moduleRootPath }
316
+ }
317
+ }
318
+
319
+ if (filePath.startsWith(`node_modules${path.sep}`) || filePath.startsWith('node_modules/')) {
320
+ const after = filePath.slice('node_modules/'.length)
321
+ const parts = after.split(/[/\\]/)
322
+ if (parts[0].startsWith('@') && parts.length >= 2) {
323
+ const moduleName = `${parts[0]}/${parts[1]}`
324
+ const moduleRootPath = `node_modules/${moduleName}`
325
+ return { moduleName, moduleRootPath }
326
+ }
327
+ if (parts.length >= 1) {
328
+ const moduleName = parts[0]
329
+ const moduleRootPath = `node_modules/${moduleName}`
330
+ return { moduleName, moduleRootPath }
331
+ }
332
+ }
333
+
334
+ return null
335
+ }
336
+
337
+ /**
338
+ * Directory segments that mark a path as platform-specific, per platform.
339
+ * Used to drop node_modules entries that only apply to other platforms.
340
+ */
341
+ const PLATFORM_DIR_SEGMENTS: Record<NodePlatform, string[]> = {
342
+ darwin: ['mac', 'darwin'],
343
+ linux: ['linux'],
344
+ win32: ['win', 'win32'],
345
+ }
346
+
347
+ /** Module name prefixes that mark a module as platform-specific, per platform. */
348
+ const PLATFORM_MODULE_NAMES: Record<NodePlatform, RegExp[]> = {
349
+ darwin: [/^mac-/, /^darwin-/],
350
+ linux: [/^linux-/],
351
+ win32: [/^win-/, /^windows-/],
352
+ }
353
+
354
+ /** Path segment prefixes that mark a part as platform-specific, per platform. */
355
+ const PLATFORM_PREFIX_PATTERNS: Record<NodePlatform, RegExp> = {
356
+ darwin: /^darwin-/,
357
+ linux: /^linux-/,
358
+ win32: /^win32-/,
359
+ }
360
+
361
+ const PLATFORM_TRIPLE_RE = /-(win32|darwin|linux)-(x64|arm64|ia32|arm)(?:-|$)/
362
+
363
+ const TRIPLE_PLATFORMS: Record<string, NodePlatform> = {
364
+ win32: NodePlatform.WIN32,
365
+ darwin: NodePlatform.DARWIN,
366
+ linux: NodePlatform.LINUX,
367
+ }
368
+
369
+ export function isExcludedPlatformPath(
370
+ filePath: string,
371
+ moduleRootPath: string,
372
+ targetPlatform: NodePlatform,
373
+ moduleName: string,
374
+ ): boolean {
375
+ const relFromModuleRoot = path.relative(moduleRootPath, filePath)
376
+ if (!relFromModuleRoot) return false
377
+
378
+ const parts = relFromModuleRoot.split(/[/\\]/)
379
+
380
+ for (const [platform, segments] of Object.entries(PLATFORM_DIR_SEGMENTS)) {
381
+ if (platform === targetPlatform) continue
382
+ for (const segment of segments) {
383
+ if (parts.includes(segment)) return true
384
+ }
385
+ }
386
+
387
+ if (moduleName) {
388
+ const segments = moduleName.split(path.sep)
389
+ const shortName = segments.length > 1 ? (segments[segments.length - 1] as string) : moduleName
390
+ const triple = PLATFORM_TRIPLE_RE.exec(shortName)
391
+ if (triple) {
392
+ const variantPlatform = TRIPLE_PLATFORMS[triple[1] as string]
393
+ if (variantPlatform !== undefined && variantPlatform !== targetPlatform) return true
394
+ }
395
+ }
396
+
397
+ for (const [platform, pattern] of Object.entries(PLATFORM_PREFIX_PATTERNS)) {
398
+ if (platform === targetPlatform) continue
399
+ for (const part of parts) {
400
+ if (pattern.test(part)) return true
401
+ }
402
+ }
403
+
404
+ if (moduleName) {
405
+ const segments = moduleName.split(path.sep)
406
+ const shortName = segments.length > 1 ? (segments[segments.length - 1] as string) : moduleName
407
+ for (const [platform, patterns] of Object.entries(PLATFORM_MODULE_NAMES)) {
408
+ if (platform === targetPlatform) continue
409
+ for (const pattern of patterns) {
410
+ if (pattern.test(shortName)) return true
411
+ }
412
+ }
413
+ }
414
+
415
+ return false
416
+ }
@@ -0,0 +1,154 @@
1
+ import { readFile } from 'node:fs/promises'
2
+ import path from 'node:path'
3
+ import type { JsonObject } from '../../shared/types.ts'
4
+
5
+ const NODE_MODULES_PATTERN = `${path.sep}node_modules${path.sep}`
6
+
7
+ const ignoredPackageMetadataProperties = new Set([
8
+ 'dist',
9
+ 'gitHead',
10
+ 'build',
11
+ 'jspm',
12
+ 'ava',
13
+ 'xo',
14
+ 'nyc',
15
+ 'eslintConfig',
16
+ 'contributors',
17
+ 'bundleDependencies',
18
+ 'tags',
19
+ ])
20
+
21
+ const PROTECTED_KEYS = new Set(['__proto__', 'prototype', 'constructor'])
22
+
23
+ export function createTransformer(
24
+ srcDir: string,
25
+ extraMetadata: JsonObject | undefined,
26
+ extraTransformer?: (file: string) => Promise<string | null> | string | null,
27
+ ): (file: string) => Promise<string | null> | string | null {
28
+ const mainPackageJson = path.join(srcDir, 'package.json')
29
+ const isRemovePackageScripts = true
30
+ const isRemovePackageKeywords = true
31
+ const packageJson = `${path.sep}package.json`
32
+
33
+ // Transform the contents of a single file on read; return the modified
34
+ // content for touched files and the raw content for untouched files.
35
+ return (file) => {
36
+ if (file === mainPackageJson) {
37
+ return modifyMainPackageJson(
38
+ file,
39
+ extraMetadata,
40
+ isRemovePackageScripts,
41
+ isRemovePackageKeywords,
42
+ )
43
+ }
44
+ if (file.endsWith(packageJson) && file.includes(NODE_MODULES_PATTERN)) {
45
+ return readFile(file, 'utf-8')
46
+ .then((it) =>
47
+ cleanupPackageJson(JSON.parse(it) as JsonObject, {
48
+ isMain: false,
49
+ isRemovePackageScripts,
50
+ isRemovePackageKeywords,
51
+ }),
52
+ )
53
+ .catch((e: unknown) => {
54
+ console.warn(e)
55
+ return null
56
+ })
57
+ }
58
+ if (extraTransformer != null) {
59
+ return extraTransformer(file)
60
+ }
61
+ return null
62
+ }
63
+ }
64
+
65
+ function cleanupPackageJson(
66
+ data: JsonObject,
67
+ options: { isMain: boolean; isRemovePackageScripts: boolean; isRemovePackageKeywords: boolean },
68
+ ): string | null {
69
+ const deps = data.dependencies
70
+ const isRemoveBabel =
71
+ deps != null &&
72
+ typeof deps === 'object' &&
73
+ !Object.getOwnPropertyNames(deps).some((it) => it.startsWith('babel'))
74
+ let changed = false
75
+ for (const prop of Object.getOwnPropertyNames(data)) {
76
+ if (
77
+ prop[0] === '_' ||
78
+ ignoredPackageMetadataProperties.has(prop) ||
79
+ (options.isRemovePackageScripts && prop === 'scripts') ||
80
+ (options.isRemovePackageKeywords && prop === 'keywords') ||
81
+ (options.isMain && prop === 'devDependencies') ||
82
+ (!options.isMain && prop === 'bugs') ||
83
+ (isRemoveBabel && prop === 'babel')
84
+ ) {
85
+ delete data[prop]
86
+ changed = true
87
+ }
88
+ }
89
+ if (changed) {
90
+ return JSON.stringify(data, null, 2)
91
+ }
92
+ return null
93
+ }
94
+
95
+ async function modifyMainPackageJson(
96
+ file: string,
97
+ extraMetadata: JsonObject | undefined,
98
+ isRemovePackageScripts: boolean,
99
+ isRemovePackageKeywords: boolean,
100
+ ): Promise<string | null> {
101
+ const mainPackageData = JSON.parse(await readFile(file, 'utf-8')) as JsonObject
102
+ if (extraMetadata != null) {
103
+ deepAssign(mainPackageData, extraMetadata)
104
+ }
105
+ const serializedDataIfChanged = cleanupPackageJson(mainPackageData, {
106
+ isMain: true,
107
+ isRemovePackageScripts,
108
+ isRemovePackageKeywords,
109
+ })
110
+ if (serializedDataIfChanged != null) {
111
+ return serializedDataIfChanged
112
+ }
113
+ return null
114
+ }
115
+
116
+ /** Check that a key is safe for assignment that avoids prototype pollution. */
117
+ function isValidKey(key: string): boolean {
118
+ return !PROTECTED_KEYS.has(key)
119
+ }
120
+
121
+ /** Check whether a value is a non-array object (or a function). */
122
+ function isObject(x: unknown): boolean {
123
+ if (Array.isArray(x)) return false
124
+ const type = typeof x
125
+ return type === 'object' || type === 'function'
126
+ }
127
+
128
+ function assignKey(target: JsonObject, from: JsonObject, key: string): void {
129
+ const value = from[key]
130
+ if (value === undefined) return
131
+ const prevValue = target[key]
132
+ if (prevValue == null || value == null || !isObject(prevValue) || !isObject(value)) {
133
+ if (Array.isArray(prevValue) && Array.isArray(value)) {
134
+ target[key] = Array.from(new Set(prevValue.concat(value)))
135
+ } else {
136
+ target[key] = value
137
+ }
138
+ } else {
139
+ deepAssign(prevValue as JsonObject, value as JsonObject)
140
+ }
141
+ }
142
+
143
+ function deepAssign(target: JsonObject, ...objects: (JsonObject | undefined)[]): JsonObject {
144
+ for (const o of objects) {
145
+ if (o != null) {
146
+ for (const key of Object.getOwnPropertyNames(o)) {
147
+ if (isValidKey(key)) {
148
+ assignKey(target, o, key)
149
+ }
150
+ }
151
+ }
152
+ }
153
+ return target
154
+ }
@@ -0,0 +1,38 @@
1
+ import type { FileStat } from '../../shared/types.ts'
2
+
3
+ /**
4
+ * Check whether a file is a native binary library or executable that
5
+ * should be unpacked from the asar archive by default.
6
+ */
7
+ export function isLibOrExe(file: string): boolean {
8
+ return (
9
+ file.endsWith('.dll') ||
10
+ file.endsWith('.exe') ||
11
+ file.endsWith('.dylib') ||
12
+ file.endsWith('.so') ||
13
+ file.endsWith('.node')
14
+ )
15
+ }
16
+
17
+ export function detectUnpackedDirs(
18
+ fileSet: { files: string[]; metadata: Map<string, FileStat> },
19
+ autoUnpackDirs: Set<string>,
20
+ ): void {
21
+ const metadata = fileSet.metadata
22
+ for (let i = 0, n = fileSet.files.length; i < n; i++) {
23
+ const file = fileSet.files[i]
24
+ const stat = metadata.get(file)
25
+ if (!stat?.moduleRootPath || autoUnpackDirs.has(stat.moduleRootPath)) continue
26
+ if (stat.type !== 'file') continue
27
+
28
+ let shouldUnpack = false
29
+ const moduleName = stat.moduleName
30
+
31
+ if (moduleName === 'ffprobe-static' || moduleName === 'ffmpeg-static' || isLibOrExe(file)) {
32
+ shouldUnpack = true
33
+ }
34
+
35
+ if (!shouldUnpack) continue
36
+ autoUnpackDirs.add(stat.moduleRootPath)
37
+ }
38
+ }