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

130 lines
4.2 KiB
JavaScript

#!/usr/bin/env node
import { writeFile } from "node:fs/promises";
import process from "node:process";
import { bindingSchema } from "../bindings/index.js";
import { compileCapabilityResourceRepository, type ResolvedCapabilityResource } from "./assembly.js";
import { createGitCapabilityResolver } from "./git-resolver.js";
const usage = `usage: quixos-resource-compile --root DIRECTORY --kind interface|package
--repository URL --commit GIT_REV --checkout-root DIRECTORY
[--snapshot-map PATH] [--snapshot-only true] [--graph-out PATH] [--schema-out PATH]
Resolves a standalone resource's recursive dependency-lock graph, validates
its interface.qx or package.qx manifest, and emits the checked resource as JSON.`;
const parseArgs = (args: string[]) => {
const values = new Map<string, string>();
for (let index = 0; index < args.length; index += 2) {
const key = args[index];
const value = args[index + 1];
if (!key?.startsWith("--") || !value) throw new Error(usage);
if (
![
"--root",
"--kind",
"--repository",
"--commit",
"--checkout-root",
"--snapshot-map",
"--snapshot-only",
"--graph-out",
"--schema-out",
].includes(key) ||
values.has(key)
)
throw new Error(usage);
values.set(key, value);
}
const rootDirectory = values.get("--root");
const kind = values.get("--kind");
const repository = values.get("--repository");
const commit = values.get("--commit")?.toLowerCase();
const checkoutRoot = values.get("--checkout-root");
if (!rootDirectory || !repository || !commit || !checkoutRoot || (kind !== "interface" && kind !== "package")) {
throw new Error(usage);
}
if (!/^([0-9a-f]{40}|[0-9a-f]{64})$/.test(commit)) {
throw new Error("--commit must be a full Git object ID");
}
if (values.has("--snapshot-only") && values.get("--snapshot-only") !== "true")
throw new Error("--snapshot-only accepts true");
return {
rootDirectory,
kind,
repository,
commit,
checkoutRoot,
snapshotMap: values.get("--snapshot-map"),
snapshotOnly: values.get("--snapshot-only") === "true",
graphOut: values.get("--graph-out"),
schemaOut: values.get("--schema-out"),
} as const;
};
const graphEntry = (node: ResolvedCapabilityResource) => ({
key: node.key,
kind: node.kind,
source: node.source,
directory: node.directory,
resourceId:
node.resource.kind === "interface" ? node.resource.revision.interfaceId : node.resource.revision.packageId,
revisionId: node.resource.revision.revisionId,
dependencies: [...node.dependencies.entries()].map(([binding, dependency]) => ({
binding,
resourceKey: dependency.key,
})),
});
const main = async () => {
if (process.argv.includes("--help") || process.argv.includes("-h")) {
process.stdout.write(`${usage}\n`);
return;
}
const options = parseArgs(process.argv.slice(2));
const resolveResource = await createGitCapabilityResolver({
checkoutRoot: options.checkoutRoot,
snapshotMap: options.snapshotMap,
snapshotOnly: options.snapshotOnly,
});
const compiled = await compileCapabilityResourceRepository({
rootDirectory: options.rootDirectory,
kind: options.kind,
source: {
resolver: "git",
repository: options.repository,
commit: options.commit,
},
resolveResource,
});
if (options.graphOut) {
await writeFile(
options.graphOut,
`${JSON.stringify(
{
formatVersion: 1,
quixos: compiled.lock.quixos,
root: graphEntry(
compiled.resources.find(
(node) =>
node.source.repository === options.repository &&
node.source.commit === options.commit &&
node.kind === options.kind,
)!,
),
resources: compiled.resources.map(graphEntry),
},
null,
2,
)}\n`,
);
}
if (options.schemaOut) await writeFile(options.schemaOut, `${JSON.stringify(bindingSchema(compiled), null, 2)}\n`);
process.stdout.write(`${JSON.stringify(compiled.resource, null, 2)}\n`);
};
main().catch((error: unknown) => {
process.stderr.write(`${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`);
process.exitCode = 1;
});