Skip to main content

Update your extension

Follow this page to merge a new Eddy release into your extension, with Claude Code or by hand. A release brings changes such as new CI jobs, tooling releases and fixes to the build and to the docs it generates. The merge keeps your own code and your own changes to the template. The last section lists what stays current between updates.

Coming from 4.x?

An extension created from a 4.x release needs a one-off upgrade to 5.x first. From 4.15.0, its AGENTS.md fetches the update skill from a URL that returns 404, and earlier releases have no update skill. The upgrade also replaces .devtools/ with the tooling package and CircleCI with GitHub Actions. For the steps, see Upgrade a 4.x project to 5.x.

Update with Claude Code​

If you use Claude Code, ask Claude to "update scaffold" in your project. Claude follows the instructions in your project's AGENTS.md: it fetches the update-consumer-eddy skill with these commands, then runs it:

mkdir -p .claude/skills/update-consumer-eddy
curl -fsSL -o .claude/skills/update-consumer-eddy/SKILL.md \
https://raw.githubusercontent.com/drevops/eddy/main/.eddy/skills/update-consumer-eddy/SKILL.md

The skill installs 2 releases in scratch directories: the release your project is on and the release it updates to. Both use the installer answers of your project. Then it merges the difference between them into your project:

Only a line that both you and the release changed needs a decision. On such a line, the skill keeps your change and applies the release's change next to it. Then it builds, lints and tests the result, and opens a pull request that lists the conflicts it resolved by judgment. In a project without a command wrapper, the pull request's CI runs the lint and the tests instead.

The merge skips your extension's own code, the paths that step 4 of Update by hand lists. The skill changes these files on top of the merge:

  • It takes scripts/eddy-tooling from the release, even when you changed it. The pull request lists your change as one that belongs in a patch for the package.
  • It removes a template file that the release removed, even when you changed it, and lists it in the pull request.
  • It keeps your package-lock.json, and regenerates it when the merged package.json changed.
  • It finds references to example files that your project doesn't have, in the docs and configurations. It points each one at your own file or removes it.
  • For a project from 4.x, it removes .devtools/, .circleci/ and the Cloudflare tunnel hooks.

The skill decides these from what it finds:

  • Which release. Without a tag, the skill reads the 10 most recently published releases on GitHub. It skips drafts and the releases marked as pre-releases, then updates to the highest version left. To update to a particular release, name its tag: /update-consumer-eddy <tag>. When your project is already on that release or a newer one, the skill stops, unless you add or drop a Drupal major.
  • Which release you're on. The skill reads it from extra.eddy.version in composer.dev.json, or from your last scaffold update commit. The installer records that value when you install from a GitHub archive, and every update records it again. An extension created from a branch download records the commit it came from, such as 5.0.0-3-g1a2b3c4. The skill then renders that commit in place of a release. Without a recorded release, or with one older than 4.14.0, you decide on every difference between your files and the new release.
  • Adding or dropping a Drupal major. Ask for it in the same request, such as /update-consumer-eddy add Drupal 12. The skill changes the major's CI jobs and badges. For a new major, it also makes the code changes the major needs, each in a commit of its own. It does this even when there's no newer release to update to.
  • Permissions. On its first run, the skill might add a few command permissions to .claude/settings.local.json and ask you to restart Claude Code. Run it again after the restart.

Update by hand​

To update without Claude Code:

  1. Read the notes of every release after the one your project is on, which extra.eddy.version in composer.dev.json names, on the releases page. Look for breaking changes.
  2. In a new, empty directory, run the installer with the answers your project uses. That directory isn't a clone of your repository, so pass your workflow branches and your GitHub repository, such as INSTALL_BRANCHES=1.x,2.x INSTALL_GITHUB_REPOSITORY=owner/myextension php install.php. The installer installs the release shown in this site's footer. To update to another release or to the current code, install from a download instead. Your project shows its answers in these places:
    • The name and the type in the name and type of its .info.yml, and the machine name in that file's name
    • The command wrappers in which of .ahoy.yml and Makefile it has
    • The Drupal majors in the drupal-version values of .github/workflows/test.yml
    • The tools in their configuration files, such as phpcs.xml for PHPCS and renovate.json for Renovate
  3. Create a new branch in your extension's repository.
  4. Copy the new files into your extension's directory, apart from your extension's own code: src, tests, config, css, js, composer.json, logo.png, the files named after your machine name and scripts/*-example.sh. The new files hold the example extension under your machine name, so copying those would overwrite your code.
  5. Remove the template files that the new release dropped:
    1. Install from a download of the release your project is on into another new directory, with the same answers.
    2. Find each file that this directory holds and the directory from step 2 lacks. Remove it from your project, apart from the paths that step 4 leaves out.
  6. Resolve the differences between the new files and yours:
    • Unless you passed them in step 2, keep your own branches in the workflow branch filters and your repository in the README badges.
    • Keep the extra.eddy.version of the new composer.dev.json, since the next update reads the release you're on from it.
    • Take scripts/eddy-tooling as the release ships it, and drop any change you made to it. To change what a tooling command does, patch the package instead.
  7. When your project has a package-lock.json, restore your own copy with git checkout HEAD -- package-lock.json.
  8. If package.json changed, run npm install --package-lock-only to bring the lock file in line with it.
  9. Build the site.
  10. Check coding standards.
  11. Run the tests.
  12. Commit and push to your GitHub repository. Confirm that every CI job passes.
  13. Merge the new branch into your main branch. Confirm that the deployment job works.

To add or drop a Drupal major by hand:

  1. Select the majors you want when you run the installer in step 2 of the update.
  2. To add a major, add it to the core_version_requirement of every *.info.yml your project has, because step 4 of the update leaves those files out. To drop a major from CI, leave core_version_requirement as it is, as the update skill does. Remove a major from it only when your extension stops supporting that major.
  3. For Drupal 12, also make the other code changes that Drupal 12 lists.

What else stays current​

Between scaffold updates, these parts get updates of their own:

  • The tooling package: CI installs its newest patch release within the ~ constraint in composer.dev.json on every run. A local checkout installs that release after reset. A scaffold update raises the constraint for a new minor version, or when the new scaffold needs a newer release. The next run of scripts/eddy-tooling then installs a release that the new constraint allows.
  • When you keep Renovate, it updates the GitHub Actions and Docker images, and the npm packages within their major versions (see Dependency updates).