-
Notifications
You must be signed in to change notification settings - Fork 321
188 lines (171 loc) · 8.04 KB
/
Copy pathpr-metrics.yml
File metadata and controls
188 lines (171 loc) · 8.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.13
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
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@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
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 })