Repository navigation
chore(actions): bump the github-actions group with 3 updates #198
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: Deploy | |
| # Continuous deployment to Cloudflare (ADR 0029 (all-cloudflare-hosting), issue #111). | |
| # | |
| # On merge to main: build and deploy the stacker frontend to Cloudflare Pages | |
| # and the retriever + petdata backends to Cloudflare Containers (Worker-fronted). | |
| # A gated Alembic migration runs against Supabase before each backend revision | |
| # takes traffic; a migration failure fails the job and the cutover step never | |
| # runs. On pull requests: preview deploys only (no production traffic, no | |
| # migration against the production database). | |
| # | |
| # Manual runs (workflow_dispatch) behave as a production deploy: the operator | |
| # triggers one for the first cutover and for redeploys after setting secrets, | |
| # without needing a code change. A manual run deploys all three services by | |
| # default; the `service` input narrows it to a single service. Like a push, a | |
| # manual run runs the gated migration and cutover (never a preview). | |
| # | |
| # Secrets (never committed) come from Actions secrets: | |
| # CLOUDFLARE_API_TOKEN scoped token: Pages + Workers + Containers deploy | |
| # CLOUDFLARE_ACCOUNT_ID Cloudflare account | |
| # RETRIEVER_DATABASE_URL retriever Supabase pooler URL (alembic DATABASE_URL) | |
| # PETDATA_DATABASE_URL petdata Supabase pooler URL | |
| # | |
| # Until an operator configures CLOUDFLARE_API_TOKEN (the issue's operator step), | |
| # the preflight job below reports the secret as absent and every deploy job is | |
| # skipped, not failed. The same skip covers pull requests from forks, which do | |
| # not receive repository secrets, and manual runs before the token is set. Once | |
| # the token is set, deploys and previews run. | |
| # | |
| # Hyperdrive (issue #109) is deferred: the migration and the containers connect | |
| # directly to the Supabase pooler via the *_DATABASE_URL secrets. When #109 | |
| # lands, only the connection string behind these secrets changes. | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: | |
| inputs: | |
| service: | |
| description: "Service to deploy" | |
| type: choice | |
| default: all | |
| options: | |
| - all | |
| - petdata | |
| - retriever | |
| - stacker | |
| # Never cancel a production deploy mid-migration/cutover; PR previews are cheap | |
| # to leave running. | |
| concurrency: | |
| group: deploy-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: false | |
| permissions: | |
| contents: read | |
| deployments: write | |
| pull-requests: write | |
| # wrangler-action installs an old default Wrangler (3.90) when no local install | |
| # exists, and that version predates wrangler.jsonc support, so it cannot read the | |
| # backends' container config. Pin a modern Wrangler for every deploy step. | |
| env: | |
| WRANGLER_VERSION: "4.105.0" | |
| jobs: | |
| changes: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| petdata: ${{ steps.resolve.outputs.petdata }} | |
| retriever: ${{ steps.resolve.outputs.retriever }} | |
| stacker: ${{ steps.resolve.outputs.stacker }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| # paths-filter needs a diff base, which a manual dispatch has no commit | |
| # range for, so run it only for push/pull_request and resolve manual runs | |
| # from the `service` input below. | |
| - uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 | |
| id: filter | |
| if: ${{ github.event_name != 'workflow_dispatch' }} | |
| with: | |
| filters: | | |
| petdata: | |
| - 'services/petdata/**' | |
| - '.github/workflows/deploy.yml' | |
| retriever: | |
| - 'services/retriever/**' | |
| - '.github/workflows/deploy.yml' | |
| stacker: | |
| - 'apps/stacker/**' | |
| - '.github/workflows/deploy.yml' | |
| # On manual dispatch, select services from the `service` input ('all' picks | |
| # all three); otherwise pass the paths-filter results through unchanged. | |
| - id: resolve | |
| env: | |
| SERVICE: ${{ github.event.inputs.service }} | |
| FILTER_PETDATA: ${{ steps.filter.outputs.petdata }} | |
| FILTER_RETRIEVER: ${{ steps.filter.outputs.retriever }} | |
| FILTER_STACKER: ${{ steps.filter.outputs.stacker }} | |
| run: | | |
| { | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| for svc in petdata retriever stacker; do | |
| if [ "$SERVICE" = "all" ] || [ "$SERVICE" = "$svc" ]; then | |
| echo "$svc=true" | |
| else | |
| echo "$svc=false" | |
| fi | |
| done | |
| else | |
| echo "petdata=$FILTER_PETDATA" | |
| echo "retriever=$FILTER_RETRIEVER" | |
| echo "stacker=$FILTER_STACKER" | |
| fi | |
| } >> "$GITHUB_OUTPUT" | |
| # Secrets cannot be read in a job-level `if`, so detect the Cloudflare token in | |
| # a step and expose the result as an output the deploy jobs gate on. When it is | |
| # absent (operator has not set it yet, or a fork PR), the deploy jobs skip. | |
| preflight: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| configured: ${{ steps.check.outputs.configured }} | |
| steps: | |
| - id: check | |
| env: | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| run: | | |
| if [ -n "$CLOUDFLARE_API_TOKEN" ]; then | |
| echo "configured=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "configured=false" >> "$GITHUB_OUTPUT" | |
| echo "::notice::CLOUDFLARE_API_TOKEN is not set; skipping all deploy jobs." | |
| fi | |
| deploy-stacker: | |
| needs: [changes, preflight] | |
| if: ${{ needs.changes.outputs.stacker == 'true' && needs.preflight.outputs.configured == 'true' }} | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: apps/stacker | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| cache-dependency-path: apps/stacker/package-lock.json | |
| # Inline the production public env at build time. SvelteKit bakes | |
| # $env/static/public values (PUBLIC_SUPABASE_URL/PUBLISHABLE_KEY, the backend | |
| # URLs) into the bundle during `npm run build`, so they must be the | |
| # production values here rather than the localhost defaults in | |
| # .env.example. Supabase values come from Actions secrets (the publishable key | |
| # is browser-safe but managed as a secret); the backend URLs default to | |
| # the production subdomains and can be overridden by Actions variables. | |
| - name: Write production env | |
| env: | |
| PUBLIC_SUPABASE_URL: ${{ secrets.STACKER_PUBLIC_SUPABASE_URL }} | |
| PUBLIC_SUPABASE_PUBLISHABLE_KEY: ${{ secrets.STACKER_PUBLIC_SUPABASE_PUBLISHABLE_KEY }} | |
| PUBLIC_RETRIEVER_API_URL: ${{ vars.STACKER_PUBLIC_RETRIEVER_API_URL || 'https://retriever.evermore.backchain.app' }} | |
| PUBLIC_PETDATA_API_URL: ${{ vars.STACKER_PUBLIC_PETDATA_API_URL || 'https://petdata.evermore.backchain.app' }} | |
| run: | | |
| { | |
| echo "PUBLIC_SUPABASE_URL=$PUBLIC_SUPABASE_URL" | |
| echo "PUBLIC_SUPABASE_PUBLISHABLE_KEY=$PUBLIC_SUPABASE_PUBLISHABLE_KEY" | |
| echo "PUBLIC_RETRIEVER_API_URL=$PUBLIC_RETRIEVER_API_URL" | |
| echo "PUBLIC_PETDATA_API_URL=$PUBLIC_PETDATA_API_URL" | |
| echo "PUBLIC_ENABLED_MODULES=retriever" | |
| } > .env | |
| - run: npm ci | |
| - run: npm run build | |
| # push or manual dispatch -> production branch deploy; PR head branch -> | |
| # preview deploy (Cloudflare Pages treats any non-production branch as a | |
| # preview). Project name and build-output dir come from | |
| # apps/stacker/wrangler.toml. DEPLOY_BRANCH is passed as an env var and | |
| # referenced with quoted shell expansion (not GitHub template | |
| # interpolation) so an attacker-controlled PR branch name cannot inject | |
| # into the deploy command. | |
| - name: Deploy to Cloudflare Pages | |
| # wrangler-action does not shell-expand its `command` string, so a branch | |
| # passed as --branch="$DEPLOY_BRANCH" reached wrangler as the literal | |
| # "$DEPLOY_BRANCH", which Cloudflare sanitized to a phantom "deploy-branch" | |
| # preview: the production slot never filled. A direct run step lets the | |
| # shell expand DEPLOY_BRANCH from the environment (injection-safe: an env | |
| # var, not a ${{ }} interpolation of an attacker-controlled branch name), | |
| # so the real branch reaches wrangler and a push or manual dispatch on the | |
| # production branch produces a production deployment. Project name and | |
| # build-output dir come from apps/stacker/wrangler.toml; the job already | |
| # runs in apps/stacker via the job-level working-directory default. | |
| env: | |
| DEPLOY_BRANCH: ${{ (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref_name || github.head_ref }} | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| run: npx --yes wrangler@${{ env.WRANGLER_VERSION }} pages deploy --branch="$DEPLOY_BRANCH" | |
| deploy-retriever: | |
| needs: [changes, preflight] | |
| if: ${{ needs.changes.outputs.retriever == 'true' && needs.preflight.outputs.configured == 'true' }} | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: services/retriever | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 | |
| with: | |
| enable-cache: true | |
| cache-dependency-glob: services/retriever/uv.lock | |
| - run: uv sync | |
| # The Worker entry (worker/index.ts) imports @cloudflare/containers from the | |
| # Worker's isolated npm project (worker/package.json). wrangler's esbuild | |
| # bundler resolves it from worker/node_modules, so install those deps before | |
| # any wrangler step or the build fails with "Could not resolve". | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| cache-dependency-path: services/retriever/worker/package-lock.json | |
| - name: Install Worker dependencies | |
| working-directory: services/retriever/worker | |
| run: npm ci | |
| # Gated migration (production only): must succeed before the new container | |
| # revision takes traffic (ADR 0029 (all-cloudflare-hosting)). A failure here fails the job, so the | |
| # wrangler deploy step below never runs and production keeps the old revision. | |
| # Push and manual dispatch are production; pull requests are not. | |
| - name: Migrate database (gated, production only) | |
| if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }} | |
| env: | |
| DATABASE_URL: ${{ secrets.RETRIEVER_DATABASE_URL }} | |
| run: uv run alembic upgrade head | |
| # Production cutover on merge to main or manual dispatch: builds the | |
| # container image from the Dockerfile and deploys the Worker + Container, | |
| # shifting traffic. | |
| - name: Deploy container (production) | |
| if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }} | |
| uses: cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4.0.0 | |
| with: | |
| apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| wranglerVersion: ${{ env.WRANGLER_VERSION }} | |
| workingDirectory: services/retriever | |
| command: deploy | |
| # Preview on PRs: upload a new version (with its container image) that gets | |
| # a preview URL and takes no production traffic. No migration runs against | |
| # the production database for previews. | |
| - name: Upload preview version (pull request) | |
| if: ${{ github.event_name == 'pull_request' }} | |
| uses: cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4.0.0 | |
| with: | |
| apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| wranglerVersion: ${{ env.WRANGLER_VERSION }} | |
| workingDirectory: services/retriever | |
| command: versions upload | |
| deploy-petdata: | |
| needs: [changes, preflight] | |
| if: ${{ needs.changes.outputs.petdata == 'true' && needs.preflight.outputs.configured == 'true' }} | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: services/petdata | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 | |
| with: | |
| enable-cache: true | |
| cache-dependency-glob: services/petdata/uv.lock | |
| - run: uv sync --extra dev | |
| # The Worker entry (worker/index.ts) imports @cloudflare/containers from the | |
| # Worker's isolated npm project (worker/package.json). wrangler's esbuild | |
| # bundler resolves it from worker/node_modules, so install those deps before | |
| # any wrangler step or the build fails with "Could not resolve". | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| cache-dependency-path: services/petdata/worker/package-lock.json | |
| - name: Install Worker dependencies | |
| working-directory: services/petdata/worker | |
| run: npm ci | |
| # Gated migration (production only): see deploy-retriever for rationale. | |
| # petdata's alembic env reads PETDATA_DATABASE_URL. Push and manual | |
| # dispatch are production; pull requests are not. | |
| - name: Migrate database (gated, production only) | |
| if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }} | |
| env: | |
| PETDATA_DATABASE_URL: ${{ secrets.PETDATA_DATABASE_URL }} | |
| run: uv run alembic upgrade head | |
| - name: Deploy container (production) | |
| if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }} | |
| uses: cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4.0.0 | |
| with: | |
| apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| wranglerVersion: ${{ env.WRANGLER_VERSION }} | |
| workingDirectory: services/petdata | |
| command: deploy | |
| - name: Upload preview version (pull request) | |
| if: ${{ github.event_name == 'pull_request' }} | |
| uses: cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4.0.0 | |
| with: | |
| apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| wranglerVersion: ${{ env.WRANGLER_VERSION }} | |
| workingDirectory: services/petdata | |
| command: versions upload |