Add postWrap script for macOS bundle expansion and implement expand-macos-bundle script
Some checks failed
farmcontrol/farmcontrol-server/pipeline/head There was a failure building this commit

- Updated electrobun.config.ts to include a new postWrap script for expanding macOS bundles.
- Introduced expand-macos-bundle.mjs script to handle the extraction and setup of macOS application bundles, including metadata validation and resource management.
- Enhanced error handling and logging for better visibility during the expansion process.
This commit is contained in:
Tom Butcher 2026-08-01 21:38:05 +01:00
parent 6f3c312f6d
commit cda0286db7
2 changed files with 139 additions and 0 deletions

View File

@ -54,6 +54,7 @@ export default {
}, },
scripts: { scripts: {
preBuild: "scripts/pre-build.mjs", preBuild: "scripts/pre-build.mjs",
postWrap: "scripts/expand-macos-bundle.mjs",
postPackage: "scripts/finalize-desktop-artifacts.mjs", postPackage: "scripts/finalize-desktop-artifacts.mjs",
}, },
release: { release: {

View File

@ -0,0 +1,138 @@
import {
cpSync,
existsSync,
mkdirSync,
readFileSync,
readdirSync,
rmSync,
} from "node:fs";
import path from "node:path";
import { spawnSync } from "node:child_process";
import { tmpdir } from "node:os";
import { fileURLToPath } from "node:url";
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const buildEnv = process.env.ELECTROBUN_BUILD_ENV || "dev";
const targetOs = process.env.ELECTROBUN_OS || "macos";
const wrapperPath = process.env.ELECTROBUN_WRAPPER_BUNDLE_PATH;
if (buildEnv === "dev" || targetOs !== "macos") {
console.log("expand-macos-bundle: skipping (dev or non-macOS build)");
process.exit(0);
}
if (!wrapperPath || !existsSync(wrapperPath)) {
console.error("expand-macos-bundle: ELECTROBUN_WRAPPER_BUNDLE_PATH not set or missing");
process.exit(1);
}
const resolvedWrapperPath = path.resolve(wrapperPath);
const contentsPath = path.join(resolvedWrapperPath, "Contents");
const resourcesPath = path.join(contentsPath, "Resources");
const metadataPath = path.join(resourcesPath, "metadata.json");
if (!existsSync(metadataPath)) {
console.log("expand-macos-bundle: no extractor metadata, assuming already expanded");
process.exit(0);
}
const metadata = JSON.parse(readFileSync(metadataPath, "utf8"));
const hash = metadata.hash;
const appName = metadata.name || "Farm Control Server";
const tarZstPath = path.resolve(resourcesPath, `${hash}.tar.zst`);
if (!existsSync(tarZstPath)) {
console.log(`expand-macos-bundle: ${hash}.tar.zst not found, skipping`);
process.exit(0);
}
function findZstd() {
const fromPath = spawnSync("zstd", ["--version"], { encoding: "utf8" });
if (fromPath.status === 0) {
return "zstd";
}
const hostArch = process.arch === "arm64" ? "arm64" : "x64";
const electrobunZstd = path.join(
rootDir,
"node_modules/electrobun/dist-macos-" + hostArch,
"zig-zstd",
);
if (existsSync(electrobunZstd)) {
return electrobunZstd;
}
throw new Error(
"expand-macos-bundle: zstd not found (install zstd or use electrobun dist binaries)",
);
}
const workDir = path.join(tmpdir(), `farmcontrol-server-expand-${hash}`);
const tarPath = path.join(workDir, `${hash}.tar`);
rmSync(workDir, { recursive: true, force: true });
mkdirSync(workDir, { recursive: true });
const zstd = findZstd();
const decompress = spawnSync(zstd, ["-d", "-f", "-o", tarPath, tarZstPath], {
stdio: "inherit",
});
if (decompress.status !== 0) {
console.error("expand-macos-bundle: zstd decompression failed");
process.exit(decompress.status ?? 1);
}
const extractTar = spawnSync("tar", ["-xf", tarPath, "-C", workDir], {
stdio: "inherit",
});
if (extractTar.status !== 0) {
console.error("expand-macos-bundle: tar extraction failed");
process.exit(extractTar.status ?? 1);
}
const innerAppPath = path.join(workDir, `${appName}.app`);
if (!existsSync(innerAppPath)) {
const appBundle = readdirSync(workDir).find((entry) => entry.endsWith(".app"));
if (!appBundle) {
console.error("expand-macos-bundle: extracted app bundle not found");
process.exit(1);
}
}
const resolvedInnerApp = existsSync(innerAppPath)
? innerAppPath
: path.join(workDir, readdirSync(workDir).find((e) => e.endsWith(".app")));
const innerContentsPath = path.join(resolvedInnerApp, "Contents");
function replaceDirectory(sourceDir, destinationDir) {
rmSync(destinationDir, { recursive: true, force: true });
cpSync(sourceDir, destinationDir, { recursive: true, dereference: true });
}
replaceDirectory(
path.join(innerContentsPath, "MacOS"),
path.join(contentsPath, "MacOS"),
);
rmSync(resourcesPath, { recursive: true, force: true });
cpSync(path.join(innerContentsPath, "Resources"), resourcesPath, {
recursive: true,
dereference: true,
});
const innerFrameworks = path.join(innerContentsPath, "Frameworks");
const wrapperFrameworks = path.join(contentsPath, "Frameworks");
if (existsSync(innerFrameworks)) {
replaceDirectory(innerFrameworks, wrapperFrameworks);
}
cpSync(path.join(innerContentsPath, "Info.plist"), path.join(contentsPath, "Info.plist"));
rmSync(workDir, { recursive: true, force: true });
if (process.platform === "darwin") {
spawnSync("xattr", ["-cr", resolvedWrapperPath], { stdio: "inherit" });
}
console.log(`expand-macos-bundle: pre-expanded ${resolvedWrapperPath}`);