Add macOS code signing functionality and integrate into artifact finalization
All checks were successful
farmcontrol/farmcontrol-server/pipeline/head This commit looks good

- Introduced a new script, codesign-macos-app.mjs, to handle the code signing of macOS application bundles.
- Replaced the existing resignMacAppBundle function in finalize-desktop-artifacts.mjs with a call to the new codesignMacAppBundle function for improved clarity and maintainability.
- Enhanced error handling and logging during the code signing process to provide better feedback on failures.
- Updated the logic for resolving the app bundle path to streamline the signing process.
This commit is contained in:
Tom Butcher 2026-08-01 22:35:25 +01:00
parent 67cbe3ec6e
commit cf0643b7dd
2 changed files with 93 additions and 18 deletions

View File

@ -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);
}

View File

@ -19,6 +19,7 @@ import {
cleanExpandedWindowsApp, cleanExpandedWindowsApp,
expandWindowsAppFromArchive, expandWindowsAppFromArchive,
} from "./expand-windows-installer.mjs"; } from "./expand-windows-installer.mjs";
import { codesignMacAppBundle } from "./codesign-macos-app.mjs";
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const packageJson = JSON.parse( 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) { function canUseDirectDmgSourceFolder(appBundlePath) {
const platformDir = path.dirname(appBundlePath); const platformDir = path.dirname(appBundlePath);
const appName = path.basename(appBundlePath); const appName = path.basename(appBundlePath);
@ -290,7 +274,7 @@ function buildMacDmg(appBundlePath, arch) {
path.basename(appBundlePath), path.basename(appBundlePath),
); );
copyMacAppBundle(appBundlePath, stagedAppPath); copyMacAppBundle(appBundlePath, stagedAppPath);
resignMacAppBundle(stagedAppPath); codesignMacAppBundle(stagedAppPath);
} }
let builtDmgPath; let builtDmgPath;
@ -417,6 +401,8 @@ if (targetOs === "macos") {
process.exit(0); process.exit(0);
} }
codesignMacAppBundle(appBundle);
const existingDmg = findMacDmgSource(buildArch); const existingDmg = findMacDmgSource(buildArch);
const dmgPath = existingDmg const dmgPath = existingDmg
? publishArtifact(existingDmg, buildArch, "dmg") ? publishArtifact(existingDmg, buildArch, "dmg")