Skip to content

Liber Tertius · Chapter 5 · Working on modules

Change a module and send it back

A module under src/ is a checkout of a released tag, and its repository is written by the release process alone. So a change does not go back the way it came. It travels to the monorepo as a pull request and returns to every project as a release.

≈20 min 6 steps A project that installs and runs The GitHub CLI logged in Write access to the monorepo
Source

Before you start

The stack is up and the suite passes, so a red test after your change is yours.

$ grim up
$ grim test

Sending the change needs the GitHub CLI gh, logged in, and write access to the monorepo the modules are released from. Reading it is enough for the dry run. A project that belongs to another organisation names its own monorepo in grim.json, and then that is where the pull request goes.

"upstream": { "repo": "acme/platform", "branch": "master" }

The ritual, in order

Six steps. The fifth is the only one that leaves your machine, and the sixth happens days later, when the release is out.

  1. See what you are about to change

    Two facts decide whether the change can be sent at all. The module has to be a git checkout of its own, and it has to sit exactly on a released tag with nothing committed on top.

    $ ls -d src/Cart/.git
    src/Cart/.git
    $ git -C src/Cart describe --tags --exact-match
    2.5.0
    $ git -C src/Cart status --short

    A tag and an empty status are what you want. When src/Cart/.git does not exist, the module is part of core, which reaches the project as one copied tree. It cannot be sent this way. See When it does not work.

  2. Make the change and test it

    Edit the files under src/Cart/ in place. The project runs the module from there, so the browser and grim test see the change at once.

    $ grim test --filter=CartTotalsTest
    $ grim test

    Do not commit inside src/Cart/, and do not make a branch there. grim recognises the release you started from by the checkout's HEAD, and one commit on top makes it unrecognisable. Your work stays in the working tree until it is sent. New files count as long as the module's own .gitignore does not exclude them.

  3. Check the manifest

    Optional. Do it when you touched module.json, for instance to require another module. grim manifest:validate applies the rules the monorepo enforces on every pull request.

    $ grim manifest:validate src/Cart

    Only ~2.5 passes as a constraint in requires. ^2.5 and 2.5.* are refused.

  4. Read what would be sent

    The dry run of grim push fetches the monorepo into .grim/upstream/, finds the commit your installed release was cut from, and lays your working copy over it. Nothing is sent.

    $ grim push Cart -m "fix(cart): correct totals" --dry-run
    Pushing module Cart (2.5.0) to acme/platform master
      Fetching the monorepo (blobless, sparse on src/Cart)... OK (first clone)
      Looking for the base commit... 4f2a91c chore(release): train 2.5
    
      fix(cart): correct totals
    
     src/Cart/Models/Cart.php | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    Dry run — nothing pushed.

    Read the file list. It has to be your change and nothing else, however far behind the newest train the project is. A debug file you forgot shows up here. Add --patch to read the full diff.

    The title is yours to write, and grim suggests none. It becomes both the commit message and the pull request title, so it follows the monorepo's rules: type(scope): subject, one line, at most 100 characters, a lowercase subject with no full stop. A title that breaks them is refused before anything is fetched.

  5. Open the pull request

    The same command without the flag commits your change on the branch grim-push/module/cart in the monorepo, pushes it and opens the pull request with gh.

    $ grim push Cart -m "fix(cart): correct totals"
    
      Pushing grim-push/module/cart... OK
      Opening the pull request... OK
    
    https://github.com/acme/platform/pull/128

    One module travels per push. The branch name is fixed per module and the push is forced, so pushing Cart again rewrites the same pull request with the current state of src/Cart/. That is how you answer a review. It also means a second, unrelated fix to the same module waits until the first is merged.

  6. Take the release when it arrives

    The pull request is reviewed and merged in the monorepo, and the module gets a new release on your train. None of that happens on your machine. Until then your edits stay in src/Cart/, and the project keeps running them. When the release is out, put your copy aside and take the released one with grim update.

    $ git -C src/Cart stash -u
    $ grim update cart
    $ git -C src/Cart stash drop
    $ grim test

    The stash comes first because the checkout of the new tag refuses to overwrite edited files. -u takes the files you added along. Commit the grim.lock that the update wrote. Take one module fix mid-train covers this step on its own, including what to do when a newer train is already out.

What you have now

After step five, a pull request in the monorepo with a three-line body: the module, the release it was installed at, and the base commit. In the project, .grim/upstream/ holds a partial clone of the monorepo that later pushes reuse. It is ignored by git and safe to delete.

After step six, src/Cart/ is a clean checkout of the new tag, grim.lock names that version, and every other project gets the same fix with its next update. Until the release, your colleagues on this project do not have your change. It lives in your working tree only.

When it does not work

“Users is a core module”

The message goes on to say that core reaches a project as one copied tree, so there is no checkout to find a base with. grim push cannot send it. Make the change in a clone of the monorepo itself, under src/Users/ there, and open the pull request from that clone with git and gh.

“No commit on acme/platform master carries this exact module Cart.”

The checkout's HEAD matches no release in the monorepo. Nearly always you committed inside src/Cart/. Undo the commit and keep the changes, then push again.

$ git -C src/Cart reset --soft HEAD~1

If it still fails, the installed tag is older than the search reaches. Stash the change, run grim update, apply the stash and push again.

“Commit message rejected (commitlint runs on the PR too):”

Each line under it names one rule the title broke, such as subject must not be sentence-case or upper-case — start it lowercase. Allowed types are build, chore, ci, docs, feat, fix, perf, refactor, revert, style and test.

“Your gh token is missing the scope(s) …”

That is not a permissions problem in the monorepo. The token gh holds lacks repo or read:packages. Run grim auth:login, which adds them, and push again.

The push succeeded and the pull request did not open

Opening the pull request... failed is followed by a compare URL. The branch is already upstream. Open the URL in a browser and create the pull request by hand.

“No changes in module Cart against its base — nothing to push.”

Your working copy equals the installed release. Either the edit went into another module, or the file you added is excluded by the module's .gitignore.

Questions

Can I send a theme the same way?

Yes. Name it with --theme instead of the module argument, as grim push --theme frontend/shop-front -m "…". Everything else in this recipe holds, with resources/themes/frontend/shop-front in place of src/Cart.

Can I send a module I made with grim make:module?

No. It has a repository of its own and no copy in the monorepo, so no base commit exists. Push it with git from src/<Name>/, as Create a module shows.