Files
web-terminal/control-panel/build.mjs
Yaojia Wang 675de771c7 feat(control-panel): web admin UI for the zero-touch tunnel
Loopback Fastify auth-broker + esbuild SPA. Operator password login (constant-time,
signed HttpOnly session cookie, per-forwarded-IP rate-limit) → session-gated proxy
that mints a fresh 60s manage capability token per call to the control-plane admin
API: list hosts, mint pairing codes (with QR + pair command), revoke hosts. Security
headers + CSP, CP_URL pinned loopback (anti-SSRF), hostId dot-segment guard. 55 tests
pass; security-reviewed. Deployed behind nginx panel.terminal.yaojia.wang.
2026-07-19 19:47:51 +02:00

38 lines
1.2 KiB
JavaScript

/**
* Frontend build — mirrors the base app's esbuild style (vanilla TS → ESM bundle). Emits
* public/build/{app.js, index.html, styles.css}. index.html + styles.css are copied verbatim
* (index.html already references ./app.js and ./styles.css, both siblings in the build dir).
*/
import { build } from 'esbuild'
import { mkdir, copyFile } from 'node:fs/promises'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
const here = dirname(fileURLToPath(import.meta.url))
const pub = resolve(here, 'public')
const out = resolve(pub, 'build')
async function main() {
await mkdir(out, { recursive: true })
await build({
entryPoints: [resolve(pub, 'app.ts')],
bundle: true,
format: 'esm',
target: 'es2022',
minify: true,
sourcemap: true,
outfile: resolve(out, 'app.js'),
logLevel: 'info',
})
await copyFile(resolve(pub, 'index.html'), resolve(out, 'index.html'))
await copyFile(resolve(pub, 'styles.css'), resolve(out, 'styles.css'))
process.stdout.write('[control-panel] frontend build → public/build\n')
}
main().catch((err) => {
process.stderr.write(`[control-panel] build failed: ${err instanceof Error ? err.message : String(err)}\n`)
process.exit(1)
})