Fork Horse repository, listed by its owner. Publisher snapshot.

packages/forkhorse-gatekeeper/vite.config.ts

Snapshot at 34e3175

Containing directory

4,203 bytes · typescriptView raw
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        "&": "&amp;",
17        "<": "&lt;",
18        ">": "&gt;",
19        '"': "&quot;",
20        "'": "&#39;",
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