diff --git a/.gitignore b/.gitignore index f2afeb3..a61006c 100644 --- a/.gitignore +++ b/.gitignore @@ -141,4 +141,8 @@ data/* dist/* -src/buildInfo.json \ No newline at end of file +src/buildInfo.json +.electrobun-cache +artifacts +app_dist +build \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile index 64771f9..7bed5bd 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -3,16 +3,14 @@ properties([ ]) def writeBuildMetadata() { - nodejs(nodeJSInstallationName: 'Node23') { - if (isUnix()) { - sh ''' - node -e "const fs = require('fs'); fs.writeFileSync('src/buildInfo.json', JSON.stringify({ buildNumber: process.env.BUILD_NUMBER || 'dev' }, null, 2) + '\\n');" - ''' - } else { - bat ''' - node -e "const fs = require('fs'); fs.writeFileSync('src/buildInfo.json', JSON.stringify({ buildNumber: process.env.BUILD_NUMBER || 'dev' }, null, 2) + '\\n');" - ''' - } + if (isUnix()) { + sh ''' + bun --eval "await Bun.write('src/buildInfo.json', JSON.stringify({ buildNumber: process.env.BUILD_NUMBER || 'dev' }, null, 2) + '\\n')" + ''' + } else { + bat ''' + bun --eval "await Bun.write('src/buildInfo.json', JSON.stringify({ buildNumber: process.env.BUILD_NUMBER || 'dev' }, null, 2) + '\\n')" + ''' } } @@ -23,17 +21,12 @@ def buildLinux() { checkout scm } - stage('Setup Node.js (Ubuntu)') { - nodejs(nodeJSInstallationName: 'Node23') { - sh 'node -v' - sh 'pnpm -v' - } + stage('Setup Bun (Ubuntu)') { + sh 'bun -v' } stage('Install Dependencies (Ubuntu)') { - nodejs(nodeJSInstallationName: 'Node23') { - sh 'pnpm install --frozen-lockfile --production=false' - } + sh 'bun install --frozen-lockfile' } stage('Write Build Metadata (Ubuntu)') { @@ -41,9 +34,7 @@ def buildLinux() { } stage('Build (Ubuntu)') { - nodejs(nodeJSInstallationName: 'Node23') { - sh "VITE_BUILD_NUMBER=${env.BUILD_NUMBER} NODE_ENV=production pnpm build:linux" - } + sh "VITE_BUILD_NUMBER=${env.BUILD_NUMBER} NODE_ENV=production bun run build:linux" } stage('Archive Artifacts (Ubuntu)') { @@ -62,25 +53,19 @@ def buildOnLabel(label, buildCommand) { checkout scm } - stage("Setup Node.js (${label})") { - nodejs(nodeJSInstallationName: 'Node23') { - if (isUnix()) { - sh 'node -v' - sh 'pnpm -v' - } else { - bat 'node -v' - bat 'pnpm -v' - } + stage("Setup Bun (${label})") { + if (isUnix()) { + sh 'bun -v' + } else { + bat 'bun -v' } } stage("Install Dependencies (${label})") { - nodejs(nodeJSInstallationName: 'Node23') { - if (isUnix()) { - sh 'pnpm install --frozen-lockfile --production=false' - } else { - bat 'pnpm install --frozen-lockfile --production=false' - } + if (isUnix()) { + sh 'bun install --frozen-lockfile' + } else { + bat 'bun install --frozen-lockfile' } } @@ -89,17 +74,15 @@ def buildOnLabel(label, buildCommand) { } stage("Build (${label})") { - nodejs(nodeJSInstallationName: 'Node23') { - if (isUnix()) { - sh "VITE_BUILD_NUMBER=${env.BUILD_NUMBER} NODE_ENV=production ${buildCommand}" - } else { - bat "set VITE_BUILD_NUMBER=${env.BUILD_NUMBER} && set NODE_ENV=production && ${buildCommand}" - } + if (isUnix()) { + sh "VITE_BUILD_NUMBER=${env.BUILD_NUMBER} NODE_ENV=production ${buildCommand}" + } else { + bat "set VITE_BUILD_NUMBER=${env.BUILD_NUMBER} && set NODE_ENV=production && ${buildCommand}" } } stage("Archive Artifacts (${label})") { - archiveArtifacts artifacts: 'app_dist/**/farmcontrol-server-*.dmg, app_dist/**/farmcontrol-server-*.exe, app_dist/**/farmcontrol-server-*.pkg, app_dist/**/farmcontrol-server-*.msi', fingerprint: true + archiveArtifacts artifacts: 'app_dist/**/farmcontrol-server-*', fingerprint: true } } } @@ -109,13 +92,10 @@ def setBuildNameFromPackageVersion() { node('ubuntu') { stage('Set Build Name') { checkout scm - def version - nodejs(nodeJSInstallationName: 'Node23') { - version = sh( - script: "node -p \"require('./package.json').version\"", - returnStdout: true - ).trim() - } + def version = sh( + script: "bun --eval \"console.log(JSON.parse(await Bun.file('package.json').text()).version)\"", + returnStdout: true + ).trim() def buildName = "v${version}-b${env.BUILD_NUMBER}" currentBuild.displayName = buildName echo "Build name set to: ${buildName}" @@ -127,8 +107,8 @@ try { setBuildNameFromPackageVersion() parallel( - 'Windows Build': buildOnLabel('windows', 'pnpm build:electron'), - 'MacOS Build': buildOnLabel('macos', 'pnpm build:electron:mac'), + 'Windows Build': buildOnLabel('windows', 'bun run build:app'), + 'MacOS Build': buildOnLabel('macos', 'bun run build:app:mac'), 'Ubuntu Build': { buildLinux() } ) diff --git a/config.json b/config.json index 836db2d..fef8889 100644 --- a/config.json +++ b/config.json @@ -4,9 +4,10 @@ "url": "https://dev-wss.tombutcher.work", "apiUrl": "https://dev.tombutcher.work/api", "host": { - "id": "6a6654425330570c496f12ee", - "authCode": "VCXo44VxFhJb2pvZDQ0b3QPX7lYMg7SGZ6s4HBU72dJb-PBNk81UfwYhqqYKn1Ez" - } + "id": "691a1db49ce913faf0e51284", + "authCode": "6wdCQPKMr_zuFaJe3-AY99uRuAEZRgU2lLNTuAFPW5hPvzzTrSra3rh3woZLj-eE" + }, + "dataDir": "/Users/tombutcher/Projects/farmcontrol-server/data" }, "production": { "logLevel": "info", diff --git a/readme.md b/readme.md index 2310c4e..3f92c1d 100644 --- a/readme.md +++ b/readme.md @@ -2,83 +2,55 @@ [![Build Status](https://ci.tombutcher.work/buildStatus/icon?job=farmcontrol%2Ffarmcontrol-server%2Fmain&style=flat-square)](https://ci.tombutcher.work/job/farmcontrol/job/farmcontrol-server/job/main/) -A Node.js application that bridges communication between external websocket clients and a Moonraker-controlled 3D printer. +A Bun application that bridges communication between Farm Control cloud services, local websocket clients, and connected printers. ## Features -- Connects to Moonraker API via websocket -- Provides a websocket server for external clients -- Relays JSON-RPC commands between clients and the printer -- Broadcasts printer status updates to all connected clients -- Handles reconnection if the Moonraker connection is lost +- Connects to the Farm Control websocket API +- Provides a local HTTP server for CLI and integrations +- Desktop UI built with React, Ant Design, and Electrobun +- Headless Linux service packaging for servers and Raspberry Pi hosts +- Relays printer and document printer state to the desktop UI + +## Requirements + +- [Bun](https://bun.sh) 1.1 or newer ## Installation 1. Clone this repository 2. Install dependencies: ``` - npm install + bun install ``` -3. Edit the `config.json` file to match your Moonraker setup +3. Edit `config.json` for your environment 4. Start the server: ``` - npm start + bun run dev ``` +## Development + +- Headless server: `bun run dev` +- Desktop app with hot reload: `bun run dev:app` +- Renderer only: `bun run dev:renderer` + +## Building + +- Desktop app (current platform): `bun run build:app` +- Linux headless packages: `bun run build:linux` + +Electrobun handles desktop packaging. Linux headless builds use `bun build --compile`. + ## Configuration -The `config.json` file contains the following options: +`config.json` contains `development` and `production` sections. Each section supports: -```json -{ - "moonraker": { - "host": "localhost", - "port": 7125, - "protocol": "ws", - "apiKey": null, - "identity": { - "name": "printer-bridge", - "version": "0.1.0", - "type": "external" - } - }, - "server": { - "port": 8080 - } -} -``` - -- `moonraker.host`: The hostname or IP address of your Moonraker instance -- `moonraker.port`: The port number of your Moonraker instance -- `moonraker.protocol`: The protocol to use (`ws` or `wss`) -- `moonraker.apiKey`: Your Moonraker API key (if required) -- `server.port`: The port number for the websocket server - -## Usage - -Connect to the websocket server at `ws://[host]:[port]` and send JSON-RPC formatted messages to control the printer. - -Example client-side code: - -```javascript -const ws = new WebSocket('ws://localhost:8080'); - -ws.onopen = () => { - console.log('Connected to printer bridge'); - - // Send a command to get printer info - ws.send(JSON.stringify({ - jsonrpc: "2.0", - method: "printer.info", - id: 1 - })); -}; - -ws.onmessage = (event) => { - const message = JSON.parse(event.data); - console.log('Received:', message); -}; -``` +- `logLevel` +- `url` (websocket server) +- `apiUrl` +- `host.id` and `host.authCode` +- `dataDir` ## License