ci: upload each run's screenshots + meta.json to R2 (runs/pcbjam/<run-id>/)

Durable per-run screenshot store for the morelli review app
(github.com/PCBJam/morelli): after the report step, CI uploads the renders
and a meta.json index (identity, branch/commit, per-shot sha256+dims,
embedded compare summary) to runs/pcbjam/<GITHUB_RUN_ID>/ — 30-day R2
lifecycle; GH artifacts remain the debugging archive. meta.json is written
last as the upload-complete marker. Uses a new optional WRITE keypair
(CI_SCREENSHOTS_S3_WRITE_*); without it the step no-ops, so secretless
callers stay green. R2Store gains putKey() for the non-CAS run keys.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Istvan Matejcsok 2026-08-19 11:45:40 +02:00
commit 4196958c10
4 changed files with 192 additions and 0 deletions

View file

@ -98,6 +98,21 @@ export class R2Store {
await res.arrayBuffer().catch(() => undefined);
return 'uploaded';
}
/**
* Upload to an ARBITRARY key (the per-run uploads under runs/…, consumed by
* the morelli review app) — unlike put(), not content-addressed and always
* overwrites (workflow re-runs reuse the run id; last attempt wins).
*/
async putKey(key: string, bytes: Buffer, contentType: string): Promise<void> {
const res = await this.fetchWithRetry(`${this.base}/${key}`, {
method: 'PUT',
body: bytes as unknown as BodyInit,
headers: { 'content-type': contentType },
});
if (res.status !== 200) throw new Error(`PUT ${key} → HTTP ${res.status}`);
await res.arrayBuffer().catch(() => undefined);
}
}
let envFileLoaded = false;