Development¶
The repository is a DDEV project built with ddev-drupal-contrib.
ddev start
ddev poser
ddev symlink-project
ddev exec "cd web/core && yarn install"
ddev drush site:install standard -y
Run ddev symlink-project again after adding a top-level file or directory.
Content Deployment comes from its git repository, through a vcs entry in
composer.json's repositories, until it has a release.
Real providers¶
The Anthropic, Gemini, OpenRouter and Cloudflare AI Gateway provider modules
are dev dependencies, for manual runs only. Put the keys in the gitignored
.ddev/.env and run ddev restart:
| Variable | Used by |
|---|---|
ANTHROPIC_API_KEY |
Anthropic, through a Key entity with the Environment key provider |
GEMINI_API_KEY |
Gemini Provider, the same way |
OPENROUTER_API_KEY |
OpenRouter, the same way |
CF_ACCOUNT_ID, CF_API_KEY, CF_GATEWAY |
Cloudflare AI Gateway Provider, through settings.php |
The Cloudflare provider reads its gateway from a cloudflare_gateway entity
and its account and token from a cloudflare_credentials set resolved from
settings.php. On the dev site those are site_agent and ai_gateway, with:
$settings['cloudflare']['credentials']['ai_gateway'] = [
'account_id' => (string) getenv('CF_ACCOUNT_ID'),
'token' => (string) getenv('CF_API_KEY'),
];
$config['cloudflare_ai.gateway.site_agent']['gateway_slug'] = (string) getenv('CF_GATEWAY');
Checks¶
| Check | Command |
|---|---|
| PHPUnit | ddev phpunit |
| JavaScript stream regressions | node --test tests/js/chat-stream.test.cjs |
| PHP_CodeSniffer (Drupal, DrupalPractice) | ddev phpcs, ddev phpcbf to fix |
| PHPStan | ddev phpstan |
| CSpell, as the GitLab CI job runs it | ddev cspell |
| ESLint | ddev eslint |
| Stylelint | ddev stylelint |
FunctionalJavascript tests use the Selenium container from ddev-selenium-standalone-chrome.
Locked-config site¶
tests/e2e/locked-config.sh builds "live", a second site in the DDEV project
at live.site-agent.ddev.site, from a copy of the dev site, with
$settings['config_readonly'] = TRUE;, and checks that config-write tools are
offered on dev and absent on live. It then runs a scripted structure session,
tests/e2e/structure-session.php, which adds a field to a content type and
places it: on dev, config:export must write exactly the field storage, the
field and the two displays; on live, every call must be refused. Last, on
live, a scripted Editor session, tests/e2e/editor-session.php, has an editor
change a page with Content Deployment's tools: the edit must run without a
prompt and leave a draft, and publishing it as the card's Publish does must
make it live. web/ isn't in the repository, so the script builds the site
again on every run. Address the live site with
ddev drush -l live.site-agent.ddev.site.
Core versions¶
- Floor:
ddev core-version '~11.3.0', thenddev poser --prefer-lowest --prefer-stable. - Current:
ddev core-version ^11. - Next major: CI's next-major jobs run Drupal 12.
_LENIENT_ALLOW_LISTin.gitlab-ci.ymlnames the dependencies that don't declare it yet; drop each one when its release does.
require-dev constrains two packages the module doesn't use itself, so that
--prefer-lowest skips releases that can't run: composer/installers 1.0.0,
allowed by the CI template's ^1 || ^2, doesn't support the drupal-core
package type, and grasmash/yaml-cli 3.2.0, allowed by every Drush 13 release,
is incompatible with Symfony Console 7.
Composer refuses releases with security advisories, so the floor runs the lowest 11.3 release without one: 11.3.17 on 2026-10-01.