Skip to content

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', then ddev poser --prefer-lowest --prefer-stable.
  • Current: ddev core-version ^11.
  • Next major: CI's next-major jobs run Drupal 12. _LENIENT_ALLOW_LIST in .gitlab-ci.yml names 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.