feat: CLI backend auth and same-account iroh auto-trust #2408
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: PR Metrics | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number }} | |
| cancel-in-progress: true | |
| jobs: | |
| metrics: | |
| runs-on: ubuntu-latest | |
| # 20min cap matches ci.yml `typescript` job — bounds the whole run (coverage | |
| # test, size-limit build, Render preview wait, Lighthouse) so a hang can't | |
| # burn GitHub's 6h default. | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| with: | |
| fetch-depth: 0 | |
| - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: 1.3.13 | |
| - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 | |
| with: | |
| path: | | |
| ~/.bun/install/cache | |
| node_modules | |
| key: bun-${{ hashFiles('bun.lock') }} | |
| restore-keys: bun- | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| # --- Line count (excludes tests, migrations, lockfiles) --- | |
| - name: Compute line count | |
| id: lines | |
| run: | | |
| git fetch origin ${{ github.base_ref }} | |
| STATS=$(git diff --numstat origin/${{ github.base_ref }}...HEAD -- \ | |
| ':!*.test.ts' ':!*.test.tsx' ':!*.spec.ts' ':!*.spec.tsx' \ | |
| ':!backend/drizzle/**' ':!bun.lock' ':!*.lock') | |
| ADDED=$(echo "$STATS" | awk '{sum += $1} END {print sum+0}') | |
| REMOVED=$(echo "$STATS" | awk '{sum += $2} END {print sum+0}') | |
| echo "added=$ADDED" >> $GITHUB_OUTPUT | |
| echo "removed=$REMOVED" >> $GITHUB_OUTPUT | |
| # --- Bundle size (gzipped, via size-limit) --- | |
| - name: Build | |
| id: build | |
| run: bun run build | |
| continue-on-error: true | |
| env: | |
| NODE_ENV: production | |
| NODE_OPTIONS: --max-old-space-size=4096 | |
| - name: Run size-limit | |
| id: bundle | |
| if: steps.build.outcome == 'success' | |
| run: | | |
| # size-limit --json outputs gzipped sizes, which is what users actually download. | |
| # We extract the first entry's gzip value and save it for the metrics comment. | |
| bunx size-limit --json > /tmp/size-limit.json 2>/dev/null || true | |
| GZIP=$(jq -r '.[0].size // 0' /tmp/size-limit.json 2>/dev/null || echo 0) | |
| echo "gzip=${GZIP}" >> $GITHUB_OUTPUT | |
| # --- Test coverage --- | |
| - name: Run tests with coverage | |
| id: coverage | |
| run: | | |
| # No per-test timeout in CI (large value ≈ disabled — bun `--timeout 0` hangs async tests): | |
| # the job-level 20min cap is the real ceiling. Local `test` keeps the 5s timeout. | |
| bun test --cwd=src --timeout 3600000 --coverage 2>&1 | tee /tmp/coverage.txt || true | |
| TOTAL=$(grep -E "All files" /tmp/coverage.txt | grep -oE '[0-9]+\.[0-9]+' | head -1) | |
| echo "total=${TOTAL:-N/A}" >> $GITHUB_OUTPUT | |
| continue-on-error: true | |
| # --- Restore main baseline for deltas --- | |
| - name: Restore main baseline | |
| uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 | |
| with: | |
| path: .metrics-baseline | |
| key: pr-metrics-main-${{ github.event.pull_request.base.sha }} | |
| restore-keys: pr-metrics-main- | |
| # --- Lighthouse (load time via Render PR preview) --- | |
| # Render auto-deploys a preview at thunderbolt-pr-{number}.onrender.com for every PR. | |
| # We poll until it's ready (up to 5 minutes), then run Lighthouse against it. | |
| - name: Wait for Render preview | |
| id: preview | |
| run: | | |
| URL="https://thunderbolt-pr-${{ github.event.pull_request.number }}.onrender.com" | |
| echo "url=$URL" >> $GITHUB_OUTPUT | |
| for i in $(seq 1 30); do | |
| STATUS=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$URL" 2>/dev/null) | |
| if [ "$STATUS" = "200" ]; then | |
| echo "ready=true" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| echo "Attempt $i/30: HTTP $STATUS — retrying in 10s..." | |
| sleep 10 | |
| done | |
| echo "ready=false" >> $GITHUB_OUTPUT | |
| continue-on-error: true | |
| - name: Run Lighthouse (collect + upload) | |
| id: lighthouse | |
| if: steps.preview.outputs.ready == 'true' | |
| uses: treosh/lighthouse-ci-action@3e7e23fb74242897f95c0ba9cabad3d0227b9b18 # v12 | |
| with: | |
| urls: ${{ steps.preview.outputs.url }} | |
| configPath: ./lighthouserc.js | |
| uploadArtifacts: false | |
| continue-on-error: true | |
| - name: Run Lighthouse assertions | |
| id: lh_assert | |
| if: hashFiles('.lighthouseci/*.report.json') != '' | |
| run: | | |
| # Run assertions separately so we can capture warnings as text. | |
| # The action already ran collect+upload; we just need assert. | |
| bunx @lhci/cli@0.15.x assert --config=./lighthouserc.js 2>&1 | tee /tmp/lhci-assert.txt || true | |
| # Extract only warning lines for the PR comment. | |
| WARNINGS=$(grep -E '^\s*warning' /tmp/lhci-assert.txt | sed 's/^\s*//' || true) | |
| # Store as multiline output | |
| { | |
| echo 'warnings<<LHCI_EOF' | |
| echo "$WARNINGS" | |
| echo 'LHCI_EOF' | |
| } >> $GITHUB_OUTPUT | |
| continue-on-error: true | |
| - name: Parse Lighthouse results | |
| id: lh | |
| if: hashFiles('.lighthouseci/*.report.json') != '' | |
| run: | | |
| # With multiple runs, pick the median report (middle file by name). | |
| REPORTS=($(ls .lighthouseci/*.report.json 2>/dev/null | sort)) | |
| COUNT=${#REPORTS[@]} | |
| if [ "$COUNT" -eq 0 ]; then exit 0; fi | |
| MEDIAN_IDX=$(( COUNT / 2 )) | |
| REPORT="${REPORTS[$MEDIAN_IDX]}" | |
| echo "perf=$(jq -r '.categories.performance.score * 100 | floor' "$REPORT")" >> $GITHUB_OUTPUT | |
| echo "a11y=$(jq -r '.categories.accessibility.score * 100 | floor' "$REPORT")" >> $GITHUB_OUTPUT | |
| echo "bp=$(jq -r '.categories["best-practices"].score * 100 | floor' "$REPORT")" >> $GITHUB_OUTPUT | |
| echo "seo=$(jq -r '.categories.seo.score * 100 | floor' "$REPORT")" >> $GITHUB_OUTPUT | |
| echo "fcp=$(jq -r '.audits["first-contentful-paint"].displayValue' "$REPORT")" >> $GITHUB_OUTPUT | |
| echo "lcp=$(jq -r '.audits["largest-contentful-paint"].displayValue' "$REPORT")" >> $GITHUB_OUTPUT | |
| echo "tbt=$(jq -r '.audits["total-blocking-time"].displayValue' "$REPORT")" >> $GITHUB_OUTPUT | |
| echo "cls=$(jq -r '.audits["cumulative-layout-shift"].displayValue' "$REPORT")" >> $GITHUB_OUTPUT | |
| # Extract temporary public storage URL from LHCI output | |
| LINKS=$(cat .lighthouseci/links.json 2>/dev/null || echo '{}') | |
| REPORT_URL=$(echo "$LINKS" | jq -r 'to_entries[0].value // empty' 2>/dev/null) | |
| echo "report_url=${REPORT_URL}" >> $GITHUB_OUTPUT | |
| continue-on-error: true | |
| # --- Post or update the metrics comment --- | |
| - name: Post metrics comment | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 | |
| env: | |
| LINES_ADDED: ${{ steps.lines.outputs.added }} | |
| LINES_REMOVED: ${{ steps.lines.outputs.removed }} | |
| BUNDLE_GZIP: ${{ steps.bundle.outputs.gzip }} | |
| COVERAGE: ${{ steps.coverage.outputs.total }} | |
| BUILD_OUTCOME: ${{ steps.build.outcome }} | |
| PREVIEW_URL: ${{ steps.preview.outputs.url }} | |
| PREVIEW_READY: ${{ steps.preview.outputs.ready }} | |
| LH_PERF: ${{ steps.lh.outputs.perf }} | |
| LH_A11Y: ${{ steps.lh.outputs.a11y }} | |
| LH_BP: ${{ steps.lh.outputs.bp }} | |
| LH_SEO: ${{ steps.lh.outputs.seo }} | |
| LH_FCP: ${{ steps.lh.outputs.fcp }} | |
| LH_LCP: ${{ steps.lh.outputs.lcp }} | |
| LH_TBT: ${{ steps.lh.outputs.tbt }} | |
| LH_CLS: ${{ steps.lh.outputs.cls }} | |
| LH_REPORT_URL: ${{ steps.lh.outputs.report_url }} | |
| LH_WARNINGS: ${{ steps.lh_assert.outputs.warnings }} | |
| with: | |
| script: | | |
| const script = require('./.github/scripts/post-pr-metrics.cjs') | |
| await script({ github, context }) |