repomatic.cloudflare module

Reconcile a Cloudflare Pages project against the state its repository declares.

A Direct Upload project is not reproducible from anything committed: wrangler.toml only describes what a build would need, and these projects are never built by Cloudflare. Everything that actually shapes the live site (the compatibility date, Smart Placement, the build image, whether a git source got attached) lives server-side in the project’s deployment_configs and is invisible to anyone reading the repository. One project’s compatibility date sat three years behind the live value with nothing noticing. This module makes that state explicit, diffable and re-applicable, from the [tool.repomatic] site.* keys.

Backs the cloudflare-pages command, in four modes: --check diffs live against declared and exits non-zero on drift, --apply writes the declared values back, --create creates the Pages project when missing (reusing an existing one) then applies, and --dump prints the live state with secrets redacted.

Credentials resolve in this order, so the same command works in CI and on a laptop without a token ever landing on a command line:

  1. CLOUDFLARE_API_TOKEN from the environment (what CI uses).

  2. The OAuth token wrangler login stores locally.

The account is never declared; the credential settles it. GET /accounts answers what the token sees, and one scoped to nothing but Cloudflare Pages: Edit still enumerates the account it belongs to, so CI carries the token alone. A credential seeing several accounts resolves the ambiguity by asking which one owns the project being reconciled, and fails rather than guesses when that question has no single answer. No identifier is ever hardcoded either: repositories using this are public, and account IDs do not belong in them.

Caution

Never gate anything on GET /user/tokens/verify: that endpoint is user-scoped, so an account-owned token (the recommended kind, cfat_ prefix) answers 401 there while every project call succeeds. Proving the credential against the project it is meant to touch is the only verification that means anything, which is what every mode here does implicitly.

repomatic.cloudflare.API_ROOT = 'https://api.cloudflare.com/client/v4'

Cloudflare v4 API root every call below is relative to.

repomatic.cloudflare.API_TIMEOUT = 30

Socket timeout in seconds, wider than repomatic’s JSON default: a PATCH that stalls mid-write is worth waiting out rather than retrying blind.

repomatic.cloudflare.EXPIRY_WARNING_DAYS = 30

How close a token’s expiry gets before --check starts warning.

Cloudflare notifies about neither an approaching expiry nor a passed one, so the monthly Docs run carrying this check is the only calendar the token has. A month of warnings is enough to rotate without ever reaching the red run.

repomatic.cloudflare.SECRET_KEYS = frozenset({'api_token', 'oauth_token', 'refresh_token', 'secret'})

Response keys whose values must never be printed.

repomatic.cloudflare.WRANGLER_CONFIG_PATHS: Final = (PosixPath('/home/runner/Library/Preferences/.wrangler/config/default.toml'), PosixPath('/home/runner/.config/.wrangler/config/default.toml'))

Where wrangler login stores its OAuth token, macOS first then XDG.

exception repomatic.cloudflare.CloudflareError[source]

Bases: RuntimeError

Raised when the Cloudflare API refuses or a credential cannot be found.

exception repomatic.cloudflare.CloudflareHTTPError(message, status)[source]

Bases: CloudflareError

An HTTP-level refusal from the Cloudflare API, carrying its status.

Lets a caller act on which refusal arrived (a 404 meaning “create it” is a different instruction than a 403 meaning “stop”) without parsing the message string it was raised with.

class repomatic.cloudflare.Setting(path, desired, default, why, verified=False, managed=True)[source]

Bases: object

One server-side setting, with enough context to justify its value.

default is what a stock Cloudflare Pages project reports for this key. Where that value is quoted from Cloudflare’s documentation, verified is True. Where it is inferred from how the product behaves, it is False and the diff labels it as such: an unverified default is a reasonable guess, not a fact, and this module should not launder one into the other.

path: tuple[str, ...]
desired: Any
default: Any
why: str
verified: bool = False
managed: bool = True
repomatic.cloudflare.desired_settings(compatibility_date='', placement='')[source]

The settings to enforce, from the repository’s site.* declarations.

Only what the repository declares is managed, plus one documented floor: the build image major version, which Cloudflare auto-migrates old projects onto (v1 on 2026-09-15, v2 on 2027-02-23) and then freezes, so asserting 3 requests nothing from a current project and names the stragglers.

Pages supports exactly production and preview, so the two environments are enumerated rather than globbed, and every managed setting applies identically to both.

Parameters:
  • compatibility_date (str) – site.cloudflare-compatibility-date, empty to leave the live value unmanaged.

  • placement (str) – site.cloudflare-placement, empty to leave the live value unmanaged.

Return type:

tuple[Setting, ...]

repomatic.cloudflare.MISSING

Sentinel a _dig walk answers when the path names nothing.

A named alias of the class it always was, so the three-way tests below read as the absence checks they are rather than as exception handling.

repomatic.cloudflare.run_cloudflare_pages(project, *, check=False, apply=False, dump=False, create=False, attach_domain='', compatibility_date='', placement='')[source]

Drive one mode of the Pages reconciliation against project project.

Exactly one of check, apply, dump or create must be set; the CLI enforces that before calling.

Parameters:
  • project (str) – Cloudflare Pages project name to reconcile.

  • check (bool) – Diff live against declared, exit 1 on any drift.

  • apply (bool) – PATCH every managed drifted setting back to its declared value. Read-only drift is reported and keeps the exit code non-zero.

  • dump (bool) – Print the live project state as JSON, secrets redacted.

  • create (bool) – Create the Pages project (Direct Upload, main as its production branch) when it does not exist yet, then apply the declared settings to it. An existing project is reused, so a re-run converges on the declared state instead of failing on the API’s 409.

  • attach_domain (str) – Serve the project at this hostname, creating the DNS record it needs when the credential can. See _attach_domain().

  • compatibility_date (str) – Declared Workers runtime date, empty for unmanaged.

  • placement (str) – Declared Smart Placement mode, empty for unmanaged.

Return type:

int

Returns:

Exit code: 0 clean, 1 drift found (or left, for the read-only settings --apply cannot write).