Deploy #323
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: Deploy | |
| # FR-1/FR-12 — one workflow owns every production deploy: | |
| # - push to main → production deploy (runs when a PR is merged to main) | |
| # - hourly schedule → fresh build (news/release/docs refresh) + production deploy | |
| # - workflow_dispatch → "right now" button | |
| # pull_request is deliberately NOT a trigger: PRs are validated by CI (ci.yml), | |
| # which builds and uploads a `site-preview` artifact — no per-PR preview deploys. | |
| # | |
| # One-time setup (repo Settings, no Cloudflare dashboard clicks needed): | |
| # Secrets: CLOUDFLARE_API_TOKEN (Cloudflare Pages: Edit), CLOUDFLARE_ACCOUNT_ID | |
| # Variable: PAGES_PROJECT (e.g. keeltrading-com) | |
| # Until those exist, every run builds and then skips the deploy gracefully. | |
| on: | |
| push: | |
| branches: [main] | |
| schedule: | |
| # Hourly at :07, off the top of the hour. | |
| - cron: "7 * * * *" | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: deploy-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| deploy: | |
| name: Build & deploy to Cloudflare Pages | |
| runs-on: ubuntu-latest | |
| env: | |
| PAGES_PROJECT: ${{ vars.PAGES_PROJECT }} | |
| # Cookieless Web Analytics beacon (FR-11) — omitted from the build when unset. | |
| PUBLIC_CF_ANALYTICS_TOKEN: ${{ vars.PUBLIC_CF_ANALYTICS_TOKEN }} | |
| # Google Search Console verification meta tag (#61) — public by design. | |
| PUBLIC_GSC_VERIFICATION_TOKEN: ${{ vars.PUBLIC_GSC_VERIFICATION_TOKEN }} | |
| # Email announcements (#65) — form renders only when the backend is wired. | |
| PUBLIC_SUBSCRIPTIONS_ENABLED: ${{ vars.PUBLIC_SUBSCRIPTIONS_ENABLED }} | |
| HAS_DEPLOY_SECRETS: ${{ secrets.CLOUDFLARE_API_TOKEN != '' && secrets.CLOUDFLARE_ACCOUNT_ID != '' && 'true' || 'false' }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci --ignore-scripts | |
| # FR-4: `npm run build` chains the fetch scripts before astro build — a | |
| # moved engine doc fails here and blocks the deploy by design. (Never | |
| # call `astro build` directly in CI: the fetched content collection is | |
| # gitignored and would silently render a site without the docs pages.) | |
| - name: Build site | |
| run: npm run build | |
| env: | |
| # The fetch scripts hit api.github.com; unauthenticated calls from | |
| # shared Actions runner IPs are rate-limited (60/hr per IP), which | |
| # silently degraded the Install and News pages to their fallbacks. | |
| # The workflow's built-in token removes the limit — no new secrets. | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Skip deploy (Cloudflare not configured yet) | |
| if: env.PAGES_PROJECT == '' || env.HAS_DEPLOY_SECRETS != 'true' | |
| run: | | |
| echo "PAGES_PROJECT or Cloudflare secrets not configured — deploy skipped." | |
| echo "Setup: repo secrets CLOUDFLARE_API_TOKEN (Pages:Edit) + CLOUDFLARE_ACCOUNT_ID," | |
| echo "and variable PAGES_PROJECT. See docs/DEPLOYMENT.md." | |
| - name: Deploy | |
| if: env.PAGES_PROJECT != '' && env.HAS_DEPLOY_SECRETS == 'true' | |
| env: | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| BRANCH: ${{ github.head_ref || github.ref_name }} | |
| run: | | |
| # Create the project on first run; "already exists" is fine. | |
| ./node_modules/.bin/wrangler pages project create "$PAGES_PROJECT" --production-branch main || \ | |
| echo "project exists (or deploy will surface the real error)" | |
| ./node_modules/.bin/wrangler pages deploy dist \ | |
| --project-name="$PAGES_PROJECT" \ | |
| --branch="$BRANCH" \ | |
| --commit-hash="$GITHUB_SHA" | |
| # Email announcements (#65): after a production deploy, ask the Pages | |
| # Function to email any announcements newer than the last-sent marker. | |
| # Non-fatal by design — the site itself is already live. (The secret | |
| # check lives in the shell: `secrets` is not valid in a step `if`.) | |
| - name: Dispatch announcement emails | |
| if: github.ref == 'refs/heads/main' && env.PAGES_PROJECT != '' && env.HAS_DEPLOY_SECRETS == 'true' | |
| env: | |
| DISPATCH_ADMIN_TOKEN: ${{ secrets.DISPATCH_ADMIN_TOKEN }} | |
| run: | | |
| if [ -z "$DISPATCH_ADMIN_TOKEN" ]; then | |
| echo "DISPATCH_ADMIN_TOKEN not set — skipping (activation checklist on #65)." | |
| exit 0 | |
| fi | |
| sleep 20 # let the new deployment settle at the edge | |
| curl -sS -X POST -H "X-Admin-Token: $DISPATCH_ADMIN_TOKEN" \ | |
| -w "\nHTTP %{http_code}\n" \ | |
| https://keeltrading.com/api/dispatch-send || echo "(dispatch failed — site unaffected)" |