94 lines
3.3 KiB
TypeScript
94 lines
3.3 KiB
TypeScript
/**
|
|
* @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<string, string> | undefined;
|
|
|
|
/** Absolute path to the directory of WrNexus UI component `.wrn` files. */
|
|
export function uiComponentsDir(): string {
|
|
return join(packageRoot, "components");
|
|
}
|
|
|
|
/** Absolute path to the WrNexus UI stylesheet. */
|
|
export function uiCssPath(): string {
|
|
return join(packageRoot, "ui.css");
|
|
}
|
|
|
|
/** The WrNexus UI stylesheet contents (all `.wrn-*` classes, themed via tokens). */
|
|
export function uiCss(): string {
|
|
return (cachedCss ??= readFileSync(uiCssPath(), "utf8"));
|
|
}
|
|
|
|
function uiComponentFiles(): Map<string, string> {
|
|
if (cachedComponentFiles) return cachedComponentFiles;
|
|
|
|
const files = new Map<string, string>();
|
|
|
|
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";
|