Tooling package
The commands that assemble, start, provision and deploy your extension come from the drevops/eddy-tooling Composer package, so fixes reach you through a version constraint rather than a manual merge. The make and Ahoy commands call them, CI calls them, and you can run them yourself as vendor/bin/eddy-<command> from the project root.
The package is published from the .eddy/tooling directory of Eddy, so report bugs and propose changes in drevops/eddy, not in the package's read-only mirror.
How it's installed
A project created from Eddy doesn't install the package with a plain composer install: its composer.json belongs to the extension, and the tooling assembles the Drupal site into build itself. scripts/eddy-tooling installs the package into vendor/ instead, with each command in vendor/bin/eddy-*. make and Ahoy run the installer before the commands that need it, and CI runs it as a step of its own. Without a wrapper, run it yourself first:
scripts/eddy-tooling
The version constraint lives in composer.dev.json:
"require-dev": {
"drevops/eddy-tooling": "~1.0.0"
}
Keep the ~ constraint. It accepts patch releases and holds the minor version, so CI picks up fixes on its own, and a new minor version arrives with the next scaffold update rather than in the middle of a CI run.
The installer runs Composer only when the constraint, the patches for the package or the contents of a local patch file change, or when a command is missing from vendor/bin. Otherwise it returns straight away, so running it before every command costs next to nothing. A local checkout keeps its patch release until vendor/ is removed, which reset does.
The package requires PHP 8.3 or newer, so every CI job and local build that installs it needs PHP 8.3 or newer too.
scripts/eddy-tooling as it isNever modify scripts/eddy-tooling, by hand or with AI: your project uses it as-is from Eddy, and a scaffold update replaces it with the release's copy. To add a step of your own, use a lifecycle script. To change what a command does, patch the package.
Commands
| Command | Purpose |
|---|---|
eddy-assemble | Assembles a Drupal codebase in build/, installs the dependencies and symlinks the extension. |
eddy-start | Starts the PHP built-in webserver. Discovers a free port from 8000 to 8099 and writes it to .env. |
eddy-stop | Stops the webserver. |
eddy-provision | Installs Drupal on the assembled site and enables the extension. |
eddy-deploy | Mirrors the extension to a remote git repository, such as Drupal.org. CI runs it. |
eddy-browser-start | Starts the WebDriver backend for FunctionalJavascript tests. Discovers a free port from 4444 and writes it to .env. |
eddy-browser-stop | Stops the WebDriver backend. |
eddy-info | Prints a summary of the environment, or a single field such as site-url for the wrappers to read. |
eddy-qrcode | Draws a URL as a QR code in the terminal. |
The pages of this section describe what each command does in detail: Building the site for eddy-assemble, Webserver for eddy-start and eddy-stop, Provisioning for eddy-provision, FunctionalJavascript tests for the browser commands, Public tunnel for eddy-qrcode, and Deployment for eddy-deploy.
Command output
The commands print their own progress and hide the output of the tools they run, such as Composer, npm and Drush, unless one of them fails. Debugging explains how to see all of it, and where the processes that keep running write their logs.
Patching the package
The package is installed into vendor/, so an edit made there is lost on the next install. To change what a command does, declare a patch for the package in composer.dev.json, next to its version constraint:
"extra": {
"patches": {
"drevops/eddy-tooling": {
"Describe the change": "patches/eddy-tooling-change.patch"
}
}
}
The installer applies the patches with cweagans/composer-patches, and installs the package again whenever the list changes or a local patch file is edited. Local patch paths are relative to the project root. The patches, like the package, stay out of the site build.
Reach for a patch only when a lifecycle script can't do the job. A script adds a step to a phase, while a patch changes the command itself, and it can stop applying when a tooling release changes the lines it touches.
How the package is tested
Eddy's own test suite covers every command: unit tests run each one with its external calls mocked, and functional tests build, run and lint a real extension through the same commands. Scaffold tests describes the suite.