Files
quixos-protocol/src/capability-language/cli.ts
T

130 lines
4.7 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
import { readFile } from "node:fs/promises";
import process from "node:process";
import { compileCapabilityResourceSource, 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] [--resource]
[--workspace-id ID] [--workspace-revision-id ID]
[--source-root-commit GIT_REV] <workspace.qx | ->
Parses, lowers, and validates a Quixos capability workspace. --resource instead
expects one standalone interface or package document. By default the checked
semantic workspace revision or resource 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 resource = 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 === "--resource") resource = 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)
|| (resource && Boolean(workspaceId || workspaceRevisionId || sourceRootCommit))
) {
throw new Error(usage);
}
return { checkOnly, resource, 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,
resource,
workspaceId,
workspaceRevisionId,
sourceRootCommit,
fileName,
} = parseArgs(args);
const source =
fileName === "-"
? await new Promise<string>((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 reportDiagnostics = (diagnostics: readonly {
fileName: string;
line: number;
column: number;
phase: string;
code: string;
message: string;
path?: string;
}[]) => {
for (const diagnostic of 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;
};
if (resource) {
const result = compileCapabilityResourceSource(source, {
source: {
repository: "https://compiler.invalid/resource.git",
commit: "0000000000000000000000000000000000000000",
},
fileName,
});
if (!result.ok) {
reportDiagnostics(result.diagnostics);
return;
}
if (!checkOnly) process.stdout.write(`${JSON.stringify(result.resource, null, 2)}\n`);
return;
}
const result = compileCapabilitySource(source, fileName);
if (!result.ok) {
reportDiagnostics(result.diagnostics);
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;
});