feat(catalog): verify deterministic release manifests before Git export
This commit is contained in:
1 parent
422be3ce2d
commit
a320e47c29
9 files changed
+491
-9
No files matched your search
@@ -1,6 +1,14 @@
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import type { RowDataPacket } from "mysql2";
|
||||
import {
|
||||
digestFile,
|
||||
sourceFingerprint,
|
||||
} from "@/features/catalog/releases/files";
|
||||
import {
|
||||
CATALOG_RELEASE_MANIFEST_PATH,
|
||||
createReleaseManifest,
|
||||
} from "@/features/catalog/releases/manifest";
|
||||
import { db } from "@/lib/db";
|
||||
import { getEffectMapPath } from "@/lib/services/effectmap";
|
||||
import { getFigureDataPath } from "@/lib/services/figuredata";
|
||||
@@ -133,16 +141,13 @@ export async function createCatalogSnapshot(
|
||||
}
|
||||
const files: CatalogFile[] = [];
|
||||
const fingerprints = new Map<string, string>();
|
||||
const fingerprint = async (source: string) => {
|
||||
const stat = await fs.lstat(source);
|
||||
if (!stat.isFile() || stat.isSymbolicLink())
|
||||
throw new Error("Invalid catalog source");
|
||||
return `${stat.size}:${stat.mtimeMs}:${stat.ctimeMs}`;
|
||||
};
|
||||
const copiedHashes = new Map<string, string>();
|
||||
const fingerprint = sourceFingerprint;
|
||||
for (const [target, source] of sources) {
|
||||
fingerprints.set(source, await fingerprint(source));
|
||||
const copy = path.join(directory, String(files.length));
|
||||
await fs.copyFile(source, copy);
|
||||
copiedHashes.set(source, (await digestFile(copy)).sha256);
|
||||
if (target.endsWith(".json")) JSON.parse(await fs.readFile(copy, "utf8"));
|
||||
files.push({ source: copy, target });
|
||||
}
|
||||
@@ -210,8 +215,22 @@ export async function createCatalogSnapshot(
|
||||
connection.release();
|
||||
}
|
||||
for (const [source, stamp] of fingerprints) {
|
||||
if ((await fingerprint(source)) !== stamp)
|
||||
if (
|
||||
(await fingerprint(source)) !== stamp ||
|
||||
(await digestFile(source)).sha256 !== copiedHashes.get(source)
|
||||
)
|
||||
throw new Error("Assets changed during export; retry required");
|
||||
}
|
||||
const entries = [];
|
||||
for (const file of files) {
|
||||
const { bytes, sha256 } = await digestFile(file.source);
|
||||
entries.push({ path: file.target, bytes, sha256 });
|
||||
}
|
||||
const manifest = createReleaseManifest(entries);
|
||||
const manifestSource = path.join(directory, "catalog-release.json");
|
||||
await fs.writeFile(manifestSource, `${JSON.stringify(manifest, null, 2)}\n`, {
|
||||
flag: "wx",
|
||||
});
|
||||
files.push({ source: manifestSource, target: CATALOG_RELEASE_MANIFEST_PATH });
|
||||
return files;
|
||||
}
|
||||
Reference in new issue
Block a user