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,244 @@
1
+ /**
2
+ * NSIS toolchain management: download, cache, script header assembly, makensis execution.
3
+ *
4
+ * Third-party notice: NSIS binaries come from electron-userland/electron-builder-binaries
5
+ * (MIT licensed). The nsis templates under templates/win/nsis originate from
6
+ * electron-builder's app-builder-lib and keep their upstream license headers.
7
+ */
8
+ import { spawnSync } from 'node:child_process'
9
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
10
+ import { cp, readFile, rm, writeFile } from 'node:fs/promises'
11
+ import os from 'node:os'
12
+ import path from 'node:path'
13
+ import { load as yamlLoad } from 'js-yaml'
14
+ import type { NormalizedConfig } from '../../config/index.ts'
15
+ import { logger } from '../../shared/logger.ts'
16
+ import { getTemplatesDir } from '../common/template.ts'
17
+ import { downloadAndExtract7z } from './toolchain.ts'
18
+
19
+ export function getNsisTemplatesDir(): string {
20
+ return getTemplatesDir('win', 'nsis')
21
+ }
22
+
23
+ export async function prepareNsisToolchain(projectDir: string, winConfig: NormalizedConfig['win']) {
24
+ const NSIS_VERSION = winConfig.nsisVersion
25
+ const NSIS_RESOURCES_VERSION = winConfig.nsisResourcesVersion
26
+
27
+ const cacheDir = getNsisCacheDir(projectDir, winConfig.nsisCacheDir)
28
+
29
+ const nsisDir = await ensureNsisComponent({
30
+ cacheDir,
31
+ name: 'nsis',
32
+ version: NSIS_VERSION,
33
+ validate: (dir) => existsSync(path.join(dir, 'Bin', 'makensis.exe')),
34
+ sha512:
35
+ 'VKMiizYdmNdJOWpRGz4trl4lD++BvYP2irAXpMilheUP0pc93iKlWAoP843Vlraj8YG19CVn0j+dCo/hURz9+Q==',
36
+ })
37
+ logger.info(` NSIS: ${nsisDir}`)
38
+
39
+ const nsisResourcesDir = await ensureNsisComponent({
40
+ cacheDir,
41
+ name: 'nsis-resources',
42
+ version: NSIS_RESOURCES_VERSION,
43
+ validate: (dir) => existsSync(path.join(dir, 'plugins')),
44
+ sha512:
45
+ 'Dqd6g+2buwwvoG1Vyf6BHR1b+25QMmPcwZx40atOT57gH27rkjOei1L0JTldxZu4NFoEmW4kJgZ3DlSWVON3+Q==',
46
+ })
47
+
48
+ return { nsisDir, nsisResourcesDir }
49
+ }
50
+
51
+ /**
52
+ * Assemble the NSIS script header: include paths, plugin directories,
53
+ * and localized messages.
54
+ */
55
+ export async function buildNsisScriptHeader({
56
+ nsisIncludeDir,
57
+ nsisPluginsDir,
58
+ pluginArch,
59
+ nsisTemplatesDir,
60
+ stagingDir,
61
+ }: {
62
+ nsisIncludeDir: string
63
+ nsisPluginsDir: string
64
+ pluginArch: string
65
+ nsisTemplatesDir: string
66
+ stagingDir: string
67
+ }): Promise<string> {
68
+ const headerTemplatePath = getTemplatesDir('win', 'nsis-header.nsh')
69
+ let header = await readFile(headerTemplatePath, 'utf8')
70
+
71
+ const stdUtilsNshPath = path.join(nsisIncludeDir, 'StdUtils.nsh').replace(/\//g, '\\')
72
+ const replacements: Record<string, string> = {
73
+ NSIS_STDLIBS_NSH_PATH: stdUtilsNshPath,
74
+ NSIS_INCLUDE_DIR: nsisIncludeDir.replace(/\//g, '\\'),
75
+ NSIS_TEMPLATES_DIR: nsisTemplatesDir.replace(/\//g, '\\'),
76
+ NSIS_PLUGIN_ARCH: pluginArch,
77
+ NSIS_PLUGINS_DIR: nsisPluginsDir.replace(/\//g, '\\'),
78
+ }
79
+
80
+ for (const [key, value] of Object.entries(replacements)) {
81
+ header = header.replaceAll(`\${${key}}`, () => value)
82
+ }
83
+
84
+ const messagesNshPath = path.join(stagingDir, 'messages.nsh')
85
+ await generateMessagesNsh(nsisTemplatesDir, messagesNshPath)
86
+ header = header.replaceAll(`\${NSIS_MESSAGES_NSH}`, () => messagesNshPath.replace(/\//g, '\\'))
87
+
88
+ return header
89
+ }
90
+
91
+ export async function executeMakensis({
92
+ nsisDir,
93
+ defines,
94
+ commands,
95
+ script,
96
+ cwd,
97
+ }: {
98
+ nsisDir: string
99
+ defines: Record<string, string | null>
100
+ commands: Record<string, unknown>
101
+ script: string
102
+ cwd: string
103
+ }): Promise<void> {
104
+ const makensisPath = path.join(nsisDir, 'Bin', 'makensis.exe')
105
+ if (!existsSync(makensisPath)) {
106
+ throw new Error(`makensis.exe not found: ${makensisPath}`)
107
+ }
108
+
109
+ const tmpScriptPath = path.join(cwd, `_cross_packer_installer_${Date.now()}.nsi`)
110
+ await writeFile(tmpScriptPath, script, 'utf8')
111
+
112
+ const args = ['-WX', '-InputCHARSET', 'UTF8']
113
+
114
+ for (const [name, value] of Object.entries(defines)) {
115
+ if (value == null) {
116
+ args.push(`-D${name}`)
117
+ } else {
118
+ args.push(`-D${name}=${value}`)
119
+ }
120
+ }
121
+
122
+ for (const [name, value] of Object.entries(commands)) {
123
+ if (Array.isArray(value)) {
124
+ for (const c of value) {
125
+ args.push(`-X${name} ${c}`)
126
+ }
127
+ } else {
128
+ args.push(`-X${name} ${value}`)
129
+ }
130
+ }
131
+
132
+ args.push(tmpScriptPath)
133
+
134
+ const child = spawnSync(makensisPath, args, {
135
+ encoding: 'utf8',
136
+ stdio: ['pipe', 'pipe', 'pipe'],
137
+ windowsHide: true,
138
+ env: { ...process.env, NSISDIR: nsisDir },
139
+ cwd,
140
+ })
141
+
142
+ if (child.status !== 0) {
143
+ const stderr = child.stderr || ''
144
+ const stdout = child.stdout || ''
145
+ throw new Error(`makensis compilation failed (exit ${child.status}):\n${stderr}\n${stdout}`)
146
+ }
147
+
148
+ await rm(tmpScriptPath, { force: true })
149
+ }
150
+
151
+ /**
152
+ * Ensure a NSIS toolchain component is available in the local cache:
153
+ * reuse a completed extraction, copy from an electron-builder cache, or
154
+ * download from electron-builder-binaries.
155
+ */
156
+ async function ensureNsisComponent({
157
+ cacheDir,
158
+ name,
159
+ version,
160
+ validate,
161
+ sha512,
162
+ }: {
163
+ cacheDir: string
164
+ name: string
165
+ version: string
166
+ validate: (dir: string) => boolean
167
+ sha512: string
168
+ }): Promise<string> {
169
+ const localDir = path.join(cacheDir, `${name}-${version}`)
170
+ const completeMarker = `${localDir}.complete`
171
+
172
+ if (existsSync(completeMarker) && validate(localDir)) {
173
+ return localDir
174
+ }
175
+
176
+ const builderDir = path.join(
177
+ getElectronBuilderCacheDir(),
178
+ 'nsis',
179
+ `${name}-${version}-${name}-${version}`,
180
+ )
181
+ if (validate(builderDir)) {
182
+ logger.info(` Copying ${name} from electron-builder cache ...`)
183
+ mkdirSync(localDir, { recursive: true })
184
+ await cp(builderDir, localDir, { recursive: true })
185
+ writeFileSync(completeMarker, '')
186
+ return localDir
187
+ }
188
+
189
+ logger.info(` Downloading ${name} ...`)
190
+ mkdirSync(cacheDir, { recursive: true })
191
+ await downloadAndExtract7z({
192
+ url: `https://github.com/electron-userland/electron-builder-binaries/releases/download/${name}-${version}/${name}-${version}.7z`,
193
+ sha512,
194
+ name: `${name}-${version}`,
195
+ cacheDir,
196
+ targetDir: localDir,
197
+ validateExtracted: validate,
198
+ })
199
+ logger.info(` ${name} download complete`)
200
+ return localDir
201
+ }
202
+
203
+ function getNsisCacheDir(projectDir: string, nsisCacheDir?: string): string {
204
+ return path.join(projectDir, nsisCacheDir || '.nsis-cache')
205
+ }
206
+
207
+ function getElectronBuilderCacheDir(): string {
208
+ const env = process.env.ELECTRON_BUILDER_CACHE?.trim()
209
+ if (env) return env
210
+
211
+ const localAppData = process.env.LOCALAPPDATA?.trim()
212
+ if (localAppData) {
213
+ return path.join(localAppData, 'electron-builder', 'Cache')
214
+ }
215
+ return path.join(os.tmpdir(), 'electron-builder-cache')
216
+ }
217
+
218
+ async function generateMessagesNsh(nsisTemplatesDir: string, outputPath: string): Promise<void> {
219
+ const messagesYmlPath = path.join(nsisTemplatesDir, 'messages.yml')
220
+ if (!existsSync(messagesYmlPath)) {
221
+ await writeFile(outputPath, '', 'utf8')
222
+ return
223
+ }
224
+
225
+ // Localized message strings keyed by message id. yaml.load returns unknown;
226
+ // the shape is asserted once at this boundary.
227
+ const messagesData = yamlLoad(await readFile(messagesYmlPath, 'utf8')) as Record<
228
+ string,
229
+ { en?: string; zh_CN?: string }
230
+ >
231
+ const LCID_EN_US = 1033
232
+ const LCID_ZH_CN = 2052
233
+
234
+ const lines: string[] = []
235
+ for (const [messageId, translations] of Object.entries(messagesData)) {
236
+ const enValue = (translations.en || '').replace(/\n/g, '$\\r$\\n')
237
+ const zhRaw = translations.zh_CN || translations.en || ''
238
+ const zhValue = zhRaw.replace(/\n/g, '$\\r$\\n')
239
+ lines.push(`LangString ${messageId} ${LCID_EN_US} "${enValue}"`)
240
+ lines.push(`LangString ${messageId} ${LCID_ZH_CN} "${zhValue}"`)
241
+ }
242
+
243
+ await writeFile(outputPath, `${lines.join('\n')}\n`, 'utf8')
244
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Windows PE resource editing (icon + version info) via resedit.
3
+ */
4
+ import { readFile, writeFile } from 'node:fs/promises'
5
+ import path from 'node:path'
6
+ import type { NormalizedConfig } from '../../config/index.ts'
7
+ import { getBuildVersion } from '../../update/version.ts'
8
+
9
+ export async function editExeResources(
10
+ exePath: string,
11
+ { iconPath, config }: { iconPath: string | null; config: NormalizedConfig },
12
+ ): Promise<void> {
13
+ const { NtExecutable, NtExecutableResource, Resource, Data } = await import('resedit')
14
+
15
+ const buffer = await readFile(exePath)
16
+ const executable = NtExecutable.from(buffer)
17
+ const res = NtExecutableResource.from(executable)
18
+
19
+ const viList = Resource.VersionInfo.fromEntries(res.entries)
20
+ const vi = viList.length > 0 ? viList[0] : Resource.VersionInfo.createEmpty()
21
+
22
+ const languages = vi.getAllLanguagesForStringValues()
23
+ const lang = languages.length > 0 ? languages[0] : { lang: 0x0409, codepage: 1200 }
24
+
25
+ const numericVersion = getBuildVersion(config.version, config.buildNumber)
26
+ const displayVersion = config.version
27
+
28
+ const versionStrings: Record<string, string> = {
29
+ FileDescription: config.productName,
30
+ ProductName: config.productName,
31
+ LegalCopyright: config.copyright,
32
+ FileVersion: displayVersion,
33
+ ProductVersion: displayVersion,
34
+ }
35
+ if (config.author) {
36
+ versionStrings.CompanyName = config.author
37
+ }
38
+ const internalName = path.basename(exePath, '.exe')
39
+ versionStrings.InternalName = internalName
40
+ versionStrings.OriginalFilename = ''
41
+
42
+ vi.setStringValues(lang, versionStrings)
43
+ vi.setFileVersion(numericVersion)
44
+ vi.setProductVersion(numericVersion)
45
+ vi.outputToResourceEntries(res.entries)
46
+
47
+ if (iconPath) {
48
+ const iconBuf = await readFile(iconPath)
49
+ const iconFile = Data.IconFile.from(iconBuf)
50
+ Resource.IconGroupEntry.replaceIconsForResource(
51
+ res.entries,
52
+ 1,
53
+ lang.lang,
54
+ iconFile.icons.map((i) => i.data),
55
+ )
56
+ }
57
+
58
+ res.outputResource(executable)
59
+ await writeFile(exePath, Buffer.from(executable.generate()))
60
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Download, sha512-verify, and extract 7z toolchain archives
3
+ * (NSIS, nsis-resources).
4
+ */
5
+ import { spawnSync } from 'node:child_process'
6
+ import { existsSync, mkdirSync, renameSync, writeFileSync } from 'node:fs'
7
+ import path from 'node:path'
8
+ import { get7zaPath } from '../../archive/seven-za.ts'
9
+ import { downloadFile } from '../../electron/download.ts'
10
+ import { hashFile } from '../../shared/hash.ts'
11
+ import { logger } from '../../shared/logger.ts'
12
+
13
+ export async function downloadAndExtract7z({
14
+ url,
15
+ sha512,
16
+ name,
17
+ cacheDir,
18
+ targetDir,
19
+ validateExtracted,
20
+ }: {
21
+ url: string
22
+ sha512: string
23
+ name: string
24
+ cacheDir: string
25
+ targetDir: string
26
+ validateExtracted: (dir: string) => boolean
27
+ }): Promise<void> {
28
+ const completeMarker = `${targetDir}.complete`
29
+
30
+ if (existsSync(completeMarker) && validateExtracted(targetDir)) {
31
+ return
32
+ }
33
+
34
+ const archivePath = path.join(cacheDir, `${name}.7z`)
35
+ if (!existsSync(archivePath)) {
36
+ const tmpPath = `${archivePath}.downloading`
37
+ logger.info(` Downloading ${name} from ${url} ...`)
38
+ await downloadFile(url, tmpPath)
39
+ renameSync(tmpPath, archivePath)
40
+ }
41
+
42
+ const hash = hashFile(archivePath, 'sha512', 'base64')
43
+ if (hash !== sha512) {
44
+ throw new Error(`Checksum mismatch for ${name}: expected ${sha512}, got ${hash}`)
45
+ }
46
+
47
+ mkdirSync(targetDir, { recursive: true })
48
+ const sevenZipPath = get7zaPath()
49
+ const result = spawnSync(sevenZipPath, ['x', '-bd', archivePath, `-o${targetDir}`, '-y'], {
50
+ stdio: 'pipe',
51
+ windowsHide: true,
52
+ })
53
+ if (result.status !== 0) {
54
+ const stderr = result.stderr?.toString() || ''
55
+ throw new Error(`7z extraction failed for ${name} (exit ${result.status}): ${stderr}`)
56
+ }
57
+
58
+ if (!validateExtracted(targetDir)) {
59
+ throw new Error(`Extraction of ${name} did not produce expected files in ${targetDir}`)
60
+ }
61
+
62
+ writeFileSync(completeMarker, '')
63
+ }