From d14f6a3f2c53527b587b0ccc640b3e4ac633dcb0 Mon Sep 17 00:00:00 2001 From: SandipBajracharya Date: Tue, 15 Sep 2026 14:38:19 +0545 Subject: [PATCH 1/3] feat(OUT-4184): auto-deploy to Trigger.dev on merge to main Add a self-contained deploy workflow that runs on push to main: lint, typecheck, unit, and integration jobs gate a deploy job (needs all four) that runs `pnpm trigger:deploy` with VERCEL_ENV=production so Sentry sourcemaps upload. Secrets come from the Production GitHub Environment. - add trigger:deploy script mirroring trigger:dev - concurrency group; newest push to main supersedes an in-flight run - pin all actions to full commit SHAs across the three workflows - run code-quality and test on pull_request only (merges to main are now checked by deploy.yml), removing duplicate push+PR runs Co-Authored-By: Claude Opus 4.8 --- .github/workflows/code-quality.yml | 19 ++-- .github/workflows/deploy.yml | 177 +++++++++++++++++++++++++++++ .github/workflows/test.yml | 17 +-- package.json | 1 + 4 files changed, 197 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 49e3a4c..4682181 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -1,8 +1,7 @@ name: Code Quality Checks -# pull_request also covers fork PRs, so lint + type-check gate them too. +# pull_request covers fork PRs too; merges to main are checked by deploy.yml. on: - push: pull_request: permissions: @@ -15,6 +14,8 @@ concurrency: env: CI: true +# Actions pinned to full commit SHAs: tags are mutable and can be moved +# after a compromise. Bump SHA + version comment together. jobs: run-linter: name: Run linter @@ -22,16 +23,16 @@ jobs: timeout-minutes: 15 steps: - name: Check out repository - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Set up pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0 with: version: 10.15 run_install: false - name: Set up Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '22' cache: pnpm @@ -51,16 +52,16 @@ jobs: timeout-minutes: 5 steps: - name: Check out repository - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Set up pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0 with: version: 10.15 run_install: false - name: Set up Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '22' cache: pnpm @@ -72,7 +73,7 @@ jobs: run: pnpm install --frozen-lockfile - name: Cache TypeScript build info - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: | .tsbuildinfo diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..5f84046 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,177 @@ +name: Deploy + +on: + push: + branches: [main] + +permissions: + contents: read + +# Newer push to main supersedes an in-flight deploy run. +concurrency: + group: deploy-production + cancel-in-progress: true + +env: + CI: true + +# Actions are pinned to full commit SHAs, not @v4 tags: tags are mutable, +# and the deploy job runs these before handing production secrets to the +# deploy step. A moved tag must not run here. Bump SHA + version comment together. +jobs: + lint: + name: Run linter + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Check out repository + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + + - name: Set up pnpm + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0 + with: + version: 10.15 + run_install: false + + - name: Set up Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: '22' + cache: pnpm + cache-dependency-path: | + pnpm-lock.yaml + **/pnpm-lock.yaml + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run linter + run: pnpm lint + + typecheck: + name: TypeScript type-check + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Check out repository + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + + - name: Set up pnpm + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0 + with: + version: 10.15 + run_install: false + + - name: Set up Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: '22' + cache: pnpm + cache-dependency-path: | + pnpm-lock.yaml + **/pnpm-lock.yaml + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Type check + run: pnpm typecheck + + unit-tests: + name: Unit tests + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Check out repository + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + + - name: Set up pnpm + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0 + with: + version: 10.15 + run_install: false + + - name: Set up Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: '22' + cache: pnpm + cache-dependency-path: | + pnpm-lock.yaml + **/pnpm-lock.yaml + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run unit tests + run: pnpm test + + integration-tests: + name: Integration tests + runs-on: ubuntu-latest + # Testcontainers spins up a real Postgres via the Docker daemon that + # ubuntu-latest runners provide out of the box — no `services:` needed. + timeout-minutes: 20 + steps: + - name: Check out repository + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + + - name: Set up pnpm + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0 + with: + version: 10.15 + run_install: false + + - name: Set up Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: '22' + cache: pnpm + cache-dependency-path: | + pnpm-lock.yaml + **/pnpm-lock.yaml + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run integration tests + run: pnpm test:integration + + deploy: + name: Deploy to Trigger.dev + needs: [lint, typecheck, unit-tests, integration-tests] + runs-on: ubuntu-latest + environment: Production + timeout-minutes: 20 + steps: + - name: Check out repository + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + + - name: Set up pnpm + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0 + with: + version: 10.15 + run_install: false + + - name: Set up Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: '22' + cache: pnpm + cache-dependency-path: | + pnpm-lock.yaml + **/pnpm-lock.yaml + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Deploy to Trigger.dev production + run: pnpm trigger:deploy + env: + TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }} + TRIGGER_PROJECT_ID: ${{ secrets.TRIGGER_PROJECT_ID }} + VERCEL_ENV: production + SENTRY_ORG: ${{ secrets.SENTRY_ORG }} + SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + NEXT_PUBLIC_SENTRY_DSN: ${{ secrets.NEXT_PUBLIC_SENTRY_DSN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 51e5949..3109a5f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,8 +1,7 @@ name: Tests -# pull_request also covers fork PRs, which never trigger push upstream. +# pull_request covers fork PRs too; merges to main are checked by deploy.yml. on: - push: pull_request: permissions: @@ -15,6 +14,8 @@ concurrency: env: CI: true +# Actions pinned to full commit SHAs: tags are mutable and can be moved +# after a compromise. Bump SHA + version comment together. jobs: unit-tests: name: Unit tests @@ -22,16 +23,16 @@ jobs: timeout-minutes: 10 steps: - name: Check out repository - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Set up pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0 with: version: 10.15 run_install: false - name: Set up Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '22' cache: pnpm @@ -53,16 +54,16 @@ jobs: timeout-minutes: 20 steps: - name: Check out repository - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Set up pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0 with: version: 10.15 run_install: false - name: Set up Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '22' cache: pnpm diff --git a/package.json b/package.json index 2383118..0657df1 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "version": "0.1.0", "scripts": { "trigger:dev": "npx trigger.dev@4.0.6 dev", + "trigger:deploy": "npx trigger.dev@4.0.6 deploy", "dev": "next dev --turbopack", "concurrent:dev": "npx concurrently --kill-others --names \"next,trigger\" --prefix-colors \"yellow,blue\" \"next dev --turbopack\" \"npm run trigger:dev\"", "build": "next build --turbopack", From 139e68f1bad1770b0531cd8bc323f499912647c6 Mon Sep 17 00:00:00 2001 From: SandipBajracharya Date: Tue, 15 Sep 2026 14:59:09 +0545 Subject: [PATCH 2/3] fix(OUT-4184): make production deploy non-cancelable Greptile P1: workflow-level cancel-in-progress could cancel an in-flight deploy when a newer push arrived; if that push then failed a check, neither commit deployed. Move concurrency to the deploy job with cancel-in-progress: false so deploys serialize and a running publish is never interrupted. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/deploy.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5f84046..131c2f1 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -7,11 +7,6 @@ on: permissions: contents: read -# Newer push to main supersedes an in-flight deploy run. -concurrency: - group: deploy-production - cancel-in-progress: true - env: CI: true @@ -143,6 +138,11 @@ jobs: runs-on: ubuntu-latest environment: Production timeout-minutes: 20 + # Serialize deploys and never cancel one mid-publish: a newer push waits + # rather than interrupting an in-flight production deploy. + concurrency: + group: deploy-production + cancel-in-progress: false steps: - name: Check out repository uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 From 321f969767ee56a871695c7ce130533082ecc7ef Mon Sep 17 00:00:00 2001 From: SandipBajracharya Date: Tue, 15 Sep 2026 15:20:28 +0545 Subject: [PATCH 3/3] fix(OUT-4184): skip deploy when a newer commit is on the branch With serial (non-cancelable) deploys, a slower older run could publish after a newer one and leave production on stale code. Guard the deploy step: if this run's commit is no longer the branch tip, skip it so only the newest commit publishes. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/deploy.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 131c2f1..45a2ebd 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -165,7 +165,20 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + # Skip if a newer commit already landed on the branch, so a slower older + # run can't publish stale code over a newer deploy. + - name: Skip if a newer commit is on the branch + id: freshness + run: | + git fetch --depth=1 origin "$GITHUB_REF_NAME" + latest="$(git rev-parse FETCH_HEAD)" + if [ "$GITHUB_SHA" != "$latest" ]; then + echo "Newer commit $latest on $GITHUB_REF_NAME; skipping stale deploy of $GITHUB_SHA." + echo "skip=true" >> "$GITHUB_OUTPUT" + fi + - name: Deploy to Trigger.dev production + if: steps.freshness.outputs.skip != 'true' run: pnpm trigger:deploy env: TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }}