Web Deploy #27
Workflow file for this run
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: Web Deploy | |
| permissions: | |
| contents: read | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - "apps/web/**" | |
| - "packages/ui/**" | |
| - "pnpm-lock.yaml" | |
| - "package.json" | |
| - "turbo.json" | |
| - ".github/workflows/web-deploy.yml" | |
| - ".github/scripts/web/**" | |
| pull_request: | |
| branches: [main] | |
| types: [opened, reopened, synchronize, closed] | |
| paths: | |
| - "apps/web/**" | |
| - "packages/ui/**" | |
| - "pnpm-lock.yaml" | |
| - "package.json" | |
| - "turbo.json" | |
| - ".github/workflows/web-deploy.yml" | |
| - ".github/scripts/web/**" | |
| merge_group: | |
| branches: [main] | |
| workflow_dispatch: | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| env: | |
| NODE_VERSION: "24" | |
| PNPM_VERSION: "10.28.2" | |
| jobs: | |
| changes: | |
| name: File Detection | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read | |
| outputs: | |
| deploy: ${{ steps.deploy_changes.outputs.any_changed }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check deploy-affecting files | |
| uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47 | |
| id: deploy_changes | |
| with: | |
| files: | | |
| apps/web/src/app/** | |
| apps/web/src/components/** | |
| apps/web/src/lib/** | |
| apps/web/content/** | |
| apps/web/public/** | |
| apps/web/src/data/** | |
| apps/web/src/types/** | |
| apps/web/proxy.ts | |
| apps/web/next.config.ts | |
| apps/web/next-env.d.ts | |
| apps/web/open-next.config.ts | |
| apps/web/tailwind.config.* | |
| apps/web/postcss.config.* | |
| apps/web/tsconfig.json | |
| apps/web/tsconfig.tsbuildinfo | |
| apps/web/eslint.config.mjs | |
| apps/web/prettier.config.mjs | |
| apps/web/components.json | |
| apps/web/src/env.ts | |
| apps/web/alchemy.run.ts | |
| apps/web/trigger.config.ts | |
| apps/web/src/trigger/** | |
| apps/web/scripts/** | |
| .github/scripts/web/** | |
| apps/web/docs/** | |
| apps/web/package.json | |
| pnpm-lock.yaml | |
| packages/ui/** | |
| files_ignore: | | |
| apps/web/**/*.test.* | |
| apps/web/**/*.spec.* | |
| apps/web/src/__tests__/** | |
| apps/web/**/test/** | |
| apps/web/**/tests/** | |
| apps/web/*.md | |
| apps/web/docs/**/*.md | |
| .github/renovate.json5 | |
| deploy: | |
| name: Deploy | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| needs: [changes] | |
| if: >- | |
| (github.event_name != 'pull_request' || github.event.action != 'closed') && | |
| (github.event_name != 'pull_request' || github.event.pull_request.user.login != 'renovate[bot]') && | |
| (needs.changes.outputs.deploy == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'merge_group') | |
| environment: | |
| name: ${{ github.event_name == 'merge_group' && 'dev' || github.event_name == 'pull_request' && 'dev' || 'prod' }} | |
| url: ${{ github.event_name == 'merge_group' && 'https://allthingslinux.dev' || (contains(github.ref, 'main') && 'https://allthingslinux.org' || 'https://allthingslinux.dev') }} | |
| permissions: | |
| contents: read | |
| deployments: write | |
| pull-requests: write | |
| defaults: | |
| run: | |
| working-directory: apps/web | |
| env: | |
| STAGE: ${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || (github.ref == 'refs/heads/main' && 'prod' || 'dev') }} | |
| PULL_REQUEST: ${{ github.event_name == 'pull_request' && 'true' || 'false' }} | |
| GITHUB_PR_NUMBER: ${{ github.event_name == 'pull_request' && github.event.pull_request.number || '' }} | |
| steps: | |
| - name: Harden runner | |
| uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| - name: Setup Node and pnpm | |
| uses: ./.github/actions/setup-node-pnpm | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| pnpm-version: ${{ env.PNPM_VERSION }} | |
| - name: Merge queue — verify production build | |
| if: github.event_name == 'merge_group' | |
| working-directory: ${{ github.workspace }} | |
| run: pnpm exec turbo run build --filter=@atl/web | |
| - name: Setup Cloudflare Bindings (R2, KV) | |
| if: github.event_name != 'merge_group' | |
| run: | | |
| echo "🔧 Setting up Cloudflare bindings (R2, KV) if they don't exist..." | |
| chmod +x scripts/setup-bindings.sh | |
| scripts/setup-bindings.sh || true | |
| env: | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| - name: Deploy with Alchemy (OpenNext build runs in-stack) | |
| if: github.event_name != 'merge_group' | |
| run: | | |
| echo "🚀 Deploying Alchemy app ${{ env.STAGE }} (worker profile from alchemy.run.ts)..." | |
| pnpm exec alchemy deploy --app web --stage "${{ env.STAGE }}" | |
| env: | |
| ALCHEMY_PASSWORD: ${{ secrets.ALCHEMY_PASSWORD }} | |
| ALCHEMY_STATE_TOKEN: ${{ secrets.ALCHEMY_STATE_TOKEN }} | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| CLOUDFLARE_EMAIL: ${{ secrets.CLOUDFLARE_EMAIL }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GITHUB_REPOSITORY: ${{ github.repository }} | |
| - name: Set secrets in Cloudflare Worker | |
| if: github.event_name != 'merge_group' | |
| run: | | |
| chmod +x "${{ github.workspace }}/.github/scripts/web/secrets.sh" | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| bash "${{ github.workspace }}/.github/scripts/web/secrets.sh" "dev" "allthingslinux-${{ env.STAGE }}" | |
| else | |
| ENV_NAME="${{ contains(github.ref, 'refs/heads/main') && 'prod' || 'dev' }}" | |
| bash "${{ github.workspace }}/.github/scripts/web/secrets.sh" "$ENV_NAME" | |
| fi | |
| env: | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| QUICKBOOKS_CLIENT_ID: ${{ secrets.QUICKBOOKS_CLIENT_ID }} | |
| QUICKBOOKS_CLIENT_SECRET: ${{ secrets.QUICKBOOKS_CLIENT_SECRET }} | |
| QUICKBOOKS_REFRESH_TOKEN: ${{ secrets.QUICKBOOKS_REFRESH_TOKEN }} | |
| QUICKBOOKS_REALM_ID: ${{ secrets.QUICKBOOKS_REALM_ID }} | |
| QUICKBOOKS_ADMIN_KEY: ${{ secrets.QUICKBOOKS_ADMIN_KEY }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| MONDAY_API_KEY: ${{ secrets.MONDAY_API_KEY }} | |
| TRIGGER_SECRET_KEY: ${{ secrets.TRIGGER_SECRET_KEY }} | |
| MONDAY_BOARD_ID: ${{ vars.MONDAY_BOARD_ID }} | |
| DISCORD_WEBHOOK_URL: ${{ vars.DISCORD_WEBHOOK_URL }} | |
| QUICKBOOKS_ENVIRONMENT: ${{ vars.QUICKBOOKS_ENVIRONMENT }} | |
| cleanup: | |
| name: Destroy PR preview | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| if: github.event_name == 'pull_request' && github.event.action == 'closed' | |
| permissions: | |
| contents: read | |
| defaults: | |
| run: | |
| working-directory: apps/web | |
| steps: | |
| - name: Harden runner | |
| uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| - name: Setup Node and pnpm | |
| uses: ./.github/actions/setup-node-pnpm | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| pnpm-version: ${{ env.PNPM_VERSION }} | |
| - name: Destroy PR stage (never prod) | |
| run: | | |
| STAGE="pr-${{ github.event.pull_request.number }}" | |
| if [ "$STAGE" = "pr-" ] || [ "${{ github.event.pull_request.number }}" = "" ]; then | |
| echo "Refusing to run destroy without a valid PR number." | |
| exit 1 | |
| fi | |
| if [ "$STAGE" = "prod" ]; then | |
| echo "Refusing to destroy prod." | |
| exit 1 | |
| fi | |
| echo "🧹 Destroying Alchemy stage $STAGE" | |
| pnpm exec alchemy destroy --app web --stage "$STAGE" | |
| env: | |
| ALCHEMY_PASSWORD: ${{ secrets.ALCHEMY_PASSWORD }} | |
| ALCHEMY_STATE_TOKEN: ${{ secrets.ALCHEMY_STATE_TOKEN }} | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| CLOUDFLARE_EMAIL: ${{ secrets.CLOUDFLARE_EMAIL }} |