/** * @wrnexus/ui registry — SERVER-ONLY filesystem helpers for discovering the * package's `.wrn` component sources and stylesheet on disk. * * This is the code that used to live in `index.ts`. It was moved here * because it depends on `node:fs`/`node:path`/`node:url`, which don't exist * in a browser build — and `index.ts` is the package's main entry, so any * client-side (browser) bundle that reaches ANY `@wrnexus/ui` component * transitively tried to bundle this file too, failing with: * "Browser polyfill for module 'node:url' doesn't have a matching export * named 'fileURLToPath'" * * Import from `@wrnexus/ui/registry` (server-side code only — CLI, * dev-server, build). Never import this from a `.wrn` component or anything * that can end up in a client runtime bundle. */ import { readdirSync, readFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; const here = dirname(fileURLToPath(import.meta.url)); const packageRoot = join(here, ".."); let cachedCss: string | undefined; let cachedComponentFiles: Map | undefined; /** Absolute path to the directory of Wire UI component `.wrn` files. */ export function uiComponentsDir(): string { return join(packageRoot, "components"); } /** Absolute path to the Wire UI stylesheet. */ export function uiCssPath(): string { return join(packageRoot, "ui.css"); } /** The Wire UI stylesheet contents (all `.wire-*` classes, themed via tokens). */ export function uiCss(): string { return (cachedCss ??= readFileSync(uiCssPath(), "utf8")); } function uiComponentFiles(): Map { if (cachedComponentFiles) return cachedComponentFiles; const files = new Map(); const componentFiles = readdirSync(uiComponentsDir()).filter((entry) => entry.endsWith(".wrn")); for (const file of componentFiles) { const path = join(uiComponentsDir(), file); const source = readFileSync(path, "utf8"); const declaration = /^\s*component\s+([A-Za-z][A-Za-z0-9_]*)\b/m.exec(source); if (!declaration) { throw new Error(`UI component source does not declare a component: ${path}`); } const name = declaration[1]!; const previous = files.get(name); if (previous) { throw new Error(`Duplicate UI component declaration '${name}' in ${previous} and ${path}`); } files.set(name, path); } cachedComponentFiles = files; return files; } /** Names declared by the bundled components, independent of filename casing. */ export function uiComponentNames(): string[] { return [...uiComponentFiles().keys()].sort((left, right) => left.localeCompare(right)); } /** Absolute path to a bundled component by its declared component name. */ export function uiComponentPath(name: string): string { const files = uiComponentFiles(); const exact = files.get(name); if (exact) return exact; const normalized = name.toLowerCase(); const matches = [...files.entries()].filter( ([componentName]) => componentName.toLowerCase() === normalized, ); if (matches.length === 1) return matches[0]![1]; throw new Error(`Unknown WRNexus UI component '${name}'.`); } export { uiComponentReference, findUiComponent, auditUiComponents } from "./metadata.ts"; export type { UiComponentMetadata, UiComponentReference } from "./metadata.ts";