Skip to content

v1.6.0

Choose a tag to compare

@github-actions github-actions released this 07 Jul 20:12
· 39 commits to main since this release

What's Changed

Added

  • Drupal coding standards via drupal/coder (phpcs) — new phpcs.xml.dist running the Drupal + DrupalPractice rulesets over src/, tests/ and the module files; drupal/coder promoted to an explicit require-dev entry; composer phpcs / composer phpcbf aliases; CI's composer hygiene job gains a phpcs step. The initial sweep fixed all 172 pre-existing violations at the source — 82 via phpcbf, 90 by hand (property @var docblocks, empty/short doc comments filled with real descriptions, comment rewraps to ≤80 chars, missing @param definitions, three genuinely-unused variable assignments dropped while keeping their side-effectful calls) — zero suppressions, no phpcs:ignore anywhere. Full suite (347 tests) and PHPStan level 8 stay green.
  • Release automation: release-stamp.yml + release.yml — the two-workflow pattern from parisek/timber-kit. Stamp (manual workflow_dispatch with a semver input) validates the version + non-empty [Unreleased], runs the full test + PHPStan suite as a release gate, stamps the CHANGELOG, commits, pushes an annotated tag and cross-dispatches Release. Release (tag push / manual re-run / cross-dispatch) builds GitHub Release notes from the tag's CHANGELOG section + the (#N) squash-merge PR list between tags, and marks Latest only for the highest semver. Adapted for this repo: checkout@v4, SHA-pinned setup-php, the kernel-test prerequisites (gd/pdo_sqlite + scripts/dev-link-module.sh) inside the stamp gate, and no registry-sync step (consumers install via vcs — the pushed tag is immediately consumable, per RELEASING.md). Nothing fires without a manual dispatch or tag push.
  • AGENTS.md: TDD-non-negotiable + feature-flag doctrine — two sections ported from parisek/timber-kit and adapted: (1) test-first discipline stated as doctrine (failing test first, bug fixes reproduce as regression tests, lowest-tier-first with the decision tree in CONTRIBUTING.md, pristine output under the existing failOnRisky/failOnWarning PHPUnit flags); (2) behavior-changing features ship opt-in default-off — protected bool flags on the consumer-subclassed ComponentBase/DisplayBase, $params-key opt-ins on container services, breaking changes allowed only behind such opt-ins, opinionated defaults expressed downstream in drupal-base/site projects rather than in library defaults.
  • docs/adr/ — Architecture Decision Records — Nygard-triad ADRs (Context / Decision / Consequences), numbered permanently, written sparingly (hard-to-reverse + surprising-without-context + real-trade-off, all three). docs/ is git-ignored repo-wide with only the adr/ subtree tracked, so scratch planning docs never enter history. Ships with ADR 0001 recording the deliberate no-composer.lock policy (drift-detection over reproducibility, contained by the platform.php pin and the CI PHP matrix + hygiene job). Doctrine ported from parisek/timber-kit.
  • RELEASING.md — release doctrine — tag-driven flow adapted to this package's no-Packagist distribution (consumers install via a vcs repository entry, so a pushed annotated tag is immediately consumable): semver procedure, Conventional Commits → bump mapping table, a Public API surface definition specific to this package (service IDs + public methods, ComponentBase/DisplayBase overridables, the Twig function/filter surface, documented data shapes; container-wired constructor signatures explicitly excluded), and a Deprecation lifecycle (docblock-only @deprecated, no runtime notices in request-serving paths, ≥ one MINOR grace period, live deprecations table — currently empty). README gains a short ## Releasing section pointing at it. Ported from parisek/timber-kit and adapted.
  • Status-report warning when menu.language_tree_manipulator is missing on a multilingual site (#90) — since the MenuTreeBuilder extraction, the language manipulator (shipped by the Drupal core patch from #2466553) is an optional dependency and menu language filtering silently no-ops when the service is absent. New hook_requirements() runtime check in custom_components.install makes the gap visible on /admin/reports/status: on a multilingual site it reports Available (REQUIREMENT_OK) when the service exists and a REQUIREMENT_WARNING with a link to the core issue when it doesn't; monolingual sites get no entry (filtering is irrelevant there). Three kernel tests pin the contract: non-runtime phases report nothing, monolingual sites report nothing, multilingual sites without the service warn with the service name in the description.
  • Auto-typography translation helpers _xt / __t / _nt / _nxt (#87) — typography-aware twins of the existing _x / __ / _n / _nx Twig functions. Each translates first, then pipes the result through the typography filter, so editors get curly quotes / non-breaking spaces / dewidowing on translated UI strings with a one-character opt-in (_x(…)|typography_xt(…)). Registered on TwigExtension with needs_environment (the typography filter is resolved from the environment at call time, so this extension stays decoupled from the sibling TypographyExtension that provides it) and is_safe: ['html'] (mirrors the filter's own safety flag — no double-escaping of the markup it returns). Signatures match the WordPress originals 1:1 so the same templates render across parisek/styleguide (#21), parisek/timber-kit (#42) and Drupal: _xt($text, $context, $domain), __t($text, $domain), _nt($single, $plural, $number, $domain), _nxt($single, $plural, $number, $context, $domain). The $domain argument has no Drupal analogue (translations are keyed by langcode, not text domain), so it is accepted for cross-CMS parity and otherwise ignored; __t / _nt carry no context (matching WP), while _xt / _nxt forward it via t() / formatPlural() options. If the typography filter is absent the helpers degrade to a plain translation rather than throwing. Six unit tests assert translate-then-typography compose order, context forwarding, plural selection, the no-filter fallback, and end-to-end is_safe (no double-escaping) through a real Twig render. Drupal side of parisek/styleguide#21.

Changed

  • PHPStan raised to level 8 (was 5) — the max-rigor level parisek/timber-kit runs. The 264 pre-existing findings are grandfathered in a regenerated phpstan-baseline.neon (dominant categories: missing param/return typehints ~137, missing iterable value types ~57 — routine follow-up, entry by entry); new code baselines nothing and is held to level 8. The 8 call to undefined method object::… findings were fixed, not baselinedTaxonomyTreeBuilder now narrows loadTree(..., TRUE) results with an instanceof TermInterface guard and MenuActiveTrailResolver guards createInstance() results with instanceof MenuLinkInterface — so that whole error class stays live for future typos instead of hiding in the baseline. mglaman/phpstan-drupal was already active via phpstan/extension-installer auto-discovery (no wiring change needed).
  • CI: PHP 8.3/8.4 matrix + composer-hygiene job — the test job now runs on a fail-fast: false matrix of PHP 8.3 and 8.4 (coverage + the ratchet threshold stay on the 8.3 floor leg only; the 8.4 leg proves the suite passes on the newer runtime before consumers hit it). A separate composer hygiene job runs composer validate --strict, composer audit --abandoned=report (security advisories fail the job, abandoned transitive packages only report) and composer normalize --dry-run; ergebnis/composer-normalize joins require-dev + allow-plugins and composer.json is normalized once to establish the canonical shape. Pattern ported from parisek/timber-kit tests.yml.
  • CI: Conventional-Commits lint on PR titles — new commitlint.yml workflow (amannn/action-semantic-pull-request, SHA-pinned to the v5 line) gates every PR title against the feat/fix/docs/chore/refactor/perf/test/ci/build/revert taxonomy that AGENTS.md documents but nothing previously enforced. PRs squash-merge with the title as the commit subject, so the title is what the future release bump-mapping reads. Scope optional. Pattern ported from parisek/timber-kit.
  • composer.json: scripts aliases + config.platform.php pincomposer test / test:unit / test:kernel / phpstan aliases so contributors and docs stop spelling vendor/bin/... paths, and config.platform.php: 8.3.0 so dependency resolution targets the package's PHP floor even on newer dev machines (the repo deliberately ships no composer.lock, so every composer install/update re-resolves — the pin keeps that resolution honest against the >=8.3 requirement). Pattern ported from parisek/timber-kit.
  • Distribution trimmed via .gitattributes export-ignorecomposer require parisek/custom-components previously shipped the full tracked tree (tests/, .github/, .ddev/, scripts/, CHANGELOG, AGENTS/CLAUDE/CONTRIBUTING, phpstan/phpunit configs) into consumers' vendor/ because the repo had no tracked .gitattributes — the local one is generated by drupal/core-composer-scaffold and was .gitignored. Now a tracked .gitattributes export-ignores everything development-only, so the dist archive carries just the module files (custom_components.*), src/, templates/, composer.json, LICENSE, README.md. Scaffold generation of the file is disabled via extra.drupal-scaffold.file-mapping so it no longer collides with the tracked copy. Pattern ported from parisek/timber-kit.
  • Issue references stripped from source comments — the builder docblocks (MenuTreeBuilder, TaxonomyTreeBuilder, MediaArrayBuilder), TwigExtension::getResizer() and the custom_components.services.yml resizer note referenced repo issue numbers (#6, #44), violating the AGENTS.md comment doctrine ("Don't reference PRs, issues, or call sites in code; those belong in commit messages / CHANGELOG"). The WHY content stays; only the issue-number pointers are gone. Version references (removed in v1.4.0) remain — they resolve against this CHANGELOG, not the issue tracker.
  • CI: symfony/runtime added to allow-plugins, claude-code-review workflow removed — the repo intentionally ships no composer.lock, so CI resolves fresh dependencies every run; the current resolution (Drupal core 11.4.x / Symfony 7.4.x) transitively pulls symfony/runtime, whose Composer plugin was not in config.allow-plugins, aborting composer install before any test ran. Allow-listed (official Symfony package). The claude-code-review.yml workflow is removed — it failed on missing auth secrets and review runs on demand instead; the interactive claude.yml workflow stays.

Fixed

  • MediaArrayBuilder::buildRemoteVideo() no-resolver fallback resolves the wrong field (#89) — the fallback called buildImage($media), which reads the media's own source field via getSourceFieldValue(); for an oembed remote video that is the video URL string, not a file ID, so File::load() failed and the image key came back empty instead of the field_media_image thumbnail. The fallback now reads field_media_image's File references directly via a new protected buildImageField() helper that mirrors EntityHelper::getImageField()'s return shape (single item unwraps, multiple items return a list) minus translation handling — which is exactly what the docblock always claimed the fallback did. Two new kernel tests pin the behavior with real entities (a file-source media type whose source file deliberately differs from field_media_image): the fallback returns the thumbnail (not the source file), and its output is identical to the EntityHelper::getImageField resolver path. Consumers going through the EntityHelper facade were never affected (it always passes the resolver); only direct custom_components.media_array_builder consumers hit the bug.

Pull Requests

  • #85 — docs(readme): drop Packagist + PHP-version badges
  • #86 — docs: drop Packagist-dependent install instructions + URL
  • #88 — feat(twig): auto-typography translation helpers _xt/__t/_nt/_nxt (#87)
  • #91 — fix(media): resolve field_media_image in buildRemoteVideo no-resolver fallback (#89)
  • #92 — feat(install): status-report warning when language tree manipulator is missing (#90)
  • #93 — chore: strip issue references from source comments
  • #94 — chore(ci): allow symfony/runtime composer plugin, drop claude-code-review workflow
  • #95 — chore(dist): trim composer dist archive via tracked .gitattributes export-ignore
  • #96 — chore(composer): add scripts aliases and platform.php pin
  • #97 — ci: PHP 8.3/8.4 matrix + composer hygiene job (audit, normalize)
  • #98 — ci: enforce Conventional Commits on PR titles
  • #99 — docs: add RELEASING.md — semver, public API surface, deprecation lifecycle
  • #100 — docs: add ADR directory with doctrine and ADR 0001 (no composer.lock)
  • #101 — docs(agents): add TDD-non-negotiable and feature-flag doctrine
  • #102 — chore(phpstan): raise level 5 -> 8 with regenerated baseline
  • #103 — ci: add release-stamp and release workflows
  • #104 — chore(phpcs): adopt drupal/coder with zero-suppression initial sweep

Full Changelog: v1.5.0...v1.6.0