// SPDX-FileCopyrightText: 2026 Alexander Bates // // SPDX-License-Identifier: 0BSD // electron-builder's afterPack hook. On macOS it signs and notarizes the app // itself, but it can't elsewhere, so a macOS app packaged on another // platform is signed, notarized, and stapled here with rcodesign instead, // before electron-builder zips it for updates. // // Needs APPLE_DEVELOPER_ID_PEM (a Developer ID Application certificate and // its key, as PEM) and APPLE_NOTARY_API_KEY (an App Store Connect API key, // in the JSON form `rcodesign encode-app-store-connect-api-key` writes), // both base64-encoded. Without them the app is left unsigned. import { spawnSync } from "node:child_process"; import { mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { Arch } from "builder-util"; const modules = join( dirname(fileURLToPath(import.meta.url)), "..", "..", "node_modules", ); function rcodesign(args) { const result = spawnSync("rcodesign", args, { stdio: "inherit" }); if (result.status !== 0) throw new Error(`rcodesign ${args[0]} failed.`); } export default async function afterPack(context) { // The per-architecture apps only exist to be merged into the universal // one, which is what ships. if ( process.platform === "darwin" || context.electronPlatformName !== "darwin" ) return; if (context.arch !== Arch.universal) return; const { APPLE_DEVELOPER_ID_PEM: pem, APPLE_NOTARY_API_KEY: apiKey } = process.env; if (!pem || !apiKey) { console.log( "No Apple signing credentials, so the macOS app is left unsigned.", ); return; } const app = join( context.appOutDir, `${context.packager.appInfo.productFilename}.app`, ); const secrets = await mkdtemp(join(tmpdir(), "diorama64-signing-")); try { const pemFile = join(secrets, "developer-id.pem"); const keyFile = join(secrets, "notary-key.json"); await writeFile(pemFile, Buffer.from(pem, "base64"), { mode: 0o600 }); await writeFile(keyFile, Buffer.from(apiKey, "base64"), { mode: 0o600 }); // The entitlements electron-builder signs Electron apps with by default. const entitlements = join( modules, "app-builder-lib", "templates", "entitlements.mac.plist", ); rcodesign([ "sign", "--pem-source", pemFile, "--code-signature-flags", "runtime", "--entitlements-xml-file", entitlements, "--for-notarization", app, ]); rcodesign([ "notary-submit", "--api-key-file", keyFile, "--max-wait-seconds", "3600", "--staple", app, ]); } finally { await rm(secrets, { recursive: true, force: true }); } }