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) {
return {
node(label) {
@ -96,9 +99,9 @@ def buildOnLabel(label, buildCommand) {
stage("Build (${label})") {
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 {
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 {
app: {
name: "Farm Control Server",
identifier: "com.tombutcher.farmcontrolserver",
version: "1.0.0",
version: packageJson.version,
description: "Farm Control Server desktop host application",
},
runtime: {
exitOnLastWindowClosed: false,
},
build: {
buildFolder: "build",
artifactFolder: "app_dist",
@ -17,7 +24,8 @@ export default {
"dist/app/assets": "views/mainview/assets",
"config.json": "config.json",
},
watchIgnore: ["dist/**"],
watch: ["scripts"],
watchIgnore: ["dist/**", "app_dist/**"],
mac: {
bundleCEF: false,
},
@ -28,10 +36,14 @@ export default {
bundleCEF: false,
},
},
runtime: {
exitOnLastWindowClosed: false,
},
scripts: {
preBuild: "scripts/pre-build.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";
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const hostArch = process.arch === "arm64" ? "arm64" : "x64";
const targetArchs = ["arm64", "x64"];
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"]);
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`);
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(
readFileSync(path.join(rootDir, "package.json"), "utf8"),
);
const version = getReleaseVersion(packageJson);
const artifactDir = path.join(rootDir, "app_dist");
const identifier = "com.tombutcher.farmcontrolserver";
const buildEnv = process.env.ELECTROBUN_BUILD_ENV || "stable";
const targetOs =
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}-`;
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) {
const files = [];
if (!existsSync(dir)) {
@ -51,7 +81,7 @@ function findByExtension(root, extension) {
}
function findMacAppBundle(arch) {
const platformDir = path.join(rootDir, "build", `stable-macos-${arch}`);
const platformDir = path.join(getBuildRoot(), `stable-macos-${arch}`);
if (!existsSync(platformDir)) {
return null;
}
@ -76,7 +106,7 @@ function findMacDmgSource(arch) {
}
const buildDmg = findByExtension(
path.join(rootDir, "build", `stable-macos-${arch}`),
path.join(getBuildRoot(), `stable-macos-${arch}`),
".dmg",
);
if (buildDmg) {
@ -87,30 +117,26 @@ function findMacDmgSource(arch) {
}
function findWindowsInstallerFiles() {
const buildDir = path.join(rootDir, "build");
if (!existsSync(buildDir)) {
const buildRoot = getBuildRoot();
if (!existsSync(buildRoot)) {
return null;
}
for (const entry of readdirSync(buildDir)) {
for (const entry of readdirSync(buildRoot)) {
if (!entry.startsWith("stable-win-")) {
continue;
}
const platformDir = path.join(buildDir, entry);
const platformDir = path.join(buildRoot, entry);
const files = walkFiles(platformDir);
const setupExe = files.find((filePath) =>
/-setup\.exe$/i.test(filePath),
);
const setupExe = files.find((filePath) => /-setup\.exe$/i.test(filePath));
const setupArchive = files.find((filePath) =>
/-setup\.tar\.zst$/i.test(filePath),
);
const setupMetadata = files.find((filePath) =>
/-setup\.metadata\.json$/i.test(filePath),
);
const setupZip = files.find((filePath) =>
/-setup\.zip$/i.test(filePath),
);
const setupZip = files.find((filePath) => /-setup\.zip$/i.test(filePath));
if (setupExe && setupArchive && setupMetadata) {
return { setupExe, setupArchive, setupMetadata, setupZip };
@ -235,10 +261,12 @@ function buildWindowsMsi(installerFiles, arch) {
return msiPath;
}
if (process.platform === "darwin") {
const buildArch = getReleaseArch(
process.env.ELECTROBUN_ARCH || process.arch,
);
if (buildEnv === "dev") {
console.log("finalize-desktop-artifacts: skipping dev build");
process.exit(0);
}
if (targetOs === "macos") {
const dmgSource = findMacDmgSource(buildArch);
const appBundle = findMacAppBundle(buildArch);
@ -255,8 +283,8 @@ if (process.platform === "darwin") {
];
cleanStagingArtifacts(published.map((filePath) => path.basename(filePath)));
} else if (process.platform === "win32") {
const arch = "x64";
} else if (targetOs === "win") {
const arch = getReleaseArch(process.env.ELECTROBUN_ARCH || "x64");
const installerFiles = findWindowsInstallerFiles();
if (!installerFiles) {
@ -273,6 +301,9 @@ if (process.platform === "darwin") {
cleanStagingArtifacts(published.map((filePath) => path.basename(filePath)));
} 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);
}

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