Skip to main content

make-cloudflare

Cloudflare Pages direct upload for mkrun, in Python: publish a built directory with no Node on the machine and no wrangler in the pipeline.

# Makefile.py in a consuming repo
# /// script
# requires-python = ">=3.11"
# dependencies = ["mkrun>=0.4", "make-cloudflare>=0.1"]
# ///
from make_cloudflare import cloudflare  # importing is what registers the group
$ mk cloudflare.deploy site/dist --project mkrun     # production (branch main)
$ mk cloudflare.deploy site/dist --branch try        # a preview, on its own URL
$ mk cloudflare.deploy dist --functions functions    # with Pages Functions
$ mk cloudflare.projects                             # what the account has
$ mk cloudflare.deployments --limit 5                # one project's recent ones
$ mk cloudflare.prune --keep 10                      # delete all but the newest ten
$ mk -n cloudflare.deploy site/dist                  # the plan; nothing is sent

Credentials are the two variables wrangler already reads, so a repository that deploys from CI needs no new secret: CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN (a token with Cloudflare Pages: Edit). The token's name ends in TOKEN, so mkrun treats it as a credential — it is withheld from the environment until a task asks for it, redacted in everything printed, and readable from the encrypted store. The project can come from --project or from CLOUDFLARE_PAGES_PROJECT in the repo's env layer.

Every deploy prunes the project to its newest 10 deployments (--keep N to change it, --keep 0 to keep them all; pages.deploy(..., keep=) from Python). Pages never deletes one by itself, and each stays reachable on its own <hash>.<project>.pages.dev URL forever. The live production deployment is never deleted, however old; a pruned preview that was still its branch's alias takes that branch URL with it.

Pages Functions go up with the deployment. --functions functions (or pages.deploy(..., functions=)) names the tree. It sits beside the built directory, as wrangler expects, and /api/... answers from the same project and origin. Bundling it is a build step, not an upload, so that one step is wrangler pages functions build --outfile (from PATH, or through npx). It writes exactly the _worker.bundle the deployment takes, plus functions-filepath-routing-config.json and a generated _routes.json; the site's own _routes.json wins. Everything else is still the four calls below. Bindings (D1, KV) and secrets are settings on the Pages project, never part of the upload.

Why this exists

wrangler pages deploy is four HTTPS calls and a hash. Reaching them through npx costs a Node toolchain in repositories that otherwise have none — a Rust one, a frontage one — and a node_modules in the deploy image of every one of them.

POST /accounts/<acct>/pages/projects/<p>/upload-token   -> a short-lived JWT
POST /pages/assets/check-missing                        -> what is not stored yet
POST /pages/assets/upload                               -> the files, batched
POST /pages/assets/upsert-hashes
POST /accounts/<acct>/pages/projects/<p>/deployments    -> the manifest

Three things make a naive port wrong, and all three fail silently — every call returns 200 and the site serves the old bytes, or none:

  • The hash is BLAKE3 of a strange input: the base64 text of the contents with the extension (no dot) appended, first 32 hex characters. Not of the bytes, and not SHA-256. tests/test_pages.py locks it with a golden value.
  • _headers, _redirects and _routes.json are not assets. They are fields on the deployment. Walk them in as ordinary files and the deploy reports success while the site's CSP stops applying.
  • check-missing, upload and upsert-hashes take the JWT, not the account token, and carry no /accounts/<id> prefix — the JWT already names the account.

What it does not do

Advanced-mode _worker.js. A _worker.js (or a functions/) inside the built directory is refused by name: it would otherwise be served as a static file. Pages Functions go beside it, through --functions.

Steps 2 and 3 are undocumented. They exist because wrangler uses them, and Cloudflare can change them without a changelog; the documented deployments endpoint alone cannot upload a file. That is the real cost of dropping wrangler, and it is the reason this is a small module with a golden test rather than a wrapper nobody reads.

Nothing here is specific to any owner or account. The group name is cloudflare, and it merges with a repo's own cloudflare.* tasks — groups are namespaces — so only a same-named task collides; this package claims deploy, projects, deployments and prune, nothing else. blake3 is the one dependency, and it lives here rather than in the runner so mkrun itself stays dependency-free.

Metadata

Release files for make-cloudflare 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for make-cloudflare 0.3.0
File Size Uploaded
make_cloudflare-0.3.0.tar.gz 16.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for make-cloudflare 0.3.0
File Interpreter ABI Platform
make_cloudflare-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 29.7 kB

Release files / make_cloudflare-0.3.0.tar.gz

Download URL make_cloudflare-0.3.0.tar.gz
Size 16.5 kB
Tags Source
SHA-256 checksum
How to use checksums
aca61b7004c7b0ddd097dbb7f8b7ea78551d7efafa3e6156427511792e8b2258
BLAKE2b-256 checksum
How to use checksums
211fb0c0775c864953a8fe07fbb65146bbe0351fc5d4daf275bfc76b815f6a44
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / make_cloudflare-0.3.0-py3-none-any.whl

Download URL make_cloudflare-0.3.0-py3-none-any.whl
Size 13.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ebdb7acc2c6e005eddf1144d41e544af020f3a999ce8aa76a631737a8789ee01
BLAKE2b-256 checksum
How to use checksums
6f06625d6f8d32b05b77127dffa5562c6ad92b7d80460a2d053ca60123f65f68
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

0.4.0

2 release files

This release

0.3.0 This release

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page