1010 - main
1111
1212jobs :
13- test :
14- name : Test Suite
13+ lint :
14+ name : Lint
1515 runs-on : ubuntu-latest
16+ if : ${{ !startsWith(github.head_ref || github.ref_name, 'hotfix/') }}
17+
18+ steps :
19+ - name : Checkout repository
20+ uses : actions/checkout@v4
21+
22+ - name : Setup Node.js
23+ uses : actions/setup-node@v4
24+ with :
25+ node-version : ' 22.x'
26+ cache : ' npm'
27+
28+ - name : Install dependencies
29+ run : npm ci --legacy-peer-deps
30+
31+ - name : Sync Astro types
32+ run : npm run sync
33+
34+ - name : Run Astro check
35+ run : npm run check
36+
37+ - name : Run lint
38+ run : npm run lint:base
39+
40+ - name : Run Actions lint
41+ run : npm run lint:actions
42+
43+ unit-test :
44+ name : Unit Tests
45+ runs-on : ubuntu-latest
46+ needs : lint
47+ if : ${{ !startsWith(github.head_ref || github.ref_name, 'hotfix/') }}
48+
49+ steps :
50+ - name : Checkout repository
51+ uses : actions/checkout@v4
52+
53+ - name : Setup Node.js
54+ uses : actions/setup-node@v4
55+ with :
56+ node-version : ' 22.x'
57+ cache : ' npm'
58+
59+ - name : Install dependencies
60+ run : npm ci --legacy-peer-deps
61+
62+ - name : Sync Astro types
63+ run : npm run sync
64+
65+ - name : Run Astro check
66+ run : npm run check
67+
68+ - name : Run unit tests (Vitest — coverage with GitHub Actions reporter)
69+ id : vitest
70+ run : npm run test:coverage
71+
72+ - name : Report Coverage
73+ if : steps.vitest.outcome == 'success' && hashFiles('coverage/coverage-summary.json') != ''
74+ uses : davelosert/vitest-coverage-report-action@v2.9.0
75+ with :
76+ json-summary-path : ' ./coverage/coverage-summary.json'
77+ json-final-path : ' ./coverage/coverage-final.json'
78+
79+ - name : Upload test coverage
80+ if : always() && hashFiles('coverage/coverage-summary.json') != ''
81+ uses : actions/upload-artifact@v5
82+ with :
83+ name : test-coverage
84+ path : coverage/
85+ retention-days : 30
86+
87+ e2e-test :
88+ name : E2E Tests
89+ runs-on : ubuntu-latest
90+ needs : unit-test
91+ if : ${{ !startsWith(github.head_ref || github.ref_name, 'hotfix/') }}
1692
1793 env :
1894 CONVERTKIT_API_KEY : ${{ secrets.CONVERTKIT_API_KEY }}
@@ -38,79 +114,32 @@ jobs:
38114 - name : Checkout repository
39115 uses : actions/checkout@v4
40116
41- - name : Determine hotfix skip
42- id : branch-filter
43- shell : bash
44- run : |
45- ref="${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}"
46- if [[ "$ref" == hotfix/* ]]; then
47- echo "skip_tests=true" >> "$GITHUB_OUTPUT"
48- else
49- echo "skip_tests=false" >> "$GITHUB_OUTPUT"
50- fi
51-
52- - name : Skip notice for hotfix branch
53- if : steps.branch-filter.outputs.skip_tests == 'true'
54- run : |
55- echo "hotfix/* branch detected; skipping lint and test steps."
56-
57117 - name : Setup Node.js
58- if : steps.branch-filter.outputs.skip_tests != 'true'
59118 uses : actions/setup-node@v4
60119 with :
61120 node-version : ' 22.x'
62121 cache : ' npm'
63122
64123 - name : Install dependencies
65- if : steps.branch-filter.outputs.skip_tests != 'true'
66124 run : npm ci --legacy-peer-deps
67125
68- - name : Sync Astro types
69- if : steps.branch-filter.outputs.skip_tests != 'true'
70- run : npm run sync
71-
72- - name : Run Astro check
73- if : steps.branch-filter.outputs.skip_tests != 'true'
74- run : npm run check
75-
76- - name : Run lint
77- if : steps.branch-filter.outputs.skip_tests != 'true'
78- run : npm run lint
79-
80- - name : Run unit tests (Vitest — coverage with GitHub Actions reporter)
81- if : steps.branch-filter.outputs.skip_tests != 'true'
82- id : vitest
83- run : |
84- npm run test:coverage
85-
86- - name : Report Coverage
87- if : steps.branch-filter.outputs.skip_tests != 'true' && steps.vitest.outcome == 'success' && hashFiles('coverage/coverage-summary.json') != ''
88- uses : davelosert/vitest-coverage-report-action@v2.9.0
89- with :
90- json-summary-path : ' ./coverage/coverage-summary.json'
91- json-final-path : ' ./coverage/coverage-final.json'
92-
93126 - name : Install Playwright browsers
94- if : steps.branch-filter.outputs.skip_tests != 'true'
95127 run : npx playwright install --with-deps
96128
97129 - name : Set up Docker Buildx
98- if : steps.branch-filter.outputs.skip_tests != 'true'
99130 uses : docker/setup-buildx-action@v3.11.1
100131
101132 - name : Build Upstash mock image
102- if : steps.branch-filter.outputs.skip_tests != 'true'
103133 run : docker buildx build --load -t wb/upstash-redis-local:test test/containers/upstash/local-proxy
104134
105135 - name : Generate container env file
106- if : steps.branch-filter.outputs.skip_tests != 'true'
107136 shell : bash
108137 run : |
109138 set -euo pipefail
110139 mkdir -p test/containers
111140 : "${SUPABASE_SERVICE_ROLE_KEY:?SUPABASE_SERVICE_ROLE_KEY secret is required}"
112141
113- cat <<EOF > test/containers/.env
142+ cat <<EOF_ENV > test/containers/.env
114143 COMPOSE_PROJECT_NAME=wb-e2e
115144 CONVERTKIT_HTTP_PORT=9010
116145 RESEND_HTTP_PORT=9011
@@ -120,32 +149,93 @@ jobs:
120149 SUPABASE_HEALTH_TIMEOUT=${SUPABASE_HEALTH_TIMEOUT:-240}
121150 SUPABASE_URL=${SUPABASE_URL:-http://127.0.0.1:54321}
122151 SUPABASE_SERVICE_ROLE_KEY=${SUPABASE_SERVICE_ROLE_KEY}
123- EOF
152+ EOF_ENV
124153
125154 - name : Start mock containers
126- if : steps.branch-filter.outputs.skip_tests != 'true'
127155 run : npm run containers:up
128156
129157 - name : Wait for mock services
130- if : steps.branch-filter.outputs.skip_tests != 'true'
131158 run : npm run containers:wait
132159
133160 - name : Start Supabase stack
134- if : steps.branch-filter.outputs.skip_tests != 'true'
135161 run : npm run containers:supabase:start
136162
137163 - name : Apply Supabase migrations (local container)
138- if : steps.branch-filter.outputs.skip_tests != 'true'
139- run : npm run containers:supabase:db-push
164+ run : |
165+ set -euo pipefail
166+
167+ echo "::group::Supabase migrations"
168+ npm run containers:supabase:db-push
169+ echo "::endgroup::"
170+
171+ echo "::group::Supabase schema snapshot"
172+ FORCE_COLOR=1 dotenv -e test/containers/.env -- bash -c 'npx supabase db dump --local --schema-only --workdir suprabase' > /tmp/supabase-schema.sql
173+ head -n 200 /tmp/supabase-schema.sql || true
174+ echo "(full schema saved to /tmp/supabase-schema.sql)"
175+ echo "::endgroup::"
176+
177+ echo "::group::Restart Supabase REST container"
178+ bash -euo pipefail -c '
179+ project_id=$(grep -m1 "^project_id" suprabase/config.toml | awk -F '"' "{print \$2}")
180+ if [ -z "${project_id}" ]; then
181+ echo "Unable to determine Supabase project_id" >&2
182+ exit 1
183+ fi
184+ sanitize() {
185+ echo "$1" | tr "[:upper:]" "[:lower:]" | sed "s/[^a-z0-9]/-/g"
186+ }
187+ for candidate in "${project_id}" "$(sanitize "${project_id}")"; do
188+ rest_container=$(docker ps --filter "label=com.docker.compose.project=${candidate}" --format "{{.ID}} {{.Names}}" | grep -E "rest" | head -n 1 || true)
189+ if [ -n "${rest_container}" ]; then
190+ container_id=$(echo "${rest_container}" | awk "{print \$1}")
191+ container_name=$(echo "${rest_container}" | awk "{print \$2}")
192+ echo "Restarting ${container_name} (${container_id}) to refresh schema cache"
193+ docker restart "${container_id}"
194+ break
195+ fi
196+ done
197+ if [ -z "${rest_container:-}" ]; then
198+ echo "Supabase REST container not found; skipping restart" >&2
199+ fi
200+ '
201+ echo "::endgroup::"
202+
203+ echo "::group::Supabase container logs (last 200 lines)"
204+ bash -euo pipefail -c '
205+ project_id=$(grep -m1 "^project_id" suprabase/config.toml | awk -F '"' "{print \$2}")
206+ if [ -z "${project_id}" ]; then
207+ echo "Unable to determine Supabase project_id" >&2
208+ exit 1
209+ fi
210+ sanitize() {
211+ echo "$1" | tr "[:upper:]" "[:lower:]" | sed "s/[^a-z0-9]/-/g"
212+ }
213+ containers=""
214+ for candidate in "${project_id}" "$(sanitize "${project_id}")"; do
215+ containers=$(docker ps --filter "label=com.docker.compose.project=${candidate}" --format "{{.ID}} {{.Names}}" || true)
216+ if [ -n "${containers}" ]; then
217+ break
218+ fi
219+ done
220+ if [ -z "${containers}" ]; then
221+ echo "No Supabase containers found" >&2
222+ exit 0
223+ fi
224+ echo "Capturing logs for project ${project_id}"
225+ echo "${containers}" | while read -r container_id container_name; do
226+ [ -z "${container_id}" ] && continue
227+ echo "--- ${container_name} (tail -n 200) ---"
228+ docker logs --tail 200 "${container_id}" || true
229+ done
230+ '
231+ echo "::endgroup::"
140232
141233 - name : Start Astro dev server
142- if : steps.branch-filter.outputs.skip_tests != 'true'
143234 run : |
144235 npm run dev -- --host 0.0.0.0 > /tmp/astro-dev.log 2>&1 &
145236 echo $! > /tmp/astro-dev.pid
146237
147238 - name : Wait for dev server
148- if : steps.branch-filter.outputs.skip_tests != 'true'
149239 run : |
150240 for attempt in $(seq 1 60); do
151241 if curl -fsS http://127.0.0.1:4321 >/dev/null; then
@@ -162,41 +252,41 @@ jobs:
162252 exit 1
163253
164254 - name : Run Playwright E2E tests
165- if : steps.branch-filter.outputs.skip_tests != 'true'
166255 run : npx playwright test
167256 env :
168257 CI : ' 1'
169258 FORCE_COLOR : ' 1'
170259 E2E_MOCKS : ' 1'
171260
172261 - name : Stop Astro dev server
173- if : always() && steps.branch-filter.outputs.skip_tests != 'true'
262+ if : always()
174263 run : |
175264 if [ -f /tmp/astro-dev.pid ]; then
176265 kill $(cat /tmp/astro-dev.pid) || true
177266 rm /tmp/astro-dev.pid
178267 fi
179268
180269 - name : Upload Playwright report
181- if : always() && steps.branch-filter.outputs.skip_tests != 'true'
270+ if : always()
182271 uses : actions/upload-artifact@v4
183272 with :
184273 name : playwright-report
185274 path : playwright-report/
186275 retention-days : 30
187276
188- - name : Upload test coverage
189- if : always() && steps.branch-filter.outputs.skip_tests != 'true'
190- uses : actions/upload-artifact@v5
191- with :
192- name : test-coverage
193- path : coverage/
194- retention-days : 30
195-
196277 - name : Stop Supabase stack
197- if : always() && steps.branch-filter.outputs.skip_tests != 'true'
278+ if : always()
198279 run : npm run containers:supabase:stop || true
199280
200281 - name : Stop mock containers
201- if : always() && steps.branch-filter.outputs.skip_tests != 'true'
282+ if : always()
202283 run : npm run containers:down || true
284+
285+ hotfix-bypass :
286+ name : Hotfix Bypass Notice
287+ runs-on : ubuntu-latest
288+ if : ${{ startsWith(github.head_ref || github.ref_name, 'hotfix/') }}
289+
290+ steps :
291+ - name : Skip testing for hotfix branch
292+ run : echo "hotfix/* branch detected; skipping lint, unit, and e2e workflows but allowing deployments."
0 commit comments