Skip to content

build: guard against duplicate @wordpress package installs - #618

Open
dcalhoun wants to merge 2 commits into
trunkfrom
build/guard-wordpress-package-duplicates
Open

build: guard against duplicate @wordpress package installs#618
dcalhoun wants to merge 2 commits into
trunkfrom
build/guard-wordpress-package-duplicates

Conversation

@dcalhoun

@dcalhoun dcalhoun commented Sep 2, 2026

Copy link
Copy Markdown
Member

What?

Guard the single-instance requirement for @wordpress packages: group @wordpress/* Dependabot updates so they move together, and add a CI check that fails when any production @wordpress package has more than one install.

Why?

Plugin scripts reach the bundled packages through window.wp, mirroring WP Admin. That only works while each package is a single module instance. A second copy nested under another dependency brings its own React contexts, data stores, and private APIs, so a plugin receives a different instance than the editor's own components use, with nothing at runtime to signal the split.

Nothing enforced this, and per-package Dependabot bumps have already broken it on trunk: @wordpress/icons resolves to both 11.8.0 and 12.1.0 because commands, components, and preferences were bumped to versions requiring ^12.1.0 while block-editor and editor still require ^11.8.0. Review of #614 raised the same risk for the new @wordpress/theme dependency.

How?

Prevention. Dependabot groups @wordpress/* into a single pull request, scoped to production dependencies. Dev tooling — eslint-plugin, env, prettier-config, dependency-extraction-webpack-plugin — never reaches the bundle, and because a group ships atomically, a breaking tooling major would otherwise hold back every runtime bump alongside it.

Detection. bin/check-wordpress-package-duplicates.js fails when any production @wordpress package has more than one install, exposed as make check-wp-packages and wired into Buildkite and the release target.

package-lock.json is the source of truth rather than the installed tree. It describes what a fresh npm ci produces, so the check needs no node_modules and cannot report on a stale one, and its packages keys are install paths — meaning two copies of the same version, still two module instances, are caught alongside mismatched versions. A lockfile that is unreadable, malformed, or free of @wordpress packages fails rather than reporting that nothing was wrong.

KNOWN_DUPLICATES lets a duplicate through as a warning for cases where the coordinated bump genuinely has to wait, such as a security advisory that moves one package alone. The failure output names the allowlist along with what accepting a split costs, so the exception is taken knowingly; an entry that no longer applies fails the check, so an allowance cannot outlive the mismatch it covers.

docs/code/plugins.md documents the requirement, both safeguards, and the outstanding allowance.

Known duplicate: @wordpress/icons

The only allowlisted entry. No single version satisfies the current tree: block-editor and editor require ^11.8.0, while components, commands, and preferences require ^12.1.0, so raising the direct pin would only flip which copy is nested. Every current latest release agrees on ^15.5.0, so the first grouped Dependabot bump resolves it. That bump is a sizable upgrade (block-editor 15 → 17, components 32 → 40) and the five version-pinned patches under patches/ will need re-verifying, so it should be handled as its own pull request. Remove the allowlist entry there.

Testing Instructions

  1. Run make check-wp-packages. It passes, warning about the known @wordpress/icons duplicate and naming each install path.
  2. Set the @wordpress/icons entry in KNOWN_DUPLICATES to false and rerun. It exits 1, reports the duplicate, and points at the allowlist.
  3. Restore the entry to true, temporarily rename package-lock.json, and rerun. It exits 1 naming the unreadable lockfile rather than reporting that nothing was wrong.

Accessibility Testing Instructions

N/A. No UI changes.

Screenshots or screencast

N/A

🤖 Generated with Claude Code

https://claude.ai/code/session_01W7uwvhYJ8vzNFAZddFoxD2

@dcalhoun dcalhoun added the [Type] Build Tooling Issues or PRs related to build tooling label Sep 2, 2026
@wpmobilebot

wpmobilebot commented Sep 2, 2026

Copy link
Copy Markdown

XCFramework Build

This PR's XCFramework is available for testing. Add the following to your Package.swift:

.package(url: "https://github.com/wordpress-mobile/GutenbergKit", branch: "pr-build/618")

Built from aaf162a

The editor exposes its @WordPress packages on `window.wp` as single
instances, so they have to move together the way a Gutenberg release does.
Bumping them one at a time leaves mismatched ranges that nest duplicate
copies under the packages that moved ahead.

Group them into a single pull request, scoped to production dependencies.
Dev tooling -- eslint-plugin, env, prettier-config, and
dependency-extraction-webpack-plugin -- never reaches the bundle, and since
Dependabot ships a group atomically, a breaking tooling major would
otherwise hold back every runtime bump alongside it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7uwvhYJ8vzNFAZddFoxD2
@dcalhoun
dcalhoun force-pushed the build/guard-wordpress-package-duplicates branch from da8b7bc to a916f90 Compare September 3, 2026 14:35
Plugin scripts reach the bundled packages through `window.wp`, which only
works while each package is a single module instance. `wordPressExternals()`
rewrites source imports to `window.wp` but deliberately skips
`node_modules`, so a copy nested under another dependency is bundled by path
and becomes a second instance, carrying its own React contexts, data stores,
and private APIs. Nothing at runtime reports the split.

Add `make check-wp-packages`, which reads `package-lock.json` and fails when
any production `@wordpress` package has more than one install. The lockfile
is the source of truth rather than the installed tree: it describes what a
fresh `npm ci` produces, needs no `node_modules`, and its `packages` keys are
install paths, so two copies of the same version -- still two module
instances -- are caught alongside mismatched versions. A lockfile that is
unreadable, malformed, or free of `@wordpress` packages fails rather than
reporting that nothing was wrong.

`@wordpress/icons` currently resolves to two versions and is allowed through
`KNOWN_DUPLICATES` as a warning, so the invariant is enforced going forward
rather than blocked on a coordinated bump. An entry that no longer applies
fails the check, so an allowance cannot outlive the mismatch it covers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7uwvhYJ8vzNFAZddFoxD2
@dcalhoun
dcalhoun force-pushed the build/guard-wordpress-package-duplicates branch from a916f90 to aaf162a Compare September 3, 2026 14:38
@dcalhoun dcalhoun changed the title build: fail CI when a @wordpress package resolves to multiple versions build: guard against duplicate @wordpress package installs Sep 3, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've never implemented or seen a check list this, but duplicates can cause cryptic failures so avoiding them is valuable. If this check proves problematic in the future, we can remove it.

@dcalhoun
dcalhoun marked this pull request as ready for review September 3, 2026 14:47
@dcalhoun
dcalhoun requested a review from a team as a code owner September 3, 2026 14:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Type] Build Tooling Issues or PRs related to build tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants