#!/usr/bin/env node import { readFile } from "node:fs/promises"; import process from "node:process"; import { compileCapabilitySource } from "./parser.js"; import { capabilityId } from "../capability-model/types.js"; import { compileWorkspaceRevision } from "../capability-model/validation.js"; const usage = `usage: quixos-capability-compile [--check] [--workspace-id ID] [--workspace-revision-id ID] [--source-root-commit GIT_REV] Parses, lowers, and validates a Quixos capability workspace. By default the checked semantic workspace revision is written as JSON. --check emits no JSON. The identity overrides instantiate a checked built-in/template assembly for one real workspace; both must be supplied together.`; const parseArgs = (args: string[]) => { let checkOnly = false; let workspaceId: string | undefined; let workspaceRevisionId: string | undefined; let sourceRootCommit: string | undefined; const positional: string[] = []; for (let index = 0; index < args.length; index += 1) { const argument = args[index]!; if (argument === "--check") checkOnly = true; else if (argument === "--workspace-id") workspaceId = args[++index]; else if (argument === "--workspace-revision-id") workspaceRevisionId = args[++index]; else if (argument === "--source-root-commit") sourceRootCommit = args[++index]; else if (argument === "-") positional.push(argument); else if (argument.startsWith("-")) throw new Error(usage); else positional.push(argument); } if (positional.length !== 1 || Boolean(workspaceId) !== Boolean(workspaceRevisionId)) { throw new Error(usage); } return { checkOnly, workspaceId, workspaceRevisionId, sourceRootCommit, fileName: positional[0]! }; }; const main = async () => { const args = process.argv.slice(2); if (args.includes("--help") || args.includes("-h")) { process.stdout.write(`${usage}\n`); return; } const { checkOnly, workspaceId, workspaceRevisionId, sourceRootCommit, fileName, } = parseArgs(args); const source = fileName === "-" ? await new Promise((resolve, reject) => { const chunks: Buffer[] = []; process.stdin.on("data", (chunk: Buffer) => chunks.push(chunk)); process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf8"))); process.stdin.on("error", reject); }) : await readFile(fileName, "utf8"); const result = compileCapabilitySource(source, fileName); if (!result.ok) { for (const diagnostic of result.diagnostics) { const location = diagnostic.line ? `${diagnostic.fileName}:${diagnostic.line}:${diagnostic.column + 1}` : `${diagnostic.fileName}${diagnostic.path ? `:${diagnostic.path}` : ""}`; process.stderr.write( `${location}: ${diagnostic.phase} ${diagnostic.code}: ${diagnostic.message}\n`, ); } process.exitCode = 1; return; } const workspace = workspaceId && workspaceRevisionId ? { ...result.workspace, workspaceId: capabilityId.workspace(workspaceId), id: capabilityId.workspaceRevision(workspaceRevisionId), ...(sourceRootCommit ? { sourceRootCommit } : {}), } : { ...result.workspace, ...(sourceRootCommit ? { sourceRootCommit } : {}) }; const instantiated = compileWorkspaceRevision(workspace); if (!instantiated.ok) { throw new Error(instantiated.issues.map((issue) => `${issue.path}: ${issue.message}`).join("\n")); } if (!checkOnly) { process.stdout.write(`${JSON.stringify(workspace, null, 2)}\n`); } }; main().catch((error: unknown) => { process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); process.exitCode = 1; });