quality: align libraries checks with shared process owner

This commit is contained in:
aphrody-dev committed 2026-10-13 18:57:04 +02:00
1 parent 090786e140
commit 89731c4f07
2 files changed
+20 -49

No files matched your search

+15 -45
View File
@@ -1,14 +1,6 @@
import { realpath } from "node:fs/promises";
import { join, relative, sep } from "node:path";
import {
checkoutPath,
findModule,
hashContent,
hashFile,
hashStream,
repositoryRoot,
type Config,
} from "./index.ts";
import { checkoutPath, findModule, hashContent, hashFile, hashStream, repositoryRoot, type Config } from "./index.ts";
export interface AssetEntry {
name: string;
@@ -55,24 +47,17 @@ export interface AssetPlan {
const maxAssetBytes = 128 * 1024 * 1024;
const maxContentObjectBytes = 256 * 1024 * 1024;
export function validateAssetReference(
value: unknown,
baseUrl = "https://assets.aphrody.app",
): AssetReference {
if (!value || typeof value !== "object")
throw new Error("An immutable shared asset reference is required.");
export function validateAssetReference(value: unknown, baseUrl = "https://assets.aphrody.app"): AssetReference {
if (!value || typeof value !== "object") throw new Error("An immutable shared asset reference is required.");
const record = value as { reference?: unknown };
const reference = (record.reference ?? value) as Partial<AssetReference>;
if (!reference || typeof reference !== "object")
throw new Error("An immutable shared asset reference is required.");
if (!reference || typeof reference !== "object") throw new Error("An immutable shared asset reference is required.");
if (
typeof reference.sha256 !== "string" ||
!/^[0-9a-f]{64}$/.test(reference.sha256) ||
reference.url !== `${baseUrl}/manifests/sha256/${reference.sha256}.json`
) {
throw new Error(
"Shared assets require the canonical custom domain and pinned manifest digest.",
);
throw new Error("Shared assets require the canonical custom domain and pinned manifest digest.");
}
return { sha256: reference.sha256, url: reference.url };
}
@@ -83,38 +68,26 @@ export async function planSharedAssets(config: Config, root = repositoryRoot): P
const module = findModule(config, "craft-fonts");
const directory = await realpath(checkoutPath(root, module.path));
const manifestText = await Bun.file(join(directory, "fonts", "manifest.txt")).text();
const rows = manifestText.split(/\r?\n/).filter((line) => line.trim() && !line.startsWith("#"));
const rows = manifestText.split(/\r?\n/).filter(line => line.trim() && !line.startsWith("#"));
if (rows.length > 128) throw new Error("Public font manifest exceeds its entry limit.");
const entries: AssetEntry[] = [];
const files = new Map<string, string>();
let bytes = 0;
const add = async (
name: string,
license: string,
source: string,
expected?: string,
): Promise<AssetEntry> => {
const add = async (name: string, license: string, source: string, expected?: string): Promise<AssetEntry> => {
const path = await realpath(checkoutPath(directory, name));
const local = relative(directory, path);
if (local.startsWith(`..${sep}`) || local === "..")
throw new Error("Public asset path leaves its owner.");
if (local.startsWith(`..${sep}`) || local === "..") throw new Error("Public asset path leaves its owner.");
const file = Bun.file(path);
if (!file.size || file.size > maxAssetBytes)
throw new Error(`Public asset size invalid: ${name}`);
if (!file.size || file.size > maxAssetBytes) throw new Error(`Public asset size invalid: ${name}`);
const sha256 = await hashFile(path, { maxBytes: maxAssetBytes });
if (expected && sha256 !== expected)
throw new Error(`Font differs from its owner manifest: ${name}`);
if (expected && sha256 !== expected) throw new Error(`Font differs from its owner manifest: ${name}`);
const key = `sha256/${sha256}`;
if (!files.has(key)) {
files.set(key, path);
bytes += file.size;
}
if (bytes > 256 * 1024 * 1024) throw new Error("Public asset plan exceeds its size limit.");
const type = name.endsWith(".ttf")
? "font/ttf"
: name.endsWith(".otf")
? "font/otf"
: "text/plain; charset=utf-8";
const type = name.endsWith(".ttf") ? "font/ttf" : name.endsWith(".otf") ? "font/otf" : "text/plain; charset=utf-8";
const entry: AssetEntry = { name, key, sha256, size: file.size, type, license, source };
entries.push(entry);
return entry;
@@ -154,7 +127,7 @@ export async function publishSharedAssets(
): Promise<{ uploaded: number; retained: number }> {
let uploaded = 0;
let retained = 0;
const objects = new Map(plan.manifest.entries.map((entry) => [entry.key, entry]));
const objects = new Map(plan.manifest.entries.map(entry => [entry.key, entry]));
for (const [key, path] of plan.files) {
const entry = objects.get(key)!;
if ((await hashFile(path, { maxBytes: maxAssetBytes })) !== entry.sha256)
@@ -175,12 +148,10 @@ export async function publishSharedAssets(
}
}
const body = JSON.stringify(plan.manifest);
if (hashContent(body) !== plan.reference.sha256)
throw new Error("Shared asset manifest changed after planning.");
if (hashContent(body) !== plan.reference.sha256) throw new Error("Shared asset manifest changed after planning.");
const remote = client.file(`manifests/sha256/${plan.reference.sha256}.json`);
if (await remote.exists()) {
if (hashContent(await remote.text()) !== plan.reference.sha256)
throw new Error("Immutable R2 manifest differs.");
if (hashContent(await remote.text()) !== plan.reference.sha256) throw new Error("Immutable R2 manifest differs.");
retained++;
} else {
await remote.write(body, { type: "application/json", retry: 1 });
@@ -273,8 +244,7 @@ export async function publishContentObject(
maxBytes: expected.bytes,
expectedBytes: expected.bytes,
});
if (digest !== expected.sha256)
throw new Error("Published R2 asset does not match its content address.");
if (digest !== expected.sha256) throw new Error("Published R2 asset does not match its content address.");
return {
...expected,
objectKey,
+5 -4
View File
@@ -2,14 +2,15 @@
import { resolve } from "node:path";
import { runProcess } from "../packages/workspace/src/process.ts";
const checks = ["doctor", "typecheck", "lint", "format:check"];
const root = resolve(import.meta.dir, "..");
const checks = ["typecheck", "lint", "format:check"];
for (const check of checks) {
const child = await runProcess([process.execPath, "run", "--bun", check], resolve(import.meta.dir, ".."), {
timeoutMs: 120_000,
const result = await runProcess([process.execPath, "run", "--bun", check], root, {
timeoutMs: 300_000,
maxOutputBytes: 64 * 1024 * 1024,
stdin: "inherit",
stdout: "inherit",
stderr: "inherit",
});
if (child.code !== 0) process.exit(child.code);
if (result.code !== 0) process.exit(result.code);
}