@@ -215,7 +215,19 @@ jobs:
215215 env :
216216 ECR_REPO : ${{ matrix.ecr_repo_secret == 'ECR_APP' && secrets.ECR_APP || matrix.ecr_repo_secret == 'ECR_MIGRATIONS' && secrets.ECR_MIGRATIONS || matrix.ecr_repo_secret == 'ECR_REALTIME' && secrets.ECR_REALTIME || matrix.ecr_repo_secret == 'ECR_PII' && secrets.ECR_PII || '' }}
217217
218+ # App leg only: capture the digest the :dev tag currently points at, BEFORE
219+ # this build overwrites it, so promote-trigger-dev can tell whether the app
220+ # image actually changed (a no-op :dev push triggers no ECS deploy).
221+ - name : Capture previous :dev app digest
222+ id : prevdigest
223+ if : matrix.ecr_repo_secret == 'ECR_APP'
224+ run : |
225+ REF="${{ steps.login-ecr.outputs.registry }}/${{ steps.ecr-repo.outputs.name }}:dev"
226+ PREV=$(docker buildx imagetools inspect "$REF" 2>/dev/null | awk '/^Digest:/{print $2; exit}' || true)
227+ echo "digest=${PREV}" >> "$GITHUB_OUTPUT"
228+
218229 - name : Build and push
230+ id : build
219231 uses : ./.github/actions/docker-build
220232 with :
221233 provider : ${{ vars.CI_PROVIDER }}
@@ -224,15 +236,45 @@ jobs:
224236 tags : ${{ steps.login-ecr.outputs.registry }}/${{ steps.ecr-repo.outputs.name }}:dev
225237 max-cache-size-mb : ${{ matrix.cache_mb }}
226238
227- # Dev: deploy Trigger.dev background tasks to the preview "dev-sim" branch.
228- # Gated after migrate-dev for the same reason as build-dev — the new task
229- # code runs against the dev DB, so the schema must be pushed first.
239+ # App leg only: publish the metadata promote-trigger-dev needs to correlate
240+ # this push to its dev ECS deploy and decide whether to wait. Dev has no
241+ # promote-images job, so this stands in for its retag_epoch/app_image_changed
242+ # outputs. The epoch is recorded just after the :dev push (the pipeline trigger).
243+ - name : Publish dev cutover metadata
244+ if : matrix.ecr_repo_secret == 'ECR_APP'
245+ run : |
246+ mkdir -p dev-meta
247+ NEW="${{ steps.build.outputs.digest }}"
248+ PREV="${{ steps.prevdigest.outputs.digest }}"
249+ echo "$NEW" > dev-meta/digest.txt
250+ date +%s > dev-meta/retag_epoch.txt
251+ if [ -n "$NEW" ] && [ "$NEW" = "$PREV" ]; then
252+ echo "false" > dev-meta/app_image_changed.txt
253+ echo "ℹ️ :dev already points at ${NEW}; no ECS dev deploy will be triggered."
254+ else
255+ echo "true" > dev-meta/app_image_changed.txt
256+ fi
257+
258+ - name : Upload dev cutover metadata
259+ if : matrix.ecr_repo_secret == 'ECR_APP'
260+ uses : actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
261+ with :
262+ name : dev-cutover-meta
263+ path : dev-meta/
264+ retention-days : 1
265+
266+ # Dev: build & upload the Trigger.dev task version WITHOUT promoting it
267+ # (--skip-promotion) to the preview "dev-sim" branch. promote-trigger-dev flips
268+ # it at the dev ECS traffic cutover. Gated after migrate-dev so the schema is
269+ # pushed before the new task version can run against the dev DB.
230270 deploy-trigger-dev :
231271 name : Deploy Trigger.dev (Dev)
232272 needs : [migrate-dev]
233273 if : github.event_name == 'push' && github.ref == 'refs/heads/dev'
234274 runs-on : ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }}
235275 timeout-minutes : 15
276+ outputs :
277+ version : ${{ steps.deploy.outputs.version }}
236278 steps :
237279 - name : Checkout code
238280 uses : actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
@@ -256,17 +298,111 @@ jobs:
256298 - name : Install dependencies
257299 run : bun install --frozen-lockfile --ignore-scripts
258300
259- - name : Deploy to Trigger.dev
301+ - name : Deploy to Trigger.dev (skip promotion)
302+ id : deploy
260303 working-directory : ./apps/sim
261304 env :
262305 TRIGGER_ACCESS_TOKEN : ${{ secrets.DEV_TRIGGER_ACCESS_TOKEN }}
263306 TRIGGER_PROJECT_ID : ${{ secrets.TRIGGER_PROJECT_ID }}
264307 run : |
308+ set -eo pipefail
265309 if [ -z "$TRIGGER_ACCESS_TOKEN" ] || [ -z "$TRIGGER_PROJECT_ID" ]; then
266310 echo "ERROR: DEV_TRIGGER_ACCESS_TOKEN and TRIGGER_PROJECT_ID repo secrets must both be set" >&2
267311 exit 1
268312 fi
269- bunx trigger.dev@4.5.12 deploy --env preview --branch dev-sim
313+ bunx trigger.dev@4.4.3 deploy --env preview --branch dev-sim --skip-promotion 2>&1 | tee deploy.log
314+ VERSION=$(sed -E 's/\x1b\[[0-9;]*m//g' deploy.log | grep -oE '20[0-9]{6}\.[0-9]+' | tail -n1 || true)
315+ if [ -z "$VERSION" ]; then
316+ echo "ERROR: could not parse deployed version from deploy output" >&2
317+ exit 1
318+ fi
319+ echo "Captured deployed version: $VERSION"
320+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
321+
322+ # Dev: promote the skip-promoted preview version at the dev ECS traffic cutover.
323+ # Dev has no promote-images gate (build-dev pushes :dev directly), so the digest,
324+ # trigger epoch, and app-image-changed signal come from build-dev's artifact.
325+ # trigger.dev supports promoting a specific preview branch: promote --env preview
326+ # --branch dev-sim.
327+ promote-trigger-dev :
328+ name : Promote Trigger.dev (Dev)
329+ needs : [build-dev, deploy-trigger-dev]
330+ if : github.event_name == 'push' && github.ref == 'refs/heads/dev'
331+ runs-on : blacksmith-4vcpu-ubuntu-2404
332+ # Dev bake is 5 min; the poll budget (30 min) and session (40 min) are sized for
333+ # that with margin, well short of the prod path's 70/90.
334+ timeout-minutes : 40
335+ permissions :
336+ contents : read
337+ id-token : write
338+ steps :
339+ - name : Checkout code
340+ uses : actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
341+
342+ - name : Setup Bun
343+ uses : oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
344+ with :
345+ bun-version : 1.3.13
346+
347+ - name : Cache Bun dependencies
348+ uses : actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
349+ with :
350+ path : |
351+ ~/.bun/install/cache
352+ node_modules
353+ **/node_modules
354+ key : ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
355+ restore-keys : |
356+ ${{ runner.os }}-bun-
357+
358+ - name : Install dependencies
359+ run : bun install --frozen-lockfile
360+
361+ - name : Download dev cutover metadata
362+ uses : actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
363+ with :
364+ name : dev-cutover-meta
365+ path : dev-meta
366+
367+ - name : Configure AWS credentials
368+ uses : aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6
369+ with :
370+ role-to-assume : ${{ secrets.DEV_AWS_ROLE_TO_ASSUME }}
371+ aws-region : ${{ secrets.DEV_AWS_REGION }}
372+ role-duration-seconds : 2400
373+
374+ - name : Wait for ECS traffic cutover
375+ env :
376+ OVERALL_TIMEOUT : " 1800"
377+ run : |
378+ set -eo pipefail
379+ CHANGED=$(cat dev-meta/app_image_changed.txt)
380+ if [ "$CHANGED" != "true" ]; then
381+ echo "App image unchanged — no dev ECS deploy triggered; promoting immediately."
382+ exit 0
383+ fi
384+ DIGEST=$(cat dev-meta/digest.txt)
385+ EPOCH=$(cat dev-meta/retag_epoch.txt)
386+ bash .github/scripts/wait-for-ecs-cutover.sh sim-dev-us-east-1-app-deployment "$DIGEST" "$EPOCH"
387+
388+ - name : Promote Trigger.dev version
389+ working-directory : ./apps/sim
390+ env :
391+ TRIGGER_ACCESS_TOKEN : ${{ secrets.DEV_TRIGGER_ACCESS_TOKEN }}
392+ TRIGGER_PROJECT_ID : ${{ secrets.TRIGGER_PROJECT_ID }}
393+ VERSION : ${{ needs.deploy-trigger-dev.outputs.version }}
394+ run : |
395+ set -eo pipefail
396+ if [ -z "$TRIGGER_ACCESS_TOKEN" ] || [ -z "$TRIGGER_PROJECT_ID" ]; then
397+ echo "ERROR: DEV_TRIGGER_ACCESS_TOKEN and TRIGGER_PROJECT_ID repo secrets must both be set" >&2
398+ exit 1
399+ fi
400+ if [ -z "$VERSION" ]; then
401+ echo "ERROR: no deployed version passed from deploy-trigger-dev" >&2
402+ exit 1
403+ fi
404+ echo "Promoting Trigger.dev version $VERSION (preview / dev-sim)"
405+ bunx trigger.dev@4.4.3 promote "$VERSION" --env preview --branch dev-sim
270406
271407 # Main/staging: build & upload the Trigger.dev task version WITHOUT promoting it
272408 # (--skip-promotion). New runs keep executing the OLD promoted version until
0 commit comments