chore: import upstream snapshot with attribution
Deploy (to testing) and Test Playground Preview Worker / Deploy Playground Preview Worker (testing) (push) Has been skipped
Deploy Workers Shared Staging / Deploy Workers Shared Staging (push) Failing after 0s
Prerelease / build (push) Has been skipped
Handle Changesets / Handle Changesets (push) Has been cancelled
Semgrep OSS scan / semgrep-oss (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:30:11 +08:00
commit 70bf21e064
5213 changed files with 731895 additions and 0 deletions
+151
View File
@@ -0,0 +1,151 @@
# GitHub Actions
See below for a summary of this repo's Actions
- _Actions marked with "⚠️" are expected to sometimes fail._
## Security auditing
We use [`zizmor`](https://docs.zizmor.sh/) to audit GitHub Actions workflow definitions and keep CI workflows as safe as possible. When changing files in this directory, run:
```sh
zizmor .github/workflows/*.yml
```
Workflow changes should avoid unsuppressed `zizmor` findings. In particular:
- Pin external actions to immutable commit SHAs, not tags.
- Use `actions/checkout` v6 or newer so persisted credentials are stored under `$RUNNER_TEMP`; set `persist-credentials: false` when a job does not need follow-up authenticated Git operations.
- Pass GitHub expression values into shell steps through `env` instead of expanding `${{ ... }}` directly inside `run` blocks.
- Treat privileged triggers such as `pull_request_target` and `workflow_run` as security-sensitive. If a privileged trigger is required, document the safety model and add a targeted `zizmor` ignore with a reason.
## PR related actions
### Tests + Checks (test-and-check.yml)
- Triggers
- Updates to PRs.
- PRs in the merge queue.
- Actions
- Builds all the packages.
- Runs formatting, linting and type checks.
- Runs fixture tests, Wrangler unit tests, C3 unit tests, Miniflare unit tests, and ESLint + Prettier checks.
- Adds the PR to a GitHub project
- Makes sure that Wrangler's warning for old Node.js versions works.
### Wrangler E2E tests (e2e-wrangler.yml)
- Triggers
- Updates to PRs on the Cloudflare fork.
- PRs in the merge queue.
- Actions
- Runs the E2E tests for Wrangler.
- Cloudflare API credentials are only passed on Version Packages PRs (`changeset-release/main`), in the merge queue, or when the `ci:run-remote-tests` label is applied. Other PRs run the E2E suite without remote tests.
### Vite Plugin E2E tests (e2e-vite.yml)
- Triggers
- Updates to PRs on the Cloudflare fork.
- PRs in the merge queue.
- Actions
- Runs the E2E tests for the Vite plugin.
- Cloudflare API credentials are only passed on Version Packages PRs (`changeset-release/main`), in the merge queue, or when the `ci:run-remote-tests` label is applied. Other PRs run the E2E suite without remote tests.
## Deploy Pages Previews (deploy-pages-preview.yml)
- Triggers
- Updates to PRs that have one of the `preview:...` labels.
- Actions
- Deploy a preview of the matching Pages project to Cloudflare.
## Deploy (to testing) and Test Playground Preview Worker (worker-playground-preview-testing-env-deploy-and-test.yml)
- Triggers
- Commits merged to the `main` branch, on the Cloudflare fork, which touch files in the `packages/playground-preview-worker` directory.
- Updates to PRs, on the Cloudflare fork, with the `playground-worker` label applied.
- Actions
- Runs integrations tests to ensure the behaviour of the Worker powering the Workers Playground.
## Create Pull Request Prerelease (prerelease.yml)
- Triggers
- Updates to PRs.
- Actions
- Creates an installable pre-release of any package containing `{ "workers-sdk": { "prerelease": true } }` in its `package.json` (e.g. Wrangler, C3, and Miniflare) on every PR.
- Adds a comment to the PR with links to the pre-releases.
## Housekeeping actions
### Add issues to DevProd project (issues.yml)
- Triggers
- Updates to issues.
- Actions
- Add the issue to a GitHub project.
### Triage Issue (triage-issue.yml)
- Triggers
- A new issue is opened (skips PRs and bot-authored issues).
- A new comment is created on an issue — re-triages with the latest context. Skips PRs, bot comments, and the triage bot's own sticky comment (matched by the sticky-comment marker) to avoid re-triage loops.
- Manual `workflow_dispatch` with an `issue-number` input.
- Actions
- Runs an OpenCode agent with the `.github/skills/issue-review.md` skill against the pre-fetched issue data (including existing comments) to produce a markdown triage report (`report.md`) and a structured JSON summary (`summary.json`).
- Uploads the report to the triage dashboard.
- Posts the report and structured summary as a maintainer-facing sticky comment on the issue and applies the suggested labels (validated against existing repo labels). Because the comment is sticky, re-triage updates the existing comment rather than posting a duplicate. Comments and labels are attributed to the workers-devprod bot via `GH_ACCESS_TOKEN`.
- The AI agent itself runs sandboxed (no shell or network access); all GitHub writes happen in workflow steps from the generated files.
### Generate changesets for dependabot PRs (c3-dependabot-versioning-prs.yml and miniflare-dependabot-versioning-prs.yml)
- Triggers
- Updates to PRs, by the dependabot user, which update one of:
- frameworks dependencies in C3,
- miniflare.
- Actions
- Generates changesets for the affected package.
### E2E Project Cleanup (e2e-project-cleanup.yml)
- Triggers
- Scheduled to run at 3am each day.
- Actions
- Deletes any Workers and Pages projects that were not properly cleaned up by the E2E tests.
## Main branch actions
### Handle Changesets (changesets.yml)
- Triggers
- Commits merged to the `main` branch, on the Cloudflare fork.
- Actions
- If there are changeset in the working directory, create or update a "Version Packages" PR to prep for a release.
- If there are no changesets, release any packages that have a bump to their version in this change.
- Public packages are deployed to npm
- Private packages will run their `deploy` script, if they have one.
## C3 related actions
### C3 E2E Tests (c3-e2e.yml)
- Triggers
- Updates to PRs.
- Actions
- Runs the E2E tests for C3.
- Cloudflare API credentials are only passed on Version Packages PRs (`changeset-release/main`), in the merge queue, or when the `ci:run-remote-tests` label is applied. Other PRs run the E2E suite without remote tests.
### Rerun Code Owners (rerun-codeowners.yml + rerun-codeowners-privileged.yml)
- Triggers
- A review is submitted or dismissed on a PR.
- Actions
- Re-runs the "Run Codeowners Plus" check so it re-evaluates approval status after the review change.
- Uses the `workflow_run` pattern: the trigger workflow exists solely to fire a `workflow_run` event; the privileged companion workflow (which has full permissions) reads the PR head SHA from `github.event.workflow_run.head_sha` and performs the re-run. This is necessary because `pull_request_review` gives a read-only token for fork PRs and has no `_target` variant.
### Rerun Remote Tests (rerun-remote-tests.yml)
- Triggers
- The `ci:run-remote-tests` or `run-c3-frameworks-tests` label is added to or removed from a PR.
- Actions
- Re-runs the E2E workflows for the PR so they pick up the label change and pass (or withhold) API credentials to the test steps.
- `ci:run-remote-tests` re-runs Wrangler, Vite, and C3 E2E workflows; `run-c3-frameworks-tests` re-runs only C3 E2E.
- Uses `pull_request_target` to get a privileged token even for fork PRs (safe because no untrusted code is checked out).
+69
View File
@@ -0,0 +1,69 @@
name: New PR Review
on:
pull_request:
types: [opened]
jobs:
review:
# Skip Version Packages PRs (auto-generated by the changesets action) since they don't need a Bonk review
if: |
github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name &&
!(github.event.pull_request.base.repo.owner.login == 'cloudflare' && github.event.pull_request.head.ref == 'changeset-release/main')
runs-on: ubuntu-latest
timeout-minutes: 30
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: false
permissions:
id-token: write
contents: read
issues: write
pull-requests: write
steps:
- name: Check if PR author is Cloudflare org member
run: |
STATUS=$(gh api \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"/orgs/cloudflare/members/${PR_AUTHOR}" \
--silent -i 2>/dev/null | head -1 | awk '{print $2}') || true
if [ "$STATUS" != "204" ]; then
echo "User ${PR_AUTHOR} is not a member of the Cloudflare organization"
exit 1
fi
echo "User ${PR_AUTHOR} is a Cloudflare org member"
env:
GH_TOKEN: ${{ secrets.READ_ONLY_ORG_GITHUB_TOKEN }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 30
persist-credentials: false
- name: Load review prompt
id: prompt
run: |
{
echo 'value<<EOF'
echo "You are reviewing PR #${{ github.event.pull_request.number }} on ${{ github.repository }}."
echo ""
cat .github/bonk_reviewer.md
echo EOF
} >> "$GITHUB_OUTPUT"
- name: Run Bonk
uses: ask-bonk/ask-bonk/github@24832587005550860c8ad13fd96519e731ca632a # main
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_AI_GATEWAY_ACCOUNT_ID }}
CLOUDFLARE_GATEWAY_ID: ${{ secrets.CF_AI_GATEWAY_NAME }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_AI_GATEWAY_TOKEN }}
with:
model: "cloudflare-ai-gateway/anthropic/claude-opus-4-8"
variant: "high"
forks: "false"
permissions: write
opencode_version: 1.15.13 # pin to this version as certain ones cause ProviderInitError issues
prompt: ${{ steps.prompt.outputs.value }}
+80
View File
@@ -0,0 +1,80 @@
name: Bonk
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.issue.number || github.ref }}
cancel-in-progress: false
jobs:
bonk:
if: github.event.sender.type != 'Bot' && (contains(github.event.comment.body, '/bonk') || contains(github.event.comment.body, '@ask-bonk'))
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
id-token: write
contents: write
issues: write
pull-requests: write
steps:
- name: Check if comment author is Cloudflare org member
run: |
STATUS=$(gh api \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"/orgs/cloudflare/members/${COMMENT_AUTHOR}" \
--silent -i 2>/dev/null | head -1 | awk '{print $2}') || true
if [ "$STATUS" != "204" ]; then
echo "User ${COMMENT_AUTHOR} is not a member of the Cloudflare organization"
exit 1
fi
echo "User ${COMMENT_AUTHOR} is a Cloudflare org member"
env:
GH_TOKEN: ${{ secrets.READ_ONLY_ORG_GITHUB_TOKEN }}
COMMENT_AUTHOR: ${{ github.event.comment.user.login }}
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 1
persist-credentials: false
- name: Build prompt with triggering comment
id: prompt
run: |
{
DELIMITER=$(openssl rand -hex 16)
echo "value<<$DELIMITER"
echo "You were invoked by @${COMMENT_AUTHOR} on ${COMMENT_URL}"
echo ""
echo "Their comment:"
echo '```'
echo "$COMMENT_BODY"
echo '```'
echo ""
echo "This is your task. Read it carefully and act on it."
echo "$DELIMITER"
} >> "$GITHUB_OUTPUT"
env:
COMMENT_AUTHOR: ${{ github.event.comment.user.login }}
COMMENT_BODY: ${{ github.event.comment.body }}
COMMENT_URL: ${{ github.event.comment.html_url }}
- name: Run Bonk
uses: ask-bonk/ask-bonk/github@c39e982defd0114385df54e72012a3fc4333c4d4
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_AI_GATEWAY_ACCOUNT_ID }}
CLOUDFLARE_GATEWAY_ID: ${{ secrets.CF_AI_GATEWAY_NAME }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_AI_GATEWAY_TOKEN }}
with:
model: "cloudflare-ai-gateway/anthropic/claude-opus-4-8"
variant: "high"
mentions: "/bonk,@ask-bonk"
permissions: write
agent: bonk
opencode_version: 1.15.13 # pin to this version as certain ones cause ProviderInitError issues
prompt: ${{ steps.prompt.outputs.value }}
@@ -0,0 +1,39 @@
name: "C3 - Generate changesets for dependabot PRs"
on:
pull_request_target: # zizmor: ignore[dangerous-triggers] dependabot-only job requires write access to push generated changesets; checkout v6 persists credentials under RUNNER_TEMP
paths:
- "packages/create-cloudflare/src/frameworks/package.json"
permissions:
# content:write permission needed to update add changesets to dependabot PRs
# (see tools/dependabot/generate-dependabot-pr-changesets.ts)
contents: write
jobs:
generate-changeset:
runs-on: ubuntu-slim
if: |
github.event.pull_request.user.login == 'dependabot[bot]' &&
github.event.pull_request.head.repo.full_name == github.repository
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 2
persist-credentials: true
ref: ${{ github.head_ref }}
token: ${{ secrets.GH_ACCESS_TOKEN }}
- name: Install Dependencies
uses: ./.github/actions/install-dependencies
- name: Configure Git
run: |
git config --global user.email wrangler@cloudflare.com
git config --global user.name 'Wrangler automated PR updater'
- name: Generate C3 changesets
# Keep the final param (the changeset prefix: `c3-frameworks-update`) in sync with the filter in the `.github/workflows/c3-e2e-dependabot.yml` workflow.
run: node -r esbuild-register tools/dependabot/generate-dependabot-pr-changesets.ts "$PR_NUMBER" create-cloudflare packages/create-cloudflare/src/frameworks/package.json c3-frameworks-update
env:
PR_NUMBER: ${{ github.event.number }}
+311
View File
@@ -0,0 +1,311 @@
name: C3 E2E
on:
merge_group:
pull_request:
permissions:
contents: read
pull-requests: read
# pnpm 11-specific settings - recognised by pnpm 11+ and
# silently ignored by pnpm 10.
#
# - pmOnFail=ignore: pnpm 11's default `download` would silently re-exec as
# the monorepo's pinned pnpm 10.33.0, defeating the matrix override.
# - verifyDepsBeforeRun=false: pnpm 11's default `install` would re-run
# `pnpm install` at the monorepo root before invoking the script, which
# then trips on engines.pnpm.
env:
pnpm_config_pm_on_fail: ignore
pnpm_config_verify_deps_before_run: "false"
jobs:
e2e:
# Runs the non-frameworks C3 E2E tests on all supported operating systems
# and package managers.
timeout-minutes: 45
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.os.name }}-${{ matrix.pm.name }}-${{ matrix.pm.version }}${{ matrix.experimental && '-experimental' || '' }}-${{ matrix.filter }}
cancel-in-progress: ${{ github.head_ref != 'changeset-release/main' }}
# `matrix.pm.label` is the display name in job/artifact names. Default
# pm entries use the plain name (e.g. `pnpm`) to keep historical required-
# check names stable; non-default entries (e.g. pnpm 11) suffix the version.
name: ${{ format('C3 E2E ({0}, {1}{2}) - {3}', matrix.pm.label, matrix.os.description, matrix.experimental && ', experimental' || '', matrix.filter) }}
strategy:
fail-fast: false
matrix:
experimental: [false]
filter: ["cli", "workers"]
os: [{ name: ubuntu-latest, description: Linux }]
pm:
- { name: pnpm, version: "10.33.0", label: "pnpm" }
- { name: pnpm, version: "11.5.1", label: "pnpm@11.5.1" }
- { name: npm, version: "0.0.0", label: "npm" }
# The yarn tests keep failing on Linux with out of space errors, with no clear reason why. Disabling for now.
# - { name: yarn, version: "1.0.0", label: "yarn" }
include:
# Windows and experimental entries stay on the default pnpm to
# preserve historical required-check names. pnpm 11 coverage comes
# from the Linux base entries above.
- os: { name: windows-latest, description: Windows }
pm: { name: pnpm, version: "10.33.0", label: "pnpm" }
filter: "cli"
- os: { name: windows-latest, description: Windows }
pm: { name: pnpm, version: "10.33.0", label: "pnpm" }
filter: "workers"
- os: { name: ubuntu-latest, description: Linux }
pm: { name: pnpm, version: "10.33.0", label: "pnpm" }
experimental: true
filter: "cli"
- os: { name: ubuntu-latest, description: Linux }
pm: { name: pnpm, version: "10.33.0", label: "pnpm" }
experimental: true
filter: "workers"
runs-on: ${{ matrix.os.name }}
steps:
- name: Checkout Repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
persist-credentials: false
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: changes
with:
filters: |
everything_but_markdown:
- '!**/*.md'
- name: Install Dependencies
if: steps.changes.outputs.everything_but_markdown == 'true'
uses: ./.github/actions/install-dependencies
with:
turbo-api: ${{ secrets.TURBO_API }}
turbo-team: ${{ secrets.TURBO_TEAM }}
turbo-token: ${{ secrets.TURBO_TOKEN }}
turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
- name: Install Python uv
if: steps.changes.outputs.everything_but_markdown == 'true'
uses: ./.github/actions/install-python-uv
# Needed because gatsby requires git
- name: Configure Git
shell: bash
run: |
git config --global user.email wrangler@cloudflare.com
git config --global user.name 'Wrangler Tester'
# Put the matrix pnpm version on PATH so scaffolded-project installs
# run it (not the monorepo's pinned pnpm 10.33.0). Without this, the
# matrix only relabels C3's `npm_config_user_agent` and pnpm 11
# regressions go undetected.
#
# `pnpm/action-setup` refuses when both `version` input and the root
# `package.json#packageManager` are set, so we point it at a stub
# `package.json`. We also loosen `engines.pnpm` to avoid pnpm 11
# bailing with ERR_PNPM_UNSUPPORTED_ENGINE at the monorepo root.
- name: Prepare workspace for matrix pnpm install
if: matrix.pm.name == 'pnpm' && steps.changes.outputs.everything_but_markdown == 'true'
shell: bash
run: |
mkdir -p .ci-pnpm-setup-stub
printf '{"name":"ci-stub","private":true}\n' > .ci-pnpm-setup-stub/package.json
node -e "
const fs = require('node:fs');
const pkg = JSON.parse(fs.readFileSync('package.json','utf8'));
if (pkg.engines && pkg.engines.pnpm) pkg.engines.pnpm = '*';
fs.writeFileSync('package.json', JSON.stringify(pkg, null, '\t') + '\n');
"
- name: Install matrix pnpm version for scaffolded installs
if: matrix.pm.name == 'pnpm' && steps.changes.outputs.everything_but_markdown == 'true'
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
with:
version: ${{ matrix.pm.version }}
package_json_file: .ci-pnpm-setup-stub/package.json
# Distinct dest so `addPath` prepends ahead of the monorepo's
# pinned pnpm 10.x from the earlier `~/setup-pnpm`.
dest: ~/matrix-pnpm
- name: Verify pnpm on PATH matches matrix version
if: matrix.pm.name == 'pnpm' && steps.changes.outputs.everything_but_markdown == 'true'
shell: bash
run: |
got="$(pnpm --version)"
want="${{ matrix.pm.version }}"
echo "pnpm on PATH: $got (expected $want)"
[ "$got" = "$want" ] || { echo "ERROR: PATH pnpm is $got, not $want — scaffolded installs would silently run the wrong version."; exit 1; }
- id: run-e2e
if: steps.changes.outputs.everything_but_markdown == 'true'
shell: bash
run: pnpm run test:e2e:c3
env:
NODE_VERSION: ${{ env.NODE_VERSION }}
E2E_EXPERIMENTAL: ${{ matrix.experimental }}
E2E_TEST_PM: ${{ matrix.pm.name }}
E2E_TEST_PM_VERSION: ${{ matrix.pm.version }}
E2E_TEST_FILTER: ${{ matrix.filter }}
CI_OS: ${{ runner.os }}
GITHUB_TOKEN: ${{ github.token }} # Needed for begit to clone the repo in the e2e tests for solid-start
- name: Upload Logs
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !cancelled() && steps.changes.outputs.everything_but_markdown == 'true' }}
with:
name: ${{ format('e2e-logs-{0}-{1}-{2}-{3}', matrix.pm.label, matrix.os.description, matrix.experimental && 'experimental' || 'normal', matrix.filter) }}
path: packages/create-cloudflare/.e2e-logs${{matrix.experimental == 'true' && '-experimental' || ''}}/${{ matrix.pm.name }}/${{ matrix.filter }}
include-hidden-files: true
- name: Upload Turbo Summary
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !cancelled() && steps.changes.outputs.everything_but_markdown == 'true' }}
with:
name: ${{ format('turbo-runs-{0}-{1}-{2}-{3}', matrix.pm.label, matrix.os.description, matrix.experimental && 'experimental' || 'normal', matrix.filter) }}
path: .turbo/runs
include-hidden-files: true
frameworks-e2e:
# Runs the C3 frameworks E2E tests only in the merge queue, on the release branch, if create-cloudflare has changed, or when explicitly requested via label.
#
# Frameworks that delegate install to their own generator (e.g. `hono`)
# don't route through C3's `ERR_PNPM_IGNORED_BUILDS` recovery path; those
# tests opt out of pnpm 11 via `unsupportedPmRanges` in the test config.
timeout-minutes: 45
concurrency:
group: ${{ github.workflow }}-frameworks${{ matrix.experimental && '-experimental' || '' }}-${{ github.ref }}-${{ matrix.os.name }}-${{ matrix.pm.name }}-${{ matrix.pm.version }}
cancel-in-progress: ${{ github.head_ref != 'changeset-release/main' }}
name: ${{ format('C3 E2E ({0}, {1}{2}) - frameworks', matrix.pm.label, matrix.os.description, matrix.experimental && ', experimental' || '') }}
strategy:
fail-fast: false
matrix:
experimental: [false]
os:
- { name: ubuntu-latest, description: Linux }
pm:
- { name: pnpm, version: "10.33.0", label: "pnpm" }
- { name: pnpm, version: "11.5.1", label: "pnpm@11.5.1" }
- { name: npm, version: "0.0.0", label: "npm" }
include:
# Experimental stays on the default pnpm to preserve historical
# required-check names; pnpm 11 coverage comes from the base entry.
- os: { name: ubuntu-latest, description: Linux }
pm: { name: pnpm, version: "10.33.0", label: "pnpm" }
experimental: true
runs-on: ${{ matrix.os.name }}
steps:
- name: Checkout Repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
persist-credentials: false
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: changes
with:
filters: |
everything_but_markdown:
- '!**/*.md'
framework_deps:
- 'packages/create-cloudflare/**'
- name: Check if frameworks tests should run
id: check-frameworks
shell: bash
env:
GH_TOKEN: ${{ github.token }}
EVENT_NAME: ${{ github.event_name }}
HEAD_REF: ${{ github.head_ref }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
FRAMEWORK_DEPS_CHANGED: ${{ steps.changes.outputs.framework_deps }}
run: |
if [ "$EVENT_NAME" = "merge_group" ] || \
[ "$HEAD_REF" = "changeset-release/main" ] || \
[ "$FRAMEWORK_DEPS_CHANGED" = "true" ]; then
echo "run_frameworks=true" >> "$GITHUB_OUTPUT"
elif [ "$EVENT_NAME" = "pull_request" ]; then
HAS_LABEL=$(gh pr view "$PR_NUMBER" \
--repo "$REPO" --json labels \
--jq '[.labels[].name] | any(. == "run-c3-frameworks-tests")')
echo "run_frameworks=${HAS_LABEL}" >> "$GITHUB_OUTPUT"
else
echo "run_frameworks=false" >> "$GITHUB_OUTPUT"
fi
- name: Install Dependencies
if: steps.check-frameworks.outputs.run_frameworks == 'true' && steps.changes.outputs.everything_but_markdown == 'true'
uses: ./.github/actions/install-dependencies
with:
turbo-api: ${{ secrets.TURBO_API }}
turbo-team: ${{ secrets.TURBO_TEAM }}
turbo-token: ${{ secrets.TURBO_TOKEN }}
turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
- name: Install Python uv
if: steps.check-frameworks.outputs.run_frameworks == 'true' && steps.changes.outputs.everything_but_markdown == 'true'
uses: ./.github/actions/install-python-uv
- name: Configure Git
if: steps.check-frameworks.outputs.run_frameworks == 'true' && steps.changes.outputs.everything_but_markdown == 'true'
shell: bash
run: |
git config --global user.email wrangler@cloudflare.com
git config --global user.name 'Wrangler Tester'
# See the equivalent step in the `e2e` job above for context.
- name: Prepare workspace for matrix pnpm install
if: matrix.pm.name == 'pnpm' && steps.check-frameworks.outputs.run_frameworks == 'true' && steps.changes.outputs.everything_but_markdown == 'true'
shell: bash
run: |
mkdir -p .ci-pnpm-setup-stub
printf '{"name":"ci-stub","private":true}\n' > .ci-pnpm-setup-stub/package.json
node -e "
const fs = require('node:fs');
const pkg = JSON.parse(fs.readFileSync('package.json','utf8'));
if (pkg.engines && pkg.engines.pnpm) pkg.engines.pnpm = '*';
fs.writeFileSync('package.json', JSON.stringify(pkg, null, '\t') + '\n');
"
- name: Install matrix pnpm version for scaffolded installs
if: matrix.pm.name == 'pnpm' && steps.check-frameworks.outputs.run_frameworks == 'true' && steps.changes.outputs.everything_but_markdown == 'true'
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
with:
version: ${{ matrix.pm.version }}
package_json_file: .ci-pnpm-setup-stub/package.json
dest: ~/matrix-pnpm
- name: Verify pnpm on PATH matches matrix version
if: matrix.pm.name == 'pnpm' && steps.check-frameworks.outputs.run_frameworks == 'true' && steps.changes.outputs.everything_but_markdown == 'true'
shell: bash
run: |
got="$(pnpm --version)"
want="${{ matrix.pm.version }}"
echo "pnpm on PATH: $got (expected $want)"
[ "$got" = "$want" ] || { echo "ERROR: PATH pnpm is $got, not $want — scaffolded installs would silently run the wrong version."; exit 1; }
- id: run-e2e
if: steps.check-frameworks.outputs.run_frameworks == 'true' && steps.changes.outputs.everything_but_markdown == 'true'
shell: bash
run: pnpm run test:e2e:c3
env:
NODE_VERSION: ${{ env.NODE_VERSION }}
E2E_EXPERIMENTAL: ${{ matrix.experimental }}
E2E_TEST_PM: ${{ matrix.pm.name }}
E2E_TEST_PM_VERSION: ${{ matrix.pm.version }}
E2E_TEST_FILTER: frameworks
CI_OS: ${{ runner.os }}
GITHUB_TOKEN: ${{ github.token }}
- name: Upload Logs
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !cancelled() && steps.check-frameworks.outputs.run_frameworks == 'true' && steps.changes.outputs.everything_but_markdown == 'true' }}
with:
name: ${{ format('e2e-logs-{0}-{1}-{2}-frameworks', matrix.pm.label, matrix.os.description, matrix.experimental && 'experimental' || 'normal') }}
path: packages/create-cloudflare/.e2e-logs${{matrix.experimental == 'true' && '-experimental' || ''}}/${{ matrix.pm.name }}/frameworks
include-hidden-files: true
- name: Upload Turbo Summary
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !cancelled() && steps.check-frameworks.outputs.run_frameworks == 'true' && steps.changes.outputs.everything_but_markdown == 'true' }}
with:
name: ${{ format('turbo-runs-{0}-{1}-{2}-frameworks', matrix.pm.label, matrix.os.description, matrix.experimental && 'experimental' || 'normal') }}
path: .turbo/runs
include-hidden-files: true
+92
View File
@@ -0,0 +1,92 @@
name: Changeset Review
on:
pull_request:
paths:
- ".changeset/*.md"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.head_ref != 'changeset-release/main' }}
permissions:
contents: read
pull-requests: write
issues: write
id-token: write
jobs:
review-changesets:
runs-on: ubuntu-latest
if: github.event.pull_request.head.repo.full_name == github.repository
steps:
- name: Checkout changesets
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
persist-credentials: false
sparse-checkout: |
.changeset
.github/opencode.json
- name: Install OpenCode
# pin OpenCode to 1.4.6 version as newer versions are causing ProviderInitError issues
run: |
npm install -g opencode-ai@1.4.6
cp .github/opencode.json ./opencode.json
- name: Get changed changeset files
id: changed-changesets
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
with:
files: |
.changeset/*.md
files_ignore: |
.changeset/README.md
# Recover deleted files so the AI can read them (needed for Version Packages PRs)
recover_deleted_files: ${{ github.event.pull_request.title == 'Version Packages' }}
- name: Review Changesets with OpenCode
id: opencode-review
# Run for Version Packages PRs (which delete changesets) or regular PRs with new changesets
if: github.event.pull_request.title == 'Version Packages' || steps.changed-changesets.outputs.added_files_count > 0
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_AI_GATEWAY_ACCOUNT_ID }}
CLOUDFLARE_GATEWAY_ID: ${{ secrets.CF_AI_GATEWAY_NAME }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_AI_GATEWAY_TOKEN }}
DELETED_FILES: ${{ steps.changed-changesets.outputs.deleted_files }}
ADDED_FILES: ${{ steps.changed-changesets.outputs.added_files }}
run: |
opencode --model "cloudflare-ai-gateway/workers-ai/@cf/moonshotai/kimi-k2.6" run --print-logs \
"Review the changeset files in this PR.
For \"Version Packages\" PRs, review: ${DELETED_FILES}
For regular PRs, review: ${ADDED_FILES}
Read \`.changeset/README.md\` for guidelines, then validate:
1. **Version Type**: Accept the author's choice of patch/minor unless clearly incorrect
2. **Changelog Quality**: Meaningful descriptions (examples encouraged but not required for features)
3. **Markdown Headers**: No h1/h2/h3 headers (breaks changelog formatting)
4. **Analytics**: If the change collects more analytics, it should be a minor even though there is no user-visible change
5. **Dependabot**: Do not validate dependency update changesets for create-cloudflare
6. **Experimental features**: Changesets for experimental features should include note on how users can opt in.
If all changesets pass, just output \"✅ All changesets look good\" - no need for a detailed checklist.
Do not review other files, only the changesets. This is specifically a changeset review action.
If there are issues, output \"⚠️ Issues found\" followed by the specific problems.
Write your review to changeset-review.md."
- name: Post review comment
if: steps.opencode-review.outcome == 'success'
uses: marocchino/sticky-pull-request-comment@d4d6b0936434b21bc8345ad45a440c5f7d2c40ff # v3.0.3
with:
header: changeset-review
path: changeset-review.md
- name: Skip notice
if: github.event.pull_request.title != 'Version Packages' && steps.changed-changesets.outputs.added_files_count == 0
run: |
echo "No new changesets to review (only minor edits to pre-existing changesets detected)"
+108
View File
@@ -0,0 +1,108 @@
name: Handle Changesets
on:
push:
branches:
- main
permissions:
id-token: write
contents: read
# note: no write permissions are needed since the workflow uses GH_ACCESS_TOKEN instead of GITHUB_TOKEN
env:
# This workflow only builds and publishes — it never runs Playwright tests.
# Skip the browser download to avoid the postinstall script hanging in CI.
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
jobs:
release:
if: ${{ github.repository_owner == 'cloudflare' }}
name: Handle Changesets
runs-on: macos-latest-large
steps:
- name: Checkout Repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
# See https://github.com/changesets/action/issues/187
token: ${{ secrets.GH_ACCESS_TOKEN }}
persist-credentials: true
# Pull in more history to cover the changeset commits
fetch-depth: 500
- name: Install Dependencies
# Defense in depth: do not pass Turbo remote cache credentials and
# disable the pnpm store cache so release builds always resolve
# packages from the registry and rebuild every task from source,
# rather than restoring from a (potentially poisoned) cache.
uses: ./.github/actions/install-dependencies
with:
# Pinned to 24.18.0 (rather than floating `24`) until it's safe to
# float back: Node 24.17.0 regressed node-fetch@2 keep-alive responses
# with ERR_STREAM_PREMATURE_CLOSE (nodejs/node#63989, fixed in 24.18.0).
node-version: 24.18.0
disable-cache: "true"
- name: Check npm version
run: node -r esbuild-register tools/deployments/check-npm-version.ts
- name: Check the changesets
run: node -r esbuild-register tools/deployments/validate-changesets.ts
- name: Build all packages
run: pnpm run build
env:
CI_OS: ${{ runner.os }}
SOURCEMAPS: "false"
ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
ALGOLIA_PUBLIC_KEY: ${{ secrets.ALGOLIA_PUBLIC_KEY }}
SENTRY_DSN: "https://9edbb8417b284aa2bbead9b4c318918b@sentry10.cfdata.org/583"
NODE_ENV: "production"
# This is the "production" key for sparrow analytics.
# Include this here because this step will rebuild Wrangler and needs to have this available
SPARROW_SOURCE_KEY: "50598e014ed44c739ec8074fdc16057c"
- name: Create Version PR or Publish to NPM
id: changesets
uses: changesets/action@a45c4d594aa4e2c509dc14a9f2b3b67ba3780d0d # v1.9.0
with:
version: node .github/changeset-version.js
publish: pnpm exec changeset publish
env:
GITHUB_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }}
# Switch to Node 22 for non-npm package deployments because quick-edit
# builds native modules (tree-sitter) that are not compatible with
# Node 24's C++20 requirement for V8 headers.
- name: Switch to Node 22 for non-npm package deployments
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
# Pinned to 22.23.1 (rather than floating `22`) until it's safe to
# float back: Node 22.23.0 regressed node-fetch@2 keep-alive responses
# with ERR_STREAM_PREMATURE_CLOSE (nodejs/node#63989, fixed in 22.23.1).
node-version: 22.23.1
- name: Deploy non-NPM Packages
id: deploy
run: |
node -r esbuild-register tools/deployments/deploy-non-npm-packages.ts
echo "status=$(cat deployment-status.json)" >> $GITHUB_OUTPUT;
env:
PUBLISHED_PACKAGES: ${{ steps.changesets.outputs.publishedPackages }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
WORKERS_NEW_CLOUDFLARE_ACCOUNT_ID: ${{ secrets.WORKERS_NEW_CLOUDFLARE_ACCOUNT_ID }}
WORKERS_NEW_CLOUDFLARE_API_TOKEN: ${{ secrets.WORKERS_NEW_CLOUDFLARE_API_TOKEN }}
WORKERS_DEPLOY_AND_CONFIG_CLOUDFLARE_API_TOKEN: ${{ secrets.WORKERS_DEPLOY_AND_CONFIG_CLOUDFLARE_API_TOKEN }}
WORKERS_SHARED_SENTRY_ACCESS_ID: ${{ secrets.WORKERS_SHARED_SENTRY_ACCESS_ID }}
WORKERS_SHARED_SENTRY_ACCESS_SECRET: ${{ secrets.WORKERS_SHARED_SENTRY_ACCESS_SECRET }}
WORKERS_SHARED_SENTRY_AUTH_TOKEN: ${{ secrets.WORKERS_SHARED_SENTRY_AUTH_TOKEN }}
- name: Send Alert
if: always()
run: node -r esbuild-register tools/deployments/alert-on-error.ts
env:
PUBLISH_STATUS: ${{ steps.changesets.outcome }}
HAS_CHANGESETS: ${{ steps.changesets.outputs.hasChangesets }}
DEPLOYMENT_STATUS: ${{ steps.deploy.outputs.status }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
TOKEN: ${{ secrets.STATUS_BOT_SECRET }}
+54
View File
@@ -0,0 +1,54 @@
name: "Code Owners"
# Re-evaluate when PRs are opened/updated.
# When reviews are submitted/dismissed, the separate rerun-codeowners.yml workflow
# re-runs this check (rather than creating a second check context).
# Using pull_request_target (not pull_request) so the workflow has access to secrets
# for fork PRs. This is safe because:
# - The checkout is the BASE branch (ownership rules come from the protected branch)
# - PR head commits are fetched as git objects only (never checked out or executed)
# - The action only reads config files and calls the GitHub API
on:
pull_request_target: # zizmor: ignore[dangerous-triggers] checks base branch ownership rules and fetches PR head for diff computation without executing PR code
types: [opened, reopened, synchronize, ready_for_review, labeled, unlabeled]
concurrency:
group: codeowners-${{ github.event.pull_request.number }}
cancel-in-progress: ${{ github.head_ref != 'changeset-release/main' }}
permissions:
contents: read
issues: write
pull-requests: write
jobs:
codeowners:
name: "Run Codeowners Plus"
runs-on: ubuntu-latest
# Each step is skipped when:
# - the PR head is the changeset-release branch (auto-generated changeset PRs), or
# - the PR base is not `main` (PRs against feature branches don't need formal review).
# Note: if we ever introduce a maintenance branch (e.g. a long-lived release branch),
# it would need to be added to the base branch allowlist below.
steps:
- name: "Checkout Base Branch"
if: github.event.pull_request.head.ref != 'changeset-release/main' && github.event.pull_request.base.ref == 'main'
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
persist-credentials: false
- name: "Fetch PR Head (for diff computation)"
if: github.event.pull_request.head.ref != 'changeset-release/main' && github.event.pull_request.base.ref == 'main'
run: git fetch origin +refs/pull/${{ github.event.pull_request.number }}/head
env:
GITHUB_TOKEN: "${{ secrets.CODEOWNERS_GITHUB_PAT }}"
- name: "Codeowners Plus"
if: github.event.pull_request.head.ref != 'changeset-release/main' && github.event.pull_request.base.ref == 'main'
uses: multimediallc/codeowners-plus@ff02aa993a92e8efe01642916d0877beb9439e9f # v1.9.0
with:
github-token: "${{ secrets.CODEOWNERS_GITHUB_PAT }}"
pr: "${{ github.event.pull_request.number }}"
verbose: true
quiet: ${{ github.event.pull_request.draft }}
+141
View File
@@ -0,0 +1,141 @@
name: "Dependabot - auto-merge workerd updates"
# workerd ships a release every weekday, so the workerd-and-workers-types
# Dependabot group produces a steady stream of mechanical PRs that bump
# `workerd`, `@cloudflare/workers-types`, and miniflare's pinned version in
# lockstep (see .github/dependabot.yml). When CI is green these PRs require
# no human review, so we enable GitHub auto-merge on them — required status
# checks remain the gate, and a failing build still parks the PR for a human.
#
# Security model: this workflow effectively bypasses the human-review
# requirement on PRs whose head branch matches the Dependabot naming pattern,
# so we have to be paranoid about exactly what we're auto-merging. Before
# enabling auto-merge we verify that the PR contains exactly the two commits
# we expect (one from Dependabot, one from `miniflare-dependabot-versioning-prs.yml`)
# and that nothing outside the expected fileset has been touched. If any
# subsequent push violates those invariants we actively *disable* auto-merge,
# so a maintainer pushing a follow-up commit cancels rather than rides the
# auto-merge.
#
# DO NOT add `actions/checkout` to this workflow — `pull_request_target`
# grants write-scoped tokens, and checking out PR-controlled code with
# those tokens is the standard pwn vector.
on:
pull_request_target: # zizmor: ignore[dangerous-triggers] intentionally privileged metadata-only workflow; it never checks out or executes PR code
types: [opened, reopened, synchronize, ready_for_review]
permissions:
contents: write
pull-requests: write
jobs:
enable-auto-merge:
if: github.event.pull_request.user.login == 'dependabot[bot]'
runs-on: ubuntu-slim
steps:
- name: Fetch Dependabot metadata
id: meta
uses: dependabot/fetch-metadata@d7267f607e9d3fb96fc2fbe83e0af444713e90b7 # v2.3.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Verify PR matches expected workerd-bump shape
id: verify
if: steps.meta.outputs.dependency-group == 'workerd-and-workers-types'
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
# Pull commits and changed files via the GitHub API. `--repo` is
# required because this workflow runs without `actions/checkout`,
# so `gh` has no git remote to infer the repo from.
commits_json=$(gh pr view --repo "$REPO" "$PR_NUMBER" --json commits)
files_json=$(gh pr view --repo "$REPO" "$PR_NUMBER" --json files)
fail() {
echo "verified=false" >> "$GITHUB_OUTPUT"
echo "reason=$1" >> "$GITHUB_OUTPUT"
echo "::warning::Refusing to enable auto-merge: $1"
exit 0
}
# --- Commit shape ---------------------------------------------------
# Expected: exactly two commits.
# 1. dependabot[bot] author, with a verified GitHub signature.
# 2. The changeset commit pushed by miniflare-dependabot-versioning-prs.yml,
# authored as `Wrangler automated PR updater <wrangler@cloudflare.com>`.
#
# We can't require a signature on the second commit (it's pushed via
# GH_ACCESS_TOKEN, which doesn't sign), so we lean on path/content
# checks below to constrain what that commit can do.
commit_count=$(echo "$commits_json" | jq '.commits | length')
if [ "$commit_count" -ne 2 ]; then
fail "expected exactly 2 commits, found $commit_count"
fi
first_author=$(echo "$commits_json" | jq -r '.commits[0].authors[0].login')
first_oid=$(echo "$commits_json" | jq -r '.commits[0].oid')
if [ "$first_author" != "dependabot[bot]" ]; then
fail "first commit author is '$first_author', expected 'dependabot[bot]'"
fi
# `gh pr view --json commits` doesn't expose signature info, so look
# it up via the REST commit endpoint.
first_verified=$(gh api "repos/$REPO/commits/$first_oid" --jq '.commit.verification.verified')
if [ "$first_verified" != "true" ]; then
fail "first commit (Dependabot) does not have a verified signature"
fi
second_email=$(echo "$commits_json" | jq -r '.commits[1].authors[0].email // ""')
second_message=$(echo "$commits_json" | jq -r '.commits[1].messageHeadline // ""')
if [ "$second_email" != "wrangler@cloudflare.com" ]; then
fail "second commit author email is '$second_email', expected 'wrangler@cloudflare.com'"
fi
if ! echo "$second_message" | grep -qE '^Update dependencies of '; then
fail "second commit message '$second_message' does not match expected changeset commit shape"
fi
# --- Changed files allowlist ---------------------------------------
# The only paths a workerd bump should touch:
# - .changeset/dependabot-update-*.md (added by the changeset job)
# - packages/*/package.json (workerd, workers-types pins)
# - pnpm-lock.yaml
# - pnpm-workspace.yaml (catalog entries)
allowed='^(\.changeset/dependabot-update-.*\.md|packages/[^/]+/package\.json|pnpm-lock\.yaml|pnpm-workspace\.yaml)$'
unexpected=$(echo "$files_json" | jq -r '.files[].path' | grep -vE "$allowed" || true)
if [ -n "$unexpected" ]; then
fail "PR touches unexpected files:$(echo "$unexpected" | sed 's/^/ /' | tr '\n' ',')"
fi
# Make sure the changeset is actually present — the changeset job
# may not have run yet, in which case we bail and wait for the
# `synchronize` event from its push.
if ! echo "$files_json" | jq -e '.files[] | select(.path | startswith(".changeset/dependabot-update-"))' > /dev/null; then
fail "changeset file not yet present; waiting for changeset job to push it"
fi
echo "verified=true" >> "$GITHUB_OUTPUT"
- name: Enable auto-merge
if: steps.meta.outputs.dependency-group == 'workerd-and-workers-types' && steps.verify.outputs.verified == 'true'
run: gh pr merge --auto --squash "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Disable auto-merge if verification failed
# If a previous run enabled auto-merge but a later push broke the
# invariants, actively cancel auto-merge so the bad commit can't ride.
# `gh pr merge --disable-auto` is a no-op (and exits 0) if auto-merge
# was never enabled, so this is safe to always run on the failure path.
if: always() && steps.meta.outputs.dependency-group == 'workerd-and-workers-types' && steps.verify.outputs.verified != 'true'
run: gh pr merge --disable-auto "$PR_URL" || true
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+90
View File
@@ -0,0 +1,90 @@
name: Deploy Previews
# This workflow is designed to deploy a "preview" version of a project based on PR labels.
# Triggers:
# - update to a PR that has one of the `preview:...` labels
#
# Actions:
# - deploy the matching project to Cloudflare.
#
# PR Label | Project
# ---------------------------------------------------------
# preview:chrome-devtools-patches | packages/chrome-devtools-patches
# preview:quick-edit | packages/quick-edit
#
# Note: this workflow does not run tests against these packages, only deploys previews.
on:
pull_request:
types: [synchronize, opened, reopened, labeled, unlabeled]
permissions:
contents: read
# pull-request:write permission needed so that the workflow can comment on PRs
pull-requests: write
jobs:
deploy-projects:
# Only run this on PRs that are for the "cloudflare" org and not "from" `main`
# - non-Cloudflare PRs will not have the secrets needed
# - PRs "from" main would accidentally do a production deployment
if: github.repository_owner == 'cloudflare' && github.head_ref != 'main' && (contains(github.event.*.labels.*.name, 'preview:chrome-devtools-patches') || contains(github.event.*.labels.*.name, 'preview:quick-edit'))
timeout-minutes: 60
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-app-previews
runs-on: macos-latest-large
steps:
- name: Checkout Repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 1
persist-credentials: false
- name: Install Dependencies
uses: ./.github/actions/install-dependencies
- name: Build tools and libraries
run: pnpm run build
env:
NODE_ENV: "production"
CI_OS: ${{ runner.os }}
- name: Deploy Wrangler DevTools preview
if: contains(github.event.*.labels.*.name, 'preview:chrome-devtools-patches')
run: |
output=$(pnpm --filter @cloudflare/chrome-devtools-patches run deploy:preview)
echo "Command output: $output"
echo "Extracting deployed URL from command output"
url=$(echo "$output" | sed -nE "s/.*Version Preview URL: ([^[:space:]]+).*/\1/p")
echo "Extracted URL: $url"
echo "VITE_DEVTOOLS_PREVIEW_URL=$url" >> $GITHUB_ENV
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
- name: Build and Deploy Quick Edit preview
if: contains(github.event.*.labels.*.name, 'preview:quick-edit')
run: pnpm --filter quick-edit run preview
env:
DEBIAN_FRONTEND: noninteractive
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
NODE_OPTIONS: "--max_old_space_size=30000"
- name: "Comment on PR with Devtools Link"
if: contains(github.event.*.labels.*.name, 'preview:chrome-devtools-patches')
uses: marocchino/sticky-pull-request-comment@d4d6b0936434b21bc8345ad45a440c5f7d2c40ff # v3.0.3
with:
header: chrome-devtools-preview
message: |
The Wrangler DevTools preview is now live. You can access it directly at: ${{ env.VITE_DEVTOOLS_PREVIEW_URL }}/js_app
In order to test the DevTools preview in `wrangler`:
1. `npx wrangler dev`.
2. Hit `d` to open the DevTools in a fresh browser window.
3. Paste the DevTools preview URL into the address bar (keeping all existing query parameters), e.g:
```
- https://devtools.devprod.cloudflare.dev/js_app?theme=systemPreferred&ws=127.0.0.1%3A9229%2Fws&domain=tester&debugger=true
+ ${{ env.VITE_DEVTOOLS_PREVIEW_URL }}/js_app?theme=systemPreferred&ws=127.0.0.1%3A9229%2Fws&domain=tester&debugger=true
```
@@ -0,0 +1,69 @@
name: Local Explorer UI E2E
on:
merge_group:
pull_request:
permissions:
contents: read
env:
# We run `playwright install` manually instead
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
jobs:
test:
name: Local Explorer UI E2E
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.head_ref != 'changeset-release/main' }}
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
persist-credentials: false
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: changes
with:
filters: |
relevant:
- 'packages/local-explorer-ui/**'
- 'packages/miniflare/src/workers/local-explorer/**'
- 'fixtures/worker-with-resources/**'
- 'fixtures/shared/**'
- name: Install Dependencies
if: steps.changes.outputs.relevant == 'true'
uses: ./.github/actions/install-dependencies
with:
turbo-api: ${{ secrets.TURBO_API }}
turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
turbo-team: ${{ secrets.TURBO_TEAM }}
turbo-token: ${{ secrets.TURBO_TOKEN }}
- name: Install Playwright
if: steps.changes.outputs.relevant == 'true'
run: pnpm --filter @cloudflare/local-explorer-ui playwright:install
- name: Build wrangler
if: steps.changes.outputs.relevant == 'true'
run: pnpm turbo build --filter wrangler
- name: Run Local Explorer UI E2E tests
if: steps.changes.outputs.relevant == 'true'
run: pnpm test:e2e -F @cloudflare/local-explorer-ui --log-order=stream
timeout-minutes: 30
env:
CI_OS: Linux
NODE_OPTIONS: "--max_old_space_size=8192"
- name: Upload turbo logs
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: turbo-runs
path: .turbo/runs
+33
View File
@@ -0,0 +1,33 @@
# This workflow cleans up any leftover projects created by e2e runs.
name: E2E Project Cleanup
on:
workflow_dispatch:
schedule:
- cron: "0 */2 * * *" # Run every 2 hours
permissions:
contents: read
jobs:
cleanup:
timeout-minutes: 30
name: "Cleanup Test Projects"
if: ${{ github.repository_owner == 'cloudflare' }}
runs-on: ubuntu-slim
steps:
- name: Checkout Repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 1
persist-credentials: false
- name: Install Dependencies
uses: ./.github/actions/install-dependencies
- name: Cleanup E2E test projects
run: node -r esbuild-register tools/e2e/e2eCleanup.ts
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.TEST_CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.TEST_CLOUDFLARE_ACCOUNT_ID }}
+73
View File
@@ -0,0 +1,73 @@
name: Vite Plugin E2E
on:
merge_group:
pull_request:
permissions:
contents: read
pull-requests: read
jobs:
e2e-vite-plugin-test:
name: ${{ format('Vite Plugin E2E ({0})', matrix.description) }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.os }}
cancel-in-progress: ${{ github.head_ref != 'changeset-release/main' }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- os: macos-latest
description: macOS
- os: windows-latest
description: Windows
- os: ubuntu-latest
description: Linux
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
persist-credentials: false
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: changes
with:
filters: |
everything_but_markdown:
- '!**/*.md'
- name: Check if remote tests should run
id: check-remote
uses: ./.github/actions/check-remote-tests
- name: Install Dependencies
if: steps.changes.outputs.everything_but_markdown == 'true'
uses: ./.github/actions/install-dependencies
with:
turbo-api: ${{ secrets.TURBO_API }}
turbo-team: ${{ secrets.TURBO_TEAM }}
turbo-token: ${{ secrets.TURBO_TOKEN }}
turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
- name: Run Vite plugin E2E tests
if: steps.changes.outputs.everything_but_markdown == 'true'
run: pnpm test:e2e -F @cloudflare/vite-plugin --log-order=stream
timeout-minutes: 20
env:
NODE_DEBUG: "vite-plugin:test"
# The remote-binding tests need to connect to Cloudflare
CLOUDFLARE_API_TOKEN: ${{ steps.check-remote.outputs.run-remote == 'true' && secrets.TEST_CLOUDFLARE_API_TOKEN || '' }}
CLOUDFLARE_ACCOUNT_ID: ${{ steps.check-remote.outputs.run-remote == 'true' && secrets.TEST_CLOUDFLARE_ACCOUNT_ID || '' }}
NODE_OPTIONS: "--max_old_space_size=8192"
CI_OS: ${{ matrix.os }}
- name: Upload turbo logs
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: turbo-runs-${{ matrix.os }}
path: .turbo/runs
+88
View File
@@ -0,0 +1,88 @@
name: Wrangler E2E
on:
merge_group:
pull_request:
permissions:
contents: read
pull-requests: read
jobs:
e2e-wrangler-test:
name: ${{ format('Wrangler E2E ({0}, shard {1}/4)', matrix.description, matrix.shard) }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.os }}-${{ matrix.shard }}
cancel-in-progress: ${{ github.head_ref != 'changeset-release/main' }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
shard: [1, 2, 3, 4]
include:
- os: macos-latest
description: macOS
- os: windows-latest
description: Windows
- os: ubuntu-latest
description: Linux
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
persist-credentials: false
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: changes
with:
filters: |
everything_but_markdown:
- '!**/*.md'
- name: Check if remote tests should run
id: check-remote
uses: ./.github/actions/check-remote-tests
- name: Install Dependencies
if: steps.changes.outputs.everything_but_markdown == 'true'
uses: ./.github/actions/install-dependencies
with:
turbo-api: ${{ secrets.TURBO_API }}
turbo-team: ${{ secrets.TURBO_TEAM }}
turbo-token: ${{ secrets.TURBO_TOKEN }}
turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
- name: Run Wrangler E2E tests
if: steps.changes.outputs.everything_but_markdown == 'true'
run: pnpm run test:e2e:wrangler
env:
CLOUDFLARE_API_TOKEN: ${{ steps.check-remote.outputs.run-remote == 'true' && secrets.TEST_CLOUDFLARE_API_TOKEN || '' }}
CLOUDFLARE_ACCOUNT_ID: ${{ steps.check-remote.outputs.run-remote == 'true' && secrets.TEST_CLOUDFLARE_ACCOUNT_ID || '' }}
HYPERDRIVE_DATABASE_URL: ${{ secrets.TEST_HYPERDRIVE_DATABASE_URL}}
HYPERDRIVE_MYSQL_DATABASE_URL: ${{ secrets.TEST_HYPERDRIVE_MYSQL_DATABASE_URL}}
NODE_OPTIONS: "--max_old_space_size=8192"
WRANGLER_LOG_PATH: ${{ runner.temp }}/wrangler-debug-logs/
TEST_REPORT_PATH: ${{ runner.temp }}/test-report/index.html
CI_OS: ${{ matrix.os }}
E2E_SHARD: ${{ matrix.shard }}
E2E_SHARD_COUNT: "4"
- name: Run getPlatformProxy() remote-bindings e2e tests
if: steps.changes.outputs.everything_but_markdown == 'true' && matrix.shard == 1
run: pnpm run test:e2e -F @fixture/get-platform-proxy-remote-bindings
env:
TEST_CLOUDFLARE_API_TOKEN: ${{ steps.check-remote.outputs.run-remote == 'true' && secrets.TEST_CLOUDFLARE_API_TOKEN || '' }}
TEST_CLOUDFLARE_ACCOUNT_ID: ${{ steps.check-remote.outputs.run-remote == 'true' && secrets.TEST_CLOUDFLARE_ACCOUNT_ID || '' }}
NODE_OPTIONS: "--max_old_space_size=8192"
CI_OS: ${{ matrix.os }}
- name: Upload turbo logs
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: turbo-runs-${{ matrix.os }}-shard-${{ matrix.shard }}
path: .turbo/runs
+82
View File
@@ -0,0 +1,82 @@
name: Release a hotfix
on:
workflow_dispatch:
inputs:
pr-number:
description: "Which PR should be released?"
required: true
confirm:
description: "Confirm that the PR is up to date with the last published release, with the specific additional changes that should be released as part of this hotfix and the relevant package patch versions bumped"
type: boolean
required: true
label:
description: "Which dist-tag should this release have?"
type: string
default: hotfix
required: true
permissions:
contents: read
id-token: write
jobs:
hotfix-release:
name: Hotfix Release
if: ${{ inputs.confirm == true }}
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
persist-credentials: false
- name: Check user for team affiliation
uses: tspascoal/get-user-teams-membership@ba78054988f58bea69b7c6136d563236f8ed2fc0 # v2
id: teamAffiliation
with:
GITHUB_TOKEN: ${{ secrets.READ_ONLY_ORG_GITHUB_TOKEN }}
username: ${{ github.actor }}
team: wrangler
- name: Stop workflow if user is not a wrangler team member
if: ${{ steps.teamAffiliation.outputs.isTeamMember == false }}
run: |
echo "You must be on the "wrangler" team to trigger this job."
exit 1
- name: "Checkout PR"
run: |
if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then
echo "::error::PR number must be numeric"
exit 1
fi
gh pr checkout "$PR_NUMBER"
env:
GH_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }}
PR_NUMBER: ${{ inputs.pr-number }}
- name: Install Dependencies
# Defense in depth: do not pass Turbo remote cache credentials and
# disable the pnpm store cache so release builds always resolve
# packages from the registry and rebuild every task from source,
# rather than restoring from a (potentially poisoned) cache.
uses: ./.github/actions/install-dependencies
with:
disable-cache: "true"
- name: Build all packages
run: pnpm run build
env:
CI_OS: ${{ runner.os }}
- name: Publish packages
run: |
if ! [[ "$NPM_TAG" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]]; then
echo "::error::npm dist-tag contains unsupported characters"
exit 1
fi
pnpm publish -r --tag "$NPM_TAG" --filter wrangler --filter miniflare --filter create-cloudflare
env:
NPM_TAG: ${{ inputs.label }}
+41
View File
@@ -0,0 +1,41 @@
name: Add issues to DevProd project
on:
issues:
types: [opened, transferred]
permissions:
# issues:write permission needed so that the workflow can manage issues
issues: write
jobs:
add-to-project:
name: Add issue to project
runs-on: ubuntu-slim
steps:
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
with:
project-url: https://github.com/orgs/cloudflare/projects/1
github-token: ${{ secrets.GH_ACCESS_TOKEN }}
labeled: bug, enhancement
label-operator: OR
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
with:
project-url: https://github.com/orgs/cloudflare/projects/2
github-token: ${{ secrets.GH_ACCESS_TOKEN }}
labeled: product:pages
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
with:
project-url: https://github.com/orgs/cloudflare/projects/6
github-token: ${{ secrets.GH_ACCESS_TOKEN }}
labeled: product:d1
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
with:
project-url: https://github.com/orgs/cloudflare/projects/12
github-token: ${{ secrets.GH_ACCESS_TOKEN }}
labeled: product:c3
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
with:
project-url: https://github.com/orgs/cloudflare/projects/8
github-token: ${{ secrets.GH_ACCESS_TOKEN }}
labeled: product:queues
@@ -0,0 +1,51 @@
name: "Miniflare - Generate changesets for dependabot PRs"
on:
pull_request_target: # zizmor: ignore[dangerous-triggers] dependabot-only job requires write access to push generated changesets; checkout v6 persists credentials under RUNNER_TEMP
paths:
- "packages/miniflare/package.json"
- "pnpm-workspace.yaml"
permissions:
# content:write permission needed to update add changesets to dependabot PRs
# (see tools/dependabot/generate-dependabot-pr-changesets.ts)
contents: write
jobs:
generate-changeset:
runs-on: ubuntu-slim
if: |
github.actor == 'dependabot[bot]' &&
github.event.pull_request.user.login == 'dependabot[bot]' &&
github.event.pull_request.head.repo.full_name == github.repository
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 2
persist-credentials: true
ref: ${{ github.head_ref }}
token: ${{ secrets.GH_ACCESS_TOKEN }}
- name: Install Dependencies
uses: ./.github/actions/install-dependencies
- name: Configure Git
run: |
git config --global user.email wrangler@cloudflare.com
git config --global user.name 'Wrangler automated PR updater'
- name: Generate Miniflare changesets
# The paremeters to the script are:
# - PR: The number of the current Dependabot PR
# - Packages: Coma-separated names of the workers-sdk packages whose dependencies are being updated
# - PackageJSON: The path to the package JSON being updated by Dependabot
# - Changeset prefix: The prefix to go on the front of the filename of the generated changeset
run: >-
node -r esbuild-register tools/dependabot/generate-dependabot-pr-changesets.ts
"$PR_NUMBER"
miniflare,wrangler
packages/miniflare/package.json
dependabot-update
env:
PR_NUMBER: ${{ github.event.number }}
@@ -0,0 +1,19 @@
name: OpenCode Issue Solver
on:
workflow_dispatch:
inputs:
issue-number:
description: "Issue number to attempt to fix"
required: true
type: number
jobs:
assess-issue:
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Exit
run: |
exit 1
+53
View File
@@ -0,0 +1,53 @@
name: Prerelease
on:
pull_request:
push:
branches:
- main
- d1
- workflows
- next
- v3-maintenance
permissions:
contents: read
jobs:
build:
if: ${{ github.repository_owner == 'cloudflare' }}
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.head_ref != 'changeset-release/main' }}
timeout-minutes: 30
steps:
- name: Checkout Repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 1
persist-credentials: false
- name: Install Dependencies
# Defense in depth: do not pass Turbo remote cache credentials and
# disable the pnpm store cache so release builds always resolve
# packages from the registry and rebuild every task from source,
# rather than restoring from a (potentially poisoned) cache.
uses: ./.github/actions/install-dependencies
with:
disable-cache: "true"
- name: Build
run: pnpm build --filter="./packages/*"
env:
NODE_ENV: "production"
CI_OS: ${{ runner.os }}
# this is the "test/staging" key for sparrow analytics
SPARROW_SOURCE_KEY: "5adf183f94b3436ba78d67f506965998"
ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
SENTRY_DSN: "https://9edbb8417b284aa2bbead9b4c318918b@sentry10.cfdata.org/583"
ALGOLIA_PUBLIC_KEY: ${{ secrets.ALGOLIA_PUBLIC_KEY }}
WRANGLER_PRERELEASE_LABEL: ${{ github.head_ref || github.ref_name }}
- name: Upload packages
run: node -r esbuild-register .github/prereleases/upload.mjs
@@ -0,0 +1,67 @@
name: "Rerun Code Owners (Privileged)"
# Privileged companion to rerun-codeowners.yml.
#
# Runs after the "Rerun Code Owners" trigger workflow completes. The
# workflow_run event always executes from the default branch with full
# permissions — this is what allows us to call the Actions API even when the
# original event (pull_request_review) came from a fork PR, where the
# GITHUB_TOKEN would otherwise be read-only.
#
# The PR head SHA is read from github.event.workflow_run.head_sha — this is
# GitHub-provided metadata and cannot be manipulated by fork code (unlike
# the previous artifact-based approach).
#
# Strategy: CHECK-NAME LOOKUP
# ----------------------------
# The codeowners.yml workflow uses pull_request_target, which means its
# workflow runs are indexed by the BASE branch SHA (e.g. main), NOT the PR
# head SHA. So we cannot find the run via:
#
# GET /actions/workflows/codeowners.yml/runs?head_sha=<PR_HEAD>
#
# That query returns nothing because the run's head_sha is main's HEAD.
#
# However, the codeowners-plus action creates its CHECK RUN on the PR head
# commit (so the status appears on the PR). The check-runs API lets us look
# up by check name + commit SHA:
#
# GET /commits/<PR_HEAD>/check-runs?check_name=Run+Codeowners+Plus
#
# From the check run's details_url we extract the Actions job ID, then
# re-run that specific job.
on:
workflow_run: # zizmor: ignore[dangerous-triggers] privileged companion reruns a fixed default-branch workflow using GitHub-provided workflow_run metadata only
workflows: ["Rerun Code Owners"]
types: [completed]
permissions: {}
jobs:
rerun-codeowners:
name: "Rerun Codeowners Plus"
runs-on: ubuntu-latest
if: github.event.workflow_run.conclusion == 'success'
permissions:
actions: write
checks: read
steps:
- name: Re-run Codeowners check
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
REPO: ${{ github.repository }}
run: |
# Find the "Run Codeowners Plus" check run on the PR head commit
# and extract the Actions job ID from the check run's details URL
# (the last path segment before any query string).
job_id=$(gh api "repos/${REPO}/commits/${HEAD_SHA}/check-runs?check_name=Run+Codeowners+Plus" \
--jq '(.check_runs[0].details_url // "") | split("/") | last | split("?") | first' || true)
if [ -n "$job_id" ]; then
gh api "repos/${REPO}/actions/jobs/${job_id}/rerun" --method POST \
|| echo "::warning::Job ${job_id} may already be running"
echo "Re-triggered 'Run Codeowners Plus' (job ${job_id})"
else
echo "::warning::Check 'Run Codeowners Plus' not found for SHA ${HEAD_SHA}"
fi
+35
View File
@@ -0,0 +1,35 @@
name: "Rerun Code Owners"
# Trigger: a review is submitted or dismissed on a pull request.
#
# Goal: re-run the "Run Codeowners Plus" check from the main codeowners.yml
# workflow (which is triggered by pull_request_target) so it re-evaluates
# approval status after the review change.
#
# Why a two-workflow design?
# -------------------------
# For fork PRs, pull_request_review gives a read-only GITHUB_TOKEN and no
# access to secrets. Unlike labeled/unlabeled events there is no
# pull_request_review variant of pull_request_target, so we cannot get a
# privileged token in a single workflow. Instead this workflow simply needs
# to *exist and succeed* — the companion workflow
# (rerun-codeowners-privileged.yml) is triggered by the workflow_run event
# when THIS workflow completes. workflow_run always runs from the default
# branch with full permissions, and it reads the PR head SHA directly from
# github.event.workflow_run.head_sha (GitHub-provided metadata, not
# controllable by fork code).
on:
pull_request_review:
types: [submitted, dismissed]
permissions: {}
jobs:
trigger:
name: "Trigger Privileged Rerun"
runs-on: ubuntu-latest
steps:
- name: Log trigger
run: echo "Review event on ${PR_URL}, privileged rerun will follow."
env:
PR_URL: ${{ github.event.pull_request.html_url }}
+86
View File
@@ -0,0 +1,86 @@
name: "Rerun Remote Tests"
# Trigger: the "run-remote-tests" or "run-c3-frameworks-tests" label is added
# to or removed from a pull request.
#
# Goal: re-run the E2E workflows (e2e-wrangler.yml, e2e-vite.yml, c3-e2e.yml)
# so they pick up the label change and either pass or withhold the Cloudflare
# API token to the test steps. This avoids adding "labeled" as a trigger type
# to every E2E workflow (which would cause wasteful re-runs on unrelated label
# changes).
#
# Using pull_request_target (not pull_request) so the workflow has access to a
# privileged GITHUB_TOKEN even for fork PRs. This is safe because the workflow
# runs code from the default branch — fork authors cannot modify it — and only
# calls the Actions API (no checkout of untrusted code).
on:
pull_request_target: # zizmor: ignore[dangerous-triggers] label-driven rerun workflow needs actions:write and does not check out or execute PR code
types: [labeled, unlabeled]
permissions: {}
jobs:
rerun-e2e:
name: "Rerun E2E Tests"
if: github.event.label.name == 'ci:run-remote-tests' || github.event.label.name == 'run-c3-frameworks-tests'
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: "Re-run E2E workflows"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
LABEL_NAME: ${{ github.event.label.name }}
REPO: ${{ github.repository }}
run: |
# Determine which workflows to re-run based on the label.
if [ "$LABEL_NAME" = "ci:run-remote-tests" ]; then
WORKFLOWS="e2e-wrangler.yml e2e-vite.yml c3-e2e.yml"
else
WORKFLOWS="c3-e2e.yml"
fi
for workflow in ${WORKFLOWS}; do
# Find the most recent run of this workflow at the PR head SHA.
run_info=$(gh api "repos/${REPO}/actions/workflows/${workflow}/runs?head_sha=${HEAD_SHA}&per_page=1" \
--jq '.workflow_runs[0] | "\(.id) \(.status)"' || true)
run_id=$(echo "$run_info" | awk '{print $1}')
status=$(echo "$run_info" | awk '{print $2}')
if [ -z "$run_id" ] || [ "$run_id" = "null" ]; then
echo "::warning::No run found for ${workflow} at SHA ${HEAD_SHA}"
continue
fi
# Runs that haven't started yet will see the newly-added label
# when they begin — no need to cancel and re-run them.
if [ "$status" != "completed" ] && [ "$status" != "in_progress" ]; then
echo "${workflow} run ${run_id} is ${status} — not yet started, skipping."
continue
fi
# If the run is actively executing, the label check has already
# evaluated (likely to the old value). Cancel it first — the
# rerun endpoint only works on completed runs.
if [ "$status" = "in_progress" ]; then
echo "Cancelling in-progress ${workflow} run ${run_id}..."
gh api "repos/${REPO}/actions/runs/${run_id}/cancel" --method POST || true
# Cancellation is async; poll until the run reaches "completed".
for i in $(seq 1 30); do
current=$(gh api "repos/${REPO}/actions/runs/${run_id}" --jq '.status')
if [ "$current" = "completed" ]; then
echo "Run ${run_id} is now completed (cancelled)."
break
fi
echo " Waiting for cancellation to finish (${i}/30, status: ${current})..."
sleep 2
done
fi
gh api "repos/${REPO}/actions/runs/${run_id}/rerun" --method POST \
&& echo "Re-triggered ${workflow} (run ${run_id})" \
|| echo "::warning::Failed to re-run ${workflow} (run ${run_id})"
done
@@ -0,0 +1,100 @@
name: Run CI on behalf of External Forks
on:
workflow_dispatch:
inputs:
pr-number:
description: "The PR number to run CI on behalf of"
required: true
commit-sha:
description: "The specific commit SHA from the PR branch to run CI on"
required: true
reviewed:
description: "Confirm that the PR has been reviewed for use/leakage of secrets"
type: boolean
required: true
permissions:
contents: read # no write permissions are needed since the workflow uses GH_ACCESS_TOKEN instead of GITHUB_TOKEN
pull-requests: write # Required for creating or updating the draft PR on behalf of the user
jobs:
create-draft-pr:
name: Create or Update Draft PR
if: ${{ inputs.reviewed == true }}
runs-on: ubuntu-slim
steps:
- name: Checkout Repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
persist-credentials: true
token: ${{ secrets.GH_ACCESS_TOKEN }}
- name: Check user for team affiliation
uses: tspascoal/get-user-teams-membership@ba78054988f58bea69b7c6136d563236f8ed2fc0
id: teamAffiliation
with:
GITHUB_TOKEN: ${{ secrets.READ_ONLY_ORG_GITHUB_TOKEN }}
username: ${{ github.actor }}
team: wrangler
- name: Stop workflow if user is not a team member
if: ${{ steps.teamAffiliation.outputs.isTeamMember != 'true' }}
run: |
echo "You must be on the \"wrangler\" team to trigger this job."
exit 1
- name: "Checkout PR"
run: |
gh pr checkout "$PR_NUM" --branch "run-ci-on-behalf-of-$PR_NUM"
git reset --hard "$COMMIT_SHA"
env:
# We need a PAT to checkout the fork
GH_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }}
PR_NUM: ${{ inputs.pr-number }}
COMMIT_SHA: ${{ inputs.commit-sha }}
- name: Push Branch
run: |
git config user.email "github-actions[bot]@users.noreply.github.com"
git config user.name "github-actions[bot]"
git push origin HEAD --force
- name: "Create or Update Draft PR"
run: |
existing_pr=$(gh pr list \
--head "run-ci-on-behalf-of-$PR_NUM" \
--state open \
--json number \
--jq 'first(.[].number) // ""')
if [ -n "$existing_pr" ]; then
gh pr edit "$existing_pr" \
--add-label "ci:e2e" \
--add-label "ci:run-remote-tests" \
--title "$TITLE" \
--body "$BODY"
else
gh pr create \
--head "run-ci-on-behalf-of-$PR_NUM" \
--draft \
--label "ci:e2e" \
--label "ci:run-remote-tests" \
--title "$TITLE" \
--body "$BODY"
fi
env:
GH_TOKEN: ${{ github.token }}
PR_NUM: ${{ inputs.pr-number }}
TITLE: "Run CI on behalf of #${{ inputs.pr-number }} @${{ inputs.commit-sha }}"
BODY: "This PR runs CI on behalf of #${{ inputs.pr-number }} at commit ${{ inputs.commit-sha }}. It can be closed after the CI run is complete."
- name: "Trigger CI"
run: |
# Push an empty commit to trigger CI workflows
# This ensures CI runs because the PR already exists when this push happens
git commit --allow-empty -m "Trigger CI for #$PR_NUM"
git push origin HEAD
env:
PR_NUM: ${{ inputs.pr-number }}
+31
View File
@@ -0,0 +1,31 @@
name: Semgrep OSS scan
on:
pull_request: {}
push:
branches: [main]
workflow_dispatch: {}
schedule:
- cron: "0 0 15 * *"
concurrency:
group: semgrep-${{ github.event_name }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
permissions:
contents: read
jobs:
semgrep:
name: semgrep-oss
runs-on: ubuntu-slim
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 1
persist-credentials: false
- id: cache-semgrep
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: ~/.local
key: semgrep-1.160.0-${{ runner.os }}
- if: steps.cache-semgrep.outputs.cache-hit != 'true'
run: pip install --user semgrep==1.160.0
- run: echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- run: semgrep scan --config=auto || true
@@ -0,0 +1,118 @@
name: CI (Other Node Versions)
description: Run primary tests on other supported Node.js versions
on:
merge_group:
pull_request:
types: [synchronize, opened, reopened, labeled, unlabeled]
permissions:
contents: read
jobs:
test-other-node-versions:
timeout-minutes: 45
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.node_version }}-test-other-node-versions
cancel-in-progress: ${{ github.head_ref != 'changeset-release/main' }}
name: ${{ format('Tests ({0})', matrix.description) }}
strategy:
fail-fast: false
matrix:
# expected_to_fail: if true, tests may fail on this Node version.
# These versions are skipped on normal PRs but run on Version Packages PRs
# (changeset-release/main) or when the 'ci:test-all-node-versions' label is added.
#
# Node 24 is pinned to 24.18.0 (rather than floating `24`): Node 24.17.0
# regressed node-fetch@2 keep-alive responses with ERR_STREAM_PREMATURE_CLOSE
# (nodejs/node#63989, fixed in 24.18.0). `description` stays "Node 24" to
# keep the job/check name stable.
include:
- {
node_version: 24.18.0,
description: "Node 24",
expected_to_fail: true,
}
- { node_version: 25, description: "Node 25", expected_to_fail: true }
steps:
- name: Checkout Repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 1
persist-credentials: false
- name: Filter changed paths
uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: changes
with:
filters: |
everything_but_markdown:
- '!**/*.md'
- name: Check if should run tests
id: should_run
env:
CHANGES_EVERYTHING_BUT_MARKDOWN: ${{ steps.changes.outputs.everything_but_markdown }}
EXPECTED_TO_FAIL: ${{ matrix.expected_to_fail }}
HAS_TEST_ALL_NODE_VERSIONS_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'ci:test-all-node-versions') }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
run: |
if [[ "$CHANGES_EVERYTHING_BUT_MARKDOWN" == "true" ]] && \
[[ "$EXPECTED_TO_FAIL" != "true" || \
"$HEAD_REF" == "changeset-release/main" || \
"$HAS_TEST_ALL_NODE_VERSIONS_LABEL" == "true" ]]; then
echo "result=true" >> $GITHUB_OUTPUT
else
echo "result=false" >> $GITHUB_OUTPUT
fi
- name: Install Dependencies
if: steps.should_run.outputs.result == 'true'
uses: ./.github/actions/install-dependencies
with:
node-version: ${{ matrix.node_version }}
turbo-api: ${{ secrets.TURBO_API }}
turbo-team: ${{ secrets.TURBO_TEAM }}
turbo-token: ${{ secrets.TURBO_TOKEN }}
turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
# The miniflare browser-rendering tests download Chrome (~150 MB) via
# @puppeteer/browsers at runtime. Caching the binary avoids repeat
# downloads and reduces the chance of tests hanging while waiting for
# the download to finish within their timeout window.
- name: Restore Chrome browser cache (Browser Run)
if: steps.should_run.outputs.result == 'true'
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
key: chrome-${{ runner.os }}-${{ hashFiles('packages/miniflare/src/plugins/browser-rendering/browser-version.ts') }}
path: |
~/.cache/.wrangler/chrome
- name: Run tests (packages)
# We are running the package tests first be able to get early feedback on changes.
# There is no point in running the fixtures if a package is broken.
if: steps.should_run.outputs.result == 'true'
run: pnpm run test:ci --log-order=stream --concurrency=1 --filter="./packages/*"
env:
NODE_OPTIONS: "--max_old_space_size=8192"
WRANGLER_LOG_PATH: ${{ runner.temp }}/wrangler-debug-logs/
TEST_REPORT_PATH: ${{ runner.temp }}/test-report/packages/index.html
CI_OS: ${{ matrix.description }}
- name: Run e2e tests (unenv-preset)
if: steps.should_run.outputs.result == 'true'
run: pnpm test:e2e --filter wrangler -- unenv-preset
env:
NODE_OPTIONS: "--max_old_space_size=8192"
WRANGLER_LOG_PATH: ${{ runner.temp }}/wrangler-debug-logs/
CI_OS: ${{ matrix.description }}
- name: Run tests (vite-plugin)
if: steps.should_run.outputs.result == 'true'
run: pnpm test:ci --filter @cloudflare/vite-plugin
env:
NODE_OPTIONS: "--max_old_space_size=8192"
WRANGLER_LOG_PATH: ${{ runner.temp }}/wrangler-debug-logs/
CI_OS: ${{ matrix.description }}
+202
View File
@@ -0,0 +1,202 @@
name: CI
on:
merge_group:
pull_request:
permissions:
contents: read
env:
# We run `playwright install` manually instead
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
jobs:
add-to-project:
if: github.head_ref != 'changeset-release/main'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-add-pr
cancel-in-progress: ${{ github.head_ref != 'changeset-release/main' }}
timeout-minutes: 30
name: Add PR to project
runs-on: ubuntu-latest
steps:
- run: curl -X POST https://devprod-status-bot.devprod.workers.dev/pr-project/workers-sdk/${{ github.event.number }}
check:
timeout-minutes: 30
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-checks
cancel-in-progress: ${{ github.head_ref != 'changeset-release/main' }}
name: "Checks"
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
persist-credentials: false
- name: Install Dependencies
uses: ./.github/actions/install-dependencies
with:
turbo-api: ${{ secrets.TURBO_API }}
turbo-team: ${{ secrets.TURBO_TEAM }}
turbo-token: ${{ secrets.TURBO_TOKEN }}
turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
- name: Check the changesets
run: node -r esbuild-register tools/deployments/validate-changesets.ts
- name: Check for errors
run: pnpm run check --summarize
env:
CI_OS: ${{ runner.os }}
NODE_OPTIONS: "--max_old_space_size=8192"
- name: Filter changed paths
uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: changes
with:
filters: |
miniflare_openapi:
- 'packages/miniflare/src/workers/local-explorer/openapi.local.json'
- 'packages/miniflare/src/workers/local-explorer/generated/**'
- 'packages/miniflare/scripts/openapi-filter-config.ts'
- 'packages/miniflare/scripts/filter-openapi.ts'
- 'packages/miniflare/scripts/check-generate-api.ts'
- 'packages/miniflare/openapi-ts.config.ts'
- name: Check generated OpenAPI types are up to date
if: steps.changes.outputs.miniflare_openapi == 'true'
run: pnpm -F miniflare check:generate-api
# Check for old Node.js version warnings and errors
- name: Use Node.js v20
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 20
- name: Check for error message on Node.js < v22
run: node packages/wrangler/src/__tests__/test-old-node-version.js error
test:
timeout-minutes: 30
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.os }}-${{ matrix.suite }}-test
cancel-in-progress: ${{ github.head_ref != 'changeset-release/main' }}
name: ${{ format('Tests ({0}, {1})', matrix.description, matrix.suite) }}
strategy:
fail-fast: false
matrix:
os:
- macos-latest
- ubuntu-latest
- windows-latest
suite:
- packages-and-tools
- fixtures
include:
- os: macos-latest
description: macOS
- os: ubuntu-latest
description: Linux
- os: windows-latest
description: Windows
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
persist-credentials: false
- name: Filter changed paths
uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: changes
with:
filters: |
everything_but_markdown:
- '!**/*.md'
- name: Install Dependencies
if: steps.changes.outputs.everything_but_markdown == 'true'
uses: ./.github/actions/install-dependencies
with:
turbo-api: ${{ secrets.TURBO_API }}
turbo-team: ${{ secrets.TURBO_TEAM }}
turbo-token: ${{ secrets.TURBO_TOKEN }}
turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
# Browser Run tests in `packages/miniflare/test/plugins/browser/index.spec.ts`
# and the `fixtures/browser-run` fixture use `@puppeteer/browsers` to
# download Chrome into the global Wrangler cache. Caching that binary
# across CI runs avoids ~150 MB of repeat downloads per run and reduces
# the surface area for the intermittent partial-extraction race that
# surfaces as `The browser folder (...) exists but the executable (...)
# is missing` (see #13971, #13980).
#
# The Browser Run fixture is skipped on Ubuntu (AppArmor), but the
# miniflare browser spec runs everywhere, so the cache is needed on all
# three OSes for `packages-and-tools` and on Windows + macOS for
# `fixtures`.
- name: Restore Chrome browser cache (Browser Run)
if: steps.changes.outputs.everything_but_markdown == 'true' && (matrix.suite == 'packages-and-tools' || (matrix.suite == 'fixtures' && matrix.os != 'ubuntu-latest'))
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
# The Chrome version lives in
# `packages/miniflare/src/plugins/browser-rendering/browser-version.ts`.
# Bumping that constant invalidates this cache automatically via
# `hashFiles()` — a cache miss only triggers a fresh download, no
# functional impact.
key: chrome-${{ runner.os }}-${{ hashFiles('packages/miniflare/src/plugins/browser-rendering/browser-version.ts') }}
path: |
~/.cache/.wrangler/chrome
~/Library/Caches/.wrangler/chrome
~/AppData/Local/xdg.cache/.wrangler/chrome
- name: Run tests (tools only)
# tools _only_ needs to be tested on Linux because they're only intended to run in CI
if: steps.changes.outputs.everything_but_markdown == 'true' && matrix.suite == 'packages-and-tools' && matrix.os == 'ubuntu-latest'
run: pnpm run test:ci --log-order=stream --filter="@cloudflare/tools"
env:
NODE_OPTIONS: "--max_old_space_size=8192"
TEST_REPORT_PATH: ${{ runner.temp }}/test-report/tools/index.html
CI_OS: ${{ matrix.description }}
- name: Run tests (packages)
if: steps.changes.outputs.everything_but_markdown == 'true' && matrix.suite == 'packages-and-tools'
# We skip @cloudflare/vitest-pool-workers tests in CI on Windows because they're very flaky. We still run the vitest-pool-workers-examples fixture, which is a comprehensive set of example tests and gives us a lot of confidence.
# The @cloudflare/vitest-pool-workers tests skipped are things like watch mode, which constantly times out probably due to the github runners in use.
# Package tests are well-isolated (separate processes, temp dirs, random ports, MSW mocks) and can safely run in parallel.
# Concurrency is capped at 3 to avoid CPU starvation on 4-vCPU CI runners when heavyweight suites
# (wrangler/forks, miniflare/workerd, vitest-pool-workers/Verdaccio) overlap.
run: pnpm run test:ci --log-order=stream --concurrency=3 --filter="./packages/*" ${{ matrix.os == 'windows-latest' && '--filter="!./packages/vitest-pool-workers"' || '' }}
env:
NODE_OPTIONS: "--max_old_space_size=8192"
WRANGLER_LOG_PATH: ${{ runner.temp }}/wrangler-debug-logs/
TEST_REPORT_PATH: ${{ runner.temp }}/test-report/packages/index.html
CI_OS: ${{ matrix.description }}
- name: Run tests (fixtures)
if: steps.changes.outputs.everything_but_markdown == 'true' && matrix.suite == 'fixtures'
# Browser Run fixture is disabled on Ubuntu because of https://pptr.dev/troubleshooting#issues-with-apparmor-on-ubuntu
# Since the dev registry is now file-based (not network-based), fixture tests can safely run in parallel.
# Concurrency is capped at 2 to avoid CPU starvation on CI runners when multiple fixtures
# spawn workerd processes simultaneously (Windows runners are especially slow under load).
run: pnpm run test:ci --concurrency=2 --log-order=stream --filter="./fixtures/*" ${{ matrix.os == 'ubuntu-latest' && '--filter="!./fixtures/browser-run"' || '' }}
env:
NODE_OPTIONS: "--max_old_space_size=8192"
WRANGLER_LOG_PATH: ${{ runner.temp }}/wrangler-debug-logs/
TEST_REPORT_PATH: ${{ runner.temp }}/test-report/index.html
CI_OS: ${{ matrix.description }}
- name: Upload turbo logs
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: turbo-runs-${{ matrix.os }}-${{ matrix.suite }}
path: .turbo/runs
+235
View File
@@ -0,0 +1,235 @@
name: Triage Issue
on:
issues:
types: [opened]
issue_comment:
types: [created]
workflow_dispatch:
inputs:
issue-number:
description: "Issue number to triage"
required: true
type: number
permissions:
contents: read
# Writes (comment + labels) are performed with GH_ACCESS_TOKEN (the
# workers-devprod bot) so they are attributed to that identity, not
# github-actions[bot]. Only read access is needed from the default token.
issues: read
# Cancel in-progress runs for the same issue
concurrency:
group: triage-${{ github.event.issue.number || inputs.issue-number }}
cancel-in-progress: ${{ github.head_ref != 'changeset-release/main' }}
jobs:
triage:
runs-on: ubuntu-latest
# Always run for manual dispatch. For issue events, skip PRs and
# bot-created issues. For comment events, skip PRs, bot comments, and the
# triage bot's own sticky comment (identified by the marocchino marker) to
# avoid infinite re-triage loops.
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'issues' &&
!github.event.issue.pull_request &&
github.event.issue.user.type != 'Bot') ||
(github.event_name == 'issue_comment' &&
!github.event.issue.pull_request &&
github.event.comment.user.type != 'Bot' &&
!contains(github.event.comment.body, '<!-- Sticky Pull Request Comment'))
timeout-minutes: 60
steps:
- name: Resolve issue number
id: issue
env:
EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
EVENT_NAME: ${{ github.event_name }}
INPUT_ISSUE_NUMBER: ${{ inputs.issue-number }}
run: |
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
echo "number=${INPUT_ISSUE_NUMBER}" >> "$GITHUB_OUTPUT"
else
echo "number=${EVENT_ISSUE_NUMBER}" >> "$GITHUB_OUTPUT"
fi
- name: Checkout (sparse — just the skill and opencode config)
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
sparse-checkout: |
.github/skills
.github/opencode.json
- name: Install OpenCode
run: |
npm install -g opencode-ai
cp .github/opencode.json ./opencode.json
- name: Fetch issue data
env:
GH_TOKEN: ${{ github.token }}
ISSUE: ${{ steps.issue.outputs.number }}
REPO: ${{ github.repository }}
run: |
mkdir -p "data/${ISSUE}"
gh issue view "$ISSUE" \
--repo "$REPO" \
--json number,title,body,comments,createdAt,updatedAt,labels,state,author \
> "data/${ISSUE}/context.json"
- name: Analyze issue with OpenCode
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_AI_GATEWAY_ACCOUNT_ID }}
CLOUDFLARE_GATEWAY_ID: ${{ secrets.CF_AI_GATEWAY_NAME }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_AI_GATEWAY_TOKEN }}
ISSUE: ${{ steps.issue.outputs.number }}
REPO: ${{ github.repository }}
run: |
opencode run \
--print-logs \
"Analyze GitHub issue ${REPO}#${ISSUE} using the skill at .github/skills/issue-review.md.
The issue data has been pre-fetched to data/${ISSUE}/context.json.
Read it with the read tool and follow the triage process.
Create:
- data/${ISSUE}/report.md (full markdown report)
- data/${ISSUE}/summary.json (structured JSON summary)
"
- name: Upload report to dashboard
env:
CF_ACCESS_CLIENT_ID: ${{ secrets.CF1_ACCESS_CLIENT_ID }}
CF_ACCESS_CLIENT_SECRET: ${{ secrets.CF1_ACCESS_CLIENT_SECRET }}
DASHBOARD_URL: ${{ secrets.REPORTS_DASHBOARD_URL }}
ISSUE: ${{ steps.issue.outputs.number }}
REPO: ${{ github.repository }}
run: |
# Check files were created
if [ ! -f "data/${ISSUE}/report.md" ]; then
echo "::error::report.md was not generated"
exit 1
fi
if [ ! -f "data/${ISSUE}/summary.json" ]; then
echo "::error::summary.json was not generated"
exit 1
fi
# Validate the summary is parseable JSON before doing anything with it.
if ! jq empty "data/${ISSUE}/summary.json" 2>/dev/null; then
echo "::error::summary.json is not valid JSON"
exit 1
fi
# Build the dashboard payload from the structured summary + markdown report.
# suggestedComment is kept as a "Yes"/"No" string for dashboard compatibility.
PAYLOAD=$(jq \
--arg githubUrl "https://github.com/${REPO}/issues/${ISSUE}" \
--rawfile reportMarkdown "data/${ISSUE}/report.md" \
'{
title: .title,
githubUrl: $githubUrl,
recommendation: .recommendation,
difficulty: .difficulty,
reasoning: .reasoning,
suggestedAction: .suggestedAction,
suggestedComment: (if .hasSuggestedComment then "Yes" else "No" end),
reportMarkdown: $reportMarkdown
}' "data/${ISSUE}/summary.json")
# POST to dashboard API
HTTP_STATUS=$(curl -L --post302 -s -o response.json -w "%{http_code}" \
-X POST "${DASHBOARD_URL}/api/report/${ISSUE}" \
-H "Content-Type: application/json" \
-H "CF-Access-Client-Id: ${CF_ACCESS_CLIENT_ID}" \
-H "CF-Access-Client-Secret: ${CF_ACCESS_CLIENT_SECRET}" \
-d "$PAYLOAD")
echo "Dashboard API response: HTTP ${HTTP_STATUS}"
cat response.json
if [ "$HTTP_STATUS" != "200" ]; then
echo "::error::Failed to upload report (HTTP ${HTTP_STATUS})"
exit 1
fi
echo "Report uploaded for issue #${ISSUE}"
- name: Build triage comment
env:
ISSUE: ${{ steps.issue.outputs.number }}
run: |
{
echo "> [!NOTE]"
echo "> This is an automated, advisory triage report generated by workers-devprod. It is not an official maintainer response — a maintainer will follow up."
echo ""
echo "<details>"
echo "<summary>🤖 Automated triage report</summary>"
echo ""
cat "data/${ISSUE}/report.md"
echo ""
echo "</details>"
echo ""
echo "<details>"
echo "<summary>Structured summary (JSON)</summary>"
echo ""
echo '```json'
cat "data/${ISSUE}/summary.json"
echo ""
echo '```'
echo ""
echo "</details>"
} > "data/${ISSUE}/comment.md"
- name: Post triage comment
uses: marocchino/sticky-pull-request-comment@d4d6b0936434b21bc8345ad45a440c5f7d2c40ff # v3.0.3
with:
header: triage-report
number: ${{ steps.issue.outputs.number }}
path: data/${{ steps.issue.outputs.number }}/comment.md
GITHUB_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }}
- name: Apply suggested labels
env:
GH_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }}
ISSUE: ${{ steps.issue.outputs.number }}
REPO: ${{ github.repository }}
run: |
SUMMARY_PATH="data/${ISSUE}/summary.json"
if [ ! -f "$SUMMARY_PATH" ]; then
echo "No summary.json; skipping label application"
exit 0
fi
# suggestedLabels is a JSON array; emit one label per line.
jq -r '.suggestedLabels // [] | .[]' "$SUMMARY_PATH" > suggested-labels.txt
if [ ! -s suggested-labels.txt ]; then
echo "No suggested labels"
exit 0
fi
# Only apply labels that actually exist in the repo.
gh label list --repo "$REPO" --limit 500 --json name --jq '.[].name' > repo-labels.txt
ARGS=()
while IFS= read -r label; do
if [ -z "$label" ]; then
continue
fi
if grep -Fxq "$label" repo-labels.txt; then
ARGS+=(--add-label "$label")
else
echo "Skipping unknown label: ${label}"
fi
done < suggested-labels.txt
if [ ${#ARGS[@]} -eq 0 ]; then
echo "No valid labels to apply"
exit 0
fi
gh issue edit "$ISSUE" --repo "$REPO" "${ARGS[@]}"
@@ -0,0 +1,68 @@
name: Validate PR Description
on:
merge_group:
pull_request:
types:
[
opened,
synchronize,
reopened,
labeled,
unlabeled,
edited,
ready_for_review,
converted_to_draft,
]
permissions:
contents: read
jobs:
check:
# Don't check the Version Packages PR or merge queue jobs
if: github.event_name != 'merge_group' && github.head_ref != 'changeset-release/main'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-add-pr
cancel-in-progress: ${{ github.head_ref != 'changeset-release/main' }}
timeout-minutes: 30
name: Check
runs-on: ubuntu-slim
steps:
- name: Checkout Repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 1
persist-credentials: false
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: changes
with:
filters: |
everything_but_markdown:
- '!**/*.md'
- name: Install Dependencies
if: steps.changes.outputs.everything_but_markdown == 'true'
uses: ./.github/actions/install-dependencies
with:
turbo-api: ${{ secrets.TURBO_API }}
turbo-team: ${{ secrets.TURBO_TEAM }}
turbo-token: ${{ secrets.TURBO_TOKEN }}
turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
- name: List changed files
if: steps.changes.outputs.everything_but_markdown == 'true'
id: files
uses: Ana06/get-changed-files@25f79e676e7ea1868813e21465014798211fad8c # v2.3.0
with:
format: "json"
- run: node -r esbuild-register tools/deployments/validate-pr-description.ts
if: steps.changes.outputs.everything_but_markdown == 'true'
env:
TITLE: ${{ github.event.pull_request.title }}
BODY: ${{ github.event.pull_request.body }}
LABELS: ${{ toJson(github.event.pull_request.labels.*.name) }}
FILES: ${{ steps.files.outputs.all }}
DRAFT: ${{ github.event.pull_request.draft }}
@@ -0,0 +1,100 @@
name: Vite plugin playgrounds
on:
merge_group:
pull_request:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.number || github.sha }}
cancel-in-progress: ${{ github.head_ref != 'changeset-release/main' }}
env:
# We run `playwright install` manually instead
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
jobs:
test:
timeout-minutes: 30
name: ${{ format('Vite Plugin Playground ({0}, {1})', matrix.os, matrix.vite) }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
vite: ["vite-8"]
include:
- os: ubuntu-latest
vite: "vite-6"
- os: ubuntu-latest
vite: "vite-7"
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Filter changed paths
uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: changes
with:
filters: |
everything_but_markdown:
- '!**/*.md'
- name: Install Dependencies
if: steps.changes.outputs.everything_but_markdown == 'true'
uses: ./.github/actions/install-dependencies
with:
turbo-api: ${{ secrets.TURBO_API }}
turbo-team: ${{ secrets.TURBO_TEAM }}
turbo-token: ${{ secrets.TURBO_TOKEN }}
turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
- name: Install playwright
if: steps.changes.outputs.everything_but_markdown == 'true'
run: |
pnpm --filter @vite-plugin-cloudflare/playground playwright:install
- name: Build plugin with initial Vite version
if: steps.changes.outputs.everything_but_markdown == 'true'
run: |
pnpm dotenv -- pnpm turbo build --filter @cloudflare/vite-plugin
- name: Downgrade to Vite 6
if: steps.changes.outputs.everything_but_markdown == 'true' && matrix.vite == 'vite-6'
run: |
pnpm update -r --no-save vite@^6.1.0
- name: Downgrade to Vite 7
if: steps.changes.outputs.everything_but_markdown == 'true' && matrix.vite == 'vite-7'
run: |
pnpm update -r --no-save vite@^7.0.0
- name: Run dev playground tests
if: steps.changes.outputs.everything_but_markdown == 'true'
# We use `--only` to prevent TurboRepo from rebuilding dependencies
# This ensures the Vite plugin is not rebuilt using a different Vite version
run: |
pnpm dotenv -- pnpm turbo test:ci:serve --filter @vite-plugin-cloudflare/playground --only
env:
VITE_VERSION: ${{ matrix.vite }}
NODE_OPTIONS: "--max_old_space_size=8192"
WRANGLER_LOG_PATH: ${{ runner.temp }}/wrangler-debug-logs/
TEST_REPORT_PATH: ${{ runner.temp }}/test-report/index.html
NODE_DEBUG: "@cloudflare:vite-plugin"
CI_OS: ${{ matrix.os }}
- name: Run build/preview playground tests
if: steps.changes.outputs.everything_but_markdown == 'true'
# We use `--only` to prevent TurboRepo from rebuilding dependencies
# This ensures the Vite plugin is not rebuilt using a different Vite version
run: |
pnpm dotenv -- pnpm turbo test:ci:build --filter @vite-plugin-cloudflare/playground --only
env:
VITE_VERSION: ${{ matrix.vite }}
NODE_OPTIONS: "--max_old_space_size=8192"
WRANGLER_LOG_PATH: ${{ runner.temp }}/wrangler-debug-logs/
TEST_REPORT_PATH: ${{ runner.temp }}/test-report/index.html
NODE_DEBUG: "@cloudflare:vite-plugin"
CI_OS: ${{ matrix.os }}
- name: Upload turbo logs
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: turbo-runs-${{ matrix.os }}-${{ matrix.vite }}
path: .turbo/runs
@@ -0,0 +1,60 @@
name: Deploy (to testing) and Test Playground Preview Worker
# This workflow is designed to deploy a "testing" version of the Playground Preview Worker
# and test it via integration tests.
# Triggers:
# - push to `main`, on Cloudflare, where there are changes to the files in this worker's package
# - update to a PR, on Cloudflare, labelled as `playground-worker`
#
# Actions:
# - deploy to testing environment
# - run the end-to-end tests against testing deployment
on:
push:
branches:
- main
paths:
- "packages/playground-preview-worker/**"
pull_request:
types: [synchronize, opened, reopened, labeled, unlabeled]
permissions:
contents: read
jobs:
e2e-test:
if: github.repository_owner == 'cloudflare' && (github.event_name != 'pull_request' || contains(github.event.*.labels.*.name, 'feature:playground-worker'))
name: "Deploy Playground Preview Worker (testing)"
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 1
persist-credentials: false
- name: Install Dependencies
uses: ./.github/actions/install-dependencies
- name: Build tools and libraries
run: pnpm run build
env:
NODE_ENV: "production"
CI_OS: ${{ runner.os }}
- name: Build & deploy Worker
run: pnpm run deploy:testing
env:
NODE_ENV: "production"
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
working-directory: packages/playground-preview-worker
- name: Run tests
run: pnpm run test:e2e
env:
TMP_CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
TMP_CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
NODE_OPTIONS: "--max_old_space_size=8192"
working-directory: packages/playground-preview-worker
@@ -0,0 +1,34 @@
name: Deploy Workers Shared Production
on:
workflow_dispatch:
permissions:
contents: read
jobs:
deploy-workers:
name: Deploy Workers Shared Production
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 1
persist-credentials: false
- name: Install Dependencies
uses: ./.github/actions/install-dependencies
- name: Deploy
run: pnpm run deploy
working-directory: packages/workers-shared
env:
WORKERS_DEPLOY_AND_CONFIG_CLOUDFLARE_API_TOKEN: ${{ secrets.WORKERS_DEPLOY_AND_CONFIG_CLOUDFLARE_API_TOKEN }}
WORKERS_SHARED_SENTRY_ACCESS_ID: ${{ secrets.WORKERS_SHARED_SENTRY_ACCESS_ID }}
WORKERS_SHARED_SENTRY_ACCESS_SECRET: ${{ secrets.WORKERS_SHARED_SENTRY_ACCESS_SECRET }}
WORKERS_SHARED_SENTRY_AUTH_TOKEN: ${{ secrets.WORKERS_SHARED_SENTRY_AUTH_TOKEN }}
- name: Publish deployment summary
run: echo -e "# Deployment summary\n\n| Service | Version | Release link |\n| --- | --- | --- |\n| Asset Worker | \`aw-$(node -r esbuild-register ./scripts/get-version-tag.ts)\` | https://cflare.co/release-asset-worker |\n| Router Worker | \`rw-$(node -r esbuild-register ./scripts/get-version-tag.ts)\` | https://cflare.co/release-router-worker |\n" >> $GITHUB_STEP_SUMMARY
working-directory: packages/workers-shared
@@ -0,0 +1,31 @@
name: Deploy Workers Shared Staging
on:
push:
branches:
- main
paths:
- "packages/workers-shared/**"
permissions:
contents: read
jobs:
deploy-workers:
name: "Deploy Workers Shared Staging"
runs-on: ubuntu-latest
steps:
- name: "Checkout repo"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 1
persist-credentials: false
- name: Install Dependencies
uses: ./.github/actions/install-dependencies
- name: Deploy
run: pnpm deploy:staging
working-directory: packages/workers-shared
env:
WORKERS_DEPLOY_AND_CONFIG_CLOUDFLARE_API_TOKEN: ${{ secrets.WORKERS_DEPLOY_AND_CONFIG_CLOUDFLARE_API_TOKEN }}