Skip to content

5.0.0-alpha1

Choose a tag to compare

@github-actions github-actions released this 12 Oct 07:31
· 36 commits to main since this release
7572b0c

What's new since 4.19.0

Highlights

5.0 is the first release under the Eddy name, and most of it is about where things live: the template has a new home, the build tooling became a Composer package, and GitHub Actions is the only CI left. Drupal 12 joins the CI matrix, and deployments no longer rewind branches. A project created from 4.x needs a one-off upgrade, which MIGRATION.md walks through.

The Drupal Extension Scaffold is now Eddy

The template moved from AlexSkrypnyk/drupal_extension_scaffold to drevops/eddy, next to Vortex, and the README now spells out how the 2 differ: Eddy maintains a contributed Drupal module or theme on GitHub, Vortex builds websites, and a repository uses one or the other.

4.x 5.x
Repository AlexSkrypnyk/drupal_extension_scaffold drevops/eddy
Default branch 1.x main
Template maintenance files .scaffold/ .eddy/
Update skill update-consumer-drupal-extension-scaffold update-consumer-eddy
Installer php init.php, answers from DEX_* php install.php, answers from INSTALL_*

The branch rename only applies to the Eddy repository itself. New projects still build and deploy from 1.x, and when the installer runs during an update, it keeps whatever branches the project's committed test.yml lists.

The build tooling is a Composer package

In 4.x, every project carried its own copy of the build, run and deploy scripts in .devtools/, so a fix reached a project only when its maintainer merged the next release by hand. In 5.x, those scripts ship as the drevops/eddy-tooling package, and every project installs the same one:

4.x: each project carries its own copy       5.x: each project installs the same package

ahoy assemble, make assemble or CI           ahoy assemble, make assemble or CI
               │                                            │
               ▼                                            ▼
.devtools/assemble                           scripts/eddy-tooling
  your copy, fixed only when you merge         installs drevops/eddy-tooling ~1.0.0
  the next release by hand                     into vendor/, and skips Composer
                                               while nothing has changed
                                                            │
                                                            ▼
                                             vendor/bin/eddy-assemble
                                               fixes arrive as patch releases,
                                               your changes as patches or hooks

Each .devtools/<script> is now vendor/bin/eddy-<script>, and .devtools/browser start and stop are eddy-browser-start and eddy-browser-stop, while the ahoy and make commands keep their names. CI picks up tooling fixes on its own, and a local checkout picks them up after ahoy reset or make reset, which now also remove vendor/. A project-specific step goes into a custom lifecycle script, and a change to what a command does becomes a patch for the package in composer.dev.json.

