From 26c603c0d15c32bdfc643f3b66ca1e8ade70ab90 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 12 Jan 2026 11:50:39 +0100 Subject: [PATCH 1/8] Build: Improve Gutenberg integration workflow. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit improves the Gutenberg build integration in several ways: 1. Adds a new `gutenberg:sync` script that follows the same pattern as `install-changed` - storing a hash of the built ref in `.gutenberg-hash` and only rebuilding when the ref changes. This ensures that when the `gutenberg.ref` in package.json changes (e.g., after `git pull`), the next build will automatically rebuild Gutenberg. 2. Updates `postinstall` to run the full checkout + build + copy flow, so `npm install` produces a working development environment. 3. Adds conditional `file_exists()` checks around the PHP files from `wp-includes/build/` so WordPress doesn't fatal error if these files don't exist yet. 4. Restores Gutenberg's `package.json` after the build completes using `git checkout`, keeping the Gutenberg checkout clean. 5. Removes the `gutenberg:integrate` script and `gutenberg-integrate` grunt task since their functionality is now handled by `gutenberg:sync`. This simplifies the developer workflow and restores a similar flow to how package dependencies worked before - where `npm install` handles everything and subsequent builds are fast because they skip the already- built Gutenberg. See #64393. šŸ¤– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .gitignore | 1 + Gruntfile.js | 20 ++-- package.json | 4 +- src/index.php | 2 +- src/wp-admin/font-library.php | 2 +- src/wp-admin/index.php | 2 +- src/wp-includes/blocks.php | 4 + src/wp-includes/blocks/index.php | 21 +++- src/wp-includes/formatting.php | 5 + src/wp-includes/script-loader.php | 3 +- src/wp-includes/script-modules.php | 3 +- src/wp-settings.php | 20 +++- tools/gutenberg/build-gutenberg.js | 20 ++++ tools/gutenberg/sync-gutenberg.js | 149 +++++++++++++++++++++++++++++ 14 files changed, 235 insertions(+), 21 deletions(-) create mode 100644 tools/gutenberg/sync-gutenberg.js diff --git a/.gitignore b/.gitignore index 901a775c5af23..330a92ca02c7b 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,7 @@ wp-tests-config.php /src/wp-includes/class-wp-block-parser-frame.php /src/wp-includes/theme.json /packagehash.txt +/.gutenberg-hash /artifacts /setup.log /coverage diff --git a/Gruntfile.js b/Gruntfile.js index 457ddccab2c19..a6635fc3428e5 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1458,10 +1458,16 @@ module.exports = function(grunt) { } ); } ); - grunt.registerTask( 'gutenberg-integrate', 'Complete Gutenberg integration workflow.', [ - 'gutenberg-build', - 'gutenberg-copy' - ] ); + grunt.registerTask( 'gutenberg-sync', 'Syncs Gutenberg checkout and build if ref has changed.', function() { + const done = this.async(); + grunt.util.spawn( { + cmd: 'node', + args: [ 'tools/gutenberg/sync-gutenberg.js' ], + opts: { stdio: 'inherit' } + }, function( error ) { + done( ! error ); + } ); + } ); grunt.registerTask( 'copy-vendor-scripts', 'Copies vendor scripts from node_modules to wp-includes/js/dist/vendor/.', function() { const done = this.async(); @@ -1896,7 +1902,8 @@ module.exports = function(grunt) { grunt.task.run( [ 'build:js', 'build:css', - 'gutenberg-integrate', + 'gutenberg-sync', + 'gutenberg-copy', 'copy-vendor-scripts', 'build:certificates' ] ); @@ -1906,7 +1913,8 @@ module.exports = function(grunt) { 'build:files', 'build:js', 'build:css', - 'gutenberg-integrate', + 'gutenberg-sync', + 'gutenberg-copy', 'copy-vendor-scripts', 'replace:source-maps', 'verify:build' diff --git a/package.json b/package.json index efd866faccdea..71f05355f5b70 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "wicg-inert": "3.1.3" }, "scripts": { - "postinstall": "npm run gutenberg:checkout", + "postinstall": "npm run gutenberg:sync && npm run gutenberg:copy -- --dev", "build": "grunt build", "build:dev": "grunt build --dev", "dev": "grunt watch --dev", @@ -126,7 +126,7 @@ "gutenberg:checkout": "node tools/gutenberg/checkout-gutenberg.js", "gutenberg:build": "node tools/gutenberg/build-gutenberg.js", "gutenberg:copy": "node tools/gutenberg/copy-gutenberg-build.js", - "gutenberg:integrate": "npm run gutenberg:checkout && npm run gutenberg:build && npm run gutenberg:copy", + "gutenberg:sync": "node tools/gutenberg/sync-gutenberg.js", "vendor:copy": "node tools/vendors/copy-vendors.js", "sync-gutenberg-packages": "grunt sync-gutenberg-packages", "postsync-gutenberg-packages": "grunt wp-packages:sync-stable-blocks && grunt build --dev && grunt build" diff --git a/src/index.php b/src/index.php index 91c0517857339..e7c4355181817 100644 --- a/src/index.php +++ b/src/index.php @@ -15,7 +15,7 @@ * Load the actual index.php file if the assets were already built. * Note: WPINC is not defined yet, it is defined later in wp-settings.php. */ -if ( file_exists( ABSPATH . 'wp-includes/js/dist/edit-post.js' ) ) { +if ( file_exists( ABSPATH . 'wp-includes/js/dist/edit-post.js' ) && file_exists( ABSPATH . 'wp-includes/build' ) ) { require_once ABSPATH . '_index.php'; return; } diff --git a/src/wp-admin/font-library.php b/src/wp-admin/font-library.php index 5c9fe21264997..370586aad3dcb 100644 --- a/src/wp-admin/font-library.php +++ b/src/wp-admin/font-library.php @@ -22,7 +22,7 @@ if ( ! function_exists( 'font_library_wp_admin_render_page' ) ) { wp_die( '

