diff --git a/src/capability-language/structural-plan.ts b/src/capability-language/structural-plan.ts index 6b03c5f..2ddcf44 100644 --- a/src/capability-language/structural-plan.ts +++ b/src/capability-language/structural-plan.ts @@ -167,11 +167,12 @@ export const planStructure = async (rootPath: string, request: StructuralRequest resolveResource, }); if (compiled.resource.kind === "package") { - const configuration = JSON.parse(await fs.readFile(path.join(resourceRoot, "quixos.check.json"), "utf8")); + const configurationSource = await read(resourceRoot, "quixos.check.json"); + const configuration = configurationSource === null ? undefined : JSON.parse(configurationSource); // Execution bindings are immutable build outputs owned by checkedArtifacts, // not source edits produced by a structural preview. const artifacts = - configuration.backend === "execution" + !configuration || configuration.backend === "execution" ? [] : [ { diff --git a/test/structural-plan.test.ts b/test/structural-plan.test.ts index 8ecaedc..4cad42e 100644 --- a/test/structural-plan.test.ts +++ b/test/structural-plan.test.ts @@ -13,6 +13,37 @@ import { } from "../src/capability-language/structural-plan.js"; const execFile = promisify(callback); +test("package structural validation does not require source-generated bindings", async (context) => { + const root = await fs.mkdtemp(path.join(os.tmpdir(), "qx-structural-package-test-")); + context.after(() => fs.rm(root, { recursive: true, force: true })); + await execFile("git", ["-C", root, "init"]); + await fs.writeFile(path.join(root, ".gitignore"), ".quixos/\n"); + await fs.writeFile( + path.join(root, "quixos.lock"), + `quixos-lock version 1 { quixos source { repository "https://example.test/quixos.git"; commit "${"a".repeat(40)}"; } }`, + ); + const before = 'package Test id "package:test" revision "package:test@1" {}'; + await fs.writeFile(path.join(root, "package.qx"), before); + const request: StructuralRequest = { + kind: "package", + source: { repository: "https://example.test/package.git", commit: "b".repeat(40) }, + files: [{ file: "package.qx", expected: before, replace: `${before}\n// updated` }], + }; + const plan = await planStructure(root, request); + assert.deepEqual( + plan.changes.map((change) => change.file), + ["package.qx"], + ); + assert.equal(plan.validation, "resource-graph"); + await fs.writeFile(path.join(root, "quixos.check.json"), "not valid json"); + await assert.rejects(() => planStructure(root, request), SyntaxError); + await fs.writeFile(path.join(root, "quixos.check.json"), '{"backend":"execution"}'); + assert.deepEqual( + (await planStructure(root, request)).changes.map((change) => change.file), + ["package.qx"], + ); +}); + test("structural plans validate the graph, journal originals, and reject stale edits", async (context) => { const root = await fs.mkdtemp(path.join(os.tmpdir(), "qx-structural-test-")); context.after(() => fs.rm(root, { recursive: true, force: true }));