Search by

Reusable Drupal module for pre-release QA gate checks and Drush reporting.

Package info

gitlab.com/Proton.Systems/drupal/qa

Issues

Type:drupal-module

pkg:composer/protonsystems/qa

Statistics

Installs: 137

Dependents: 1

Suggesters: 0

Stars: 0

v2.13.0 2026-10-08 21:40 UTC

README

Reusable Drupal module for pre-release QA checks, release gating, and Drush reporting.

Purpose

The module is intended to be reused across Drupal 10 and Drupal 11 projects by installing it as a Composer package and enabling it in the target site.

Requirements

  • PHP 8.3+
  • Drupal 10 or 11
  • No extra package is required for shared scanners; this package bundles the qa_audit_core submodule

Features

  • Runs a consistent pre-release QA gate through drush qa:run
  • Supports full runs or targeted check selection with --checks=<ids>
  • Produces table output for operators and JSON output for automation
  • Auto-saves timestamped reports to docs/report/ by default
  • Reads shipped defaults from qa.config.yml and project overrides from qa.settings without a cache rebuild

Available Checks

  • config_drift: verifies active configuration matches config/sync
  • prod_split: validates expected production and development config split state
  • forbidden_modules: blocks dev-only modules from production releases
  • performance_hardening: validates cache, asset, Twig, and error-level production settings
  • storage_config: verifies public/temporary (and, if configured, private) stream wrappers are registered and writable, and that $settings['config_sync_directory'] is set and points at an existing directory
  • recent_errors: reads the dblog watchdog table and fails on error-level entries in a lookback window (default 24h), grouped by type and message with counts and last location — surfaces the real cause behind Drupal's generic "The website encountered an unexpected error" page
  • migration_integrity: detects invalid translation source rows and broken entity references with field-level breakdowns
  • translation_completeness: reports translation coverage, missing languages, and sample untranslated nodes
  • orphaned_content: finds stale unpublished content and configurable content lacking menu or taxonomy relationships
  • broken_links: validates entity links, internal paths, and sampled external URLs
  • missing_required_fields: reports published content missing required or configured critical fields
  • orphaned_paragraphs: surfaces orphan paragraph findings through the shared qa_audit_core service layer
  • render_smoke: runs sampled render validation across selected bundles
  • navigation: verifies menus are driven by Drupal: required menus have an enabled block in the default theme, links are not broken, unpublished or untranslated, content types do not offer restricted menus (admin, account) to editors, and the theme's layout templates contain no hardcoded hrefs
  • module_configuration: verifies that a registered list of contrib modules (e.g. pathauto, honeypot, antibot, simple_sitemap) are installed and properly configured, not just enabled
  • interface_translations: on multilingual sites with locale enabled, reads the translation status cache (populated by drush locale:check or the translations report cron job) and warns when a language has updates available, a project has no translation file at all, or the status is stale past max_status_age_hours (default 168). "en" is never reported as missing a translation file — interface strings are authored in English, so no project ships one upstream, and translate_english exists precisely to let a site override its own English strings locally via config. Use ignored_projects to skip specific projects entirely, e.g. a small contrib module with no upstream translation for a language the site otherwise supports — a permanent upstream gap a re-check will never resolve

Extending module_configuration

module_configuration delegates to one validator service per module, tagged qa.module_config_validator (see src/ReleaseQa/ModuleConfig/ModuleConfigValidatorInterface.php). Shipped validators live in src/ReleaseQa/ModuleConfig/Validator/:

  • PathautoValidator: at least one enabled pattern exists; optionally checks specific node bundles are covered; warns when published nodes (per language) in pattern-covered bundles have no URL alias (max_missing_aliases, sample_limit), listing the offenders and the command to generate them
  • BackupMigrateValidator: fails when no schedule is enabled, a schedule has no period or points at a missing source/destination, a destination uses public://, an unregistered stream wrapper (typically private:// with no file_private_path) or an unwritable/in-webroot directory, or when no backup file exists; warns when the newest backup is older than max_backup_age_days, a schedule keeps every backup, or a source listed in required_sources is not scheduled
  • HoneypotValidator: fails if honeypot provides no protection at all (time limit is 0, "protect all forms" is off, and no forms are individually protected); warns on a low time limit or missing required forms
  • AntibotValidator: warns if Antibot isn't applied to any forms, or to specific required forms
  • SimpleSitemapValidator: fails if no content is indexed for the sitemap; warns if cron generation is disabled
  • MetatagValidator: fails if the global default is missing or lacks title/canonical_url; warns when the front page or content ends up without a description, title or canonical URL (inheritance from global is respected), or the Open Graph / Twitter cards submodules are off
  • SchemaMetatagValidator: warns when no schema_* tag is configured on any default (the module is enabled but outputs no JSON-LD), or when site-level (Organization/WebSite) or content (Article/WebPage) structured data is missing. Site-level tags belong on the front page default — search engines read them from the homepage only; setting them on global repeats them on every page and is reported as a note, not a warning. Options: organization_types (require e.g. LocalBusiness), require_content_schema (disable for sites with nothing to mark up), note_global_site_tags. Configuration only; it does not render a page

