diff --git a/scripts/codesign-macos-app.mjs b/scripts/codesign-macos-app.mjs new file mode 100644 index 0000000..ae2e8c7 --- /dev/null +++ b/scripts/codesign-macos-app.mjs @@ -0,0 +1,89 @@ +import { existsSync } from "node:fs"; +import path from "node:path"; +import { spawnSync } from "node:child_process"; +import { fileURLToPath } from "node:url"; + +function getCodesignArgs() { + const identity = process.env.ELECTROBUN_DEVELOPER_ID || "-"; + const args = ["--force", "--deep"]; + + if (identity !== "-") { + args.push("--options", "runtime", "--timestamp"); + } + + args.push("--sign", identity); + return { identity, args }; +} + +export function codesignMacAppBundle(appBundlePath) { + if (process.platform !== "darwin") { + console.log("codesign-macos-app: skipping (not macOS)"); + return; + } + + if (!appBundlePath || !existsSync(appBundlePath)) { + throw new Error(`codesign-macos-app: app bundle not found: ${appBundlePath}`); + } + + const contentsPath = path.join(appBundlePath, "Contents"); + if (!existsSync(contentsPath)) { + throw new Error(`codesign-macos-app: invalid app bundle: ${appBundlePath}`); + } + + const { identity, args } = getCodesignArgs(); + const result = spawnSync("codesign", [...args, appBundlePath], { + stdio: "inherit", + }); + + if (result.status !== 0) { + throw new Error( + `codesign failed with exit code ${result.status ?? 1}`, + ); + } + + const verify = spawnSync( + "codesign", + ["--verify", "--deep", "--strict", "--verbose=2", appBundlePath], + { encoding: "utf8" }, + ); + + if (verify.status !== 0) { + throw new Error( + `codesign verify failed: ${verify.stderr || verify.stdout || "unknown error"}`, + ); + } + + const label = identity === "-" ? "ad-hoc" : identity; + console.log(`codesign-macos-app: signed ${appBundlePath} (${label})`); +} + +function resolveAppBundlePath() { + const fromEnv = process.env.ELECTROBUN_WRAPPER_BUNDLE_PATH; + if (fromEnv && existsSync(fromEnv)) { + return path.resolve(fromEnv); + } + + const fromArgv = process.argv[2]; + if (fromArgv && existsSync(fromArgv)) { + return path.resolve(fromArgv); + } + + return null; +} + +const invokedPath = process.argv[1] + ? path.resolve(process.argv[1]) + : null; +const modulePath = fileURLToPath(import.meta.url); + +if (invokedPath === modulePath) { + const appBundlePath = resolveAppBundlePath(); + if (!appBundlePath) { + console.error( + "codesign-macos-app: set ELECTROBUN_WRAPPER_BUNDLE_PATH or pass app bundle path", + ); + process.exit(1); + } + + codesignMacAppBundle(appBundlePath); +} diff --git a/scripts/finalize-desktop-artifacts.mjs b/scripts/finalize-desktop-artifacts.mjs index dd81c54..c1b5d45 100644 --- a/scripts/finalize-desktop-artifacts.mjs +++ b/scripts/finalize-desktop-artifacts.mjs @@ -19,6 +19,7 @@ import { cleanExpandedWindowsApp, expandWindowsAppFromArchive, } from "./expand-windows-installer.mjs"; +import { codesignMacAppBundle } from "./codesign-macos-app.mjs"; const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const packageJson = JSON.parse( @@ -203,23 +204,6 @@ function copyMacAppBundle(sourcePath, destinationPath) { } } -function resignMacAppBundle(appBundlePath) { - if (process.platform !== "darwin") { - return; - } - - const identity = process.env.ELECTROBUN_DEVELOPER_ID || "-"; - const result = spawnSync( - "codesign", - ["--force", "--deep", "--sign", identity, appBundlePath], - { stdio: "inherit" }, - ); - - if (result.status !== 0) { - throw new Error(`codesign failed with exit code ${result.status ?? 1}`); - } -} - function canUseDirectDmgSourceFolder(appBundlePath) { const platformDir = path.dirname(appBundlePath); const appName = path.basename(appBundlePath); @@ -290,7 +274,7 @@ function buildMacDmg(appBundlePath, arch) { path.basename(appBundlePath), ); copyMacAppBundle(appBundlePath, stagedAppPath); - resignMacAppBundle(stagedAppPath); + codesignMacAppBundle(stagedAppPath); } let builtDmgPath; @@ -417,6 +401,8 @@ if (targetOs === "macos") { process.exit(0); } + codesignMacAppBundle(appBundle); + const existingDmg = findMacDmgSource(buildArch); const dmgPath = existingDmg ? publishArtifact(existingDmg, buildArch, "dmg")