Refactor macOS bundle handling in expand-macos-bundle.mjs and finalize-desktop-artifacts.mjs
Some checks failed
farmcontrol/farmcontrol-server/pipeline/head There was a failure building this commit

- Replaced cpSync with ditto for copying files in expand-macos-bundle.mjs to improve reliability and error handling.
- Introduced a new copyMacAppBundle function in finalize-desktop-artifacts.mjs to streamline app bundle copying and signing processes.
- Enhanced error handling for ditto and codesign commands to provide clearer feedback on failures.
- Updated DMG creation logic to conditionally use direct source folder, optimizing the build process.
This commit is contained in:
Tom Butcher 2026-08-01 22:26:24 +01:00
parent 0aa26eab66
commit 935e06a5d7
2 changed files with 77 additions and 23 deletions

View File

@ -1,5 +1,4 @@
import {
cpSync,
existsSync,
mkdirSync,
readFileSync,
@ -111,7 +110,13 @@ const innerContentsPath = path.join(resolvedInnerApp, "Contents");
function replaceDirectory(sourceDir, destinationDir) {
rmSync(destinationDir, { recursive: true, force: true });
cpSync(sourceDir, destinationDir, { recursive: true, dereference: true });
const result = spawnSync("ditto", [sourceDir, destinationDir], {
stdio: "inherit",
});
if (result.status !== 0) {
console.error("expand-macos-bundle: ditto copy failed");
process.exit(result.status ?? 1);
}
}
replaceDirectory(
@ -120,10 +125,7 @@ replaceDirectory(
);
rmSync(resourcesPath, { recursive: true, force: true });
cpSync(path.join(innerContentsPath, "Resources"), resourcesPath, {
recursive: true,
dereference: true,
});
replaceDirectory(path.join(innerContentsPath, "Resources"), resourcesPath);
const innerFrameworks = path.join(innerContentsPath, "Frameworks");
const wrapperFrameworks = path.join(contentsPath, "Frameworks");
@ -131,7 +133,15 @@ if (existsSync(innerFrameworks)) {
replaceDirectory(innerFrameworks, wrapperFrameworks);
}
cpSync(path.join(innerContentsPath, "Info.plist"), path.join(contentsPath, "Info.plist"));
const infoPlistResult = spawnSync(
"ditto",
[path.join(innerContentsPath, "Info.plist"), path.join(contentsPath, "Info.plist")],
{ stdio: "inherit" },
);
if (infoPlistResult.status !== 0) {
console.error("expand-macos-bundle: Info.plist copy failed");
process.exit(infoPlistResult.status ?? 1);
}
rmSync(workDir, { recursive: true, force: true });

View File

@ -190,18 +190,48 @@ function cleanStagingArtifacts(keepNames) {
const CREATE_DMG_PATH = "/usr/local/bin/create-dmg";
function buildMacDmgWithCreateDmg(
createDmg,
appBundlePath,
dmgPath,
stagingDir,
) {
const appName = path.basename(appBundlePath);
cpSync(appBundlePath, path.join(stagingDir, appName), {
recursive: true,
dereference: true,
function copyMacAppBundle(sourcePath, destinationPath) {
rmSync(destinationPath, { recursive: true, force: true });
mkdirSync(path.dirname(destinationPath), { recursive: true });
const result = spawnSync("ditto", [sourcePath, destinationPath], {
stdio: "inherit",
});
if (result.status !== 0) {
throw new Error(`ditto failed with exit code ${result.status ?? 1}`);
}
}
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);
const entries = readdirSync(platformDir).filter(
(entry) =>
!entry.startsWith(".") && entry !== ".finalize-dmg-staging",
);
return entries.length === 1 && entries[0] === appName;
}
function buildMacDmgWithCreateDmg(createDmg, dmgPath, sourceFolder) {
rmSync(dmgPath, { force: true });
const result = spawnSync(
@ -215,7 +245,7 @@ function buildMacDmgWithCreateDmg(
"--format",
"UDZO",
dmgPath,
stagingDir,
sourceFolder,
],
{ stdio: "inherit" },
);
@ -247,20 +277,34 @@ function buildMacDmg(appBundlePath, arch) {
path.dirname(appBundlePath),
".finalize-dmg-staging",
);
rmSync(stagingDir, { recursive: true, force: true });
mkdirSync(stagingDir, { recursive: true });
const useDirectSource = canUseDirectDmgSourceFolder(appBundlePath);
const sourceFolder = useDirectSource
? path.dirname(appBundlePath)
: stagingDir;
if (!useDirectSource) {
rmSync(stagingDir, { recursive: true, force: true });
mkdirSync(stagingDir, { recursive: true });
const stagedAppPath = path.join(
stagingDir,
path.basename(appBundlePath),
);
copyMacAppBundle(appBundlePath, stagedAppPath);
resignMacAppBundle(stagedAppPath);
}
let builtDmgPath;
try {
builtDmgPath = buildMacDmgWithCreateDmg(
CREATE_DMG_PATH,
appBundlePath,
dmgPath,
stagingDir,
sourceFolder,
);
} finally {
rmSync(stagingDir, { recursive: true, force: true });
if (!useDirectSource) {
rmSync(stagingDir, { recursive: true, force: true });
}
}
console.log(`Published ${builtDmgPath}`);