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

@ -50,6 +50,13 @@ on:
required: false
CI_SCREENSHOTS_S3_SECRET_ACCESS_KEY:
required: false
# WRITE keypair for the per-run screenshot uploads (runs/pcbjam/<run-id>/,
# 30-day R2 lifecycle) consumed by the morelli review app. Optional for the
# same reason: without it the upload-run step just no-ops.
CI_SCREENSHOTS_S3_WRITE_ACCESS_KEY_ID:
required: false
CI_SCREENSHOTS_S3_WRITE_SECRET_ACCESS_KEY:
required: false
jobs:
build-and-test:
@ -479,6 +486,27 @@ jobs:
npm run screenshots:check
npm run screenshots:report -- --e2e "$E2E"
# Durable per-run screenshot store for the morelli review app: upload this
# run's renders + meta.json to runs/pcbjam/<run-id>/ in the R2 bucket
# (30-day lifecycle rule — GH artifacts stay the debugging archive, R2 is
# the promotion source). Runs AFTER the report step so compare.ts's
# report.json exists to embed. Needs the WRITE keypair (mapped onto the
# standard env names for this step only); without it the tool no-ops, so
# forks/secretless callers stay green. Never blocks a build.
- name: Upload run screenshots to R2
if: inputs.run_tests && !cancelled() && steps.e2e.outcome != 'skipped'
continue-on-error: true
working-directory: tests
env:
CI_SCREENSHOTS_S3_ENDPOINT: ${{ vars.CI_SCREENSHOTS_S3_ENDPOINT }}
CI_SCREENSHOTS_S3_ACCESS_KEY_ID: ${{ secrets.CI_SCREENSHOTS_S3_WRITE_ACCESS_KEY_ID }}
CI_SCREENSHOTS_S3_SECRET_ACCESS_KEY: ${{ secrets.CI_SCREENSHOTS_S3_WRITE_SECRET_ACCESS_KEY }}
run: |
E2E=pass
{ [ "${{ steps.e2e.outcome }}" = "success" ] \
&& [ "${{ steps.web_e2e.outcome }}" = "success" ]; } || E2E=fail
npm run screenshots:upload-run -- --e2e "$E2E"
# FALLBACK on failure: a minimal text-only "CI failed" notice, only when the
# rich screenshot report above did NOT post (build broke before the tests →
# report skipped, or the report itself errored). An e2e-only failure already