Getting Started

Building From Source

Two commands turn a clean clone into a deployable tree, and they are not interchangeable.

route command needs takes
payload bun run hydrate network seconds
source bun run build:local network, PHP, composer, node 24+ minutes

bun run hydrate takes the payload route by default and falls back to the source route when no payload exists. --payload-only forbids the fallback; --from-source skips straight to it.

Pages

Why Two Routes

assets/ is generated packs and .interp/ holds the interpreter. Both are gitignored, so a clean checkout has neither and wrangler deploy has nothing to upload.

The payload route downloads them as one verified tarball. It is a plain HTTPS GET: no Docker, no gh auth, no PHP, no Cloudflare credential. That is what makes the Deploy to Cloudflare button work. Its build command is bun run hydrate (build.command in wrangler.jsonc), and Workers Builds has no Docker to build with.

The source route regenerates the same bytes locally. It covers what the payload cannot: a checkout of a commit no release was cut from, and a repository with no release yet. It produces every artifact the payload carries, assets/prefill.json included.

Where a Payload Comes From

hydrate tries four places and takes the first that answers, and every one of them is verified against the SHA256SUMS published beside it.

order source what it is
1 --from=<path> or dist/ a tarball on disk, which outranks the network
2 payloads/v<version>/ on R2 the release, mirrored to the CDN
3 the GitHub Release the same bytes, canonical
4 payloads/dev-<branch>/ on R2 that branch's tip, replaced on every push

The CDN copy is tried before the GitHub Release. A Workers Builds job once stalled for its whole timeout on a release asset download, while the same 13.4 MB restored from the CDN in seconds. The Release stays canonical and stays the fallback, so a CDN outage costs a retry rather than a deploy.

The dev line is tried last and never displaces a release. It is keyed by branch: the checkout's own branch first, then master. It lets a checkout on a commit no release was cut from download a tree in seconds instead of building one with a toolchain the deploy container does not have. bun run build:local is still there when you want the bytes regenerated rather than fetched.

--dev[=branch] takes the dev line directly. bun run hydrate -- --dev=master skips dist/ and every release and downloads the tip of that branch, and a bare --dev names the checkout's own branch. It is for a checkout whose package.json already carries the next version: without it, a release of the previous version answers first and lands older bytes under newer code. It refuses to combine with --from, --tag or --from-source, since each of those names a different payload. The bytes are a branch tip, so the next push replaces them and the run says so.

Both lines are published by CI from the tarball the release job already built, so nothing is rebuilt to publish it. The keys come from scripts/payload-cdn.ts, which the resolver and the workflows both read; tests/node/workflows.spec.ts fails if a workflow starts spelling a key of its own.

Quick Start

git clone https://github.com/drupflare/worker.git
cd worker
bun install     # postinstall restores the interpreter from the CDN, verified by sha256
bun run hydrate # payload if there is one, source build if there is not
bun run dev

To see what a build would do without doing it:

bun run build:plan

Prerequisites, Per Route

The payload route needs Bun and a network. Nothing else.

The source route needs each of these, and bun run build:plan names any that are missing before it spends a minute:

tool version used by note
bun latest everything
node 24+ core, sql, patch node:sqlite, which Bun does not ship
php 8.3+ site, twig with pdo_sqlite, sqlite3, mbstring, iconv
composer 2 tree the Drupal release tarball is core only
git any siblings
tar any tree

Two of these run under node rather than bun. pack-sql.ts needs node:sqlite. pack-drupal.ts writes core.bin.gz from gzipSync(blob, { level: 9 }), and the two runtimes do not emit the same deflate stream for the same input, so running it under Bun would move a shipped asset with nothing reporting it.