@crossdelta/platform-sdk 0.22.0 โ†’ 0.22.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,409 @@
1
+ name: ๐Ÿš€ Build and Deploy
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - 'apps/*/**'
8
+ - 'services/*/**'
9
+ - 'infra/**'
10
+ - 'bun.lock'
11
+ - 'bunfig.toml'
12
+ - '.github/workflows/**'
13
+ workflow_run:
14
+ workflows: ['๐Ÿ“ฆ Publish Packages']
15
+ types: [completed]
16
+ branches: [main]
17
+ workflow_dispatch:
18
+ inputs:
19
+ environment:
20
+ description: 'Target environment'
21
+ required: true
22
+ default: 'stage'
23
+ type: choice
24
+ options:
25
+ - stage
26
+ - production
27
+
28
+ permissions:
29
+ contents: read
30
+ packages: write
31
+
32
+ env:
33
+ SCOPE_ROOTS: apps,services
34
+ REGISTRY: ghcr.io/$\{{ github.repository_owner }}/$\{{ github.event.repository.name }}
35
+ PULUMI_STACK: $\{{ vars.PULUMI_STACK_BASE }}/$\{{ github.event.inputs.environment || 'stage' }}
36
+ WORKFLOW_HEAD_SHA: $\{{ github.event.workflow_run.head_sha || github.sha }}
37
+ WORKFLOW_BASE_SHA_INPUT: $\{{ github.event.before || '' }}
38
+
39
+ jobs:
40
+ gate:
41
+ name: ๐Ÿšฆ Publish Gate
42
+ runs-on: ubuntu-latest
43
+ outputs:
44
+ proceed: $\{{ steps.decide.outputs.proceed }}
45
+ steps:
46
+ - uses: actions/checkout@v6
47
+ if: github.event_name == 'push'
48
+ with:
49
+ fetch-depth: 0
50
+
51
+ - name: Check for package changes
52
+ if: github.event_name == 'push'
53
+ id: pkg-check
54
+ run: |
55
+ BASE="$\{{ github.event.before }}"
56
+ NULL_SHA="0000000000000000000000000000000000000000"
57
+ if [ -z "$BASE" ] || [ "$BASE" = "$NULL_SHA" ]; then
58
+ BASE=$(git rev-parse HEAD^ 2>/dev/null || git rev-parse HEAD)
59
+ fi
60
+ CHANGED=$(git diff --name-only "$BASE" "$\{{ github.sha }}" -- packages/ | head -1)
61
+ if [ -n "$CHANGED" ]; then
62
+ echo "changed=true" >> "$GITHUB_OUTPUT"
63
+ else
64
+ echo "changed=false" >> "$GITHUB_OUTPUT"
65
+ fi
66
+
67
+ - name: Decide whether to proceed
68
+ id: decide
69
+ run: |
70
+ EVENT="$\{{ github.event_name }}"
71
+ if [ "$EVENT" = "workflow_run" ]; then
72
+ CONCLUSION="$\{{ github.event.workflow_run.conclusion }}"
73
+ if [ "$CONCLUSION" = "success" ]; then
74
+ echo "โœ… Publish completed โ€” proceeding with build & deploy"
75
+ echo "proceed=true" >> "$GITHUB_OUTPUT"
76
+ else
77
+ echo "โŒ Publish workflow $CONCLUSION โ€” skipping"
78
+ echo "proceed=false" >> "$GITHUB_OUTPUT"
79
+ fi
80
+ elif [ "$EVENT" = "push" ]; then
81
+ if [ "$\{{ steps.pkg-check.outputs.changed }}" = "true" ]; then
82
+ echo "๐Ÿ“ฆ Package changes detected โ€” deferring to publish workflow"
83
+ echo "proceed=false" >> "$GITHUB_OUTPUT"
84
+ else
85
+ echo "โœ… No package changes โ€” proceeding"
86
+ echo "proceed=true" >> "$GITHUB_OUTPUT"
87
+ fi
88
+ else
89
+ echo "โœ… Manual dispatch โ€” proceeding"
90
+ echo "proceed=true" >> "$GITHUB_OUTPUT"
91
+ fi
92
+
93
+ prepare-scopes:
94
+ name: ๐Ÿงช Prepare Scopes
95
+ needs: gate
96
+ if: needs.gate.outputs.proceed == 'true'
97
+ runs-on: ubuntu-latest
98
+ outputs:
99
+ scopes: $\{{ steps.scope-generator.outputs.scopes }}
100
+ scope_count: $\{{ steps.scope-generator.outputs.scopes_count }}
101
+ infrastructure_changed: $\{{ steps.infra-eval.outputs.changed }}
102
+ steps:
103
+ - uses: actions/checkout@v6
104
+ with:
105
+ fetch-depth: 0
106
+ ref: $\{{ env.WORKFLOW_HEAD_SHA }}
107
+
108
+ - name: Set commit range
109
+ run: |
110
+ set -euo pipefail
111
+ HEAD_SHA="${WORKFLOW_HEAD_SHA}"
112
+ BASE_SHA="${WORKFLOW_BASE_SHA_INPUT}"
113
+ NULL_SHA="0000000000000000000000000000000000000000"
114
+ if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" = "$NULL_SHA" ]; then
115
+ if git rev-parse "${HEAD_SHA}^" >/dev/null 2>&1; then
116
+ BASE_SHA=$(git rev-parse "${HEAD_SHA}^")
117
+ else
118
+ BASE_SHA="$HEAD_SHA"
119
+ fi
120
+ fi
121
+ echo "GITHUB_SHA=$HEAD_SHA" >> "$GITHUB_ENV"
122
+ echo "GITHUB_EVENT_BEFORE=$BASE_SHA" >> "$GITHUB_ENV"
123
+
124
+ - name: Check for infrastructure changes
125
+ id: infra-check
126
+ run: |
127
+ CHANGED=$(git diff --name-only "$GITHUB_EVENT_BEFORE" "$GITHUB_SHA" -- infra/ | head -1)
128
+ if [ -n "$CHANGED" ]; then
129
+ echo "changed=true" >> "$GITHUB_OUTPUT"
130
+ else
131
+ echo "changed=false" >> "$GITHUB_OUTPUT"
132
+ fi
133
+
134
+ - name: Evaluate infrastructure change
135
+ id: infra-eval
136
+ run: |
137
+ if [ "$\{{ github.event_name }}" = "workflow_run" ] || [ "$\{{ github.event_name }}" = "workflow_dispatch" ] || [ "$\{{ steps.infra-check.outputs.changed }}" = "true" ]; then
138
+ echo "changed=true" >> "$GITHUB_OUTPUT"
139
+ else
140
+ echo "changed=false" >> "$GITHUB_OUTPUT"
141
+ fi
142
+
143
+ - name: Generate scope matrix
144
+ id: scope-generator
145
+ uses: ./.github/actions/generate-scope-matrix
146
+ with:
147
+ scope-roots: $\{{ env.SCOPE_ROOTS }}
148
+ force-all: $\{{ github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_run' }}
149
+
150
+ build:
151
+ if: needs.prepare-scopes.outputs.scope_count && needs.prepare-scopes.outputs.scope_count != '0'
152
+ needs: prepare-scopes
153
+ runs-on: ubuntu-latest
154
+ strategy:
155
+ matrix:
156
+ scope: $\{{ fromJson(needs.prepare-scopes.outputs.scopes) }}
157
+ name: ๐Ÿณ Build $\{{ matrix.scope.shortName }}
158
+ environment: $\{{ github.event.inputs.environment || 'stage' }}
159
+ env:
160
+ DOCKER_BUILDKIT: 1
161
+ IMAGE_TAG: $\{{ github.sha }}
162
+
163
+ steps:
164
+ - uses: actions/checkout@v6
165
+ with:
166
+ fetch-depth: 2
167
+ ref: $\{{ env.WORKFLOW_HEAD_SHA }}
168
+
169
+ - name: Extract Bun version from package.json
170
+ run: echo "BUN_VERSION=$(jq -r '.packageManager // empty' package.json | sed 's/bun@//')" >> $GITHUB_ENV
171
+
172
+ - name: Set scope metadata
173
+ run: |
174
+ echo "SCOPE_DIR=$\{{ matrix.scope.dir }}" >> $GITHUB_ENV
175
+ echo "scope_name=$\{{ matrix.scope.shortName }}" >> $GITHUB_OUTPUT
176
+
177
+ - name: Setup Bun and install dependencies
178
+ uses: ./.github/actions/setup-bun-install
179
+ with:
180
+ enable-cache: 'true'
181
+ cache-key: bun-v4-$\{{ runner.os }}-$\{{ hashFiles('bun.lock', 'bunfig.toml') }}
182
+ cache-restore-keys: bun-v4-$\{{ runner.os }}-
183
+ npm-token: $\{{ github.token }}
184
+
185
+ - name: Prune
186
+ run: |
187
+ bunx turbo prune --scope=$\{{ matrix.scope.name }} --docker --out-dir out/$\{{ matrix.scope.shortName }}
188
+ # turbo prune omits the bun patchedDependencies patch files. Copy them into the
189
+ # pruned context so `bun install` can apply them (rxdb patch for studio/storefront).
190
+ # Done before the scope checksum so a patch change busts the image tag.
191
+ if [ -d patches ]; then
192
+ cp -rT patches "out/$\{{ matrix.scope.shortName }}/full/patches"
193
+ fi
194
+
195
+ - name: Compute scope checksum
196
+ id: scope-checksum
197
+ run: |
198
+ set -euo pipefail
199
+ cd "out/$\{{ matrix.scope.shortName }}"
200
+ HASH=$(find . -type f -print0 | sort -z | xargs -0 sha256sum | sha256sum)
201
+ HASH=${HASH%% *}
202
+ echo "scope_hash=$HASH" >> $GITHUB_OUTPUT
203
+
204
+ - name: Use scope checksum as image tag
205
+ run: echo "IMAGE_TAG=$\{{ steps.scope-checksum.outputs.scope_hash }}" >> $GITHUB_ENV
206
+
207
+ - name: Check if image tag exists
208
+ id: tag-check
209
+ uses: ./.github/actions/check-image-tag-exists
210
+ with:
211
+ scope-short-name: $\{{ matrix.scope.shortName }}
212
+ image-tag: $\{{ env.IMAGE_TAG }}
213
+ github-token: $\{{ secrets.GITHUB_TOKEN }}
214
+
215
+ - name: Set up Docker Buildx
216
+ if: steps.tag-check.outputs.exists != 'true'
217
+ uses: docker/setup-buildx-action@v4
218
+
219
+ - name: Login to GitHub Packages (GHCR)
220
+ if: steps.tag-check.outputs.exists != 'true'
221
+ uses: docker/login-action@v4
222
+ with:
223
+ registry: ghcr.io
224
+ username: $\{{ github.actor }}
225
+ password: $\{{ secrets.GITHUB_TOKEN }}
226
+
227
+ - name: Prepare build context
228
+ if: steps.tag-check.outputs.exists != 'true'
229
+ id: prepare-context
230
+ uses: ./.github/actions/prepare-build-context
231
+ with:
232
+ scope-short-name: $\{{ matrix.scope.shortName }}
233
+ scope-dir: $\{{ matrix.scope.dir }}
234
+ npm-token: $\{{ github.token }}
235
+
236
+ - name: Build and push image
237
+ if: steps.tag-check.outputs.exists != 'true'
238
+ uses: docker/build-push-action@v7
239
+ env:
240
+ NPM_TOKEN: $\{{ github.token }}
241
+ with:
242
+ context: out/$\{{ matrix.scope.shortName }}/full
243
+ file: out/$\{{ matrix.scope.shortName }}/full/$\{{ steps.prepare-context.outputs.dockerfile }}
244
+ push: true
245
+ tags: |
246
+ $\{{ env.REGISTRY }}/$\{{ matrix.scope.shortName }}:$\{{ env.IMAGE_TAG }}
247
+ $\{{ env.REGISTRY }}/$\{{ matrix.scope.shortName }}:latest
248
+ cache-from: type=gha,scope=$\{{ matrix.scope.shortName }}
249
+ cache-to: type=gha,scope=$\{{ matrix.scope.shortName }},mode=max
250
+ build-args: |
251
+ BUN_VERSION=$\{{ env.BUN_VERSION }}
252
+ secrets: |
253
+ NPM_TOKEN=$\{{ env.NPM_TOKEN }}
254
+
255
+ - name: Mark this scope as changed
256
+ run: |
257
+ mkdir -p .changed/$\{{ matrix.scope.shortName }}
258
+ echo "$\{{ matrix.scope.shortName }}" > .changed/$\{{ matrix.scope.shortName }}/scope.txt
259
+ printf '{"shortName":"%s","imageTag":"%s"}\n' "$\{{ matrix.scope.shortName }}" "$\{{ env.IMAGE_TAG }}" > .changed/$\{{ matrix.scope.shortName }}/metadata.json
260
+
261
+ - name: Upload change marker
262
+ uses: actions/upload-artifact@v6
263
+ with:
264
+ name: changed-$\{{ matrix.scope.shortName }}
265
+ path: .changed/$\{{ matrix.scope.shortName }}
266
+
267
+ deploy:
268
+ runs-on: ubuntu-latest
269
+ concurrency:
270
+ group: deploy-job-$\{{ github.ref }}
271
+ cancel-in-progress: false
272
+ needs: [gate, build, prepare-scopes]
273
+ if: |
274
+ always() &&
275
+ needs.gate.outputs.proceed == 'true' &&
276
+ needs.prepare-scopes.result == 'success' &&
277
+ needs.build.result != 'failure' &&
278
+ (needs.prepare-scopes.outputs.scope_count != '0' || needs.prepare-scopes.outputs.infrastructure_changed == 'true')
279
+ name: ๐ŸŒ Deploy ($\{{ github.event.inputs.environment || 'stage' }})
280
+ environment: $\{{ github.event.inputs.environment || 'stage' }}
281
+ steps:
282
+ - uses: actions/checkout@v6
283
+ with:
284
+ ref: $\{{ env.WORKFLOW_HEAD_SHA }}
285
+ fetch-depth: 2
286
+
287
+ - name: Download all change markers
288
+ uses: actions/download-artifact@v7
289
+ with:
290
+ pattern: changed-*
291
+ path: .artifacts
292
+
293
+ - name: Resolve scope image tags
294
+ id: resolve_scope_tags
295
+ shell: bash
296
+ env:
297
+ REGISTRY: $\{{ env.REGISTRY }}
298
+ GH_TOKEN: $\{{ secrets.GITHUB_TOKEN }}
299
+ REPO_OWNER: $\{{ github.repository_owner }}
300
+ REPO_PREFIX: $\{{ github.event.repository.name }}
301
+ run: |
302
+ set -euo pipefail
303
+ RESULT="{}"
304
+
305
+ # 1. Read markers from build job
306
+ if [ -d .artifacts ]; then
307
+ for marker_dir in .artifacts/changed-*/; do
308
+ [ -f "$marker_dir/metadata.json" ] || continue
309
+ SHORT=$(jq -r '.shortName' "$marker_dir/metadata.json")
310
+ TAG=$(jq -r '.imageTag' "$marker_dir/metadata.json")
311
+ [ -n "$SHORT" ] && [ -n "$TAG" ] && \
312
+ RESULT=$(echo "$RESULT" | jq --arg k "$SHORT" --arg v "$REGISTRY/$SHORT:$TAG" '. + {($k): $v}')
313
+ done
314
+ fi
315
+
316
+ # 2. For scopes without markers, query GHCR for latest checksum tag
317
+ MISSING=""
318
+ for scope_dir in apps/*/Dockerfile services/*/Dockerfile; do
319
+ [ -f "$scope_dir" ] || continue
320
+ SHORT=$(basename "$(dirname "$scope_dir")")
321
+ echo "$RESULT" | jq -e --arg k "$SHORT" '.[$k]' >/dev/null 2>&1 && continue
322
+
323
+ # Query GHCR for the latest version with a checksum tag (retry transient API errors)
324
+ CHECKSUM_TAG=""
325
+ for attempt in 1 2 3; do
326
+ CHECKSUM_TAG=$(curl -sf \
327
+ -H "Authorization: Bearer $GH_TOKEN" \
328
+ -H "Accept: application/vnd.github+json" \
329
+ "https://api.github.com/orgs/$REPO_OWNER/packages/container/${REPO_PREFIX}%2F${SHORT}/versions?per_page=50" \
330
+ | jq -r '[.[].metadata.container.tags[]? | select(test("^[0-9a-f]{64}$"))][0] // empty' 2>/dev/null || true)
331
+ [ -n "$CHECKSUM_TAG" ] && break
332
+ sleep 2
333
+ done
334
+
335
+ if [ -n "$CHECKSUM_TAG" ]; then
336
+ RESULT=$(echo "$RESULT" | jq --arg k "$SHORT" --arg v "$REGISTRY/$SHORT:$CHECKSUM_TAG" '. + {($k): $v}')
337
+ echo " $SHORT โ†’ $CHECKSUM_TAG (from GHCR)"
338
+ else
339
+ MISSING="$MISSING $SHORT"
340
+ fi
341
+ done
342
+
343
+ if [ -n "$MISSING" ]; then
344
+ echo "::error::No checksum-tagged image found in GHCR for:$MISSING. Refusing to deploy :latest โ€” trigger a build for these scopes first (workflow_dispatch builds all)."
345
+ exit 1
346
+ fi
347
+
348
+ echo "scope_image_tags=$(echo "$RESULT" | jq -c .)" >> "$GITHUB_OUTPUT"
349
+ echo "Resolved image tags: $RESULT"
350
+
351
+ - name: Setup Bun and install dependencies
352
+ uses: ./.github/actions/setup-bun-install
353
+ with:
354
+ enable-cache: 'true'
355
+ cache-key: bun-v4-$\{{ runner.os }}-$\{{ hashFiles('bun.lock', 'bunfig.toml') }}
356
+ cache-restore-keys: bun-v4-$\{{ runner.os }}-
357
+ npm-token: $\{{ github.token }}
358
+
359
+ - name: Build infrastructure dependencies
360
+ run: |
361
+ bunx turbo run build --filter=@crossdelta/infrastructure --filter={{scope}}/contracts
362
+
363
+ - name: Pulumi up
364
+ uses: pulumi/actions@v7
365
+ timeout-minutes: 5
366
+ with:
367
+ command: up
368
+ suppress-progress: true
369
+ suppress-outputs: true
370
+ comment-on-summary: true
371
+ stack-name: $\{{ env.PULUMI_STACK }}
372
+ work-dir: infra
373
+ env:
374
+ PULUMI_ACCESS_TOKEN: $\{{ secrets.PULUMI_ACCESS_TOKEN }}
375
+ DIGITALOCEAN_TOKEN: $\{{ secrets.DIGITALOCEAN_TOKEN }}
376
+ SCOPE_IMAGE_TAGS: $\{{ steps.resolve_scope_tags.outputs.scope_image_tags }}
377
+
378
+ - name: Check if Studio was deployed
379
+ id: studio-deployed
380
+ run: |
381
+ if [ -f .artifacts/changed-studio/metadata.json ]; then
382
+ echo "deployed=true" >> "$GITHUB_OUTPUT"
383
+ else
384
+ echo "deployed=false" >> "$GITHUB_OUTPUT"
385
+ fi
386
+
387
+ - name: Get Pulumi outputs
388
+ id: pulumi-outputs
389
+ if: steps.studio-deployed.outputs.deployed == 'true'
390
+ working-directory: infra
391
+ run: |
392
+ URL=$(pulumi stack output studioUrl --stack "$PULUMI_STACK" --non-interactive)
393
+ echo "studio_url=${URL}" >> "$GITHUB_OUTPUT"
394
+ env:
395
+ PULUMI_ACCESS_TOKEN: $\{{ secrets.PULUMI_ACCESS_TOKEN }}
396
+
397
+ - name: Smoke-check new Studio deployment
398
+ id: studio-smoke
399
+ if: steps.studio-deployed.outputs.deployed == 'true'
400
+ env:
401
+ STUDIO_URL: $\{{ steps.pulumi-outputs.outputs.studio_url }}
402
+ run: |
403
+ curl_opts=(-sf)
404
+ for i in $(seq 1 30); do
405
+ if curl "${curl_opts[@]}" "${STUDIO_URL}/health"; then break; fi
406
+ sleep 2
407
+ done
408
+ curl "${curl_opts[@]}" "${STUDIO_URL}/health" || exit 1
409
+
@@ -0,0 +1,83 @@
1
+ name: Pull Request Checks
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - main
7
+
8
+ concurrency:
9
+ group: $\{{ github.workflow }}-$\{{ github.ref }}
10
+ cancel-in-progress: true
11
+
12
+ jobs:
13
+ lint:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - name: Checkout code
17
+ uses: actions/checkout@v6
18
+
19
+ - name: Setup Bun and install dependencies
20
+ uses: ./.github/actions/setup-bun-install
21
+ with:
22
+ enable-cache: 'true'
23
+ cache-key: bun-v4-$\{{ runner.os }}-$\{{ hashFiles('bun.lock', 'bunfig.toml') }}
24
+ cache-restore-keys: bun-v4-$\{{ runner.os }}-
25
+
26
+ - name: Run Linter
27
+ run: bun run --if-present lint
28
+
29
+ test:
30
+ runs-on: ubuntu-latest
31
+ env:
32
+ steps:
33
+ - name: Checkout code
34
+ uses: actions/checkout@v6
35
+
36
+ - name: Setup Bun and install dependencies
37
+ uses: ./.github/actions/setup-bun-install
38
+ with:
39
+ enable-cache: 'true'
40
+ cache-key: bun-v4-$\{{ runner.os }}-$\{{ hashFiles('bun.lock', 'bunfig.toml') }}
41
+ cache-restore-keys: bun-v4-$\{{ runner.os }}-
42
+
43
+ - name: Run Tests
44
+ run: bun run --if-present test
45
+
46
+ e2e:
47
+ runs-on: ubuntu-latest
48
+ needs: test
49
+ if: false # disabled until E2E tests are stable
50
+ env:
51
+ E2E_STUDIO_EMAIL: $\{{ secrets.E2E_STUDIO_EMAIL }}
52
+ E2E_STUDIO_PASSWORD: $\{{ secrets.E2E_STUDIO_PASSWORD }}
53
+ steps:
54
+ - name: Checkout code
55
+ uses: actions/checkout@v6
56
+
57
+ - name: Setup Bun and install dependencies
58
+ uses: ./.github/actions/setup-bun-install
59
+ with:
60
+ enable-cache: 'true'
61
+ cache-key: bun-v4-$\{{ runner.os }}-$\{{ hashFiles('bun.lock', 'bunfig.toml') }}
62
+ cache-restore-keys: bun-v4-$\{{ runner.os }}-
63
+
64
+ - name: Cache Playwright browsers
65
+ id: playwright-cache
66
+ uses: actions/cache@v4
67
+ with:
68
+ path: ~/.cache/ms-playwright
69
+ key: playwright-$\{{ runner.os }}-$\{{ hashFiles('apps/studio/package.json', 'apps/storefront/package.json') }}
70
+
71
+ - name: Install Playwright Browsers
72
+ if: env.E2E_STUDIO_EMAIL != '' && steps.playwright-cache.outputs.cache-hit != 'true'
73
+ run: bunx playwright install --with-deps chromium
74
+
75
+ - name: Install Playwright deps (cached)
76
+ if: env.E2E_STUDIO_EMAIL != '' && steps.playwright-cache.outputs.cache-hit == 'true'
77
+ run: bunx playwright install-deps chromium
78
+
79
+ - name: Run E2E Tests
80
+ if: env.E2E_STUDIO_EMAIL != ''
81
+ run: bunx turbo run test.e2e --concurrency=1
82
+ env:
83
+ CI: true