Installer
The Vortex installer is a self-contained Symfony Console application that
customizes the template based on user selections. It lives inside the same
repository as the template at .vortex/installer/. That way, a single pull
request can combine changes to the template and the installer, so template
modifications and the corresponding installer logic stay in sync.
Architecture
The installer is a PHP application rather than a shell script because it
touches many parts of the template and requires complex management of the
permutations of template variants. Using PHP allows the installer to provide a
terminal UI via Laravel Prompts, where
users are guided through a series of questions to customize their project. At
the same time, the installer supports non-interactive installs (via the
--no-interaction flag) for both new and existing projects, as well as
updates. This makes it suitable for automated pipelines and CI environments.
Prompt answers can be provided from multiple sources, resolved in the following priority order:
- Configuration file - a JSON file passed via the
--configoption. - Environment variables - using the
VORTEX_INSTALLER_PROMPT_<handler_id>naming convention. - Discovery - auto-detection from existing project files (for example,
composer.json,.env, hosting-specific files like.lagoon.yml). - Handler defaults - fallback values defined by each handler.
This means the installer can be driven entirely by a config file or environment variables without any user interaction, which is essential for reproducible installations and automated testing.
The core flow is:
- User downloads the installer PHAR from https://www.vortextemplate.com/install
and runs it with
php installer.php. InstallCommandorchestrates the installation process.PromptManagercollects user choices via interactive prompts.- Handlers queue file operations based on selections - each handler is responsible for a specific aspect of the template (CI provider, hosting, services, theme, deployment strategy, etc.).
- File operations execute: conditional tokens are processed to include or exclude content based on the selected features.
- The output is a fully customized Drupal project.
Conditional token system
The installer handles replacements and additions with string substitution. It also uses a token system to conditionally exclude entire blocks of content from template files. This simplifies the management of optional features - rather than requiring complex logic to surgically remove lines from configuration files, scripts, or documentation, maintainers wrap the relevant content in token markers. When a feature isn't selected during installation, the installer removes everything between the markers.
Markdown:
[//]: # (#;< TOKEN_NAME)
Content removed if feature not selected
[//]: # (#;> TOKEN_NAME)
Shell/YAML:
#;< TOKEN_NAME
Content removed if feature not selected
#;> TOKEN_NAME
Testing
The installer uses a multi-layered testing approach with unit tests for individual components and functional tests for full installation scenarios.
Unit testing
Unit tests cover core utilities (Yaml, File, Git, Validator,
Strings), downloaders, runners, and handler discovery logic. Each component
is tested in isolation with mocks provided by
Mockery.
Functional testing with snapshots
For every test permutation, the installer initiates a fresh project from the Vortex template with a specific combination of user selections and runs assertions against the resulting files. A single template change can affect a hundred plus installation permutations. Snapshot testing lets you review the impact of a change across all scenarios as diffs, so regressions are caught before they reach consumers.
Each handler has a dedicated functional test class extending
AbstractHandlerProcessTestCase, covering every aspect of the installation
process: CI providers, hosting providers, services, themes, deployment
strategies, database sources, notification channels, and more.
These tests use the alexskrypnyk/snapshot
library for snapshot-based testing. The snapshot system works on a
baseline + diff pattern:
_baseline/contains the complete reference installation - a full set of template files as they would appear after a default installation.- Each scenario directory (for example,
services_no_clamav/,hosting_acquia/,deploy_types_all_circleci/) contains only the delta changes from the baseline.
When a test runs, the snapshot library applies the scenario's diffs on top of the baseline and compares the result against the actual installer output. This approach keeps the fixture files maintainable - instead of duplicating the entire template for each scenario, only the differences are stored.
The fixture directories in .vortex/installer/tests/Fixtures/handler_process/
cover scenarios like:
- CI providers: GitHub Actions, CircleCI
- Hosting: Acquia, Lagoon
- Services: Solr, Redis, ClamAV (enabled/disabled combinations)
- Deployment: artifact, webhook, Lagoon, all combined
- Database sources: Acquia, Lagoon, FTP, S3, URL, container registry
- And many more permutations
Running tests
cd .vortex/installer
# Install Composer dependencies.
composer install
# Run all tests.
composer test
# Run only handler functional tests.
./vendor/bin/phpunit --filter "Handlers\\\\"
# Run a specific handler test.
./vendor/bin/phpunit --filter "ServicesHandlerProcessTest"
Updating snapshots
Fixture files should never be modified directly. When the template or installer
logic changes, regenerate the snapshots from the .vortex/ directory:
cd .vortex
ahoy update-snapshots
The regeneration runs the installer for every permutation, initiates a fresh project for each scenario, and updates the fixture files to match the current output. The resulting changes appear as diffs in version control, making it straightforward to review exactly how a template or installer change affects each scenario.
Commit your template and installer changes before running
ahoy update-snapshots - the snapshots regenerate from the committed
baseline, so uncommitted changes aren't picked up. ahoy update-snapshots is
the only sanctioned way to regenerate fixtures: invoking the test suite with
UPDATE_SNAPSHOTS by hand bypasses part of the workflow and produces partial,
inconsistent fixtures.
Releasing
The installer is packaged as a PHAR archive using
Box. It's released automatically on each
GitHub release of the Vortex template and deployed to
https://www.vortextemplate.com/install - the release process
needs no separate installer step. Additionally, the installer is published for
every branch containing release-docs or release-installer in its name,
which allows testing installer changes before a formal release.
Installer video
The documentation includes an interactive recording of the installer captured
with asciinema. It's one of the 9 terminal demo
videos produced by the PHP orchestrator at
.vortex/docs/.utils/update-videos.php, driven by the shared VideoRecorder
class. For the installer video, the pipeline:
- Builds the installer PHAR from source.
- Generates an Expect script that automates the installer interaction, simulating human-like typing with delays.
- Records the session with
asciinema recinto a JSON cast file. - Post-processes the recording (sanitizes paths, removes spawn lines).
- Converts the cast file to SVG and PNG using the custom
svg-term-render.jsscript.
The output files are stored in .vortex/docs/static/img/ and embedded in the
documentation using a custom AsciinemaPlayer React component.
To update the video:
cd .vortex
ahoy update-videos installer
Re-record the installer video whenever the prompt flow changes - the recording
captures the live prompts. The command doesn't commit its output - review the
diff under .vortex/docs/static/img/ and commit manually.
sharp-cli, which renders the PNG posters, is a declared dependency of
.vortex/docs, so it's pinned by yarn.lock and installed by the existing
yarn install --frozen-lockfile.
asciinema is downloaded rather than installed from a lockfile, so the
workflows that regenerate the video in CI pin it explicitly:
.github/workflows/vortex-release.yml and
.github/workflows/vortex-test-installer.yml each set ASCIINEMA_VERSION and
ASCIINEMA_SHA256. Bump both workflows together, so the release and the
installer test always generate assets with identical tooling.