#Build & infra
How the SPA is bundled, how the CLI ships (Nix and npm), and what CI and GitHub Pages do. See Architecture for the runtime picture and CLI reference for the commands these pipelines invoke.
#App bundling
src/build-app.ts bundles app/main.ts with Bun.build + bun-plugin-svelte (runes mode) — no Vite. buildApp() returns the JS and CSS as strings; pageHtml() composes them into a complete single-page HTML shell. Notable details, each documented in the source:
- Theme CSS is generated, not hand-written.
themeCss()renders the default:rootblocks (light, plus aprefers-color-scheme: darkoverride) from theTHEMESarray inapp/lib/themes.ts, so the shell's palette cannot drift from the app's. - Embedded data can't break out of its tag.
jsonTag()JSON-unicode-escapes every<, so</scriptcan never occur in the body regardless of the value — file sources contain arbitrary Nix text, including literal</script>strings. - Whitespace minification stays on even in dev, because
bun-plugin-sveltederives Svelte'spreserveWhitespacefrom it and preserved whitespace leaks visible text nodes intowhite-space: presource views.
Serve mode never embeds manifest.json — its presence is the client's static-mode signal (see Frontend and the exporter in src/export.ts).
#serve --dev
src/serve.ts builds the page in memory and, with --dev, watches app/ recursively. A .svelte/.ts/.css change triggers a debounced in-memory rebuild, then pushes data: reload to every browser connected to the GET /dev/events server-sent-events route. The client snippet pageHtml() injects reloads on that message — and also on SSE reconnect, which covers server-side .ts changes: those are bun --watch's job (the process restarts, the connection drops, the client reloads when it comes back).
#Nix packaging
flake.nix (flake-parts) exposes packages.flake-explorer from package.nix. Because serve bundles the SPA at CLI runtime, bun build --compile is out — the package ships the TypeScript tree plus vendored node_modules and a bun wrapper:
node_modulesis a fixed-output derivation (bun install --frozen-lockfile) with a pinnedoutputHash; the lock is pure JS so one hash serves every platform. Refresh procedure is documented next to the hash inpackage.nix.- Sources are an explicit fileset include-list; tests are excluded from the shipped package because bun's test scanner follows the
resultsymlink and would run a stale second copy of the suite. checks.testispassthru.tests.unit: an offlinebun testagainst the vendorednode_modulesinside the sandbox (see Testing).nixitself is deliberately not in the dev shell or wrapper: the CLI must use the host's nix so store paths and the flake registry match the user's system.treefmt(via treefmt-nix) formats Nix only; Biome owns TS/Svelte throughbiome.json.
#npm packaging
package.json publishes @kriswill/flake-explorer with a files allowlist (bin/, flake-explorer.ts, src/, app/, tsconfig.json, minus *.test.ts). The bin entry is bin/flake-explorer.mjs, a two-line launcher with a #!/usr/bin/env bun shebang that imports the TypeScript entry point directly — bun executes TS natively. The optional bun npm dependency provides a runtime for npx/bunx users; the Nix wrapper supplies its own.
#CI
.github/workflows/ci.yml runs four jobs on PRs and pushes to main:
| Job | What it does |
|---|---|
test |
bun test --coverage with nix installed and FLAKE_EXPLORER_REQUIRE_NIX=1, so the real-nix integration tests fail loudly instead of silently skipping; uploads coverage via octocov |
typecheck |
tsc --noEmit + svelte-check, then bun run docs as a smoke check so a broken docs pipeline surfaces on the PR, not on the Pages deploy |
lint |
bunx biome ci . |
nix |
nix flake check -L (builds the package, offline test derivation, treefmt check) |
Coverage is a ratchet, not a threshold: .octocov.yml reads coverage/lcov.info with acceptable: current >= prev, diffing PRs against the report stored from the default branch — coverage may rise but never drop.
#GitHub Pages
.github/workflows/pages.yml publishes on pushes to main:
bun flake-explorer.ts export . --all --sources all --html _site/flake.html— a single-file static export of this repo's own flake as the live demo;index.htmlis a copy so the site root works.bun run docs—typedoc(viatypedoc-plugin-markdowninto.docs-api), thenbun scripts/build-docs.ts --out _site/docs --api .docs-api.
Resulting site layout: / is the demo, /docs/ renders these pages, /docs/api/ is the generated API reference. scripts/build-docs.ts converts docs/*.md with marked into a shared shell styled by the same themeCss() as the app; mermaid is bundled locally from the scripts/docs-mermaid-client.ts entry (no CDN) and included only on pages containing a mermaid fence. Links out of docs/ (e.g. ../src/schema.ts) are rewritten to the GitHub blob view.
#Docs workflow for contributors
Edit docs/*.md directly — GitHub renders them natively, including mermaid fences, so the markdown must stand on its own. Pages are registered in the ordered PAGES nav list in scripts/build-docs.ts; README.md becomes index.html. Run bun run docs to build the full site locally into _site/docs (CI runs the same command).