Skip to content

Support for testing unmerged upstream changes.#11809

Open
desrosj wants to merge 4 commits into
WordPress:trunkfrom
desrosj:add/support-for-mutable-assets
Open

Support for testing unmerged upstream changes.#11809
desrosj wants to merge 4 commits into
WordPress:trunkfrom
desrosj:add/support-for-mutable-assets

Conversation

@desrosj
Copy link
Copy Markdown
Member

@desrosj desrosj commented May 12, 2026

Allows the value of gutenberg.sha in package.json to be either a Git SHA (current behavior) or a mutable tag (trunk, release-X.Y, wp-X.Y, pr-<N>) published by the Gutenberg build-plugin-zip workflow.

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 src can be ignored, they were committed by the workflow that ensures all built file changes are included because the upstream PR was created from trunk and this repo is currently pinned to the wp/7.0 branch.

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.

desrosj added 2 commits May 12, 2026 14:16
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.
@github-actions
Copy link
Copy Markdown

Test using WordPress Playground

The 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

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@desrosj desrosj marked this pull request as ready for review May 12, 2026 18:29
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 12, 2026

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 props-bot label.

Unlinked Accounts

The 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:

Props desrosj, westonruter.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@westonruter
Copy link
Copy Markdown
Member

I'm going to push up a commit that adds the JS files to what is checked by TypeScript.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.

Comment thread package.json
Comment on lines 9 to 11
"gutenberg": {
"sha": "c15cef1d6b07f666df28dac0383bafb0edfe0914",
"sha": "pr-78211",
"ghcrRepo": "WordPress/gutenberg/gutenberg-wp-develop-build"
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.

This is only here for testing purposes to demonstrate that specifying a mutable tag is supported and works as expected.

Comment thread src/wp-includes/blocks/home-link.php
Comment thread src/wp-includes/blocks/page-list.php
Comment thread src/wp-includes/blocks/navigation-link.php
Comment thread src/wp-includes/blocks/navigation-submenu.php
Comment thread src/wp-includes/blocks/button.php
Comment thread src/wp-includes/blocks/home-link.php
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>
Comment on lines -188 to +196
response.body,
Readable.fromWeb(
/** @type {import('stream/web').ReadableStream} */ ( response.body )
),
zlib.createGunzip(),
Writable.toWeb( tar.stdin ),
tar.stdin,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

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.

Admittedly I'm not too familiar either. had worked with @dmsnell on this part. Do you have any thoughts here, @dmsnell?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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:

  1. response.body was the DOM ReadableStream, not Node's. tsconfig.json sets "lib": [ "es2020", "dom" ] (the other files in the files list — code-editor.js etc. — need DOM types). So the global fetch/Response come from lib.dom.d.ts, and response.body is typed as the WHATWG/browser ReadableStream<Uint8Array>. But @types/node's pipeline overloads want either Node Readable-likes or Node's own stream/web.ReadableStream. Those two ReadableStream declarations are structurally similar but not the same — Node's has values() / [Symbol.asyncIterator] that the DOM one lacks — so no pipeline overload accepted response.bodyTS2769: No overload matches this call. The Writable.toWeb wrapper on the destination did nothing to fix this, because the failing argument was the source.

  2. response.body is ReadableStream<Uint8Array> | null. Response.body is nullable, so even ignoring the DOM/Node split, passing it straight to pipeline is 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
);
  • ReadableGunziptar.stdin matches a clean, well-typed pipeline overload. No Writable.toWeb, no destination conversion at all.
  • The /** @type {import('stream/web').ReadableStream} */ cast bridges the DOM-vs-Node ReadableStream declaration gap that Update class class-pclzip.php #1 created. It's sound at runtime: Node's global fetch is undici, and its Response.body really is a Node-compatible web ReadableStream that Readable.fromWeb accepts — the mismatch is purely a TypeScript-declarations artifact of compiling with lib: ["dom"]. Scoping the cast to that one boundary is the minimal correct move; we can't drop "dom" from lib because the other files in tsconfig rely 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 confusing pipeline failure.

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.

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.

3 participants