#!/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(); 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; });