#!/usr/bin/env node import path from "node:path"; import process from "node:process"; import { formatMessages } from "esbuild"; import { buildModuleUi, ModuleUiBuildError } from "./build-module-ui.js"; function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } /** * `ekklesia-build-module-ui` — the canonical module UI build command, run from a module directory. * * A module with neither UI role is not an error: backend-only modules stay first-class and this * command reports that there was nothing to build and exits successfully. When one or both roles * are declared, this one command builds exactly that set. */ async function main(): Promise { const moduleDirectory = process.argv[2] ?? process.cwd(); try { const result = await buildModuleUi({ moduleDirectory }); if (result.artifacts.length === 0) { process.stdout.write("No module UI entrypoints declared; skipping the module UI build.\n"); return 0; } for (const artifact of result.artifacts) { process.stdout.write( `Built module UI (${artifact.contributionId ?? artifact.kind}): ${path.relative(moduleDirectory, artifact.outputFile)}\n`, ); } return 0; } catch (error) { if (error instanceof ModuleUiBuildError) { process.stderr.write(`${error.kind ? `${error.kind}: ` : ""}${error.message}\n`); // esbuild's own diagnostics carry the file, line, and reason a developer needs. They are // printed here, never folded into the sanitized error message itself. const diagnostics = isRecord(error) ? error.diagnostics : undefined; if (Array.isArray(diagnostics) && diagnostics.length > 0) { const formatted = await formatMessages(diagnostics, { kind: "error", color: true }); process.stderr.write(formatted.join("")); } return 1; } process.stderr.write("The module UI build failed unexpectedly.\n"); return 1; } } process.exitCode = await main();