Tooling
The shell scripts that consumer projects run for provisioning, database
work, deployment, notifications, and helpers such as doctor and info
live in .vortex/tooling/. This page is for contributors who change them:
it covers the package layout, the script conventions, the test levels, and
how the package gets published. To use the scripts in a project, see
Architecture > Automation
scripts.
The scripts are published as the standalone
drevops/vortex-tooling
Composer package. Consumer projects install the package and run the
user-facing scripts, the ones listed under bin in the package
composer.json, from vendor/bin/vortex-*. The other scripts, such as the
per-channel vortex-notify-* ones, run through those entry points.
Package layout
The following table lists the contents of .vortex/tooling/:
| Path | Purpose |
|---|---|
src/ | The shipped scripts; the only directory in the published archive |
composer.json | The package definition, with the user-facing scripts under bin and no version field |
tests/unit/ | BATS unit tests covering the shipped scripts and a few template scripts outside the package |
tests/_helper.bash, tests/fixtures/ | The shared BATS setup and the fixture files the tests read |
package.json, yarn.lock | The bats-helpers library that the BATS tests load; ahoy test-bats installs it on first run |
playground/ | Manual scripts that hit live services (email, GitHub, Jira, New Relic, Slack, webhooks, and S3); not automated |
README.md, LICENSE | The package readme and license, which ship with the scripts |
.gitattributes, .gitignore | The Git settings, which stay out of the published archive |
.gitattributes marks only .gitattributes, .gitignore, playground/,
and tests/ as export-ignore. The published archive holds the rest:
src/, composer.json, package.json, yarn.lock, README.md, and
LICENSE.
While developing Vortex itself, the template's root composer.json
resolves the package from the in-tree copy through a path repository with
a pinned version. The installer strips that entry so consumer sites resolve
the published package from Packagist. The same version is also pinned in
scripts/vortex-tooling.sh and .vortex/tests/phpunit/Traits/SutTrait.php;
Release > Release process lists every spot
to bump.
Script conventions
Every shipped script follows the same pattern: an environment-loading
header, set -eu with an opt-in VORTEX_DEBUG=1 trace, variables with
defaults, and the shared output helpers. The header reads .env and
.env.local while letting real environment variables win.
The output helpers have a strict contract:
infoopens and closes an operation.taskannounces work that's starting. Everytaskmust be closed by apassor afail, even when the work itself can't fail, so the reader can always tell whether a step succeeded.passreports success. It closes the open task, or it stands on its own for a check that holds or a step that's skipped or finished. In the shipped scripts,taskalso starts a timer, andpassappends the seconds the task took to its message.noteis a standalone remark that starts no task.failreports the failure and exits.
The following example closes a task whose command can't fail:
task "Disabling Search API Solr server."
drush search-api:server-disable solr || true
pass "Disabled Search API Solr server."
The || true in this example, from scripts/provision-20-migration.sh, is
deliberate: the disable is best-effort, so a failed disable doesn't stop the
script. The contract still requires the closing pass so the step reports
an outcome.
In a shipped script, a pass that closes a task prints [ OK ], the
message, and the task's duration in seconds, such as
[ OK ] Disabled Search API Solr server. (2s). The provision subscripts
in scripts/ define simpler helpers, so in the example's own script,
pass prints the message after a + marker, with no duration.
See Template > Authoring scripts for the full authoring requirements shared by all shell scripts in the template.
Add a script
To add a user-facing script, follow these steps:
- Create it in
src/asvortex-<name>, following the script conventions. - Add
src/vortex-<name>to thebinlist in.vortex/tooling/composer.json.
Composer creates a vendor/bin entry only for the scripts in that list.
Leave helper scripts off it: vortex-notify, for example, runs each
vortex-notify-* channel script by path from src/.
Testing
Tests cover the scripts at 3 levels:
-
Unit - BATS tests in
tooling/tests/unit/cover the shipped scripts with external commands mocked. The same suite also covers a few template scripts outside the package; see Template > Unit testing. Most test files are named after the script they cover, without itsvortex-prefix, such asdeploy.batsforvortex-deploy. Run the tests from.vortex/:cd .vortex# Run all BATS tests.ahoy test-bats tooling/tests/unit# Run a single test file.ahoy test-bats tooling/tests/unit/deploy.batsahoy test-batsneeds a path: without one, BATS stops withMust specify at least one <test>. Each test runs the scripts from a copy of the repository at its last commit, so commit your changes before you run it. The exception isupdate-vortex.bats, which runsvortex-updatefrom your working tree. The setup also expectscurl,composer,docker,ahoy, andjqon yourPATH.ahoy test-batsruns BATS with--tap, so each test printsok <n> <name>when it passes ornot ok <n> <name>when it fails. The command exits with a non-zero code when any test fails.The
Vortex - Testworkflow (.github/workflows/vortex-test-common.yml) runs the whole suite in itsvortex-test-commonjob, with.vortex/tests/test.common.sh, the script behindahoy test-common. The script runs BATS on.vortex/tooling/tests/unit. WhenVORTEX_DEV_TEST_COVERAGE_DIRis set, which that job does, it runs BATS throughkcovand writes the scripts' code coverage to that directory. -
Integration - the template's PHPUnit functional tests in
.vortex/tests/run the scripts end-to-end. See Template > Testing. -
Manual -
tooling/playground/holds scripts that hit live services. They aren't automated; seetooling/playground/README.md.
Publishing
The Vortex - Publish tooling workflow
(.github/workflows/vortex-publish-tooling.yml) mirrors .vortex/tooling/
to the drevops/vortex-tooling
repository on every push to main, 2.x, and any branch whose name
contains vortex-tooling, matching the source branch name.
Each push publishes 1 commit to the mirror, an empty one when nothing in
.vortex/tooling/ changed. Its subject matches the subject of the pushed
commit, and its body records provenance in 2 lines,
Source: drevops/vortex@<sha> and Branch: <branch>. A branch that's new
to the mirror starts there as an orphan with no shared history. Treat the
mirror as read-only, because each publish replaces its files with the
contents of .vortex/tooling/.
To confirm a publish, open the Vortex - Publish tooling run for your
push. Its publish step ends with
Published 'drevops/vortex@<sha>' to drevops/vortex-tooling#<branch>.
The workflow doesn't set a package version: Packagist reads it from the
mirror's tags and branch names. So never hardcode a version in the
package composer.json.
You tag tooling releases on the mirror repository. When the tooling has changed since the last release, tag it before the Vortex release, so the template can require the new tag. See Release > Tagging.