Files
shadcn/app/lib/highlight-code.ts
zeekay 7d781bc1eb build: serve ui.hanzo.ai from our own stack
The site was on Cloudflare Pages as a direct upload — no git connection,
last deployed 2026-03-28 — while a GitHub Pages workflow ran on every
push against a repo that has Pages disabled (the API 404s). Two deploy
paths, neither of which shipped what main said.

One way now: a Dockerfile builds the static export and serves it with
ghcr.io/hanzoai/static, exactly like every other Hanzo static site, on
our own runners and our own ingress.

Along the way, drop what those two vendors left behind:
- output:'export' was gated on GITHUB_ACTIONS || CF_PAGES, so a build
  anywhere else silently produced no export at all. The site is a static
  export wherever it is built.
- basePath pointed at a '/react-sdk' GitHub Pages subdirectory that is
  not this project.
- highlight-code keyed off GITHUB_ACTIONS to pick server vs client
  highlighting; it means 'is this a production export', so it says that.
- pages/api/components returned a JSON blob and 404s in production,
  because a static export cannot serve an API route. The live index is
  the generated /api/registry/components.json.
2026-07-25 12:46:18 -07:00

82 lines
2.2 KiB
TypeScript

// Removed "use server" for static export compatibility
import { promises as fs } from "fs"
import path from "path"
import type { Highlighter } from "shiki"
// Highlight on the server while developing. A production build is a static
// export, which highlights on the client instead — so the build stays cheap.
const highlightCodeEnabled = process.env.NODE_ENV === "development"
// Singleton highlighter instance to prevent memory leaks
let highlighterInstance: Highlighter | null = null
let highlighterPromise: Promise<Highlighter> | null = null
async function getHighlighter(): Promise<Highlighter> {
if (highlighterInstance) {
return highlighterInstance
}
if (highlighterPromise) {
return highlighterPromise
}
highlighterPromise = (async () => {
const { createHighlighter } = await import("shiki")
const editorTheme = await fs.readFile(
path.join(process.cwd(), "lib/themes/dark.json"),
"utf-8"
)
const highlighter = await createHighlighter({
langs: ["typescript"],
themes: [JSON.parse(editorTheme)],
})
highlighterInstance = highlighter
return highlighter
})()
return highlighterPromise
}
export async function highlightCode(code: string) {
if (!highlightCodeEnabled) {
// Return code wrapped in basic HTML for static export
return `<pre><code class="language-typescript">${escapeHtml(code)}</code></pre>`
}
try {
const highlighter = await getHighlighter()
const html = await highlighter.codeToHtml(code, {
lang: "typescript",
theme: "Lambda Studio — Blackout",
})
return html
} catch (error) {
console.error("Syntax highlighting failed:", error)
return `<pre><code class="language-typescript">${escapeHtml(code)}</code></pre>`
}
}
// Clean up highlighter on process exit (for server)
if (typeof process !== "undefined") {
process.on("exit", () => {
if (highlighterInstance) {
highlighterInstance.dispose()
highlighterInstance = null
}
})
}
function escapeHtml(unsafe: string): string {
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;")
}