Files
rea/src/domain/javascript/javascriptRecovery.ts
T
JingHao-Leon 12c416005f 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>
2026-10-11 18:39:49 +08:00

125 lines
4.0 KiB
TypeScript

import { z } from "zod";
import { digestSchema } from "../digests.js";
import { analyzeJavaScriptApplicationInputSchema } from "./javascriptApplicationAnalysis.js";
import { localPathStringSchema } from "../localPath.js";
/** Recover derived readable modules from explicitly selected local scripts. */
export const javascriptRecoveryInputSchema = z.strictObject({
path: localPathStringSchema.describe(
"Absolute path to a UTF-8 JavaScript file; never executed",
),
additional_paths: z
.array(localPathStringSchema)
.max(16)
.optional()
.describe(
"Additional absolute UTF-8 JavaScript files recovered together with path; each is snapshotted separately so imports can resolve across related chunks. All inputs share a 64 MiB total budget",
),
output_directory: localPathStringSchema.describe(
"Absolute absent output directory; parent must exist",
),
extraction_mode: z
.enum(["structural", "heuristic", "inspection"])
.default("structural")
.describe(
"Structural module boundaries, heuristic fallback, or finer inspection-only regions",
),
rewrite_level: z
.enum(["minimal", "standard", "aggressive"])
.default("standard")
.describe(
"Upstream syntax recovery level; runtime equivalence remains unverified",
),
});
const fileSchema = z.strictObject({
path: z.string().min(1),
sha256: digestSchema,
bytes: z.number().int().nonnegative(),
});
const rangeSchema = z.strictObject({
start: z.number().int().nonnegative(),
end: z.number().int().nonnegative(),
});
const sourceSchema = fileSchema.extend({
snapshot_path: z.string().min(1),
published_copy: fileSchema,
});
/** Complete published artifacts and exact producer provenance for source recovery. */
export const javascriptRecoveryResultSchema = z.strictObject({
source: sourceSchema,
additional_sources: z.array(sourceSchema).optional(),
engine: z.strictObject({
id: z.string().min(1),
version: z.string().min(1),
executable: fileSchema,
audited_source_revision: z.string().min(1),
executed_source_revision: z.string().nullable(),
}),
options: z.strictObject({
extraction_mode:
javascriptRecoveryInputSchema.shape.extraction_mode.removeDefault(),
rewrite_level:
javascriptRecoveryInputSchema.shape.rewrite_level.removeDefault(),
}),
status: z.enum(["complete", "partial"]),
detected_formats: z.array(z.string()),
reported_safety: z.string(),
reported_strategy: z.string(),
reported_format: z.string(),
total: z.number().int().nonnegative(),
failed: z.number().int().nonnegative(),
modules: z.array(
z.strictObject({
reported_filename: z.string().min(1),
reported_status: z.string().min(1),
artifact: fileSchema,
source_map: fileSchema.nullable(),
provenance: z.strictObject({
reported_input: z.string(),
original_path: z.string(),
original_sha256: digestSchema,
extraction: z.string(),
byte_ranges: z.array(rangeSchema),
context_byte_ranges: z.array(rangeSchema),
range_semantics: z.literal("half-open-utf8-bytes"),
}),
}),
),
warnings: z.array(
z.strictObject({
filename: z.string(),
kind: z.string(),
is_error: z.boolean(),
message: z.string(),
}),
),
report: fileSchema,
provenance: fileSchema,
manifest: fileSchema,
analysis_input: z
.strictObject({
input_path: analyzeJavaScriptApplicationInputSchema.shape.input_path,
format: z.literal("directory"),
})
.nullable(),
runtime_equivalence: z.literal("unknown"),
limitations: z.array(z.string()),
});
export type JavaScriptRecoveryInput = z.output<
typeof javascriptRecoveryInputSchema
>;
export type JavaScriptRecoveryResult = z.output<
typeof javascriptRecoveryResultSchema
>;
/** Completed publication retained when cleanup fails after durable commit. */
export interface JavaScriptRecoveryPartialObservation {
readonly kind: "javascript-recovery";
readonly result: JavaScriptRecoveryResult;
}