Preserve CommonJS literal paths and confined ESM URL semantics, including package main fallback, directory intent, Unicode identity, and present opaque modules. Parse extraction inputs at their AST/source owner, retain skipped HTML and source-map coverage, and distinguish endpoint candidates from proven receiver facts. Version structural fingerprints with computed literal contents and propagate them through worker contracts. Reviewed fixes include #1810, #1840, #2642, #2700, #2702, #2714, #2803, #2960, #2968, #2986, #3035, and #3036. Validation: 158 resolver tests passed; public CLI/MCP module lookup controls, package-main/Unicode workflows, and six builtin workflows passed in the current compiled lane. Worker schema/fingerprint regressions passed. Large public pdf.js completion and real Electron analysis remain unverified.
209 lines
7.2 KiB
TypeScript
209 lines
7.2 KiB
TypeScript
import type { WebPageInspection } from "./browserObservationSchemas.js";
|
|
import {
|
|
webBundleAnalysisSchema,
|
|
type WebBundleAnalysis,
|
|
} from "./webBundleAnalysis.js";
|
|
import type {
|
|
AnalysisAccumulator,
|
|
IncludedScript,
|
|
} from "./webBundleAnalyzerInspection.js";
|
|
|
|
export const buildWebBundleAnalysis = (
|
|
inspection: WebPageInspection,
|
|
sourceScripts: IncludedScript[],
|
|
sourceMaps: WebBundleAnalysis["observations"]["source_maps"],
|
|
accumulator: AnalysisAccumulator,
|
|
): WebBundleAnalysis => {
|
|
const unavailable = inspection.scripts.items
|
|
.filter((script) => !script.source.included)
|
|
.map(({ script_key }) => script_key);
|
|
const omittedScripts = captureBudgetOmission(inspection, "scripts");
|
|
const omittedSourceMaps =
|
|
sourceMaps.status === "not_requested"
|
|
? undefined
|
|
: captureBudgetOmission(inspection, "source_maps");
|
|
const sourceMapIncomplete =
|
|
sourceMaps.status === "partial" || sourceMaps.status === "unavailable";
|
|
const partial =
|
|
accumulator.failedScripts.length > 0 ||
|
|
accumulator.recoveredScripts.length > 0 ||
|
|
unavailable.length > 0 ||
|
|
sourceMapIncomplete ||
|
|
omittedScripts !== undefined ||
|
|
omittedSourceMaps !== undefined;
|
|
return webBundleAnalysisSchema.parse({
|
|
capture: buildCaptureObservation(inspection, sourceScripts),
|
|
observations: {
|
|
chunks: {
|
|
nodes: sourceScripts.map(chunkNode),
|
|
edges: accumulator.edges,
|
|
},
|
|
routes: accumulator.routes,
|
|
endpoints: accumulator.endpoints,
|
|
webmcp_declarations: accumulator.webMcp,
|
|
source_maps: sourceMaps,
|
|
},
|
|
inferences: accumulator.inferences,
|
|
unknowns: buildUnknowns({
|
|
unavailable,
|
|
failedScripts: accumulator.failedScripts,
|
|
recoveredScripts: accumulator.recoveredScripts,
|
|
sourceMaps,
|
|
omittedScripts,
|
|
omittedSourceMaps,
|
|
}),
|
|
completeness: buildCompleteness({
|
|
partial,
|
|
parsedScripts: accumulator.parsedScripts,
|
|
parseFailures: accumulator.failedScripts.length,
|
|
visitedNodes: accumulator.visitedNodes,
|
|
}),
|
|
limitations: bundleLimitations(),
|
|
});
|
|
};
|
|
|
|
const buildCaptureObservation = (
|
|
inspection: WebPageInspection,
|
|
sourceScripts: IncludedScript[],
|
|
) => ({
|
|
target_url: inspection.target.url,
|
|
scripts_observed: inspection.scripts.total,
|
|
scripts_analyzed: sourceScripts.length,
|
|
source_artifacts: sourceScripts.map(sourceArtifact),
|
|
});
|
|
|
|
const sourceArtifact = (script: IncludedScript) => {
|
|
if (!script.source.included) throw new TypeError("Filtered source changed");
|
|
const { text: _text, ...artifact } = script.source.artifact;
|
|
return { ...artifact, text_available: true };
|
|
};
|
|
|
|
const chunkNode = (script: IncludedScript) => {
|
|
if (!script.source.included) throw new TypeError("Filtered source changed");
|
|
return {
|
|
script_key: script.script_key,
|
|
url: script.url,
|
|
artifact_sha256: script.source.artifact.sha256,
|
|
bytes: script.source.artifact.bytes,
|
|
};
|
|
};
|
|
|
|
interface UnknownsInput {
|
|
readonly unavailable: string[];
|
|
readonly failedScripts: readonly string[];
|
|
readonly recoveredScripts: readonly string[];
|
|
readonly sourceMaps: WebBundleAnalysis["observations"]["source_maps"];
|
|
readonly omittedScripts: number | null | undefined;
|
|
readonly omittedSourceMaps: number | null | undefined;
|
|
}
|
|
|
|
const buildUnknowns = (input: UnknownsInput): WebBundleAnalysis["unknowns"] => [
|
|
...(input.omittedScripts === undefined
|
|
? []
|
|
: [
|
|
{
|
|
dimension: "script_inventory",
|
|
reason: captureBudgetReason("script records", input.omittedScripts),
|
|
affected_script_keys: [],
|
|
},
|
|
]),
|
|
...(input.unavailable.length === 0
|
|
? []
|
|
: [
|
|
{
|
|
dimension: "script_source" as const,
|
|
reason: "Source artifact was not captured",
|
|
affected_script_keys: input.unavailable,
|
|
},
|
|
]),
|
|
...(input.failedScripts.length === 0
|
|
? []
|
|
: [
|
|
{
|
|
dimension: "javascript_ast" as const,
|
|
reason: "One or more source artifacts could not be parsed",
|
|
affected_script_keys: [...input.failedScripts],
|
|
},
|
|
]),
|
|
...(input.recoveredScripts.length === 0
|
|
? []
|
|
: [
|
|
{
|
|
dimension: "javascript_ast" as const,
|
|
reason:
|
|
"The parser recovered from errors in one or more source artifacts; findings may be incomplete",
|
|
affected_script_keys: [...input.recoveredScripts],
|
|
},
|
|
]),
|
|
...(input.omittedSourceMaps === undefined
|
|
? []
|
|
: [
|
|
{
|
|
dimension: "source_maps" as const,
|
|
reason: captureBudgetReason(
|
|
"source-map declarations",
|
|
input.omittedSourceMaps,
|
|
),
|
|
affected_script_keys: [],
|
|
},
|
|
]),
|
|
...(input.sourceMaps.status === "not_requested" ||
|
|
input.sourceMaps.status === "included"
|
|
? []
|
|
: [
|
|
{
|
|
dimension: "source_maps" as const,
|
|
reason:
|
|
input.sourceMaps.limitation ??
|
|
"One or more requested source maps were unavailable or incomplete",
|
|
affected_script_keys: input.sourceMaps.items
|
|
.filter(({ status }) => status !== "included")
|
|
.map(({ script_key }) => script_key)
|
|
.sort(),
|
|
},
|
|
]),
|
|
];
|
|
|
|
const captureBudgetOmission = (
|
|
inspection: WebPageInspection,
|
|
section: "scripts" | "source_maps",
|
|
): number | null | undefined => {
|
|
const exclusions = inspection.completeness.excluded.filter(
|
|
(exclusion) =>
|
|
exclusion.section === section &&
|
|
exclusion.reason === "resource_budget_exhausted",
|
|
);
|
|
if (exclusions.length === 0) return undefined;
|
|
if (exclusions.some(({ count }) => count === null)) return null;
|
|
return exclusions.reduce((sum, { count }) => sum + (count ?? 0), 0);
|
|
};
|
|
|
|
const captureBudgetReason = (label: string, count: number | null): string =>
|
|
count === null
|
|
? `Capture omitted an unknown number of ${label} because its resource budget was exhausted.`
|
|
: `Capture omitted ${String(count)} ${label} because its resource budget was exhausted.`;
|
|
|
|
interface CompletenessInput {
|
|
readonly partial: boolean;
|
|
readonly parsedScripts: number;
|
|
readonly parseFailures: number;
|
|
readonly visitedNodes: number;
|
|
}
|
|
|
|
const buildCompleteness = (
|
|
input: CompletenessInput,
|
|
): WebBundleAnalysis["completeness"] => ({
|
|
status: input.partial ? "partial" : "complete",
|
|
parsed_scripts: input.parsedScripts,
|
|
parse_failures: input.parseFailures,
|
|
visited_ast_nodes: input.visitedNodes,
|
|
});
|
|
|
|
const bundleLimitations = (): WebBundleAnalysis["limitations"] => [
|
|
"Static bundle findings are observations or bounded inferences; REA does not execute captured JavaScript.",
|
|
"Bare string arguments of unbound HTTP-method-named calls are low-confidence endpoint candidates; form, translation, storage and other APIs can use the same syntax. URL-shaped findings still do not prove network execution.",
|
|
'Endpoint candidates are deduplicated by script, value and detector; basis retains only the first matching source coordinate. They are not a complete call-site inventory or a runtime occurrence count. completeness.status="complete" describes examination of captured source, not a call-site census.',
|
|
"String-built routes and endpoints, encrypted configuration, and server-side behavior may remain unknown.",
|
|
"Page-declared WebMCP metadata is untrusted and is never registered or invoked as an REA tool.",
|
|
];
|