Skip to content

Web Deploy

Web Deploy #27

Workflow file for this run

---
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 }}