Build and activate checked component packages through normal source authoring (WI-12)
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { execFile as callback } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
import { scaffoldRecipe } from "../src/capability-language/scaffold-recipes.js";
|
||||
import { planStructure, applyStructure } from "../src/capability-language/structural-plan.js";
|
||||
const execFile = promisify(callback);
|
||||
test("checked component scaffolding composes with execution handlers without replacing authored files", async (context) => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), "qx-component-scaffold-"));
|
||||
context.after(() => fs.rm(root, { recursive: true, force: true }));
|
||||
await execFile("git", ["-C", root, "init"]);
|
||||
const source = { repository: "https://example.test/component.git", commit: "a".repeat(40) };
|
||||
const apply = async (
|
||||
command: Parameters<typeof scaffoldRecipe>[1],
|
||||
spec: Omit<Parameters<typeof scaffoldRecipe>[2], "source">,
|
||||
) => applyStructure(await planStructure(root, await scaffoldRecipe(root, command, { ...spec, source })));
|
||||
await apply("package", {
|
||||
name: "Widgets",
|
||||
id: "package:widgets",
|
||||
revision: "package:widgets@1",
|
||||
template: "typescript-component",
|
||||
tools: { quixos: source, protocol: source, helpers: source, sdk: source },
|
||||
});
|
||||
const packageFile = path.join(root, "package.qx");
|
||||
await fs.writeFile(packageFile, "import interface Data;\n" + (await fs.readFile(packageFile, "utf8")));
|
||||
await apply("component", {
|
||||
name: "Card",
|
||||
id: "component:card",
|
||||
declaration: 'component Card id "component:card" subject Data runtime react;',
|
||||
});
|
||||
assert.deepEqual(JSON.parse(await fs.readFile(path.join(root, "quixos.components.json"), "utf8")), {
|
||||
"component:card": { entry: "src/components/Card.tsx", export: "Card" },
|
||||
});
|
||||
const card = path.join(root, "src/components/Card.tsx");
|
||||
await fs.appendFile(card, "\n// authored extension\n");
|
||||
const retained = await fs.readFile(card, "utf8");
|
||||
await apply("function", {
|
||||
name: "echo",
|
||||
id: "echo",
|
||||
declaration: 'function echo id "echo" : string -> string execution snapshot targets [browser,server];',
|
||||
});
|
||||
await apply("component", {
|
||||
name: "Details",
|
||||
id: "component:details",
|
||||
declaration: 'component Details id "component:details" subject Data runtime react;',
|
||||
});
|
||||
assert.equal(await fs.readFile(card, "utf8"), retained);
|
||||
assert.match(await fs.readFile(path.join(root, "src/server.ts"), "utf8"), /echo/);
|
||||
assert.match(await fs.readFile(path.join(root, "flake.nix"), "utf8"), /react \? null/);
|
||||
await assert.rejects(
|
||||
apply("component", {
|
||||
name: "Card",
|
||||
id: "component:other",
|
||||
declaration: 'component Card id "component:other" subject Data runtime react;',
|
||||
}),
|
||||
/unique/,
|
||||
);
|
||||
await assert.rejects(
|
||||
apply("component", {
|
||||
name: "Injected",
|
||||
id: "component:inject",
|
||||
declaration:
|
||||
'component Injected id "component:inject" subject Data runtime react; function sneak id "sneak" : unit -> unit;',
|
||||
}),
|
||||
/exactly one/,
|
||||
);
|
||||
});
|
||||
|
||||
test("controller scaffolding preserves existing registrations and rejects duplicate identities", async (context) => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), "qx-controller-scaffold-"));
|
||||
context.after(() => fs.rm(root, { recursive: true, force: true }));
|
||||
await execFile("git", ["-C", root, "init"]);
|
||||
const source = { repository: "https://example.test/adapter.git", commit: "a".repeat(40) };
|
||||
const apply = async (
|
||||
command: Parameters<typeof scaffoldRecipe>[1],
|
||||
spec: Omit<Parameters<typeof scaffoldRecipe>[2], "source">,
|
||||
) => applyStructure(await planStructure(root, await scaffoldRecipe(root, command, { ...spec, source })));
|
||||
await apply("package", {
|
||||
name: "Adapter",
|
||||
id: "adapter",
|
||||
revision: "adapter@1",
|
||||
tools: { quixos: source, protocol: source, helpers: source },
|
||||
});
|
||||
const controller = {
|
||||
subject: "display@1",
|
||||
snapshot: "display:requested",
|
||||
remote: "display:apply",
|
||||
input: "value" as const,
|
||||
};
|
||||
await apply("controller", { id: "display:controller", controller });
|
||||
await apply("controller", { id: "display:monitor", controller: { ...controller, snapshot: "display:status" } });
|
||||
const entries = JSON.parse(await fs.readFile(path.join(root, "quixos.controllers.json"), "utf8")).controllers;
|
||||
assert.equal(entries.length, 2);
|
||||
assert.deepEqual(entries[0], { id: "display:controller", ...controller });
|
||||
await assert.rejects(apply("controller", { id: "display:controller", controller }), /duplicate/);
|
||||
});
|
||||
Reference in New Issue
Block a user