fix(javascript): report JSON status for imported package manifests

Use the existing JSON-text status checker for explicit package.json imports while retaining separate package-metadata validity. Preserve malformed/unavailable status and partial facts after cancellation.

Co-authored-by: Immanuel Tikhonov <[email protected]>
This commit is contained in:
Immanuel Tikhonov
2026-10-11 17:07:57 +08:00
committed by GitHub
parent 4daea941bf
commit 914ba833de
3 changed files with 178 additions and 2 deletions
@@ -379,7 +379,9 @@ const relationshipProperties = (
resolution_status: target?.status ?? null,
target_file_kind: target?.file?.kind ?? null,
target_json_status:
target?.file?.kind === "json" ? jsonStatus(target.file) : null,
target?.file?.kind === "json" || target?.file?.kind === "package-json"
? jsonStatus(target.file)
: null,
});
const relationshipEvidence = (
@@ -0,0 +1,105 @@
import { mkdir, writeFile } from "node:fs/promises";
import { createRequire } from "node:module";
import { dirname, 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)(
"reports JSON availability for imported package manifests through %s",
async (surface, { cli }) => {
const root = await createTestTempDirectory("rea-package-json-imports-");
const files = {
"main.cjs": [
'const version = require("./package.json").version;',
'const data = require("./data.json");',
'const helper = require("./helper.cjs");',
'const broken = require("./broken/package.json");',
'const nonObject = require("./non-object/package.json");',
'const unavailable = require("./unavailable/package.json");',
].join("\n"),
"package.json": '{"name":"app","version":"1.0.0"}',
"data.json": "[]",
"helper.cjs": "module.exports = 1;",
"broken/package.json": "{",
"non-object/package.json": "[]",
"unavailable/package.json": Buffer.from([0xff]),
};
await Promise.all(
Object.entries(files).map(async ([path, content]) => {
const absolute = join(root, path);
await mkdir(dirname(absolute), { recursive: true });
await writeFile(absolute, content);
}),
);
const nativeRequire = createRequire(join(root, "main.cjs"));
expect(nativeRequire("./package.json").version).toBe("1.0.0");
expect(nativeRequire("./non-object/package.json")).toEqual([]);
expect(nativeRequire("./data.json")).toEqual([]);
let document: unknown;
if (surface === "CLI") {
const response = await cli.run({
arguments: ["analyze-javascript-application", root, "--json"],
});
expect(response.exitCode, response.stderr).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;
const imports = graph.edges.filter(
({ properties }) => properties.module_link_kind === "require",
);
for (const [path, kind, status, metadataStatus] of [
["package.json", "package-json", "included", "included"],
["data.json", "json", "included", null],
["helper.cjs", "javascript", null, null],
["broken/package.json", "package-json", "invalid", "invalid"],
["non-object/package.json", "package-json", "included", "invalid"],
[
"unavailable/package.json",
"package-json",
"unavailable",
"unavailable",
],
]) {
expect(imports).toContainEqual(
expect.objectContaining({
properties: expect.objectContaining({
specifier: `./${path}`,
resolved_path: path,
resolution_status: "resolved",
target_file_kind: kind,
target_json_status: status,
}),
}),
);
if (kind === "package-json")
expect(
graph.nodes
.filter(({ kind }) => kind === "package")
.flatMap(({ observations }) => observations),
).toContainEqual(
expect.objectContaining({
properties: expect.objectContaining({
path,
parse_status: metadataStatus,
}),
}),
);
}
},
);
@@ -1,11 +1,80 @@
import { setImmediate } from "node:timers";
import { writeFile } from "node:fs/promises";
import { mkdir, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { z } from "zod";
import { javascriptApplicationAnalysisResultSchema } from "../../../src/domain/javascript/javascriptApplicationAnalysis.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js";
import { analyzeJavaScriptApplication } from "../../support/javascriptApplicationScope.js";
it("preserves imported manifest status when cancelled before visiting manifests", async () => {
const root = await createTestTempDirectory("rea-js-cancel-package-");
const manifests = [
["valid", '{"version":"1.0.0"}', "included"],
["malformed", "{", "invalid"],
["non-object", "[]", "included"],
["unavailable", Buffer.from([0xff]), "unavailable"],
] as const;
await Promise.all(
manifests.map(async ([name, content]) => {
const directory = join(root, "z", name);
await mkdir(directory, { recursive: true });
await writeFile(join(directory, "package.json"), content);
}),
);
await writeFile(
join(root, "a.cjs"),
manifests
.map(
([name], index) =>
`const manifest${index} = require("./z/${name}/package.json");`,
)
.join("\n"),
);
await writeFile(join(root, "b.cjs"), "module.exports = 1;");
const controller = new AbortController();
const visited: string[] = [];
const result = await analyzeJavaScriptApplication(
{ input_path: root, format: "directory" },
{
signal: controller.signal,
progress: {
async report(event) {
if (event.phase !== "parse_javascript_source") return;
visited.push(event.message);
if (event.message.includes("b.cjs")) controller.abort();
},
},
},
);
if (result.ok) throw new Error("Cancelled analysis must return a failure");
const failure = projectAnalysisError(result.error);
expect(failure.code).toBe("cancelled");
expect(visited).toHaveLength(2);
const { graph } = z
.object({
details: z.object({
partial_observation: z.object({
normalized_result: javascriptApplicationAnalysisResultSchema,
}),
}),
})
.parse(failure).details.partial_observation.normalized_result;
for (const [name, , status] of manifests)
expect(
graph.edges.find(
({ properties }) =>
properties.module_link_kind === "require" &&
properties.specifier === `./z/${name}/package.json`,
)?.properties,
).toMatchObject({
resolution_status: "resolved",
target_file_kind: "package-json",
target_json_status: status,
});
});
describe("JavaScript application cancellation", () => {
it("interrupts semantic graph commitment before result validation", async () => {
const root = await createTestTempDirectory("rea-js-cancel-semantic-");