Getting Started / Building From Source
Upgrading Drupal Core
Changing the core version means replacing the Drupal tree, then rebuilding every artifact made from
it. A plain DRUPAL_VERSION=<version> bun run build:local re-runs the tree step, which refuses to
replace a tree at another version without --force. The steps after tree are keyed on their own
outputs, so they keep what is on disk unless --force or --only says otherwise.
The Sequence
DRUPAL_VERSION must be set on both commands, or on neither. A forced build runs fetch:drupal
again, and that script reads the version from its own environment.
DRUPAL_VERSION=11.4.5 bun scripts/fetch-drupal-tree.ts --force
DRUPAL_VERSION=11.4.5 bun run build:local -- --force \
--skip=interpreter,frame,siblings,driver,prefillFour of the five skipped steps do not depend on the core version: two are the interpreter, one is
the sibling checkouts and one is the driver pack. The fifth, prefill, does; drop it from that list
on a real upgrade, since it re-renders the prefilled pages and a core patch can change them.
Wall clock on one development machine, six core-version swaps (median in the first row):
| measure | n | wall clock |
|---|---|---|
| a core-version swap, end to end | 6 | 23-27 s, median 25 s |
| of which: tarball fetch, extract, contrib install | 6 | 8-11 s |
| of which: reinstall, rebake, repack, recopy | 6 | 15-16 s |
a from-scratch build, same steps plus driver |
1 | 23 s |
A swap replaces about 167 MB of tree, reinstalls the build site, rebakes 23 Twig templates, repacks 11,675 files and recopies 4,028 static assets.
Verified by artifact. A rebuilt assets/core/misc/ajax.js hashed identically to the 11.4.5
reference tree and differently from 11.4.4. assets/drupal/core.bin.gz carried const VERSION = '11.4.5' and no other version string.
What A Patch Release Moves
scripts/tree-diff.mjs hashes a tree and plans the objects a rollout has to move. Example, 11.4.4
to 11.4.5:
| set | count |
|---|---|
| pack objects to upload | 209 |
| pack objects to delete | 0 |
| fraction of the 11,024-file pack set | 1.90% |
| static assets that also change | 2 |
The 209 are 93 .info.yml version stamps, 86 files under vendor/, 28 PHP classes under core/
and two core/*.json. Zero *.routing.yml, zero .install, zero config/schema/*.yml, zero
config/{install,optional}/*.yml, zero *.post_update.php and zero *.services.yml, so this
release is the code-only case, and no route, schema or config default moved.
tree-diff.mjs cannot see the static half. Its SHIPPED regex matches the extensions the PHP
packs carry, which leaves out css and js, because assets:static serves those. Diffed
separately, 11 browser-fetchable files changed, of which two match pack-static.ts's own SERVED
set and therefore ship: core/misc/ajax.js and core/misc/details.js. Read objectsToUpload as the
pack half of a rollout, with the static half counted separately.
The Two Rows Nothing Regenerates
sql reads assets/drupal/site.sqlite verbatim. Two of its prefilled rows embed the core version,
both at expire = -1:
| bin | cid | bytes |
|---|---|---|
cache_discovery |
library_info:olivero |
89,184 |
cache_data |
fonts:olivero:... |
630 |
The version in them is the ?v= cache-buster Drupal appends to every asset URL. Across repeated
core swaps, site.sqlite came out byte-identical to the committed one. A core upgrade does not
change these rows, so after a patch that changes core/misc/ajax.js, the site serves new bytes at
/core/misc/ajax.js while the query string still carries the old version.
The correction is a cache invalidation: delete both rows and Drupal rebuilds them, at the cost of one
discovery pass on the first render. scripts/bake-collectors.php produced them, and it is not
wired into build:local. It reads <root>/sites/default/files/.sqlite, which the site step does
not create (that step writes sites/build/files/build-site.sqlite), and it writes into the build
database, from which the rows were copied by hand into site.sqlite.
bun run release:payload builds the payload the fast route downloads and bun run release:check
prices the canonical config against the 64 MiB ceiling.