Merge pull request #3 from OrigenStudio/ci/upstream-sync-check #16
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI/CD | |
| # Single-pane CI/CD for the Mike monorepo, with two fully isolated environments. | |
| # | |
| # PR -> main or staging ci-backend + ci-frontend (build / lint / typecheck) only. | |
| # push to staging CI, then deploy changed package(s) to the STAGING env. | |
| # push to main CI, then deploy changed package(s) to the PRODUCTION env. | |
| # "Run workflow" manual deploy; target env follows the branch you run it from. | |
| # | |
| # The deploy jobs pick their GitHub Environment from the branch, so the SAME secret | |
| # names resolve to different values per environment. Configure these under | |
| # Settings -> Environments -> (staging | production): | |
| # | |
| # Environment secrets (set in BOTH environments, with each env's own values): | |
| # NEXT_PUBLIC_SUPABASE_URL baked into the frontend bundle | |
| # NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY Supabase anon/public key | |
| # NEXT_PUBLIC_API_BASE_URL public URL of that env's backend | |
| # RAILWAY_TOKEN Railway *project* token for that env | |
| # | |
| # Environment variable (set in BOTH environments): | |
| # RAILWAY_SERVICE backend service name in that env | |
| # | |
| # Repository-level secrets (shared across environments — same Cloudflare account): | |
| # CLOUDFLARE_API_TOKEN token with "Edit Workers" | |
| # CLOUDFLARE_ACCOUNT_ID Cloudflare account id | |
| # | |
| # Cloudflare worker names come from frontend/wrangler.jsonc env blocks: | |
| # staging -> mike-frontend-staging | |
| # production -> mike-frontend-production | |
| on: | |
| push: | |
| branches: [main, staging] | |
| pull_request: | |
| branches: [main, staging] | |
| workflow_dispatch: | |
| # Cancel superseded PR runs; never cancel an in-flight deploy. | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| permissions: | |
| contents: read | |
| jobs: | |
| # -------------------------------------------------------------------------- | |
| # Detect which package changed, to gate the deploy jobs. | |
| # -------------------------------------------------------------------------- | |
| changes: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| backend: ${{ steps.filter.outputs.backend }} | |
| frontend: ${{ steps.filter.outputs.frontend }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: dorny/paths-filter@v3 | |
| id: filter | |
| with: | |
| filters: | | |
| backend: | |
| - 'backend/**' | |
| - '.github/workflows/**' | |
| frontend: | |
| - 'frontend/**' | |
| - '.github/workflows/**' | |
| # -------------------------------------------------------------------------- | |
| # CI: backend (always runs — safe to mark as a required status check) | |
| # -------------------------------------------------------------------------- | |
| ci-backend: | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: backend | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: actions/setup-node@v5 | |
| with: | |
| node-version: 20 | |
| cache: npm | |
| cache-dependency-path: backend/package-lock.json | |
| - run: npm ci | |
| - name: Typecheck / build (tsc) | |
| run: npm run build | |
| # -------------------------------------------------------------------------- | |
| # CI: frontend (always runs — safe to mark as a required status check) | |
| # Placeholder NEXT_PUBLIC_* values: the build only needs them to be non-empty | |
| # so the Supabase client constructed at module load does not throw. | |
| # -------------------------------------------------------------------------- | |
| ci-frontend: | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: frontend | |
| env: | |
| NEXT_PUBLIC_SUPABASE_URL: https://placeholder.supabase.co | |
| NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY: placeholder-anon-key | |
| NEXT_PUBLIC_API_BASE_URL: http://localhost:3001 | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: actions/setup-node@v5 | |
| with: | |
| node-version: 20 | |
| cache: npm | |
| cache-dependency-path: frontend/package-lock.json | |
| - run: npm ci | |
| # Lint is advisory for now: the existing codebase has pre-existing eslint | |
| # errors (no-explicit-any, unescaped entities, react-compiler, etc.). This | |
| # surfaces them as annotations without blocking the pipeline. Once the | |
| # backlog is cleared, drop `continue-on-error` to make lint a hard gate. | |
| - name: Lint (advisory) | |
| run: npm run lint | |
| continue-on-error: true | |
| - name: Build (next) | |
| run: npm run build | |
| # -------------------------------------------------------------------------- | |
| # CD: backend -> Railway (Nixpacks builds remotely; LibreOffice baked in) | |
| # Target env = production on main, staging otherwise. Secrets/vars resolve | |
| # from the matching GitHub Environment. | |
| # -------------------------------------------------------------------------- | |
| deploy-backend: | |
| needs: [changes, ci-backend] | |
| if: >- | |
| (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && | |
| (github.ref_name == 'main' || github.ref_name == 'staging') && | |
| (github.event_name == 'workflow_dispatch' || needs.changes.outputs.backend == 'true') | |
| runs-on: ubuntu-latest | |
| environment: ${{ github.ref_name == 'main' && 'production' || 'staging' }} | |
| defaults: | |
| run: | |
| working-directory: backend | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Install Railway CLI | |
| run: npm i -g @railway/cli | |
| - name: Deploy | |
| env: | |
| RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }} | |
| run: railway up --service "${{ vars.RAILWAY_SERVICE }}" --ci | |
| # -------------------------------------------------------------------------- | |
| # CD: frontend -> Cloudflare Workers (OpenNext build, then wrangler deploy) | |
| # NEXT_PUBLIC_* are inlined into the bundle here, so real values are required. | |
| # `npm run deploy:<env>` targets the matching wrangler env block. | |
| # -------------------------------------------------------------------------- | |
| deploy-frontend: | |
| needs: [changes, ci-frontend] | |
| if: >- | |
| (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && | |
| (github.ref_name == 'main' || github.ref_name == 'staging') && | |
| (github.event_name == 'workflow_dispatch' || needs.changes.outputs.frontend == 'true') | |
| runs-on: ubuntu-latest | |
| environment: ${{ github.ref_name == 'main' && 'production' || 'staging' }} | |
| defaults: | |
| run: | |
| working-directory: frontend | |
| env: | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }} | |
| NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY }} | |
| NEXT_PUBLIC_API_BASE_URL: ${{ secrets.NEXT_PUBLIC_API_BASE_URL }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| # Wrangler 4 requires Node 22+ at deploy time. (ci-frontend still uses | |
| # Node 20 — that's only for typecheck/build, not the wrangler invocation.) | |
| - uses: actions/setup-node@v5 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| cache-dependency-path: frontend/package-lock.json | |
| # OpenNext detects frontend/bun.lock and tries `bun run build`; install | |
| # bun so that path works. (We still use npm ci for our own install.) | |
| - uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - run: npm ci | |
| - name: Build & deploy (OpenNext + Wrangler) | |
| run: npm run deploy:${{ github.ref_name == 'main' && 'production' || 'staging' }} |