Enhance build configuration and scripts
Some checks failed
farmcontrol/farmcontrol-server/pipeline/head There was a failure building this commit

- Updated electrobun.config.ts to include environment-based settings for stability and external dependencies.
- Refactored package.json scripts for improved build processes and streamlined commands.
- Added new build scripts for Linux and renderer, while removing the obsolete build-server script.
- Enhanced pre-build validation to ensure required files are present and added logging for better visibility.
- Updated clean-build script to remove additional artifact directories for thorough cleanup.
This commit is contained in:
Tom Butcher 2026-08-01 21:13:51 +01:00
parent c14a12648a
commit 8741a2c6fd
12 changed files with 158 additions and 39 deletions

View File

@ -2,6 +2,15 @@ import type { ElectrobunConfig } from "electrobun";
import { readFileSync } from "node:fs";
const packageJson = JSON.parse(readFileSync("./package.json", "utf8"));
const buildEnv = process.env.ELECTROBUN_BUILD_ENV || "dev";
const isStable = buildEnv === "stable";
const nativeExternals = [
"canvas",
"@napi-rs/canvas",
"@img/sharp-libvips*",
"@napi-rs/*",
];
export default {
app: {
@ -9,6 +18,7 @@ export default {
identifier: "com.tombutcher.farmcontrolserver",
version: packageJson.version,
description: "Farm Control Server desktop host application",
urlSchemes: ["farmcontrolserver"],
},
runtime: {
exitOnLastWindowClosed: false,
@ -16,18 +26,23 @@ export default {
build: {
buildFolder: "build",
artifactFolder: "app_dist",
useAsar: isStable,
asarUnpack: ["*.node", "*.dll", "*.dylib", "*.so"],
bun: {
entrypoint: "src/bun/index.js",
external: nativeExternals,
packages: "external",
},
copy: {
"dist/app/index.html": "views/mainview/index.html",
"dist/app/assets": "views/mainview/assets",
"dist/mainview": "views/mainview",
"config.json": "config.json",
},
watch: ["scripts"],
watchIgnore: ["dist/**", "app_dist/**"],
watch: ["scripts", "src/app"],
watchIgnore: ["dist/**", "build/**", "app_dist/**"],
mac: {
bundleCEF: false,
codesign: isStable,
notarize: isStable,
},
linux: {
bundleCEF: false,

View File

@ -2,23 +2,16 @@
"name": "farmcontrol-server",
"version": "1.0.0",
"description": "Connects to moonraker and also manages the socket connection to the printer.",
"main": "build/index.js",
"bin": "build/index.js",
"type": "module",
"scripts": {
"start": "bun run build/server && bun build/index.js",
"start": "bun src/headless.js",
"dev": "cross-env NODE_ENV=development bun --watch src/index.js",
"dev:app": "concurrently \"cross-env NODE_ENV=development bun run dev:renderer\" \"cross-env NODE_ENV=development electrobun dev\"",
"dev:renderer": "vite src/app --port 5287 --no-open",
"build": "bun run cleanBuild && bun run build:server && bun run build:renderer",
"build:server": "bun run scripts/build-server.mjs",
"build:renderer": "vite build src/app",
"build:app": "bun run build && electrobun build --env=stable",
"build:app": "electrobun build --env=stable",
"build:app:mac": "bun scripts/build-macos.mjs",
"build:linux": "bun run cleanBuild && bun run build:server && bun run build:linux-binary && bun run build:linux-packages",
"build:linux-binary": "bun scripts/build-linux-binary.mjs",
"build:linux-packages": "bash scripts/build-linux-packages.sh",
"cleanBuild": "bun scripts/clean-build.mjs"
"build:linux": "bun scripts/build-linux.mjs",
"clean": "bun scripts/clean-build.mjs"
},
"author": "Tom Butcher",
"license": "ISC",

View File

@ -37,10 +37,19 @@ A Bun application that bridges communication between Farm Control cloud services
## Building
- Desktop app (current platform): `bun run build:app`
- Linux headless packages: `bun run build:linux`
Build configuration lives in `electrobun.config.ts` and follows the [Electrobun build configuration](https://framework.blackboard.sh/electrobun/apis/cli/build-configuration/) model:
Electrobun handles desktop packaging. Linux headless builds use `bun build --compile`.
- **preBuild** — validates sources, writes build metadata, and runs the Vite renderer build
- **postPackage** — normalizes release artifacts (DMG/PKG on macOS, MSI on Windows)
Commands:
- Desktop app (current platform): `bun run build:app`
- macOS universal (arm64 + x64): `bun run build:app:mac`
- Linux headless packages: `bun run build:linux`
- Clean build outputs: `bun run clean`
Desktop artifacts are written to `app_dist/`. Linux headless builds produce `.deb` and `.rpm` packages in the same folder.
## Configuration

35
scripts/build-linux.mjs Normal file
View File

@ -0,0 +1,35 @@
import { spawnSync } from "node:child_process";
import path from "node:path";
import { fileURLToPath } from "node:url";
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
function run(scriptRelativePath) {
const scriptPath = path.join(rootDir, scriptRelativePath);
const result = spawnSync("bun", [scriptPath], {
cwd: rootDir,
stdio: "inherit",
env: process.env,
});
if (result.status !== 0) {
process.exit(result.status ?? 1);
}
}
function runBash(scriptRelativePath) {
const scriptPath = path.join(rootDir, scriptRelativePath);
const result = spawnSync("bash", [scriptPath], {
cwd: rootDir,
stdio: "inherit",
env: process.env,
});
if (result.status !== 0) {
process.exit(result.status ?? 1);
}
}
run("scripts/write-build-info.mjs");
run("scripts/build-linux-binary.mjs");
runBash("scripts/build-linux-packages.sh");

View File

@ -41,8 +41,6 @@ function getElectrobunInvocation(targetArch) {
return null;
}
run("bun", ["run", "build"]);
for (const targetArch of targetArchs) {
const invocation = getElectrobunInvocation(targetArch);
if (!invocation) {

View File

@ -0,0 +1,22 @@
import { spawnSync } from "node:child_process";
import path from "node:path";
import { fileURLToPath } from "node:url";
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const result = spawnSync("bun", ["x", "vite", "build", "src/app"], {
cwd: rootDir,
stdio: "inherit",
env: {
...process.env,
NODE_ENV: process.env.NODE_ENV || "production",
},
});
if (result.status !== 0) {
process.exit(result.status ?? 1);
}
console.log(
`build-renderer: output at ${path.join(rootDir, "dist/mainview")}`,
);

View File

@ -1,15 +0,0 @@
import { cpSync, mkdirSync, rmSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const buildDir = path.join(rootDir, "build");
rmSync(buildDir, { recursive: true, force: true });
mkdirSync(buildDir, { recursive: true });
cpSync(path.join(rootDir, "src"), buildDir, { recursive: true });
cpSync(path.join(rootDir, "package.json"), path.join(buildDir, "package.json"));
cpSync(path.join(rootDir, "config.json"), path.join(buildDir, "config.json"));
console.log(`Server sources copied to ${buildDir}`);

View File

@ -4,6 +4,8 @@ import { fileURLToPath } from "node:url";
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
for (const dir of ["build", "dist"]) {
for (const dir of ["build", "dist", "app_dist", "artifacts"]) {
rmSync(path.join(rootDir, dir), { recursive: true, force: true });
}
console.log("clean: removed build, dist, app_dist, and artifacts");

View File

@ -1,5 +1,6 @@
import { existsSync } from "node:fs";
import path from "node:path";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
@ -7,8 +8,11 @@ const buildEnv = process.env.ELECTROBUN_BUILD_ENV || "dev";
const requiredFiles = [
"src/bun/index.js",
"src/app/index.html",
"src/app/main.jsx",
"electrobun.config.ts",
"package.json",
"config.json",
];
for (const relativePath of requiredFiles) {
@ -35,4 +39,45 @@ if (buildEnv === "stable" && process.env.ELECTROBUN_OS === "macos") {
}
}
console.log(`pre-build: validation passed (${buildEnv})`);
const writeBuildInfo = spawnSync(
"bun",
[path.join(rootDir, "scripts/write-build-info.mjs")],
{ cwd: rootDir, stdio: "inherit", env: process.env },
);
if (writeBuildInfo.status !== 0) {
process.exit(writeBuildInfo.status ?? 1);
}
if (buildEnv === "dev") {
console.log(
"pre-build: dev environment — skipping production renderer build (use dev:renderer for Vite)",
);
console.log(`pre-build: validation passed (${buildEnv})`);
process.exit(0);
}
const buildRenderer = spawnSync(
"bun",
[path.join(rootDir, "scripts/build-renderer.mjs")],
{
cwd: rootDir,
stdio: "inherit",
env: {
...process.env,
NODE_ENV: "production",
},
},
);
if (buildRenderer.status !== 0) {
process.exit(buildRenderer.status ?? 1);
}
const rendererIndex = path.join(rootDir, "dist/mainview/index.html");
if (!existsSync(rendererIndex)) {
console.error(`pre-build: renderer output not found: dist/mainview/index.html`);
process.exit(1);
}
console.log(`pre-build: validation and renderer build passed (${buildEnv})`);

View File

@ -0,0 +1,15 @@
import path from "node:path";
import { fileURLToPath } from "node:url";
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const buildNumber =
process.env.BUILD_NUMBER || process.env.VITE_BUILD_NUMBER || "dev";
const buildInfoPath = path.join(rootDir, "src/buildInfo.json");
await Bun.write(
buildInfoPath,
`${JSON.stringify({ buildNumber }, null, 2)}\n`,
);
console.log(`write-build-info: ${buildInfoPath} (buildNumber=${buildNumber})`);

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="stylesheet" href="./fonts.css" />
<link rel="stylesheet" href="/fonts.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Farm Control Server</title>
</head>

View File

@ -13,7 +13,7 @@ export default defineConfig({
plugins: [react(), svgr(), svgo()],
root: path.resolve(__dirname),
build: {
outDir: path.resolve(__dirname, "../../dist/app"),
outDir: path.resolve(__dirname, "../../dist/mainview"),
emptyOutDir: true,
rollupOptions: {
input: {