diff --git a/.gitignore b/.gitignore index 96c30a86..0994d83a 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,6 @@ src/config.ts # Playwright playwright/ .wwebjs_cache/ + +# generated from index.html by scripts/gen-index.ts +src/apps/officer-web/index.gen.html diff --git a/package.json b/package.json index e43b6aab..042a4213 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,10 @@ ], "scripts": { "preinstall": "node -e \"var v = +process.versions.node.split('.')[0]; if (v < 22 || v > 22) { console.error('Node 22 required (got ' + process.versions.node + '). Run: nvm use 22'); process.exit(1); }\"", + "gen:index": "bun run ./scripts/gen-index.ts", + "predev": "bun run ./scripts/gen-index.ts", "dev": "bun --env-file=.env --watch src/server.tsx", + "prestart": "bun run ./scripts/gen-index.ts", "start": "NODE_ENV=production bun src/server.tsx", "prebuild": "bun run ./scripts/prebuild.ts", "build:web": "bun run ./scripts/build/web.ts", diff --git a/scripts/gen-index.ts b/scripts/gen-index.ts new file mode 100644 index 00000000..75e2efbf --- /dev/null +++ b/scripts/gen-index.ts @@ -0,0 +1,48 @@ +#!/usr/bin/env bun +// Generates src/apps/officer-web/index.gen.html from index.html, substituting __PUBLIC_URL__ with +// PUBLIC_URL from .env. +// +// The OpenGraph tags need absolute URLs — crawlers fetch the page standalone and cannot resolve +// relative ones — but hardcoding the domain forces every deployment to carry its own edit of +// index.html. Bun's HTML import offers no substitution hook, so the swap happens here and the server +// imports the generated file. index.gen.html is gitignored; index.html is the tracked template and +// is identical on every instance. + +import { existsSync } from 'node:fs'; +import { dirname, join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const root = resolve(dirname(fileURLToPath(import.meta.url)), '..'); +const template = join(root, 'src/apps/officer-web/index.html'); +const output = join(root, 'src/apps/officer-web/index.gen.html'); + +// The server reads .env through --env-file, but this script runs standalone. +const envPath = join(root, '.env'); +if (!process.env.PUBLIC_URL && existsSync(envPath)) { + for (const line of (await Bun.file(envPath).text()).split('\n')) { + const match = line.match(/^\s*PUBLIC_URL\s*=\s*(.*)$/); + if (match) process.env.PUBLIC_URL = match[1]!.trim().replace(/^["']|["']$/g, ''); + } +} + +const publicUrl = (process.env.PUBLIC_URL ?? '').replace(/\/+$/, ''); +if (!publicUrl) { + console.error('[gen-index] PUBLIC_URL is not set — set it in .env (e.g. https://officer.example.com)'); + process.exit(1); +} + +const html = await Bun.file(template).text(); +const generated = html.replaceAll('__PUBLIC_URL__', publicUrl); + +if (generated.includes('__PUBLIC_URL__')) { + console.error('[gen-index] substitution left placeholders behind'); + process.exit(1); +} + +// Only write when the content actually changes, so `bun --watch` does not restart in a loop. +if (existsSync(output) && (await Bun.file(output).text()) === generated) { + console.log(`[gen-index] up to date (${publicUrl})`); +} else { + await Bun.write(output, generated); + console.log(`[gen-index] wrote index.gen.html (${publicUrl})`); +} diff --git a/src/apps/officer-web/index.html b/src/apps/officer-web/index.html index a33cabe1..201c04bf 100644 --- a/src/apps/officer-web/index.html +++ b/src/apps/officer-web/index.html @@ -6,29 +6,35 @@ Officer Dev (Alpha) + - - + + - + - + - - - - - + + + + + diff --git a/src/server.tsx b/src/server.tsx index 4ac935bb..8c66487d 100644 --- a/src/server.tsx +++ b/src/server.tsx @@ -12,7 +12,7 @@ import { cliampWebsocket } from './servers/api/cliamp/websocket'; import { cliampAudioWebsocket } from './servers/api/cliamp/audio-ws'; import { desktopWebsocket } from './servers/api/desktop/websocket'; import { findEntryByProxyId, touchEntry } from './servers/api/dev-server/router'; -import officerWeb from './apps/officer-web/index.html'; +import officerWeb from './apps/officer-web/index.gen.html'; import { startBrowserRelay } from './servers/api/browser/relay'; import { registerSidecar, unregisterSidecar, handleSidecarMessage } from './servers/sidecar-registry'; import './servers/api/chat/opencode/sidecar-server'; // subscribe to the opencode sidecar's port report