Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 88 additions & 16 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,24 @@ on:
required: false
type: boolean
default: false
cloudflare-production-on-release:
description: >-
Deploy Cloudflare production only on GitHub release events instead of
on every production-branch commit. The caller must also trigger the
workflow on the release event.
required: false
type: boolean
default: false
cloudflare-acceptance:
description: "Deploy an acceptance build to Cloudflare Pages on production-branch commits"
required: false
type: boolean
default: false
cloudflare-acceptance-branch:
description: "Cloudflare branch name used for acceptance deploys (gives a stable acceptance URL)"
required: false
type: string
default: "acceptance"
cloudflare-project-name:
description: "Cloudflare Pages project name; defaults to the repo name"
required: false
Expand Down Expand Up @@ -113,31 +131,43 @@ jobs:
outputs:
enabled: ${{ steps.detect.outputs.enabled }}
project-name: ${{ steps.detect.outputs.project-name }}
# JSON array of production-style deploy targets ({target, environment,
# branch}) that should run for this event. Empty when nothing deploys.
targets: ${{ steps.detect.outputs.targets }}
steps:
- name: Detect Cloudflare secrets
id: detect
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_PRODUCTION: ${{ inputs.cloudflare-production }}
CLOUDFLARE_PRODUCTION_ON_RELEASE: ${{ inputs.cloudflare-production-on-release }}
CLOUDFLARE_ACCEPTANCE: ${{ inputs.cloudflare-acceptance }}
CLOUDFLARE_PREVIEW: ${{ inputs.cloudflare-preview }}
CLOUDFLARE_PRODUCTION_BRANCH: ${{ inputs.cloudflare-production-branch }}
CLOUDFLARE_ACCEPTANCE_BRANCH: ${{ inputs.cloudflare-acceptance-branch }}
PRODUCTION_BRANCH: ${{ inputs.production-branch }}
EVENT_NAME: ${{ github.event_name }}
GIT_REF: ${{ github.ref }}
CONFIGURED_PROJECT_NAME: ${{ inputs.cloudflare-project-name }}
REPOSITORY_NAME: ${{ github.event.repository.name }}
run: |-
set -euo pipefail

project_name="${CONFIGURED_PROJECT_NAME:-$REPOSITORY_NAME}"

if [ "${CLOUDFLARE_PREVIEW}" != "true" ] && [ "${CLOUDFLARE_PRODUCTION}" != "true" ]; then
if [ "${CLOUDFLARE_PREVIEW}" != "true" ] && [ "${CLOUDFLARE_PRODUCTION}" != "true" ] && [ "${CLOUDFLARE_ACCEPTANCE}" != "true" ]; then
echo "enabled=false" >>"${GITHUB_OUTPUT}"
echo "targets=[]" >>"${GITHUB_OUTPUT}"
echo "Cloudflare Pages deployments are disabled by input."
exit 0
fi

if [ -z "${CLOUDFLARE_API_TOKEN}" ] || [ -z "${CLOUDFLARE_ACCOUNT_ID}" ]; then
echo "enabled=false" >>"${GITHUB_OUTPUT}"
if [ "${CLOUDFLARE_PRODUCTION}" = "true" ]; then
echo "::error::cloudflare-production is enabled, but CLOUDFLARE_API_TOKEN or CLOUDFLARE_ACCOUNT_ID is missing."
echo "targets=[]" >>"${GITHUB_OUTPUT}"
if [ "${CLOUDFLARE_PRODUCTION}" = "true" ] || [ "${CLOUDFLARE_ACCEPTANCE}" = "true" ]; then
echo "::error::cloudflare-production or cloudflare-acceptance is enabled, but CLOUDFLARE_API_TOKEN or CLOUDFLARE_ACCOUNT_ID is missing."
exit 1
fi

Expand All @@ -155,9 +185,45 @@ jobs:
exit 1
fi

