96 lines
3.7 KiB
JavaScript
96 lines
3.7 KiB
JavaScript
#!/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] <workspace.qx | ->
|
|
|
|
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<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 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;
|
|
});
|