Enhance Electrobun configuration and build scripts
Some checks reported errors
farmcontrol/farmcontrol-server/pipeline/head Something is wrong with the build of this commit

- Updated electrobun.config.ts to dynamically set the application version from package.json and added a release configuration for base URL.
- Modified Jenkinsfile to include the ELECTROBUN_RELEASE_BASE_URL environment variable during the build process.
- Refactored build-macos.mjs to streamline the build command execution for different architectures.
- Enhanced finalize-desktop-artifacts.mjs to improve artifact handling and introduced a new pre-build script for validation of required files and environment variables.
- Improved error handling and logging for better build process visibility.
This commit is contained in:
Tom Butcher 2026-08-01 20:12:30 +01:00
parent 8b8475d11d
commit d867d017bb
5 changed files with 122 additions and 59 deletions

7
Jenkinsfile vendored
View File

@ -70,6 +70,9 @@ def buildLinux() {
} }
} }
def releaseBaseUrl =
"https://dist.farmcontrol.app/jenkins/farmcontrol/farmcontrol-server"
def buildOnLabel(label, buildCommand) { def buildOnLabel(label, buildCommand) {
return { return {
node(label) { node(label) {
@ -96,9 +99,9 @@ def buildOnLabel(label, buildCommand) {
stage("Build (${label})") { stage("Build (${label})") {
if (isUnix()) { if (isUnix()) {
sh "VITE_BUILD_NUMBER=${env.BUILD_NUMBER} NODE_ENV=production ${buildCommand}" sh "VITE_BUILD_NUMBER=${env.BUILD_NUMBER} NODE_ENV=production ELECTROBUN_RELEASE_BASE_URL=${releaseBaseUrl} ${buildCommand}"
} else { } else {
bat "set VITE_BUILD_NUMBER=${env.BUILD_NUMBER} && set NODE_ENV=production && ${buildCommand}" bat "set VITE_BUILD_NUMBER=${env.BUILD_NUMBER} && set NODE_ENV=production && set ELECTROBUN_RELEASE_BASE_URL=${releaseBaseUrl} && ${buildCommand}"
} }
} }
} }

View File

@ -1,11 +1,18 @@
// Electrobun requires this filename; application code remains JavaScript. import type { ElectrobunConfig } from "electrobun";
import { readFileSync } from "node:fs";
const packageJson = JSON.parse(readFileSync("./package.json", "utf8"));
export default { export default {
app: { app: {
name: "Farm Control Server", name: "Farm Control Server",
identifier: "com.tombutcher.farmcontrolserver", identifier: "com.tombutcher.farmcontrolserver",
version: "1.0.0", version: packageJson.version,
description: "Farm Control Server desktop host application", description: "Farm Control Server desktop host application",
}, },
runtime: {
exitOnLastWindowClosed: false,
},
build: { build: {
buildFolder: "build", buildFolder: "build",
artifactFolder: "app_dist", artifactFolder: "app_dist",
@ -17,7 +24,8 @@ export default {
"dist/app/assets": "views/mainview/assets", "dist/app/assets": "views/mainview/assets",
"config.json": "config.json", "config.json": "config.json",
}, },
watchIgnore: ["dist/**"], watch: ["scripts"],
watchIgnore: ["dist/**", "app_dist/**"],
mac: { mac: {
bundleCEF: false, bundleCEF: false,
}, },
@ -28,10 +36,14 @@ export default {
bundleCEF: false, bundleCEF: false,
}, },
}, },
runtime: {
exitOnLastWindowClosed: false,
},
scripts: { scripts: {
preBuild: "scripts/pre-build.mjs",
postPackage: "scripts/finalize-desktop-artifacts.mjs", postPackage: "scripts/finalize-desktop-artifacts.mjs",
}, },
}; release: {
baseUrl:
process.env.ELECTROBUN_RELEASE_BASE_URL ||
process.env.RELEASE_BASE_URL ||
"",
},
} satisfies ElectrobunConfig;

View File

@ -3,7 +3,6 @@ import { fileURLToPath } from "node:url";
import path from "node:path"; import path from "node:path";
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const hostArch = process.arch === "arm64" ? "arm64" : "x64";
const targetArchs = ["arm64", "x64"]; const targetArchs = ["arm64", "x64"];
function run(command, args, options = {}) { function run(command, args, options = {}) {
@ -19,35 +18,15 @@ function run(command, args, options = {}) {
} }
} }
function getElectrobunCommand(targetArch) {
const electrobunArgs = ["electrobun", "build", "--env=stable"];
if (targetArch === hostArch) {
return { command: "bun", args: electrobunArgs };
}
if (hostArch === "arm64" && targetArch === "x64") {
return { command: "arch", args: ["-x86_64", "bun", ...electrobunArgs] };
}
if (hostArch === "x64" && targetArch === "arm64") {
return { command: "arch", args: ["-arm64", "bun", ...electrobunArgs] };
}
return null;
}
run("bun", ["run", "build"]); run("bun", ["run", "build"]);
for (const targetArch of targetArchs) { for (const targetArch of targetArchs) {
const invocation = getElectrobunCommand(targetArch);
if (!invocation) {
console.warn(
`Skipping macOS ${targetArch} build: unsupported cross-compile from ${hostArch}.`,
);
continue;
}
console.log(`\n=== Building macOS ${targetArch} ===\n`); console.log(`\n=== Building macOS ${targetArch} ===\n`);
run(invocation.command, invocation.args);
run("bun", ["electrobun", "build", "--env=stable"], {
env: {
...process.env,
ELECTROBUN_ARCH: targetArch,
},
});
} }

View File

@ -20,11 +20,41 @@ const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
const packageJson = JSON.parse( const packageJson = JSON.parse(
readFileSync(path.join(rootDir, "package.json"), "utf8"), readFileSync(path.join(rootDir, "package.json"), "utf8"),
); );
const version = getReleaseVersion(packageJson); const buildEnv = process.env.ELECTROBUN_BUILD_ENV || "stable";
const artifactDir = path.join(rootDir, "app_dist"); const targetOs =
const identifier = "com.tombutcher.farmcontrolserver"; process.env.ELECTROBUN_OS ||
(process.platform === "darwin"
? "macos"
: process.platform === "win32"
? "win"
: process.platform === "linux"
? "linux"
: null);
const buildArch = getReleaseArch(
process.env.ELECTROBUN_ARCH || process.arch,
);
const version =
process.env.ELECTROBUN_APP_VERSION || getReleaseVersion(packageJson);
const artifactDir =
process.env.ELECTROBUN_ARTIFACT_DIR || path.join(rootDir, "app_dist");
const identifier =
process.env.ELECTROBUN_APP_IDENTIFIER || "com.tombutcher.farmcontrolserver";
const artifactPrefix = `farmcontrol-server-${version}-`; const artifactPrefix = `farmcontrol-server-${version}-`;
function getBuildRoot() {
const electrobunBuildDir = process.env.ELECTROBUN_BUILD_DIR;
if (!electrobunBuildDir) {
return path.join(rootDir, "build");
}
const baseName = path.basename(electrobunBuildDir);
if (baseName.startsWith("stable-")) {
return path.dirname(electrobunBuildDir);
}
return electrobunBuildDir;
}
function walkFiles(dir) { function walkFiles(dir) {
const files = []; const files = [];
if (!existsSync(dir)) { if (!existsSync(dir)) {
@ -51,7 +81,7 @@ function findByExtension(root, extension) {
} }
function findMacAppBundle(arch) { function findMacAppBundle(arch) {
const platformDir = path.join(rootDir, "build", `stable-macos-${arch}`); const platformDir = path.join(getBuildRoot(), `stable-macos-${arch}`);
if (!existsSync(platformDir)) { if (!existsSync(platformDir)) {
return null; return null;
} }
@ -76,7 +106,7 @@ function findMacDmgSource(arch) {
} }
const buildDmg = findByExtension( const buildDmg = findByExtension(
path.join(rootDir, "build", `stable-macos-${arch}`), path.join(getBuildRoot(), `stable-macos-${arch}`),
".dmg", ".dmg",
); );
if (buildDmg) { if (buildDmg) {
@ -87,30 +117,26 @@ function findMacDmgSource(arch) {
} }
function findWindowsInstallerFiles() { function findWindowsInstallerFiles() {
const buildDir = path.join(rootDir, "build"); const buildRoot = getBuildRoot();
if (!existsSync(buildDir)) { if (!existsSync(buildRoot)) {
return null; return null;
} }
for (const entry of readdirSync(buildDir)) { for (const entry of readdirSync(buildRoot)) {
if (!entry.startsWith("stable-win-")) { if (!entry.startsWith("stable-win-")) {
continue; continue;
} }
const platformDir = path.join(buildDir, entry); const platformDir = path.join(buildRoot, entry);
const files = walkFiles(platformDir); const files = walkFiles(platformDir);
const setupExe = files.find((filePath) => const setupExe = files.find((filePath) => /-setup\.exe$/i.test(filePath));
/-setup\.exe$/i.test(filePath),
);
const setupArchive = files.find((filePath) => const setupArchive = files.find((filePath) =>
/-setup\.tar\.zst$/i.test(filePath), /-setup\.tar\.zst$/i.test(filePath),
); );
const setupMetadata = files.find((filePath) => const setupMetadata = files.find((filePath) =>
/-setup\.metadata\.json$/i.test(filePath), /-setup\.metadata\.json$/i.test(filePath),
); );
const setupZip = files.find((filePath) => const setupZip = files.find((filePath) => /-setup\.zip$/i.test(filePath));
/-setup\.zip$/i.test(filePath),
);
if (setupExe && setupArchive && setupMetadata) { if (setupExe && setupArchive && setupMetadata) {
return { setupExe, setupArchive, setupMetadata, setupZip }; return { setupExe, setupArchive, setupMetadata, setupZip };
@ -235,10 +261,12 @@ function buildWindowsMsi(installerFiles, arch) {
return msiPath; return msiPath;
} }
if (process.platform === "darwin") { if (buildEnv === "dev") {
const buildArch = getReleaseArch( console.log("finalize-desktop-artifacts: skipping dev build");
process.env.ELECTROBUN_ARCH || process.arch, process.exit(0);
); }
if (targetOs === "macos") {
const dmgSource = findMacDmgSource(buildArch); const dmgSource = findMacDmgSource(buildArch);
const appBundle = findMacAppBundle(buildArch); const appBundle = findMacAppBundle(buildArch);
@ -255,8 +283,8 @@ if (process.platform === "darwin") {
]; ];
cleanStagingArtifacts(published.map((filePath) => path.basename(filePath))); cleanStagingArtifacts(published.map((filePath) => path.basename(filePath)));
} else if (process.platform === "win32") { } else if (targetOs === "win") {
const arch = "x64"; const arch = getReleaseArch(process.env.ELECTROBUN_ARCH || "x64");
const installerFiles = findWindowsInstallerFiles(); const installerFiles = findWindowsInstallerFiles();
if (!installerFiles) { if (!installerFiles) {
@ -273,6 +301,9 @@ if (process.platform === "darwin") {
cleanStagingArtifacts(published.map((filePath) => path.basename(filePath))); cleanStagingArtifacts(published.map((filePath) => path.basename(filePath)));
} else { } else {
console.log("finalize-desktop-artifacts: skipping unsupported platform", process.platform); console.log(
"finalize-desktop-artifacts: no desktop packaging configured for",
targetOs ?? "unknown target",
);
process.exit(0); process.exit(0);
} }

38
scripts/pre-build.mjs Normal file
View File

@ -0,0 +1,38 @@
import { existsSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const buildEnv = process.env.ELECTROBUN_BUILD_ENV || "dev";
const requiredFiles = [
"src/bun/index.js",
"electrobun.config.ts",
"package.json",
];
for (const relativePath of requiredFiles) {
const filePath = path.join(rootDir, relativePath);
if (!existsSync(filePath)) {
console.error(`pre-build: required file not found: ${relativePath}`);
process.exit(1);
}
}
if (buildEnv === "stable" && process.env.ELECTROBUN_OS === "macos") {
const codesignVars = [
"ELECTROBUN_DEVELOPER_ID",
"ELECTROBUN_APPLEID",
"ELECTROBUN_APPLEIDPASS",
"ELECTROBUN_TEAMID",
];
const missing = codesignVars.filter((name) => !process.env[name]);
if (missing.length > 0) {
console.warn(
`pre-build: stable macOS build without code signing credentials (${missing.join(", ")}); Electrobun will skip signing.`,
);
}
}
console.log(`pre-build: validation passed (${buildEnv})`);