' . __( 'Font Library is not available.' ) . '

' . - '

' . __( 'The Font Library requires Gutenberg integration. Please run npm run gutenberg:integrate to build the necessary files.' ) . '

', + '

' . __( 'The Font Library requires Gutenberg build files. Please run npm install to build the necessary files.' ) . '

', 503 ); } diff --git a/src/wp-admin/index.php b/src/wp-admin/index.php index 628096844c08b..bd150a0336fc7 100644 --- a/src/wp-admin/index.php +++ b/src/wp-admin/index.php @@ -6,7 +6,7 @@ * please refer to wp-admin/_index.php. */ -if ( file_exists( __DIR__ . '/../wp-includes/js/dist/edit-post.js' ) ) { +if ( file_exists( __DIR__ . '/../wp-includes/js/dist/edit-post.js' ) && file_exists( __DIR__ . '/../wp-includes/build' ) ) { require_once __DIR__ . '/_index.php'; return; } diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index 2a9968608106a..e2c594d7ecfc6 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -2421,6 +2421,10 @@ function parse_blocks( $content ) { */ $parser_class = apply_filters( 'block_parser_class', 'WP_Block_Parser' ); + if ( ! class_exists( $parser_class ) ) { + return array(); + } + $parser = new $parser_class(); return $parser->parse( $content ); } diff --git a/src/wp-includes/blocks/index.php b/src/wp-includes/blocks/index.php index 98615ea1ba766..65d2cb5ad67a3 100644 --- a/src/wp-includes/blocks/index.php +++ b/src/wp-includes/blocks/index.php @@ -13,9 +13,15 @@ define( 'BLOCKS_PATH', ABSPATH . WPINC . '/blocks/' ); // Include files required for core blocks registration. -require BLOCKS_PATH . 'legacy-widget.php'; -require BLOCKS_PATH . 'widget-group.php'; -require BLOCKS_PATH . 'require-dynamic-blocks.php'; +if ( file_exists( BLOCKS_PATH . 'legacy-widget.php' ) ) { + require BLOCKS_PATH . 'legacy-widget.php'; +} +if ( file_exists( BLOCKS_PATH . 'widget-group.php' ) ) { + require BLOCKS_PATH . 'widget-group.php'; +} +if ( file_exists( BLOCKS_PATH . 'require-dynamic-blocks.php' ) ) { + require BLOCKS_PATH . 'require-dynamic-blocks.php'; +} /** * Registers core block style handles. @@ -43,6 +49,9 @@ function register_core_block_style_handles() { static $core_blocks_meta; if ( ! $core_blocks_meta ) { + if ( ! file_exists( BLOCKS_PATH . 'blocks-json.php' ) ) { + return; + } $core_blocks_meta = require BLOCKS_PATH . 'blocks-json.php'; } @@ -150,6 +159,9 @@ static function ( $file ) use ( $normalized_blocks_path ) { * @since 5.5.0 */ function register_core_block_types_from_metadata() { + if ( ! file_exists( BLOCKS_PATH . 'require-static-blocks.php' ) ) { + return; + } $block_folders = require BLOCKS_PATH . 'require-static-blocks.php'; foreach ( $block_folders as $block_folder ) { register_block_type_from_metadata( @@ -169,6 +181,9 @@ function register_core_block_types_from_metadata() { * @since 6.7.0 */ function wp_register_core_block_metadata_collection() { + if ( ! file_exists( BLOCKS_PATH . 'blocks-json.php' ) ) { + return; + } wp_register_block_metadata_collection( BLOCKS_PATH, BLOCKS_PATH . 'blocks-json.php' diff --git a/src/wp-includes/formatting.php b/src/wp-includes/formatting.php index f59f877775b77..b3a5a1ca135b4 100644 --- a/src/wp-includes/formatting.php +++ b/src/wp-includes/formatting.php @@ -5227,6 +5227,11 @@ function wp_pre_kses_less_than_callback( $matches ) { * @return string Filtered text to run through KSES. */ function wp_pre_kses_block_attributes( $content, $allowed_html, $allowed_protocols ) { + // If the block parser isn't available, skip block attribute filtering. + if ( ! class_exists( 'WP_Block_Parser' ) ) { + return $content; + } + /* * `filter_block_content` is expected to call `wp_kses`. Temporarily remove * the filter to avoid recursion. diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 388940e33e1ea..f39fa49ab4b32 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -281,7 +281,8 @@ function wp_default_packages_scripts( $scripts ) { * 'annotations.js' => array('dependencies' => array(...), 'version' => '...'), * 'api-fetch.js' => array(... */ - $assets = include ABSPATH . WPINC . "/assets/script-loader-packages{$suffix}.php"; + $assets_file = ABSPATH . WPINC . "/assets/script-loader-packages{$suffix}.php"; + $assets = file_exists( $assets_file ) ? include $assets_file : array(); foreach ( $assets as $file_name => $package_data ) { $basename = str_replace( $suffix . '.js', '', basename( $file_name ) ); diff --git a/src/wp-includes/script-modules.php b/src/wp-includes/script-modules.php index 85cc4accf2e52..f851d41bf21f2 100644 --- a/src/wp-includes/script-modules.php +++ b/src/wp-includes/script-modules.php @@ -153,7 +153,8 @@ function wp_default_script_modules() { * 'interactivity-router/index.min.js' => array('dependencies' => array(…), 'version' => '…'), * 'block-library/navigation/view.min.js' => … */ - $assets = include ABSPATH . WPINC . "/assets/script-modules-packages{$suffix}.php"; + $assets_file = ABSPATH . WPINC . "/assets/script-modules-packages{$suffix}.php"; + $assets = file_exists( $assets_file ) ? include $assets_file : array(); foreach ( $assets as $file_name => $script_module_data ) { /* diff --git a/src/wp-settings.php b/src/wp-settings.php index adaa0b161c3f6..14749eff51041 100644 --- a/src/wp-settings.php +++ b/src/wp-settings.php @@ -242,8 +242,12 @@ require ABSPATH . WPINC . '/cron.php'; require ABSPATH . WPINC . '/deprecated.php'; require ABSPATH . WPINC . '/script-loader.php'; -require ABSPATH . WPINC . '/build/routes.php'; -require ABSPATH . WPINC . '/build/pages.php'; +if ( file_exists( ABSPATH . WPINC . '/build/routes.php' ) ) { + require ABSPATH . WPINC . '/build/routes.php'; +} +if ( file_exists( ABSPATH . WPINC . '/build/pages.php' ) ) { + require ABSPATH . WPINC . '/build/pages.php'; +} require ABSPATH . WPINC . '/taxonomy.php'; require ABSPATH . WPINC . '/class-wp-taxonomy.php'; require ABSPATH . WPINC . '/class-wp-term.php'; @@ -373,9 +377,15 @@ require ABSPATH . WPINC . '/class-wp-block.php'; require ABSPATH . WPINC . '/class-wp-block-list.php'; require ABSPATH . WPINC . '/class-wp-block-metadata-registry.php'; -require ABSPATH . WPINC . '/class-wp-block-parser-block.php'; -require ABSPATH . WPINC . '/class-wp-block-parser-frame.php'; -require ABSPATH . WPINC . '/class-wp-block-parser.php'; +if ( file_exists( ABSPATH . WPINC . '/class-wp-block-parser-block.php' ) ) { + require ABSPATH . WPINC . '/class-wp-block-parser-block.php'; +} +if ( file_exists( ABSPATH . WPINC . '/class-wp-block-parser-frame.php' ) ) { + require ABSPATH . WPINC . '/class-wp-block-parser-frame.php'; +} +if ( file_exists( ABSPATH . WPINC . '/class-wp-block-parser.php' ) ) { + require ABSPATH . WPINC . '/class-wp-block-parser.php'; +} require ABSPATH . WPINC . '/class-wp-classic-to-block-menu-converter.php'; require ABSPATH . WPINC . '/class-wp-navigation-fallback.php'; require ABSPATH . WPINC . '/block-bindings.php'; diff --git a/tools/gutenberg/build-gutenberg.js b/tools/gutenberg/build-gutenberg.js index 0b989a13495ec..c41fe4be73b95 100644 --- a/tools/gutenberg/build-gutenberg.js +++ b/tools/gutenberg/build-gutenberg.js @@ -156,8 +156,28 @@ async function main() { console.log( `āœ… Build completed in ${ duration }s` ); } catch ( error ) { console.error( 'āŒ Build failed:', error.message ); + // Restore package.json even on failure + await restorePackageJson(); process.exit( 1 ); } + + // Restore Gutenberg's package.json after successful build + await restorePackageJson(); +} + +/** + * Restore Gutenberg's package.json to its original state. + */ +async function restorePackageJson() { + console.log( '\nšŸ”„ Restoring Gutenberg package.json...' ); + try { + await exec( 'git', [ 'checkout', 'package.json' ], { + cwd: gutenbergDir, + } ); + console.log( 'āœ… package.json restored' ); + } catch ( error ) { + console.warn( 'āš ļø Could not restore package.json:', error.message ); + } } // Run main function diff --git a/tools/gutenberg/sync-gutenberg.js b/tools/gutenberg/sync-gutenberg.js new file mode 100644 index 0000000000000..814188d920cfa --- /dev/null +++ b/tools/gutenberg/sync-gutenberg.js @@ -0,0 +1,149 @@ +#!/usr/bin/env node + +/** + * Sync Gutenberg Script + * + * This script ensures Gutenberg is checked out and built for the correct ref. + * It follows the same pattern as install-changed: + * - Stores the built ref in .gutenberg-hash + * - Compares current package.json ref with stored hash + * - Only runs checkout + build when they differ + * + * @package WordPress + */ + +const { spawn } = require( 'child_process' ); +const fs = require( 'fs' ); +const path = require( 'path' ); + +// Paths +const rootDir = path.resolve( __dirname, '../..' ); +const gutenbergDir = path.join( rootDir, 'gutenberg' ); +const gutenbergBuildDir = path.join( gutenbergDir, 'build' ); +const packageJsonPath = path.join( rootDir, 'package.json' ); +const hashFilePath = path.join( rootDir, '.gutenberg-hash' ); + +/** + * Execute a command and return a promise. + * + * @param {string} command - Command to execute. + * @param {string[]} args - Command arguments. + * @param {Object} options - Spawn options. + * @return {Promise} Promise that resolves when command completes. + */ +function exec( command, args, options = {} ) { + return new Promise( ( resolve, reject ) => { + const child = spawn( command, args, { + cwd: options.cwd || rootDir, + stdio: 'inherit', + shell: process.platform === 'win32', + ...options, + } ); + + child.on( 'close', ( code ) => { + if ( code !== 0 ) { + reject( + new Error( + `${ command } ${ args.join( ' ' ) } failed with code ${ code }` + ) + ); + } else { + resolve(); + } + } ); + + child.on( 'error', reject ); + } ); +} + +/** + * Read the expected Gutenberg ref from package.json. + * + * @return {string} The Gutenberg ref. + */ +function getExpectedRef() { + const packageJson = JSON.parse( fs.readFileSync( packageJsonPath, 'utf8' ) ); + const ref = packageJson.gutenberg?.ref; + + if ( ! ref ) { + throw new Error( 'Missing "gutenberg.ref" in package.json' ); + } + + return ref; +} + +/** + * Read the stored hash from .gutenberg-hash file. + * + * @return {string|null} The stored ref, or null if file doesn't exist. + */ +function getStoredHash() { + try { + return fs.readFileSync( hashFilePath, 'utf8' ).trim(); + } catch ( error ) { + return null; + } +} + +/** + * Write the ref to .gutenberg-hash file. + * + * @param {string} ref - The ref to store. + */ +function writeHash( ref ) { + fs.writeFileSync( hashFilePath, ref + '\n' ); +} + +/** + * Check if Gutenberg build exists. + * + * @return {boolean} True if build directory exists. + */ +function hasBuild() { + return fs.existsSync( gutenbergBuildDir ); +} + +/** + * Main execution function. + */ +async function main() { + console.log( 'šŸ” Checking Gutenberg sync status...' ); + + const expectedRef = getExpectedRef(); + const storedHash = getStoredHash(); + + console.log( ` Expected ref: ${ expectedRef }` ); + console.log( ` Stored hash: ${ storedHash || '(none)' }` ); + + // Check if we need to rebuild + if ( storedHash === expectedRef && hasBuild() ) { + console.log( 'āœ… Gutenberg is already synced and built' ); + return; + } + + if ( storedHash !== expectedRef ) { + console.log( '\nšŸ“¦ Gutenberg ref has changed, rebuilding...' ); + } else { + console.log( '\nšŸ“¦ Gutenberg build not found, building...' ); + } + + // Run checkout + console.log( '\nšŸ”„ Running gutenberg:checkout...' ); + await exec( 'node', [ 'tools/gutenberg/checkout-gutenberg.js' ] ); + + // Run build + console.log( '\nšŸ”„ Running gutenberg:build...' ); + await exec( 'node', [ 'tools/gutenberg/build-gutenberg.js' ] ); + + // Write the hash after successful build + writeHash( expectedRef ); + console.log( `\nāœ… Updated .gutenberg-hash to ${ expectedRef }` ); + + console.log( '\nāœ… Gutenberg sync complete!' ); +} + +// Run main function +main().catch( ( error ) => { + console.error( 'āŒ Sync failed:', error.message ); + process.exit( 1 ); +} ); From 9b8e410e4adb65876005d2c3c3406539a367b859 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 12 Jan 2026 23:54:03 +0100 Subject: [PATCH 2/8] Small tweak to restoring package.json Co-authored-by: Miguel Fonseca <150562+mcsf@users.noreply.github.com> --- tools/gutenberg/build-gutenberg.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tools/gutenberg/build-gutenberg.js b/tools/gutenberg/build-gutenberg.js index c41fe4be73b95..cf6dd973953bc 100644 --- a/tools/gutenberg/build-gutenberg.js +++ b/tools/gutenberg/build-gutenberg.js @@ -156,13 +156,11 @@ async function main() { console.log( `āœ… Build completed in ${ duration }s` ); } catch ( error ) { console.error( 'āŒ Build failed:', error.message ); - // Restore package.json even on failure + throw error; + } finally { + // Restore Gutenberg's package.json regardless of success or failure await restorePackageJson(); - process.exit( 1 ); } - - // Restore Gutenberg's package.json after successful build - await restorePackageJson(); } /** @@ -171,7 +169,7 @@ async function main() { async function restorePackageJson() { console.log( '\nšŸ”„ Restoring Gutenberg package.json...' ); try { - await exec( 'git', [ 'checkout', 'package.json' ], { + await exec( 'git', [ 'checkout', '--', 'package.json' ], { cwd: gutenbergDir, } ); console.log( 'āœ… package.json restored' ); From 52fa2d6c983da70d2543c50e1e9ae8014a766667 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 13 Jan 2026 03:29:44 +0100 Subject: [PATCH 3/8] Build: Stop copying .js.map files to wp-includes/js/dist. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The source map files contain path references to the original Gutenberg source files which don't exist in WordPress Core. Since the .js files already have their sourceMappingURL comments stripped, these .map files serve no purpose. šŸ¤– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- tools/gutenberg/copy-gutenberg-build.js | 54 +++++++++---------------- 1 file changed, 20 insertions(+), 34 deletions(-) diff --git a/tools/gutenberg/copy-gutenberg-build.js b/tools/gutenberg/copy-gutenberg-build.js index 7257e5f3b1d8d..a5c3c2a1d8c67 100644 --- a/tools/gutenberg/copy-gutenberg-build.js +++ b/tools/gutenberg/copy-gutenberg-build.js @@ -48,7 +48,7 @@ const COPY_CONFIG = { source: 'scripts', destination: 'js/dist', copyDirectories: true, // Copy subdirectories - patterns: [ '*.js', '*.js.map' ], + patterns: [ '*.js' ], // Rename vendors/ to vendor/ when copying directoryRenames: { vendors: 'vendor', @@ -917,24 +917,21 @@ async function main() { const vendorFiles = fs.readdirSync( src ); let copiedCount = 0; for ( const file of vendorFiles ) { - if ( file.startsWith( 'react-jsx-runtime' ) ) { + if ( + file.startsWith( 'react-jsx-runtime' ) && + file.endsWith( '.js' ) && + ! file.endsWith( '.js.map' ) + ) { const srcFile = path.join( src, file ); const destFile = path.join( dest, file ); fs.mkdirSync( dest, { recursive: true } ); - if ( - file.endsWith( '.js' ) && - ! file.endsWith( '.js.map' ) - ) { - let content = fs.readFileSync( - srcFile, - 'utf8' - ); - content = removeSourceMaps( content ); - fs.writeFileSync( destFile, content ); - } else { - fs.copyFileSync( srcFile, destFile ); - } + let content = fs.readFileSync( + srcFile, + 'utf8' + ); + content = removeSourceMaps( content ); + fs.writeFileSync( destFile, content ); copiedCount++; } } @@ -955,9 +952,7 @@ async function main() { for ( const file of packageFiles ) { if ( - /^index\.(js|js\.map|min\.js|min\.js\.map|min\.asset\.php)$/.test( - file - ) + /^index\.(js|min\.js|min\.asset\.php)$/.test( file ) ) { const srcFile = path.join( src, file ); // Replace 'index.' with 'package-name.' @@ -972,10 +967,7 @@ async function main() { } ); // Apply source map removal for .js files - if ( - file.endsWith( '.js' ) && - ! file.endsWith( '.js.map' ) - ) { + if ( file.endsWith( '.js' ) ) { let content = fs.readFileSync( srcFile, 'utf8' @@ -983,7 +975,7 @@ async function main() { content = removeSourceMaps( content ); fs.writeFileSync( destPath, content ); } else { - // Copy other files as-is + // Copy other files as-is (.min.asset.php) fs.copyFileSync( srcFile, destPath ); } } @@ -991,22 +983,16 @@ async function main() { } } else if ( entry.isFile() && - /\.(js|js\.map)$/.test( entry.name ) + entry.name.endsWith( '.js' ) && + ! entry.name.endsWith( '.js.map' ) ) { // Copy root-level JS files const dest = path.join( scriptsDest, entry.name ); fs.mkdirSync( path.dirname( dest ), { recursive: true } ); - if ( - entry.name.endsWith( '.js' ) && - ! entry.name.endsWith( '.js.map' ) - ) { - let content = fs.readFileSync( src, 'utf8' ); - content = removeSourceMaps( content ); - fs.writeFileSync( dest, content ); - } else { - fs.copyFileSync( src, dest ); - } + let content = fs.readFileSync( src, 'utf8' ); + content = removeSourceMaps( content ); + fs.writeFileSync( dest, content ); } } From a927a03c7eac94c97a5a8a4df3c428bd6ca4a538 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 13 Jan 2026 03:34:26 +0100 Subject: [PATCH 4/8] Build: Update build check to use jquery.js instead of edit-post.js. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The edit-post.js file comes from Gutenberg and may not always be present. jQuery is a more fundamental dependency that's always copied during the build process via Grunt's copy:vendor task. šŸ¤– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.php b/src/index.php index e7c4355181817..b1d1b372913d0 100644 --- a/src/index.php +++ b/src/index.php @@ -15,7 +15,7 @@ * Load the actual index.php file if the assets were already built. * Note: WPINC is not defined yet, it is defined later in wp-settings.php. */ -if ( file_exists( ABSPATH . 'wp-includes/js/dist/edit-post.js' ) && file_exists( ABSPATH . 'wp-includes/build' ) ) { +if ( file_exists( ABSPATH . 'wp-includes/js/jquery/jquery.js' ) && file_exists( ABSPATH . 'wp-includes/build' ) ) { require_once ABSPATH . '_index.php'; return; } From 6fee48ec3104dc2b6e673fcdc67c408210a71775 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 15 Jan 2026 13:29:39 +0100 Subject: [PATCH 5/8] Update build tool --- package.json | 2 +- src/wp-admin/font-library.php | 4 ++-- src/wp-admin/index.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 71f05355f5b70..a5d54d773efb6 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "url": "https://develop.svn.wordpress.org/trunk" }, "gutenberg": { - "ref": "892bfad51d2261f44f3a21f934b1c72bd29a2449" + "ref": "7bf80ea84eb8b62eceb1bb3fe82e42163673ca79" }, "engines": { "node": ">=20.10.0", diff --git a/src/wp-admin/font-library.php b/src/wp-admin/font-library.php index 370586aad3dcb..abc2ea4f4da70 100644 --- a/src/wp-admin/font-library.php +++ b/src/wp-admin/font-library.php @@ -19,7 +19,7 @@ } // Check if Gutenberg build files are available -if ( ! function_exists( 'font_library_wp_admin_render_page' ) ) { +if ( ! function_exists( 'wp_font_library_wp_admin_render_page' ) ) { wp_die( '

' . __( 'Font Library is not available.' ) . '

' . '

' . __( 'The Font Library requires Gutenberg build files. Please run npm install to build the necessary files.' ) . '

', @@ -33,6 +33,6 @@ require_once ABSPATH . 'wp-admin/admin-header.php'; // Render the Font Library page -font_library_wp_admin_render_page(); +wp_font_library_wp_admin_render_page(); require_once ABSPATH . 'wp-admin/admin-footer.php'; diff --git a/src/wp-admin/index.php b/src/wp-admin/index.php index bd150a0336fc7..4b26d53f49cb0 100644 --- a/src/wp-admin/index.php +++ b/src/wp-admin/index.php @@ -6,7 +6,7 @@ * please refer to wp-admin/_index.php. */ -if ( file_exists( __DIR__ . '/../wp-includes/js/dist/edit-post.js' ) && file_exists( __DIR__ . '/../wp-includes/build' ) ) { +if ( file_exists( __DIR__ . '/../wp-includes/js/jquery/jquery.js' ) && file_exists( __DIR__ . '/../wp-includes/build' ) ) { require_once __DIR__ . '/_index.php'; return; } From bee63c5ac1a932b3c8b9c0fc5a9f5e3e2bc10527 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 15 Jan 2026 18:46:12 +0100 Subject: [PATCH 6/8] Build: Remove redundant .js.map check. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Files ending with .js.map don't match .endsWith('.js'), so the explicit exclusion check is unnecessary. šŸ¤– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- tools/gutenberg/copy-gutenberg-build.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tools/gutenberg/copy-gutenberg-build.js b/tools/gutenberg/copy-gutenberg-build.js index a5c3c2a1d8c67..b3cdac2c671c9 100644 --- a/tools/gutenberg/copy-gutenberg-build.js +++ b/tools/gutenberg/copy-gutenberg-build.js @@ -919,8 +919,7 @@ async function main() { for ( const file of vendorFiles ) { if ( file.startsWith( 'react-jsx-runtime' ) && - file.endsWith( '.js' ) && - ! file.endsWith( '.js.map' ) + file.endsWith( '.js' ) ) { const srcFile = path.join( src, file ); const destFile = path.join( dest, file ); @@ -983,8 +982,7 @@ async function main() { } } else if ( entry.isFile() && - entry.name.endsWith( '.js' ) && - ! entry.name.endsWith( '.js.map' ) + entry.name.endsWith( '.js' ) ) { // Copy root-level JS files const dest = path.join( scriptsDest, entry.name ); From e12aece8510add2338167ce155a4758b310b5423 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 15 Jan 2026 18:46:17 +0100 Subject: [PATCH 7/8] Build: Use is_dir() instead of file_exists() for build folder check. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit More semantically correct since we're checking for a directory. šŸ¤– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/index.php | 2 +- src/wp-admin/index.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.php b/src/index.php index b1d1b372913d0..544acab805b09 100644 --- a/src/index.php +++ b/src/index.php @@ -15,7 +15,7 @@ * Load the actual index.php file if the assets were already built. * Note: WPINC is not defined yet, it is defined later in wp-settings.php. */ -if ( file_exists( ABSPATH . 'wp-includes/js/jquery/jquery.js' ) && file_exists( ABSPATH . 'wp-includes/build' ) ) { +if ( file_exists( ABSPATH . 'wp-includes/js/jquery/jquery.js' ) && is_dir( ABSPATH . 'wp-includes/build' ) ) { require_once ABSPATH . '_index.php'; return; } diff --git a/src/wp-admin/index.php b/src/wp-admin/index.php index 4b26d53f49cb0..7c549b6d8d4b7 100644 --- a/src/wp-admin/index.php +++ b/src/wp-admin/index.php @@ -6,7 +6,7 @@ * please refer to wp-admin/_index.php. */ -if ( file_exists( __DIR__ . '/../wp-includes/js/jquery/jquery.js' ) && file_exists( __DIR__ . '/../wp-includes/build' ) ) { +if ( file_exists( __DIR__ . '/../wp-includes/js/jquery/jquery.js' ) && is_dir( __DIR__ . '/../wp-includes/build' ) ) { require_once __DIR__ . '/_index.php'; return; } From 8c99034355d13eeeb787f3d16cb4b44597fe8a3c Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 15 Jan 2026 19:32:53 +0100 Subject: [PATCH 8/8] Build: Move mkdirSync outside of loop. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create the destination directory once before copying files rather than on each iteration. šŸ¤– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- tools/gutenberg/copy-gutenberg-build.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/gutenberg/copy-gutenberg-build.js b/tools/gutenberg/copy-gutenberg-build.js index b3cdac2c671c9..a66ca113e0cc2 100644 --- a/tools/gutenberg/copy-gutenberg-build.js +++ b/tools/gutenberg/copy-gutenberg-build.js @@ -916,6 +916,7 @@ async function main() { // Only copy react-jsx-runtime files, skip react and react-dom const vendorFiles = fs.readdirSync( src ); let copiedCount = 0; + fs.mkdirSync( dest, { recursive: true } ); for ( const file of vendorFiles ) { if ( file.startsWith( 'react-jsx-runtime' ) && @@ -923,7 +924,6 @@ async function main() { ) { const srcFile = path.join( src, file ); const destFile = path.join( dest, file ); - fs.mkdirSync( dest, { recursive: true } ); let content = fs.readFileSync( srcFile,