Simplify workspace authoring and isolate managed component styles
This commit is contained in:
@@ -11,6 +11,9 @@ import os from "node:os";
|
||||
import {spawnSync} from "node:child_process";
|
||||
import { planStructure, applyStructure, resumeStructure, type StructuralRequest } from "./structural-plan.js";
|
||||
import {scaffoldRecipe, type ScaffoldRecipe} from "./scaffold-recipes.js";
|
||||
import {checkBundleSources} from "../bindings/bundle-policy.js";
|
||||
import {sealMigrations} from "./migration-seal.js";
|
||||
import {generatePackageDescriptor} from "../bindings/index.js";
|
||||
import {buildCheckedPackage, buildImmutableCandidate, snapshotCommit} from "./checked-build.js";
|
||||
import {formatQuixosLock, loadQuixosLock, parseQuixosLockDocument} from "../resource-lock/index.js";
|
||||
import { walkSyntax } from "./source.js";
|
||||
@@ -38,6 +41,32 @@ const planSummary = (plan: Awaited<ReturnType<typeof planStructure>>) => ({
|
||||
|
||||
const main = async () => {
|
||||
const [command, ...args] = process.argv.slice(2);
|
||||
if (command === "package-identity") {
|
||||
if (args.length !== 1) throw new Error("usage: quixos-qx package-identity PACKAGE_QX");
|
||||
const authored = await readFile(args[0], "utf8");
|
||||
const syntax = parseQx(authored);
|
||||
const declarations = [...walkSyntax(syntax.root)].filter(node => node.kind === "packageResourceDecl");
|
||||
if (syntax.diagnostics.length || declarations.length !== 1) throw new Error("Expected one valid package declaration");
|
||||
const literals = declarations[0].children.filter(node => node.kind === "stringLiteral");
|
||||
process.stdout.write(JSON.stringify({id: JSON.parse(authored.slice(literals[0].start, literals[0].end)),
|
||||
revision: JSON.parse(authored.slice(literals[1].start, literals[1].end))}) + "\n");
|
||||
return;
|
||||
}
|
||||
if (command === "package-descriptor") {
|
||||
if (args.length !== 2) throw new Error("usage: quixos-qx package-descriptor SCHEMA REVISION_ID");
|
||||
process.stdout.write(generatePackageDescriptor(JSON.parse(await readFile(args[0], "utf8")), args[1]));
|
||||
return;
|
||||
}
|
||||
if (command === "migration-seal") {
|
||||
if (args.length !== 1) throw new Error("usage: quixos-qx migration-seal PACKAGE_DIRECTORY");
|
||||
process.stdout.write(JSON.stringify(await sealMigrations(args[0])) + "\n");
|
||||
return;
|
||||
}
|
||||
if (command === "bundle-policy") {
|
||||
if (args.length !== 1) throw new Error("usage: quixos-qx bundle-policy SOURCE_DIRECTORY");
|
||||
await checkBundleSources(args[0]);
|
||||
return;
|
||||
}
|
||||
if (command === "worklist" && !args.includes("--help")) {
|
||||
if (args.length !== 1) throw new Error("usage: quixos-qx worklist WORKBENCH");
|
||||
process.stdout.write(`${JSON.stringify(await authoringWorklist(args[0]), null, 2)}\n`);
|
||||
@@ -62,10 +91,10 @@ const main = async () => {
|
||||
if (!args.length || args.length > 2) throw new Error("usage: quixos-qx converge WORKBENCH [REGISTERED_DIRECTORY] (join package writers first)");
|
||||
const context = await authoringContext(args[0]);
|
||||
if (command === "converge") {
|
||||
const result = spawnSync("flock", ["--exclusive", "--nonblock", "--conflict-exit-code", "75", path.join(context.workbench, ".quixos/converge.lock"),
|
||||
const result = spawnSync("flock", ["--exclusive", "--timeout", "120", "--conflict-exit-code", "75", path.join(context.workbench, ".quixos/converge.lock"),
|
||||
process.execPath, process.argv[1], "_converge", context.workbench, ...(args[1] ? [args[1]] : [])], {stdio: "inherit"});
|
||||
if (result.error) throw result.error;
|
||||
if (result.status === 75) process.stderr.write("Another source coordinator is running; retry when it finishes.\n");
|
||||
if (result.status === 75) process.stderr.write("Timed out after 120 seconds waiting for source capture; inspect the active coordinator. No build lock is held.\n");
|
||||
process.exitCode = result.status ?? 1; return;
|
||||
}
|
||||
const result = await convergeAuthoring(context.workbench, args[1]);
|
||||
@@ -130,7 +159,7 @@ const main = async () => {
|
||||
const parsed = parseQuixosLockDocument(await readFile(path.join(root, file), "utf8"));
|
||||
if (parsed.ok && parsed.document.resources.some(entry => entry.kind === kind && entry.binding === name)) target = file;
|
||||
}
|
||||
const request: StructuralRequest = {kind: entrypoint, source: await authorSource(root), files: [
|
||||
const request: StructuralRequest = {kind: entrypoint, source: await authorSource(root), validation: "syntax", files: [
|
||||
{file: `${entrypoint}.qx`, edits: [{operation: "import", kind: resourceKind, name}]},
|
||||
{file: target, edits: [{operation: "dependency", kind: resourceKind, name, source: {repository, commit}}]},
|
||||
]};
|
||||
@@ -180,12 +209,17 @@ const main = async () => {
|
||||
process.stdout.write(`${JSON.stringify(publish ? await applyPinUpgrades(plan, resume, undefined, {acceptEdits}) : plan, null, 2)}\n`);
|
||||
return;
|
||||
}
|
||||
if (["scaffold-package", "scaffold-function", "scaffold-migration", "scaffold-refresh"].includes(command)) {
|
||||
if (command === "scaffold-refresh") throw new Error("Refresh is no longer required: edit declarations and typed implementation wiring, then run qx-workspace check. Dependency installation uses scaffold install.");
|
||||
if (["scaffold-package", "scaffold-function", "scaffold-migration", "scaffold-install"].includes(command)) {
|
||||
const [root, specFile, ...flags] = args;
|
||||
let spec: ScaffoldRecipe;
|
||||
if (command === "scaffold-function" && /^[A-Za-z_][A-Za-z0-9_]*$/.test(specFile ?? "")) {
|
||||
const registry = JSON.parse(await readFile(path.join(root, "quixos.scaffold.json"), "utf8"));
|
||||
spec = {source: await authorSource(root), name: specFile, id: `export:${registry.name}:${specFile}`};
|
||||
const authored = parseQx(await readFile(path.join(root, "package.qx"), "utf8"));
|
||||
const declarationNode = [...walkSyntax(authored.root)].find(node => node.kind === "packageResourceDecl");
|
||||
const nameNode = declarationNode?.children.find(node => node.kind === "identifier");
|
||||
if (!nameNode) throw new Error("Expected a package declaration");
|
||||
const name = authored.source.slice(nameNode.start, nameNode.end);
|
||||
spec = {source: await authorSource(root), name: specFile, id: `export:${name}:${specFile}`};
|
||||
const declaration = flags.indexOf("--declaration");
|
||||
if (declaration >= 0) {
|
||||
if (!flags[declaration + 1]) throw new Error("--declaration requires a QX declaration file");
|
||||
@@ -201,9 +235,9 @@ const main = async () => {
|
||||
}
|
||||
} else spec = await readSpec(specFile) as ScaffoldRecipe;
|
||||
if (!root || !specFile || flags.some((flag) => !["--write", "--install"].includes(flag)) || (flags.includes("--install") && !flags.includes("--write"))) throw new Error("usage: quixos-qx scaffold-package|function|migration|refresh ROOT SPEC_JSON [--write [--install]]");
|
||||
const request = await scaffoldRecipe(root, command.slice(9) as "package" | "function" | "migration" | "refresh", spec);
|
||||
const plan = await planStructure(root, request, process.env.QUIXOS_SNAPSHOT_MAP);
|
||||
const applied = flags.includes("--write") ? await applyStructure(plan) : undefined;
|
||||
const plan = command === "scaffold-install" ? undefined : await planStructure(root,
|
||||
await scaffoldRecipe(root, command.slice(9) as "package" | "function" | "migration", spec), process.env.QUIXOS_SNAPSHOT_MAP);
|
||||
const applied = plan && flags.includes("--write") ? await applyStructure(plan) : undefined;
|
||||
if (flags.includes("--install")) {
|
||||
const cwd = path.resolve(root, spec.directory ?? "");
|
||||
const toolchain = JSON.parse(await readFile(path.join(cwd, "quixos.toolchain.json"), "utf8"));
|
||||
@@ -218,7 +252,7 @@ const main = async () => {
|
||||
const locked = spawnSync("nix", ["flake", "lock"], {cwd, stdio: ["inherit", 2, 2]});
|
||||
if (locked.error || locked.status !== 0) throw new Error("Scaffold files retained; nix flake lock failed");
|
||||
}
|
||||
process.stdout.write(`${JSON.stringify({...planSummary(plan), applied}, null, 2)}\n`);
|
||||
process.stdout.write(`${JSON.stringify({...plan ? planSummary(plan) : {installed: true}, applied}, null, 2)}\n`);
|
||||
return;
|
||||
}
|
||||
if (command === "scaffold-structure") {
|
||||
|
||||
Reference in New Issue
Block a user