Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207import { createHash } from "node:crypto";import { execFile as execFileCallback } from "node:child_process";import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";import { tmpdir } from "node:os";import { join } from "node:path";import { promisify } from "node:util";
const execFile = promisify(execFileCallback);const args = process.argv.slice(2).filter((arg) => arg !== "--");const bucket = args .find((arg) => arg.startsWith("--bucket=")) ?.slice("--bucket=".length);if (!bucket || args.some((arg) => arg === "--bucket=")) throw new Error( "Usage: node scripts/publish-release-artifacts.mjs --bucket=<private R2 bucket>", );if (args.some((arg) => !arg.startsWith("--bucket="))) throw new Error("Unexpected publish argument");
const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");const identicalIdentity = (left, right) => left?.version === right.version && left?.sourceRevision === right.sourceRevision && left?.artifactDigest === right.artifactDigest;const releaseKey = (digest, path) => `releases/${digest}/${path}`;const identityKey = (version) => `releases/versions/${encodeURIComponent(version)}.json`;const validateIdentity = (value) => { if ( !value || typeof value.version !== "string" || typeof value.sourceRevision !== "string" || !/^[a-f0-9]{64}$/.test(value.artifactDigest) ) throw new Error("Stored release identity is invalid"); return value;};
const temporary = await mkdtemp(join(tmpdir(), "flarebot-release-r2-"));let serial = 0;const objectGet = async (key) => { const output = join(temporary, String(serial++)); try { await execFile("pnpm", [ "exec", "wrangler", "r2", "object", "get", `${bucket}/${key}`, "--remote", `--file=${output}`, ]); return readFile(output); } catch (error) { const text = `${error.stdout ?? ""}\n${error.stderr ?? ""}\n${error.message ?? ""}`; if (/NoSuchKey|not found|does not exist/i.test(text)) return null; throw new Error(`Cannot read private release object ${key}: ${text}`); }};const objectPut = async (key, file) => { await execFile("pnpm", [ "exec", "wrangler", "r2", "object", "put", `${bucket}/${key}`, "--remote", `--file=${file}`, ]);};const verify = (key, bytes, expected) => { if ( !bytes || bytes.length !== expected.size || sha256(bytes) !== expected.sha256 ) throw new Error(`R2 verification failed for ${key}`);};const ensureImmutable = async (key, file, expected) => { let stored = await objectGet(key); if (stored) { verify(key, stored, expected); return; } await objectPut(key, file); stored = await objectGet(key); verify(key, stored, expected);};
try { const manifestPath = "dist/release/manifest.json"; const manifestBytes = await readFile(manifestPath); const manifest = JSON.parse(manifestBytes); const identity = validateIdentity({ version: manifest.release, sourceRevision: manifest.sourceRevision, artifactDigest: sha256(manifestBytes), }); if (manifest.sourceDirty) throw new Error("Refusing to publish a dirty customer artifact"); const versionObject = identityKey(identity.version); const publishedForVersion = await objectGet(versionObject); if ( publishedForVersion && !identicalIdentity( validateIdentity(JSON.parse(publishedForVersion)), identity, ) ) throw new Error( "A human release version is already published with a different digest", ); const publishedLatest = await objectGet("releases/latest.json"); if ( publishedLatest && validateIdentity(JSON.parse(publishedLatest)).version === identity.version && !identicalIdentity(validateIdentity(JSON.parse(publishedLatest)), identity) ) throw new Error( "The currently published release reuses this human version with a different digest", );
await ensureImmutable( releaseKey(identity.artifactDigest, "manifest.json"), manifestPath, { size: manifestBytes.length, sha256: identity.artifactDigest, }, ); for (const file of manifest.files) { const bytes = await readFile(`dist/release/${file.path}`); verify(file.path, bytes, file); await ensureImmutable( releaseKey(identity.artifactDigest, file.path), `dist/release/${file.path}`, file, ); }
const identityBytes = Buffer.from(`${JSON.stringify(identity)}\n`); const pointer = join(temporary, "identity.json"); await writeFile(pointer, identityBytes); const pointerExpected = { size: identityBytes.length, sha256: sha256(identityBytes), }; // R2 has no conditional write through this CLI, so publication must be // serialized by the operator: one publisher per human version at a time. // Every write below is re-read and verified, so a concurrent writer turns // into a loud verification failure instead of a silent overwrite. The only // residue a precise same-version interleave can leave is a version record // and latest pointer that disagree; installations consume exact pinned // identities, and the next publication reconciles the pointers. await objectPut(versionObject, pointer); verify(versionObject, await objectGet(versionObject), pointerExpected); // Never rewind another publisher's latest pointer. Re-read it and advance // only when it still contains what was observed before the upload above. const latestBeforeWrite = await objectGet("releases/latest.json"); const latestChanged = (publishedLatest === null) !== (latestBeforeWrite === null) || (publishedLatest !== null && latestBeforeWrite !== null && !latestBeforeWrite.equals(publishedLatest)); if (latestChanged) throw new Error( "The published latest release changed during publication; reconcile explicitly instead of overwriting it", ); if ( !latestBeforeWrite || !identicalIdentity( validateIdentity(JSON.parse(latestBeforeWrite)), identity, ) ) { await objectPut("releases/latest.json", pointer); verify( "releases/latest.json", await objectGet("releases/latest.json"), pointerExpected, ); } // Final gate before reporting success: re-read both pointers so an // interleaved writer is reported instead of acknowledged. verify(versionObject, await objectGet(versionObject), pointerExpected); const settledLatest = await objectGet("releases/latest.json"); const latestSettledWell = settledLatest !== null && (identicalIdentity( validateIdentity(JSON.parse(settledLatest)), identity, ) || (publishedLatest !== null && settledLatest.equals(publishedLatest))); if (!latestSettledWell) throw new Error( "The published latest release changed during publication; reconcile explicitly instead of overwriting it", ); console.log( `Verified immutable R2 release ${identity.version} (${identity.artifactDigest})`, );} finally { await rm(temporary, { recursive: true, force: true });}