// Auto-installs the FlyWithLua companion scripts into X-Plane on bridge start. // // The web cockpit needs three .lua helpers running INSIDE X-Plane (they have the // FMS / scenery SDK the Web API lacks — see plugins/*.lua). Rather than make the // user copy files by hand, the bridge drops them into the sim's FlyWithLua // Scripts folder on startup and keeps them up to date (content-compare, so a new // build self-updates the installed copy; unchanged files are left alone). // // Everything degrades gracefully: no X-Plane found, no FlyWithLua installed, or // the script sources missing → we log a hint and carry on. We never create the // FlyWithLua folder ourselves (its absence means the user must install // FlyWithLua NG+ first; making an empty folder would only hide that). import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); // The companion scripts to install, in load-independent order. const SCRIPTS = ['fms-sync.lua', 'ui-sync.lua', 'terrain-probe.lua']; // Where do the canonical .lua sources live? plugins/ sits next to server/ in the // repo; in the packaged desktop app it's bundled as a Tauri resource. Probe a // few locations so both `node server/bridge.js` and the compiled sidecar work. function sourceDir() { const candidates = [ process.env.LUA_SRC_DIR, path.join(__dirname, '..', 'plugins'), // repo: server/ -> ../plugins path.join(process.cwd(), 'plugins'), // run from repo root path.join(path.dirname(process.execPath), 'plugins'), path.join(path.dirname(process.execPath), '..', 'Resources', 'plugins'), ].filter(Boolean); for (const dir of candidates) { try { if (fs.existsSync(path.join(dir, SCRIPTS[0]))) return dir; } catch { /* ignore */ } } return null; } // Install / update the scripts under . Returns a report object; never throws. export function installLuaScripts(root, log = console.log) { if (!root) { return { ok: false, reason: 'no-xplane', installed: [], updated: [], unchanged: [] }; } const fwl = path.join(root, 'Resources', 'plugins', 'FlyWithLua'); if (!fs.existsSync(fwl)) { log('lua-install: FlyWithLua not found — install FlyWithLua NG+ into ' + `${path.join(root, 'Resources', 'plugins')} to enable FMS/terrain sync`); return { ok: false, reason: 'no-flywithlua', installed: [], updated: [], unchanged: [] }; } const src = sourceDir(); if (!src) { log('lua-install: companion script sources not found (set LUA_SRC_DIR)'); return { ok: false, reason: 'no-source', installed: [], updated: [], unchanged: [] }; } const dest = path.join(fwl, 'Scripts'); const report = { ok: true, reason: 'ok', dir: dest, installed: [], updated: [], unchanged: [], failed: [] }; try { fs.mkdirSync(dest, { recursive: true }); } catch { /* ignore */ } for (const name of SCRIPTS) { const from = path.join(src, name); const to = path.join(dest, name); try { if (!fs.existsSync(from)) { report.failed.push(name); continue; } const want = fs.readFileSync(from, 'utf8'); const have = fs.existsSync(to) ? fs.readFileSync(to, 'utf8') : null; if (have === null) { fs.writeFileSync(to, want); report.installed.push(name); } else if (have !== want) { fs.writeFileSync(to, want); report.updated.push(name); } else { report.unchanged.push(name); } } catch (e) { report.failed.push(name); log(`lua-install: ${name} failed: ${e.message}`); } } const parts = []; if (report.installed.length) parts.push(`installed ${report.installed.join(', ')}`); if (report.updated.length) parts.push(`updated ${report.updated.join(', ')}`); if (report.unchanged.length) parts.push(`${report.unchanged.length} up to date`); log(`lua-install: ${parts.join('; ') || 'nothing to do'} → ${dest}`); if (report.installed.length || report.updated.length) { log('lua-install: reload in X-Plane — "FlyWithLua > Reload all Lua script files" (or restart)'); } return report; }