Skip to content

Update the monorepo root and rehearse every plugin's release - #27

Merged
ahegyes merged 18 commits into
trunkfrom
fix/monorepo-root
Nov 11, 2026
Merged

ahegyes merged 18 commits into
trunkfrom
fix/monorepo-root

Conversation

@ahegyes

@ahegyes ahegyes commented Nov 11, 2026 •

Copy link
Copy Markdown
Owner

Summary

The monorepo pinned configs and the framework from before their latest changes, no workflow had ever built or run Plugin Check on a plugin's release, and the root's audit found gaps in its CI, its tag handling and its docs. This moves the root and the three plugins to configs b107001 and the framework at 1dfb070 with every change they force, starts each plugin's changelog at an unreleased 2.0.0 block, rehearses every plugin's release on pull requests, runs the shared environment on the newest WordPress and nightly, lints the root and the plugins' workflows, isolates the WordPress listing to Tests, refuses misnamed tags, drops the root's changelog and configs requirement, and corrects the root and plugin docs.

Type of change

  • Bug fix
  • New feature
  • Refactor
  • Build / CI / tooling
  • Documentation
  • Dependency update

Checklist

  • Changelog fragment added (composer changelog:add in the changed plugin's directory): no plugin's behavior changes beyond its unreleased 2.0.0 entries
  • Tests added/updated
  • composer quality-check passes locally in every changed plugin
  • Plugin header and readme.txt updated if runtime requirements changed: no requirement changed

Breaking changes

  • Root scripts: the root's changelog:* scripts, CHANGELOG.md and changelog/ are gone; lint:all, test:all and quality-check:all also run every plugin's checks.
  • Check names: "PHP Lint (…)" is "Lint PHP (…)" and runs changelog:validate, which no longer has a job of its own; "Scripts and styles lint (…)" is "Lint scripts and styles (…)" over the root and each plugin; Release Rehearsal joins.
  • Tags: a tag naming no plugin, or a <slug>/ tag that misses vMAJOR.MINOR.PATCH, fails.
  • Reports: each plugin's npm bugs, README and SECURITY.md send issues and vulnerability reports to this repository.

The monorepo pinned configs at 8d53efd, before configs' scoping,
workflow, baseline and docs branches, and each plugin locked the
framework mirrors from before the framework's CI, runtime and docs
branches, so the plugins built and tested against tooling and a
framework upstream no longer has. Every lock also held
symfony/filesystem 7.4.21, whose advisory GHSA-6prc-h956-hxjq has failed
the Supply-Chain Audit on trunk since 2026-11-09.

Configs moves to b107001 in the 53 workflow references, the four npm
pins and every lock, and the framework to 1dfb070's split in each
plugin's lock. Every other package moves to its newest release
(symfony 7.4.23, PHPStan 2.3.5, PHPUnit 13.4.6, WooCommerce 11.2.2 and
its stubs, Groups 4.9.0, serializable-closure 2.2.0 and the @wordpress/*
minors, whose minimums rise to them), with TypeScript held on 6. The
wp-env sites move to WordPress 7.1.4, and their $schema to the wp-env
URL that resolves.

The moves force six changes:
- WooCommerceRequirement::check() is static, in LO's and LPM's
  compositions.
- The kernel no longer lists the components it resolved, so the boot
  rows and LPM's strategy rows read the components it skipped.
- PHPStan's strict rules refuse a constant of unknown type as an
  operand, so WP_CLI, XMLRPC_REQUEST and REST_REQUEST compare with true.
- Configs' Playwright base reads tests/EndToEnd, so the root's and each
  plugin's end-to-end folder takes that name.
- Configs' release declares id-token and attestations, which each
  plugin's release caller grants.
- A rejected setting's message also says the other changes were saved.

Assisted-by: Claude Code:claude-opus-5-5
The monorepo's .editorconfig files asked for JSON indented with two
spaces and plain-text files with CRLF line endings, while the manifests
and wp-env configs used four spaces, the Composer manifests and
tsconfigs two, and the PHPStan profiles four. Configs' .editorconfig,
which every repository copies, indents every file but YAML with tabs
four columns wide and keeps LF line endings everywhere (X190, X314).

The root and each plugin take configs' .editorconfig, and their tracked
JSON and NEON files, npm's locks included, are re-indented with tabs.
The change is whitespace only: git diff -w is empty outside the
.editorconfig files, and each JSON file parses to the same value.
Composer's locks keep four spaces, which Composer always writes.

Assisted-by: Claude Code:claude-opus-5-5
Each plugin's entry reads Version: 2.0.0, while its CHANGELOG.md opens
at its last 1.x release and its 2.0.0 entries wait as fragments in
changelog/. Configs' release refuses a version whose changelog holds no
section for it, so every rehearsal of a plugin's first release, and the
release itself, would fail.

Each CHANGELOG.md gains a "2.0.0 - unreleased" block holding the
plugin's v2-framework entry: Linked Orders and Internal Comments move
theirs out of its fragment, and Locked Payment Methods, which had none,
takes Linked Orders' sentence, which describes its boot as well. The
other fragments stay pending, and the first release dates the block
with changelogger's --amend, which folds them in (X365). A plain
changelog:write would now open a 3.0.0 block above it, so each
changelog's header and each AGENTS.md command line send the release to
the monorepo's CONTRIBUTING.md, as the template's header sends it to
its own.

Assisted-by: Claude Code:claude-opus-5-5
Each plugin's release workflow runs on its tag, a pull request or a
dispatch, but only on its mirror, which takes no pull requests, and no
root workflow called configs' release. So Plugin Check, the archive
build, the version, changelog and header checks and the test of the
built artifact had never run for any plugin, and would first run on
its 2.0.0 tag, which the split forwards and never moves again.

A root Release Rehearsal workflow runs configs' release for each
plugin on pull requests and pushes to trunk, with wp.org's full checks
for a plugin with a readme.txt, the plugins' excluded checks and
publish off, granting every scope the release declares (X130). The
mirrors keep publishing on forwarded tags.

Assisted-by: Claude Code:claude-opus-5-5
The root kept a changelog with a hand-written 1.0.0 block, changelogger
and a fragments folder, but the monorepo itself is never released: its
only tags are the plugins' <slug>/vX.Y.Z, so root fragments were
validated and never written. Quality also validated changelogs in a
job of its own, where the framework validates them inside the lint job,
which it names "Lint PHP".

The root's CHANGELOG.md, changelog/, changelogger, its scripts and
config and its quality-check step go, and CONTRIBUTING and the pull
request template ask for fragments only in the changed plugin (X158).
Each plugin's "Lint PHP" job runs changelog:validate after its PHPCS
and PHPStan, and the separate changelog job goes (X270).

Assisted-by: Claude Code:claude-opus-5-5
The root required wordpress-configs through its own vcs repository and
allowed its PHPCS and PHPStan installers, yet nothing in the root reads
it: the plugins lint and scope with their own copies, and the root's
workflows call configs by reference. The root also kept a platform-check
override the plugins need and it does not, since it ships no code, and
its test:integration:ci split the test:integration pair it belongs
after.

The requirement, its repository and the two installers' allow-plugins
entries go, composer/installers staying for WooCommerce's install path,
and composer audit --locked keeps the advisories check (X236). The
platform-check override goes, and test:integration:ci follows
test:integration:run (X271).

Assisted-by: Claude Code:claude-opus-5-5
plugins.yml listed WordPress's versions in the job every workflow
needs, so one failed call to the version API stopped the split, the
tag forwarding and every lane, and it called the API once, without a
retry. A tag starting with a plugin's slug but missing the release
pattern split nothing and reported success, as did a tag naming no
plugin, and a plugin's directory could drift from its text domain,
which the template's workflows read as its slug.

The versions move into a job of their own, which runs only when a
caller asks for it, as Tests does, and calls the API with the
framework's retry flags (X235's A453, X370). The plugins job refuses a
tag that names no listed plugin and a plugin whose extra.text-domain
differs from its directory, and the split refuses a plugin's tag that
misses the release pattern (A454, A455).

Assisted-by: Claude Code:claude-opus-5-5
The shared environment ran only on its pinned WordPress, never on a
newer release or WordPress trunk, so a coexistence break there would
surface only once the pin moved. The root's end-to-end spec and its
Playwright config were never linted or type-checked. The root's "all"
commands left out every plugin's script, style and type checks, and CI
installed the plugins with npm install, which rewrites a lock that has
drifted instead of refusing it.

Tests runs the shared Integration and end-to-end lanes on the newest
WordPress the plugins list, and the nightly runs both on WordPress
trunk (X234's A449). The root gains lint:scripts and lint:types, with
the template's ESLint config, a tsconfig over tests/EndToEnd and the
typescript and @types/node they need, and Quality's script lint covers
the root; its Playwright config takes the template's shape, which
Prettier accepts, and the spec references Node's types (A450). lint:all,
test:all and quality-check:all also run each plugin's checks, through
a plugins:lint fan-out over the template's lint script, which each
plugin gains (A451). CI installs the plugins through
plugins:packages-install:ci and setup:ci, which run npm ci (A452).

Assisted-by: Claude Code:claude-opus-5-5
Each plugin's workflows become its mirror's own at the split, yet the
root's Workflow Checks ran only on changes under .github/workflows and
linted only the root's, so a change to a plugin's workflows reached its
mirror unchecked; a change to dependabot.yml or another .github file
skipped them too. Dependabot's Actions entry listed /plugins/*, where
it reads only a directory's own workflow files, so it never updated
the plugins' action pins.

Workflow Checks run on changes under .github and every plugin's
.github/workflows, and pass the plugins' workflows to actionlint
through configs' extra-workflows input, zizmor already auditing every
workflow in the repository (X154, X310, X275's A705); AGENTS.md drops
the limitation that recorded the gap. Dependabot's Actions entry names
each plugin's .github/workflows (X131). The schedule's comment takes
the framework's one line (X237's A459).

Assisted-by: Claude Code:claude-opus-5-5
The root's Quality, Tests and Supply-Chain Audit had no concurrency
group, so every push to a pull request queued its full matrix beside
the runs it made stale. CodeQL analysed only the workflows, not the
TypeScript of the shared spec and the plugins' scripts and specs. The
audit passed npm flags configs now defaults to, and its and the split's
comments restated a schedule and named a hash in lower case.

Quality, Tests and the audit take the template's concurrency group,
which cancels only a pull request's earlier runs, so trunk runs always
finish (X275's A704). CodeQL also analyses JavaScript and TypeScript
(X293). The audit drops its flags for configs' identical default
(X274), and the comments take the template's and the framework's
wording (X237's A459).

Assisted-by: Claude Code:claude-opus-5-5
tests/Fixtures/multi-plugin-smoke/ held only a .gitkeep, reserved for a
fixture plugin the shared environment would mount once a second plugin
landed. All three plugins have landed, and the shared environment
mounts them and WooCommerce with no fixture, so the folder reserved
nothing.

The folder and its AGENTS.md layout line go; a fixture plugin adds its
folder with it when one is needed (X157).

Assisted-by: Claude Code:claude-opus-5-5
The shared Integration run left out Locked Payment Methods' groups and
wc-memberships groups, and AGENTS.md gave as the reason that the shared
environment installs neither plugin. That holds for Groups, mapped only
in the plugin's own environments, but the Memberships rows run on the
plugin's own stub of WooCommerce Memberships in every environment, so
the shared run skipped rows it can run.

The shared run excludes only the groups group, and AGENTS.md names
Groups alone (X237's A460).

Assisted-by: Claude Code:claude-opus-5-5
Both issue forms offered only Linked Orders, so a report about Internal
Comments or Locked Payment Methods had to pick "Other". Their runtime
area still named the "Plugin singleton" the plugins no longer have, the
CI area named three of the workflows, and the version placeholders
suggested releases below the plugins' floors.

Both forms list the three plugins, the runtime and CI areas take the
template's wording, and the version placeholders read x.y.z, which no
release outdates (X237's A458, X280's A718).

Assisted-by: Claude Code:claude-opus-5-5
The root's .gitignore covered no wp-env override, though wp-env 12
merges a .wp-env.override.json, or a <name>.override.json beside a
named config, from a developer's own machine. Its toptal block, and
each plugin's, had also lost the two carriage returns of macOS's
Icon\r\r rule, so the rule matched any file named Icon and left a stray
blank line.

The root ignores .wp-env*.override.json above the toptal block (X282's
A724), and the root's and every plugin's block is toptal's again, byte
for byte, as the template's is (X199's A233).

Assisted-by: Claude Code:claude-opus-5-5
AGENTS.md's convention 2 listed what names each plugin as "one line
each", which the shared spec's boot row, the README table, the layout
tree and the issue forms do not follow and did not name, and the shared
environment mapped the plugins alphabetically where every other list
reads Linked Orders, Internal Comments, Locked Payment Methods. The
layout said the root's lanes are the template's, though they also lint
the root and run the shared environment, and convention 1 said a plugin
keeps "the template's files", which hides what it drops. Convention 3
allocated ports in pairs, leaving no place for the multisite ports, and
convention 4 forbade the option-key migration Locked Payment Methods'
2.0.0 makes. Neither the release step's home nor the root's
lint:all and quality-check:all were named.

Convention 2 names every list a plugin joins, and the mappings follow
the plugins' order (X261's A649). The layout and convention 1 state
what the root lanes and plugin directories hold (A650), and convention
4 keeps option and meta keys unless a major version migrates them
without losing data, with changelog and Upgrade Notice lines (A651).
Convention 3 allocates ports in threes: 8820 shared, 8821 the root's
multisite, 8822 to 8827 the three plugins' pairs and 8828 to 8830 their
multisite ports, and a new plugin the next free three, from 8831
(X273's A697). Convention 5 points at CONTRIBUTING.md's release step
(A694), and Commands lists lint:all and quality-check:all (X234's A451).

Assisted-by: Claude Code:claude-opus-5-5
The root's CONTRIBUTING and README sent a release to "the plugin's own
CONTRIBUTING.md", which no plugin has, since the plugins drop the
template's for the monorepo's. So the release step was written nowhere:
not the changelog write and readme sync, not the first release's amend
of the 2.0.0 - unreleased block, which a plain write would leave below
a new version, not the version headers the release checks, and not
that the release waits for the mirror's runs.

CONTRIBUTING's Releases section holds the step: the changelog write,
the first release's two commands run apart, since Composer hands the
arguments after -- to both of changelog:write's, the version headers,
the wait for the mirror's Quality and Tests runs, and the signed
<slug>/v<version> tag the split forwards (X273's A694, X365). README's
Releases section points there.

Assisted-by: Claude Code:claude-opus-5-5
The mirrors are read-only and take no issues, yet each plugin's npm
bugs field and SECURITY.md advisory link named its mirror, and no
plugin's README said where to report, so a report reached a repository
that refuses it. Internal Comments' SECURITY.md also kept the
template's line about plugins generated from it, and said "the
template" where it meant the plugin.

Each plugin's bugs field and advisory link name the monorepo, whose
private vulnerability reporting is on, Internal Comments' template
leftovers go, and each README ends by saying the plugin is developed in
the monorepo, where issues and pull requests go (X262's A652 and A653,
X273's A694). The READMEs' lint step runs the plugin's lint script.

Assisted-by: Claude Code:claude-opus-5-5
The root's audit script ran a bare composer audit and npm audit, so it
failed on an advisory among npm's development tools, which the gate
reports through Dependabot instead, and on a locked package marked
abandoned, which the gate only reports; it also resolved the manifest
rather than auditing the lock CI audits.

The script runs composer audit --locked --abandoned=report and npm
audit --omit=dev --audit-level=high, the gate's defaults (X199's A231,
X319).

Assisted-by: Claude Code:claude-opus-5-5
@ahegyes ahegyes changed the title Fix/monorepo root Update the monorepo root and rehearse every plugin's release Nov 11, 2026
@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

@ahegyes
ahegyes merged commit bbb99e4 into trunk Nov 11, 2026
93 of 100 checks passed
@ahegyes
ahegyes deleted the fix/monorepo-root branch November 11, 2026 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants