Tooling package
The commands that assemble, start, provision and deploy your extension come from the drevops/eddy-tooling Composer package, so fixes reach your project through a version constraint. The make and Ahoy commands and CI call them, and you can run them yourself as vendor/bin/eddy-<command> from the project root. This page covers how the package gets installed, what each command does and how to change one.
The package is published from the .eddy/tooling directory of Eddy, and its own repository is a read-only mirror. Report bugs and propose changes in drevops/eddy.
How it's installed
Your project's composer.json belongs to the extension, and the tooling assembles the Drupal site into build itself. So scripts/eddy-tooling installs the package into vendor/, 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 make or Ahoy, run it yourself first:
scripts/eddy-tooling
The version constraint lives in composer.dev.json:
"require-dev": {
"drevops/eddy-tooling": "~1.0.5"
}
Keep the ~ constraint. It accepts patch releases and holds the minor version, so CI picks up fixes on its own. A new minor version arrives only with the next scaffold update. A scaffold update also raises the constraint whenever the new scaffold needs a newer release. The next run of the installer then replaces a release that lacks what the scaffold calls.
The installer records what it installed in vendor/eddy-tooling.json, and runs Composer only in these cases:
- The constraint or the patches for the package change.
- A local patch file changes.
vendor/eddy-tooling.jsonis missing, such as afterresetremovesvendor/.- A command of the installed package is missing from
vendor/bin.
Otherwise it exits without running Composer, so running it before each command adds almost no time. A local checkout keeps its patch release until vendor/ is removed. CI installs from scratch, so it takes the newest patch release on every run. To take the newest patch release locally and keep vendor/, delete vendor/eddy-tooling.json. The installer reinstalls the package the next time it runs.
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. It comes from Eddy as-is, 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
The package installs the following commands into vendor/bin:
| Command | Purpose |
|---|---|
eddy-assemble | Assembles a Drupal codebase in build/, installs the dependencies and links the extension into it. |
eddy-start | Starts the PHP built-in webserver. When neither your shell nor .env sets WEBSERVER_PORT, 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-login | Prints a one-time login link without opening it in a browser, and draws it as a QR code when LOGIN_QRCODE is set. |
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. When neither your shell nor .env sets WEBDRIVER_PORT, discovers a free port from 4444 to 4543 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. |
These pages describe each command in detail:
- Build the site for
eddy-assemble - Webserver for
eddy-startandeddy-stop - Provision the site for
eddy-provisionandeddy-login - FunctionalJavascript tests for the browser commands
- Public tunnel for
eddy-qrcode - Deployment for
eddy-deploy
eddy-info fields
vendor/bin/eddy-info FIELD prints a single value. An unknown field prints - and exits with code 1. The following table lists the fields:
| Field | Value |
|---|---|
drupal-version | The installed Drupal version. |
php-version | The PHP version on your PATH. |
composer | The Composer version. |
drush | The Drush version of the build. |
node | The Node.js version. |
npm | The npm version. |
webserver-host | The webserver host, from WEBSERVER_HOST. |
webserver-port | The webserver port, from WEBSERVER_PORT. |
site-url | The site URL: the tunnel URL when a tunnel is up, the webserver URL otherwise. |
xdebug | enabled or disabled for the running webserver, or - when it isn't running. |
webdriver-port | The WebDriver port, from WEBDRIVER_PORT. |
webdriver-backend | The WebDriver backend, from WEBDRIVER_BACKEND: chromedriver or selenium. |
webdriver-host | The value of WEBDRIVER_HOST. An empty line when the variable isn't set, since the default depends on the backend. |
build-dir | The absolute path of the build directory. |
database | The path of the SQLite database. |
drupal-profile | The install profile, from DRUPAL_PROFILE. |
Apart from webdriver-host, a value that can't be resolved, such as the Drupal version before the first build, prints -.
Command output
The commands print their own progress and hide the output of the tools they run, such as Composer, npm ci and Drush, unless one of them fails. The output of npm run build and of your lifecycle scripts always shows. Debug your extension explains how to see all of it, and where the processes that keep running write their logs.
Patch 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 reads patches for the package from composer.dev.json only, and applies them with cweagans/composer-patches. A patch for the package in your extension's composer.json never reaches the tooling. Local patch paths are relative to the project root. The site build leaves out the package and its patch declarations, so it never applies them. The patch files still reach build/patches with the rest of the patches directory.
Use 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. A patch can also 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.