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,495 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* macOS .app/zip packer.
|
|
3
|
+
*/
|
|
4
|
+
import { createWriteStream, existsSync, lstatSync, readdirSync, readlinkSync } from 'node:fs'
|
|
5
|
+
import { mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises'
|
|
6
|
+
import { createRequire } from 'node:module'
|
|
7
|
+
import os from 'node:os'
|
|
8
|
+
import path from 'node:path'
|
|
9
|
+
import type { Archiver, ArchiverOptions } from 'archiver'
|
|
10
|
+
import { build as buildPlist, type PlistValue, parse as parsePlist } from 'plist'
|
|
11
|
+
import {
|
|
12
|
+
getEntryBuffer,
|
|
13
|
+
getSymlinkTarget,
|
|
14
|
+
isDefaultAppEntry,
|
|
15
|
+
isSymlink,
|
|
16
|
+
type ZipEntry,
|
|
17
|
+
} from '../../archive/zip-reader.ts'
|
|
18
|
+
import { hashHeader } from '../../asar/packer/integrity.ts'
|
|
19
|
+
import type { NormalizedConfig } from '../../config/index.ts'
|
|
20
|
+
import { downloadAndReadElectron } from '../../electron/dist-zip.ts'
|
|
21
|
+
import type { PackerContext } from '../../pipeline/targets.ts'
|
|
22
|
+
import { logger } from '../../shared/logger.ts'
|
|
23
|
+
import { NodePlatform, nodeArchName } from '../../shared/platform.ts'
|
|
24
|
+
import { buildBlockMap } from '../../update/blockmap.ts'
|
|
25
|
+
import { generateUpdateMetadata } from '../../update/metadata.ts'
|
|
26
|
+
import { renderAppUpdateYml } from '../common/app-update-yml.ts'
|
|
27
|
+
import {
|
|
28
|
+
type ExtraResourceEntry,
|
|
29
|
+
isExtraResourcePathExcluded,
|
|
30
|
+
resolveExtraResources,
|
|
31
|
+
} from '../common/extra-resources.ts'
|
|
32
|
+
import { createStagingDir, removeStagingDir } from '../common/staging.ts'
|
|
33
|
+
import { getTemplatesDir } from '../common/template.ts'
|
|
34
|
+
|
|
35
|
+
const require = createRequire(import.meta.url)
|
|
36
|
+
const { ZipArchive } = require('archiver') as {
|
|
37
|
+
ZipArchive: new (options: ArchiverOptions) => Archiver
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const TEMPLATES_DIR = getTemplatesDir('mac')
|
|
41
|
+
|
|
42
|
+
interface MacBundleIdentity {
|
|
43
|
+
/** Bundle directory name, for example "MyApp.app". */
|
|
44
|
+
appName: string
|
|
45
|
+
/** On-disk bundle base name. */
|
|
46
|
+
bundleName: string
|
|
47
|
+
/** User-visible product name. */
|
|
48
|
+
displayName: string
|
|
49
|
+
executableName: string
|
|
50
|
+
helperName: string
|
|
51
|
+
/** Helper name with unsafe characters removed. */
|
|
52
|
+
sanitizedHelperName: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface HelperPlistEntry {
|
|
56
|
+
executable: string
|
|
57
|
+
displayName: string
|
|
58
|
+
name: string
|
|
59
|
+
bundleId: string
|
|
60
|
+
version: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function packMac({
|
|
64
|
+
config,
|
|
65
|
+
arch,
|
|
66
|
+
outputDir,
|
|
67
|
+
asarPath,
|
|
68
|
+
unpackedDir,
|
|
69
|
+
}: PackerContext): Promise<string> {
|
|
70
|
+
if (asarPath === undefined) throw new Error('packMac: asarPath is required')
|
|
71
|
+
logger.info(`\n[macOS] ${arch} packaging started`)
|
|
72
|
+
|
|
73
|
+
const stagingDir = await createStagingDir(outputDir, 'mac', arch)
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
logger.info(' [1/6] Downloading and reading macOS Electron dist ...')
|
|
77
|
+
const { entries } = await downloadAndReadElectron(NodePlatform.DARWIN, arch, config)
|
|
78
|
+
|
|
79
|
+
logger.info(' [2/6] Generating Info.plist and computing asar integrity ...')
|
|
80
|
+
const identity = resolveMacBundleIdentity(config)
|
|
81
|
+
const asarHash = await computeAsarHash(asarPath)
|
|
82
|
+
const defaultAppAsarHash = await computeDefaultAppAsarHash(entries)
|
|
83
|
+
const mainPlist = await generateMainPlist(config, identity, { asarHash, defaultAppAsarHash })
|
|
84
|
+
const helperPlistMap = buildHelperPlistMap(config, identity)
|
|
85
|
+
|
|
86
|
+
logger.info(' [3/6] Writing zip (in-memory assembly) ...')
|
|
87
|
+
const zipFilename = `${config.artifactName}_${config.version}_${nodeArchName(arch)}_unsigned.zip`
|
|
88
|
+
const zipPath = path.join(outputDir, zipFilename)
|
|
89
|
+
await writeMacZip({
|
|
90
|
+
entries,
|
|
91
|
+
identity,
|
|
92
|
+
mainPlist,
|
|
93
|
+
helperPlistMap,
|
|
94
|
+
asarPath,
|
|
95
|
+
unpackedDir,
|
|
96
|
+
zipPath,
|
|
97
|
+
config,
|
|
98
|
+
})
|
|
99
|
+
logger.info(` [macOS] ${arch} zip complete: ${zipPath}`)
|
|
100
|
+
|
|
101
|
+
let distributablePath = zipPath
|
|
102
|
+
|
|
103
|
+
if (config.sign.enabled && config.sign.mac?.enabled && config.sign.mac?.hook) {
|
|
104
|
+
logger.info(' [4/6] Running sign hook ...')
|
|
105
|
+
distributablePath =
|
|
106
|
+
(await config.sign.mac.hook(zipPath, { config, arch, outputDir })) || zipPath
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (config.update.generateMetadata) {
|
|
110
|
+
logger.info(' [6/6] Generating update metadata ...')
|
|
111
|
+
const zipBlockmapPath = `${distributablePath}.blockmap`
|
|
112
|
+
let blockMapSize: number | undefined
|
|
113
|
+
try {
|
|
114
|
+
blockMapSize = (await buildBlockMap(distributablePath, 'gzip', zipBlockmapPath))
|
|
115
|
+
.blockMapSize
|
|
116
|
+
} catch (e) {
|
|
117
|
+
console.warn(
|
|
118
|
+
` [macOS] Warning: blockmap generation failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
generateUpdateMetadata({
|
|
122
|
+
filePath: distributablePath,
|
|
123
|
+
version: config.version,
|
|
124
|
+
outputDir,
|
|
125
|
+
platform: NodePlatform.DARWIN,
|
|
126
|
+
blockMapSize,
|
|
127
|
+
channel: config.update.channel,
|
|
128
|
+
extraFiles: config.update.extraArtifacts,
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
logger.info(` [macOS] ${arch} packaging complete: ${distributablePath}`)
|
|
133
|
+
return distributablePath
|
|
134
|
+
} finally {
|
|
135
|
+
await removeStagingDir(stagingDir)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Derive bundle identity (app name, executable, helper names) from config. */
|
|
140
|
+
export function resolveMacBundleIdentity(config: NormalizedConfig): MacBundleIdentity {
|
|
141
|
+
const displayName = config.productName.trim()
|
|
142
|
+
const bundleName = config.mac.bundleName.trim() || displayName
|
|
143
|
+
const executableName = bundleName
|
|
144
|
+
const helperName = displayName
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
appName: `${bundleName}.app`,
|
|
148
|
+
bundleName,
|
|
149
|
+
displayName,
|
|
150
|
+
executableName,
|
|
151
|
+
helperName,
|
|
152
|
+
sanitizedHelperName: helperName.replace(/[^\w()\- ]/g, ''),
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Normalize an archive entry mode to executable / regular file bits. */
|
|
157
|
+
export function normalizeMacArchiveFileMode(sourceMode: string | number): number {
|
|
158
|
+
return Number(sourceMode) & 0o111 ? 0o100755 : 0o100644
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function addDirectoryToArchive(
|
|
162
|
+
archive: Archiver,
|
|
163
|
+
dirPath: string,
|
|
164
|
+
archivePath: string,
|
|
165
|
+
resource: ExtraResourceEntry = {},
|
|
166
|
+
relativePath = '',
|
|
167
|
+
): void {
|
|
168
|
+
for (const entry of readdirSync(dirPath, { withFileTypes: true })) {
|
|
169
|
+
const fullPath = path.join(dirPath, entry.name)
|
|
170
|
+
const entryArchivePath = `${archivePath}/${entry.name}`
|
|
171
|
+
const entryRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name
|
|
172
|
+
|
|
173
|
+
if (isExtraResourcePathExcluded(resource, entryRelativePath)) continue
|
|
174
|
+
|
|
175
|
+
if (entry.isDirectory()) {
|
|
176
|
+
archive.append('', { name: `${entryArchivePath}/`, mode: 0o40755 })
|
|
177
|
+
addDirectoryToArchive(archive, fullPath, entryArchivePath, resource, entryRelativePath)
|
|
178
|
+
} else if (entry.isSymbolicLink()) {
|
|
179
|
+
archive.symlink(entryArchivePath, readlinkSync(fullPath), 0o120755)
|
|
180
|
+
} else if (entry.isFile()) {
|
|
181
|
+
archive.file(fullPath, {
|
|
182
|
+
name: entryArchivePath,
|
|
183
|
+
mode: normalizeMacArchiveFileMode(lstatSync(fullPath).mode),
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Build Electron helper-process rename rules for the sanitized helper name. */
|
|
190
|
+
function buildRenameRules(sanitizedHelperName: string): { from: string; to: string }[] {
|
|
191
|
+
const rules: { from: string; to: string }[] = []
|
|
192
|
+
const helperSuffixes = [
|
|
193
|
+
' Helper (Renderer)',
|
|
194
|
+
' Helper (Plugin)',
|
|
195
|
+
' Helper (GPU)',
|
|
196
|
+
' Helper EH',
|
|
197
|
+
' Helper NP',
|
|
198
|
+
' Helper',
|
|
199
|
+
]
|
|
200
|
+
for (const suffix of helperSuffixes) {
|
|
201
|
+
rules.push({ from: `Electron${suffix}`, to: `${sanitizedHelperName}${suffix}` })
|
|
202
|
+
}
|
|
203
|
+
rules.push({ from: 'Electron Login Helper', to: `${sanitizedHelperName} Login Helper` })
|
|
204
|
+
return rules
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function applyRenameRules(pathStr: string, rules: { from: string; to: string }[]): string {
|
|
208
|
+
const sorted = [...rules].sort((a, b) => b.from.length - a.from.length)
|
|
209
|
+
let result = pathStr
|
|
210
|
+
for (const { from, to } of sorted) {
|
|
211
|
+
if (result.includes(from)) {
|
|
212
|
+
result = result.split(from).join(to)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return result
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function writeMacZip({
|
|
219
|
+
entries,
|
|
220
|
+
identity,
|
|
221
|
+
mainPlist,
|
|
222
|
+
helperPlistMap,
|
|
223
|
+
asarPath,
|
|
224
|
+
unpackedDir,
|
|
225
|
+
zipPath,
|
|
226
|
+
config,
|
|
227
|
+
}: {
|
|
228
|
+
entries: ZipEntry[]
|
|
229
|
+
identity: MacBundleIdentity
|
|
230
|
+
mainPlist: string
|
|
231
|
+
helperPlistMap: Record<string, HelperPlistEntry>
|
|
232
|
+
asarPath: string
|
|
233
|
+
unpackedDir?: string
|
|
234
|
+
zipPath: string
|
|
235
|
+
config: NormalizedConfig
|
|
236
|
+
}): Promise<void> {
|
|
237
|
+
const oldAppPrefix = 'Electron.app/'
|
|
238
|
+
const finalAppName = identity.appName
|
|
239
|
+
const productFilename = identity.executableName
|
|
240
|
+
const sanitizedHelperName = identity.sanitizedHelperName
|
|
241
|
+
const renameRules = buildRenameRules(sanitizedHelperName)
|
|
242
|
+
|
|
243
|
+
const iconSrc = config.mac.iconFile ? path.join(config.projectDir, config.mac.iconFile) : null
|
|
244
|
+
const iconBuffer = iconSrc && existsSync(iconSrc) ? await readFile(iconSrc) : null
|
|
245
|
+
|
|
246
|
+
const output = createWriteStream(zipPath)
|
|
247
|
+
const archive = new ZipArchive({ zlib: { level: 9 } })
|
|
248
|
+
|
|
249
|
+
const writePromise = new Promise<void>((resolve, reject) => {
|
|
250
|
+
output.on('close', () => resolve())
|
|
251
|
+
output.on('error', reject)
|
|
252
|
+
archive.on('error', reject)
|
|
253
|
+
})
|
|
254
|
+
archive.pipe(output)
|
|
255
|
+
|
|
256
|
+
const writtenDirs = new Set<string>()
|
|
257
|
+
|
|
258
|
+
function ensureDir(archiveDirPath: string, mode: string | number): void {
|
|
259
|
+
if (!writtenDirs.has(archiveDirPath)) {
|
|
260
|
+
writtenDirs.add(archiveDirPath)
|
|
261
|
+
archive.append('', { name: `${archiveDirPath}/`, mode: Number(mode) || 0o40755 })
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
for (const e of entries) {
|
|
266
|
+
const oldPath = e.path
|
|
267
|
+
if (!oldPath.startsWith(oldAppPrefix)) continue
|
|
268
|
+
|
|
269
|
+
if (isDefaultAppEntry(oldPath)) continue
|
|
270
|
+
if (oldPath === `${oldAppPrefix}Contents/Resources/electron.icns`) continue
|
|
271
|
+
if (/^Electron\.app\/Contents\/Resources\/[^/]+\.lproj\/?$/.test(oldPath)) continue
|
|
272
|
+
|
|
273
|
+
let newPath = oldPath.replace(oldAppPrefix, `${finalAppName}/`)
|
|
274
|
+
|
|
275
|
+
const mainExeOldPath = `${finalAppName}/Contents/MacOS/Electron`
|
|
276
|
+
if (newPath === mainExeOldPath) {
|
|
277
|
+
newPath = `${finalAppName}/Contents/MacOS/${productFilename}`
|
|
278
|
+
} else if (newPath.includes('/Electron Helper') || newPath.includes('/Electron Login Helper')) {
|
|
279
|
+
newPath = applyRenameRules(newPath, renameRules)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const pathParts = newPath.split('/')
|
|
283
|
+
for (let i = 1; i < pathParts.length; i++) {
|
|
284
|
+
ensureDir(pathParts.slice(0, i).join('/'), e.mode)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (isSymlink(e)) {
|
|
288
|
+
const target = await getSymlinkTarget(e.entry)
|
|
289
|
+
let newTarget = target
|
|
290
|
+
const isHelper =
|
|
291
|
+
newPath.includes(`/${sanitizedHelperName} Helper`) ||
|
|
292
|
+
newPath.includes(`/${sanitizedHelperName} Login Helper`)
|
|
293
|
+
if (isHelper) {
|
|
294
|
+
newTarget = applyRenameRules(newTarget, renameRules)
|
|
295
|
+
}
|
|
296
|
+
archive.symlink(newPath, newTarget, 0o120755)
|
|
297
|
+
continue
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (e.dir) {
|
|
301
|
+
ensureDir(newPath.replace(/\/$/, ''), e.mode)
|
|
302
|
+
continue
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (newPath === `${finalAppName}/Contents/Info.plist`) {
|
|
306
|
+
archive.append(mainPlist, { name: newPath, mode: 0o100644 })
|
|
307
|
+
continue
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const helperPlistEntry = helperPlistMap[newPath]
|
|
311
|
+
if (helperPlistEntry) {
|
|
312
|
+
const helperInfo = parsePlist(await e.entry.async('text')) as Record<string, PlistValue>
|
|
313
|
+
helperInfo.CFBundleExecutable = helperPlistEntry.executable
|
|
314
|
+
helperInfo.CFBundleDisplayName = helperPlistEntry.displayName
|
|
315
|
+
helperInfo.CFBundleName = helperPlistEntry.name
|
|
316
|
+
helperInfo.CFBundleIdentifier = helperPlistEntry.bundleId
|
|
317
|
+
helperInfo.CFBundleVersion = helperPlistEntry.version
|
|
318
|
+
archive.append(buildPlist(helperInfo), { name: newPath, mode: 0o100644 })
|
|
319
|
+
continue
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const buffer = await getEntryBuffer(e.entry)
|
|
323
|
+
archive.append(buffer, { name: newPath, mode: Number(e.mode) })
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (iconBuffer) {
|
|
327
|
+
archive.append(iconBuffer, {
|
|
328
|
+
name: `${finalAppName}/Contents/Resources/icon.icns`,
|
|
329
|
+
mode: 0o100644,
|
|
330
|
+
})
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (existsSync(asarPath)) {
|
|
334
|
+
const asarStat = await stat(asarPath)
|
|
335
|
+
if (asarStat.size > 2 * 1024 * 1024 * 1024) {
|
|
336
|
+
archive.file(asarPath, {
|
|
337
|
+
name: `${finalAppName}/Contents/Resources/app.asar`,
|
|
338
|
+
mode: 0o100644,
|
|
339
|
+
})
|
|
340
|
+
} else {
|
|
341
|
+
archive.append(await readFile(asarPath), {
|
|
342
|
+
name: `${finalAppName}/Contents/Resources/app.asar`,
|
|
343
|
+
mode: 0o100644,
|
|
344
|
+
})
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (unpackedDir && existsSync(unpackedDir)) {
|
|
349
|
+
archive.append('', {
|
|
350
|
+
name: `${finalAppName}/Contents/Resources/app.asar.unpacked/`,
|
|
351
|
+
mode: 0o40755,
|
|
352
|
+
})
|
|
353
|
+
addDirectoryToArchive(
|
|
354
|
+
archive,
|
|
355
|
+
unpackedDir,
|
|
356
|
+
`${finalAppName}/Contents/Resources/app.asar.unpacked`,
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
await injectExtraResourcesToZip(archive, finalAppName, config)
|
|
361
|
+
|
|
362
|
+
if (config.update.generateMetadata && config.update.url) {
|
|
363
|
+
const updateYml = await renderAppUpdateYml(config)
|
|
364
|
+
archive.append(updateYml, {
|
|
365
|
+
name: `${finalAppName}/Contents/Resources/app-update.yml`,
|
|
366
|
+
mode: 0o100644,
|
|
367
|
+
})
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
await archive.finalize()
|
|
371
|
+
await writePromise
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function buildHelperPlistMap(
|
|
375
|
+
config: NormalizedConfig,
|
|
376
|
+
identity: MacBundleIdentity,
|
|
377
|
+
): Record<string, HelperPlistEntry> {
|
|
378
|
+
const map: Record<string, HelperPlistEntry> = {}
|
|
379
|
+
const helperBundleId = `${config.appId}.helper`
|
|
380
|
+
const finalAppName = identity.appName
|
|
381
|
+
const helpers = [
|
|
382
|
+
{ suffix: ' Helper', bundleSuffix: '' },
|
|
383
|
+
{ suffix: ' Helper (Renderer)', bundleSuffix: '.(Renderer)' },
|
|
384
|
+
{ suffix: ' Helper (Plugin)', bundleSuffix: '.(Plugin)' },
|
|
385
|
+
{ suffix: ' Helper (GPU)', bundleSuffix: '.(GPU)' },
|
|
386
|
+
{ suffix: ' Helper EH', bundleSuffix: '.EH' },
|
|
387
|
+
{ suffix: ' Helper NP', bundleSuffix: '.NP' },
|
|
388
|
+
]
|
|
389
|
+
|
|
390
|
+
for (const h of helpers) {
|
|
391
|
+
const plistPath = `${finalAppName}/Contents/Frameworks/${identity.sanitizedHelperName}${h.suffix}.app/Contents/Info.plist`
|
|
392
|
+
map[plistPath] = {
|
|
393
|
+
executable: `${identity.sanitizedHelperName}${h.suffix}`,
|
|
394
|
+
displayName: `${identity.displayName}${h.suffix}`,
|
|
395
|
+
name: `${identity.displayName}${h.suffix}`,
|
|
396
|
+
bundleId: `${helperBundleId}${h.bundleSuffix}`,
|
|
397
|
+
version: config.version,
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const loginPlistPath = `${finalAppName}/Contents/Library/LoginItems/${identity.sanitizedHelperName} Login Helper.app/Contents/Info.plist`
|
|
402
|
+
map[loginPlistPath] = {
|
|
403
|
+
executable: `${identity.sanitizedHelperName} Login Helper`,
|
|
404
|
+
displayName: `${identity.displayName} Login Helper`,
|
|
405
|
+
name: `${identity.displayName} Login Helper`,
|
|
406
|
+
bundleId: `${config.appId}.loginhelper`,
|
|
407
|
+
version: config.version,
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return map
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
async function generateMainPlist(
|
|
414
|
+
config: NormalizedConfig,
|
|
415
|
+
identity: MacBundleIdentity,
|
|
416
|
+
{ asarHash, defaultAppAsarHash }: { asarHash: string; defaultAppAsarHash: string },
|
|
417
|
+
): Promise<string> {
|
|
418
|
+
const buildVersion = config.version.includes('.') ? config.version : `${config.version}.0`
|
|
419
|
+
const year = new Date().getFullYear()
|
|
420
|
+
|
|
421
|
+
const templateXml = await readFile(path.join(TEMPLATES_DIR, 'Info.plist'), 'utf8')
|
|
422
|
+
const info = parsePlist(templateXml) as Record<string, PlistValue>
|
|
423
|
+
|
|
424
|
+
info.CFBundleDisplayName = identity.displayName
|
|
425
|
+
info.CFBundleExecutable = identity.executableName
|
|
426
|
+
info.CFBundleIconFile = config.mac.iconFile ? 'icon.icns' : 'electron.icns'
|
|
427
|
+
info.CFBundleIdentifier = config.appId
|
|
428
|
+
info.CFBundleName = identity.bundleName
|
|
429
|
+
info.CFBundleShortVersionString = config.version
|
|
430
|
+
info.CFBundleVersion = buildVersion
|
|
431
|
+
info.ElectronAsarIntegrity = {
|
|
432
|
+
'Resources/app.asar': { algorithm: 'SHA256', hash: asarHash },
|
|
433
|
+
'Resources/default_app.asar': { algorithm: 'SHA256', hash: defaultAppAsarHash },
|
|
434
|
+
}
|
|
435
|
+
info.LSApplicationCategoryType = config.mac.category
|
|
436
|
+
info.LSMinimumSystemVersion = config.mac.minimumSystemVersion
|
|
437
|
+
info.NSHumanReadableCopyright = `Copyright © ${year} ${config.author}`.trim()
|
|
438
|
+
|
|
439
|
+
if (config.mac.urlSchemes?.length) {
|
|
440
|
+
info.CFBundleURLTypes = config.mac.urlSchemes.map((scheme) => ({
|
|
441
|
+
CFBundleTypeRole: 'Editor',
|
|
442
|
+
CFBundleURLName: config.productName,
|
|
443
|
+
CFBundleURLSchemes: [scheme],
|
|
444
|
+
}))
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (config.mac.plist && Object.keys(config.mac.plist).length > 0) {
|
|
448
|
+
Object.assign(info, config.mac.plist)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return buildPlist(info)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async function computeAsarHash(asarPath: string): Promise<string> {
|
|
455
|
+
const { hash } = await hashHeader(asarPath)
|
|
456
|
+
return hash
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
async function computeDefaultAppAsarHash(entries: ZipEntry[]): Promise<string> {
|
|
460
|
+
// Find the default_app.asar entry inside the Electron distribution zip.
|
|
461
|
+
const entry = entries.find((e) => e.path === 'Electron.app/Contents/Resources/default_app.asar')
|
|
462
|
+
if (!entry) {
|
|
463
|
+
throw new Error('default_app.asar not found in Electron zip')
|
|
464
|
+
}
|
|
465
|
+
const tmpDir = await mkdtemp(path.join(os.tmpdir(), 'cross-packer-asar-'))
|
|
466
|
+
const tmpAsar = path.join(tmpDir, 'default_app.asar')
|
|
467
|
+
try {
|
|
468
|
+
await writeFile(tmpAsar, await getEntryBuffer(entry.entry))
|
|
469
|
+
const { hash } = await hashHeader(tmpAsar)
|
|
470
|
+
return hash
|
|
471
|
+
} finally {
|
|
472
|
+
await rm(tmpDir, { recursive: true, force: true })
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
async function injectExtraResourcesToZip(
|
|
477
|
+
archive: Archiver,
|
|
478
|
+
finalAppName: string,
|
|
479
|
+
config: NormalizedConfig,
|
|
480
|
+
): Promise<void> {
|
|
481
|
+
const resourcesPrefix = `${finalAppName}/Contents/Resources`
|
|
482
|
+
|
|
483
|
+
for (const { res, srcPath, srcStat, destPath: resTo } of await resolveExtraResources(config)) {
|
|
484
|
+
const archivePath = `${resourcesPrefix}/${resTo}`
|
|
485
|
+
if (srcStat.isDirectory()) {
|
|
486
|
+
archive.append('', { name: `${archivePath}/`, mode: 0o40755 })
|
|
487
|
+
addDirectoryToArchive(archive, srcPath, archivePath, res)
|
|
488
|
+
} else {
|
|
489
|
+
archive.append(await readFile(srcPath), {
|
|
490
|
+
name: archivePath,
|
|
491
|
+
mode: normalizeMacArchiveFileMode(srcStat.mode),
|
|
492
|
+
})
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|