Files
rea/scripts/verify-macos-bundle.mjs
T
Kaoru0822andClaude Opus 5.5 dd3e7aef0d feat(apple): project macOS bundle anatomy in application graphs
project_apple_application_graph accepted only IPA inventories. It now
also projects macOS .app directories and ZIP or DMG inventories that
contain applications. It reports application roots, platforms, and
every nested bundle with its path-convention role, layout, parent,
Info.plist path, executable candidates, and signing paths:
- roles: app extensions, XPC services (including inside frameworks),
  frameworks, login items, system and driver extensions, plug-ins,
  resource bundles, and helper or nested applications;
- privileged helpers in Contents/Library/LaunchServices and launchd
  plists in Contents/Library/LaunchAgents or LaunchDaemons;
- direct files of Contents/Helpers;
- symlinks by path. Their targets are not inventoried, so versioned
  frameworks are described through their real Versions/<version>/
  directories.

AppleDouble sidecars (._* and __MACOSX/) are excluded from roles. A DMG
whose contents could not be inventoried is reported as partial. Other
inventory formats remain input errors. The projection still reads no
plist or CMS semantics; it points callers at inspect_plist.

The result schema is additive. projection_id now covers the new fields,
so IPA projection IDs differ from earlier versions.

verify:macos-bundle compiles a source-owned app with clang (CLT only).
It packs it as a directory, a ditto ZIP, and an APFS DMG, and checks
identical anatomy through the CLI with stdio MCP parity. It runs in the
macOS CI job.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 10:06:48 +00:00

168 lines
5.7 KiB
JavaScript

import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { promisify } from "node:util";
import {
artifactCliEvidence,
artifactMcpResult,
withArtifactMcp,
} from "./lib/artifact-e2e.mjs";
import {
buildMacosBundleFixture,
preflightMacosBundleFixture,
} from "./lib/macos-bundle-fixture.mjs";
const exec = promisify(execFile);
await preflightMacosBundleFixture();
const root = await mkdtemp(join(tmpdir(), "rea-macos-bundle-"));
/** Bundle roles relative to the application root, independent of the container. */
const anatomy = (projection, applicationRoot) => {
const relative = (path) =>
applicationRoot === "."
? path
: path === applicationRoot
? "."
: path.slice(applicationRoot.length + 1);
const paths = (components) =>
components.map(({ path }) => relative(path)).sort();
return {
bundles: projection.bundles.map((bundle) => ({
path: relative(bundle.path),
role: bundle.role,
layout: bundle.layout,
parent: bundle.parent_path === null ? null : relative(bundle.parent_path),
info_plist: bundle.info_plist_path && relative(bundle.info_plist_path),
executables: bundle.executable_candidates.map(relative),
signing: bundle.signing_paths.map(relative),
})),
privileged_helpers: paths(projection.components.privileged_helpers),
launchd_plists: projection.components.launchd_plists
.map(({ path, domain }) => `${domain}:${relative(path)}`)
.sort(),
helpers: paths(projection.components.helpers),
};
};
const FRAMEWORK = "Contents/Frameworks/Core.framework";
const EXPECTED_BUNDLES = [
[".", "application", "macos-deep", null],
[FRAMEWORK, "framework", "versioned-framework", "."],
[
`${FRAMEWORK}/Versions/A/XPCServices/FwSvc.xpc`,
"xpc-service",
"macos-deep",
FRAMEWORK,
],
["Contents/Library/LoginItems/Login.app", "login-item", "macos-deep", "."],
["Contents/PlugIns/Ext.appex", "app-extension", "macos-deep", "."],
["Contents/XPCServices/Svc.xpc", "xpc-service", "macos-deep", "."],
];
try {
const { app } = await buildMacosBundleFixture(root);
const zip = join(root, "MacFixture.zip");
await exec("/usr/bin/ditto", ["-c", "-k", "--keepParent", app, zip]);
const staging = join(root, "dmg-source");
await mkdir(staging);
await exec("/usr/bin/ditto", [app, join(staging, "MacFixture.app")]);
await symlink("/Applications", join(staging, "Applications"));
const dmg = join(root, "MacFixture.dmg");
// The default APFS image attaches as an image disk plus a synthesized
// container, which exercises detach of devices sharing one image.
await exec("/usr/bin/hdiutil", [
"create",
"-quiet",
"-srcfolder",
staging,
"-volname",
"ReaFixture",
"-format",
"UDZO",
dmg,
]);
const containers = [
{ target: app, format: "directory", applicationRoot: "." },
{ target: zip, format: "zip", applicationRoot: "MacFixture.app" },
{
target: dmg,
format: "dmg",
applicationRoot: "MacFixture.dmg/ReaFixture/MacFixture.app",
},
];
const anatomies = [];
for (const { target, format, applicationRoot } of containers) {
const inventory = await artifactCliEvidence("inspect-artifact", target);
const input = join(root, `${format}-projection.json`);
await writeFile(input, JSON.stringify({ inventory_evidence: [inventory] }));
const evidence = await artifactCliEvidence(
"project-apple-application-graph",
input,
);
const projection = evidence.normalized_result;
assert.equal(evidence.subject?.format, format);
assert.equal(projection.root_format, format);
assert.deepEqual(projection.platforms, ["macos"]);
assert.deepEqual(projection.application_roots, [applicationRoot]);
assert.equal(projection.coverage.status, "complete-within-inventory");
const observed = anatomy(projection, applicationRoot);
assert.deepEqual(
observed.bundles.map(({ path, role, layout, parent }) => [
path,
role,
layout,
parent,
]),
EXPECTED_BUNDLES,
`${format} bundle roles drifted`,
);
assert.deepEqual(observed.privileged_helpers, [
"Contents/Library/LaunchServices/com.example.rea.helper",
]);
assert.deepEqual(observed.launchd_plists, [
"agent:Contents/Library/LaunchAgents/com.example.rea.agent.plist",
"daemon:Contents/Library/LaunchDaemons/com.example.rea.helper.plist",
]);
assert.deepEqual(observed.helpers, ["Contents/Helpers/rea-tool"]);
assert.ok(
projection.symlinks.some((path) =>
path.endsWith(`${FRAMEWORK}/Versions/Current`),
),
"Versions/Current symlink was not reported",
);
anatomies.push(observed);
if (format === "directory")
await withArtifactMcp(app, async (client) => {
assert.deepEqual(
await artifactMcpResult(client, "project_apple_application_graph", {
inventory_evidence: [inventory],
}),
projection,
);
});
}
assert.deepEqual(anatomies[1], anatomies[0], "ZIP anatomy differs");
assert.deepEqual(anatomies[2], anatomies[0], "DMG anatomy differs");
const { stdout: attached } = await exec("/usr/bin/hdiutil", ["info"]);
assert.ok(!attached.includes(dmg), "DMG remained attached after inventory");
process.stdout.write(
`${JSON.stringify({
ok: true,
mocked: false,
cli: true,
stdio_mcp: true,
containers: containers.map(({ format }) => format),
bundles: EXPECTED_BUNDLES.length,
dmg_detached: true,
})}\n`,
);
} finally {
await rm(root, { recursive: true, force: true });
}