mirror of
https://github.com/morluto/rea.git
synced 2026-10-31 20:06:14 +00:00
feat(javascript): recover code-split bundles from multiple inputs (#1686)
* feat(javascript): recover code-split bundles from multiple inputs recover_javascript_sources accepted a single path, and validateModuleSet bound every module's provenance to that one snapshot. A code-split app (runtime + chunks, or an Electron main/preload/renderer set) had to be recovered one file per call, and modules recovered alone could not resolve imports across chunks: cross-chunk require(id) references stayed as numeric ids with property reads. Add an optional additional_paths input. The listed files are concatenated, in order after path, into the single engine snapshot, so one Wakaru run sees every module and cross-chunk imports resolve. validateModuleSet's single-snapshot binding is unchanged. Module provenance is attributed back to the input file its first extraction range starts in, with byte ranges rebased to that file's own offsets; a module whose ranges cross an input boundary keeps the concatenated snapshot identity and offsets, recorded as an explicit limitation. Single-path behavior is byte-for-byte unchanged (one segment, offset 0). Fixes #1605 * fix(javascript): preserve source identity in multi-input recovery --------- Co-authored-by: JingHao-Leon <w15909130000@gmail.com> Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
This commit is contained in:
23 files changed
+695
-121
No files matched your search
@@ -56,7 +56,7 @@ try {
|
||||
process.env.REA_JAVASCRIPT_FIXTURE_TOOLS,
|
||||
);
|
||||
await client.connect(transport);
|
||||
for (const fixture of fixtures.files) {
|
||||
for (const fixture of [...fixtures.files, fixtures.codeSplit]) {
|
||||
for (const surface of ["cli", "stdio_mcp"]) {
|
||||
const output = join(root, `${fixture.name}-${surface}`);
|
||||
const flags =
|
||||
@@ -75,11 +75,18 @@ try {
|
||||
fixture.path,
|
||||
output,
|
||||
...flags,
|
||||
...(fixture.additional_paths ?? []).flatMap((path) => [
|
||||
"--additional-path",
|
||||
path,
|
||||
]),
|
||||
)
|
||||
: await call("recover_javascript_sources", {
|
||||
path: fixture.path,
|
||||
output_directory: output,
|
||||
...fixture.options,
|
||||
...(fixture.additional_paths
|
||||
? { additional_paths: fixture.additional_paths }
|
||||
: {}),
|
||||
});
|
||||
const result = javascriptRecoveryResultSchema.parse(
|
||||
evidence.normalized_result,
|
||||
@@ -97,6 +104,25 @@ try {
|
||||
analysis.normalized_result.statistics.parsed_javascript_files,
|
||||
result.modules.length,
|
||||
);
|
||||
const imports = analysis.normalized_result.graph.edges.filter(
|
||||
(edge) =>
|
||||
edge.relation === "imports" &&
|
||||
edge.properties.resolution_status === "resolved",
|
||||
);
|
||||
if (fixture.additional_paths) {
|
||||
assert.ok(
|
||||
imports.length >= 4,
|
||||
"Expected cross-chunk imports in the recovered application graph",
|
||||
);
|
||||
const recoveredPaths = new Set(
|
||||
result.modules.map((module) => module.reported_filename),
|
||||
);
|
||||
for (const edge of imports)
|
||||
assert.ok(
|
||||
recoveredPaths.has(edge.properties.resolved_path),
|
||||
`Expected an emitted import target: ${String(edge.properties.resolved_path)}`,
|
||||
);
|
||||
}
|
||||
const recoveredEntry =
|
||||
result.modules.find(
|
||||
(module) => module.reported_filename === "entry.js",
|
||||
@@ -104,7 +130,8 @@ try {
|
||||
assert.ok(recoveredEntry);
|
||||
const oracle =
|
||||
'await import((await import("node:url")).pathToFileURL(process.argv[1])); const f=globalThis.recoveryFixture; console.log(JSON.stringify([f.greet(null),f.greet("REA"),f.total([1,2,3])]));';
|
||||
for (const selected of fixture.options?.extraction_mode === "inspection"
|
||||
for (const selected of fixture.additional_paths ||
|
||||
fixture.options?.extraction_mode === "inspection"
|
||||
? []
|
||||
: [fixture.path, recoveredEntry.artifact.path]) {
|
||||
const execution = await run(
|
||||
@@ -125,7 +152,9 @@ try {
|
||||
maps: result.modules.filter((module) => module.source_map !== null)
|
||||
.length,
|
||||
finite_fixture_oracle:
|
||||
!fixture.additional_paths &&
|
||||
fixture.options?.extraction_mode !== "inspection",
|
||||
cross_chunk_imports: fixture.additional_paths ? imports.length : null,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -157,9 +186,15 @@ async function assertRecoveredArtifacts(evidence, result, fixture) {
|
||||
assert.equal(result.status, "complete");
|
||||
assert.equal(result.reported_format, fixture.expectedFormat);
|
||||
assert.equal(result.modules.length, fixture.expectedModules);
|
||||
assert.equal(result.source.sha256, fingerprint(await readFile(fixture.path)));
|
||||
const sources = [result.source, ...(result.additional_sources ?? [])];
|
||||
assert.deepEqual(
|
||||
sources.map((source) => source.path),
|
||||
[fixture.path, ...(fixture.additional_paths ?? [])],
|
||||
);
|
||||
for (const source of sources)
|
||||
assert.equal(source.sha256, fingerprint(await readFile(source.path)));
|
||||
for (const file of [
|
||||
result.source.published_copy,
|
||||
...sources.map((source) => source.published_copy),
|
||||
result.report,
|
||||
result.provenance,
|
||||
result.manifest,
|
||||
@@ -173,16 +208,27 @@ async function assertRecoveredArtifacts(evidence, result, fixture) {
|
||||
assert.equal(fingerprint(bytes), file.sha256);
|
||||
}
|
||||
for (const module of result.modules) {
|
||||
assert.equal(module.provenance.original_sha256, result.source.sha256);
|
||||
assert.equal(module.provenance.reported_input, result.source.snapshot_path);
|
||||
for (const range of module.provenance.byte_ranges)
|
||||
assert.ok(range.start <= range.end && range.end <= result.source.bytes);
|
||||
const source = sources.find(
|
||||
(source) => source.snapshot_path === module.provenance.reported_input,
|
||||
);
|
||||
assert.ok(source, "Module must name a selected input snapshot");
|
||||
assert.equal(module.provenance.original_path, source.path);
|
||||
assert.equal(module.provenance.original_sha256, source.sha256);
|
||||
for (const range of [
|
||||
...module.provenance.byte_ranges,
|
||||
...module.provenance.context_byte_ranges,
|
||||
])
|
||||
assert.ok(range.start <= range.end && range.end <= source.bytes);
|
||||
if (module.source_map) {
|
||||
const map = JSON.parse(await readFile(module.source_map.path, "utf8"));
|
||||
for (const source of map.sources)
|
||||
assert.equal(
|
||||
resolve(dirname(module.source_map.path), source),
|
||||
result.source.published_copy.path,
|
||||
assert.ok(
|
||||
sources.some(
|
||||
(selected) =>
|
||||
selected.published_copy.path ===
|
||||
resolve(dirname(module.source_map.path), source),
|
||||
),
|
||||
"Map source must name a published input snapshot",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user