echo "project-name=${project_name}" >>"${GITHUB_OUTPUT}"
echo "enabled=true" >>"${GITHUB_OUTPUT}"
echo "Cloudflare Pages deployments are configured."
# Work out which production-style targets deploy for this event.
# Pull request previews are handled by a separate job.
is_production_branch="false"
if [ "${EVENT_NAME}" != "pull_request" ] && [ "${GIT_REF}" = "refs/heads/${PRODUCTION_BRANCH}" ]; then
is_production_branch="true"
fi

deploy_production="false"
if [ "${CLOUDFLARE_PRODUCTION}" = "true" ]; then
if [ "${CLOUDFLARE_PRODUCTION_ON_RELEASE}" = "true" ]; then
[ "${EVENT_NAME}" = "release" ] && deploy_production="true"
else
[ "${is_production_branch}" = "true" ] && deploy_production="true"
fi
fi

deploy_acceptance="false"
if [ "${CLOUDFLARE_ACCEPTANCE}" = "true" ] && [ "${is_production_branch}" = "true" ]; then
deploy_acceptance="true"
fi

targets="$(
jq -cn \
--arg prod_branch "${CLOUDFLARE_PRODUCTION_BRANCH}" \
--arg acc_branch "${CLOUDFLARE_ACCEPTANCE_BRANCH}" \
--argjson prod "${deploy_production}" \
--argjson acc "${deploy_acceptance}" \
'[
(if $prod then {target: "production", environment: "cloudflare-pages", branch: $prod_branch} else empty end),
(if $acc then {target: "acceptance", environment: "cloudflare-pages-acceptance", branch: $acc_branch} else empty end)
]'
)"

{
echo "project-name=${project_name}"
echo "enabled=true"
echo "targets=${targets}"
} >>"${GITHUB_OUTPUT}"
echo "Cloudflare Pages deployments are configured (targets: ${targets})."

test:
name: Run tests
Expand Down Expand Up @@ -279,23 +345,27 @@ jobs:
id: deployment
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0

deploy-cloudflare-production:
name: Deploy Cloudflare production
deploy-cloudflare:
name: Deploy Cloudflare ${{ matrix.target }}
needs:
- detect-cloudflare
- test
if: >-
inputs.cloudflare-production &&
github.event_name != 'pull_request' &&
github.ref == format('refs/heads/{0}', inputs.production-branch) &&
needs.detect-cloudflare.outputs.enabled == 'true'
needs.detect-cloudflare.outputs.enabled == 'true' &&
needs.detect-cloudflare.outputs.targets != '[]'
strategy:
fail-fast: false
# Production and acceptance share the same build and deploy steps; the
# detect-cloudflare job decides which targets run for this event.
matrix:
include: ${{ fromJSON(needs.detect-cloudflare.outputs.targets) }}
runs-on: ubuntu-24.04
timeout-minutes: 10
concurrency:
group: cloudflare-pages-${{ github.repository }}
group: cloudflare-pages-${{ matrix.target }}-${{ github.repository }}
cancel-in-progress: false
environment:
name: cloudflare-pages
name: ${{ matrix.environment }}
url: ${{ steps.deploy.outputs.deployment-url }}
permissions:
contents: read
Expand All @@ -314,7 +384,9 @@ jobs:
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
with:
node-version: ${{ inputs.node-version }}
cache: ${{ inputs.node-cache }}
# Disable the package-manager cache on deploy jobs so a poisoned
# cache cannot influence the published artifact (zizmor audit).
package-manager-cache: false

- name: Install dependencies
if: ${{ inputs.build-command != '' && inputs.install-command != '' }}
Expand Down Expand Up @@ -354,7 +426,7 @@ jobs:
command: >-
pages deploy ${{ inputs.artifact-path }}
--project-name=${{ needs.detect-cloudflare.outputs.project-name }}
--branch=${{ inputs.cloudflare-production-branch }}
--branch=${{ matrix.branch }}
wranglerVersion: ${{ inputs.wrangler-version }}

deploy-preview:
Expand Down
45 changes: 24 additions & 21 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,27 +136,30 @@ optionally deploys same-repository pull request previews to Cloudflare Pages.
Cloudflare jobs detect missing Cloudflare secrets before any deploy work. Missing
secrets fail production Cloudflare deploys and skip preview-only deploys.