The Cloudflare quick tunnel moved into the package as well: eddy-start, eddy-provision and eddy-stop run it whenever CLOUDFLARE_TUNNEL=1 is set, so projects no longer carry the scripts/*-cloudflared.sh hooks. The default Drupal major for local builds lives in extra.eddy.drupal-version in composer.dev.json, now that there's no script copy to rewrite.

Drupal 12 in CI

Drupal 12 support is opt-in: select 12 when install.php asks for the Drupal versions, or ask Claude to include it when it runs the update skill, and an upgrade keeps the majors your CI tests today. Opting in adds a README badge and 5 CI jobs, all on PHP 8.5, which Drupal 12 requires:

Job DRUPAL_VERSION Builds
lint-d12, test-php-min-d12-stable, test-php-max-d12-stable 12 Newest stable 12.x, or the newest pre-release until 12.0.0 ships
test-php-min-d12-legacy 12.0.0 Newest 12.0.x, the floor a ^12 requirement promises
test-php-max-d12-canary 12@beta Newest beta, release candidate or stable, so the next minor shows up early

The jobs follow Drupal releases with no edits: until 12.0.0 ships they all build the newest Drupal 12 pre-release, and after that canary moves on to the 12.1 pre-releases while legacy stays on 12.0.x. Drupal 12 builds use Drush's 14.x development branch until Drush 14.0.0 is tagged, and they install drupal/core from source, since its packages leave out the tests directories your tests need.

Drupal 12 also needs 3 changes in your own code, which no template merge makes:

  • Add ^12 to core_version_requirement in your *.info.yml.

  • Declare string|false as the return type of getMinkDriverArgs() if your FunctionalJavascript tests extend the base class the template generated.

  • Keep both forms of test metadata: doc-comment annotations for PHPUnit 9.6 on Drupal 10, and PHP attributes for PHPUnit 12 on Drupal 12.

Builds pin Drupal core and apply your patches

Every build now pins Drupal core to 1 exact release and lets each other dependency take its most stable release that fits, so composer.dev.json and your composer.json only say which versions are acceptable:

DRUPAL_VERSION=11
        │
        ▼
drupal/core*          pinned to 1 exact release, the newest stable 11.x,
                      and printed in the build log
        │
        ▼
other dependencies    minimum-stability: dev, prefer-stable: true
                      each takes its most stable release that fits, and falls back
                      to a pre-release or development branch only when nothing
                      stable supports that core; the build log lists every branch
        │
        ▼
extra.patches         applied by cweagans/composer-patches 2.x
                      a patch that no longer applies fails the build

DRUPAL_VERSION takes values such as 11, 11.1.0, 11@beta or 12, but no longer @dev, since a development branch isn't a release.

The build also installs cweagans/composer-patches 2.x. From 4.14.0 to 4.19.0 it didn't, so unless your project required the plugin itself, Composer skipped every patch you declared without a word. Those patches apply now, and a patch that went stale while it was skipped fails the build until you fix or drop it.

Deployments that never rewind a branch

In 4.19.0, pushing a release tag force-pushed the tagged commit over a branch of your Drupal.org repository, so a tag behind that branch's tip rewound it. A late or re-run test run could do the same, the first deployment of a new branch failed, and the Deploy workflow failed on every run until deployment was set up. 5.x fixes all 4:

Event 4.19.0 5.x
Release tag pushed Force-pushes the tagged commit over a branch Pushes the tag only and leaves branches alone
Older commit deployed late or re-run Force-pushes it over the newer branch tip Skipped with a notice
First deployment of a branch Fails Creates the branch
No DEPLOY_REMOTE secret Every Deploy run fails Skipped with a notice

DEPLOY_BRANCH now applies to branch pushes only. If your release process relied on a tag push to update a branch, push the branch as well.

GitHub Actions is the only CI

CircleCI support is gone, along with the installer's CI provider question, so every project gets the same GitHub Actions workflows: test.yml, deploy.yml, draft-release-notes.yml and assign-author.yml. A project already on GitHub Actions has nothing to do. A project on CircleCI moves its settings to GitHub Actions, under Settings -> Secrets and variables -> Actions:

  • The private deployment key becomes the DEPLOY_SSH_KEY secret, and its fingerprint isn't needed anymore.

  • DEPLOY_REMOTE, DEPLOY_USER_NAME, DEPLOY_USER_EMAIL and DEPLOY_PROCEED become secrets, and DEPLOY_BRANCH becomes a repository variable.

  • CODECOV_TOKEN becomes a secret, and each CI_*_IGNORE_FAILURE toggle becomes a repository variable.

  • Each ci/circleci: <job> check in branch protection becomes the GitHub Actions job of the same name.

Then stop building the project in CircleCI, or every push gets a failing "CircleCI Pipeline" check. CircleCI is gone has the details, including which branches deploy from GitHub Actions.

Upgrading a 4.x project

A 4.x project can't update itself to 5.x: its AGENTS.md fetches the update skill from the old .scaffold/ path, which returns 404, and curl -sSL saves that error page as the skill. Fetch the new skill once by hand, from the project root:

rm -rf .claude/skills/update-consumer-drupal-extension-scaffold
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

Commit your work in progress first, since the update replaces most of the project root. Then start Claude Code in the project and run /update-consumer-eddy. The skill opens a pull request with the 5.x files, drops .devtools/ and the Cloudflare hooks, and rewrites AGENTS.md, so every later update works as usual. It leaves 3 things to you: moving your .devtools/ edits, moving CircleCI settings, and adding Drupal 12 if you want it.

MIGRATION.md covers the same upgrade by hand, what changed in each area, and a checklist for after the upgrade.

Breaking changes

  • Renamed 'init.php' to 'install.php' and its 'INIT_' variables to 'INSTALL_'. @AlexSkrypnyk (#570)
    The installer is now php install.php, and it reads pre-filled answers from INSTALL_* variables such as INSTALL_NAME and INSTALL_TYPE. In 4.19.0 it was php init.php reading DEX_* variables (the INIT_* prefix never shipped), so switch any script that runs it to the new name and prefix, and run php install.php --help for the full list. The CI provider and Cloudflare questions are gone, so their variables have no replacement.

  • [#553] Moved the Cloudflare tunnel hooks from 'scripts/' into the 'drevops/eddy-tooling' commands. @AlexSkrypnyk (#557)
    eddy-start, eddy-provision and eddy-stop now run the Cloudflare quick tunnel themselves whenever CLOUDFLARE_TUNNEL=1 is set, in the shell or now also in .env, so the 3 scripts/*-cloudflared.sh hooks are gone and the installer no longer asks about them. A project created from 4.18.0 or 4.19.0 should delete its copies of the hooks, which would otherwise act on the same tunnel, and carry any edit to them as a patch for drevops/eddy-tooling. The built-in tunnel also keeps localhost among the trusted hosts, so a tunnel provision no longer leaves the site answering 400 on http://localhost:<port>.

  • [#540] Moved '.devtools' to the 'drevops/eddy-tooling' package that 'scripts/eddy-tooling' installs into 'vendor/'. @AlexSkrypnyk (#549)
    The scripts in .devtools/ now ship as the drevops/eddy-tooling package, so every .devtools/<script> call becomes vendor/bin/eddy-<script>, while the ahoy and make command names stay the same. To upgrade, delete .devtools/ and add scripts/eddy-tooling, merge the new composer.dev.json entries, and replace any .devtools/ call in your own scripts, docs and CI steps. Move each edit you made to .devtools/ into a custom lifecycle script or a patch for the package, as .devtools/ is a package now describes.

  • [#503] Dropped CircleCI support and the 'init.php' CI provider prompt. @AlexSkrypnyk (#542)
    .circleci/config.yml is gone and GitHub Actions is the only CI, so the installer no longer asks for a provider. A project on CircleCI should delete .circleci/ and move its deployment settings to GitHub Actions secrets and variables, with the private key as the DEPLOY_SSH_KEY secret, then swap the ci/circleci: <job> checks in its branch protection for the GitHub Actions jobs of the same name. Stop building the project in CircleCI too, or every push gets a failing "CircleCI Pipeline" check.

  • [#539] Renamed the project to Eddy and moved '.scaffold' to '.eddy'. @AlexSkrypnyk (#541)
    The template now lives at drevops/eddy with its update skill renamed to update-consumer-eddy, so a 4.x project can't update itself anymore: its AGENTS.md fetches the old skill from a .scaffold/ path that returns 404, and curl -sSL saves the error page as the skill. Delete .claude/skills/update-consumer-drupal-extension-scaffold/, which 5.x no longer git-ignores, then fetch update-consumer-eddy once by hand and run it, as Upgrade with Claude Code describes. The upgrade rewrites AGENTS.md, so every update after it works as usual.

Other changes

  • [#575] Added a 'MIGRATION.md' guide for upgrading a 4.x project to 5.x and restored 'css/' and 'js/' in the update skill. @AlexSkrypnyk (#579)
    MIGRATION.md walks a 4.x project through the upgrade to 5.x, with Claude Code or by hand, and says for each change what it was, what it is now and what to do. The update skill now also restores a project's own css/ and js/ with the rest of its code, where it used to commit the template's example files over tracked ones. The 4.x skill had the same gap, so if your project tracks its own css/ or js/, check that your last scaffold update kept them.

  • Re-recorded the README demos against 'drevops/eddy-tooling' 1.0.0 and dropped the release notes skill's first-release path. @AlexSkrypnyk (#578)
    The README's build, lint and test demos now show the released vendor/bin/eddy-* commands instead of the .devtools/ scripts they replaced, recorded against drevops/eddy-tooling 1.0.0 from Packagist. The maintainer-only create-eddy-tooling-release-notes skill always starts its range at a published release or a named bound, now that the package has a release.

  • [#576] Switched the scaffold to 'main' and kept generated projects on '1.x' or their committed branches. @AlexSkrypnyk (#577)
    The Eddy repository now develops on main, and the update instructions in AGENTS.md fetch the skill from it. Generated projects still build, deploy and draft release notes from 1.x, and when the installer runs during an update, it now keeps the branches listed in the project's committed test.yml instead of leaving 1.x there, so a project that builds from 2.x no longer restores its branch filters by hand.

  • Kept the git-ignored root '.chromedriver', '.env' and 'composer.lock' out of the repository copy that the functional tests run against. @AlexSkrypnyk (#574)
    Eddy's own installer snapshot tests no longer fail locally after ahoy test or make test leaves .chromedriver/ and .env in the repository root. This only affects work on Eddy itself, and generated projects don't change.

  • Renamed the leftover 'scaffold' and 'devtools' names to 'eddy' and 'tooling'. @AlexSkrypnyk (#573)
    Eddy's own maintenance workflows are now eddy-test.yml and eddy-publish-tooling.yml, which the installer strips from new projects by their eddy- prefix, and the tooling commands live in the DrevOps\Eddy\Tooling namespace. Generated projects only run the vendor/bin/eddy-* commands, so nothing in them changes.

  • Stubbed 'MockTrait' functions through php-mock instead of the deprecated 'any()' matcher and made risky tests fail the '.eddy/tests' run. @AlexSkrypnyk (#572)
    Eddy's own test suite no longer uses PHPUnit's deprecated any() matcher, and a risky test now fails the run with exit code 1 instead of ending it early with a passing exit code. Generated projects don't change.

  • Updated the '.eddy/tests' dependencies to their latest PHP 8.3 releases and adapted the suite to PHPCS 4, Coder 9 and Rector 2.7. @AlexSkrypnyk (#571)
    Eddy's own test suite now runs on PHPCS 4, Coder 9, PHPStan 2.3, Rector 2.7 and PHPUnit 12.5, and PHPCS now checks the 10 extensionless tooling scripts it used to skip. Generated projects don't change, and neither does what the eddy-* commands do.

  • Added the 'create-eddy-tooling-release-notes' skill and bumped 'shivammathur/setup-php' to 2.40.0. @AlexSkrypnyk (#569)
    Generated projects' test.yml moves to shivammathur/setup-php 2.40.0, which officially supports the ubuntu-26.04 runners its jobs use, and Eddy's own macOS CI legs now restore a prebuilt PHP instead of building it against the latest Homebrew. The new create-eddy-tooling-release-notes skill helps maintainers write release notes for drevops/eddy-tooling, and generated projects never receive it.

  • [#563] Pointed the 'test.yml' Composer cache at the directory Composer writes to and keyed it per matrix leg and month. @AlexSkrypnyk (#568)
    The Composer cache in test.yml never stored anything, because it pointed at /tmp/composer-cache, a directory Composer never wrote to. It now caches the directory composer config cache-files-dir reports, keyed by job, UTC month and a hash of composer.json and composer.dev.json, so each CI job restores its package downloads instead of fetching about 150 archives every run. The gain is seconds per job, not minutes, and the README explains how to clear a job's cache.

  • [#562] Started the browser in 'make test' and 'ahoy test' before they run the FunctionalJavascript suite. @AlexSkrypnyk (#567)
    ahoy test and make test run every PHPUnit suite, but only test-functional-javascript started the WebDriver browser, so the README's build-then-test flow failed on the template's own FunctionalJavascript test. In a project that keeps FunctionalJavascript tests, both now start the browser first and leave it running until browser-stop, so test needs Chrome, or Docker with WEBDRIVER_BACKEND=selenium, while test-unit, test-kernel and test-functional still run without one.

  • [#564] Corrected the 'init.php' marker comments, documented the template markers and added 'TemplateMarkersTest'. @AlexSkrypnyk (#566)
    A broken template marker block, such as a misspelled token or a missing closing marker, now fails Eddy's own test suite instead of shipping into generated projects. The maintainer guide in .eddy/CLAUDE.md gains the marker rules and checklists for adding a Drupal major or a development tool.

  • Corrected doc drift around 'test', documented the CI toggles and coverage upload, flattened 'eddy-deploy' and trimmed test comments. @AlexSkrypnyk (#565)
    The README now documents the 8 CI_*_IGNORE_FAILURE repository variables that let a CI step report problems without failing its job, and the CODECOV_TOKEN secret for optional Codecov uploads. The rest is cleanup that leaves behavior alone: eddy-deploy and eddy-start read more simply, and the generated AGENTS.md lists every field eddy-info prints.

  • Split 'eddy-browser' into 'eddy-browser-start' and 'eddy-browser-stop' and aligned the banners and logs of the tooling commands. @AlexSkrypnyk (#561)
    The browser commands are eddy-browser-start and eddy-browser-stop, replacing .devtools/browser start and stop from 4.19.0, and each ends with a completion banner like the other tooling commands, while ahoy browser-start, make browser-start and their stop counterparts keep their names. The PHP webserver now logs to .logs/php.log instead of the /tmp/php.log every project on the machine shared, and chromedriver logs to .logs/chromedriver.log.

  • [#555] Removed project prefixes from environment and CI variable names. @AlexSkrypnyk (#560)
    Eddy's own CI toggles are now CI_YAMLLINT_IGNORE_FAILURE and CI_ACTIONLINT_IGNORE_FAILURE, matching CI_ZIZMOR_IGNORE_FAILURE, and a test stops project prefixes from coming back. Nothing a generated project reads was renamed: the DEPLOY_* secrets, the CI_* toggles in test.yml and the tooling's variables keep their names, and the installer's move to INSTALL_* is listed under Breaking changes.

  • [#556] Scoped the Zizmor audit to '.github' so the init fixtures no longer report as unpinned actions. @AlexSkrypnyk (#559)
    Eddy's own Zizmor audit only reads .github/, so its test fixtures stop producing false "unpinned action" alerts in code scanning. Generated projects don't run this workflow.

  • Switched actionlint to the 'kjanat/actionlint' fork and renamed its ignore-failure toggle to 'EDDY_CI_ACTIONLINT_IGNORE_FAILURE'. @AlexSkrypnyk (#558)
    Eddy's own workflow lint runs kjanat/actionlint 1.17.0, which recognizes the ubuntu-26.04 runner label that no rhysd/actionlint release knows, so the check passes without being told to ignore failures. The toggle ended up as CI_ACTIONLINT_IGNORE_FAILURE after #560, and generated projects don't run this workflow.

  • [#525] Made regenerating the README terminal recordings produce byte-identical SVGs. @AlexSkrypnyk (#552)
    Regenerating the README's terminal demos now produces byte-identical SVGs when nothing changed, so a diff in them is a real change. This only affects work on Eddy itself.

  • [#518] Fixed deployments that overwrote or rewound the remote branch, failed on a new branch, or failed without configuration. @AlexSkrypnyk (#551)
    A release tag now deploys only the tag and leaves the remote's branches alone, a branch deploy is skipped with a notice once its commit is no longer the branch tip, and the first deployment of a new branch creates it instead of failing. Until the DEPLOY_REMOTE secret is set, the Deploy workflow reports a notice instead of failing, DEPLOY_BRANCH applies to branch pushes only, and the SSH key step runs on Node.js 24. If your release process relied on a tag push to update a branch, push the branch as well.

  • Replaced the README logo with an adaptive 'logo.svg', added a social preview card and regenerated the README recordings. @AlexSkrypnyk (#550)
    The README shows the new Eddy logo, which switches its lettering for dark mode, and .eddy/assets/ gains a social preview card for the repository. The installer removes logo.svg from new projects, so they still start with the placeholder logo.png.

  • [#544] Installed 'cweagans/composer-patches' in the build so declared dependency patches apply. @AlexSkrypnyk (#548)
    From 4.14.0 to 4.19.0, the build didn't install cweagans/composer-patches, so unless a project required the plugin itself, Composer silently skipped every declared patch. The build now installs composer-patches 2.x, the patches in composer.json and composer.dev.json apply, and a patch that no longer applies fails the build with No available patcher was able to apply patch.

  • [#545] Fixed generated 'CONTRIBUTING.md' documenting both 'make' and 'ahoy' commands whatever wrapper 'init.php' keeps. @AlexSkrypnyk (#547)
    A generated CONTRIBUTING.md now documents only the command wrapper the project keeps: ahoy commands for Ahoy, make commands for the Makefile, both when both are kept, and the vendor/bin/eddy-* commands when neither is. Before, every project got both sets, even the ones with only 1 wrapper or none.

  • Rewrote the README introduction to separate Eddy from Vortex, and fixed documentation, comments and code that disagreed with each other. @AlexSkrypnyk (#546)
    The README now explains that Eddy maintains a contributed module or theme on GitHub, while Vortex builds websites, and a pass of the docs against the code fixed examples that didn't work, such as running a single test, which needs a path relative to build/. 2 tooling helpers changed along the way: drush() prints a failed command's output when DEBUG=1 is set, and kill_port() stays quiet when lsof is missing.

  • Update release-drafter/release-drafter digest to 72967cd @renovate[bot] (#537)

  • [#418] Added Drupal 12 stable, legacy and canary CI jobs, with exact core release pinning and a shared dependency stability policy. @AlexSkrypnyk (#538)
    A project that selects Drupal 12 in the installer gets 5 Drupal 12 CI jobs on PHP 8.5 and a README badge, the example extension declares core_version_requirement: ^10 || ^11 || ^12, and Drush 14 joins the allowed range. Every build now pins Drupal core to 1 exact release and resolves other dependencies with minimum-stability: dev and prefer-stable: true, listing any package installed from a development branch, and DRUPAL_VERSION no longer accepts @dev.

Full Changelog: 4.19.0...5.0.0-alpha1

@AlexSkrypnyk and @renovate[bot]