diff --git a/.changeset/chilly-starfishes-lie.md b/.changeset/chilly-starfishes-lie.md new file mode 100644 index 00000000000..efcff468ccb --- /dev/null +++ b/.changeset/chilly-starfishes-lie.md @@ -0,0 +1,5 @@ +--- +"app-builder-lib": patch +--- + +chore: replace the plist functionality in app-builder-bin with plist diff --git a/packages/app-builder-lib/package.json b/packages/app-builder-lib/package.json index 079e20de7cb..0253ba3668b 100644 --- a/packages/app-builder-lib/package.json +++ b/packages/app-builder-lib/package.json @@ -77,7 +77,8 @@ "semver": "^7.3.8", "tar": "^6.1.12", "temp-file": "^3.4.0", - "tiny-async-pool": "1.3.0" + "tiny-async-pool": "1.3.0", + "plist":"3.1.0" }, "///": "babel in devDependencies for proton tests", "devDependencies": { @@ -108,6 +109,7 @@ "@types/semver": "7.3.8", "@types/tar": "^6.1.3", "@types/tiny-async-pool": "^1", + "@types/plist": "3.0.5", "dmg-builder": "workspace:*", "electron-builder-squirrel-windows": "workspace:*", "toml": "^3.0.0" diff --git a/packages/app-builder-lib/src/electron/electronMac.ts b/packages/app-builder-lib/src/electron/electronMac.ts index 97967c92154..5b46b793fd3 100644 --- a/packages/app-builder-lib/src/electron/electronMac.ts +++ b/packages/app-builder-lib/src/electron/electronMac.ts @@ -1,11 +1,12 @@ import { asArray, copyOrLinkFile, getPlatformIconFileName, InvalidConfigurationError, log, unlinkIfExists } from "builder-util" import { rename, utimes } from "fs/promises" import * as path from "path" +import * as fs from "fs" import { filterCFBundleIdentifier } from "../appInfo" import { AsarIntegrity } from "../asar/integrity" import { MacPackager } from "../macPackager" import { normalizeExt } from "../platformPackager" -import { executeAppBuilderAndWriteJson, executeAppBuilderAsJson } from "../util/appBuilder" +import { savePlistFile, parsePlistFile, PlistObject, PlistValue } from "../util/plist" import { createBrandingOpts } from "./ElectronFramework" function doRename(basePath: string, oldName: string, newName: string) { @@ -22,11 +23,11 @@ function moveHelpers(helperSuffixes: Array, frameworksPath: string, appN } function getAvailableHelperSuffixes( - helperEHPlist: string | null, - helperNPPlist: string | null, - helperRendererPlist: string | null, - helperPluginPlist: string | null, - helperGPUPlist: string | null + helperEHPlist: PlistObject | null, + helperNPPlist: PlistObject | null, + helperRendererPlist: PlistObject | null, + helperPluginPlist: PlistObject | null, + helperGPUPlist: PlistObject | null ) { const result = [" Helper"] if (helperEHPlist != null) { @@ -69,38 +70,26 @@ export async function createMacApp(packager: MacPackager, appOutDir: string, asa const helperGPUPlistFilename = path.join(frameworksPath, `${electronBranding.productName} Helper (GPU).app`, "Contents", "Info.plist") const helperLoginPlistFilename = path.join(loginItemPath, `${electronBranding.productName} Login Helper.app`, "Contents", "Info.plist") - const plistContent: Array = await executeAppBuilderAsJson([ - "decode-plist", - "-f", - appPlistFilename, - "-f", - helperPlistFilename, - "-f", - helperEHPlistFilename, - "-f", - helperNPPlistFilename, - "-f", - helperRendererPlistFilename, - "-f", - helperPluginPlistFilename, - "-f", - helperGPUPlistFilename, - "-f", - helperLoginPlistFilename, - ]) + const safeParsePlistFile = async (filePath: string): Promise => { + if (!fs.existsSync(filePath)) { + return null + } + return await parsePlistFile(filePath) + } - if (plistContent[0] == null) { + const appPlist = (await safeParsePlistFile(appPlistFilename))! + if (appPlist == null) { throw new Error("corrupted Electron dist") } - const appPlist = plistContent[0]! - const helperPlist = plistContent[1]! - const helperEHPlist = plistContent[2] - const helperNPPlist = plistContent[3] - const helperRendererPlist = plistContent[4] - const helperPluginPlist = plistContent[5] - const helperGPUPlist = plistContent[6] - const helperLoginPlist = plistContent[7] + // Replace the multiple parsePlistFile calls with: + const helperPlist = await safeParsePlistFile(helperPlistFilename) + const helperEHPlist = await safeParsePlistFile(helperEHPlistFilename) + const helperNPPlist = await safeParsePlistFile(helperNPPlistFilename) + const helperRendererPlist = await safeParsePlistFile(helperRendererPlistFilename) + const helperPluginPlist = await safeParsePlistFile(helperPluginPlistFilename) + const helperGPUPlist = await safeParsePlistFile(helperGPUPlistFilename) + const helperLoginPlist = await safeParsePlistFile(helperLoginPlistFilename) const buildMetadata = packager.config @@ -133,10 +122,12 @@ export async function createMacApp(packager: MacPackager, appOutDir: string, asa configureLocalhostAts(appPlist) } - helperPlist.CFBundleExecutable = `${appFilename} Helper` - helperPlist.CFBundleDisplayName = `${appInfo.productName} Helper` - helperPlist.CFBundleIdentifier = helperBundleIdentifier - helperPlist.CFBundleVersion = appPlist.CFBundleVersion + if (helperPlist != null) { + helperPlist.CFBundleExecutable = `${appFilename} Helper` + helperPlist.CFBundleDisplayName = `${appInfo.productName} Helper` + helperPlist.CFBundleIdentifier = helperBundleIdentifier + helperPlist.CFBundleVersion = appPlist.CFBundleVersion + } /** * Configure bundleIdentifier for Electron 5+ Helper processes @@ -222,39 +213,55 @@ export async function createMacApp(packager: MacPackager, appOutDir: string, asa ) // `CFBundleDocumentTypes` may be defined in `mac.extendInfo`, so we need to merge it in that case - appPlist.CFBundleDocumentTypes = [...(appPlist.CFBundleDocumentTypes || []), ...documentTypes] + appPlist.CFBundleDocumentTypes = [...((appPlist.CFBundleDocumentTypes as PlistValue[]) || []), ...documentTypes] } - if (asarIntegrity != null) { - appPlist.ElectronAsarIntegrity = asarIntegrity + const toPlistObject = (asarIntegrity: AsarIntegrity): PlistObject => { + const result: PlistObject = {} + for (const [filePath, headerHash] of Object.entries(asarIntegrity)) { + result[filePath] = { + algorithm: headerHash.algorithm, + hash: headerHash.hash, + } + } + return result } - const plistDataToWrite: any = { - [appPlistFilename]: appPlist, - [helperPlistFilename]: helperPlist, + if (asarIntegrity != null) { + appPlist.ElectronAsarIntegrity = toPlistObject(asarIntegrity) } + if (helperEHPlist != null) { - plistDataToWrite[helperEHPlistFilename] = helperEHPlist + await savePlistFile(helperEHPlistFilename, helperEHPlist) } + if (helperNPPlist != null) { - plistDataToWrite[helperNPPlistFilename] = helperNPPlist + await savePlistFile(helperNPPlistFilename, helperNPPlist) } + if (helperRendererPlist != null) { - plistDataToWrite[helperRendererPlistFilename] = helperRendererPlist + await savePlistFile(helperRendererPlistFilename, helperRendererPlist) } + if (helperPluginPlist != null) { - plistDataToWrite[helperPluginPlistFilename] = helperPluginPlist + await savePlistFile(helperPluginPlistFilename, helperPluginPlist) } + if (helperGPUPlist != null) { - plistDataToWrite[helperGPUPlistFilename] = helperGPUPlist + await savePlistFile(helperGPUPlistFilename, helperGPUPlist) } + if (helperLoginPlist != null) { - plistDataToWrite[helperLoginPlistFilename] = helperLoginPlist + await savePlistFile(helperLoginPlistFilename, helperLoginPlist) + } + + await savePlistFile(appPlistFilename, appPlist) + if (helperPlist != null) { + await savePlistFile(helperPlistFilename, helperPlist) } await Promise.all([ - executeAppBuilderAndWriteJson(["encode-plist"], plistDataToWrite), - doRename(path.join(contentsPath, "MacOS"), electronBranding.productName, appPlist.CFBundleExecutable), + doRename(path.join(contentsPath, "MacOS"), electronBranding.productName, appPlist.CFBundleExecutable as string), unlinkIfExists(path.join(appOutDir, "LICENSE")), unlinkIfExists(path.join(appOutDir, "LICENSES.chromium.html")), ]) diff --git a/packages/app-builder-lib/src/frameworks/LibUiFramework.ts b/packages/app-builder-lib/src/frameworks/LibUiFramework.ts index f9c8381bb7c..82f7de429ff 100644 --- a/packages/app-builder-lib/src/frameworks/LibUiFramework.ts +++ b/packages/app-builder-lib/src/frameworks/LibUiFramework.ts @@ -7,7 +7,7 @@ import { Platform } from "../core" import { Framework, PrepareApplicationStageDirectoryOptions } from "../Framework" import { LinuxPackager } from "../linuxPackager" import { MacPackager } from "../macPackager" -import { executeAppBuilderAndWriteJson } from "../util/appBuilder" +import { savePlistFile } from "../util/plist" export class LibUiFramework implements Framework { readonly name: string = "libui" @@ -71,16 +71,14 @@ export class LibUiFramework implements Framework { NSHighResolutionCapable: true, } await packager.applyCommonInfo(appPlist, appContentsDir) - await Promise.all([ - executeAppBuilderAndWriteJson(["encode-plist"], { [path.join(appContentsDir, "Info.plist")]: appPlist }), - writeExecutableMain( - path.join(appContentsDir, "MacOS", appPlist.CFBundleExecutable), - `#!/bin/sh + await savePlistFile(path.join(appContentsDir, "Info.plist"), appPlist) + await writeExecutableMain( + path.join(appContentsDir, "MacOS", appPlist.CFBundleExecutable), + `#!/bin/sh DIR=$(dirname "$0") "$DIR/node" "$DIR/../Resources/app/${options.packager.info.metadata.main || "index.js"}" ` - ), - ]) + ) } private async prepareLinuxApplicationStageDirectory(options: PrepareApplicationStageDirectoryOptions) { diff --git a/packages/app-builder-lib/src/targets/pkg.ts b/packages/app-builder-lib/src/targets/pkg.ts index 6309ea3c9a2..442668c31db 100644 --- a/packages/app-builder-lib/src/targets/pkg.ts +++ b/packages/app-builder-lib/src/targets/pkg.ts @@ -8,7 +8,7 @@ import { findIdentity, Identity } from "../codeSign/macCodeSign" import { Target } from "../core" import { MacPackager } from "../macPackager" import { PkgOptions } from "../options/pkgOptions" -import { executeAppBuilderAndWriteJson, executeAppBuilderAsJson } from "../util/appBuilder" +import { savePlistFile, parsePlistFile, PlistObject } from "../util/plist" import { getNotLocalizedLicenseFile } from "../util/license" const certType = "Developer ID Installer" @@ -182,9 +182,7 @@ export class PkgTarget extends Target { await exec("pkgbuild", ["--analyze", "--root", rootPath, propertyListOutputFile]) // process the template plist - const plistInfo = (await executeAppBuilderAsJson>(["decode-plist", "-f", propertyListOutputFile]))[0].filter( - (it: any) => it.RootRelativeBundlePath !== "Electron.dSYM" - ) + const plistInfo = (await parsePlistFile(propertyListOutputFile)).filter((it: PlistObject) => it.RootRelativeBundlePath !== "Electron.dSYM") let packageInfo: any = {} if (plistInfo.length > 0) { packageInfo = plistInfo[0] @@ -237,7 +235,7 @@ export class PkgTarget extends Target { args.push("--scripts", scriptsDir) } if (plistInfo.length > 0) { - await executeAppBuilderAndWriteJson(["encode-plist"], { [propertyListOutputFile]: plistInfo }) + await savePlistFile(propertyListOutputFile, plistInfo) } args.push(packageOutputFile) diff --git a/packages/app-builder-lib/src/util/plist.ts b/packages/app-builder-lib/src/util/plist.ts new file mode 100644 index 00000000000..2f2e31aecbf --- /dev/null +++ b/packages/app-builder-lib/src/util/plist.ts @@ -0,0 +1,39 @@ +import { build, parse } from "plist" +import * as fs from "fs/promises" + +type PlistValue = string | number | boolean | Date | PlistObject | PlistValue[] + +interface PlistObject { + [key: string]: PlistValue +} + +function sortObjectKeys(obj: PlistValue): PlistValue { + if (obj === null || typeof obj !== "object") { + return obj + } + + if (Array.isArray(obj)) { + return obj.map(sortObjectKeys) + } + + const result: PlistObject = {} + Object.keys(obj) + .sort() + .forEach(key => { + result[key] = sortObjectKeys((obj as PlistObject)[key]) + }) + return result +} + +export async function savePlistFile(path: string, data: PlistValue): Promise { + const sortedData = sortObjectKeys(data) + const plist = build(sortedData) + await fs.writeFile(path, plist) +} + +export async function parsePlistFile(file: string): Promise { + const data = await fs.readFile(file, "utf8") + return parse(data) as T +} + +export type { PlistValue, PlistObject } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eda2afc797e..0cc56b26034 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -161,6 +161,9 @@ importers: minimatch: specifier: ^10.0.0 version: 10.0.1 + plist: + specifier: 3.1.0 + version: 3.1.0 resedit: specifier: ^1.7.0 version: 1.7.2 @@ -249,6 +252,9 @@ importers: '@types/js-yaml': specifier: 4.0.3 version: 4.0.3 + '@types/plist': + specifier: 3.0.5 + version: 3.0.5 '@types/semver': specifier: 7.3.8 version: 7.3.8 diff --git a/test/src/helpers/packTester.ts b/test/src/helpers/packTester.ts index bcf248687e9..d0abe029f0c 100644 --- a/test/src/helpers/packTester.ts +++ b/test/src/helpers/packTester.ts @@ -2,7 +2,7 @@ import { PublishManager } from "app-builder-lib" import { readAsar } from "app-builder-lib/out/asar/asar" import { computeArchToTargetNamesMap } from "app-builder-lib/out/targets/targetFactory" import { getLinuxToolsPath } from "app-builder-lib/out/targets/tools" -import { executeAppBuilderAsJson } from "app-builder-lib/out/util/appBuilder" +import { parsePlistFile, PlistObject } from "app-builder-lib/out/util/plist" import { AsarIntegrity } from "app-builder-lib/src/asar/integrity" import { addValue, copyDir, deepAssign, exec, executeFinally, exists, FileCopier, getPath7x, getPath7za, log, spawn, USE_HARD_LINKS, walk } from "builder-util" import { CancellationToken, UpdateFileInfo } from "builder-util-runtime" @@ -367,7 +367,7 @@ function parseDebControl(info: string): any { async function checkMacResult(packager: Packager, packagerOptions: PackagerOptions, checkOptions: AssertPackOptions, packedAppDir: string) { const appInfo = packager.appInfo const plistPath = path.join(packedAppDir, "Contents", "Info.plist") - const info = (await executeAppBuilderAsJson>(["decode-plist", "-f", plistPath]))[0] + const info = await parsePlistFile(plistPath) expect(info).toMatchObject({ CFBundleVersion: info.CFBundleVersion === "50" ? "50" : `${appInfo.version}.${process.env.TRAVIS_BUILD_NUMBER || process.env.CIRCLE_BUILD_NUM}`, @@ -389,7 +389,7 @@ async function checkMacResult(packager: Packager, packagerOptions: PackagerOptio delete info.NSRequiresAquaSystemAppearance delete info.NSQuitAlwaysKeepsWindows if (info.NSAppTransportSecurity != null) { - delete info.NSAppTransportSecurity.NSAllowsArbitraryLoads + delete (info.NSAppTransportSecurity as PlistObject).NSAllowsArbitraryLoads } // test value if (info.LSMinimumSystemVersion !== "10.12.0") { @@ -400,7 +400,7 @@ async function checkMacResult(packager: Packager, packagerOptions: PackagerOptio if (checksumData != null) { for (const name of Object.keys(checksumData)) { - checksumData[name] = { algorithm: "SHA256", hash: "hash" } + ;(checksumData as Record)[name] = { algorithm: "SHA256", hash: "hash" } } snapshot.ElectronAsarIntegrity = checksumData }