Skip to content

feat(targets): add cloudflare deploy target#843

Merged
BYK merged 1 commit into
masterfrom
feat/cloudflare-target
Jul 16, 2026
Merged

feat(targets): add cloudflare deploy target#843
BYK merged 1 commit into
masterfrom
feat/cloudflare-target

Conversation

@BYK

@BYK BYK commented Jul 15, 2026

Copy link
Copy Markdown
Member

Summary

Adds a new cloudflare release target that deploys a release artifact to Cloudflare via the wrangler CLI. Part of the getsentry/toolkit monorepo work (#842) — enables deploying the CLI docs website to Cloudflare on release.

Two config-selectable deploy modes:

  • deployType: pages (default) → wrangler pages deploy <dir> --project-name <name> --branch <productionBranch> .... Always targets production by passing the configured productionBranch (default main) as --branch (the Cloudflare environment selector, not a git branch), plus --commit-hash/--commit-message/--commit-dirty provenance so wrangler doesn't infer git state from the temp dir.
  • deployType: workerwrangler deploy using a wrangler.toml in the artifact (run with cwd = deploy dir).

Configuration

Option Description
deployType pages (default) or worker
projectName Pages project name (required for pages)
productionBranch Pages production branch, passed as --branch. Default main
wranglerCliPath wrangler binary path (default wrangler, or WRANGLER_BIN)
workingDir Subdir within the artifact to deploy from

Secrets CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID are passed via env, never argv.

Safety

  • Dry-run: the deploy is a remote, irreversible operation, so it is explicitly skipped in dry-run — including worktree mode, where spawnProcess would otherwise run for real. (Local extraction still happens; only the remote deploy is skipped.)
  • Secret injection: config values that would expand to ${VAR} via spawnProcess's env-substitution are rejected, so a config string can't be interpolated into a secret.

Docker

wrangler@4.111.0 is installed globally in the runtime stage (x64-only image; workerd prebuilt binary is fine) with a wrangler --version smoke check, matching how other target toolchains are shipped.

Other changes

  • Refactored ghPages' single-top-level-directory flatten logic into a shared extractZipArchiveWithFlattening util reused by both targets (behavior unchanged).
  • New docs page targets/cloudflare.md + overview table row.

Testing

  • pnpm test — full suite green (17 new cloudflare tests, incl. dry-run guard, secret-in-env, both deploy types, config validation).
  • pnpm lint / tsc --noEmit clean.
  • wrangler pages deploy arg parsing (incl. --commit-dirty false) verified empirically against wrangler@4.111.0.

Acceptance

Move the CLI docs website to Cloudflare and confirm a real craft publish deploy works end-to-end before the toolkit merge.

🤖 Generated with opencode

Add a `cloudflare` release target that deploys a release artifact to
Cloudflare via the `wrangler` CLI. Supports two config-selectable modes:

- `deployType: pages` (default): `wrangler pages deploy` to a Pages
  project. Always deploys to production by passing the configured
  `productionBranch` (default `main`) as `--branch`, plus commit
  provenance flags so wrangler does not infer git state from the
  extracted-artifact temp dir.
- `deployType: worker`: `wrangler deploy` using a `wrangler.toml` in the
  artifact.

Details:
- Secrets CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID are passed via env,
  never argv. Config values that would expand to `${VAR}` are rejected so
  they can't be interpolated into secrets.
- The deploy is a remote, irreversible operation, so it is explicitly
  skipped in dry-run mode (including worktree mode).
- wrangler resolved via WRANGLER_BIN override; presence checked in the
  constructor. Bundled in the Docker image (pinned wrangler@4.111.0).
- Refactor ghPages' single-top-level-dir flatten logic into a shared
  `extractZipArchiveWithFlattening` util reused by both targets.
@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-07-16 12:15 UTC

@BYK
BYK merged commit 0f8720e into master Jul 16, 2026
24 checks passed
@BYK
BYK deleted the feat/cloudflare-target branch July 16, 2026 12:15
|--------|-------------|
| `deployType` | `pages` (default) or `worker`. |
| `projectName` | Cloudflare Pages project name. **Required** when `deployType` is `pages`. |
| `productionBranch` | The Pages project's production branch name. Passed to `wrangler pages deploy --branch` so a release always targets the **production** environment. Default: `main`. This is the Cloudflare environment selector, not your git release branch. |

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is not a git branch, then why is thwe default main instead of 'production'?

| `projectName` | Cloudflare Pages project name. **Required** when `deployType` is `pages`. |
| `productionBranch` | The Pages project's production branch name. Passed to `wrangler pages deploy --branch` so a release always targets the **production** environment. Default: `main`. This is the Cloudflare environment selector, not your git release branch. |
| `wranglerCliPath` | Path to the `wrangler` binary. Default: `wrangler` (or the `WRANGLER_BIN` env var). |
| `workingDir` | Subdirectory within the extracted artifact to deploy from. For `worker` deploys this is where the `wrangler.toml` lives. |

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have auto discovery for this? If yes, we should document, if not we should.

| Name | Description |
|------|-------------|
| `CLOUDFLARE_API_TOKEN` | Cloudflare API token with permission to deploy Pages/Workers. |
| `CLOUDFLARE_ACCOUNT_ID` | Cloudflare account ID. |

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Account id doesn't seem like a secret and maybe we can infer it from a config file or somkething?

```yaml
targets:
- name: cloudflare
deployType: pages

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also infer this automatically?

BYK added a commit that referenced this pull request Jul 21, 2026
…ranch (#846)

## Summary

Follow-up to #843 addressing the review comments left on the cloudflare
docs. Three behavior changes + docs/tests.

### 1. Default `deployType: worker` (was `pages`)
Cloudflare is steering new projects to Workers (with static assets) and
positioning Pages as legacy, so `worker` is the forward-looking default.
Pages remains fully supported via `deployType: pages`.

### 2. `CLOUDFLARE_ACCOUNT_ID` is now optional (not a secret)
The account ID is an **identifier, not a credential**. Only
`CLOUDFLARE_API_TOKEN` is a required secret now. The account ID is read
from the environment and forwarded to `wrangler` **only when set**;
otherwise `wrangler` auto-discovers it for single-account tokens
(confirmed against wrangler v4 behavior).

### 3. `productionBranch` auto-inferred for Pages
`wrangler pages deploy --branch <X>` deploys to production **only** when
`<X>` exactly matches the project's server-side production branch — any
other value silently produces a *preview* deploy. Previously we
defaulted to `main`, which is a guess. Now:

1. If `productionBranch` is configured → used verbatim.
2. Else, if the account ID is known → read the project's
`production_branch` from `GET /accounts/{id}/pages/projects/{name}` (the
same call wrangler makes internally, so **no extra token scope** —
Pages:Edit covers it).
3. Else → omit `--branch` (a bare deploy from the non-git temp dir
defaults to production).

A **404** (wrong project/account) fails loudly rather than masking a
misconfig; transient/network errors fall back to omitting `--branch`.

## Safety

- **Dry-run makes zero network calls**: the `isDryRun()` guard runs
*before* both the inference `fetch()` and `spawnProcess`.
- **No token leakage**: token is env-only (never argv), and error
messages don't carry it.
- **Defense-in-depth**: API-sourced branch values are also checked
against the `${VAR}` env-expansion guard before reaching argv.
- No new dependency — raw global `fetch` (Node 24.18.0 baseline).

## Testing

- `pnpm test` — full suite green (1052 passed). 27 cloudflare tests
covering: worker default, account-id optional/forwarded-when-set, branch
inference (correct URL + `Bearer` auth header), config-branch-skips-API,
404 hard-fail, API-failure/no-account-id fallbacks, suspicious-branch
guard, worker-never-calls-API, and dry-run-makes-no-fetch.
- `tsc` / `pnpm lint` clean. Docs build clean.

## Review

Adversarial subagent review: dry-run zero-network-call invariant
verified by code walk; no token leak; no CRITICAL/MAJOR issues. Findings
MINOR-1 (guard the API-sourced branch) and MINOR-3 (hard-fail on 404)
were addressed in this PR.

Addresses review comments on #843 (productionBranch default,
account-id-not-a-secret, auto-discovery/inference).

🤖 Generated with [opencode](https://opencode.ai)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant