Switch package manager and runtime from Node/pnpm to Bun.
Update build scripts, entry points, and packaged-path detection for the new toolchain. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
07cbd2e07c
commit
8453e7c7cf
6
.npmrc
6
.npmrc
@ -1,6 +0,0 @@
|
|||||||
public-hoist-pattern[]=pdf-to-img
|
|
||||||
public-hoist-pattern[]=pdfjs-dist
|
|
||||||
public-hoist-pattern[]=sharp
|
|
||||||
public-hoist-pattern[]=@img/*
|
|
||||||
public-hoist-pattern[]=canvas
|
|
||||||
public-hoist-pattern[]=@napi-rs/*
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"ignore": ["node_modules/*", "*.log", "public/*", "config.json"]
|
|
||||||
}
|
|
||||||
92
package.json
92
package.json
@ -6,20 +6,19 @@
|
|||||||
"bin": "build/index.js",
|
"bin": "build/index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node build/index.js",
|
"start": "bun run build/server && bun build/index.js",
|
||||||
"dev": "cross-env NODE_ENV=development nodemon src/index.js",
|
"dev": "cross-env NODE_ENV=development bun --watch src/index.js",
|
||||||
"dev:electron": "concurrently \"cross-env NODE_ENV=development vite src/electron --port 5287 --no-open\" \"cross-env NODE_ENV=development electron src/index.js\"",
|
"dev:app": "concurrently \"cross-env NODE_ENV=development bun run dev:renderer\" \"cross-env NODE_ENV=development electrobun dev\"",
|
||||||
"build": "pnpm run cleanBuild && pnpm build:server && pnpm build:renderer",
|
"dev:renderer": "vite src/app --port 5287 --no-open",
|
||||||
"build:server": "shx mkdir -p build && shx cp -r src/. build/ && shx cp package.json config.json build/",
|
"build": "bun run cleanBuild && bun run build:server && bun run build:renderer",
|
||||||
"build:renderer": "pnpm build:electron-renderer && shx cp src/electron/preload.js build/electron/ && shx rm -rf build/electron/App.jsx build/electron/main.jsx build/electron/App.css build/electron/index.css build/electron/FarmControlLogo.jsx build/electron/vite.config.js build/electron/public build/electron/build",
|
"build:server": "bun run scripts/build-server.mjs",
|
||||||
"build:electron-renderer": "vite build src/electron --outDir build/electron",
|
"build:renderer": "vite build src/app",
|
||||||
"build:electron": "pnpm build && electron-builder",
|
"build:app": "bun run build && electrobun build --env=stable",
|
||||||
"build:electron:mac": "pnpm build && electron-builder --mac dmg --arm64 --x64 && electron-builder --mac pkg --arm64 && electron-builder --mac pkg --x64",
|
"build:app:mac": "bun run build && electrobun build --env=stable",
|
||||||
"build:linux": "pnpm run cleanBuild && pnpm run build:server && pnpm run build:linux-bundle && pnpm run build:linux-pkg && pnpm run build:linux-packages",
|
"build:linux": "bun run cleanBuild && bun run build:server && bun run build:linux-binary && bun run build:linux-packages",
|
||||||
"build:linux-bundle": "node scripts/build-linux-bundle.mjs",
|
"build:linux-binary": "bun scripts/build-linux-binary.mjs",
|
||||||
"build:linux-pkg": "shx mkdir -p app_dist/linux && pnpm exec pkg build/pkg-entry.cjs --config package.json --targets node18-linux-x64 --no-bytecode --public-packages \"pdf-to-img,pdfjs-dist,sharp,canvas,@img/*,@napi-rs/*\" --output app_dist/linux/farmcontrol-server",
|
|
||||||
"build:linux-packages": "bash scripts/build-linux-packages.sh",
|
"build:linux-packages": "bash scripts/build-linux-packages.sh",
|
||||||
"cleanBuild": "rimraf build"
|
"cleanBuild": "rm -rf build dist"
|
||||||
},
|
},
|
||||||
"author": "Tom Butcher",
|
"author": "Tom Butcher",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
@ -47,83 +46,18 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ant-design/icons": "^6.2.3",
|
"@ant-design/icons": "^6.2.3",
|
||||||
"@electron/rebuild": "^4.0.4",
|
|
||||||
"@vitejs/plugin-react": "^6.0.2",
|
"@vitejs/plugin-react": "^6.0.2",
|
||||||
"antd": "^5.29.2",
|
"antd": "^5.29.2",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
"cross-env": "^10.1.0",
|
"cross-env": "^10.1.0",
|
||||||
"electron": "^38.7.1",
|
"electrobun": "^1.18.1",
|
||||||
"electron-builder": "^26.0.12",
|
|
||||||
"esbuild": "^0.25.12",
|
|
||||||
"jest": "^30.4.2",
|
"jest": "^30.4.2",
|
||||||
"nodemon": "^3.1.14",
|
|
||||||
"pkg": "^5.8.1",
|
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react": "^19.2.6",
|
"react": "^19.2.6",
|
||||||
"react-dom": "^19.2.6",
|
"react-dom": "^19.2.6",
|
||||||
"rimraf": "^6.1.3",
|
|
||||||
"shx": "^0.4.0",
|
|
||||||
"supertest": "^7.2.2",
|
"supertest": "^7.2.2",
|
||||||
"vite": "^8.0.13",
|
"vite": "^8.0.13",
|
||||||
"vite-plugin-svgo": "^2.0.0",
|
"vite-plugin-svgo": "^2.0.0",
|
||||||
"vite-plugin-svgr": "^5.2.0"
|
"vite-plugin-svgr": "^5.2.0"
|
||||||
},
|
|
||||||
"pkg": {
|
|
||||||
"scripts": [
|
|
||||||
"build/pkg-entry.cjs"
|
|
||||||
],
|
|
||||||
"assets": [
|
|
||||||
"build/config.json",
|
|
||||||
"node_modules/pdf-to-img/**/*",
|
|
||||||
"node_modules/pdfjs-dist/**/*",
|
|
||||||
"node_modules/sharp/**/*",
|
|
||||||
"node_modules/@img/**/*",
|
|
||||||
"node_modules/canvas/**/*",
|
|
||||||
"node_modules/@napi-rs/**/*"
|
|
||||||
],
|
|
||||||
"targets": [
|
|
||||||
"node18-linux-x64"
|
|
||||||
],
|
|
||||||
"outputPath": "app_dist/linux"
|
|
||||||
},
|
|
||||||
"build": {
|
|
||||||
"appId": "com.tombutcher.farmcontrolserver",
|
|
||||||
"productName": "Farm Control Server",
|
|
||||||
"executableName": "farmcontrol-server",
|
|
||||||
"artifactName": "farmcontrol-server-${version}-${arch}.${ext}",
|
|
||||||
"icon": "assets/farmcontrolhosticon.png",
|
|
||||||
"directories": {
|
|
||||||
"output": "app_dist"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"build/**/*",
|
|
||||||
"node_modules/**/*"
|
|
||||||
],
|
|
||||||
"mac": {
|
|
||||||
"target": [
|
|
||||||
"dmg",
|
|
||||||
"pkg"
|
|
||||||
],
|
|
||||||
"mergeASARs": true,
|
|
||||||
"x64ArchFiles": "**/node_modules/@esbuild/darwin-x64/**",
|
|
||||||
"singleArchFiles": "**/node_modules/**"
|
|
||||||
},
|
|
||||||
"win": {
|
|
||||||
"target": [
|
|
||||||
"nsis",
|
|
||||||
"msiWrapped"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nsis": {
|
|
||||||
"oneClick": false,
|
|
||||||
"allowToChangeInstallationDirectory": true,
|
|
||||||
"perMachine": true
|
|
||||||
},
|
|
||||||
"msiWrapped": {
|
|
||||||
"upgradeCode": "{A4E29F1C-8B3D-5E2A-9C71-1D6F8E4A2B90}",
|
|
||||||
"perMachine": true,
|
|
||||||
"impersonate": false,
|
|
||||||
"wrappedInstallerArgs": "/S"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9771
pnpm-lock.yaml
generated
9771
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,9 +0,0 @@
|
|||||||
allowBuilds:
|
|
||||||
canvas: true
|
|
||||||
chromedriver: true
|
|
||||||
electron-winstaller: true
|
|
||||||
electron: true
|
|
||||||
esbuild: true
|
|
||||||
protobufjs: true
|
|
||||||
sharp: true
|
|
||||||
unrs-resolver: true
|
|
||||||
70
scripts/build-linux-binary.mjs
Normal file
70
scripts/build-linux-binary.mjs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||||
|
const packageJson = JSON.parse(
|
||||||
|
readFileSync(path.join(rootDir, "package.json"), "utf8"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let buildNumber =
|
||||||
|
process.env.BUILD_NUMBER || process.env.VITE_BUILD_NUMBER || "dev";
|
||||||
|
try {
|
||||||
|
const buildInfo = JSON.parse(
|
||||||
|
readFileSync(path.join(rootDir, "src/buildInfo.json"), "utf8"),
|
||||||
|
);
|
||||||
|
buildNumber = buildInfo.buildNumber ?? buildNumber;
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
const outputDir = path.join(rootDir, "app_dist/linux");
|
||||||
|
const outputFile = path.join(outputDir, "farmcontrol-server");
|
||||||
|
|
||||||
|
await Bun.$`mkdir -p ${outputDir}`;
|
||||||
|
|
||||||
|
const result = await Bun.build({
|
||||||
|
entrypoints: [path.join(rootDir, "src/headless.js")],
|
||||||
|
outdir: path.join(rootDir, "build/linux-bundle"),
|
||||||
|
target: "bun",
|
||||||
|
format: "esm",
|
||||||
|
external: [
|
||||||
|
"sharp",
|
||||||
|
"canvas",
|
||||||
|
"@napi-rs/canvas",
|
||||||
|
"pdf-to-img",
|
||||||
|
"pdfjs-dist",
|
||||||
|
"@img/*",
|
||||||
|
"@napi-rs/*",
|
||||||
|
],
|
||||||
|
define: {
|
||||||
|
"process.env.FC_PACKAGE_VERSION": JSON.stringify(packageJson.version),
|
||||||
|
"process.env.FC_BUILD_NUMBER": JSON.stringify(buildNumber),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
console.error("Bun build failed");
|
||||||
|
for (const log of result.logs) {
|
||||||
|
console.error(log);
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const compileResult = await Bun.spawn([
|
||||||
|
"bun",
|
||||||
|
"build",
|
||||||
|
"--compile",
|
||||||
|
"--target=bun-linux-x64",
|
||||||
|
path.join(rootDir, "build/linux-bundle/headless.js"),
|
||||||
|
"--outfile",
|
||||||
|
outputFile,
|
||||||
|
], {
|
||||||
|
cwd: rootDir,
|
||||||
|
stdout: "inherit",
|
||||||
|
stderr: "inherit",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (compileResult.exitCode !== 0) {
|
||||||
|
process.exit(compileResult.exitCode ?? 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Linux binary written to ${outputFile}`);
|
||||||
@ -1,41 +0,0 @@
|
|||||||
import { readFileSync } from 'node:fs'
|
|
||||||
import * as esbuild from 'esbuild'
|
|
||||||
import { fileURLToPath } from 'node:url'
|
|
||||||
import path from 'node:path'
|
|
||||||
|
|
||||||
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
||||||
const packageJson = JSON.parse(
|
|
||||||
readFileSync(path.join(rootDir, 'package.json'), 'utf8')
|
|
||||||
)
|
|
||||||
|
|
||||||
let buildNumber = process.env.BUILD_NUMBER || process.env.VITE_BUILD_NUMBER || 'dev'
|
|
||||||
try {
|
|
||||||
const buildInfo = JSON.parse(
|
|
||||||
readFileSync(path.join(rootDir, 'src/buildInfo.json'), 'utf8')
|
|
||||||
)
|
|
||||||
buildNumber = buildInfo.buildNumber ?? buildNumber
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
await esbuild.build({
|
|
||||||
entryPoints: [path.join(rootDir, 'src/headless.js')],
|
|
||||||
bundle: true,
|
|
||||||
platform: 'node',
|
|
||||||
target: 'node18',
|
|
||||||
format: 'cjs',
|
|
||||||
outfile: path.join(rootDir, 'build/pkg-entry.cjs'),
|
|
||||||
external: [
|
|
||||||
'electron',
|
|
||||||
'sharp',
|
|
||||||
'canvas',
|
|
||||||
'@napi-rs/canvas',
|
|
||||||
'pdf-to-img',
|
|
||||||
'pdfjs-dist'
|
|
||||||
],
|
|
||||||
inject: [path.join(rootDir, 'scripts/import-meta-url.js')],
|
|
||||||
define: {
|
|
||||||
'import.meta.url': 'import_meta_url',
|
|
||||||
'process.env.FC_PACKAGE_VERSION': JSON.stringify(packageJson.version),
|
|
||||||
'process.env.FC_BUILD_NUMBER': JSON.stringify(buildNumber)
|
|
||||||
},
|
|
||||||
logLevel: 'info'
|
|
||||||
})
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
version="$(node -p "require('./package.json').version")"
|
version="$(bun --eval "console.log(JSON.parse(await Bun.file('package.json').text()).version)")"
|
||||||
output_dir="app_dist/linux"
|
output_dir="app_dist/linux"
|
||||||
binary="${output_dir}/farmcontrol-server"
|
binary="${output_dir}/farmcontrol-server"
|
||||||
|
|
||||||
|
|||||||
15
scripts/build-server.mjs
Normal file
15
scripts/build-server.mjs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
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}`);
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import { pathToFileURL } from 'node:url'
|
|
||||||
|
|
||||||
export const import_meta_url = pathToFileURL(__filename)
|
|
||||||
8
src/bun/index.js
Normal file
8
src/bun/index.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { createDesktopWindow } from "../desktop/window.js";
|
||||||
|
import { setupIPC } from "../desktop/rpc.js";
|
||||||
|
import { init } from "../index.js";
|
||||||
|
|
||||||
|
const mainWindow = await createDesktopWindow();
|
||||||
|
await setupIPC(mainWindow);
|
||||||
|
|
||||||
|
await init({ headless: false, skipWindow: true });
|
||||||
@ -8,7 +8,9 @@ import { startWaiting, stopWaiting } from "./spinner.js";
|
|||||||
// Determine environment
|
// Determine environment
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
const isPackaged = __dirname.includes("app.asar");
|
const isPackaged =
|
||||||
|
__dirname.includes(path.sep + "build" + path.sep) ||
|
||||||
|
__dirname.includes(path.sep + "Resources" + path.sep);
|
||||||
|
|
||||||
if (!process.env.NODE_ENV) {
|
if (!process.env.NODE_ENV) {
|
||||||
process.env.NODE_ENV = "production";
|
process.env.NODE_ENV = "production";
|
||||||
|
|||||||
46
src/index.js
46
src/index.js
@ -2,8 +2,6 @@ import { fileURLToPath } from "node:url";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { loadConfig } from "./config.js";
|
import { loadConfig } from "./config.js";
|
||||||
import log4js from "log4js";
|
import log4js from "log4js";
|
||||||
import { createElectronWindow } from "./electron/window.js";
|
|
||||||
import { setupIPC } from "./electron/ipc.js";
|
|
||||||
import { LocalServer } from "./localserver/localserver.js";
|
import { LocalServer } from "./localserver/localserver.js";
|
||||||
import { SocketClient } from "./socket/socketclient.js";
|
import { SocketClient } from "./socket/socketclient.js";
|
||||||
import {
|
import {
|
||||||
@ -25,7 +23,18 @@ logger.level = config.logLevel;
|
|||||||
|
|
||||||
const cli = createCliLogger(logger);
|
const cli = createCliLogger(logger);
|
||||||
|
|
||||||
|
let initPromise = null;
|
||||||
|
|
||||||
export async function init(options = {}) {
|
export async function init(options = {}) {
|
||||||
|
if (initPromise) {
|
||||||
|
return initPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
initPromise = startApp(options);
|
||||||
|
return initPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startApp(options = {}) {
|
||||||
const headless = options.headless ?? isHeadless;
|
const headless = options.headless ?? isHeadless;
|
||||||
cli.blank();
|
cli.blank();
|
||||||
|
|
||||||
@ -42,22 +51,25 @@ export async function init(options = {}) {
|
|||||||
return runningServer;
|
return runningServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!headless) {
|
if (!headless && !options.skipWindow) {
|
||||||
startWaiting("Creating electron window...", logger);
|
const { createDesktopWindow } = await import("./desktop/window.js");
|
||||||
|
const { setupIPC } = await import("./desktop/rpc.js");
|
||||||
|
|
||||||
|
startWaiting("Creating desktop window...", logger);
|
||||||
try {
|
try {
|
||||||
await createElectronWindow().catch((err) => {
|
const mainWindow = await createDesktopWindow().catch((err) => {
|
||||||
cli.warn("Failed to create Electron window:", err);
|
cli.warn("Failed to create desktop window:", err);
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
|
if (mainWindow) {
|
||||||
|
await setupIPC(mainWindow);
|
||||||
|
}
|
||||||
await stopWaiting();
|
await stopWaiting();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await stopWaiting();
|
await stopWaiting();
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
} else if (headless) {
|
||||||
setupIPC().catch((err) => {
|
|
||||||
cli.warn("Failed to setup IPC:", err);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
cli.info("Running in headless mode. Skipping window creation.");
|
cli.info("Running in headless mode. Skipping window creation.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,11 +91,15 @@ export async function init(options = {}) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const isMainModule =
|
const entryScript = process.argv[1]
|
||||||
process.argv[1] &&
|
? path.resolve(process.argv[1])
|
||||||
fileURLToPath(import.meta.url) === path.resolve(process.argv[1]);
|
: null;
|
||||||
|
const isDirectIndexEntry =
|
||||||
|
entryScript &&
|
||||||
|
entryScript.endsWith(`${path.sep}src${path.sep}index.js`) &&
|
||||||
|
fileURLToPath(import.meta.url) === entryScript;
|
||||||
|
|
||||||
if (isMainModule && !process.env.FC_HEADLESS_ENTRY) {
|
if (isDirectIndexEntry && !process.env.FC_HEADLESS_ENTRY) {
|
||||||
init().catch((err) => {
|
init().catch((err) => {
|
||||||
logger.error(err.message);
|
logger.error(err.message);
|
||||||
cli.blank();
|
cli.blank();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user