// esbuild build for @hive/dashboard: one JS+CSS bundle per page (H0M3 at // `/`, dashboard/flow/logs/core/builds/credentials at their own // `.html`), each named after its own entry point below — see the // `entryPoints`/`for` lists for the exact map, not repeated here to // avoid this comment drifting out of sync with the real build steps. // // 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 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("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. 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's module SharedWorker support is // patchy, so keeping the worker as a classic script + IIFE bundle is // the compatible default. argus's own review nit here: 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", "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", "stats.html", "core.html", "builds.html", "credentials.html", ]) { copyFileSync(src(html), dist(html)); } console.log("dashboard build ok →", dist(""));