a174faea5c
Generate state-backed atoms, interfaces, relationships and React/CSS packages in a resumable command that prints the full source diff. Keep scaffold output ordinary editable code. Shorten installed authoring guides while preserving contracts and clarify historical design context. Test generated bundles through immutable Nix verification and cover browser isolation, nested sizing, interruption recovery, and command help.
68 lines
5.4 KiB
TypeScript
68 lines
5.4 KiB
TypeScript
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";
|
|
import {contentDigest} from "../src/capability-model/evolution.js";
|
|
import {sealMigrations} from "../src/capability-language/migration-seal.js";
|
|
import {reactPlatformTypes} from "../src/bindings/react-platform.js";
|
|
const execFile = promisify(callback);
|
|
|
|
test("React preset applies its browser build script and shared-platform imports", async (context) => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "qx-react-recipe-"));
|
|
context.after(() => fs.rm(root, {recursive: true, force: true}));
|
|
await execFile("git", ["-C", root, "init"]);
|
|
const source = {repository: "https://example.test/react.git", commit: "a".repeat(40)};
|
|
const request = await scaffoldRecipe(root, "package", {source, name: "React", id: "package:react", revision: "package:react@1", template: "typescript-react", tools: {quixos: source, protocol: source, helpers: source, sdk: source}});
|
|
await applyStructure(await planStructure(root, request));
|
|
assert.match(await fs.readFile(path.join(root, "src/impl/sourceGet.ts"), "utf8"), /component.js\?browser-source/);
|
|
assert.match(await fs.readFile(path.join(root, "flake.nix"), "utf8"), /browserSources = true/);
|
|
assert.equal(await fs.readFile(path.join(root, "src/gen/web-studio-react-runtime.d.ts"), "utf8"), reactPlatformTypes);
|
|
assert.match(await fs.readFile(path.join(root, "src/browser-assets.d.ts"), "utf8"), /declare module "\*\.css"/);
|
|
assert.equal(JSON.parse(await fs.readFile(path.join(root, "quixos.check.json"), "utf8")).options.messages["org.quixos.web-studio.ReactProps"].export, "opaqueReactPropsBinding");
|
|
await assert.rejects(fs.access(path.join(root, "quixos.scaffold.json")));
|
|
assert.equal(JSON.parse(await fs.readFile(path.join(root, "tsconfig.json"), "utf8")).compilerOptions.jsx, "react-jsx");
|
|
});
|
|
|
|
test("imperative scaffolds preserve authored wiring; migration sealing is explicit and separate", async (context) => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "qx-recipes-"));
|
|
context.after(() => fs.rm(root, {recursive: true, force: true}));
|
|
await execFile("git", ["-C", root, "init"]);
|
|
await fs.writeFile(path.join(root, ".gitignore"), ".quixos/\n");
|
|
const source = {repository: "https://example.test/chess.git", commit: "a".repeat(40)};
|
|
const base = {source, directory: "packages/Chess"};
|
|
const apply = async (command: Parameters<typeof scaffoldRecipe>[1], input: Parameters<typeof scaffoldRecipe>[2]) => applyStructure(await planStructure(root, await scaffoldRecipe(root, command, input)));
|
|
await apply("package", {...base, name: "Chess", id: "package:chess", revision: "package:chess@1", tools: {quixos: source, protocol: source, helpers: source, sdk: source}});
|
|
await apply("function", {...base, name: "play", id: "export:play"});
|
|
const filename = path.join(root, base.directory, "src/impl/play.ts");
|
|
const edited = 'import type {Implementation} from "../gen/qx.js";\nexport const handler: Implementation["play"] = async () => null;\n';
|
|
await fs.writeFile(filename, edited);
|
|
const old = {version: 1}, next = {version: 2}, from = contentDigest(old), to = contentDigest(next);
|
|
await apply("migration", {...base, name: "upgrade", id: "export:upgrade", migration: {id: "upgrade-v2", scopeId: "board", from, to, predecessors: [], ports: [], contracts: {[from]: old, [to]: next}}});
|
|
const migrationFile = path.join(root, base.directory, "src/migrations/upgrade.ts");
|
|
await fs.appendFile(migrationFile, "\n// authored migration change\n");
|
|
await sealMigrations(path.join(root, base.directory));
|
|
assert.equal(await fs.readFile(filename, "utf8"), edited);
|
|
const catalog = JSON.parse(await fs.readFile(path.join(root, base.directory, "quixos.migrations.json"), "utf8"));
|
|
assert.equal(catalog.migrations[0].implementation.digest, contentDigest(await fs.readFile(migrationFile, "utf8")));
|
|
assert.match(await fs.readFile(path.join(root, base.directory, "src/migrate.ts"), "utf8"), /export:upgrade/);
|
|
await assert.rejects(() => apply("function", {...base, name: "play", id: "export:play"}), /unique/);
|
|
const declarations = path.join(root, base.directory, "package.qx");
|
|
await fs.writeFile(declarations, (await fs.readFile(declarations, "utf8")).replace(/}\s*$/, ' function authored id "export:authored" : unit -> unit;\n}\n'));
|
|
const server = path.join(root, base.directory, "src/server.ts");
|
|
await fs.appendFile(server, "\n// authored comment must survive\n");
|
|
await apply("function", {...base, name: "another", id: "export:another"});
|
|
assert.match(await fs.readFile(server, "utf8"), /authored comment must survive/);
|
|
await assert.rejects(() => apply("refresh", base), /removed/);
|
|
await assert.rejects(fs.access(path.join(root, base.directory, "src/impl/authored.ts")));
|
|
assert.equal(await fs.readFile(filename, "utf8"), edited);
|
|
await planStructure(root, {kind: "package", source, resourceRoot: base.directory, validation: "syntax", files: [{
|
|
file: `${base.directory}/package.qx`, edits: [{operation: "replace", target: {kind: "packageResourceDecl", id: "package:chess"},
|
|
source: 'package Other id "package:other" revision "package:other@1" {}'}],
|
|
}]});
|
|
});
|