Support for testing unmerged upstream changes.#11809
Conversation
This adds support for testing mutable tags and pull requests published by the GitHub repository. This allows contributors to test unmerged changes from upstream, or to pin a mutable tag locally (`trunk` or `wp/7.0, for example) to test the latest changes as they are made.
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Unlinked AccountsThe following contributors have not linked their GitHub and WordPress.org accounts: @1178653+wordpress-develop-pr-bot[bot]@users.noreply.github.com. Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases. Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
|
I'm going to push up a commit that adds the JS files to what is checked by TypeScript. |
There was a problem hiding this comment.
Pull request overview
This PR extends the Gutenberg download/verification tooling so contributors can temporarily point package.json:gutenberg.sha at mutable GHCR tags (e.g. trunk, release-X.Y, pr-<N>) and have the scripts resolve them to the underlying immutable commit SHA for verification and downloads. The PR also contains a large set of synced Gutenberg build artifacts under src/ (noted in the PR description as workflow-generated).
Changes:
- Add support for mutable Gutenberg refs by resolving the expected commit SHA via GHCR manifest annotations.
- Refactor GHCR token/manifest fetching into shared utilities and update the download script to prefer immutable SHA tags with fallback.
- Update multiple bundled/build artifacts under
src/from upstream Gutenberg.
Reviewed changes
Copilot reviewed 46 out of 51 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/gutenberg/utils.js | Adds config parsing for mutable refs and GHCR manifest resolution helpers; makes version verification async. |
| tools/gutenberg/download.js | Resolves mutable tags to immutable SHAs for downloads; refactors GHCR token/manifest fetch logic. |
| src/wp-includes/theme.json | Updates theme.json settings (generated upstream sync). |
| src/wp-includes/images/icon-library/tab.svg | Updates bundled SVG icon (generated upstream sync). |
| src/wp-includes/build/routes/registry.php | Adds/updates route registry entries (generated upstream sync). |
| src/wp-includes/build/routes/font-list/content.min.asset.php | Updates asset metadata hash/version (generated upstream sync). |
| src/wp-includes/build/routes/font-list/content.js | Updates bundled route JS build (generated upstream sync). |
| src/wp-includes/build/routes/connectors-home/content.min.asset.php | Updates asset metadata hash/version (generated upstream sync). |
| src/wp-includes/build/routes.php | Adds additional page-specific route registration functions (generated upstream sync). |
| src/wp-includes/build/pages/options-connectors/page.php | Adjusts preload fields and adds boot dependency filter (generated upstream sync). |
| src/wp-includes/build/pages/options-connectors/page-wp-admin.php | Adjusts preload fields and adds boot dependency filter (generated upstream sync). |
| src/wp-includes/build/pages/font-library/page.php | Adjusts preload fields and adds boot dependency filter (generated upstream sync). |
| src/wp-includes/build/pages/font-library/page-wp-admin.php | Adjusts preload fields and adds boot dependency filter (generated upstream sync). |
| src/wp-includes/build/pages.php | Switches to conditional require_once list for pages (generated upstream sync). |
| src/wp-includes/build/constants.php | Updates build constants version (generated upstream sync). |
| src/wp-includes/blocks/site-title/block.json | Moves textAlign support into typography supports (generated upstream sync). |
| src/wp-includes/blocks/site-tagline/block.json | Moves textAlign usage into typography style and enables typography textAlign support (generated upstream sync). |
| src/wp-includes/blocks/search/block.json | Removes deprecated attr and adds selectors for style application (generated upstream sync). |
| src/wp-includes/blocks/search.php | Adjusts class/style application and uses raw search query in attribute value (generated upstream sync). |
| src/wp-includes/blocks/query-title/block.json | Moves textAlign support into typography supports (generated upstream sync). |
| src/wp-includes/blocks/query-title.php | Docblock parameter name tweak (generated upstream sync). |
| src/wp-includes/blocks/pullquote/block.json | Updates dimensions support configuration (generated upstream sync). |
| src/wp-includes/blocks/post-title/block.json | Moves textAlign support into typography supports; adds placeholder attr (generated upstream sync). |
| src/wp-includes/blocks/post-template.php | Adds responsive grid class when minimumColumnWidth present (generated upstream sync). |
| src/wp-includes/blocks/post-navigation-link/block.json | Moves textAlign support into typography supports (generated upstream sync). |
| src/wp-includes/blocks/post-featured-image.php | Simplifies overlay class assignment (generated upstream sync). |
| src/wp-includes/blocks/post-excerpt/block.json | Moves textAlign support into typography supports (generated upstream sync). |
| src/wp-includes/blocks/post-date/block.json | Moves textAlign support into typography supports (generated upstream sync). |
| src/wp-includes/blocks/page-list.php | Refactors font size CSS helper usage (generated upstream sync). |
| src/wp-includes/blocks/navigation.php | Expands shortcodes before parsing overlay template part blocks; removes inline submenu icon helper (generated upstream sync). |
| src/wp-includes/blocks/navigation-submenu.php | Refactors shared helper includes + submenu icon rendering (generated upstream sync). |
| src/wp-includes/blocks/navigation-link/block.json | Adds selectors.states mapping for current item (generated upstream sync). |
| src/wp-includes/blocks/navigation-link.php | Refactors shared helper includes, font-size helper usage, submenu icon rendering, and hook type (generated upstream sync). |
| src/wp-includes/blocks/loginout.php | Adds block-theme styling classes to login form submit input (generated upstream sync). |
| src/wp-includes/blocks/list-item/block.json | Disables HTML support (generated upstream sync). |
| src/wp-includes/blocks/latest-posts.php | Adjusts rel attribute on “Read more” link (generated upstream sync). |
| src/wp-includes/blocks/icon.php | Docblock simplification (generated upstream sync). |
| src/wp-includes/blocks/home-link.php | Refactors shared font-size helper usage (generated upstream sync). |
| src/wp-includes/blocks/group/block.json | Adds gradient background + minWidth dimension supports (generated upstream sync). |
| src/wp-includes/blocks/cover.php | Uses str_contains() (polyfilled) for provider detection (generated upstream sync). |
| src/wp-includes/blocks/button/block.json | Moves width to dimensions support + selectors config (generated upstream sync). |
| src/wp-includes/blocks/button.php | Adds server-side width handling for button wrapper (generated upstream sync). |
| src/wp-includes/blocks/blocks-json.php | Updates generated PHP block metadata (generated upstream sync). |
| src/wp-includes/blocks/accordion-item/block.json | Adds padding support under spacing (generated upstream sync). |
| src/wp-includes/blocks/accordion-item.php | Removes keydown handler binding (generated upstream sync). |
| src/wp-includes/assets/script-modules-packages.php | Updates script-module package metadata (generated upstream sync). |
| src/wp-includes/assets/script-loader-packages.php | Updates script-loader package metadata/deps (generated upstream sync). |
| package.json | Sets Gutenberg ref to a mutable tag for testing and retains GHCR repo config. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "gutenberg": { | ||
| "sha": "c15cef1d6b07f666df28dac0383bafb0edfe0914", | ||
| "sha": "pr-78211", | ||
| "ghcrRepo": "WordPress/gutenberg/gutenberg-wp-develop-build" |
There was a problem hiding this comment.
This is only here for testing purposes to demonstrate that specifying a mutable tag is supported and works as expected.
Adds tools/gutenberg/download.js and tools/gutenberg/utils.js to tsconfig.json's
"files" list so `npm run typecheck:js` covers them, and resolves the resulting
TypeScript errors:
- tsconfig.json: add "node" to compilerOptions.types -- these are Node CLI
scripts and need Node's ambient types (process, __dirname, require('fs'),
etc.).
- package.json: add "@types/node": "20.19.41" as a devDependency. The version
hoisted transitively via webpack-dev-server was 14.14.20, which lacks
stream/promises, Writable.toWeb, and other Node 15+ APIs used here. Pinning
to v20 matches .nvmrc (20) and engines.node (>=20.10.0).
- download.js / utils.js: fix strict-mode type errors:
* Catch variables are "unknown" under "strict" -- added inline JSDoc casts
such as /** @type {Error} */ ( error ).message, and
/** @type {NodeJS.ErrnoException} */ where .code is read.
* fetchManifest's @return was Promise<Object>; "Object" has no index
signature, so .annotations / .layers errored. Changed to
Promise<Record<string, any>> (and the matching "manifest:" field in
resolveDownloadManifest's return type).
* The custom .status property set on a "new Error()" is now typed as
Error & { status?: number }.
* "new Promise( ( resolve ) => ... resolve() )" needed a contextual hint --
added /** @type {Promise<void>} */ on "const tarDone".
* The pipeline() call mixed a web ReadableStream (response.body, from the DOM
lib) with Node streams. Switched to Readable.fromWeb( response.body ) (with
a cast to import('stream/web').ReadableStream) -> gunzip -> tar.stdin, all
Node streams, dropping the Writable.toWeb() wrapper, and added an
"if ( ! response.body )" guard so the non-null narrowing holds.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| response.body, | ||
| Readable.fromWeb( | ||
| /** @type {import('stream/web').ReadableStream} */ ( response.body ) | ||
| ), | ||
| zlib.createGunzip(), | ||
| Writable.toWeb( tar.stdin ), | ||
| tar.stdin, |
There was a problem hiding this comment.
These changes relate to APIs I'm not familiar with. According to Claude's commit:
* The pipeline() call mixed a web ReadableStream (response.body, from the DOM lib) with Node streams. Switched to Readable.fromWeb( response.body ) (with a cast to import('stream/web').ReadableStream) -> gunzip -> tar.stdin, all Node streams, dropping the Writable.toWeb() wrapper, and added an "if ( ! response.body )" guard so the non-null narrowing holds.
There was a problem hiding this comment.
I asked Claude to provide a full explanation of why it made these changes:
Short version: the old line wasn't necessarily a runtime bug — stream/promises's pipeline() can cope with a mixed web/Node chain at runtime — but it was type-incorrect under this project's tsconfig, and it was converting the wrong end of the pipe. Here's the breakdown.
What the old code did
const { Writable } = require( 'stream' );
...
await pipeline(
response.body, // source
zlib.createGunzip(), // transform
Writable.toWeb( tar.stdin ), // destination
);response.body is a web ReadableStream; zlib.createGunzip() is a Node Transform; tar.stdin is a Node Writable. The author wrapped the destination in Writable.toWeb() — presumably to make the destination "web-shaped" like the source. But the middle stage is a Node stream regardless, so the pipeline was always going to be heterogeneous; converting the destination to web didn't actually buy a consistent web pipeline, it just added a conversion.
Why TypeScript rejected it
Two distinct problems:
-
response.bodywas the DOMReadableStream, not Node's.tsconfig.jsonsets"lib": [ "es2020", "dom" ](the other files in thefileslist —code-editor.jsetc. — need DOM types). So the globalfetch/Responsecome fromlib.dom.d.ts, andresponse.bodyis typed as the WHATWG/browserReadableStream<Uint8Array>. But@types/node'spipelineoverloads want either NodeReadable-likes or Node's ownstream/web.ReadableStream. Those twoReadableStreamdeclarations are structurally similar but not the same — Node's hasvalues()/[Symbol.asyncIterator]that the DOM one lacks — so nopipelineoverload acceptedresponse.body→TS2769: No overload matches this call. TheWritable.toWebwrapper on the destination did nothing to fix this, because the failing argument was the source. -
response.bodyisReadableStream<Uint8Array> | null.Response.bodyis nullable, so even ignoring the DOM/Node split, passing it straight topipelineis a type error (and a latent runtime hazard if a response ever has no body).
There was also a third, transient one: with the pre-existing @types/node@14, Writable.toWeb didn't exist in the types at all (toWeb landed in Node 17) → TS2339: Property 'toWeb' does not exist on type 'typeof Writable'. Bumping to @types/node@20 made that go away, but #1 and #2 remained.
Why the fix goes the other direction
Instead of dragging the Node Writable into web-land, convert the one genuinely-web value into a Node stream, so the whole pipeline is Node streams:
if ( ! response.body ) {
throw new Error( 'Blob response has no body' ); // narrows to non-null, real error if absent
}
...
await pipeline(
Readable.fromWeb(
/** @type {import('stream/web').ReadableStream} */ ( response.body )
),
zlib.createGunzip(), // Node Transform
tar.stdin, // Node Writable
);Readable→Gunzip→tar.stdinmatches a clean, well-typedpipelineoverload. NoWritable.toWeb, no destination conversion at all.- The
/** @type {import('stream/web').ReadableStream} */cast bridges the DOM-vs-NodeReadableStreamdeclaration gap that Update class class-pclzip.php #1 created. It's sound at runtime: Node's globalfetchis undici, and itsResponse.bodyreally is a Node-compatible webReadableStreamthatReadable.fromWebaccepts — the mismatch is purely a TypeScript-declarations artifact of compiling withlib: ["dom"]. Scoping the cast to that one boundary is the minimal correct move; we can't drop"dom"fromlibbecause the other files intsconfigrely on it. - The
if ( ! response.body )guard does double duty: satisfies the non-null requirement and turns a "shouldn't happen" into an explicit error rather than a confusingpipelinefailure.
So "wrong" really means: typed against the wrong ReadableStream, nullable source passed unchecked, and the stream conversion applied to the end of the pipe that didn't need it. Runtime behavior is equivalent (possibly identical); the rewrite just makes it type-correct and removes the unnecessary toWeb round-trip.
Allows the value of
gutenberg.shainpackage.jsonto be either a Git SHA (current behavior) or a mutable tag (trunk,release-X.Y,wp-X.Y,pr-<N>) published by the Gutenbergbuild-plugin-zipworkflow.While the committed value should always remain a SHA, the mutable-tag form is intended for use by contributors when needing to test an unmerged pull request from https://github.com/wordpress/gutenberg, or looking to always track the latest changes from a given stream.
Note: the changes in
srccan be ignored, they were committed by the workflow that ensures all built file changes are included because the upstream PR was created fromtrunkand this repo is currently pinned to thewp/7.0branch.Trac ticket: Core-65224.
Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code
Model(s): Opus 4.7
Used for: Initial analysis and PR.
This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.