To add another module to the checked list:

  1. Create a class implementing ModuleConfigValidatorInterface in src/ReleaseQa/ModuleConfig/Validator/. getModuleName() returns the module's machine name; validate() returns a ReleaseQaCheckResult (pass/warn/fail) and is only called once that module is installed.
  2. Register it in qa.services.yml tagged qa.module_config_validator (follow the existing qa.module_config.* entries).
  3. Optionally add options.<module_name> defaults under module_configuration in qa.config.yml.

No changes to ModuleConfigurationCheck itself are needed — it discovers validators through the tagged iterator.

A module with a validator that simply isn't installed is skipped (not a failure) unless it's listed in required_modules.

Installation

composer require protonsystems/qa
drush en qa

This package bundles the reusable qa_audit_core submodule so release QA and other ProtonSystems tools can share audit services without a third Composer project.

Usage

# Run all enabled checks.
drush qa:run

# Run a selected subset of checks.
drush qa:run --checks=config_drift,prod_split,performance_hardening

# Emit JSON to stdout and write a specific artifact file.
drush qa:run --format=json --output=tmp/qa/latest.json

# Fail on warnings as well as failures.
drush qa:run --fail-on-warn

The command alias drush release-qa:run is also available.

Configuration

The module ships package defaults in qa.config.yml at the module root. Projects should override them through the qa.settings Drupal config, either through config sync or the admin form at /admin/config/development/qa. The admin form exposes each check separately, with an enable/disable override and a YAML field for that check's default options.

checks:
  broken_links:
    enabled: false
    defaults:
      max_links: 200

Rules:

  • Shipped qa.config.yml values act as defaults for every project
  • qa.settings overrides are merged on top of those defaults per check
  • enabled: false skips the check during an unfiltered drush qa:run
  • --checks=<id> always runs the named check even if it is disabled in YAML
  • defaults: values act as baselines and CLI options always override them
  • Config changes are read at runtime, so no cache rebuild is required
  • enabled accepts boolean values from config sync and Drush, including false and 'false'
  • Some checks expose complex list/mapping defaults, so the per-check override field remains YAML rather than one bespoke widget per option

Useful options:

  • --fail-on-warn: return a non-zero exit code for warnings
  • --checks=<ids>: comma-separated subset of checks
  • --format=table|json: choose human or machine output
  • --output=<path>: write the JSON report to a specific location
  • --save-report=0|1: control automatic report export to docs/report/
  • --sample-size=<n>, --max-bundles=<n>, --bundles=<ids>: render smoke scope overrides
  • --max-broken-refs=<n>, --max-invalid-translations=<n>: migration integrity thresholds
  • --max-links=<n>: broken link scan limit

Reports

When --save-report is enabled, qa:run writes a timestamped JSON report to docs/report/qa-YYYY-MM-DD_HHMMSS.json. Passing --output=<path> overrides the default report location.

For the report format and examples, see docs/report/README.md.

Releasing A New Version On Packagist

Packagist publishes versions of protonsystems/qa from git tags. Do not add a version field to composer.json; create and push a new release tag instead.

For the first public release, submit the public Git repository URL to Packagist. If you want automatic updates from GitLab, configure the Packagist integration in GitLab under Settings > Integrations using your Packagist username and API token.

Prepare the release commit on main:

git checkout main
git pull origin main
composer validate --no-check-publish --strict
git status
git add <files>
git commit -m "Release v1.0.0"
git push origin main

Create and push an annotated tag for the release:

git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0

Use semantic version tags such as v1.0.0. After the tag is pushed, Packagist should detect the new version automatically. If it does not appear, open the package page in Packagist and trigger a manual update.

Notes

  • The bundled qa_audit_core submodule ships with this package and does not require a separate Packagist release