-
Notifications
You must be signed in to change notification settings - Fork 93
294 lines (260 loc) · 12.8 KB
/
Copy pathcompatibility.yml
File metadata and controls
294 lines (260 loc) · 12.8 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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
name: Cross-Repo Compatibility
# Verifies that the bridgelet-sdk commit pinned in compatibility.json still
# works with this frontend, per the process documented in
# docs/compatibility.md. This is deliberately a *separate* workflow from
# e2e.yml: e2e.yml exercises the frontend against MSW mocks (fast, no
# external dependency) on every relevant PR, while this workflow exercises
# it against a real bridgelet-sdk checkout, which is slower and depends on
# secrets that aren't available to fork PRs.
#
# See docs/compatibility.md#required-ci-secrets for what BRIDGELET_SDK_*
# unlocks and what still gets checked without it.
on:
pull_request:
branches: [main]
paths:
- 'compatibility.json'
- 'docs/compatibility.md'
- 'e2e/**'
- 'frontend/app/**'
- 'frontend/components/**'
- 'frontend/lib/**'
schedule:
# Daily, offset from e2e.yml's 06:00 run so the two don't contend for
# runners.
- cron: '0 7 * * *'
workflow_dispatch: {}
jobs:
compatibility:
name: Verify pinned bridgelet-sdk combination
runs-on: ubuntu-latest
timeout-minutes: 25
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: bridgelet_user
POSTGRES_PASSWORD: bridgelet_pass
POSTGRES_DB: bridgelet
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U bridgelet_user -d bridgelet"
--health-interval 5s
--health-timeout 5s
--health-retries 10
steps:
- name: Checkout bridgelet (frontend)
uses: actions/checkout@v4
with:
path: bridgelet
# ── Resolve the pinned bridgelet-sdk commit ──────────────────────────────
- name: Read pinned bridgelet-sdk ref from compatibility.json
id: pin
working-directory: bridgelet
run: |
node -e '
const c = require("./compatibility.json");
const sdk = c.verified && c.verified.bridgeletSdk;
if (!sdk || !sdk.commit || !sdk.repo) {
console.error("compatibility.json is missing verified.bridgeletSdk.{repo,commit}");
process.exit(1);
}
console.log(`repo=${sdk.repo}`);
console.log(`commit=${sdk.commit}`);
console.log(`version=${sdk.version}`);
' >> "$GITHUB_OUTPUT"
- name: Checkout pinned bridgelet-sdk commit
uses: actions/checkout@v4
with:
repository: ${{ steps.pin.outputs.repo }}
ref: ${{ steps.pin.outputs.commit }}
path: bridgelet-sdk
# ── Node setup ──────────────────────────────────────────────────────────
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
cache-dependency-path: |
bridgelet/frontend/package-lock.json
bridgelet/e2e/package-lock.json
bridgelet-sdk/package-lock.json
# ── Install ────────────────────────────────────────────────────────────
- name: Install frontend dependencies
run: npm ci
working-directory: bridgelet/frontend
- name: Install e2e dependencies
run: npm ci
working-directory: bridgelet/e2e
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
working-directory: bridgelet/e2e
- name: Install bridgelet-sdk dependencies
run: npm ci
working-directory: bridgelet-sdk
# ── Determine how much of the job we can actually run ────────────────────
#
# BRIDGELET_SDK_* secrets carry a funded Stellar testnet keypair and
# live bridgelet-core contract IDs. They aren't available on fork PRs
# and must be provisioned by a maintainer (docs/compatibility.md#required-ci-secrets).
# Without them we still verify install/build/migrate/boot below —
# we just can't assert on the funded send -> claim -> sweep flow.
- name: Check whether live-testnet secrets are configured
id: secrets
env:
HAS_FUNDING_SECRET: ${{ secrets.BRIDGELET_SDK_FUNDING_ACCOUNT_SECRET != '' }}
run: echo "configured=${HAS_FUNDING_SECRET}" >> "$GITHUB_OUTPUT"
- name: Generate a throwaway keypair for the boot-only smoke path
if: steps.secrets.outputs.configured != 'true'
id: throwaway
working-directory: bridgelet-sdk
run: |
node -e '
const { Keypair } = require("@stellar/stellar-sdk");
const kp = Keypair.random();
console.log(`secret=${kp.secret()}`);
console.log(`public=${kp.publicKey()}`);
' >> "$GITHUB_OUTPUT"
# ── Configure and boot bridgelet-sdk ─────────────────────────────────────
- name: Run database migrations
working-directory: bridgelet-sdk
env:
DATABASE_HOST: localhost
DATABASE_PORT: '5432'
DATABASE_NAME: bridgelet
DATABASE_USER: bridgelet_user
DATABASE_PASSWORD: bridgelet_pass
run: npm run migration:run
- name: Build bridgelet-sdk
working-directory: bridgelet-sdk
run: npm run build
- name: Start bridgelet-sdk
working-directory: bridgelet-sdk
env:
NODE_ENV: test
PORT: '4000'
OTEL_ENABLED: 'false'
CORS_ORIGINS: http://localhost:3000
DATABASE_HOST: localhost
DATABASE_PORT: '5432'
DATABASE_NAME: bridgelet
DATABASE_USER: bridgelet_user
DATABASE_PASSWORD: bridgelet_pass
DATABASE_SYNC: 'false'
STELLAR_NETWORK: testnet
STELLAR_HORIZON_URL: https://horizon-testnet.stellar.org
STELLAR_SOROBAN_RPC_URL: https://soroban-testnet.stellar.org
# Funded testnet keypair when available; otherwise a freshly
# generated (unfunded) keypair so the process still boots — see
# the "live-testnet secrets" step above.
FUNDING_ACCOUNT_SECRET: ${{ secrets.BRIDGELET_SDK_FUNDING_ACCOUNT_SECRET || steps.throwaway.outputs.secret }}
RECOVERY_ACCOUNT_PUBLIC: ${{ secrets.BRIDGELET_SDK_RECOVERY_ACCOUNT_PUBLIC || steps.throwaway.outputs.public }}
EPHEMERAL_ACCOUNT_CONTRACT_ID: ${{ secrets.BRIDGELET_SDK_EPHEMERAL_ACCOUNT_CONTRACT_ID || 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSC4' }}
STELLAR_SWEEP_CONTROLLER_CONTRACT_ID: ${{ secrets.BRIDGELET_SDK_SWEEP_CONTROLLER_CONTRACT_ID || 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSC4' }}
SWEEP_SIGNING_KEY_SEED: f76f684a3a8b64f32a7dc7eba0b0a5040ba66b5ea67dad348c3b69b79db3339c
JWT_SECRET: ci-compatibility-job-not-a-real-secret
# 64 hex chars (32 bytes) — CI-only value, not a real secret.
ENCRYPTION_KEY: '9ef61d8fe679ea10433353f5488a91eabbaf831d9f8e422ba8f89636778683ed'
CLAIM_TOKEN_EXPIRY: '2592000'
run: |
# Don't assume `npm run start` (node dist/main.js) is correct:
# bridgelet-sdk's tsconfig.build.json doesn't exclude the
# top-level scripts/ directory, so tsc's inferred rootDir is the
# project root rather than src/, and `nest build` actually emits
# dist/src/main.js, not dist/main.js. Their own CI never runs the
# built output (only `npm run build`), so this drifted unnoticed.
# Locate the real entry point instead of hardcoding the path.
ENTRY=$(find dist -name main.js -not -path '*/node_modules/*' | head -1)
if [ -z "$ENTRY" ]; then
echo "::error::Could not find a built main.js under dist/ after 'npm run build'."
find dist -maxdepth 3 || true
exit 1
fi
echo "Starting bridgelet-sdk via: node $ENTRY"
nohup node "$ENTRY" > ../bridgelet-sdk.log 2>&1 &
echo $! > ../bridgelet-sdk.pid
- name: Wait for bridgelet-sdk to become healthy
run: |
for i in $(seq 1 30); do
if curl -sf http://localhost:4000/health > health.json; then
echo "bridgelet-sdk is up:"
cat health.json
exit 0
fi
sleep 2
done
echo "::error::bridgelet-sdk did not become healthy within 60s"
cat bridgelet-sdk.log || true
exit 1
# ── Primary gate: does the frontend's hand-written API client still
# match what this bridgelet-sdk commit actually serves? ───────────────
#
# frontend/lib/bridgelet.ts / frontend/lib/create-bridgelet-client.ts
# assume specific bridgelet-sdk endpoints and field names. This fetches
# the pinned SDK's live OpenAPI spec (GET /api/docs-json) and asserts
# those endpoints/fields still exist — catching exactly the "frontend
# assumes a response shape/endpoint a different SDK version doesn't
# provide" failure mode this issue is about, without needing a funded
# testnet account, so it runs on every PR and scheduled run regardless
# of secrets. See scripts/check-sdk-contract.mjs for exactly what's
# checked (and its known limits — it's field-presence, not full type
# equivalence).
- name: Check frontend/bridgelet-sdk API contract
working-directory: bridgelet
env:
BRIDGELET_API_URL: http://localhost:4000
run: node scripts/check-sdk-contract.mjs
# ── Secondary, best-effort: exercise the flows that are actually wired
# through to the real SDK in the browser e2e suite ────────────────────
#
# frontend/components/mock-provider.tsx currently auto-enables MSW
# whenever NODE_ENV=development regardless of E2E_USE_MOCKS, and at
# least one flow (claim-flow.tsx's redeem handler) has a hardcoded
# dev-mode stub — so this step does not yet exercise every route
# against bridgelet-sdk end-to-end. It's kept because it still catches
# regressions in what *is* wired, and to give the "SDK integration"
# mode documented in e2e/README.md a real CI run. The OpenAPI check
# above is the authoritative pass/fail signal for this job.
- name: Run e2e suite in SDK-integration mode (best-effort — see comment above)
if: steps.secrets.outputs.configured == 'true'
continue-on-error: true
id: e2e
run: npx playwright test --reporter=github
working-directory: bridgelet/e2e
env:
CI: true
E2E_USE_MOCKS: 'false'
E2E_BASE_URL: http://localhost:3000
E2E_API_BASE_URL: http://localhost:4000
- name: Job summary
if: always()
run: |
echo "### Compatibility check — bridgelet-sdk @ \`${{ steps.pin.outputs.commit }}\`" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "- OpenAPI type contract: authoritative pass/fail signal for this job (see step above)." >> "$GITHUB_STEP_SUMMARY"
if [ "${{ steps.secrets.outputs.configured }}" = "true" ]; then
echo "- Browser e2e suite (SDK-integration mode): ran, best-effort, outcome '${{ steps.e2e.outcome }}' — does not fail the job; see e2e/README.md and this workflow's comments for known coverage gaps." >> "$GITHUB_STEP_SUMMARY"
else
echo "- Browser e2e suite (SDK-integration mode): skipped — \`BRIDGELET_SDK_FUNDING_ACCOUNT_SECRET\` and related secrets are not configured (see docs/compatibility.md#required-ci-secrets)." >> "$GITHUB_STEP_SUMMARY"
fi
# ── Artifacts ────────────────────────────────────────────────────────────
- name: Upload Playwright report
if: always() && steps.secrets.outputs.configured == 'true'
uses: actions/upload-artifact@v4
with:
name: compatibility-playwright-report-${{ github.run_id }}
path: |
bridgelet/frontend/test-results/
bridgelet/e2e/playwright-report/
retention-days: 14
- name: Upload bridgelet-sdk log
if: always()
uses: actions/upload-artifact@v4
with:
name: compatibility-sdk-log-${{ github.run_id }}
path: bridgelet-sdk.log
retention-days: 7
- name: Stop bridgelet-sdk
if: always()
run: kill "$(cat bridgelet-sdk.pid)" 2>/dev/null || true