| Input | Description |
| ------------------------------ | ------------------------------------------------------------------------------------- |
| `node-version` | **Required.** Node.js version to install. |
| `node-cache` | Package manager cache for test and production jobs. Default: empty (disabled). |
| `wrangler-version` | **Required.** Wrangler version to install for previews; inputs cannot be conditional. |
| `production-branch` | Branch that deploys to production. Default: `main`. |
| `artifact-path` | Directory uploaded to Pages. Default: `.`. |
| `install-command` | Dependency install command. Default: `npm ci`. |
| `test-command` | Validation command block. Default: empty. |
| `test-setup-command` | Optional command after install and before tests. |
| `build-command` | Optional build command before deployment. |
| `pre-deploy-command` | Optional production-only pre-upload command. |
| `pre-preview-command` | Optional preview-only pre-deploy command. |
| `update-sitemap-lastmod` | Update sitemap `<lastmod>` dates. Default: `false`. |
| `sitemap-path` | Sitemap file path. Default: `sitemap.xml`. |
| `github-pages` | Deploy production to GitHub Pages. Default: `true`. |
| `cloudflare-preview` | Enable Cloudflare pull request previews. Default: `true`. |
| `cloudflare-production` | Deploy production to Cloudflare Pages. Default: `false`. |
| `cloudflare-project-name` | Cloudflare Pages project; lowercase letters, numbers, and hyphens only. |
| `cloudflare-production-branch` | Cloudflare production branch. Default: `main`. |
| `preview-comment-marker` | Marker used to update the preview PR comment. |
| Input | Description |
| ---------------------------------- | ----------------------------------------------------------------------------------------------- |
| `node-version` | **Required.** Node.js version to install. |
| `node-cache` | Package manager cache for test and production jobs. Default: empty (disabled). |
| `wrangler-version` | **Required.** Wrangler version to install for previews; inputs cannot be conditional. |
| `production-branch` | Branch that deploys to production. Default: `main`. |
| `artifact-path` | Directory uploaded to Pages. Default: `.`. |
| `install-command` | Dependency install command. Default: `npm ci`. |
| `test-command` | Validation command block. Default: empty. |
| `test-setup-command` | Optional command after install and before tests. |
| `build-command` | Optional build command before deployment. |
| `pre-deploy-command` | Optional production-only pre-upload command. |
| `pre-preview-command` | Optional preview-only pre-deploy command. |
| `update-sitemap-lastmod` | Update sitemap `<lastmod>` dates. Default: `false`. |
| `sitemap-path` | Sitemap file path. Default: `sitemap.xml`. |
| `github-pages` | Deploy production to GitHub Pages. Default: `true`. |
| `cloudflare-preview` | Enable Cloudflare pull request previews. Default: `true`. |
| `cloudflare-production` | Deploy production to Cloudflare Pages. Default: `false`. |
| `cloudflare-production-on-release` | Deploy Cloudflare production only on `release` events, not every main commit. Default: `false`. |
| `cloudflare-acceptance` | Deploy an acceptance build to Cloudflare on main commits. Default: `false`. |
| `cloudflare-acceptance-branch` | Cloudflare branch name for acceptance deploys. Default: `acceptance`. |
| `cloudflare-project-name` | Cloudflare Pages project; lowercase letters, numbers, and hyphens only. |
| `cloudflare-production-branch` | Cloudflare production branch. Default: `main`. |
| `preview-comment-marker` | Marker used to update the preview PR comment. |

**Example caller:**

Expand Down
5 changes: 5 additions & 0 deletions workflow-templates/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ jobs:
# github-pages: false
# cloudflare-production: true
# cloudflare-project-name: "my-site"
# Optional: deploy every main commit to an acceptance environment and
# reserve production for releases. Also add the release trigger to `on:`
# above, e.g. `release: { types: [published] }`.
# cloudflare-acceptance: true
# cloudflare-production-on-release: true
secrets:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
Loading