Files
rea/tests/acceptance/applications/applicationManifestRoots.test.ts
74050978be fix(javascript): keep dependency manifests out of application roots
Select the shallowest inventoried nondependency manifest and retain dependency package evidence without assigning Electron entry roles. Keep the artifact root when no application manifest is observed.

Adapted from PR #1392, with compiled CLI and stdio MCP regressions for root selection and dependency-only inputs.

Co-authored-by: Marnix Pluim <marnixpluim@users.noreply.github.com>

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 10:43:48 +08:00

121 lines
4.3 KiB
TypeScript

import { mkdir, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect } from "vitest";
import { z } from "zod";
import { javascriptApplicationAnalysisResultSchema } from "../../../src/domain/javascript/javascriptApplicationAnalysis.js";
import { connectLocalToolsMcp } from "../../fixtures/localToolsMcp.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { cliTest } from "../../support/cli/cliFixture.js";
cliTest.for(["CLI", "stdio MCP"] as const)(
"keeps dependency manifests out of application roots and Electron entries through %s",
async (surface, { cli }) => {
const root = await createTestTempDirectory("rea-application-manifest-");
const dependency = join(root, "node_modules", "dep");
const nested = join(root, "a");
await Promise.all([mkdir(dependency, { recursive: true }), mkdir(nested)]);
await Promise.all([
writeFile(
join(nested, "package.json"),
JSON.stringify({ name: "nested" }),
),
writeFile(
join(root, "package.json"),
JSON.stringify({ name: "app", main: "main.js" }),
),
writeFile(
join(root, "main.js"),
"throw new Error('source must remain inert');",
),
writeFile(
join(dependency, "package.json"),
JSON.stringify({
name: "dep",
main: "index.js",
browser: "browser.js",
}),
),
writeFile(join(dependency, "index.js"), "module.exports = {};"),
writeFile(join(dependency, "browser.js"), "export const browser = true;"),
]);
let document: unknown;
if (surface === "CLI") {
const response = await cli.run({
arguments: ["analyze-javascript-application", root, "--json"],
});
expect(response.exitCode).toBe(0);
document = response.json;
} else {
const { call } = await connectLocalToolsMcp();
const response = await call("analyze_javascript_application", {
input_path: root,
});
expect(response.isError, JSON.stringify(response)).not.toBe(true);
document = response.structuredContent;
}
const { graph } = z
.object({ normalized_result: javascriptApplicationAnalysisResultSchema })
.parse(document).normalized_result;
expect(
graph.nodes
.filter(({ node_id }) => graph.root_node_ids.includes(node_id))
.flatMap(({ observations }) => observations.map(({ label }) => label)),
).toEqual(["app"]);
expect(
graph.nodes
.filter(({ kind }) => kind === "electron-main")
.flatMap(({ observations }) =>
observations.map(({ properties }) => properties.declared_path),
),
).toEqual(["main.js"]);
expect(
graph.nodes.filter(({ kind }) => kind === "electron-renderer"),
).toEqual([]);
expect(
graph.nodes
.filter(({ kind }) => kind === "package")
.flatMap(({ observations }) => observations.map(({ label }) => label))
.sort(),
).toEqual(["app", "dep", "nested"]);
},
);
cliTest(
"keeps the artifact root when only dependency manifests are present",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-dependency-only-");
const dependency = join(root, "node_modules", "dep");
await mkdir(dependency, { recursive: true });
await writeFile(
join(dependency, "package.json"),
JSON.stringify({ name: "dep", main: "index.js" }),
);
await writeFile(join(dependency, "index.js"), "module.exports = {};");
const response = await cli.run({
arguments: ["analyze-javascript-application", root, "--json"],
});
expect(response.exitCode, response.stderr).toBe(0);
const { graph } = z
.object({ normalized_result: javascriptApplicationAnalysisResultSchema })
.parse(response.json).normalized_result;
expect(
graph.nodes
.filter(({ node_id }) => graph.root_node_ids.includes(node_id))
.map(({ kind }) => kind),
).toEqual(["artifact"]);
expect(
graph.nodes
.filter(({ kind }) => kind === "package")
.flatMap(({ observations }) => observations.map(({ label }) => label)),
).toEqual(["dep"]);
expect(
graph.nodes.some(
({ kind }) => kind === "electron-main" || kind === "electron-renderer",
),
).toBe(false);
},
);