2026-09-10 18:27:41 -07:00
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" ;
const execFile = promisify ( callback );
2026-09-13 22:07:18 -07:00
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 , "scripts/build-component.mjs" ), "utf8" ), /__quixos\/platform\/react/ );
assert . equal ( JSON . parse ( await fs . readFile ( path . join ( root , "tsconfig.json" ), "utf8" )). compilerOptions . jsx , "react-jsx" );
});
2026-09-10 18:27:41 -07:00
test ( "package/function/migration scaffolds register implementations and refresh code digests without overwriting code" , 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" );
2026-09-13 22:07:18 -07:00
const edited = 'import type {Implementation} from "../gen/qx.js";\nexport const handler: Implementation["play"] = async () => null;\n' ;
2026-09-10 18:27:41 -07:00
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 apply ( "refresh" , base );
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" )));
2026-09-13 22:07:18 -07:00
const bindings = await fs . readFile ( path . join ( root , base . directory , "src/gen/qx.ts" ), "utf8" );
2026-09-10 18:27:41 -07:00
assert . match ( bindings , /export:play/ );
assert . match ( await fs . readFile ( path . join ( root , base . directory , "src/migrate.ts" ), "utf8" ), /export:upgrade/ );
if ( process . env . QX_SCAFFOLD_TEST_SDK ) {
const sdk = await fs . realpath ( process . env . QX_SCAFFOLD_TEST_SDK );
const packageRoot = path . join ( root , base . directory );
await fs . mkdir ( path . join ( packageRoot , "node_modules/@quixos" ), { recursive : true });
await fs . symlink ( sdk , path . join ( packageRoot , "node_modules/@quixos/camino-package-runtime" ));
await fs . symlink ( path . join ( sdk , "node_modules/@types" ), path . join ( packageRoot , "node_modules/@types" ));
await execFile ( path . join ( sdk , "node_modules/.bin/tsc" ), [ "--noEmit" ], { cwd : packageRoot });
await execFile ( "nix-instantiate" , [ "--parse" , path . join ( packageRoot , "flake.nix" )]);
}
await assert . rejects (() => apply ( "function" , {... base , name : "play" , id : "export:play" }), /unique/ );
2026-09-14 15:24:37 -07:00
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' ));
await apply ( "refresh" , base );
assert . match ( await fs . readFile ( path . join ( root , base . directory , "src/server.ts" ), "utf8" ), /"authored":/ );
assert . match ( await fs . readFile ( path . join ( root , base . directory , "src/impl/authored.ts" ), "utf8" ), /Implement authored/ );
assert . equal ( await fs . readFile ( filename , "utf8" ), edited );
2026-09-14 10:26:11 -07:00
await assert . rejects (() => 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" {}' }],
}]}), /scaffold-owned package identity/ );
2026-09-10 18:27:41 -07:00
});