* test(acceptance): split competing loader ownership across files The loader matrix was one 578-second file, which set the floor for any shard that received it. Three files keep every CLI and MCP case and let workers overlap them. Co-authored-by: morluto <morluto@users.noreply.github.com> * perf(ci): run eight fast built shards under a ten-minute budget Charge each Vitest project by its resolved worker width so serial process-boundary files spread as tail time. Fast feedback uses eight built shards. The npm download cache stays; node_modules is too large to ship as a shared artifact. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(ci): keep each scheduled file on one worker lane Dividing a file by maxWorkers dropped its own wall-time floor and could pack a parallel file onto a serial tail. Place each file whole on a lane, overlap peers only inside a sequence group, and add later groups after. Co-authored-by: morluto <morluto@users.noreply.github.com> * docs(testing): separate the shard budget from workflow time The ten-minute figure is the built shard job. Classification, build, and the portable-test merge are measured separately on run 39451548945. Co-authored-by: morluto <morluto@users.noreply.github.com> * docs(testing): match fast shard count to lane scheduling Main still described five shards and half-duration weights. Fast qualification uses eight shards, and each file keeps its full duration on one worker. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(ci): seed split loader durations from pre-split history Timings published before the file split still name one 578-second path. Until a new file appears in that artifact, share the recorded duration by the pre-split case counts. Co-authored-by: morluto <morluto@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: morluto <morluto@users.noreply.github.com>
1019 lines
37 KiB
TypeScript
1019 lines
37 KiB
TypeScript
import { expect } from "vitest";
|
|
import { copyFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { createPackageWithOptions } from "@electron/asar";
|
|
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { cliTest } from "../../support/cli/cliFixture.js";
|
|
import { analyzeJavaScriptDirectory } from "../../support/cli/javascriptApplication.js";
|
|
import { writeFixtureFiles } from "../../support/javascriptApplicationFixture.js";
|
|
|
|
cliTest.for(
|
|
(["CLI", "stdio MCP"] as const).flatMap((surface) =>
|
|
[
|
|
"direct",
|
|
"alias",
|
|
"call",
|
|
"apply",
|
|
"bind",
|
|
"reflect",
|
|
"captured reflect",
|
|
"local",
|
|
].map((setter) => ({
|
|
surface,
|
|
setter,
|
|
})),
|
|
),
|
|
)(
|
|
"keeps loadFile roots unknown after an Electron application path setter through $surface ($setter)",
|
|
async ({ surface, setter }, { cli }) => {
|
|
const root = await createTestTempDirectory("rea-app-path-setter-");
|
|
const change =
|
|
setter === "direct"
|
|
? 'const { app } = require("electron"); app.setAppPath("/runtime/changed");'
|
|
: setter === "alias"
|
|
? 'const { app: runtime } = require("electron/main"); const current = runtime; current["setAppPath"]("/runtime/changed");'
|
|
: setter === "call"
|
|
? 'const { app } = require("electron"); app.setAppPath.call(app, "/runtime/changed");'
|
|
: setter === "apply"
|
|
? 'const { app } = require("electron"); app.setAppPath.apply(app, ["/runtime/changed"]);'
|
|
: setter === "bind"
|
|
? 'const { app } = require("electron"); const change = app.setAppPath.bind(app); change("/runtime/changed");'
|
|
: setter === "reflect"
|
|
? 'const { app } = require("electron"); Reflect.apply(app.setAppPath, app, ["/runtime/changed"]);'
|
|
: setter === "captured reflect"
|
|
? 'const { app } = require("electron"); const invoke = Reflect.apply; invoke(app.setAppPath, app, ["/runtime/changed"]);'
|
|
: 'const app = { setAppPath() {} }; app.setAppPath("/runtime/changed");';
|
|
await writeFixtureFiles(root, {
|
|
"a/package.json": JSON.stringify({ main: "main.cjs" }),
|
|
"a/main.cjs": `${change} require("./window.cjs");`,
|
|
"a/window.cjs":
|
|
'const { BrowserWindow } = require("electron"); new BrowserWindow().loadFile("index.html");',
|
|
"a/index.html": "package-root page",
|
|
"index.html": "artifact-root decoy",
|
|
});
|
|
const { graph } = await analyzeJavaScriptDirectory(surface, cli, root);
|
|
const role = graph.nodes.find(({ kind }) => kind === "electron-renderer");
|
|
const target = setter === "local" ? "a/index.html" : null;
|
|
expect(role?.observations).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({
|
|
resolution_status: target === null ? "unavailable" : "resolved",
|
|
resolved_path: target,
|
|
}),
|
|
}),
|
|
]),
|
|
);
|
|
expect(
|
|
graph.edges
|
|
.filter(
|
|
({ source_node_id, relation }) =>
|
|
source_node_id === role?.node_id && relation === "maps_to",
|
|
)
|
|
.map(({ properties }) => properties.resolved_path),
|
|
).toEqual(target === null ? [] : [target]);
|
|
},
|
|
);
|
|
|
|
cliTest.for(["CLI", "stdio MCP"] as const)(
|
|
"propagates the application owner into an inventoried ASAR through %s",
|
|
async (surface, { cli }) => {
|
|
const root = await createTestTempDirectory("rea-asar-inward-");
|
|
const source = join(root, "source");
|
|
const application = join(root, "application");
|
|
await writeFixtureFiles(source, {
|
|
"window.cjs":
|
|
'const { BrowserWindow } = require("electron"); new BrowserWindow().loadFile("index.html");',
|
|
"index.html": "archive-root decoy",
|
|
});
|
|
await writeFixtureFiles(application, {
|
|
"a/package.json": JSON.stringify({ main: "main.cjs" }),
|
|
"a/main.cjs": 'require("../resources/code.asar/window.cjs");',
|
|
"a/index.html": "proven application page",
|
|
});
|
|
await createPackageWithOptions(
|
|
source,
|
|
join(application, "resources/code.asar"),
|
|
{},
|
|
);
|
|
const { graph } = await analyzeJavaScriptDirectory(
|
|
surface,
|
|
cli,
|
|
application,
|
|
);
|
|
const role = graph.nodes.find(({ kind }) => kind === "electron-renderer");
|
|
expect(role?.observations).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({
|
|
resolution_status: "resolved",
|
|
resolved_path: "a/index.html",
|
|
}),
|
|
}),
|
|
]),
|
|
);
|
|
expect(graph.edges).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
relation: "imports",
|
|
properties: expect.objectContaining({
|
|
specifier: "../resources/code.asar/window.cjs",
|
|
resolution_status: "resolved",
|
|
resolved_path: "resources/code.asar/window.cjs",
|
|
}),
|
|
}),
|
|
]),
|
|
);
|
|
expect(
|
|
graph.edges
|
|
.filter(
|
|
({ source_node_id, relation }) =>
|
|
source_node_id === role?.node_id && relation === "maps_to",
|
|
)
|
|
.map(({ properties }) => properties.resolved_path),
|
|
).toEqual(["a/index.html"]);
|
|
},
|
|
);
|
|
|
|
cliTest.for(
|
|
(["CLI", "stdio MCP"] as const).flatMap((surface) =>
|
|
[false, true].map((dynamic) => ({ surface, dynamic })),
|
|
),
|
|
)(
|
|
"retains possible owners for a module across a nested ASAR boundary through $surface (dynamic=$dynamic)",
|
|
async ({ surface, dynamic }, { cli }) => {
|
|
const root = await createTestTempDirectory("rea-asar-module-gap-");
|
|
const source = join(root, "source");
|
|
const application = join(root, "application");
|
|
await writeFixtureFiles(source, {
|
|
"package.json": JSON.stringify({ main: "main.cjs" }),
|
|
"main.cjs": dynamic
|
|
? "require(process.env.WINDOW_MODULE);"
|
|
: 'require("../window.cjs");',
|
|
});
|
|
await writeFixtureFiles(application, {
|
|
"other/package.json": JSON.stringify({ main: "main.cjs" }),
|
|
"other/main.cjs": 'require("../resources/window.cjs");',
|
|
"resources/window.cjs":
|
|
'const { BrowserWindow } = require("electron"); new BrowserWindow().loadFile("index.html");',
|
|
"other/index.html": "false unique owner decoy",
|
|
});
|
|
await createPackageWithOptions(
|
|
source,
|
|
join(application, "resources/app.asar"),
|
|
{},
|
|
);
|
|
const { graph } = await analyzeJavaScriptDirectory(
|
|
surface,
|
|
cli,
|
|
application,
|
|
);
|
|
const role = graph.nodes.find(({ kind }) => kind === "electron-renderer");
|
|
expect(role?.observations).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({
|
|
resolution_status: "unavailable",
|
|
resolved_path: null,
|
|
}),
|
|
}),
|
|
]),
|
|
);
|
|
expect(graph.edges).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
relation: "imports",
|
|
properties: expect.objectContaining({
|
|
specifier: dynamic ? null : "../window.cjs",
|
|
...(dynamic ? {} : { resolution_status: "resolved" }),
|
|
resolved_path: dynamic ? null : "resources/window.cjs",
|
|
}),
|
|
}),
|
|
]),
|
|
);
|
|
expect(
|
|
graph.edges.filter(
|
|
({ source_node_id, relation }) =>
|
|
source_node_id === role?.node_id && relation === "maps_to",
|
|
),
|
|
).toEqual([]);
|
|
},
|
|
);
|
|
|
|
cliTest.for(["CLI", "stdio MCP"] as const)(
|
|
"resolves a proven application page across a nested ASAR boundary through %s",
|
|
async (surface, { cli }) => {
|
|
const root = await createTestTempDirectory("rea-asar-outer-page-");
|
|
const source = join(root, "source");
|
|
const application = join(root, "application");
|
|
await writeFixtureFiles(source, {
|
|
"package.json": JSON.stringify({ main: "main.cjs" }),
|
|
"main.cjs":
|
|
'const { BrowserWindow } = require("electron"); new BrowserWindow().loadFile("../page.html");',
|
|
});
|
|
await writeFixtureFiles(application, {
|
|
"resources/page.html": "owned outer page",
|
|
});
|
|
await createPackageWithOptions(
|
|
source,
|
|
join(application, "resources/app.asar"),
|
|
{},
|
|
);
|
|
const { graph } = await analyzeJavaScriptDirectory(
|
|
surface,
|
|
cli,
|
|
application,
|
|
);
|
|
const role = graph.nodes.find(({ kind }) => kind === "electron-renderer");
|
|
expect(role?.observations).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({
|
|
resolved_path: "resources/page.html",
|
|
resolution_status: "resolved",
|
|
}),
|
|
}),
|
|
]),
|
|
);
|
|
expect(
|
|
graph.edges
|
|
.filter(
|
|
({ source_node_id, relation }) =>
|
|
source_node_id === role?.node_id && relation === "maps_to",
|
|
)
|
|
.map(({ properties }) => properties.resolved_path),
|
|
).toEqual(["resources/page.html"]);
|
|
},
|
|
);
|
|
|
|
const cases = [
|
|
...[
|
|
"C:/app/preload.cjs",
|
|
"C:\\app\\preload.cjs",
|
|
"\\\\server\\share\\preload.cjs",
|
|
].map((path, index) => ({
|
|
directory: `windows-absolute-${index}`,
|
|
expression: JSON.stringify(path),
|
|
target: null,
|
|
status: "unavailable",
|
|
})),
|
|
{
|
|
directory: "absolute-runtime",
|
|
expression: '"/outside%2Fname.cjs"',
|
|
target: null,
|
|
status: "unavailable",
|
|
},
|
|
{
|
|
directory: "literal",
|
|
expression: 'path.join(__dirname, "preload%2F%5C%2E.cjs")',
|
|
target: "preload%2F%5C%2E.cjs",
|
|
status: "resolved",
|
|
},
|
|
{
|
|
directory: "converted",
|
|
expression:
|
|
'fileURLToPath(new URL("./preload%252Fname.cjs", import.meta.url))',
|
|
target: "preload%2Fname.cjs",
|
|
status: "resolved",
|
|
},
|
|
{
|
|
directory: "escaped",
|
|
expression: 'path.join(__dirname, "../../outside%2Fname.cjs")',
|
|
target: null,
|
|
status: "rejected",
|
|
},
|
|
{
|
|
directory: "ignored-prefix",
|
|
expression:
|
|
'path.resolve(new URL("./", import.meta.url), path.dirname(fileURLToPath(import.meta.url)), "preload%2Fname.cjs")',
|
|
target: "preload%2Fname.cjs",
|
|
status: "resolved",
|
|
},
|
|
] as const;
|
|
|
|
const writeRuntimeFixture = async (root: string): Promise<void> => {
|
|
const inert = 'throw new Error("fixture source must never execute");';
|
|
const loadFileSource = `const { BrowserWindow } = require("electron");
|
|
const win = new BrowserWindow();
|
|
win.loadFile("dist%2Findex.html");
|
|
win.loadFile("plain.html");
|
|
win.loadFile("absent.html");`;
|
|
await writeFixtureFiles(root, {
|
|
"package.json": JSON.stringify({ private: true, workspaces: ["app"] }),
|
|
...Object.fromEntries(
|
|
cases.map(({ directory, expression }) => [
|
|
`${directory}/main.mjs`,
|
|
`import { BrowserWindow, utilityProcess } from "electron";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
new BrowserWindow({ webPreferences: { preload: ${expression} } });
|
|
utilityProcess.fork(${expression});`,
|
|
]),
|
|
),
|
|
"literal/preload%2F%5C%2E.cjs": inert,
|
|
"converted/preload%2Fname.cjs": inert,
|
|
"converted/preload%252Fname.cjs": inert,
|
|
"converted/preload/name.cjs": inert,
|
|
"outside%2Fname.cjs": inert,
|
|
"ignored-prefix/preload%2Fname.cjs": inert,
|
|
"sub/main.cjs": loadFileSource,
|
|
"dist%2Findex.html": "application target",
|
|
"plain.html": "application target",
|
|
"sub/dist%2Findex.html": "nested decoy",
|
|
"sub/plain.html": "nested decoy",
|
|
"sub/absent.html": "nested-only decoy",
|
|
"app/package.json": JSON.stringify({ main: "sub/main.cjs" }),
|
|
"app/sub/package.json": JSON.stringify({ type: "commonjs" }),
|
|
"app/sub/main.cjs": loadFileSource,
|
|
"app/dist%2Findex.html": "wrapped application target",
|
|
"app/plain.html": "wrapped application target",
|
|
"app/sub/dist%2Findex.html": "wrapped source decoy",
|
|
"app/sub/plain.html": "wrapped source decoy",
|
|
"app/sub/absent.html": "wrapped missing-target decoy",
|
|
"external-app/package.json": JSON.stringify({ main: "../shared/main.cjs" }),
|
|
"shared/main.cjs": loadFileSource,
|
|
"external-app/dist%2Findex.html": "external-main application target",
|
|
"external-app/plain.html": "external-main application target",
|
|
"competition/package.json": JSON.stringify({ main: "main.cjs" }),
|
|
"competition/main.cjs": 'require("./bridge.cjs");',
|
|
"competition/bridge.cjs": 'require("./nested/main.cjs");',
|
|
"competition/nested/package.json": JSON.stringify({ main: "main.cjs" }),
|
|
"competition/nested/main.cjs": `${loadFileSource}\nrequire("../main.cjs");`,
|
|
"competition/dist%2Findex.html": "running application page",
|
|
"competition/nested/dist%2Findex.html": "competing application page",
|
|
"import-owner/package.json": JSON.stringify({
|
|
main: "../imported/start.cjs",
|
|
}),
|
|
"imported/start.cjs": 'require("./child.cjs");',
|
|
"imported/child.cjs": loadFileSource,
|
|
"import-owner/dist%2Findex.html": "imported application target",
|
|
"import-owner/plain.html": "imported application target",
|
|
"default-app/package.json": JSON.stringify({ name: "default-entry-app" }),
|
|
"default-app/index.js": `${loadFileSource}\nrequire("./nested/main.cjs");`,
|
|
"default-app/nested/package.json": JSON.stringify({ main: "main.cjs" }),
|
|
"default-app/nested/main.cjs": loadFileSource,
|
|
"default-app/nested/dist%2Findex.html": "competing default-entry page",
|
|
"default-app/dist%2Findex.html": "default-entry application target",
|
|
"default-app/plain.html": "default-entry application target",
|
|
"ambiguous/package.json": JSON.stringify({ main: "sub/main.cjs" }),
|
|
"ambiguous/sub/package.json": JSON.stringify({ main: "main.cjs" }),
|
|
"ambiguous/third/package.json": JSON.stringify({ main: "../sub/main.cjs" }),
|
|
"ambiguous/sub/main.cjs": loadFileSource,
|
|
"ambiguous/dist%2Findex.html": "unproven application target",
|
|
"ambiguous/sub/dist%2Findex.html": "unproven nested application target",
|
|
});
|
|
const archiveSource = await createTestTempDirectory("rea-loadfile-asar-");
|
|
await writeFixtureFiles(archiveSource, {
|
|
"package.json": JSON.stringify({ main: "sub/main.cjs" }),
|
|
"sub/package.json": JSON.stringify({ type: "commonjs" }),
|
|
"sub/main.cjs": loadFileSource,
|
|
"dist%2Findex.html": "application target",
|
|
"plain.html": "application target",
|
|
"sub/dist%2Findex.html": "nested decoy",
|
|
"sub/plain.html": "nested decoy",
|
|
"sub/absent.html": "nested-only decoy",
|
|
"external-app/package.json": JSON.stringify({ main: "../shared/main.cjs" }),
|
|
"shared/main.cjs": loadFileSource,
|
|
"external-app/dist%2Findex.html": "external-main application target",
|
|
"external-app/plain.html": "external-main application target",
|
|
});
|
|
await createPackageWithOptions(archiveSource, join(root, "a.asar"), {});
|
|
// Equal container digests must not select the other archive occurrence.
|
|
await copyFile(join(root, "a.asar"), join(root, "b.asar"));
|
|
};
|
|
|
|
cliTest.for(["CLI", "stdio MCP"] as const)(
|
|
"does not invent a nested default-entry owner without index.js through %s",
|
|
async (surface, { cli }) => {
|
|
const root = await createTestTempDirectory("rea-empty-default-owner-");
|
|
for (const [directory, metadata] of [
|
|
["empty", { main: "" }],
|
|
["absent", {}],
|
|
] as const)
|
|
await writeFixtureFiles(root, {
|
|
[`${directory}/package.json`]: JSON.stringify(metadata),
|
|
[`${directory}/helper.cjs`]:
|
|
'const {BrowserWindow} = require("electron"); const w = new BrowserWindow(); w.loadFile("index.html");',
|
|
[`${directory}/index.html`]: "nested decoy without launch entry",
|
|
"index.html": "artifact-root page",
|
|
});
|
|
const { graph } = await analyzeJavaScriptDirectory(surface, cli, root);
|
|
const roles = graph.nodes.filter(
|
|
({ kind }) => kind === "electron-renderer",
|
|
);
|
|
expect(roles).toHaveLength(2);
|
|
for (const role of roles) {
|
|
expect(role.observations).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({
|
|
resolution_status: "resolved",
|
|
resolved_path: "index.html",
|
|
}),
|
|
}),
|
|
]),
|
|
);
|
|
expect(
|
|
graph.edges
|
|
.filter(
|
|
({ source_node_id, relation }) =>
|
|
source_node_id === role.node_id && relation === "maps_to",
|
|
)
|
|
.map(({ properties }) => properties.resolved_path),
|
|
).toEqual(["index.html"]);
|
|
}
|
|
},
|
|
);
|
|
|
|
cliTest.for(["CLI", "stdio MCP"] as const)(
|
|
"retains non-string main evidence while following Node index entries through %s",
|
|
async (surface, { cli }) => {
|
|
const root = await createTestTempDirectory("rea-invalid-main-owner-");
|
|
for (const [index, main] of [123, false, null, {}, []].entries()) {
|
|
await writeFixtureFiles(root, {
|
|
[`a${index}/package.json`]: JSON.stringify({
|
|
name: `invalid-${index}`,
|
|
main,
|
|
}),
|
|
[`a${index}/index.js`]: `require("../shared/window${index}.cjs");`,
|
|
[`shared/window${index}.cjs`]:
|
|
'const { BrowserWindow } = require("electron"); const win = new BrowserWindow(); win.loadFile("index.html");',
|
|
[`a${index}/index.html`]: "package index launch page",
|
|
"index.html": "artifact root decoy",
|
|
});
|
|
}
|
|
const { graph } = await analyzeJavaScriptDirectory(surface, cli, root);
|
|
const packages = graph.nodes.filter(({ kind }) => kind === "package");
|
|
expect(packages).toHaveLength(5);
|
|
for (const node of packages)
|
|
expect(node.observations).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({
|
|
main: null,
|
|
main_status: "invalid",
|
|
parse_status: "included",
|
|
name: expect.stringMatching(/^invalid-\d$/u),
|
|
}),
|
|
}),
|
|
]),
|
|
);
|
|
const roles = graph.nodes.filter(
|
|
({ kind }) => kind === "electron-renderer",
|
|
);
|
|
expect(roles).toHaveLength(5);
|
|
for (const role of roles) {
|
|
const location = role.observations[0]?.evidence.location;
|
|
const source =
|
|
location?.available && location.value.kind === "source-range"
|
|
? location.value.source
|
|
: null;
|
|
const match = /^shared\/window(\d)\.cjs$/u.exec(source ?? "");
|
|
expect(match).not.toBeNull();
|
|
const target = `a${match?.[1]}/index.html`;
|
|
expect(role.observations).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({
|
|
resolved_path: target,
|
|
resolution_status: "resolved",
|
|
}),
|
|
}),
|
|
]),
|
|
);
|
|
expect(
|
|
graph.edges
|
|
.filter(
|
|
({ source_node_id, relation }) =>
|
|
source_node_id === role.node_id && relation === "maps_to",
|
|
)
|
|
.map(({ properties }) => properties.resolved_path),
|
|
).toEqual([target]);
|
|
}
|
|
},
|
|
);
|
|
|
|
cliTest.for(
|
|
(["CLI", "stdio MCP"] as const).flatMap((surface) => [
|
|
{ surface, source: "app/sub/main.cjs", selectedOwner: false },
|
|
{ surface, source: "shared/main.cjs", selectedOwner: false },
|
|
{ surface, source: "shared/main.cjs", selectedOwner: true },
|
|
]),
|
|
)(
|
|
"keeps unselected package ownership unknown through $surface for $source (selected owner: $selectedOwner)",
|
|
async ({ surface, source, selectedOwner }, { cli }) => {
|
|
const root = await createTestTempDirectory("rea-selected-loadfile-");
|
|
await writeRuntimeFixture(root);
|
|
if (selectedOwner)
|
|
await writeFixtureFiles(root, {
|
|
"other-owner/package.json": JSON.stringify({
|
|
main: "../shared/main.cjs",
|
|
}),
|
|
"other-owner/dist%2Findex.html": "unselected competing page",
|
|
});
|
|
const { graph } = await analyzeJavaScriptDirectory(surface, cli, root, {
|
|
sourcePaths: [
|
|
"package.json",
|
|
...(selectedOwner ? ["external-app/package.json"] : []),
|
|
source,
|
|
],
|
|
});
|
|
const roles = graph.nodes.filter(
|
|
({ kind, observations }) =>
|
|
kind === "electron-renderer" &&
|
|
observations.some(
|
|
({ properties }) =>
|
|
properties.runtime_path_base === "application-root",
|
|
),
|
|
);
|
|
expect(roles).toHaveLength(3);
|
|
for (const role of roles) {
|
|
expect(role.observations).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({
|
|
resolved_path: null,
|
|
resolution_status: "unavailable",
|
|
}),
|
|
}),
|
|
]),
|
|
);
|
|
expect(
|
|
graph.edges.filter(
|
|
({ source_node_id, relation }) =>
|
|
source_node_id === role.node_id && relation === "maps_to",
|
|
),
|
|
).toEqual([]);
|
|
}
|
|
},
|
|
);
|
|
|
|
cliTest.for(["CLI", "stdio MCP"] as const)(
|
|
"keeps omitted launch owners unknown between identical ASAR occurrences through %s",
|
|
async (surface, { cli }) => {
|
|
const root = await createTestTempDirectory("rea-selected-asar-owner-");
|
|
await writeRuntimeFixture(root);
|
|
const { graph } = await analyzeJavaScriptDirectory(surface, cli, root, {
|
|
sourcePaths: [
|
|
"a.asar/package.json",
|
|
"a.asar/sub/package.json",
|
|
"a.asar/external-app/package.json",
|
|
"a.asar/sub/main.cjs",
|
|
"a.asar/shared/main.cjs",
|
|
],
|
|
});
|
|
const roles = graph.nodes.filter(
|
|
({ kind }) => kind === "electron-renderer",
|
|
);
|
|
expect(roles).toHaveLength(6);
|
|
for (const role of roles) {
|
|
expect(role.observations).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
evidence: expect.objectContaining({
|
|
location: expect.objectContaining({
|
|
available: true,
|
|
value: expect.objectContaining({
|
|
source: expect.stringMatching(/^a\.asar\//u),
|
|
}),
|
|
}),
|
|
}),
|
|
}),
|
|
]),
|
|
);
|
|
expect(role.observations).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({
|
|
resolved_path: null,
|
|
resolution_status: "unavailable",
|
|
}),
|
|
}),
|
|
]),
|
|
);
|
|
expect(
|
|
graph.edges
|
|
.filter(
|
|
({ source_node_id, relation }) =>
|
|
source_node_id === role.node_id && relation === "maps_to",
|
|
)
|
|
.map(({ properties }) => properties.resolved_path),
|
|
).toEqual([]);
|
|
}
|
|
},
|
|
);
|
|
|
|
cliTest.for(["CLI", "stdio MCP"] as const)(
|
|
"retains a selected entry owner and confines loadFile after applying its root through %s",
|
|
async (surface, { cli }) => {
|
|
const root = await createTestTempDirectory("rea-proven-partial-owner-");
|
|
await writeFixtureFiles(root, {
|
|
"app/package.json": JSON.stringify({ main: "main.cjs" }),
|
|
"app/main.cjs":
|
|
'const { BrowserWindow } = require("electron"); const win = new BrowserWindow(); win.loadFile("index.html"); win.loadFile("../shared/index.html"); win.loadFile("../../escape.html");',
|
|
"app/helper.cjs": 'module.exports = "unrelated omitted module";',
|
|
"index.html": "artifact-root decoy",
|
|
"app/index.html": "proven application page",
|
|
"shared/index.html": "valid sibling page",
|
|
"escape.html": "escape decoy",
|
|
});
|
|
const { graph } = await analyzeJavaScriptDirectory(surface, cli, root, {
|
|
sourcePaths: ["app/package.json", "app/main.cjs"],
|
|
});
|
|
const roles = graph.nodes.filter(
|
|
({ kind }) => kind === "electron-renderer",
|
|
);
|
|
expect(roles).toHaveLength(3);
|
|
for (const [declaration, target, status] of [
|
|
["index.html", "app/index.html", "resolved"],
|
|
["../shared/index.html", "shared/index.html", "resolved"],
|
|
["../../escape.html", null, "rejected"],
|
|
] as const) {
|
|
const role = roles.find(({ observations }) =>
|
|
observations.some(
|
|
({ properties }) => properties.declared_path === declaration,
|
|
),
|
|
);
|
|
expect(role).toBeDefined();
|
|
expect(role?.observations).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({
|
|
resolved_path: target,
|
|
resolution_status: status,
|
|
}),
|
|
}),
|
|
]),
|
|
);
|
|
expect(
|
|
graph.edges
|
|
.filter(
|
|
({ source_node_id, relation }) =>
|
|
source_node_id === role?.node_id && relation === "maps_to",
|
|
)
|
|
.map(({ properties }) => properties.resolved_path),
|
|
).toEqual(target === null ? [] : [target]);
|
|
}
|
|
},
|
|
);
|
|
|
|
cliTest.for(["CLI", "stdio MCP"] as const)(
|
|
"keeps absolute loadFile host paths unknown through %s",
|
|
async (surface, { cli }) => {
|
|
const root = await createTestTempDirectory("rea-loadfile-absolute-");
|
|
await writeFixtureFiles(root, {
|
|
"app/package.json": JSON.stringify({ main: "main.cjs" }),
|
|
"app/main.cjs":
|
|
'const { BrowserWindow } = require("electron"); new BrowserWindow().loadFile("/dist/index.html");',
|
|
"app/dist/index.html": "application-relative decoy",
|
|
"dist/index.html": "artifact-root decoy",
|
|
});
|
|
const { graph } = await analyzeJavaScriptDirectory(surface, cli, root);
|
|
const roles = graph.nodes.filter(
|
|
({ kind }) => kind === "electron-renderer",
|
|
);
|
|
expect(roles).toHaveLength(1);
|
|
const role = roles[0];
|
|
if (role === undefined) throw new Error("Missing renderer declaration");
|
|
expect(role.observations).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({
|
|
declared_path: "/dist/index.html",
|
|
resolved_path: null,
|
|
resolution_status: "unavailable",
|
|
}),
|
|
}),
|
|
]),
|
|
);
|
|
expect(
|
|
graph.edges.filter(
|
|
({ source_node_id, relation }) =>
|
|
source_node_id === role.node_id && relation === "maps_to",
|
|
),
|
|
).toEqual([]);
|
|
},
|
|
);
|
|
|
|
cliTest.for(["CLI", "stdio MCP"] as const)(
|
|
"preserves literal-percent runtime paths through %s without executing source",
|
|
async (surface, { cli }) => {
|
|
const root = await createTestTempDirectory("rea-runtime-percent-");
|
|
await writeRuntimeFixture(root);
|
|
const { graph } = await analyzeJavaScriptDirectory(surface, cli, root);
|
|
for (const [sourcePath, targetRoot, declared] of (
|
|
[
|
|
["sub/main.cjs", ""],
|
|
["app/sub/main.cjs", "app/"],
|
|
["shared/main.cjs", "external-app/"],
|
|
["default-app/index.js", "default-app/"],
|
|
["default-app/nested/main.cjs", null],
|
|
["ambiguous/sub/main.cjs", null],
|
|
["competition/nested/main.cjs", null],
|
|
["imported/child.cjs", "import-owner/"],
|
|
["a.asar/sub/main.cjs", "a.asar/"],
|
|
["b.asar/sub/main.cjs", "b.asar/"],
|
|
["a.asar/shared/main.cjs", "a.asar/external-app/"],
|
|
["b.asar/shared/main.cjs", "b.asar/external-app/"],
|
|
] as const
|
|
).flatMap(([sourcePath, targetRoot]) =>
|
|
["dist%2Findex.html", "plain.html", "absent.html"].map(
|
|
(declared) => [sourcePath, targetRoot, declared] as const,
|
|
),
|
|
)) {
|
|
const role = graph.nodes.find(
|
|
({ kind, observations }) =>
|
|
kind === "electron-renderer" &&
|
|
observations.some(
|
|
({ properties, evidence }) =>
|
|
properties.declared_path === declared &&
|
|
evidence.location.available &&
|
|
evidence.location.value.kind === "source-range" &&
|
|
evidence.location.value.source === sourcePath,
|
|
),
|
|
);
|
|
expect(role).toBeDefined();
|
|
if (role === undefined) throw new Error(`Missing loadFile ${declared}`);
|
|
const expected =
|
|
declared === "absent.html" || targetRoot === null
|
|
? null
|
|
: `${targetRoot}${declared}`;
|
|
expect(role.observations).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({
|
|
declared_path: declared,
|
|
runtime_path_base: "application-root",
|
|
resolved_path: expected,
|
|
resolution_status:
|
|
targetRoot === null
|
|
? "unavailable"
|
|
: expected === null
|
|
? "not-found"
|
|
: "resolved",
|
|
}),
|
|
}),
|
|
]),
|
|
);
|
|
expect(
|
|
graph.edges
|
|
.filter(
|
|
({ relation, source_node_id }) =>
|
|
relation === "maps_to" && source_node_id === role.node_id,
|
|
)
|
|
.map(({ properties }) => properties.resolved_path),
|
|
).toEqual(expected === null ? [] : [expected]);
|
|
}
|
|
for (const { directory, target, status } of cases) {
|
|
for (const kind of ["electron-preload", "electron-utility"] as const) {
|
|
const nodes = graph.nodes.filter(
|
|
({ kind: nodeKind, observations }) =>
|
|
nodeKind === kind &&
|
|
observations.some(
|
|
({ evidence }) =>
|
|
evidence.location.available &&
|
|
evidence.location.value.kind === "source-range" &&
|
|
evidence.location.value.source === `${directory}/main.mjs`,
|
|
),
|
|
);
|
|
expect(nodes).toHaveLength(1);
|
|
const node = nodes[0];
|
|
if (node === undefined) throw new Error(`Missing ${directory} ${kind}`);
|
|
const resolutions = node.observations.filter(
|
|
({ properties }) => "resolution_status" in properties,
|
|
);
|
|
expect(resolutions).not.toHaveLength(0);
|
|
for (const { properties } of resolutions) {
|
|
expect(properties).toMatchObject({
|
|
resolved_path: target === null ? null : `${directory}/${target}`,
|
|
resolution_status: status,
|
|
});
|
|
}
|
|
const mappedPaths = graph.edges
|
|
.filter(
|
|
({ relation, source_node_id }) =>
|
|
relation === "maps_to" && source_node_id === node.node_id,
|
|
)
|
|
.map(({ properties }) => properties.resolved_path);
|
|
expect([...new Set(mappedPaths)]).toEqual(
|
|
target === null ? [] : [`${directory}/${target}`],
|
|
);
|
|
}
|
|
}
|
|
},
|
|
);
|
|
|
|
cliTest.for(
|
|
(["CLI", "stdio MCP"] as const).flatMap((surface) =>
|
|
["unread", "included-inert", "included-competing"].map((owner) => ({
|
|
surface,
|
|
owner,
|
|
})),
|
|
),
|
|
)(
|
|
"retains unread competing launch owners across an ASAR boundary through $surface ($owner)",
|
|
async ({ surface, owner }, { cli }) => {
|
|
const root = await createTestTempDirectory("rea-unread-asar-owner-");
|
|
const source = join(root, "source");
|
|
const application = join(root, "application");
|
|
await writeFixtureFiles(source, {
|
|
"window.cjs":
|
|
'const { BrowserWindow } = require("electron"); new BrowserWindow().loadFile("index.html");',
|
|
"index.html": "archive-root decoy",
|
|
});
|
|
await writeFixtureFiles(application, {
|
|
"a/package.json": JSON.stringify({ main: "main.cjs" }),
|
|
"a/main.cjs": 'require("../resources/shared.asar/window.cjs");',
|
|
"a/index.html": "first application page",
|
|
"b/package.json": JSON.stringify({ main: "main.cjs" }),
|
|
"b/main.cjs":
|
|
owner === "included-inert"
|
|
? "module.exports = 1;"
|
|
: 'require("../resources/shared.asar/window.cjs");',
|
|
"b/index.html": "competing application page",
|
|
});
|
|
await createPackageWithOptions(
|
|
source,
|
|
join(application, "resources/shared.asar"),
|
|
{},
|
|
);
|
|
const { graph } = await analyzeJavaScriptDirectory(
|
|
surface,
|
|
cli,
|
|
application,
|
|
{
|
|
sourcePaths: [
|
|
"a/package.json",
|
|
"a/main.cjs",
|
|
"a/index.html",
|
|
"resources/shared.asar/window.cjs",
|
|
"resources/shared.asar/index.html",
|
|
...(owner === "unread"
|
|
? []
|
|
: ["b/package.json", "b/main.cjs", "b/index.html"]),
|
|
],
|
|
},
|
|
);
|
|
const roles = graph.nodes.filter(
|
|
({ kind }) => kind === "electron-renderer",
|
|
);
|
|
expect(roles).toHaveLength(1);
|
|
const role = roles[0];
|
|
const target = owner === "included-inert" ? "a/index.html" : null;
|
|
expect(role?.observations).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({
|
|
resolved_path: target,
|
|
resolution_status: target === null ? "unavailable" : "resolved",
|
|
}),
|
|
}),
|
|
]),
|
|
);
|
|
expect(
|
|
graph.edges
|
|
.filter(
|
|
({ relation, source_node_id }) =>
|
|
relation === "maps_to" && source_node_id === role?.node_id,
|
|
)
|
|
.map(({ properties }) => properties.resolved_path),
|
|
).toEqual(target === null ? [] : [target]);
|
|
expect(graph.edges).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
relation: "imports",
|
|
properties: expect.objectContaining({
|
|
specifier: "../resources/shared.asar/window.cjs",
|
|
resolution_status: "resolved",
|
|
resolved_path: "resources/shared.asar/window.cjs",
|
|
}),
|
|
}),
|
|
]),
|
|
);
|
|
},
|
|
);
|
|
|
|
cliTest.for(["CLI", "stdio MCP"] as const)(
|
|
"keeps every application root unknown when three owners import one setter through %s",
|
|
async (surface, { cli }) => {
|
|
const root = await createTestTempDirectory("rea-shared-app-setter-");
|
|
await writeFixtureFiles(root, {
|
|
"shared/change.cjs":
|
|
'const { app } = require("electron"); app.setAppPath("/runtime/changed");',
|
|
...Object.fromEntries(
|
|
["a", "b", "c"].flatMap((owner) => [
|
|
[`${owner}/package.json`, JSON.stringify({ main: "main.cjs" })],
|
|
[
|
|
`${owner}/main.cjs`,
|
|
'require("../shared/change.cjs"); require("./window.cjs");',
|
|
],
|
|
[
|
|
`${owner}/window.cjs`,
|
|
`// Application ${owner}\nconst { BrowserWindow } = require("electron"); new BrowserWindow().loadFile("index.html");`,
|
|
],
|
|
[`${owner}/index.html`, `application ${owner} page`],
|
|
]),
|
|
),
|
|
});
|
|
const { graph } = await analyzeJavaScriptDirectory(surface, cli, root);
|
|
const roles = graph.nodes.filter(
|
|
({ kind }) => kind === "electron-renderer",
|
|
);
|
|
expect(roles).toHaveLength(3);
|
|
for (const role of roles) {
|
|
expect(role.observations).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({
|
|
resolution_status: "unavailable",
|
|
resolved_path: null,
|
|
}),
|
|
}),
|
|
]),
|
|
);
|
|
expect(
|
|
graph.edges.filter(
|
|
({ relation, source_node_id }) =>
|
|
relation === "maps_to" && source_node_id === role.node_id,
|
|
),
|
|
).toEqual([]);
|
|
}
|
|
},
|
|
);
|
|
|
|
cliTest.for(
|
|
(["CLI", "stdio MCP"] as const).flatMap((surface) =>
|
|
["relative", "bare"].map((request) => ({ surface, request })),
|
|
),
|
|
)(
|
|
"retains package main ownership after an ASAR escape through $surface ($request)",
|
|
async ({ surface, request }, { cli }) => {
|
|
const root = await createTestTempDirectory("rea-asar-metadata-");
|
|
const source = join(root, "source");
|
|
const application = join(root, "application");
|
|
const directory =
|
|
request === "bare" ? "resources/node_modules/fixture" : "resources/pkg";
|
|
await writeFixtureFiles(source, {
|
|
"package.json": JSON.stringify({ main: "main.cjs" }),
|
|
"main.cjs":
|
|
request === "bare" ? 'require("fixture");' : 'require("../pkg");',
|
|
});
|
|
await writeFixtureFiles(application, {
|
|
[`${directory}/package.json`]: JSON.stringify({ main: "actual.cjs" }),
|
|
[`${directory}/actual.cjs`]:
|
|
request === "bare"
|
|
? 'require("../../window.cjs");'
|
|
: 'require("../window.cjs");',
|
|
[`${directory}/index.js`]: "module.exports = 1;",
|
|
"other/package.json": JSON.stringify({ main: "main.cjs" }),
|
|
"other/main.cjs": 'require("../resources/window.cjs");',
|
|
"resources/window.cjs":
|
|
'const { BrowserWindow } = require("electron"); new BrowserWindow().loadFile("index.html");',
|
|
"other/index.html": "false unique owner decoy",
|
|
});
|
|
await createPackageWithOptions(
|
|
source,
|
|
join(application, "resources/app.asar"),
|
|
{},
|
|
);
|
|
const { graph } = await analyzeJavaScriptDirectory(
|
|
surface,
|
|
cli,
|
|
application,
|
|
);
|
|
expect(graph.edges).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
relation: "imports",
|
|
properties: expect.objectContaining({
|
|
specifier: request === "bare" ? "fixture" : "../pkg",
|
|
resolution_status: "resolved",
|
|
resolved_path: `${directory}/actual.cjs`,
|
|
}),
|
|
}),
|
|
]),
|
|
);
|
|
const roles = graph.nodes.filter(
|
|
({ kind }) => kind === "electron-renderer",
|
|
);
|
|
expect(roles).toHaveLength(1);
|
|
expect(roles[0]?.observations).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({
|
|
resolution_status: "unavailable",
|
|
resolved_path: null,
|
|
}),
|
|
}),
|
|
]),
|
|
);
|
|
expect(
|
|
graph.edges.filter(
|
|
({ relation, source_node_id }) =>
|
|
relation === "maps_to" && source_node_id === roles[0]?.node_id,
|
|
),
|
|
).toEqual([]);
|
|
},
|
|
);
|