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.
- package/LICENSE +661 -0
- package/README.md +151 -0
- package/README.zh-CN.md +151 -0
- package/dist/archive/ar.d.ts +12 -0
- package/dist/archive/seven-za.d.ts +32 -0
- package/dist/archive/tar.d.ts +30 -0
- package/dist/archive/zip-reader.d.ts +17 -0
- package/dist/asar/dependency/collect.d.ts +2 -0
- package/dist/asar/dependency/detector.d.ts +8 -0
- package/dist/asar/dependency/hoister.d.ts +27 -0
- package/dist/asar/dependency/utils.d.ts +59 -0
- package/dist/asar/fileset/matcher.d.ts +12 -0
- package/dist/asar/fileset/platform-fileset.d.ts +36 -0
- package/dist/asar/fileset/transformer.d.ts +2 -0
- package/dist/asar/fileset/unpack-detector.d.ts +10 -0
- package/dist/asar/fileset/walker.d.ts +14 -0
- package/dist/asar/pack.d.ts +23 -0
- package/dist/asar/packer/app-collector.d.ts +2 -0
- package/dist/asar/packer/collector.d.ts +18 -0
- package/dist/asar/packer/concurrency.d.ts +1 -0
- package/dist/asar/packer/integrity.d.ts +8 -0
- package/dist/asar/packer/nm-collector.d.ts +7 -0
- package/dist/asar/packer/stream-builder.d.ts +18 -0
- package/dist/cli.d.ts +2 -0
- package/dist/config/index.d.ts +97 -0
- package/dist/electron/dist-zip.d.ts +18 -0
- package/dist/electron/download.d.ts +21 -0
- package/dist/index.d.ts +11 -0
- package/dist/packer/common/app-update-yml.d.ts +5 -0
- package/dist/packer/common/extra-resources.d.ts +39 -0
- package/dist/packer/common/staging.d.ts +3 -0
- package/dist/packer/common/template.d.ts +2 -0
- package/dist/packer/linux/linux.d.ts +2 -0
- package/dist/packer/mac/mac.d.ts +23 -0
- package/dist/packer/win/nsis.d.ts +24 -0
- package/dist/packer/win/resedit.d.ts +5 -0
- package/dist/packer/win/toolchain.d.ts +8 -0
- package/dist/packer/win/win.d.ts +12 -0
- package/dist/pipeline/pipeline.d.ts +41 -0
- package/dist/pipeline/targets.d.ts +28 -0
- package/dist/shared/hash.d.ts +2 -0
- package/dist/shared/logger.d.ts +39 -0
- package/dist/shared/platform.d.ts +26 -0
- package/dist/shared/types.d.ts +68 -0
- package/dist/update/blockmap.d.ts +13 -0
- package/dist/update/metadata.d.ts +20 -0
- package/dist/update/version.d.ts +7 -0
- package/package.json +71 -0
- package/src/archive/ar.ts +41 -0
- package/src/archive/seven-za.ts +160 -0
- package/src/archive/tar.ts +142 -0
- package/src/archive/zip-reader.ts +61 -0
- package/src/asar/dependency/collect.ts +437 -0
- package/src/asar/dependency/detector.ts +73 -0
- package/src/asar/dependency/hoister.ts +714 -0
- package/src/asar/dependency/utils.ts +225 -0
- package/src/asar/fileset/matcher.ts +101 -0
- package/src/asar/fileset/platform-fileset.ts +416 -0
- package/src/asar/fileset/transformer.ts +154 -0
- package/src/asar/fileset/unpack-detector.ts +38 -0
- package/src/asar/fileset/walker.ts +115 -0
- package/src/asar/pack.ts +186 -0
- package/src/asar/packer/app-collector.ts +74 -0
- package/src/asar/packer/collector.ts +134 -0
- package/src/asar/packer/concurrency.ts +24 -0
- package/src/asar/packer/integrity.ts +53 -0
- package/src/asar/packer/nm-collector.ts +194 -0
- package/src/asar/packer/stream-builder.ts +723 -0
- package/src/cli.ts +182 -0
- package/src/config/index.ts +365 -0
- package/src/electron/dist-zip.ts +65 -0
- package/src/electron/download.ts +121 -0
- package/src/index.ts +14 -0
- package/src/packer/common/app-update-yml.ts +21 -0
- package/src/packer/common/extra-resources.ts +83 -0
- package/src/packer/common/staging.ts +33 -0
- package/src/packer/common/template.ts +24 -0
- package/src/packer/linux/linux.ts +329 -0
- package/src/packer/mac/mac.ts +495 -0
- package/src/packer/win/nsis.ts +244 -0
- package/src/packer/win/resedit.ts +60 -0
- package/src/packer/win/toolchain.ts +63 -0
- package/src/packer/win/win.ts +421 -0
- package/src/pipeline/pipeline.ts +149 -0
- package/src/pipeline/targets.ts +67 -0
- package/src/shared/hash.ts +15 -0
- package/src/shared/logger.ts +91 -0
- package/src/shared/platform.ts +41 -0
- package/src/shared/types.ts +82 -0
- package/src/update/blockmap.ts +247 -0
- package/src/update/metadata.ts +97 -0
- package/src/update/version.ts +59 -0
- package/templates/linux/app.desktop +11 -0
- package/templates/linux/control +9 -0
- package/templates/linux/postinst +10 -0
- package/templates/linux/postrm +8 -0
- package/templates/linux/preinst +49 -0
- package/templates/linux/prerm +43 -0
- package/templates/mac/Info.plist +18 -0
- package/templates/mac/app-update.yml +4 -0
- package/templates/win/nsis/README.md +51 -0
- package/templates/win/nsis/THIRD-PARTY-NOTICES.md +20 -0
- package/templates/win/nsis/assistedInstaller.nsh +154 -0
- package/templates/win/nsis/assistedMessages.yml +368 -0
- package/templates/win/nsis/common.nsh +147 -0
- package/templates/win/nsis/empty-license.txt +1 -0
- package/templates/win/nsis/include/FileAssociation.nsh +123 -0
- package/templates/win/nsis/include/StdUtils.nsh +496 -0
- package/templates/win/nsis/include/StrContains.nsh +48 -0
- package/templates/win/nsis/include/UAC.nsh +299 -0
- package/templates/win/nsis/include/allowOnlyOneInstallerInstance.nsh +164 -0
- package/templates/win/nsis/include/extractAppPackage.nsh +138 -0
- package/templates/win/nsis/include/getProcessInfo.nsh +157 -0
- package/templates/win/nsis/include/installUtil.nsh +248 -0
- package/templates/win/nsis/include/installer.nsh +247 -0
- package/templates/win/nsis/include/nsProcess.nsh +28 -0
- package/templates/win/nsis/include/webPackage.nsh +64 -0
- package/templates/win/nsis/installSection.nsh +110 -0
- package/templates/win/nsis/installer.nsi +132 -0
- package/templates/win/nsis/messages.yml +240 -0
- package/templates/win/nsis/multiUser.nsh +134 -0
- package/templates/win/nsis/multiUserUi.nsh +230 -0
- package/templates/win/nsis/oneClick.nsh +19 -0
- package/templates/win/nsis/portable.nsi +91 -0
- package/templates/win/nsis/uninstaller.nsh +263 -0
- 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
|
+
}
|