mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
refactor(artifacts): retain admitted Godot pack location
This commit is contained in:
1 parent
69b328eec8
commit
5d25aa4f52
6 files changed
+130
-32
No files matched your search
@@ -17,6 +17,7 @@ import { createTestTempDirectory } from "../../tests/fixtures/temporaryDirectory
|
||||
import { openRegularFile } from "../filesystem/RegularFile.js";
|
||||
|
||||
import type { ArtifactEntry } from "./ArtifactReader.js";
|
||||
import { ArtifactEmbeddedFormatProbes } from "./ArtifactEmbeddedFormatProbes.js";
|
||||
import { GodotPckArtifactReader } from "./GodotPckArtifactReader.js";
|
||||
|
||||
const writePck = async (bytes: Buffer): Promise<string> => {
|
||||
@@ -262,32 +263,83 @@ describe("GodotPckArtifactReader member streams", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("reads members from the admitted descriptor after the path is replaced", async () => {
|
||||
const root = await createTestTempDirectory("rea-godot-borrowed-source-");
|
||||
const selected = join(root, "selected");
|
||||
await mkdir(selected);
|
||||
const path = join(selected, "game");
|
||||
await writeFile(path, buildGodotPck(3, members));
|
||||
it("rejects stale located facts after the admitted carrier changes", async () => {
|
||||
const path = await writePck(buildGodotPck(3, members));
|
||||
const descriptor = await openRegularFile(path, { symlinks: "reject" });
|
||||
const { handle } = descriptor;
|
||||
try {
|
||||
// Replace the selected directory without changing the admitted inode's
|
||||
// metadata. Moving the file itself would change its validated ctime.
|
||||
await rename(selected, join(root, "original"));
|
||||
await mkdir(selected);
|
||||
await writeFile(path, buildGodotPck(3, [{ path: "other.gd", data: "" }]));
|
||||
const reader = new GodotPckArtifactReader(path, descriptor);
|
||||
const entries = await list(reader);
|
||||
expect(entries.map(({ path: member }) => member)).toEqual([
|
||||
"scenes/main.gd",
|
||||
"project.binary",
|
||||
"data/é.json",
|
||||
]);
|
||||
await reader.close();
|
||||
const location = await new ArtifactEmbeddedFormatProbes(
|
||||
path,
|
||||
descriptor,
|
||||
).godotPck();
|
||||
if (location === undefined) throw new Error("expected a pack location");
|
||||
const reader = new GodotPckArtifactReader(path, {
|
||||
kind: "godot-pck",
|
||||
source: descriptor,
|
||||
location,
|
||||
});
|
||||
try {
|
||||
await writeFile(path, Buffer.from("replaced carrier"));
|
||||
await expect(list(reader)).rejects.toMatchObject({
|
||||
reason: "integrity",
|
||||
});
|
||||
} finally {
|
||||
await reader.close();
|
||||
}
|
||||
} finally {
|
||||
await handle.close();
|
||||
await descriptor.handle.close();
|
||||
}
|
||||
});
|
||||
|
||||
it.each([false, true])(
|
||||
"reads located pack facts from the admitted descriptor after path replacement (embedded: %s)",
|
||||
async (embedded) => {
|
||||
const root = await createTestTempDirectory("rea-godot-borrowed-source-");
|
||||
const selected = join(root, "selected");
|
||||
await mkdir(selected);
|
||||
const path = join(selected, "game");
|
||||
const bytes = buildGodotPck(3, members);
|
||||
await writeFile(path, embedded ? embedGodotPck(bytes) : bytes);
|
||||
const descriptor = await openRegularFile(path, { symlinks: "reject" });
|
||||
const { handle } = descriptor;
|
||||
try {
|
||||
const location = await new ArtifactEmbeddedFormatProbes(
|
||||
path,
|
||||
descriptor,
|
||||
).godotPck();
|
||||
if (location === undefined) throw new Error("expected a pack location");
|
||||
// Replace the selected directory without changing the admitted inode's
|
||||
// metadata. Moving the file itself would change its validated ctime.
|
||||
await rename(selected, join(root, "original"));
|
||||
await mkdir(selected);
|
||||
await writeFile(
|
||||
path,
|
||||
buildGodotPck(3, [{ path: "other.gd", data: "" }]),
|
||||
);
|
||||
const reader = new GodotPckArtifactReader(path, {
|
||||
kind: "godot-pck",
|
||||
source: descriptor,
|
||||
location,
|
||||
});
|
||||
const cancelled = new AbortController();
|
||||
cancelled.abort();
|
||||
await expect(
|
||||
reader.entries(cancelled.signal)[Symbol.asyncIterator]().next(),
|
||||
).rejects.toMatchObject({
|
||||
reason: "cancelled",
|
||||
});
|
||||
const entries = await list(reader);
|
||||
expect(reader.formatMetadata()?.embedded).toBe(embedded);
|
||||
expect(entries.map(({ path: member }) => member)).toEqual([
|
||||
"scenes/main.gd",
|
||||
"project.binary",
|
||||
"data/é.json",
|
||||
]);
|
||||
await reader.close();
|
||||
} finally {
|
||||
await handle.close();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("Godot PCK logical-byte admission", () => {
|
||||
|
||||
@@ -13,7 +13,12 @@ import {
|
||||
} from "./ArtifactReader.js";
|
||||
import { ArtifactResourceScope } from "./ArtifactResourceScope.js";
|
||||
import { assertArtifactReadActive } from "./ArtifactFileOperation.js";
|
||||
import { locateGodotPck, readPckBytes } from "./GodotPckLocation.js";
|
||||
import {
|
||||
locateGodotPck,
|
||||
readPckBytes,
|
||||
type GodotPckAdmission,
|
||||
type GodotPckLocation,
|
||||
} from "./GodotPckLocation.js";
|
||||
import type { GodotPckMetadata } from "../domain/artifactFormatMetadata.js";
|
||||
import { jsonValueByteLength } from "../domain/jsonValueByteLength.js";
|
||||
import { readFileHandleChunks } from "../filesystem/readFileHandleChunks.js";
|
||||
@@ -37,12 +42,23 @@ export class GodotPckArtifactReader implements ArtifactReader {
|
||||
readonly #entries = new Map<string, ArtifactEntry>();
|
||||
readonly #streams = new Set<Readable>();
|
||||
readonly #source: ArtifactFileSource;
|
||||
readonly #location: GodotPckLocation | undefined;
|
||||
#prepared: Promise<void> | undefined;
|
||||
#header: GodotPckMetadata | undefined;
|
||||
#closing = false;
|
||||
|
||||
constructor(path: string, admitted?: StableRegularFileDescriptor) {
|
||||
this.#source = new ArtifactFileSource(path, "Godot PCK", admitted);
|
||||
constructor(
|
||||
path: string,
|
||||
admitted?: StableRegularFileDescriptor | GodotPckAdmission,
|
||||
) {
|
||||
const admission =
|
||||
admitted !== undefined && "kind" in admitted ? admitted : undefined;
|
||||
this.#source = new ArtifactFileSource(
|
||||
path,
|
||||
"Godot PCK",
|
||||
admitted !== undefined && "kind" in admitted ? admitted.source : admitted,
|
||||
);
|
||||
this.#location = admission?.location;
|
||||
}
|
||||
|
||||
formatMetadata(): GodotPckMetadata | undefined {
|
||||
@@ -174,7 +190,12 @@ export class GodotPckArtifactReader implements ArtifactReader {
|
||||
|
||||
async #prepare(signal?: AbortSignal): Promise<void> {
|
||||
const { handle, initial } = await this.#source.read(signal);
|
||||
const header = await readGodotPckHeader(handle, initial.size, signal);
|
||||
const header = await readGodotPckHeader(
|
||||
handle,
|
||||
initial.size,
|
||||
signal,
|
||||
this.#location,
|
||||
);
|
||||
this.#header = header;
|
||||
if (header.directory_status !== "available") return;
|
||||
const cursor = new PckCursor(
|
||||
@@ -298,8 +319,10 @@ const readGodotPckHeader = async (
|
||||
handle: FileHandle,
|
||||
size: number,
|
||||
signal?: AbortSignal,
|
||||
admittedLocation?: GodotPckLocation,
|
||||
): Promise<GodotPckMetadata> => {
|
||||
const location = await locateGodotPck(handle, size, signal);
|
||||
const location =
|
||||
admittedLocation ?? (await locateGodotPck(handle, size, signal));
|
||||
if (location === undefined)
|
||||
throw formatFailure("no standalone header or executable trailer");
|
||||
const { start, end, embedded } = location;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { StableRegularFileDescriptor } from "../filesystem/RegularFile.js";
|
||||
import type { FileHandle } from "node:fs/promises";
|
||||
import {
|
||||
hasGodotPckHeader,
|
||||
@@ -13,6 +14,13 @@ export interface GodotPckLocation {
|
||||
readonly embedded: boolean;
|
||||
}
|
||||
|
||||
/** Located pack facts borrowed with the descriptor that produced them. */
|
||||
export interface GodotPckAdmission {
|
||||
readonly kind: "godot-pck";
|
||||
readonly source: StableRegularFileDescriptor;
|
||||
readonly location: GodotPckLocation;
|
||||
}
|
||||
|
||||
type DeclaredGodotPckTrailer = Extract<
|
||||
GodotPckTrailerFacts,
|
||||
{ readonly state: "declared" }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { GodotPckAdmission } from "./GodotPckLocation.js";
|
||||
import type { PyInstallerAdmission } from "./pyinstaller/PyInstallerAdmission.js";
|
||||
import type { NodeSeaAdmission } from "./nodeSea/NodeSeaAdmission.js";
|
||||
import type { AsarHeaderAdmission } from "../config/asarHeaderAdmission.js";
|
||||
@@ -35,6 +36,7 @@ interface ArtifactReaderConstruction {
|
||||
| StableRegularFileDescriptor
|
||||
| NodeSeaAdmission
|
||||
| PyInstallerAdmission
|
||||
| GodotPckAdmission
|
||||
| undefined;
|
||||
readonly asarHeaderAdmission?: AsarHeaderAdmission | undefined;
|
||||
/** Caller-selected root spelling; only ASAR companion lookup consumes this. */
|
||||
@@ -90,12 +92,19 @@ export function createArtifactReader(
|
||||
path,
|
||||
admission !== undefined &&
|
||||
"kind" in admission &&
|
||||
admission.kind === "pyinstaller"
|
||||
? rootSource
|
||||
: admission,
|
||||
admission.kind === "node-sea"
|
||||
? admission
|
||||
: rootSource,
|
||||
);
|
||||
case "godot-pck":
|
||||
return new GodotPckArtifactReader(path, rootSource);
|
||||
return new GodotPckArtifactReader(
|
||||
path,
|
||||
admission !== undefined &&
|
||||
"kind" in admission &&
|
||||
admission.kind === "godot-pck"
|
||||
? admission
|
||||
: rootSource,
|
||||
);
|
||||
case "mach-o-universal":
|
||||
return new MachOSliceArtifactReader(
|
||||
path,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { GodotPckAdmission } from "../GodotPckLocation.js";
|
||||
import type { PyInstallerAdmission } from "../pyinstaller/PyInstallerAdmission.js";
|
||||
import { assertArtifactSelectedSource } from "../ArtifactSelectedSource.js";
|
||||
import { artifactNameForClassification } from "../../domain/artifactNameSelection.js";
|
||||
@@ -53,6 +54,7 @@ type RootInventoryClassification = RootClassification & {
|
||||
| StableRegularFileDescriptor
|
||||
| NodeSeaAdmission
|
||||
| PyInstallerAdmission
|
||||
| GodotPckAdmission
|
||||
| undefined;
|
||||
};
|
||||
|
||||
@@ -208,6 +210,7 @@ const classifyRootFormat = async (
|
||||
| ArtifactOccurrence["artifact_format"]
|
||||
| NodeSeaAdmission
|
||||
| PyInstallerAdmission
|
||||
| GodotPckAdmission
|
||||
> => {
|
||||
const { handle } = source;
|
||||
const probe = createArtifactClassificationProbe(asarHeaderAdmission);
|
||||
@@ -261,7 +264,8 @@ const classifyRootFormat = async (
|
||||
(await hasDiskImageSignature(archiveBytes, read))
|
||||
)
|
||||
return "dmg";
|
||||
if (await embeddedProbes.godotPck(evidence.prefix)) return "godot-pck";
|
||||
const pck = await embeddedProbes.godotPck(evidence.prefix);
|
||||
if (pck !== undefined) return { kind: "godot-pck", source, location: pck };
|
||||
const fatEvidence = await readRootFatSliceHeaders(handle, evidence, signal);
|
||||
const mzWindowsHeaderSignature = await readRootMzWindowsHeaderSignature(
|
||||
handle,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { GodotPckAdmission } from "../GodotPckLocation.js";
|
||||
import type { PyInstallerAdmission } from "../pyinstaller/PyInstallerAdmission.js";
|
||||
import type { NodeSeaAdmission } from "../nodeSea/NodeSeaAdmission.js";
|
||||
import type { AsarHeaderAdmission } from "../../config/asarHeaderAdmission.js";
|
||||
@@ -23,7 +24,8 @@ export const createReader = (
|
||||
readerAdmission?:
|
||||
| StableRegularFileDescriptor
|
||||
| NodeSeaAdmission
|
||||
| PyInstallerAdmission,
|
||||
| PyInstallerAdmission
|
||||
| GodotPckAdmission,
|
||||
asarHeaderAdmission?: AsarHeaderAdmission,
|
||||
selectedPath?: string,
|
||||
): ArtifactReader | undefined => {
|
||||
|
||||
Reference in new issue
Block a user