fix(apple): scope application roots to the inventory format (#7714)

This commit is contained in:
gensokyo-dev
2026-10-28 13:28:03 +08:00
committed by GitHub
parent 8397a0c9eb
commit b594fbc3e7
3 changed files with 160 additions and 7 deletions
+39 -1
View File
@@ -685,7 +685,7 @@ describe("Apple application bridge candidates", () => {
),
);
const result = project(
inventoryEvidence("zip", "Fixture.zip", [...scripts, ...native]),
inventoryEvidence("ipa", "Fixture.ipa", [...scripts, ...native]),
);
expect(result.components.javascript).toHaveLength(129);
@@ -970,3 +970,41 @@ it("preserves observed unavailable bundle metadata and signing paths without inv
expect(result.platforms).toEqual(["ios"]);
expect(result.unavailable_components).toHaveLength(3);
});
it.each(["zip", "directory", "dmg"] as const)(
"does not apply IPA roots to a %s inventory",
(format) => {
const result = project(
inventoryEvidence(format, `Sample.${format}`, [
{ path: "Payload/App.app/Info.plist", format: "plist" },
{ path: "Extras/Other.app/Info.plist", format: "plist" },
]),
);
expect(result.root_format).toBe(format);
expect(result.application_roots).toEqual([]);
expect(result.platforms).toEqual([]);
expect(result.bundles).toEqual([]);
},
);
it.each(["ipa", "zip", "directory", "dmg"] as const)(
"uses only the %s root convention in a mixed inventory",
(format) => {
const source = inventoryEvidence(format, `Mixed.${format}`, [
{ path: "Payload/App.app/Info.plist", format: "plist" },
{ path: "Extras/Other.app/Contents/Info.plist", format: "plist" },
macho("Extras/Other.app/Contents/MacOS/Other"),
{
path: "Extras/Other.app/Contents/Resources/app.js",
format: "javascript-bundle",
},
]);
const result = project(source);
expect(result.application_roots).toEqual([
format === "ipa" ? "Payload/App.app" : "Extras/Other.app",
]);
expect(result.platforms).toEqual([format === "ipa" ? "ios" : "macos"]);
expect(result.components.javascript).toHaveLength(1);
expect(result.components.executables).toHaveLength(1);
},
);
+7 -6
View File
@@ -123,12 +123,13 @@ const applicationRoots = (
for (const path of directoryPaths(tree)) {
const segments = path.split("/");
const app = segments[1];
if (
anatomyNameIs(segments[0] ?? "", "Payload") &&
app !== undefined &&
/\.app$/iu.test(app)
) {
found.add(`${segments[0]}/${app}`);
if (subject.format === "ipa") {
if (
anatomyNameIs(segments[0] ?? "", "Payload") &&
app !== undefined &&
/\.app$/iu.test(app)
)
found.add(`${segments[0]}/${app}`);
continue;
}
const index = segments.findIndex(
@@ -0,0 +1,114 @@
import { readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import {
TextReader,
Uint8ArrayReader,
Uint8ArrayWriter,
ZipWriter,
} from "@zip.js/zip.js";
import { expect } from "vitest";
import { appleApplicationProjectionResultSchema } from "../../../src/domain/apple/appleApplication.js";
import { parseEvidence } from "../../../src/domain/evidence.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { cliTest } from "../../support/cli/cliFixture.js";
cliTest.for([
{
name: "flat ZIP",
extension: "zip",
payload: true,
deep: false,
roots: [],
platforms: [],
},
{
name: "flat IPA",
extension: "ipa",
payload: true,
deep: false,
roots: ["Payload/Main.app"],
platforms: ["ios"],
},
{
name: "deep ZIP",
extension: "zip",
payload: false,
deep: true,
roots: ["Extras/Helper.app"],
platforms: ["macos"],
},
{
name: "IPA with unrooted deep helper",
extension: "ipa",
payload: true,
deep: true,
roots: ["Payload/Main.app"],
platforms: ["ios"],
},
])(
"keeps format-specific Apple roots through CLI and MCP ($name)",
async (fixture, { cli }) => {
const root = await createTestTempDirectory("rea-apple-root-format-");
const path = join(root, `Sample.${fixture.extension}`);
const writer = new ZipWriter(new Uint8ArrayWriter());
if (fixture.payload)
await writer.add(
"Payload/Main.app/Info.plist",
new TextReader("<plist><dict/></plist>"),
);
await writer.add(
"Extras/Other.app/Info.plist",
new TextReader("<plist><dict/></plist>"),
);
if (fixture.deep) {
await writer.add(
"Extras/Helper.app/Contents/Info.plist",
new TextReader("<plist><dict/></plist>"),
);
const macho = Buffer.concat([
Buffer.from("cffaedfe070000010300008002000000", "hex"),
Buffer.alloc(40),
]);
await writer.add(
"Extras/Helper.app/Contents/MacOS/Helper",
new Uint8ArrayReader(macho),
);
await writer.add("Shared/other.js", new TextReader("console.log(2)"));
}
const bytes = await writer.close();
await writeFile(path, bytes);
const inspected = await cli.run({
arguments: ["inspect-artifact", path, "--json"],
});
expect(inspected.exitCode, inspected.stderr).toBe(0);
const source = parseEvidence(inspected.json);
const input = { inventory_evidence: [source] };
const request = join(root, "request.json");
await writeFile(request, JSON.stringify(input));
const projected = await cli.run({
arguments: ["project-apple-application-graph", request, "--json"],
});
expect(projected.exitCode, projected.stderr).toBe(0);
const cliGraph = appleApplicationProjectionResultSchema.parse(
parseEvidence(projected.json).normalized_result,
);
expect(cliGraph.root_format).toBe(fixture.extension);
expect(cliGraph.application_roots).toEqual(fixture.roots);
expect(cliGraph.platforms).toEqual(fixture.platforms);
if (fixture.deep && fixture.extension === "ipa")
expect(cliGraph.bridge_candidates).toContainEqual({
source_path: "Shared/other.js",
native_path: "Extras/Helper.app/Contents/MacOS/Helper",
basis: "javascript-and-native-content",
});
const call = await cli.sharedMcpCall?.();
if (call === undefined) throw new Error("Missing production MCP session");
const reply = await call("project_apple_application_graph", input);
expect(reply.isError, JSON.stringify(reply)).not.toBe(true);
const mcpGraph = appleApplicationProjectionResultSchema.parse(
parseEvidence(reply.structuredContent).normalized_result,
);
expect(mcpGraph).toEqual(cliGraph);
expect(Buffer.compare(await readFile(path), Buffer.from(bytes))).toBe(0);
},
);