Changeset 61873
- Timestamp:
- 03/10/2026 04:10:38 AM (4 days ago)
- Location:
- trunk
- Files:
-
- 2 added
- 3 deleted
- 4 edited
- 1 moved
-
Gruntfile.js (modified) (9 diffs)
-
package.json (modified) (3 diffs)
-
tests/phpstan/base.neon (modified) (1 diff)
-
tools/gutenberg/build-gutenberg.js (deleted)
-
tools/gutenberg/checkout-gutenberg.js (deleted)
-
tools/gutenberg/copy.js (moved) (moved from trunk/tools/gutenberg/copy-gutenberg-build.js) (39 diffs)
-
tools/gutenberg/download.js (added)
-
tools/gutenberg/sync-gutenberg.js (deleted)
-
tools/gutenberg/utils.js (added)
-
webpack.config.js (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
trunk/Gruntfile.js
r61830 r61873 54 54 'wp-includes/assets/*', 55 55 'wp-includes/css/dist', 56 'wp-includes/blocks/**/*.css',57 56 '!wp-includes/assets/script-loader-packages.min.php', 58 57 '!wp-includes/assets/script-modules-packages.min.php', … … 589 588 src: 'vendor/composer/ca-bundle/res/cacert.pem', 590 589 dest: SOURCE_DIR + 'wp-includes/certificates/ca-bundle.crt' 591 } 590 }, 591 // Gutenberg PHP infrastructure files (routes.php, pages.php, constants.php, pages/, routes/). 592 'gutenberg-php': { 593 options: { 594 process: function( content ) { 595 // Fix boot module asset file path for Core's different directory structure. 596 return content.replace( 597 /__DIR__\s*\.\s*(['"])\/..\/\..\/modules\/boot\/index\.min\.asset\.php\1/g, 598 'ABSPATH . WPINC . \'/js/dist/script-modules/boot/index.min.asset.php\'' 599 ); 600 } 601 }, 602 files: [ { 603 expand: true, 604 cwd: 'gutenberg/build', 605 src: [ 606 'routes.php', 607 'pages.php', 608 'constants.php', 609 'pages/**/*.php', 610 'routes/**/*.php', 611 ], 612 dest: WORKING_DIR + 'wp-includes/build/', 613 } ], 614 }, 615 'gutenberg-modules': { 616 files: [ { 617 expand: true, 618 cwd: 'gutenberg/build/modules', 619 src: [ '**/*', '!**/*.map' ], 620 dest: WORKING_DIR + 'wp-includes/js/dist/script-modules/', 621 } ], 622 }, 623 'gutenberg-styles': { 624 files: [ { 625 expand: true, 626 cwd: 'gutenberg/build/styles', 627 src: [ '**/*', '!**/*.map' ], 628 dest: WORKING_DIR + 'wp-includes/css/dist/', 629 } ], 630 }, 631 'gutenberg-theme-json': { 632 options: { 633 process: function( content, srcpath ) { 634 // Replace the local schema URL with the canonical public URL for Core. 635 if ( path.basename( srcpath ) === 'theme.json' ) { 636 return content.replace( 637 '"$schema": "../schemas/json/theme.json"', 638 '"$schema": "https://schemas.wp.org/trunk/theme.json"' 639 ); 640 } 641 return content; 642 } 643 }, 644 files: [ 645 { 646 src: 'gutenberg/lib/theme.json', 647 dest: WORKING_DIR + 'wp-includes/theme.json', 648 }, 649 { 650 src: 'gutenberg/lib/theme-i18n.json', 651 dest: WORKING_DIR + 'wp-includes/theme-i18n.json', 652 }, 653 ], 654 }, 655 'gutenberg-icons': { 656 options: { 657 process: function( content, srcpath ) { 658 // Remove the 'gutenberg' text domain from _x() calls in manifest.php. 659 if ( path.basename( srcpath ) === 'manifest.php' ) { 660 return content.replace( 661 /_x\(\s*([^,]+),\s*([^,]+),\s*['"]gutenberg['"]\s*\)/g, 662 '_x( $1, $2 )' 663 ); 664 } 665 return content; 666 } 667 }, 668 files: [ 669 { 670 src: 'gutenberg/packages/icons/src/manifest.php', 671 dest: WORKING_DIR + 'wp-includes/icons/manifest.php', 672 }, 673 { 674 expand: true, 675 cwd: 'gutenberg/packages/icons/src/library', 676 src: '*.svg', 677 dest: WORKING_DIR + 'wp-includes/icons/library/', 678 }, 679 ], 680 }, 592 681 }, 593 682 sass: { … … 1324 1413 { 1325 1414 expand: true, 1326 flatten: true, 1327 src: [ 1328 BUILD_DIR + 'wp-includes/js/dist/block-editor.js', 1329 BUILD_DIR + 'wp-includes/js/dist/commands.js', 1330 ], 1331 dest: BUILD_DIR + 'wp-includes/js/dist/' 1415 cwd: BUILD_DIR + 'wp-includes/js/dist/', 1416 src: [ '*.js' ], 1417 dest: BUILD_DIR + 'wp-includes/js/dist/', 1332 1418 }, 1333 1419 { 1334 1420 expand: true, 1335 flatten: true, 1336 src: [ 1337 BUILD_DIR + 'wp-includes/js/dist/vendor/**/*.js' 1338 ], 1339 dest: BUILD_DIR + 'wp-includes/js/dist/vendor/' 1421 cwd: BUILD_DIR + 'wp-includes/js/dist/vendor/', 1422 src: [ '**/*.js' ], 1423 dest: BUILD_DIR + 'wp-includes/js/dist/vendor/', 1424 }, 1425 { 1426 expand: true, 1427 cwd: BUILD_DIR + 'wp-includes/js/dist/script-modules/', 1428 src: [ '**/*.js' ], 1429 dest: BUILD_DIR + 'wp-includes/js/dist/script-modules/', 1340 1430 } 1341 1431 ] … … 1476 1566 1477 1567 // Gutenberg integration tasks. 1478 grunt.registerTask( 'gutenberg -checkout', 'Checks out the Gutenberg repository.', function() {1568 grunt.registerTask( 'gutenberg:verify', 'Verifies the installed Gutenberg version matches the expected SHA.', function() { 1479 1569 const done = this.async(); 1480 1570 grunt.util.spawn( { 1481 1571 cmd: 'node', 1482 args: [ 'tools/gutenberg/ checkout-gutenberg.js' ],1572 args: [ 'tools/gutenberg/utils.js' ], 1483 1573 opts: { stdio: 'inherit' } 1484 1574 }, function( error ) { … … 1487 1577 } ); 1488 1578 1489 grunt.registerTask( 'gutenberg -build', 'Builds the Gutenberg repository.', function() {1579 grunt.registerTask( 'gutenberg:download', 'Downloads the built Gutenberg artifact.', function() { 1490 1580 const done = this.async(); 1581 const args = [ 'tools/gutenberg/download.js' ]; 1582 if ( grunt.option( 'force' ) ) { 1583 args.push( '--force' ); 1584 } 1491 1585 grunt.util.spawn( { 1492 1586 cmd: 'node', 1493 args : [ 'tools/gutenberg/build-gutenberg.js' ],1587 args, 1494 1588 opts: { stdio: 'inherit' } 1495 1589 }, function( error ) { … … 1498 1592 } ); 1499 1593 1500 grunt.registerTask( 'gutenberg -copy', 'Copies Gutenberg build outputto WordPress Core.', function() {1594 grunt.registerTask( 'gutenberg:copy', 'Copies Gutenberg JS packages and block assets to WordPress Core.', function() { 1501 1595 const done = this.async(); 1502 1596 const buildDir = grunt.option( 'dev' ) ? 'src' : 'build'; 1503 1597 grunt.util.spawn( { 1504 1598 cmd: 'node', 1505 args: [ 'tools/gutenberg/copy-gutenberg-build.js', `--build-dir=${ buildDir }` ], 1506 opts: { stdio: 'inherit' } 1507 }, function( error ) { 1508 done( ! error ); 1509 } ); 1510 } ); 1511 1512 grunt.registerTask( 'gutenberg-sync', 'Syncs Gutenberg checkout and build if ref has changed.', function() { 1513 const done = this.async(); 1514 grunt.util.spawn( { 1515 cmd: 'node', 1516 args: [ 'tools/gutenberg/sync-gutenberg.js' ], 1599 args: [ 'tools/gutenberg/copy.js', `--build-dir=${ buildDir }` ], 1517 1600 opts: { stdio: 'inherit' } 1518 1601 }, function( error ) { … … 1957 2040 } ); 1958 2041 2042 grunt.registerTask( 'build:gutenberg', [ 2043 'copy:gutenberg-php', 2044 'gutenberg:copy', 2045 'copy:gutenberg-modules', 2046 'copy:gutenberg-styles', 2047 'copy:gutenberg-theme-json', 2048 'copy:gutenberg-icons', 2049 ] ); 2050 1959 2051 grunt.registerTask( 'build', function() { 1960 2052 if ( grunt.option( 'dev' ) ) { 1961 2053 grunt.task.run( [ 2054 'gutenberg:verify', 1962 2055 'build:js', 1963 2056 'build:css', 1964 2057 'build:codemirror', 1965 'gutenberg-sync', 1966 'gutenberg-copy', 2058 'build:gutenberg', 1967 2059 'copy-vendor-scripts', 1968 2060 'build:certificates' … … 1970 2062 } else { 1971 2063 grunt.task.run( [ 2064 'gutenberg:verify', 1972 2065 'build:certificates', 1973 2066 'build:files', … … 1975 2068 'build:css', 1976 2069 'build:codemirror', 1977 'gutenberg-sync', 1978 'gutenberg-copy', 2070 'build:gutenberg', 1979 2071 'copy-vendor-scripts', 1980 2072 'replace:source-maps', -
trunk/package.json
r61870 r61873 8 8 }, 9 9 "gutenberg": { 10 "ref": "9b8144036fa5faf75de43d4502ff9809fcf689ad" 10 "sha": "9b8144036fa5faf75de43d4502ff9809fcf689ad", 11 "ghcrRepo": "WordPress/gutenberg/gutenberg-wp-develop-build" 11 12 }, 12 13 "engines": { … … 112 113 }, 113 114 "scripts": { 114 "postinstall": "npm run gutenberg: sync && npm run gutenberg:copy -- --dev",115 "postinstall": "npm run gutenberg:download", 115 116 "build": "grunt build", 116 117 "build:dev": "grunt build --dev", 118 "build:gutenberg": "grunt build:gutenberg", 117 119 "dev": "grunt watch --dev", 118 120 "test": "grunt test", … … 138 140 "test:visual": "wp-scripts test-playwright --config tests/visual-regression/playwright.config.js", 139 141 "typecheck:php": "node ./tools/local-env/scripts/docker.js run --rm php composer phpstan", 140 "gutenberg:checkout": "node tools/gutenberg/checkout-gutenberg.js", 141 "gutenberg:build": "node tools/gutenberg/build-gutenberg.js", 142 "gutenberg:copy": "node tools/gutenberg/copy-gutenberg-build.js", 143 "gutenberg:sync": "node tools/gutenberg/sync-gutenberg.js", 142 "gutenberg:copy": "node tools/gutenberg/copy.js", 143 "gutenberg:download": "node tools/gutenberg/download.js", 144 144 "vendor:copy": "node tools/vendors/copy-vendors.js", 145 145 "sync-gutenberg-packages": "grunt sync-gutenberg-packages", -
trunk/tests/phpstan/base.neon
r61808 r61873 106 106 - ../../src/wp-includes/ms-deprecated.php 107 107 - ../../src/wp-includes/pluggable-deprecated.php 108 # These files are sourced by wordpress/gutenberg in `tools/release/sync-stable-blocks.js`.108 # These files are autogenerated by tools/gutenberg/copy.js. 109 109 - ../../src/wp-includes/blocks 110 110 # Third-party libraries. -
trunk/tools/gutenberg/copy.js
r61872 r61873 10 10 */ 11 11 12 const child_process = require( 'child_process' ); 12 13 const fs = require( 'fs' ); 13 14 const path = require( 'path' ); 14 15 const json2php = require( 'json2php' ); 15 const glob = require( 'glob' ); 16 17 // Paths 16 17 // Paths. 18 18 const rootDir = path.resolve( __dirname, '../..' ); 19 19 const gutenbergDir = path.join( rootDir, 'gutenberg' ); 20 20 const gutenbergBuildDir = path.join( gutenbergDir, 'build' ); 21 const gutenbergPackagesDir = path.join( gutenbergDir, 'packages' ); 22 23 // Determine build target from command line argument (--dev or --build-dir) 24 // Default to 'src' for development 21 22 /* 23 * Determine build target from command line argument (--dev or --build-dir). 24 * Default to 'src' for development. 25 */ 25 26 const args = process.argv.slice( 2 ); 26 27 const buildDirArg = args.find( ( arg ) => arg.startsWith( '--build-dir=' ) ); … … 38 39 */ 39 40 const COPY_CONFIG = { 40 // PHP infrastructure files (to wp-includes/build/) 41 // PHP infrastructure files (to wp-includes/build/). 41 42 phpInfrastructure: { 42 43 destination: 'build', … … 45 46 }, 46 47 47 // JavaScript packages (to wp-includes/js/dist/) 48 // JavaScript packages (to wp-includes/js/dist/). 48 49 scripts: { 49 50 source: 'scripts', 50 51 destination: 'js/dist', 51 copyDirectories: true, // Copy subdirectories 52 patterns: [ '*.js' ], 53 // Rename vendors/ to vendor/ when copying 52 copyDirectories: true, 53 // Rename vendors/ to vendor/ when copying. 54 54 directoryRenames: { 55 55 vendors: 'vendor', … … 57 57 }, 58 58 59 // Script modules (to wp-includes/js/dist/script-modules/) 59 // Script modules (to wp-includes/js/dist/script-modules/). 60 60 modules: { 61 61 source: 'modules', 62 62 destination: 'js/dist/script-modules', 63 copyAll: true,64 63 }, 65 64 66 // Styles (to wp-includes/css/dist/) 65 // Styles (to wp-includes/css/dist/). 67 66 styles: { 68 67 source: 'styles', 69 68 destination: 'css/dist', 70 copyAll: true,71 69 }, 72 70 73 // Blocks (to wp-includes/blocks/) 74 // Unified configuration for all block types 71 /* 72 * Blocks (to wp-includes/blocks/). 73 * Unified configuration for all block types. 74 */ 75 75 blocks: { 76 76 destination: 'blocks', 77 77 sources: [ 78 78 { 79 // Block library blocks 79 // Block library blocks. 80 80 name: 'block-library', 81 81 scripts: 'scripts/block-library', 82 82 styles: 'styles/block-library', 83 php: ' block-library/src',83 php: 'scripts/block-library', 84 84 }, 85 85 { 86 // Widget blocks 86 // Widget blocks. 87 87 name: 'widgets', 88 88 scripts: 'scripts/widgets/blocks', 89 89 styles: 'styles/widgets', 90 php: ' widgets/src/blocks',90 php: 'scripts/widgets/blocks', 91 91 }, 92 92 ], 93 93 }, 94 94 95 // Theme JSON files (from Gutenberg lib directory) 95 // Theme JSON files (from Gutenberg lib directory). 96 96 themeJson: { 97 97 files: [ … … 102 102 }, 103 103 104 // Specific files to copy to wp-includes/$destination 104 // Specific files to copy to wp-includes/$destination. 105 105 wpIncludes: [ 106 106 { … … 116 116 117 117 /** 118 * Given a path to a PHP file which returns a single value, converts that 119 * value into a native JavaScript value (limited by JSON serialization). 120 * 121 * @throws Error when PHP source file unable to be read, or PHP is unavailable. 122 * 123 * @param {string} phpFilepath Absolute path of PHP file returning a single value. 124 * @return {Object|Array} JavaScript representation of value from input file. 125 */ 126 function readReturnedValueFromPHPFile( phpFilepath ) { 127 const results = child_process.spawnSync( 128 'php', 129 [ '-r', '$path = file_get_contents( "php://stdin" ); if ( ! is_file( $path ) ) { die( 1 ); } try { $data = require $path; } catch ( \\Throwable $e ) { die( 2 ); } $json = json_encode( $data ); if ( ! is_string( $json ) ) { die( 3 ); } echo $json;' ], 130 { 131 encoding: 'utf8', 132 input: phpFilepath, 133 } 134 ); 135 136 switch ( results.status ) { 137 case 0: 138 return JSON.parse( results.stdout ); 139 140 case 1: 141 throw new Error( `Could not read PHP source file: '${ phpFilepath }'` ); 142 143 case 2: 144 throw new Error( `PHP source file did not return value when imported: '${ phpFilepath }'` ); 145 146 case 3: 147 throw new Error( `Could not serialize PHP source value into JSON: '${ phpFilepath }'` ); 148 } 149 150 throw new Error( `Unknown error while reading PHP source file: '${ phpFilepath }'` ); 151 } 152 153 /** 118 154 * Check if a block is experimental by reading its block.json. 119 155 * … … 159 195 160 196 if ( entry.isDirectory() ) { 161 // Check if this directory is an experimental block 197 // Check if this directory is an experimental block. 162 198 if ( options.excludeExperimental ) { 163 199 const blockJsonPath = path.join( srcPath, 'block.json' ); … … 169 205 copyDirectory( srcPath, destPath, transform, options ); 170 206 } else { 171 // Skip source map files (.map) — these are not useful in Core 207 // Skip source map files (.map) — these are not useful in Core, 172 208 // and the sourceMappingURL references are already stripped from JS files. 173 209 if ( /\.map$/.test( entry.name ) ) { … … 175 211 } 176 212 177 // Skip non-minified VIPS files — they are ~10MB of inlined WASM 213 // Skip non-minified VIPS files — they are ~10MB of inlined WASM, 178 214 // with no debugging value over the minified versions. 179 215 if ( … … 184 220 } 185 221 186 // Skip PHP files if excludePHP is true 222 // Skip PHP files if excludePHP is true. 187 223 if ( options.excludePHP && /\.php$/.test( entry.name ) ) { 188 224 continue; … … 191 227 let content = fs.readFileSync( srcPath ); 192 228 193 // Apply transformation if provided and file is text 229 // Apply transformation if provided and file is text. 194 230 if ( transform && /\.(php|js|css)$/.test( entry.name ) ) { 195 231 try { … … 224 260 const scriptsSrc = path.join( gutenbergBuildDir, source.scripts ); 225 261 const stylesSrc = path.join( gutenbergBuildDir, source.styles ); 226 const phpSrc = path.join( gutenberg PackagesDir, source.php );262 const phpSrc = path.join( gutenbergBuildDir, source.php ); 227 263 228 264 if ( ! fs.existsSync( scriptsSrc ) ) { … … 230 266 } 231 267 232 // Get all block directories from the scripts source 268 // Get all block directories from the scripts source. 233 269 const blockDirs = fs 234 270 .readdirSync( scriptsSrc, { withFileTypes: true } ) … … 237 273 238 274 for ( const blockName of blockDirs ) { 239 // Skip experimental blocks 275 // Skip experimental blocks. 240 276 const blockJsonPath = path.join( 241 277 scriptsSrc, … … 258 294 { 259 295 recursive: true, 260 // Skip PHP, copied from packages296 // Skip PHP, copied from build in steps 3 & 4. 261 297 filter: f => ! f.endsWith( '.php' ), 262 298 } … … 278 314 } 279 315 280 // 3. Copy PHP from packages 281 const blockPhpSrc = path.join( phpSrc, blockName, 'index.php' ); 316 // 3. Copy PHP from build 317 const blockPhpSrc = path.join( phpSrc, `${ blockName }.php` ); 318 const phpDest = path.join( 319 wpIncludesDir, 320 config.destination, 321 `${ blockName }.php` 322 ); 282 323 if ( fs.existsSync( blockPhpSrc ) ) { 283 const phpDest = path.join( 284 wpIncludesDir, 285 config.destination, 286 `${ blockName }.php` 287 ); 288 const content = fs.readFileSync( blockPhpSrc, 'utf8' ); 289 fs.writeFileSync( phpDest, content ); 290 } 291 292 // 4. Copy PHP subdirectories from packages (e.g., shared/helpers.php) 324 fs.copyFileSync( blockPhpSrc, phpDest ); 325 } 326 327 // 4. Copy PHP subdirectories from build (e.g., navigation-link/shared/*.php) 293 328 const blockPhpDir = path.join( phpSrc, blockName ); 294 329 if ( fs.existsSync( blockPhpDir ) ) { … … 303 338 ); 304 339 } 305 // Copy PHP files, but skip root index.php (handled by step 3) 340 // Copy PHP files, but skip root index.php (handled by step 3). 306 341 return src.endsWith( '.php' ) && src !== rootIndex; 307 342 }, … … 346 381 } else if ( entry.name.endsWith( '.min.asset.php' ) ) { 347 382 const relativePath = path.relative( baseDir, fullPath ); 348 // Normalize path separators to forward slashes for cross-platform consistency 383 // Normalize path separators to forward slashes for cross-platform consistency. 349 384 const normalizedPath = relativePath 350 385 .split( path.sep ) … … 357 392 358 393 try { 359 // Read and parse the PHP asset file 360 const phpContent = fs.readFileSync( fullPath, 'utf8' ); 361 // Extract the array from PHP: <?php return array(...); 362 const match = phpContent.match( 363 /return\s+array\(([\s\S]*?)\);/ 364 ); 365 if ( match ) { 366 // Parse PHP array to JavaScript object 367 const assetData = parsePHPArray( match[ 1 ] ); 368 assetsMin[ jsPathMin ] = assetData; 369 assetsRegular[ jsPathRegular ] = assetData; 370 } 394 const assetData = readReturnedValueFromPHPFile( fullPath ); 395 assetsMin[ jsPathMin ] = assetData; 396 assetsRegular[ jsPathRegular ] = assetData; 371 397 } catch ( error ) { 372 398 console.error( … … 381 407 processDirectory( modulesDir, modulesDir ); 382 408 383 // Generate both minified and non-minified PHP files using json2php 409 // Generate both minified and non-minified PHP files using json2php. 384 410 const phpContentMin = 385 411 '<?php return ' + … … 450 476 451 477 try { 452 // Read and parse the PHP asset file 453 const phpContent = fs.readFileSync( assetFile, 'utf8' ); 454 // Extract the array from PHP: <?php return array(...); 455 const match = phpContent.match( /return\s+array\(([\s\S]*?)\);/ ); 456 if ( match ) { 457 // Parse PHP array to JavaScript object 458 const assetData = parsePHPArray( match[ 1 ] ); 459 460 // For regular scripts, use dependencies as-is. 461 if ( ! assetData.dependencies ) { 462 assetData.dependencies = []; 463 } 464 465 // Create entries for both minified and non-minified versions 466 const jsPathMin = `${ entry.name }.min.js`; 467 const jsPathRegular = `${ entry.name }.js`; 468 469 assetsMin[ jsPathMin ] = assetData; 470 assetsRegular[ jsPathRegular ] = assetData; 471 } 478 const assetData = readReturnedValueFromPHPFile( assetFile ); 479 480 // For regular scripts, use dependencies as-is. 481 if ( ! assetData.dependencies ) { 482 assetData.dependencies = []; 483 } 484 485 // Create entries for both minified and non-minified versions. 486 const jsPathMin = `${ entry.name }.min.js`; 487 const jsPathRegular = `${ entry.name }.js`; 488 489 assetsMin[ jsPathMin ] = assetData; 490 assetsRegular[ jsPathRegular ] = assetData; 472 491 } catch ( error ) { 473 492 console.error( … … 478 497 } 479 498 480 // Generate both minified and non-minified PHP files using json2php 499 // Generate both minified and non-minified PHP files using json2php. 481 500 const phpContentMin = 482 501 '<?php return ' + … … 525 544 const staticBlocks = []; 526 545 527 // Widget blocks to exclude (from @wordpress/widgets package) 546 // Widget blocks to exclude (from @wordpress/widgets package). 528 547 const widgetBlocks = [ 'legacy-widget', 'widget-group' ]; 529 548 … … 540 559 } 541 560 542 // Skip widget blocks 561 // Skip widget blocks. 543 562 if ( widgetBlocks.includes( entry.name ) ) { 544 563 continue; … … 549 568 const phpFilePath = path.join( blocksDir, `${ entry.name }.php` ); 550 569 551 // Skip if block.json doesn't exist 570 // Skip if block.json doesn't exist. 552 571 if ( ! fs.existsSync( blockJsonPath ) ) { 553 572 continue; 554 573 } 555 574 556 // Check if it's experimental 575 // Check if it's experimental. 557 576 if ( isExperimentalBlock( blockJsonPath ) ) { 558 577 continue; 559 578 } 560 579 561 // Determine if it's dynamic (has a PHP file) 580 // Determine if it's dynamic (has a PHP file). 562 581 if ( fs.existsSync( phpFilePath ) ) { 563 582 dynamicBlocks.push( entry.name ); … … 567 586 } 568 587 569 // Sort alphabetically 588 // Sort alphabetically. 570 589 dynamicBlocks.sort(); 571 590 staticBlocks.sort(); 572 591 573 // Generate require-dynamic-blocks.php 592 // Generate require-dynamic-blocks.php. 574 593 const dynamicContent = `<?php 575 594 576 // This file was autogenerated by tools/gutenberg/copy -gutenberg-build.js, do not change manually!595 // This file was autogenerated by tools/gutenberg/copy.js, do not change manually! 577 596 // Requires files for dynamic blocks necessary for core blocks registration. 578 597 ${ dynamicBlocks … … 588 607 ); 589 608 590 // Generate require-static-blocks.php 609 // Generate require-static-blocks.php. 591 610 const staticContent = `<?php 592 611 593 // This file was autogenerated by tools/gutenberg/copy -gutenberg-build.js, do not change manually!612 // This file was autogenerated by tools/gutenberg/copy.js, do not change manually! 594 613 // Returns folder names for static blocks necessary for core blocks registration. 595 614 return array( … … 646 665 } 647 666 648 // Generate the PHP file content using json2php for consistent formatting 667 // Generate the PHP file content using json2php for consistent formatting. 649 668 const phpContent = 650 669 '<?php return ' + … … 667 686 668 687 /** 669 * Parse PHP array syntax to JavaScript object.670 * Uses a simple but effective approach for the specific format in asset files.671 *672 * @param {string} phpArrayContent - PHP array content (without outer 'array(' and ')').673 * @return {Object|Array} Parsed JavaScript object or array.674 */675 function parsePHPArray( phpArrayContent ) {676 phpArrayContent = phpArrayContent.trim();677 678 // First, extract all nested array() blocks and replace with placeholders679 const nestedArrays = [];680 let content = phpArrayContent;681 let depth = 0;682 let inString = false;683 let stringChar = '';684 let currentArray = '';685 let arrayStart = -1;686 687 for ( let i = 0; i < content.length; i++ ) {688 const char = content[ i ];689 690 // Track strings691 if (692 ( char === "'" || char === '"' ) &&693 ( i === 0 || content[ i - 1 ] !== '\\' )694 ) {695 if ( ! inString ) {696 inString = true;697 stringChar = char;698 } else if ( char === stringChar ) {699 inString = false;700 }701 }702 703 if ( ! inString ) {704 // Look for array( keyword705 if ( content.substring( i, i + 6 ) === 'array(' ) {706 if ( depth === 0 ) {707 arrayStart = i;708 currentArray = '';709 }710 depth++;711 if ( depth > 1 ) {712 currentArray += 'array(';713 }714 i += 5; // Skip 'array('715 continue;716 }717 718 if ( depth > 0 ) {719 if ( char === '(' ) {720 depth++;721 currentArray += char;722 } else if ( char === ')' ) {723 depth--;724 if ( depth === 0 ) {725 // Found complete nested array726 const placeholder = `__ARRAY_${ nestedArrays.length }__`;727 nestedArrays.push( currentArray );728 content =729 content.substring( 0, arrayStart ) +730 placeholder +731 content.substring( i + 1 );732 i = arrayStart + placeholder.length - 1;733 currentArray = '';734 } else {735 currentArray += char;736 }737 } else {738 currentArray += char;739 }740 }741 } else if ( depth > 0 ) {742 currentArray += char;743 }744 }745 746 // Now parse the simplified content747 const result = {};748 const values = [];749 let isAssociative = false;750 751 // Split by top-level commas752 const parts = [];753 depth = 0;754 inString = false;755 let currentPart = '';756 757 for ( let i = 0; i < content.length; i++ ) {758 const char = content[ i ];759 760 if (761 ( char === "'" || char === '"' ) &&762 ( i === 0 || content[ i - 1 ] !== '\\' )763 ) {764 inString = ! inString;765 }766 767 if ( ! inString && char === ',' && depth === 0 ) {768 parts.push( currentPart.trim() );769 currentPart = '';770 } else {771 currentPart += char;772 if ( ! inString ) {773 if ( char === '(' ) depth++;774 if ( char === ')' ) depth--;775 }776 }777 }778 if ( currentPart.trim() ) {779 parts.push( currentPart.trim() );780 }781 782 // Parse each part783 for ( const part of parts ) {784 const arrowMatch = part.match( /^(.+?)\s*=>\s*(.+)$/ );785 786 if ( arrowMatch ) {787 isAssociative = true;788 let key = arrowMatch[ 1 ].trim().replace( /^['"]|['"]$/g, '' );789 let value = arrowMatch[ 2 ].trim();790 791 // Replace placeholders792 while ( value.match( /__ARRAY_(\d+)__/ ) ) {793 value = value.replace( /__ARRAY_(\d+)__/, ( match, index ) => {794 return 'array(' + nestedArrays[ parseInt( index ) ] + ')';795 } );796 }797 798 result[ key ] = parseValue( value );799 } else {800 // No arrow, indexed array801 let value = part;802 803 // Replace placeholders804 while ( value.match( /__ARRAY_(\d+)__/ ) ) {805 value = value.replace( /__ARRAY_(\d+)__/, ( match, index ) => {806 return 'array(' + nestedArrays[ parseInt( index ) ] + ')';807 } );808 }809 810 values.push( parseValue( value ) );811 }812 }813 814 return isAssociative ? result : values;815 816 /**817 * Parse a single value.818 *819 * @param {string} value - The value string to parse.820 * @return {*} Parsed value.821 */822 function parseValue( value ) {823 value = value.trim();824 825 if ( value.startsWith( 'array(' ) && value.endsWith( ')' ) ) {826 return parsePHPArray( value.substring( 6, value.length - 1 ) );827 } else if ( value.match( /^['"].*['"]$/ ) ) {828 return value.substring( 1, value.length - 1 );829 } else if ( value === 'true' ) {830 return true;831 } else if ( value === 'false' ) {832 return false;833 } else if ( ! isNaN( value ) && value !== '' ) {834 return parseInt( value, 10 );835 }836 return value;837 }838 }839 840 /**841 * Transform PHP file contents to work in Core.842 *843 * @param {string} content - File content.844 * @return {string} Transformed content.845 */846 function transformPHPContent( content ) {847 let transformed = content;848 849 // Fix boot module asset file path for Core's different directory structure850 // FROM: __DIR__ . '/../../modules/boot/index.min.asset.php'851 // TO: ABSPATH . WPINC . '/js/dist/script-modules/boot/index.min.asset.php'852 // This is needed because Core copies modules to a different location than the plugin structure853 transformed = transformed.replace(854 /__DIR__\s*\.\s*['"]\/\.\.\/\.\.\/modules\/boot\/index\.min\.asset\.php['"]/g,855 "ABSPATH . WPINC . '/js/dist/script-modules/boot/index.min.asset.php'"856 );857 858 return transformed;859 }860 861 /**862 * Transform manifest.php to remove gutenberg text domain.863 *864 * @param {string} content - File content.865 * @return {string} Transformed content.866 */867 function transformManifestPHP( content ) {868 // Remove 'gutenberg' text domain from _x() calls869 // FROM: _x( '...', 'icon label', 'gutenberg' )870 // TO: _x( '...', 'icon label' )871 const transformedContent = content.replace(872 /_x\(\s*([^,]+),\s*([^,]+),\s*['"]gutenberg['"]\s*\)/g,873 '_x( $1, $2 )'874 );875 return transformedContent;876 }877 878 /**879 688 * Main execution function. 880 689 */ 881 690 async function main() { 882 console.log( '🔍 Checking Gutenberg build...' ); 883 console.log( ` Build target: ${ buildTarget }/` ); 884 885 // Verify Gutenberg build exists 691 console.log( `📦 Copying Gutenberg build to ${ buildTarget }/...` ); 692 886 693 if ( ! fs.existsSync( gutenbergBuildDir ) ) { 887 694 console.error( '❌ Gutenberg build directory not found' ); 888 console.error( ' Run: n ode tools/gutenberg/build-gutenberg.js' );695 console.error( ' Run: npm run grunt gutenberg:download' ); 889 696 process.exit( 1 ); 890 697 } 891 698 892 console.log( '✅ Gutenberg build found' ); 893 894 // 1. Copy PHP infrastructure 895 console.log( '\n📦 Copying PHP infrastructure...' ); 896 const phpConfig = COPY_CONFIG.phpInfrastructure; 897 const phpDest = path.join( wpIncludesDir, phpConfig.destination ); 898 899 // Copy PHP files 900 for ( const file of phpConfig.files ) { 901 const src = path.join( gutenbergBuildDir, file ); 902 const dest = path.join( phpDest, file ); 903 904 if ( fs.existsSync( src ) ) { 905 fs.mkdirSync( path.dirname( dest ), { recursive: true } ); 906 let content = fs.readFileSync( src, 'utf8' ); 907 content = transformPHPContent( content ); 908 fs.writeFileSync( dest, content ); 909 console.log( ` ✅ ${ file }` ); 910 } else { 911 console.log( 912 ` ⚠️ ${ file } not found (may not exist in this Gutenberg version)` 913 ); 914 } 915 } 916 917 // Copy PHP directories 918 for ( const dir of phpConfig.directories ) { 919 const src = path.join( gutenbergBuildDir, dir ); 920 const dest = path.join( phpDest, dir ); 921 922 if ( fs.existsSync( src ) ) { 923 console.log( ` 📁 Copying ${ dir }/...` ); 924 copyDirectory( src, dest, transformPHPContent ); 925 console.log( ` ✅ ${ dir }/ copied` ); 926 } 927 } 928 929 // 2. Copy JavaScript packages 699 // 1. Copy JavaScript packages. 930 700 console.log( '\n📦 Copying JavaScript packages...' ); 931 701 const scriptsConfig = COPY_CONFIG.scripts; … … 933 703 const scriptsDest = path.join( wpIncludesDir, scriptsConfig.destination ); 934 704 935 // Transform function to remove source map comments from all JS files.936 // Only match actual source map comments at the start of a line (possibly937 // with whitespace), not occurrences inside string literals.938 const removeSourceMaps = ( content ) => {939 return content.replace( /^\s*\/\/# sourceMappingURL=.*$/gm, '' ).trimEnd();940 };941 942 705 if ( fs.existsSync( scriptsSrc ) ) { 943 706 const entries = fs.readdirSync( scriptsSrc, { withFileTypes: true } ); … … 947 710 948 711 if ( entry.isDirectory() ) { 949 // Check if this should be copied as a directory (like vendors/) 712 // Check if this should be copied as a directory (like vendors/). 950 713 if ( 951 714 scriptsConfig.copyDirectories && … … 953 716 scriptsConfig.directoryRenames[ entry.name ] 954 717 ) { 955 // Copy special directories with rename (vendors/ → vendor/) 956 // Only copy react-jsx-runtime from vendors (react and react-dom come from Core's node_modules) 718 /* 719 * Copy special directories with rename (vendors/ → vendor/). 720 * Only copy react-jsx-runtime from vendors (react and react-dom come from Core's node_modules). 721 */ 957 722 const destName = 958 723 scriptsConfig.directoryRenames[ entry.name ]; … … 960 725 961 726 if ( entry.name === 'vendors' ) { 962 // Only copy react-jsx-runtime files, skip react and react-dom 727 // Only copy react-jsx-runtime files, skip react and react-dom. 963 728 const vendorFiles = fs.readdirSync( src ); 964 729 let copiedCount = 0; … … 972 737 const destFile = path.join( dest, file ); 973 738 974 let content = fs.readFileSync( 975 srcFile, 976 'utf8' 977 ); 978 content = removeSourceMaps( content ); 979 fs.writeFileSync( destFile, content ); 739 fs.copyFileSync( srcFile, destFile ); 980 740 copiedCount++; 981 741 } … … 985 745 ); 986 746 } else { 987 // Copy other special directories normally 988 copyDirectory( src, dest , removeSourceMaps);747 // Copy other special directories normally. 748 copyDirectory( src, dest ); 989 749 console.log( 990 750 ` ✅ ${ entry.name }/ → ${ destName }/` … … 992 752 } 993 753 } else { 994 // Flatten package structure: package-name/index.js → package-name.js 995 // This matches Core's expected file structure 754 /* 755 * Flatten package structure: package-name/index.js → package-name.js. 756 * This matches Core's expected file structure. 757 */ 996 758 const packageFiles = fs.readdirSync( src ); 997 759 … … 1001 763 ) { 1002 764 const srcFile = path.join( src, file ); 1003 // Replace 'index.' with 'package-name.' 765 // Replace 'index.' with 'package-name.'. 1004 766 const destFile = file.replace( 1005 767 /^index\./, … … 1012 774 } ); 1013 775 1014 // Apply source map removal for .js files 1015 if ( file.endsWith( '.js' ) ) { 1016 let content = fs.readFileSync( 1017 srcFile, 1018 'utf8' 1019 ); 1020 content = removeSourceMaps( content ); 1021 fs.writeFileSync( destPath, content ); 1022 } else { 1023 // Copy other files as-is (.min.asset.php) 1024 fs.copyFileSync( srcFile, destPath ); 1025 } 776 fs.copyFileSync( srcFile, destPath ); 1026 777 } 1027 778 } 1028 779 } 1029 780 } else if ( entry.isFile() && entry.name.endsWith( '.js' ) ) { 1030 // Copy root-level JS files 781 // Copy root-level JS files. 1031 782 const dest = path.join( scriptsDest, entry.name ); 1032 783 fs.mkdirSync( path.dirname( dest ), { recursive: true } ); 1033 1034 let content = fs.readFileSync( src, 'utf8' ); 1035 content = removeSourceMaps( content ); 1036 fs.writeFileSync( dest, content ); 784 fs.copyFileSync( src, dest ); 1037 785 } 1038 786 } … … 1041 789 } 1042 790 1043 // 3. Copy script modules 1044 console.log( '\n📦 Copying script modules...' ); 1045 const modulesConfig = COPY_CONFIG.modules; 1046 const modulesSrc = path.join( gutenbergBuildDir, modulesConfig.source ); 1047 const modulesDest = path.join( wpIncludesDir, modulesConfig.destination ); 1048 1049 if ( fs.existsSync( modulesSrc ) ) { 1050 // Use the same source map removal transform 1051 copyDirectory( modulesSrc, modulesDest, removeSourceMaps ); 1052 console.log( ' ✅ Script modules copied' ); 1053 } 1054 1055 // 4. Copy styles 1056 console.log( '\n📦 Copying styles...' ); 1057 const stylesConfig = COPY_CONFIG.styles; 1058 const stylesSrc = path.join( gutenbergBuildDir, stylesConfig.source ); 1059 const stylesDest = path.join( wpIncludesDir, stylesConfig.destination ); 1060 1061 if ( fs.existsSync( stylesSrc ) ) { 1062 copyDirectory( stylesSrc, stylesDest ); 1063 console.log( ' ✅ Styles copied' ); 1064 } 1065 1066 // 5. Copy blocks (unified: scripts, styles, PHP, JSON) 791 // 2. Copy blocks (unified: scripts, styles, PHP, JSON). 1067 792 console.log( '\n📦 Copying blocks...' ); 1068 const blocksDest = path.join(1069 wpIncludesDir,1070 COPY_CONFIG.blocks.destination1071 );1072 793 copyBlockAssets( COPY_CONFIG.blocks ); 1073 794 1074 // 6. Copy theme JSON files (from Gutenberg lib directory) 1075 console.log( '\n📦 Copying theme JSON files...' ); 1076 const themeJsonConfig = COPY_CONFIG.themeJson; 1077 const gutenbergLibDir = path.join( gutenbergDir, 'lib' ); 1078 1079 for ( const fileMap of themeJsonConfig.files ) { 1080 const src = path.join( gutenbergLibDir, fileMap.from ); 1081 const dest = path.join( wpIncludesDir, fileMap.to ); 1082 1083 if ( fs.existsSync( src ) ) { 1084 let content = fs.readFileSync( src, 'utf8' ); 1085 1086 if ( themeJsonConfig.transform && fileMap.from === 'theme.json' ) { 1087 // Transform schema URL for Core 1088 content = content.replace( 1089 '"$schema": "../schemas/json/theme.json"', 1090 '"$schema": "https://schemas.wp.org/trunk/theme.json"' 1091 ); 1092 } 1093 1094 fs.writeFileSync( dest, content ); 1095 console.log( ` ✅ ${ fileMap.to }` ); 1096 } else { 1097 console.log( ` ⚠️ Not found: ${ fileMap.from }` ); 1098 } 1099 } 1100 1101 // Copy remaining files to wp-includes 1102 console.log( '\n📦 Copying remaining files to wp-includes...' ); 1103 for ( const fileMap of COPY_CONFIG.wpIncludes ) { 1104 const dest = path.join( wpIncludesDir, fileMap.destination ); 1105 fs.mkdirSync( dest, { recursive: true } ); 1106 for ( const src of fileMap.files ) { 1107 const matches = glob.sync( path.join( gutenbergDir, src ) ); 1108 if ( ! matches.length ) { 1109 throw new Error( `No files found matching '${ src }'` ); 1110 } 1111 for ( const match of matches ) { 1112 const destPath = path.join( dest, path.basename( match ) ); 1113 // Apply transformation for manifest.php to remove gutenberg text domain 1114 if ( path.basename( match ) === 'manifest.php' ) { 1115 let content = fs.readFileSync( match, 'utf8' ); 1116 content = transformManifestPHP( content ); 1117 fs.writeFileSync( destPath, content ); 1118 } else { 1119 fs.copyFileSync( match, destPath ); 1120 } 1121 } 1122 } 1123 } 1124 1125 // 7. Generate script-modules-packages.min.php from individual asset files 1126 console.log( '\n📦 Generating script-modules-packages.min.php...' ); 795 // 3. Generate script-modules-packages.php from individual asset files. 796 console.log( '\n📦 Generating script-modules-packages.php...' ); 1127 797 generateScriptModulesPackages(); 1128 798 1129 // 8. Generate script-loader-packages.min.php1130 console.log( '\n📦 Generating script-loader-packages. min.php...' );799 // 4. Generate script-loader-packages.php. 800 console.log( '\n📦 Generating script-loader-packages.php...' ); 1131 801 generateScriptLoaderPackages(); 1132 802 1133 // 9. Generate require-dynamic-blocks.php and require-static-blocks.php803 // 5. Generate require-dynamic-blocks.php and require-static-blocks.php. 1134 804 console.log( '\n📦 Generating block registration files...' ); 1135 805 generateBlockRegistrationFiles(); 1136 806 1137 // 10. Generate blocks-json.php from block.json files807 // 6. Generate blocks-json.php from block.json files. 1138 808 console.log( '\n📦 Generating blocks-json.php...' ); 1139 809 generateBlocksJson(); 1140 810 1141 // Summary1142 811 console.log( '\n✅ Copy complete!' ); 1143 console.log( '\n📊 Summary:' );1144 console.log( ` PHP infrastructure: ${ phpDest }` );1145 console.log( ` JavaScript: ${ scriptsDest }` );1146 console.log( ` Script modules: ${ modulesDest }` );1147 console.log( ` Styles: ${ stylesDest }` );1148 console.log( ` Blocks: ${ blocksDest }` );1149 812 } 1150 813 1151 // Run main function 814 // Run main function. 1152 815 main().catch( ( error ) => { 1153 816 console.error( '❌ Unexpected error:', error ); -
trunk/webpack.config.js
r61543 r61873 15 15 // Only building Core-specific media files and development scripts. 16 16 // Blocks, packages, script modules, and vendors are now sourced from 17 // the Gutenberg build (see tools/gutenberg/copy -gutenberg-build.js).17 // the Gutenberg build (see tools/gutenberg/copy.js). 18 18 // Note: developmentConfig returns an array of configs, so we spread it. 19 19 const config = [
Note: See TracChangeset
for help on using the changeset viewer.