release: WRNexusJS 0.4.0
This commit is contained in:
@@ -21,7 +21,7 @@ import { fileURLToPath } from "node:url";
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const packageRoot = join(here, "..");
|
||||
let cachedCss: string | undefined;
|
||||
let cachedNames: string[] | undefined;
|
||||
let cachedComponentFiles: Map<string, string> | undefined;
|
||||
|
||||
/** Absolute path to the directory of Wire UI component `.wrn` files. */
|
||||
export function uiComponentsDir(): string {
|
||||
@@ -38,11 +38,55 @@ export function uiCss(): string {
|
||||
return (cachedCss ??= readFileSync(uiCssPath(), "utf8"));
|
||||
}
|
||||
|
||||
/** Names of the built-in components (e.g. for `wrnexus eject` listing). */
|
||||
export function uiComponentNames(): string[] {
|
||||
cachedNames ??= readdirSync(uiComponentsDir())
|
||||
.filter((f) => f.endsWith(".wrn"))
|
||||
.map((f) => f.replace(/\.wrn$/, ""))
|
||||
.sort();
|
||||
return [...cachedNames];
|
||||
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";
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { uiComponentsDir } from "./index.ts";
|
||||
export interface UiComponentMetadata {
|
||||
name: string;
|
||||
mount: string;
|
||||
category?: string;
|
||||
purpose?: string;
|
||||
props?: Array<{ name: string; default?: unknown }>;
|
||||
events?: string[];
|
||||
}
|
||||
export interface UiComponentReference {
|
||||
count: number;
|
||||
components: UiComponentMetadata[];
|
||||
}
|
||||
let referenceCache: UiComponentReference | undefined;
|
||||
export function uiComponentReference(): UiComponentReference {
|
||||
if (!referenceCache)
|
||||
referenceCache = JSON.parse(
|
||||
readFileSync(join(uiComponentsDir(), "..", "component-reference.json"), "utf8"),
|
||||
) as UiComponentReference;
|
||||
return structuredClone(referenceCache);
|
||||
}
|
||||
export function findUiComponent(name: string): UiComponentMetadata | undefined {
|
||||
return uiComponentReference().components.find(
|
||||
(component) => component.name === name || component.mount === name,
|
||||
);
|
||||
}
|
||||
export function auditUiComponents(): Array<{ component: string; issue: string }> {
|
||||
const issues: Array<{ component: string; issue: string }> = [];
|
||||
for (const component of uiComponentReference().components) {
|
||||
const props = new Set((component.props ?? []).map((prop) => prop.name));
|
||||
if (!props.has("class"))
|
||||
issues.push({ component: component.name, issue: "missing class prop" });
|
||||
if (!props.has("size")) issues.push({ component: component.name, issue: "missing size prop" });
|
||||
if (!props.has("color"))
|
||||
issues.push({ component: component.name, issue: "missing color prop" });
|
||||
}
|
||||
return issues;
|
||||
}
|
||||
Reference in New Issue
Block a user