Developer Setup
How to get a working local setup for contributing to the Groups Initiative — both the Drupal module (developed in a DDEV site) and the documentation site (Astro/Starlight).
Prerequisites
Section titled “Prerequisites”- Git
- For the Drupal module: DDEV and Docker, plus a Drupal 11 DDEV site
(docroot
web). The module requires PHP 8.3+; the reference site here runs PHP 8.4. - For the docs site: Node.js 24 (the current LTS — Astro 7 requires
>=22.12, and CI, the repo (.nvmrc), and the local toolchain all standardize on 24) and npm.
Clone the repository
Section titled “Clone the repository”git clone git@git.drupal.org:project/groupsdrupalorg.gitcd groupsdrupalorgSSH host gotcha — The Git SSH host is
git.drupal.org, notgit.drupalcode.org(that’s the web UI host). Cloning or pushing with the wrong host fails as a connection timeout on port 22 — it looks like a firewall problem, not an authentication error. Drupal.org also requires SSH for pushes; HTTPS pushes are rejected.
Working on an issue
Section titled “Working on an issue”Contributions are tied to an issue and use Drupal’s shared issue-fork workflow rather than personal forks:
- Each issue gets a fork named
issue/groupsdrupalorg-<issue-number>. - Branch names are
<issue-number>-short-slug, branched off1.x. - A maintainer merges the resulting merge request.
For commit-message format, squashing, and merge-request conventions, see Contribution Standards.
See the Drupal docs on GitLab issues for drupal.org projects for the full workflow.
Install the docs toolchain
Section titled “Install the docs toolchain”cd docsnpm installDrupal module — local setup (DDEV)
Section titled “Drupal module — local setup (DDEV)”This is a module-only repository — you develop it inside your own Drupal 11 site. The
recommended loop mounts your module working copy into a DDEV site so you edit it in place from
its own clone (no copy, no composer require).
Mount the module into a DDEV site
Section titled “Mount the module into a DDEV site”In your Drupal 11 DDEV site (docroot web), add a Docker Compose override that bind-mounts
your module clone into web/modules/custom/groupsdrupalorg, then restart:
# .ddev/docker-compose.groupsdrupalorg.yaml (in your DDEV SITE, not the module repo)services: web: volumes: - "/absolute/path/to/your/groupsdrupalorg:/var/www/html/web/modules/custom/groupsdrupalorg"ddev restart- Machine name is
groupsdrupalorg. The mount target directory must be namedgroupsdrupalorg(matching the module machine name / project slug), or Drupal won’t find it. - A bind-mount — not a copy, not a host symlink. A host symlink into the site dangles
inside the container (which only sees the project directory); the Compose bind-mount is what
makes the working copy visible in-container. The host’s
web/modules/custom/stays empty — the mount exists inside the container. - Don’t
composer require drupal/groupsdrupalorgin the site while the mount is active. It lives inmodules/custom/so Composer’s installer-paths (which managemodules/contrib/) don’t fight the working copy.
Enable it
Section titled “Enable it”ddev drush pm:install groupsdrupalorg # install + enableddev drush pm:list --status=enabled | grep groupsdrupalorgddev drush pm:uninstall groupsdrupalorg # when you're doneRun the module’s checks
Section titled “Run the module’s checks”The module ships phpcs.xml.dist and phpstan.neon (both scoped to the module) and a PHPUnit
suite under tests/, run through the DDEV site (which supplies the Drupal test tooling —
drupal/core-dev, drupal/coder, mglaman/phpstan-drupal; a standard drupal.org dev site has
these, else add them to the site’s require-dev).
One-time setup. The module ships DDEV commands (check:all / check:phpcs / check:phpstan
/ check:phpunit). Register them in your DDEV site once, from the site root, pointing at
your module clone. The bind-mount is container-only — the site’s own web/modules/custom/ is
empty on the host — so copy (or symlink) from where you cloned the module, not via a relative
path inside the site:
mkdir -p .ddev/commands/web
# copy (simplest):cp /path/to/your/groupsdrupalorg/.ddev/commands/web/check-* .ddev/commands/web/
# …or symlink to your clone so they auto-update as the module changes:# ln -sf /path/to/your/groupsdrupalorg/.ddev/commands/web/check-* .ddev/commands/web/ddev picks them up on the next run (no restart needed) — verify with ddev -h.
Then run any of them from the site:
ddev check:all # PHPCS + PHPStan + PHPUnit — the same checks CI enforcesddev check:phpcs # coding standards only (Drupal, DrupalPractice)ddev check:phpstanddev check:phpunit # e.g. ddev check:phpunit --filter SettingsPageTest(The colon is a naming convention — DDEV takes each command name from its ## Usage: line, so
the shipped filenames stay colon-free for Windows/WSL2.)
Without that setup, the raw equivalents (what the commands wrap) are:
ddev exec "cd web/modules/custom/groupsdrupalorg && /var/www/html/vendor/bin/phpcs"ddev exec "cd web/modules/custom/groupsdrupalorg && /var/www/html/vendor/bin/phpstan analyse -c phpstan.neon"ddev exec "SIMPLETEST_DB=mysql://db:db@db/db vendor/bin/phpunit -c web/core/phpunit.xml.dist web/modules/custom/groupsdrupalorg/tests"The same PHPCS / PHPStan / PHPUnit checks run in CI on every merge request (the authoritative gate) and are wired into the Git hooks below.
Browser tests (Playwright) are a separate, later addition — tracked in issue #3578772.
Enable the Git hooks with lefthook (one-time)
Section titled “Enable the Git hooks with lefthook (one-time)”The repo uses lefthook as its single Git-hook manager. The hooks validate docs content before you commit or push. Install lefthook once, then enable the hooks per clone:
# 1. Install the lefthook binary (pick one):brew install lefthook # macOS/Homebrew# or: go install github.com/evilmartians/lefthook@latest# or run it via npx (no install): use `npx lefthook` in place of `lefthook` below
# 2. Release the legacy hooks path (only needed if you set it previously), then install:git config --unset core.hooksPath # ignore "not set" if it was never configuredlefthook installAfter that:
- pre-commit runs a fast, staged-only Keystatic editability check over any staged
.md/.mdocdocs content (check:keystatic --staged). It no-ops instantly on commits that touch no docs content. - pre-push runs the full docs build —
npm run build(astro check+check:keystatic+astro build+ internal link validation + content gates) — and blocks the push if it fails, the same checks CI runs. Pushes that don’t touchdocs/skip it.
Notes:
- The docs checks require the docs dependencies (
npm install, above). - To bypass the hooks for a single commit or push:
git commit --no-verify/git push --no-verify. Local hooks are a convenience; CI is the authoritative gate. - The same
lefthook.ymlalso runs the module PHP checks — phpcs at pre-commit, PHPStan + PHPUnit at pre-push — on staged/pushed module files. These need the module’s dev dependencies present (composer installin the module repo); the hook skips them gracefully when the tools aren’t installed.
Set up the AI coding pipeline (optional)
Section titled “Set up the AI coding pipeline (optional)”The repo ships a test-first coding pipeline for AI coding agents, with a per-story
review-rigor dial — base / second-opinion / panel. The base rung needs no setup.
For second-opinion or panel, bind your two models once with the setup script — the
Author (your own coding model) and the Critic (the independent outside reviewer, reached
through LiteLLM, so it can be any provider, including a fully local one):
.claude/scripts/setup-pipeline.sh # prompts for your models + key; writes .env (git-ignored).env is git-ignored and never committed. See Running the Pipeline
for the full walkthrough — the providers, local models, reasoning-model tuning, and how an agent
drives each rung.
Edit the docs
Section titled “Edit the docs”See Editing These Docs for how to run the live-reloading dev server and add or edit pages.
Run the checks manually
Section titled “Run the checks manually”From the docs/ directory:
npm run build # full build: astro check + check:keystatic + link validation (what pre-push and CI run)npm run check # astro check only (types + frontmatter), no buildnpm run check:keystatic # Keystatic editability gate only (Layer 1 round-trip + Layer 2 static)Internal links and heading anchors are validated here and block the build. External links are checked separately in CI (non-blocking) and reported on the Link Health Report page.
Troubleshooting
Section titled “Troubleshooting”A page 500s in dev (AstroUserError), especially after a config change
Section titled “A page 500s in dev (AstroUserError), especially after a config change”The dev server hot-reloads content edits (.md/.mdx) fine, but changes to
astro.config.mjs — the sidebar, logo/favicon, or a component override — and newly
added dependencies are not always picked up by a running server. The module graph can
go stale and start returning AstroUserError 500s on some routes (often a page that didn’t
exist when the server started), even though npm run build passes.
Fix: restart the dev server. Stop it (Ctrl-C) and run npm run dev again.
If it still errors after a clean restart, clear Astro’s caches and restart:
rm -rf .astro node_modules/.vitenpm run devRule of thumb: after editing astro.config.mjs, restart npm run dev rather than relying
on hot reload. If a build passes but dev shows an error, it’s almost always a stale server,
not the page.
Further reading
Section titled “Further reading”- Drupal.org documentation — the main Drupal docs hub.
- GitLab issues for drupal.org projects — the full issue-fork and merge-request workflow used here.
- GitLab help — documentation for the drupal.org GitLab instance itself.
