Skip to content

Liber Tertius · Chapter 3 · Keeping a project current

Move a project to a new train

A new train moves core, every module and every theme at once. Most of the work is reading the plan before anything changes.

≈15 min 6 steps A clean working tree A running stack
Source

Before you start

Commit what you are working on. The project repository tracks grim.json, grim.lock and whatever you added to the allowlist in .gitignore. Core and the modules are rebuilt from the lock, so the trace an update leaves in git is the diff of grim.lock. Mixed with your own changes, that diff says nothing.

Bring the stack up. After a train change grim runs composer install and the migrations in the running app container. With the stack down it skips both and leaves them to you.

$ git status --short
$ grim up

The ritual, in order

Six steps. Only the fourth changes the project.

  1. Update grim itself

    The infrastructure templates of step five come from the grim on this machine, and so does the logic that decides what an update removes. Take the newest release first with grim self-update.

    $ grim self-update
  2. Read the plan

    Nothing is written. The dry run of grim update checks that every package can be installed, resolves the train, and prints what would move and what would be removed.

    $ grim update --dry-run
    
    Train 2.4 → 2.5 available (core 2.5.0)
    
      core                 2.4.2 → 2.5.0  (2.5.*)
      cart                 2.4.0 → 2.5.0  (~2.0)
      theme/admin:float    2.4.1 → 2.5.0  (~2.0)
    
        Would remove tests/Feature/LegacyHealthTest.php

    When it prints Everything is up to date on train 2.4. instead, the project is already on the newest train its core constraint allows. A constraint such as 2.4.* pins one train on purpose. Change it in grim.json to move on.

  3. Decide what the plan turned up

    Four kinds of line around the table need a person. Everything else is automatic.

    Line Decide
    Would remove … A file core dropped, unchanged in your project. It goes. If your code still uses it, copy what you need somewhere of your own now.
    Kept … modified locally Core dropped it, you had edited it, so it stays. Keep it as yours, or delete it by hand.
    … left core in train … A whole module is no longer part of core. Nothing is deleted. Add it to grim.json with grim require, or take it out with grim remove.
    ⚠ module … has no release on train … Everything else moves and that module stays where it is. Wait for its release, or go on without it and update it later.
  4. Board the train

    The same plan, applied. Every removed file is named as it goes, the modules and themes are checked out at their new tags, the lock is written, and the app container runs composer install and the migrations.

    $ grim update

    If a checkout would overwrite untracked files inside a module, the update stops, lists them and asks. Read the list before you answer.

  5. Renew the infrastructure files

    grim update never touches the Docker files. They come from the templates of the grim you updated in step one, and grim upgrade writes them.

    $ grim upgrade
    $ grim down
    $ grim up

    The managed files are overwritten without a question, without a backup and without a preview. docker-compose.yml and the other files you own are left alone. The restart makes the containers read the new files. A managed file you once edited by hand is back to the template now, unless you had put it on the .gitignore allowlist and can restore it from git.

  6. Test, then commit the lock

    The lock is the record of what this project runs. Commit it once grim test agrees.

    $ grim test
    $ git diff grim.lock
    $ git add grim.json grim.lock
    $ git commit -m "chore: move to train 2.5"

What you have now

A lock that names the new train, and a project whose files match it, minus what core no longer ships.

{
    "train": "2.5",
    "core_version": "2.5.0",
    "modules": {
        "cart": { "version": "2.5.0" }
    }
}

Your colleagues get the same versions from git pull and grim install, which lands what the lock records. Nothing is deployed yet. The train reaches a server the way every change does, as a new image.

When it does not work

A package cannot be installed

The update stops before it touches anything and lists every package it cannot have. An archived module usually says what replaced it. Often that is core itself, and then the module comes out of grim.json. A private one means your GitHub account lacks access to it.

“Cannot read the grim-core … tree”

Cannot read the grim-core 2.4.2 tree (…) — nothing removed. means grim could not look up the core release the lock names, typically with a lock written before trains existed. The train still landed. Nothing was deleted, so files core no longer ships stay in the project until you remove them by hand. The next update, from the lock this one wrote, removes normally.

“Environment is down”

Environment is down — run `grim up` to apply composer and migrations. means the stack was not running, so both were skipped. grim up alone does not catch up: on your machine it installs Composer packages only into an empty vendor/ and never migrates. Run the three commands.

$ grim up
$ grim composer install
$ grim artisan migrate

The suite fails on the new train

There is no downgrade command, and an older train gets no fixes. The way out is forward. Find the module the failure lives in with grim why, report it or fix it, and take the patch release when it lands, which is the recipe Take one module fix mid-train.

Do not edit versions in grim.lock by hand to hold one module back. grim ignores a locked version from another train, and a module from one train against the core of another is the breakage trains exist to prevent.