#!/usr/bin/env node import assert from "node:assert/strict"; import { execFile } from "node:child_process"; import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; import { artifactCliEvidence, artifactMcpResult, withArtifactMcp, } from "../../lib/artifact-e2e.mjs"; const exec = promisify(execFile); const fixtureRoot = await mkdtemp(join(tmpdir(), "rea-interface-builder-")); const appPath = join(fixtureRoot, "Fixture.app"); const resourcesPath = join(appPath, "Contents", "Resources"); const executablePath = join(appPath, "Contents", "MacOS", "Fixture"); const nibPath = join(resourcesPath, "Main.nib"); const sourceRoot = fileURLToPath( new URL("../../../tests/conformance/interface-builder/", import.meta.url), ); let report; try { await mkdir(join(appPath, "Contents", "MacOS"), { recursive: true }); await mkdir(resourcesPath, { recursive: true }); await writeFile( join(appPath, "Contents", "Info.plist"), ` CFBundleExecutableFixtureCFBundleIdentifierdev.rea.interface-builder-fixture\n`, ); await exec("xcrun", [ "ibtool", "--errors", "--warnings", "--compile", nibPath, join(sourceRoot, "Main.xib"), ]); await exec("cc", [join(sourceRoot, "fixture.c"), "-o", executablePath]); const evidence = await artifactCliEvidence( "decode-interface-builder", appPath, ); const decoded = evidence.normalized_result; await withArtifactMcp(appPath, async (client) => { const mcp = await artifactMcpResult(client, "decode_interface_builder"); assert.deepEqual( mcp, decoded, "CLI and MCP compiled archive results differ", ); }); assert.equal(decoded.documents.length, 1); assert.equal( decoded.documents[0]?.relative_path, "Contents/Resources/Main.nib", ); assert.equal(decoded.documents[0]?.document_kind, "nib"); assert.equal(decoded.documents[0]?.hierarchy_complete, true); assert.ok(decoded.documents[0]?.object_count > 0); assert.ok(decoded.documents[0]?.connection_count >= 2); const button = decoded.graph.nodes.find( (node) => node.kind === "control" && node.name === "NSButton", ); const action = decoded.graph.nodes.find( (node) => node.kind === "action" && node.name === "runAction:", ); assert.ok(button, "compiled button control was not decoded"); assert.ok(action, "compiled action selector was not decoded"); assert.ok( decoded.graph.edges.some( (edge) => edge.relation === "target_action" && edge.resolution === "observed" && (edge.from === action.id || edge.to === action.id), ), "compiled target/action connection was not represented as observed", ); assert.ok( decoded.graph.coverage.some( (facet) => facet.facet === "archive_decode" && facet.status === "complete", ), ); assert.equal(decoded.graph.truncated, false); report = { ok: true, fixture: "compiled-appkit-nib", provider: evidence.provider, public_cli: true, public_stdio_mcp: true, target_sha256: decoded.target_sha256, archive_sha256: decoded.documents[0].archive_sha256, objects: decoded.documents[0].object_count, connections: decoded.documents[0].connection_count, recovered_control: button.name, recovered_action: action.name, hierarchy_complete: decoded.documents[0].hierarchy_complete, truncated: decoded.graph.truncated, }; } finally { await rm(fixtureRoot, { recursive: true, force: true }); } process.stdout.write(`${JSON.stringify(report)}\n`);