Vortex 1.38.0 is out. This release is a mix of new testing, default modules, security, deployment ergonomics, and a runtime bump. Below is a tour of what changed and what it means if you're running a Vortex site.
Where a feature has a switch, the variable name is in the description so you can flip it without reading the source.
JavaScript unit tests for custom modules
Until now, Vortex shipped PHPUnit (PHP) and Behat (browser flows). JavaScript got the cold shoulder. If you wrote a custom module that did anything interesting in JS, you tested it by hand or via a heavyweight Behat scenario.
This release adds Jest for custom Drupal modules. Jest is configured in the project root package.json; CI runs ahoy test-js (which calls yarn test and produces coverage). The Jest config picks up tests under any web/modules/custom/<module>/js/ directory matching *.test.js. The theme is not in scope; theme JS keeps its own tooling.
A demo test under web/modules/custom/ys_demo/js/tests/ys_demo.test.js covers the example counter behavior end to end with jest-environment-jsdom. It's a useful template for your first Jest test if you've never written one.
If you're already on Vortex, the migration is short: pull the new root package.json dependencies (yarn install at the project root inside the cli container), put your tests next to the source under web/modules/custom/<module>/js/, and run ahoy test-js locally to confirm.
Four modules now ship in the default stack
Four contrib modules join the default composer.json:
drupal/drupal_helpers(^2.0). General-purpose helper API for Drupal sites.drupal/generated_content(^2.0). Programmatic generation of fixture content via plugin classes (e.g.web/modules/custom/<module>/src/Plugin/GeneratedContent/).drupal/testmode(^2.7.0). Marks the site as being in test mode and lets configuration and behaviour branch on it.drupal/reroute_email(^2.3@RC). Intercepts outbound mail on non-production environments. Configured through three environment variables:DRUPAL_REROUTE_EMAIL_ADDRESS(where intercepted mail goes),DRUPAL_REROUTE_EMAIL_ALLOWED(a glob pattern of addresses that bypass interception), andDRUPAL_REROUTE_EMAIL_DISABLED=1to turn rerouting off. Production typically wantsDRUPAL_REROUTE_EMAIL_DISABLED=1. Everything else typically does not.
Existing projects: run composer update -W and then enable the modules you want with drush pm:install. Add the three reroute variables to the relevant environment files.
Notifications stop firing on every channel for every branch
Deployment notifications are useful right up until they aren't. The default behaviour in most Drupal CI setups is "tell everyone every time", which means after a few months people stop reading them.
Each notification channel can now be filtered to specific branches: Email, Newrelic, Jira, GitHub, Slack, Webhook. The variable is VORTEX_NOTIFY_<CHANNEL>_BRANCHES, comma-separated, e.g.:
VORTEX_NOTIFY_EMAIL_BRANCHES="main,develop"
VORTEX_NOTIFY_NEWRELIC_BRANCHES="main,master,develop"
VORTEX_NOTIFY_JIRA_BRANCHES="main"Leave a variable empty and that channel fires on all branches (the existing behaviour). New Relic now defaults to main,master,develop if not set.
Drupal core text files are no longer publicly readable
Files like web/core/CHANGELOG.txt, INSTALL.txt, and the long tail of core text and markdown files are now blocked at the web-server level by a single rule in web/.htaccess:
RewriteRule ^core/.*\.(txt|md)$ - [F]These have always been a quiet way to fingerprint a Drupal site's exact version. Existing projects: regenerate web/.htaccess from the template (or copy this rule in) and redeploy. There's no environment variable - the rule is unconditional once the file is on the web server.
Configuration import gets a second pass when you ask for it
Anyone who has run a Drupal config import on a project with cross-dependencies has seen the issue: one pass isn't always enough. The historical workaround was to run drush config:import twice manually.
This release adds an opt-in:
VORTEX_PROVISION_CONFIG_IMPORT_REPEAT=1Default is 0, so existing projects see no change. Flip it to 1 if your config has known cross-dependency issues; provision will run a second drush config:import after the first one.
Manual deploys from the GitHub Actions UI
The deploy workflow now accepts workflow_dispatch inputs:
deploy_target: a branch name (e.g.develop) orPR-<num>for a PR.override_db: a boolean that maps toVORTEX_DEPLOY_ACTION=deploy_override_dbto wipe the existing database and rebuild.enable_terminal: as before, drops a debug terminal session into the runner.
When deploy_target is set, the lint, database, and build jobs are all skipped (the if: conditions check !inputs.deploy_target); only the deploy job runs, against the resolved branch or PR head SHA.
Renovate config gets the boot it needed
The hardening lives in renovate.json:
prConcurrentLimit: 10. A cap on concurrently open Renovate PRs.prHourlyLimit: 0. No hourly throttle on PR creation, so the limit above is the only gate.labels: ["Dependencies"]anddependencyDashboardLabels: ["Dependencies"]. Every Renovate PR and dashboard entry is consistently labelled.- A package rule that turns on
automerge: truefor thegithub-actionsmanager (withpinDigests: true), so digest-pinned action bumps don't need a human to click through.
Existing projects: copy the updated renovate.json from the template. The new limits and auto-merge rules apply on the next Renovate run.
Provision reliability fixes
Two reliability fixes worth calling out together:
- Fallback to profile install (
VORTEX_PROVISION_FALLBACK_TO_PROFILE=1). When this kicks in (DB dump unavailable), the profile install path now installs theshieldmodule aftersite:install, exportsVORTEX_PROVISION_POST_OPERATIONS_SKIP=1so the rest of the post-provision steps don't run against a half-built site, and avoids--existing-configon the fallback path so a missing config directory doesn't trip the install. - Cache rebuild after
drush updb.drush cache:rebuildnow runs automatically after database updates, so the site renders correctly on the first request. SetVORTEX_PROVISION_CACHE_REBUILD_AFTER_DB_UPDATE_SKIP=1to opt out (default0= enabled).
Both apply automatically on the next provision. No installer prompt, no manual config.
Runtime: PHP 8.4 and Lagoon 26.4.0
The runtime moves to PHP 8.4 across the CLI image, Composer platform (composer.json config.platform.php), PHPStan (phpstan.neon phpVersion: 80420), and PHPCS (phpcs.xml testVersion="8.4"). If your consumer project is still pinned to PHP 8.3, update those configs to match. Run your test suite locally before rolling out.
Lagoon containers are at 26.4.0 (mysql-8.4, valkey-8, nginx-drupal, php-8.4-cli-drupal, commons, solr-9-drupal). Drupal core is at 11.3.x. CI runner is at drevops/ci-runner:26.3.0 (the latest stable; 26.4.0 is still draft).
The CI cache key is now v26.4.0-db11. The v<YY>.<M>.<minor> prefix tracks the Lagoon container version (so it changes when Lagoon does); the db<N> suffix tracks the Drupal core major version (so it changes when you move between majors, not on every release). Existing projects: the first build after pulling this release warms the new cache automatically.
Where to go next
Full release notes live on GitHub: Vortex 1.38.0 release(Opens in a new tab/window). The Vortex docs at vortextemplate.com(Opens in a new tab/window) have the per-feature reference.
If you're already running Vortex, the upgrade path is the standard: bump the package, bump PHP to 8.4 in your three lint configs, regenerate web/.htaccess for the new core-files block, and decide whether you want to flip on the new opt-in switches (VORTEX_PROVISION_CONFIG_IMPORT_REPEAT, the per-channel VORTEX_NOTIFY_*_BRANCHES, the new DRUPAL_REROUTE_EMAIL_* variables).
If you're not yet running Vortex, this is a good release to start on.