mirror of
https://github.com/morluto/rea.git
synced 2026-10-12 02:19:24 +00:00
673 lines
22 KiB
TypeScript
673 lines
22 KiB
TypeScript
import { Readable } from "node:stream";
|
|
import { mkdir, rm, symlink, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
|
|
import { createPackageWithOptions } from "@electron/asar";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { createTestTempDirectory } from "./fixtures/temporaryDirectory.js";
|
|
|
|
import { readJavaScriptArtifactFiles } from "../src/application/JavaScriptArtifactFiles.js";
|
|
import { reconstructJavaScriptArtifact } from "../src/application/JavaScriptArtifactReconstruction.js";
|
|
import {
|
|
artifactLimitsForReconstruction,
|
|
javascriptArtifactReconstructionInputSchema,
|
|
} from "../src/application/JavaScriptArtifactReconstructionInput.js";
|
|
import { scanArtifactInventory } from "../src/application/ArtifactInventory.js";
|
|
import {
|
|
type ArtifactEntry,
|
|
type ArtifactReader,
|
|
} from "../src/artifacts/ArtifactReader.js";
|
|
import { parseJavaScriptApplicationGraph } from "../src/domain/javascriptApplicationGraph.js";
|
|
import { javaScriptApplicationAnalysisResultV2Schema } from "../src/domain/javascriptApplicationAnalysis.js";
|
|
import { createJavaScriptSemanticGraph } from "../src/domain/javascriptSemanticGraph.js";
|
|
import { parseJavaScriptSemanticGraph } from "../src/domain/javascriptSemanticGraphSerialization.js";
|
|
import { writeJavaScriptArtifactFixture } from "./fixtures/javascriptArtifactApplication.js";
|
|
|
|
describe("JavaScript artifact reconstruction", () => {
|
|
it("reconstructs package, Electron roles, Webpack/Rspack modules, and cross-layer facts without execution", async () => {
|
|
const root = await fixtureDirectory();
|
|
Reflect.deleteProperty(globalThis, "__rea_bundle_executed");
|
|
|
|
const result = await reconstructJavaScriptArtifact({
|
|
input_path: root,
|
|
source_map_read_approved: true,
|
|
});
|
|
const graph = parseJavaScriptApplicationGraph(result.graph);
|
|
const semanticGraph = parseJavaScriptSemanticGraph(result.semantic_graph);
|
|
|
|
expect(Reflect.get(globalThis, "__rea_bundle_executed")).toBeUndefined();
|
|
expect(result.input_path).toBe(root);
|
|
expect(result.statistics).toMatchObject({
|
|
modules: 4,
|
|
parse_failures: 0,
|
|
omitted_text_files: 0,
|
|
policy_filtered_text_files: 0,
|
|
});
|
|
expect(graph.nodes.map(({ kind }) => kind)).toEqual(
|
|
expect.arrayContaining([
|
|
"package",
|
|
"artifact",
|
|
"electron-main",
|
|
"electron-preload",
|
|
"electron-renderer",
|
|
"javascript-asset",
|
|
"javascript-chunk",
|
|
"javascript-module",
|
|
"worker",
|
|
"service-worker",
|
|
"endpoint",
|
|
"storage",
|
|
"source-map",
|
|
"source-module",
|
|
"native-addon",
|
|
]),
|
|
);
|
|
expect(
|
|
graph.nodes.filter(({ kind }) => kind === "javascript-chunk"),
|
|
).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
observations: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({ bundler: "webpack" }),
|
|
}),
|
|
]),
|
|
}),
|
|
expect.objectContaining({
|
|
observations: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({ bundler: "rspack" }),
|
|
}),
|
|
]),
|
|
}),
|
|
]),
|
|
);
|
|
expect(
|
|
graph.nodes.filter(({ kind }) => kind === "javascript-module"),
|
|
).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
observations: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({
|
|
module_key: "1",
|
|
structural_fingerprint_algorithm: "babel-ast-v1",
|
|
}),
|
|
}),
|
|
]),
|
|
}),
|
|
]),
|
|
);
|
|
expect(graph.edges.map(({ relation }) => relation)).toEqual(
|
|
expect.arrayContaining([
|
|
"contains",
|
|
"loads",
|
|
"imports",
|
|
"maps_to",
|
|
"exposes",
|
|
"calls",
|
|
"persists_to",
|
|
]),
|
|
);
|
|
expect(semanticGraph.application_graph_id).toBe(graph.graph_id);
|
|
const { graph_id: _semanticGraphId, ...semanticGraphInput } = semanticGraph;
|
|
const mismatchedSemanticGraph = createJavaScriptSemanticGraph({
|
|
...semanticGraphInput,
|
|
application_graph_id: `jag_${"f".repeat(64)}`,
|
|
});
|
|
const { electron_summary: summary, ...analysisResult } = result;
|
|
expect(() =>
|
|
javaScriptApplicationAnalysisResultV2Schema.parse({
|
|
...analysisResult,
|
|
schema_version: 2,
|
|
summary,
|
|
semantic_graph: mismatchedSemanticGraph,
|
|
}),
|
|
).toThrow(/must commit the containing application graph/u);
|
|
expect(
|
|
semanticGraph.nodes.some(
|
|
({ application_node_ids: identifiers }) => identifiers.length > 0,
|
|
),
|
|
).toBe(true);
|
|
expect(semanticGraph.relations.length).toBeGreaterThan(0);
|
|
const mainRole = graph.nodes.find(
|
|
(node) =>
|
|
node.kind === "electron-main" &&
|
|
node.observations.some(
|
|
({ properties }) => properties.declared_path === "main.js",
|
|
),
|
|
);
|
|
const preloadRole = graph.nodes.find(
|
|
(node) =>
|
|
node.kind === "electron-preload" &&
|
|
node.observations.some(
|
|
({ properties }) => properties.declared_path === "preload.js",
|
|
),
|
|
);
|
|
expect(mainRole?.observations[0]?.properties).toMatchObject({
|
|
declared_path: "main.js",
|
|
resolution_context: "package-entrypoint",
|
|
resolved_path: "main.js",
|
|
resolution_status: "resolved",
|
|
limitations: [],
|
|
});
|
|
expect(preloadRole?.observations[0]?.properties).toMatchObject({
|
|
declared_path: "preload.js",
|
|
resolution_context: "filesystem-expression",
|
|
resolved_path: "preload.js",
|
|
resolution_status: "resolved",
|
|
limitations: [],
|
|
});
|
|
const mainAsset = graph.nodes.find(
|
|
(node) =>
|
|
node.kind === "javascript-asset" &&
|
|
node.observations.some(
|
|
({ evidence }) =>
|
|
evidence.location.available &&
|
|
evidence.location.value.kind === "artifact-path" &&
|
|
evidence.location.value.path === "main.js",
|
|
),
|
|
);
|
|
if (mainAsset === undefined)
|
|
throw new Error("Expected main JavaScript asset");
|
|
const mainSemanticNodes = semanticGraph.nodes.filter(
|
|
({ identity }) => identity.module_path === "main.js",
|
|
);
|
|
const linkedMainSemanticNodes = mainSemanticNodes.filter(
|
|
({ application_node_ids: identifiers }) =>
|
|
identifiers.includes(mainAsset.node_id),
|
|
);
|
|
expect(linkedMainSemanticNodes.length).toBeGreaterThan(0);
|
|
expect(linkedMainSemanticNodes.length).toBeLessThan(
|
|
mainSemanticNodes.length,
|
|
);
|
|
for (const role of [mainRole, preloadRole]) {
|
|
expect(role).toBeDefined();
|
|
expect(
|
|
graph.edges.some(
|
|
(edge) =>
|
|
edge.source_node_id === role?.node_id &&
|
|
edge.relation === "maps_to",
|
|
),
|
|
).toBe(true);
|
|
}
|
|
const endpointJson = JSON.stringify(
|
|
graph.nodes.filter(({ kind }) => kind === "endpoint"),
|
|
);
|
|
expect(endpointJson).toContain("token=%5BREDACTED%5D");
|
|
expect(endpointJson).not.toContain("fixture-secret");
|
|
});
|
|
|
|
it("produces deterministic ASAR graphs with unpacked native linkage and complete paths/digests", async () => {
|
|
const root = await createTestTempDirectory("rea-javascript-asar-");
|
|
const source = join(root, "source");
|
|
await mkdir(source);
|
|
await writeJavaScriptArtifactFixture(source);
|
|
const archive = join(root, "app.asar");
|
|
await createPackageWithOptions(source, archive, { unpack: "**/*.node" });
|
|
|
|
const first = await reconstructJavaScriptArtifact({
|
|
input_path: archive,
|
|
format: "asar",
|
|
source_map_read_approved: true,
|
|
});
|
|
const second = await reconstructJavaScriptArtifact({
|
|
input_path: archive,
|
|
format: "asar",
|
|
source_map_read_approved: true,
|
|
});
|
|
|
|
expect(first.graph).toEqual(second.graph);
|
|
expect(first.inventory_graph_sha256).toBe(second.inventory_graph_sha256);
|
|
expect(first.graph.graph_id).toBe(second.graph.graph_id);
|
|
expect(first.graph.nodes).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
kind: "asar-entry",
|
|
observations: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({
|
|
entry_sha256: expect.stringMatching(/^[a-f0-9]{64}$/u),
|
|
inventory_artifact_id:
|
|
expect.stringMatching(/^art_[a-f0-9]{64}$/u),
|
|
}),
|
|
}),
|
|
]),
|
|
}),
|
|
expect.objectContaining({
|
|
kind: "native-addon",
|
|
observations: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({
|
|
path: "native/addon.node",
|
|
unpacked: true,
|
|
}),
|
|
}),
|
|
]),
|
|
}),
|
|
]),
|
|
);
|
|
expect(first.graph.edges).toContainEqual(
|
|
expect.objectContaining({
|
|
relation: "loads",
|
|
properties: expect.objectContaining({
|
|
resolved_path: "native/addon.node",
|
|
}),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("keeps ASAR analysis usable when unpacked native companion bytes are absent", async () => {
|
|
const root = await createTestTempDirectory("rea-javascript-asar-missing-");
|
|
const source = join(root, "source");
|
|
await mkdir(source);
|
|
await writeJavaScriptArtifactFixture(source);
|
|
const archive = join(root, "app.asar");
|
|
await createPackageWithOptions(source, archive, { unpack: "**/*.node" });
|
|
await rm(join(`${archive}.unpacked`, "native", "addon.node"));
|
|
|
|
const snapshot = await scanArtifactInventory(archive, {
|
|
maxEntries: 8_000,
|
|
maxTotalBytes: 512 * 1024 * 1024,
|
|
maxEntryBytes: 128 * 1024 * 1024,
|
|
maxCompressionRatio: 1_000,
|
|
maxDepth: 64,
|
|
maxPathBytes: 4_096,
|
|
});
|
|
const missingNative = snapshot.occurrences.find(
|
|
({ logical_path }) => logical_path === "native/addon.node",
|
|
);
|
|
|
|
expect(missingNative).toMatchObject({
|
|
artifact_id: null,
|
|
hash_status: "unavailable",
|
|
limitations: expect.arrayContaining([
|
|
"ASAR unpacked companion bytes were unavailable; no content hash or child artifact was produced.",
|
|
]),
|
|
});
|
|
|
|
const result = await reconstructJavaScriptArtifact({
|
|
input_path: archive,
|
|
format: "asar",
|
|
source_map_read_approved: true,
|
|
});
|
|
|
|
expect(result.statistics.parsed_javascript_files).toBeGreaterThan(0);
|
|
expect(result.graph.nodes).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({ kind: "javascript-asset" }),
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("recurses into filesystem-backed ASAR containers without losing container-relative paths", async () => {
|
|
const root = await createTestTempDirectory("rea-javascript-nested-asar-");
|
|
const source = await fixtureDirectory();
|
|
const outer = join(root, "outer");
|
|
const resources = join(outer, "resources");
|
|
await mkdir(resources, { recursive: true });
|
|
await createPackageWithOptions(source, join(resources, "app.asar"), {
|
|
unpack: "**/*.node",
|
|
});
|
|
|
|
const result = await reconstructJavaScriptArtifact({
|
|
input_path: outer,
|
|
source_map_read_approved: true,
|
|
});
|
|
|
|
expect(result.statistics.nested_asar_containers).toBe(1);
|
|
expect(result.statistics.modules).toBe(4);
|
|
expect(result.graph.nodes).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
kind: "artifact",
|
|
observations: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
label: "resources/app.asar",
|
|
properties: expect.objectContaining({ format: "asar" }),
|
|
}),
|
|
]),
|
|
}),
|
|
expect.objectContaining({
|
|
kind: "javascript-asset",
|
|
observations: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({
|
|
path: "resources/app.asar/main.js",
|
|
}),
|
|
}),
|
|
]),
|
|
}),
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("discovers source maps without reading them until separately approved", async () => {
|
|
const root = await fixtureDirectory();
|
|
const unapproved = await reconstructJavaScriptArtifact({
|
|
input_path: root,
|
|
source_map_read_approved: false,
|
|
});
|
|
const approved = await reconstructJavaScriptArtifact({
|
|
input_path: root,
|
|
source_map_read_approved: true,
|
|
});
|
|
|
|
expect(
|
|
unapproved.graph.nodes.some(({ kind }) => kind === "source-map"),
|
|
).toBe(true);
|
|
expect(
|
|
unapproved.graph.nodes.some(({ kind }) => kind === "source-module"),
|
|
).toBe(false);
|
|
expect(unapproved.graph.coverage).toMatchObject({
|
|
status: "partial",
|
|
truncated: false,
|
|
omitted_count: null,
|
|
});
|
|
expect(unapproved.statistics.policy_filtered_text_files).toBe(1);
|
|
expect(unapproved.graph.nodes).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
kind: "unknown",
|
|
observations: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({
|
|
operation: "parse-local-source-map",
|
|
}),
|
|
evidence: expect.objectContaining({ state: "unavailable" }),
|
|
}),
|
|
]),
|
|
}),
|
|
]),
|
|
);
|
|
expect(
|
|
approved.graph.nodes.some(({ kind }) => kind === "source-module"),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("reports text and AST limits as truncation instead of absence", async () => {
|
|
const root = await fixtureDirectory();
|
|
const result = await reconstructJavaScriptArtifact({
|
|
input_path: root,
|
|
source_map_read_approved: true,
|
|
limits: {
|
|
max_text_file_bytes: 128,
|
|
max_total_text_bytes: 1_024,
|
|
max_ast_nodes: 50,
|
|
},
|
|
});
|
|
|
|
expect(result.statistics.limit_omitted_text_files).toBeGreaterThan(0);
|
|
expect(result.graph.coverage).toMatchObject({
|
|
status: "partial",
|
|
truncated: true,
|
|
});
|
|
expect(result.graph.coverage.omitted_count).toBeGreaterThan(0);
|
|
expect(result.graph.nodes.some(({ kind }) => kind === "unknown")).toBe(
|
|
true,
|
|
);
|
|
expect(result.graph.limitations.join(" ")).toMatch(/incomplete|bound/iu);
|
|
});
|
|
|
|
it("uses an unknown omission count when AST traversal reaches its bound", async () => {
|
|
const root = await createTestTempDirectory("rea-javascript-ast-limit-");
|
|
await writeFile(
|
|
join(root, "large.js"),
|
|
[
|
|
'import "./unobserved.js";',
|
|
...Array.from(
|
|
{ length: 100 },
|
|
(_, index) =>
|
|
`export const value${String(index)} = ${String(index)};`,
|
|
),
|
|
].join("\n"),
|
|
);
|
|
|
|
const result = await reconstructJavaScriptArtifact({
|
|
input_path: root,
|
|
limits: { max_ast_nodes: 50 },
|
|
});
|
|
|
|
expect(result.statistics.limit_omitted_text_files).toBe(0);
|
|
expect(result.statistics.truncated_scopes).toBe(1);
|
|
expect(result.graph.coverage).toMatchObject({
|
|
status: "partial",
|
|
truncated: true,
|
|
omitted_count: null,
|
|
});
|
|
const derivedImport = result.graph.edges.find(
|
|
({ relation, properties }) =>
|
|
relation === "imports" && properties.specifier === "./unobserved.js",
|
|
);
|
|
expect(derivedImport?.evidence.coverage).toEqual({
|
|
status: "partial",
|
|
truncated: true,
|
|
omitted_count: null,
|
|
limits: expect.arrayContaining([
|
|
{ name: "max-ast-nodes", value: 50, unit: "items" },
|
|
]),
|
|
});
|
|
});
|
|
|
|
it("does not follow symlinks or read oversized JavaScript text", async () => {
|
|
const root = await fixtureDirectory();
|
|
const outside = await createTestTempDirectory("rea-javascript-outside-");
|
|
const outsideFile = join(outside, "secret.js");
|
|
await writeFile(
|
|
outsideFile,
|
|
'fetch("https://outside-secret.invalid/credential");',
|
|
);
|
|
await symlink(outsideFile, join(root, "escape.js"));
|
|
await writeFile(
|
|
join(root, "oversized.js"),
|
|
`const oversizedSecret = "${"private-marker-".repeat(256)}";`,
|
|
);
|
|
|
|
const result = await reconstructJavaScriptArtifact({
|
|
input_path: root,
|
|
limits: { max_text_file_bytes: 512 },
|
|
});
|
|
const encoded = JSON.stringify(result);
|
|
|
|
expect(result.statistics.limit_omitted_text_files).toBeGreaterThan(0);
|
|
expect(result.graph.coverage).toMatchObject({
|
|
status: "partial",
|
|
truncated: true,
|
|
});
|
|
expect(encoded).not.toContain(outsideFile);
|
|
expect(encoded).not.toContain("outside-secret.invalid");
|
|
expect(encoded).not.toContain("private-marker-private-marker");
|
|
});
|
|
|
|
it("keeps malformed JavaScript, package metadata, and source maps as explicit unknowns", async () => {
|
|
const root = await fixtureDirectory();
|
|
await Promise.all([
|
|
writeFile(join(root, "package.json"), "{"),
|
|
writeFile(join(root, "broken.js"), "function broken( {"),
|
|
writeFile(join(root, "renderer", "renderer.js.map"), "{"),
|
|
]);
|
|
|
|
const result = await reconstructJavaScriptArtifact({
|
|
input_path: root,
|
|
source_map_read_approved: true,
|
|
});
|
|
const unknownOperations = result.graph.nodes
|
|
.filter(({ kind }) => kind === "unknown")
|
|
.flatMap(({ observations }) =>
|
|
observations.map(({ properties }) => properties.operation),
|
|
);
|
|
|
|
expect(result.statistics.parse_failures).toBeGreaterThan(0);
|
|
expect(result.graph.coverage).toMatchObject({
|
|
status: "partial",
|
|
truncated: false,
|
|
omitted_count: null,
|
|
});
|
|
expect(unknownOperations).toEqual(
|
|
expect.arrayContaining([
|
|
"parse-javascript",
|
|
"parse-package-json",
|
|
"parse-local-source-map",
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("applies one source-map source budget across every local map", async () => {
|
|
const root = await createTestTempDirectory("rea-javascript-source-maps-");
|
|
await Promise.all([
|
|
writeFile(
|
|
join(root, "a.js.map"),
|
|
JSON.stringify({
|
|
version: 3,
|
|
sources: ["a-one.ts", "a-two.ts"],
|
|
sourcesContent: ["one", "two"],
|
|
names: [],
|
|
mappings: "",
|
|
}),
|
|
),
|
|
writeFile(
|
|
join(root, "b.js.map"),
|
|
JSON.stringify({
|
|
version: 3,
|
|
sources: ["b-one.ts", "b-two.ts"],
|
|
sourcesContent: ["three", "four"],
|
|
names: [],
|
|
mappings: "",
|
|
}),
|
|
),
|
|
]);
|
|
|
|
const result = await reconstructJavaScriptArtifact({
|
|
input_path: root,
|
|
source_map_read_approved: true,
|
|
limits: { max_source_map_sources: 3 },
|
|
});
|
|
|
|
expect(
|
|
result.graph.nodes.filter(({ kind }) => kind === "source-module"),
|
|
).toHaveLength(3);
|
|
expect(result.graph.coverage).toMatchObject({
|
|
status: "partial",
|
|
truncated: true,
|
|
omitted_count: null,
|
|
});
|
|
});
|
|
|
|
it("bounds repeated content observations while retaining every containment path", async () => {
|
|
const root = await createTestTempDirectory("rea-javascript-observations-");
|
|
await Promise.all(
|
|
Array.from({ length: 70 }, (_, index) =>
|
|
writeFile(
|
|
join(root, `duplicate-${String(index).padStart(2, "0")}.js`),
|
|
"export const same = 1;\n",
|
|
),
|
|
),
|
|
);
|
|
|
|
const result = await reconstructJavaScriptArtifact({ input_path: root });
|
|
const duplicateNode = result.graph.nodes.find(
|
|
({ kind, observations }) =>
|
|
kind === "javascript-asset" &&
|
|
observations.some(({ properties }) =>
|
|
String(properties.path).startsWith("duplicate-"),
|
|
),
|
|
);
|
|
const duplicateEdges = result.graph.edges.filter(
|
|
({ relation, properties }) =>
|
|
relation === "contains" &&
|
|
String(properties.path).startsWith("duplicate-"),
|
|
);
|
|
|
|
expect(duplicateNode?.observations).toHaveLength(64);
|
|
expect(duplicateEdges).toHaveLength(70);
|
|
expect(result.graph.coverage).toMatchObject({
|
|
status: "partial",
|
|
truncated: true,
|
|
omitted_count: 6,
|
|
});
|
|
}, 15_000);
|
|
|
|
it("rejects traversal from a production reader seam and malformed ASAR containers", async () => {
|
|
const root = await fixtureDirectory();
|
|
const input = javascriptArtifactReconstructionInputSchema.parse({
|
|
input_path: root,
|
|
});
|
|
const snapshot = await scanArtifactInventory(
|
|
root,
|
|
artifactLimitsForReconstruction(input),
|
|
);
|
|
await expect(
|
|
readJavaScriptArtifactFiles(new TraversalReader(), snapshot, input),
|
|
).rejects.toMatchObject({ reason: "path" });
|
|
|
|
const malformed = join(root, "malformed.asar");
|
|
await writeFile(malformed, "not an asar");
|
|
await expect(
|
|
reconstructJavaScriptArtifact({ input_path: malformed }),
|
|
).rejects.toMatchObject({
|
|
name: "ArtifactReaderFailure",
|
|
reason: "format",
|
|
});
|
|
});
|
|
|
|
it("preserves cancellation and explicit format diagnostics", async () => {
|
|
const root = await fixtureDirectory();
|
|
const controller = new AbortController();
|
|
controller.abort();
|
|
await expect(
|
|
reconstructJavaScriptArtifact({ input_path: root }, controller.signal),
|
|
).rejects.toMatchObject({ reason: "cancelled" });
|
|
await expect(
|
|
reconstructJavaScriptArtifact({ input_path: root, format: "asar" }),
|
|
).rejects.toMatchObject({
|
|
reason: "format",
|
|
message: expect.stringContaining(root),
|
|
});
|
|
expect(() =>
|
|
javascriptArtifactReconstructionInputSchema.parse({
|
|
input_path: root,
|
|
limits: { max_entries: 100_000 },
|
|
}),
|
|
).toThrow(/graph contract/iu);
|
|
});
|
|
});
|
|
|
|
class TraversalReader implements ArtifactReader {
|
|
readonly format = "directory" as const;
|
|
|
|
async *entries(): AsyncIterable<ArtifactEntry> {
|
|
yield {
|
|
path: "../escape.js",
|
|
kind: "file",
|
|
declaredSize: 1,
|
|
compressedSize: null,
|
|
executable: false,
|
|
encrypted: false,
|
|
byteOffset: null,
|
|
declaredSha256: null,
|
|
unpacked: false,
|
|
limitations: [],
|
|
adapterKey: "/tmp/escape.js",
|
|
};
|
|
}
|
|
|
|
open(): Promise<Readable> {
|
|
return Promise.resolve(Readable.from("x"));
|
|
}
|
|
|
|
close(): Promise<void> {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
provenance(): readonly [] {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
const fixtureDirectory = async (): Promise<string> => {
|
|
const root = await createTestTempDirectory("rea-javascript-artifact-");
|
|
await writeJavaScriptArtifactFixture(root);
|
|
return root;
|
|
};
|