Fork Horse repository, listed by its owner. Publisher snapshot.
packages/forkhorse-gatekeeper/vite.config.ts
Snapshot at 34e3175
1import tailwindcss from "@tailwindcss/vite";
2import { readFileSync } from "node:fs";
3import { defineConfig } from "vite-plus";
4import { markIconUrl } from "../forkhorse-dashboard/src/mark.ts";
5
6const brand = JSON.parse(readFileSync(new URL("../../brand.json", import.meta.url), "utf8"));
7const vars = (values: Record<string, string>) =>
8 Object.entries(values)
9 .map(([name, value]) => `${name}:${value}`)
10 .join(";");
11const escape = (text: string) =>
12 text.replace(
13 /[&<>"']/g,
14 (character) =>
15 ({
16 "&": "&",
17 "<": "<",
18 ">": ">",
19 '"': """,
20 "'": "'",
21 })[character]!,
22 );
23
24/** A network-free iframe file. Fixture host/data are separate and never in this build graph. */
25export default defineConfig(({ mode }) => ({
26 // Library builds retain this React switch by default; the iframe has no Node globals.
27 define: { "process.env.NODE_ENV": JSON.stringify("production") },
28 resolve: { dedupe: ["capnweb"] },
29 build: {
30 outDir: "dist",
31 emptyOutDir: mode !== "configurator" && mode !== "picker",
32 lib: {
33 entry:
34 mode === "picker"
35 ? "app/picker.ts"
36 : mode === "configurator"
37 ? "app/configurator.ts"
38 : "app/main.ts",
39 name: "SidebarApp",
40 formats: ["iife"],
41 },
42 cssCodeSplit: false,
43 rollupOptions: {
44 onwarn(warning, warn) {
45 // Kumo is client-only here; Rollup may safely remove its React server directive.
46 if (warning.code === "MODULE_LEVEL_DIRECTIVE" && warning.message.includes("use client"))
47 return;
48 warn(warning);
49 },
50 output: { inlineDynamicImports: true },
51 },
52 },
53 esbuild: { jsx: "automatic" },
54 plugins: [
55 tailwindcss(),
56 {
57 name: "sidebar-single-file",
58 enforce: "post",
59 generateBundle(_options, bundle) {
60 const scripts: string[] = [];
61 const styles: string[] = [];
62 // Seed reviewed host variables before any JS mounts or host theme arrives.
63 const tokens = JSON.parse(
64 readFileSync(
65 new URL("../forkhorse-dashboard/src/host-tokens.json", import.meta.url),
66 "utf8",
67 ),
68 );
69 for (const [name, entry] of Object.entries(bundle)) {
70 if (entry.type === "chunk") scripts.push(entry.code);
71 else if (name.endsWith(".css")) styles.push(String(entry.source));
72 else throw new Error(`Unexpected external sidebar asset: ${name}`);
73 delete bundle[name];
74 }
75 // Host variables follow Kumo CSS, matching the palette applied after mount.
76 styles.push(
77 `:root{${vars({ ...tokens.shared, ...tokens.light })}}[data-mode="dark"]{${vars(tokens.dark)}}`,
78 );
79 styles.push(
80 `.loading-panel{display:none}@media(min-width:1280px){.loading-panel{display:block;flex:0 0 var(--layout-activity-panel)}}.loading-caption{margin:0}`,
81 );
82 this.emitFile({
83 type: "asset",
84 fileName:
85 mode === "picker"
86 ? "picker.html"
87 : mode === "configurator"
88 ? "configurator.html"
89 : "sidebar.html",
90 source: `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${escape(brand.name)}</title><link rel="icon" type="image/svg+xml" href="${markIconUrl(brand.mark)}"><script>document.documentElement.dataset.mode=matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light";document.documentElement.style.colorScheme=document.documentElement.dataset.mode;</script><style>${styles.join("\n").replace(/<\/style/gi, "<\\/style")}</style></head><body><div id="sidebar">${mode === "picker" || mode === "configurator" ? "" : `<div class="app-shell"><main class="main-pane"><div class="main-container"><header class="page-header"><p role="status" class="caption muted loading-caption">Loading ${escape(brand.slug)}…</p></header></div></main><div class="loading-panel" aria-hidden="true"></div></div>`}</div><script>${scripts.join("\n").replace(/<\/script/gi, "<\\/script")}</script></body></html>`,
91 });
92 },
93 },
94 ],
95}));
96