Skip to content

feat: CLI backend auth and same-account iroh auto-trust #2408

feat: CLI backend auth and same-account iroh auto-trust

feat: CLI backend auth and same-account iroh auto-trust #2408

Workflow file for this run

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 })