Build composable Camino packages with explicit type and source dependencies
This commit is contained in:
@@ -192,16 +192,16 @@ const main = async () => {
|
||||
}
|
||||
if (command === "check-committed" && !args.includes("--help")) {
|
||||
const [kind, repository, commit, log, ...extra] = args;
|
||||
if (!["workspace", "interface", "package"].includes(kind) || !log || extra.length)
|
||||
throw new Error("usage: quixos-qx check-committed workspace|interface|package REPOSITORY COMMIT LOG_FILE");
|
||||
if (!["workspace", "interface", "package", "build"].includes(kind) || !log || extra.length)
|
||||
throw new Error("usage: quixos-qx check-committed workspace|interface|package|build REPOSITORY COMMIT LOG_FILE");
|
||||
process.stdout.write(
|
||||
`${await buildImmutableCandidate({ repository, commit }, kind as "workspace" | "interface" | "package", log)}\n`,
|
||||
`${await buildImmutableCandidate({ repository, commit }, kind as "workspace" | "interface" | "package" | "build", log)}\n`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!command || command === "--help" || args.includes("--help")) {
|
||||
process.stdout.write(
|
||||
"quixos-qx: author-check, author-contract, converge, worklist, inspect, resources, check-committed, source-baseline, scaffold-package, scaffold-interface, scaffold-function, scaffold-query, scaffold-dependency, scaffold-structure, scaffold-resume, pin-upgrade, parse, lint, format\n" +
|
||||
"quixos-qx: author-check, author-contract, converge, worklist, inspect, resources, check-committed, source-baseline, scaffold-package, scaffold-interface, scaffold-build, scaffold-function, scaffold-query, scaffold-dependency, scaffold-structure, scaffold-resume, pin-upgrade, parse, lint, format\n" +
|
||||
"inspect WORKBENCH [RESOURCE] shows provisional contracts, with explicit historical fallback; never verification evidence.\n" +
|
||||
"resources WORKBENCH lists registered editable repositories. Use qx-workspace for the workspace authoring workflow.\n",
|
||||
);
|
||||
@@ -226,7 +226,7 @@ const main = async () => {
|
||||
if (flags.length && !flags[0].startsWith("--")) {
|
||||
repository = flags.shift();
|
||||
commit = flags.shift();
|
||||
} else if (root && ["interface", "package"].includes(kind) && name) {
|
||||
} else if (root && ["interface", "package", "build"].includes(kind) && name) {
|
||||
const context = await authoringContext(root);
|
||||
const alias = await realpath(path.join(context.workbench, `${kind}s`, name)).catch(() => null);
|
||||
const matches = context.resources.filter(
|
||||
@@ -256,15 +256,17 @@ const main = async () => {
|
||||
}
|
||||
if (
|
||||
!root ||
|
||||
!["interface", "package"].includes(kind) ||
|
||||
!["interface", "package", "build"].includes(kind) ||
|
||||
!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name ?? "") ||
|
||||
!repository ||
|
||||
!commit ||
|
||||
flags.some((flag) => flag !== "--write")
|
||||
)
|
||||
throw new Error("usage: quixos-qx scaffold-dependency ROOT interface|package NAME REPOSITORY COMMIT [--write]");
|
||||
const resourceKind = kind as "package" | "interface";
|
||||
let entrypoint: "workspace" | "package" | "interface" | undefined;
|
||||
throw new Error(
|
||||
"usage: quixos-qx scaffold-dependency ROOT interface|package|build NAME REPOSITORY COMMIT [--write]",
|
||||
);
|
||||
const resourceKind = kind as "package" | "interface" | "build";
|
||||
let entrypoint: "workspace" | "package" | "interface" | "build" | undefined;
|
||||
for (const candidate of ["workspace", "package", "interface"] as const) {
|
||||
try {
|
||||
await readFile(path.join(root, `${candidate}.qx`));
|
||||
@@ -274,7 +276,14 @@ const main = async () => {
|
||||
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
||||
}
|
||||
}
|
||||
if (!entrypoint) throw new Error("No QX repository entrypoint");
|
||||
if (!entrypoint) {
|
||||
const context = await authoringContext(root);
|
||||
const selected = context.resources.find(
|
||||
(entry) => path.join(context.workbench, entry.directory) === path.resolve(root),
|
||||
);
|
||||
if (selected?.kind !== "build") throw new Error("No QX repository entrypoint");
|
||||
entrypoint = "build";
|
||||
}
|
||||
const lock = await loadQuixosLock(path.join(root, "quixos.lock"));
|
||||
if (!lock.ok) throw new Error("Invalid resource lock");
|
||||
let target = "quixos.lock";
|
||||
@@ -288,7 +297,9 @@ const main = async () => {
|
||||
source: await authorSource(root),
|
||||
validation: "syntax",
|
||||
files: [
|
||||
{ file: `${entrypoint}.qx`, edits: [{ operation: "import", kind: resourceKind, name }] },
|
||||
...(entrypoint !== "build" && resourceKind !== "build"
|
||||
? [{ file: `${entrypoint}.qx`, edits: [{ operation: "import" as const, kind: resourceKind, name }] }]
|
||||
: []),
|
||||
{
|
||||
file: target,
|
||||
edits: [{ operation: "dependency", kind: resourceKind, name, source: { repository, commit } }],
|
||||
@@ -301,26 +312,45 @@ const main = async () => {
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (command === "scaffold-interface") {
|
||||
if (command === "scaffold-interface" || command === "scaffold-build") {
|
||||
const isBuild = command === "scaffold-build";
|
||||
const [root, specFile, ...flags] = args;
|
||||
if (!root || !specFile || flags.some((flag) => flag !== "--write"))
|
||||
throw new Error("usage: quixos-qx scaffold-interface ROOT SPEC_JSON [--write]");
|
||||
throw new Error("usage: quixos-qx scaffold-interface|scaffold-build ROOT SPEC_JSON [--write]");
|
||||
const spec = (await readSpec(specFile)) as ScaffoldRecipe;
|
||||
if (!spec.name || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(spec.name) || !spec.id || !spec.revision || !spec.tools?.quixos)
|
||||
throw new Error("Interface scaffold requires name, id, revision and Quixos toolchain source");
|
||||
if (
|
||||
!spec.name ||
|
||||
!/^[A-Za-z_][A-Za-z0-9_]*$/.test(spec.name) ||
|
||||
(!isBuild && (!spec.id || !spec.revision)) ||
|
||||
!spec.tools?.quixos
|
||||
)
|
||||
throw new Error(
|
||||
isBuild
|
||||
? "Build scaffold requires name and Quixos toolchain source"
|
||||
: "Interface scaffold requires name, id, revision and Quixos toolchain source",
|
||||
);
|
||||
const request: StructuralRequest = {
|
||||
kind: "interface",
|
||||
kind: isBuild ? "build" : "interface",
|
||||
// Like package scaffolding, declaration creation is provisional. Imports
|
||||
// can be attached next; the normal check verifies the complete graph.
|
||||
validation: "syntax",
|
||||
source: spec.source,
|
||||
files: [
|
||||
{
|
||||
file: "interface.qx",
|
||||
create:
|
||||
spec.declaration ??
|
||||
`interface ${spec.name} id ${JSON.stringify(spec.id)} revision ${JSON.stringify(spec.revision)} {\n}\n`,
|
||||
},
|
||||
...(!isBuild
|
||||
? [
|
||||
{
|
||||
file: "interface.qx",
|
||||
create:
|
||||
spec.declaration ??
|
||||
`interface ${spec.name} id ${JSON.stringify(spec.id)} revision ${JSON.stringify(spec.revision)} {\n}\n`,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
file: "README.md",
|
||||
create: `# ${spec.name}\n\nManaged build dependency. Consumers select sources or Nix outputs through context.dependencies.\n`,
|
||||
},
|
||||
]),
|
||||
{
|
||||
file: "quixos.lock",
|
||||
create: formatQuixosLock({
|
||||
|
||||
Reference in New Issue
Block a user