import { existsSync, readFileSync, readdirSync, statSync } from "node:fs"; import { dirname, join, relative, resolve } from "node:path"; import { buildRouter, createRouteManifest, nameRoutes } from "@wrnexus/router"; import { createPluginRunner, discoverPlugins } from "@wrnexus/plugin"; import { loadAppConfig } from "@wrnexus/styles"; import { uiComponentsDir } from "@wrnexus/ui/registry"; import { parse } from "@wrnexus/syntax"; export type InspectTarget = "packages" | "plugins" | "routes" | "assets" | "runtimes" | "styles" | "migrations" | "bundle"; export function inspectComponent(appRoot: string, requestedName: string): unknown { const root = resolve(appRoot); const router = buildRouter(join(root, "app"), { componentDirs: [uiComponentsDir()] }); const component = router.components.find( (candidate) => candidate.name.toLowerCase() === requestedName.toLowerCase(), ); if (!component) throw new Error(`Component not found: ${requestedName}`); const ast = parse(readFileSync(component.file, "utf8")); return { name: ast.name, file: relative(root, component.file).replace(/\\/g, "/"), props: ast.props.map(({ name, valueType, required, default: defaultValue }) => ({ name, type: valueType ?? "unknown", required, ...(defaultValue === undefined || defaultValue === "undefined" ? {} : { default: defaultValue }), })), outputs: ast.outputs.map(({ name, payload }) => ({ name, payload: payload ?? null })), functions: ast.runtimeFunctions.map(({ name, runtime, async, parameters, returnType }) => ({ name, runtime, async, parameters, returnType: returnType ?? "unknown", })), }; } function json(path: string): Record | null { try { return JSON.parse(readFileSync(path, "utf8")) as Record; } catch { return null; } } function workspaceRoot(start: string): string { let current = resolve(start); while (true) { if (json(join(current, "package.json"))?.workspaces) return current; const parent = dirname(current); if (parent === current) return resolve(start); current = parent; } } function packageRows(root: string) { const dirs = [join(root, "packages"), join(root, "services")]; const rows: Array> = []; for (const dir of dirs) { if (!existsSync(dir)) continue; for (const name of readdirSync(dir)) { const path = join(dir, name); if (!statSync(path).isDirectory()) continue; const pkg = json(join(path, "package.json")); if (!pkg?.name) continue; rows.push({ name: pkg.name, version: pkg.version, private: pkg.private === true, path: relative(root, path).replace(/\\/g, "/"), plugin: !!pkg.wrnexus?.plugin, }); } } return rows.sort((a, b) => String(a.name).localeCompare(String(b.name))); } export async function inspectProject(appRoot: string, target: InspectTarget): Promise { const root = resolve(appRoot); const workspace = workspaceRoot(root); if (target === "packages") return packageRows(workspace); if (target === "bundle") { const report = join(root, "dist", "build-report.json"); if (!existsSync(report)) throw new Error("Run `wrnexus build` before inspecting the bundle."); return json(report); } const config = await loadAppConfig(root); const input = await discoverPlugins(root, config.plugins, { includeDevDependencies: true, strict: true, }); const runner = createPluginRunner(input, { root, mode: "development", command: "dev", metadata: new Map(), warn: () => {}, }); await runner.configure(config as Record); await runner.configResolved(config as Readonly>); const contributions = await runner.contributions(); if (target === "plugins") return runner.plugins.map((plugin) => ({ name: plugin.name, version: plugin.version, enforce: plugin.enforce ?? "normal", })); if (target === "assets") return contributions.assets.map(({ id, publicPath, contentType, immutable }) => ({ id, publicPath, contentType, immutable: immutable ?? false, })); if (target === "runtimes") return contributions.clientRuntimes.map( ({ id, publicPath, type, load, singleton, bundle }) => ({ id, publicPath, type, load, singleton, bundle, }), ); if (target === "styles") return contributions.styles.map(({ id, entry, source, order }) => ({ id, entry, source, order: order ?? "normal", })); if (target === "migrations") return contributions.migrations.map(({ id, entry, source, database }) => ({ id, entry, inline: source !== undefined, database: database ?? "default", })); const router = buildRouter(join(root, "app"), { componentDirs: [uiComponentsDir(), ...contributions.componentDirs], externalRoutes: contributions.routes, middlewareFiles: contributions.middleware, }); return { pages: createRouteManifest(nameRoutes(router.pages)), api: createRouteManifest(nameRoutes(router.api)), realtime: createRouteManifest(nameRoutes(router.realtime)), middleware: router.middlewareFiles.map((file) => relative(root, file).replace(/\\/g, "/")), components: router.components.map((component) => ({ ...component, file: relative(root, component.file).replace(/\\/g, "/"), })), }; } export async function runInspect( appRoot: string, targetArg?: string, args: string[] = [], ): Promise { const target = (targetArg ?? "plugins") as InspectTarget; if ( ![ "packages", "plugins", "routes", "assets", "runtimes", "styles", "migrations", "bundle", ].includes(target) ) throw new Error(`Unknown inspect target: ${target}`); const value = await inspectProject(appRoot, target); if (args.includes("--json")) { console.log(JSON.stringify(value, null, 2)); return; } console.log(`WRNexus ${target}\n`); if (Array.isArray(value)) for (const row of value) console.log( ` ${Object.entries(row as Record) .map(([key, item]) => `${key}=${String(item)}`) .join(" ")}`, ); else console.log(JSON.stringify(value, null, 2)); } export function runInspectComponent(appRoot: string, name: string, args: string[] = []): void { const value = inspectComponent(appRoot, name); if (args.includes("--json")) console.log(JSON.stringify(value, null, 2)); else { console.log(`WRNexus component ${name}\n`); console.log(JSON.stringify(value, null, 2)); } }