Skip to main content
Version: v1

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/:

PathPurpose
src/The shipped scripts; the only directory in the published archive
composer.jsonThe 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.lockThe 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, LICENSEThe package readme and license, which ship with the scripts
.gitattributes, .gitignoreThe 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:

  • info opens and closes an operation.
  • task announces work that's starting. Every task must be closed by a pass or a fail, even when the work itself can't fail, so the reader can always tell whether a step succeeded.
  • pass reports 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, task also starts a timer, and pass appends the seconds the task took to its message.
  • note is a standalone remark that starts no task.
  • fail reports 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:

  1. Create it in src/ as vortex-<name>, following the script conventions.
  2. Add src/vortex-<name> to the bin list 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 its vortex- prefix, such as deploy.bats for vortex-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.bats

    ahoy test-bats needs a path: without one, BATS stops with Must 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 is update-vortex.bats, which runs vortex-update from your working tree. The setup also expects curl, composer, docker, ahoy, and jq on your PATH.

    ahoy test-bats runs BATS with --tap, so each test prints ok <n> <name> when it passes or not ok <n> <name> when it fails. The command exits with a non-zero code when any test fails.

    The Vortex - Test workflow (.github/workflows/vortex-test-common.yml) runs the whole suite in its vortex-test-common job, with .vortex/tests/test.common.sh, the script behind ahoy test-common. The script runs BATS on .vortex/tooling/tests/unit. When VORTEX_DEV_TEST_COVERAGE_DIR is set, which that job does, it runs BATS through kcov and 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; see tooling/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.