BoxLang Desktop Runtime (Electron + MiniServer)
Overview
This skill applies to desktop applications that embed BoxLang using Electron and run a local BoxLang MiniServer process.
Core model:
- Electron owns desktop lifecycle (window, tray, menu, shortcuts).
- A local MiniServer process serves the BoxLang app.
- The BrowserWindow loads the local MiniServer URL.
- Frontend assets are served via Vite in development and built artifacts in production.
This pattern gives a desktop UX without replacing BoxLang with another backend stack.
Architecture Baseline
Expected module boundaries:
app/electron/Main.js: app bootstrap and module wiring.app/electron/BoxLang.js: MiniServer process manager (start/stop/restart/readiness).app/electron/AppMenu.js: application menu.app/electron/TrayMenu.js: tray behavior and status.app/electron/Shortcuts.js: global keyboard shortcuts.runtime/Package.bx: MiniServer runtime packager using.bvmrc.miniserver.json: local server runtime controls..boxlang-dev.json: development BoxLang runtime defaults..boxlang.json: production BoxLang runtime defaults.
Preserve modular boundaries in app/electron/* instead of collapsing logic into a single file.
Packaging Model
Package the MiniServer runtime, not Java:
- Packaged runtime paths:
runtime/binruntime/lib
- Version source:
.bvmrc - Packager script:
runtime/Package.bx - Startup preference: packaged
runtime/bin/boxlang-miniserverfirst, globalboxlang-miniserverfallback.
Important runtime constraint:
- Java 21+ must exist on every machine running the desktop app.
- Only MiniServer binaries/libs are packaged, not a JRE/JDK.
Configuration Responsibilities
miniserver.json (development server control)
Use this as the primary local server control file for desktop development:
- host/port binding
- webRoot
- rewrites
- debug
- envFile
- serverHome
Example:
{
"port": 59700,
"host": "127.0.0.1",
"webRoot": "public",
"serverHome": ".boxlang",
"rewrites": true,
"debug": false,
"envFile": ".env"
}
.boxlang-dev.json vs .boxlang.json
Use split runtime config for predictable behavior:
.boxlang-dev.json: development-focused runtime behavior (debugMode, cache toggles, testing mappings)..boxlang.json: production defaults for packaged desktop execution.
Both should include baseline mappings so classes/templates resolve predictably:
"/app": {
"path": "${user-dir}/app",
"external": false
},
"/public": "${user-dir}/public"
Startup and Readiness Pattern
Recommended process flow in BoxLang.js:
- Detect packaged runtime executable and fallback command.
- Ensure execute permission on Unix-like systems.
- Spawn MiniServer with
miniserver.json. - Poll server URL readiness with timeout and retry interval.
- On ready: load BrowserWindow URL.
- On crash: apply controlled restart strategy with delay and user notification.
Checklist for robust lifecycle handling:
- startup timeout with clear error dialog
- readiness probe over HTTP before loading app URL
- restart suppression on intentional shutdown
- graceful stop on app quit/SIGINT/SIGTERM
- per-platform executable permission handling
Build and Packaging Workflow
Common script set for this runtime style:
npm run dev: Vite + Electron development loop.npm run build: build frontend assets topublic/includes/resources.npm run package:miniserver: download/extract runtime from.bvmrc.npm run package: build assets and package desktop app.npm run package:full: package MiniServer, then package app.
Use package:full for distributable builds.
Electron Forge
This project uses Electron Forge as the packaging and distribution tool (forge.config.cjs).
- Docs / MCP reference: https://www.electronforge.io/~gitbook/mcp
- Config file must be CommonJS (
forge.config.cjs) — Forge does not support ESM config files. - Invoked via
electron-forge make(wrapped in thepackage/package:mac/package:win/package:linuxnpm scripts).
Critical constraint — asar must always be false
packagerConfig: {
asar: false,
...
}
BoxLang.js spawns runtime/bin/boxlang-miniserver as a real OS process. If asar: true, Forge archives the app directory into a single .asar file and the spawned path no longer exists on the filesystem, breaking MiniServer startup entirely.
Icon resolution
Forge resolves icons from the base name in packagerConfig.icon:
icon: "./public/includes/icon",
It then appends the platform-appropriate extension automatically:
| Platform | Extension | Source |
|---|---|---|
| macOS | .icns |
public/includes/icon.icns (from iconset via iconutil) |
| Windows | .ico |
public/includes/icon.ico (generated by npm run generate:icons) |
| Linux | .png |
public/includes/icon.iconset/icon.png |
All three files must exist at package time. Missing any one causes a fatal Forge error on that platform.
Makers
| Platform | Maker | Output |
|---|---|---|
| macOS | @electron-forge/maker-dmg, @electron-forge/maker-zip |
.dmg, .zip |
| Windows | @electron-forge/maker-squirrel, @electron-forge/maker-zip |
.exe (Squirrel), .zip |
| Linux | @electron-forge/maker-deb, @electron-forge/maker-rpm |
.deb, .rpm |
Windows installer uses Squirrel, not NSIS.
postMake hook
The config includes a postMake hook that copies unsigned-build helper scripts (mac-open.sh, win-unblock.ps1, UNSIGNED-BUILD.md) into every ZIP artifact so end users have bypass instructions without hunting for docs.
BoxLang and Runtime Rules
- Use tag-based component syntax (
bx:http {},bx:zip {}), nevernew bx:.... - For CLI scripts (such as runtime packaging), parse args with
CliGetArgs(). - Use
expandPath()and check file/directory existence before destructive operations. - Keep path construction cross-platform in Electron with
path.join().
Troubleshooting Guide
MiniServer fails to start
- Confirm
runtime/binandruntime/libexist (runnpm run package:miniserver). - If using fallback mode, verify
boxlang-miniserveris onPATH. - Validate
miniserver.jsonhost/port/webRoot values.
Permission denied on Unix-like hosts
- Repackage with force:
npm run package:miniserver:force. - Verify executable bit on
runtime/bin/boxlang-miniserver.
App runs but UI assets missing
- Run
npm run buildand confirm manifest exists inpublic/includes/resources/.vite/manifest.json.
Runs on dev machine but fails on user machine
- Confirm Java 21+ is installed on target machine.
- Remember: MiniServer is bundled, JRE is not.
CI / GitHub Actions Constraints
Node.js Version for Windows Builds
Pin the Windows runner to Node 20 LTS. cross-zip (a transitive dependency of @electron-forge/maker-zip) calls fs.rmdir(path, { recursive: true }), which was removed in Node.js 22+. Running Node 25 on windows-latest causes a hard failure during the Squirrel make step:
TypeError [ERR_INVALID_ARG_VALUE]: The property 'options.recursive' is no longer supported. Received true
Workaround in .github/workflows/build.yml:
- name: Set up Node.js
uses: actions/setup-node@v6
with:
# Pin to Node 20 LTS — cross-zip uses fs.rmdir({recursive:true}) removed in Node 22+
node-version: "20"
cache: "npm"
Revisit when cross-zip or @electron-forge/maker-zip ships a patched release.
Windows .ico Icon Requirement
The Squirrel installer (@electron-forge/maker-squirrel) requires a real .ico file on Windows. Forge auto-resolves icons from the base name configured in packagerConfig.icon. A missing .ico produces:
Fatal error: Unable to set icon
The scripts/generate-icons.js script generates public/includes/icon.ico from the PNG iconset. Run it in CI before packaging:
- name: Generate Windows icon
run: npm run generate:icons
- name: Package Electron app (Windows)
run: npm run package:win
Alternatively, commit public/includes/icon.ico to the repository to skip the generation step in CI entirely (recommended for faster, more predictable builds).
Agent Instruction Conventions
For repositories using this starter, adopt the two-file instruction pattern for maximum agent compatibility:
AGENTS.mdat the repository root: canonical instructions, full project rules. Read by Claude, Copilot, and most coding agents..github/copilot-instructions.md: a compact compatibility bridge that points toAGENTS.mdand repeats critical safety guardrails.
Template for .github/copilot-instructions.md:
# Project — Copilot Bridge Instructions
Use AGENTS.md at repository root as the canonical instruction source.
## Canonical Source
- Primary instructions: AGENTS.md
- If any conflict exists, AGENTS.md wins.
## Critical Guardrails
- Keep modular boundaries in app/electron/* — do not collapse into a single file.
- Preserve process lifecycle safety in MiniServer startup and shutdown paths.
- Preserve cross-platform behavior (macOS, Windows, Linux).
- Do not remove packaged-runtime-first fallback to global boxlang-miniserver.
- Never enable asar in forge.config.cjs — MiniServer spawning requires real filesystem paths.
Best Practices
- Keep desktop, runtime, and web concerns separated by module.
- Treat
miniserver.jsonas developer-facing runtime control. - Keep both
.boxlang-dev.jsonand.boxlang.jsonunder source control. - Keep
/appand/publicmappings stable unless there is a deliberate architecture change. - Preserve packaged runtime fallback logic unless explicitly removing it.
- Commit
public/includes/icon.icoto avoid Windows icon generation in CI. - Pin Windows CI runner to Node 20 LTS until
cross-zipis updated for Node 22+.