Skip to main content

Edits made to core files

The core of a site is a copy of a published build. cms9:self-update and the nightly cms9:auto-update replace those files with the files of the next build, so every edit made inside app4/app/, app4/themes/administrator/, app4/themes/default/ or app4/resources/ is temporary by definition.

Until build 527 that happened without a word: the updater verified the downloaded files against the new manifest and never asked whether the files on disk still equalled the build they came from. A patch applied on a production site disappeared at the next update, and the only sign was the old behaviour coming back.

Now the update asks first. The yardstick is the manifest of the installed build — one sha256 per file, exactly what the build server published — cached in writable/update/core_baseline.json:

  • files that differ, files added under the core roots and files deleted locally are found before anything on disk is touched;
  • an apply that would overwrite an edit stops with local_changes and lists the files;
  • cms9:auto-update skips only the core on such a site (modules keep updating) and raises a banner in the admin panel;
  • --overwrite-local replaces the edits on purpose and keeps a copy of every file it replaced.

The same idea as local changes in a module, applied one level up.

See what this site changed: cms9:core-drift​

php spark cms9:core-drift # human-readable report
php spark cms9:core-drift --json # machine-readable
php spark cms9:core-drift --fetch # take the yardstick from the channel now

The command only reads, and without --fetch it makes no network call at all. Run it per site — over an ssh loop or a deploy tool — before cutting a release; nothing here aggregates several sites, because nothing here talks to them.

codemeaning
0every core file equals the build that shipped it
1core files were edited on this site (listed by name)
2no baseline for the installed build — no assessment is made
7usage error

Exit code 2 is an honest "unknown", not a failure: a site that has not updated since this feature landed has no cached manifest yet. It gets one automatically at the next core update, or immediately with --fetch — the channel serves the manifest of any published build without a licence token.

What is never counted as an edit​

A clean install must report zero, otherwise the whole gate becomes noise that operators learn to pass with --overwrite-local. Excluded by design:

  • writable/ (caches, logs, sessions, uploads), .env, the public docroot, the composer tree at the install root and every tests/ directory;
  • editor and backup leftovers: *.bak*, *.orig, *.rej, *~, *.swp;
  • .baseline.json of core-shipped modules — the core update rewrites those itself on every site;
  • modules the core package never shipped (store and site modules such as Pixozip or CloudStorage);
  • themes of the site: only administrator and default travel in the core package, every other theme is the site's own;
  • a file that already equals the incoming build (the site applied the same fix by hand), and a file this particular update does not touch at all: both are reported by cms9:core-drift but neither blocks the update.

A file deleted locally is listed too, and never blocks: the update simply puts it back.

What the update does​

php spark cms9:self-update # dry run: says it up front
php spark cms9:self-update --apply # refuses, lists the files
php spark cms9:self-update --apply --overwrite-local # replaces them on purpose

The check sits before the backup step, so a refusal changes nothing on disk. With --overwrite-local the edited files are copied to writable/core_local_changes/<release>-<timestamp>/ first, keeping the site's version of every file the update overwrote, and the path is printed in the log.

An unattended run (cms9:auto-update) never overwrites. It writes the skip and the file names into writable/logs/auto-update.log, records the held build and shows it in the admin panel — in the chrome banner and on System update. The banner closes with «×» for the build it names and comes back for the next one, so a site that keeps its edits is reminded once per build, not once per page.

Carrying the edits somewhere they survive​

The gate makes an edit visible, not permanent. Three supported places for the change itself:

1. The theme, when the change is presentation​

Markup, CSS and JS of the storefront belong to the theme. A theme other than default and administrator is not part of the core package and survives every update.

2. A module of your own, listening to events​

Business behaviour — an extra check at checkout, a field on an order, an integration — belongs in app/Modules/<YourModule>/ with event handlers instead of an edited core class. Modules are updated independently of the core.

3. A fix in the core itself, upstream​

If the change belongs in the core — a real bug, a missing hook — it should be made in the core repository and reach the site as a build. That is the only form of "core edit" that survives, and it benefits every other site as well.

Before a release

php spark cms9:core-drift --json on every site of a fleet answers "which installs would this build break" in one pass — while the build can still grow the hook that makes the edit unnecessary.