Files
rea/tests/acceptance/artifactZipLeadingBytes.test.ts
T
Kaoru0822 35d6d5bb0a fix(artifacts): admit ZIP archives after unclaimed leading bytes (#5803)
* fix(artifacts): admit ZIP archives after unclaimed leading bytes

ZIP recognition required the archive at byte 0, so a ZIP behind a
script or self-extractor stub was refused as an unsupported binary
format although standard readers accept it. Target resolution and root
inventory now locate a trailing single-disk ZIP from its end record,
central directory and first local header, whether member offsets include
the leading bytes or not. Leading bytes any executable header family
claims keep their executable route.

Fixes #5732

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(artifacts): share the root ZIP family across recognition paths

The trailing-ZIP fallback named a stub-prefixed root `.app` archive
`zip`, while root inventory and the leading-signature route name it
`app-pack`. The root mapping now lives once in `zipRootPackageFormat`
and both target resolution routes and root inventory use it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 05:06:28 -07:00

75 lines
2.9 KiB
TypeScript

import { readFile, rm, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { TextReader, Uint8ArrayWriter, ZipWriter } from "@zip.js/zip.js";
import { expect } from "vitest";
import {
artifactExtractionResultSchema,
artifactInventoryResultSchema,
} from "../../src/domain/artifactGraph.js";
import { artifactInspectionResultSchema } from "../../src/domain/artifactInspection.js";
import { parseEvidence } from "../../src/domain/evidence.js";
import { ZIP_NON_ENTRY_LIMITATION } from "../../src/domain/zipPackageFormat.js";
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
import { cliTest } from "../support/cli/cliFixture.js";
cliTest(
"inspects and extracts a ZIP after an inert script stub",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-zip-script-stub-");
const archivePath = join(root, "installer.sh");
const writer = new ZipWriter(new Uint8ArrayWriter());
await writer.add("a.txt", new TextReader("hello world\n"));
await writer.add("d/b.txt", new TextReader("x\n"));
// A self-extractor stub prepended to an unchanged archive.
await writeFile(
archivePath,
Buffer.concat([Buffer.from("#!/bin/sh\nexit 0\n"), await writer.close()]),
);
const inspected = await cli.run({
arguments: ["inspect-artifact", archivePath, "--json"],
});
expect(inspected.exitCode, inspected.stdout + inspected.stderr).toBe(0);
const inspection = artifactInspectionResultSchema.parse(
parseEvidence(inspected.json).normalized_result,
);
expect(inspection.subject.root_format).toBe("zip");
const inventoryEvidence = inspection.substeps[0]?.evidence;
if (inventoryEvidence === undefined)
throw new Error("CLI inspection did not retain ZIP inventory Evidence");
const inventory = artifactInventoryResultSchema.parse(
inventoryEvidence.normalized_result,
);
expect(
inventory.occurrences
.filter(({ entry_kind }) => entry_kind === "file")
.map(({ logical_path, hash_status }) => [logical_path, hash_status]),
).toEqual([
[".", "verified"],
["a.txt", "verified"],
["d/b.txt", "verified"],
]);
expect(inventory.limitations).toContain(ZIP_NON_ENTRY_LIMITATION);
const extracted = await cli.run({
arguments: ["extract-artifact", archivePath, "--json"],
});
expect(extracted.exitCode, extracted.stdout + extracted.stderr).toBe(0);
const extraction = artifactExtractionResultSchema.parse(
parseEvidence(extracted.json).normalized_result,
);
try {
expect(
extraction.artifacts.map(({ relative_path }) => relative_path),
).toEqual(["a.txt", "d/b.txt"]);
expect(
await readFile(join(extraction.output_root, "a.txt"), "utf8"),
).toBe("hello world\n");
} finally {
await rm(extraction.output_root, { force: true, recursive: true });
}
},
);