// esbuild build for @hive/dashboard. Output layout (`dist/`): // // dist/index.html H0M3 menu hub — served by the Rust // router at GET / (the landing page) // dist/dashboard.html the operator dashboard SPA — served // at GET /dashboard.html // dist/flow.html served at GET /flow.html // dist/logs.html served at GET /logs.html (AGENT/SYSTEM/AUDIT) // dist/core.html served at GET /core.html (C0R3: kept // state / container load) // dist/builds.html served at GET /builds.html (BU1LDS: // rebuild queue / meta inputs / build logs) // dist/credentials.html served at GET /credentials.html // (CR3D3NTIALS: matrix + github accounts) // dist/static/home.js index.html (H0M3) entry — menu tiles + // matrix-tile gating + identity line // dist/static/tabs.js dashboard.html entry — tab renderers + // tab routing + refreshState // dist/static/flow.js /flow.html entry — broker terminal + // @-mention composer // dist/static/logs.js /logs.html entry — agent/system/audit // log viewer sub-tabs // dist/static/builds.js /builds.html entry — rebuild queue, // meta inputs, build log history // dist/static/credentials.js /credentials.html entry — matrix + // github account sub-tabs // dist/static/{home,tabs,flow,logs,builds,credentials}.js.map source map // siblings // dist/static/common.css loaded by every page (@hive/shared // imports + shared typography/badges/ // buttons/inbox/side-panel) // dist/static/dashboard.css dashboard.html only (dashboard-specific) // dist/static/flow.css /flow.html only (flow chrome + composer) // dist/static/logs.css /logs.html only (log viewer sub-tabs) // dist/static/builds.css /builds.html only (build lifecycle hub) // dist/static/home.css index.html (H0M3) only (tile grid) // dist/static/credentials.css /credentials.html only (matrix + github // account forms) // // Each JS entry bundles `./common.js` (DOM helpers, Panel singleton, // NOTIF, path linkification) independently — esbuild inlines the shared // module into each bundle rather than emitting a shared chunk (no // `splitting: true`). The Rust binary mounts `dist/` as a // `tower_http::ServeDir` fallback (`append_index_html_on_directories` // serves index.html at /); the layout above keeps every URL the HTML // files reference reachable without rewriting paths in the HTML. The // dashboard SPA lives at /dashboard.html (a plain file, not a /dashboard/ // prefix) so it never shadows the exact-match /dashboard/stream + // /dashboard/history SSE routes registered before the ServeDir fallback. import { build } from 'esbuild'; import { mkdirSync, copyFileSync, rmSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; const here = dirname(fileURLToPath(import.meta.url)); const src = (p) => resolve(here, 'src', p); const dist = (p) => resolve(here, 'dist', p); const staticDir = (p) => resolve(here, 'dist', 'static', p); rmSync(dist(''), { recursive: true, force: true }); mkdirSync(staticDir(''), { recursive: true }); // Bundle the JS entries. ES-module output, browser target, no minify // (line-aligned source aids debugging; minification belongs in a later // follow-up once asset sizes warrant it). esbuild writes each entry // to `static/.js` based on the entryPoint basename. await build({ entryPoints: [src('tabs.js'), src('flow.js'), src('logs.js'), src('home.js'), src('settings.js'), src('stats.js'), src('core.js'), src('builds.js'), src('credentials.js')], outdir: staticDir(''), bundle: true, format: 'esm', platform: 'browser', target: ['es2022'], sourcemap: true, logLevel: 'info', // `@hive/shared/modal.js` and `hive-btn.js` import their shadow-DOM // component CSS (hive-dialog.css, hive-toast.css, hive-btn.css) as raw // text via a plain `import css from './foo.css'` — the `text` loader turns // that into a string constant at bundle time instead of erroring on an // unrecognised extension. None of these JS entries import a `.css` // file any other way, so this doesn't collide with the separate // page-stylesheet bundling below (`loader: { '.css': 'css' }`), which // runs as its own esbuild invocation over different entry points. loader: { '.css': 'text' }, // `@hive/shared/jobq-graph.js` resolves to a real `.jsx` file // (`JobqGraph.jsx`), pulled in transitively by `builds.js` — esbuild // already picks the `jsx` loader for `.jsx` by extension, this just // sets the transform mode to match swarm-ui's (which also authors // this file). No other entry here uses JSX today; this doesn't turn // any plain `.js` file into one, `.js` still parses as plain JS. jsx: 'automatic', jsxImportSource: 'preact', }); // Stream-worker entry (#448). Lives in a separate bundle: SharedWorker // scripts run in a different global (`self` is the worker scope, no // `window`) so they can't be inlined into tabs.js / flow.js. Output is // at `static/stream-worker.js`; common.js's `openStream` references // `/static/stream-worker.js` as the SharedWorker URL. `format: 'iife'` // matches the classic-script load (`new SharedWorker(url, name)` with // no `{ type: 'module' }`); Firefox is the #448 target and module // SharedWorker support there is patchy, so keeping the worker as a // classic script + IIFE bundle is the compatible default. argus nit // on #453: if a future contributor adds an `import` to this bundle, // the IIFE format will surface it as a build error rather than // silently shipping broken code. await build({ entryPoints: [src('stream-worker.js')], outdir: staticDir(''), bundle: true, format: 'iife', platform: 'browser', target: ['es2022'], sourcemap: true, logLevel: 'info', }); // Bundle CSS — one entry per page. esbuild resolves @import including // the package re-exports from @hive/shared. Each page loads colors.css // (the standalone base16 palette — the theme swap contract, its own file // so a swap replaces only it) + theme.css (the semantic derivation // layer) + common.css (shared typography, badges, buttons, inbox, side // panel) plus its own page-specific bundle. for (const entry of ['colors.css', 'theme.css', 'common.css', 'dashboard.css', 'flow.css', 'logs.css', 'home.css', 'settings.css', 'stats.css', 'core.css', 'builds.css', 'credentials.css']) { await build({ entryPoints: [src(entry)], outfile: staticDir(entry), bundle: true, loader: { '.css': 'css' }, logLevel: 'info', }); } for (const html of ['index.html', 'dashboard.html', 'flow.html', 'logs.html', 'settings.html', 'stats.html', 'core.html', 'builds.html', 'credentials.html']) { copyFileSync(src(html), dist(html)); } console.log('dashboard build ok →', dist(''));