diff --git a/.github/workflows/reusable-test-core-build-process.yml b/.github/workflows/reusable-test-core-build-process.yml index 86f9e1ba1234a..e51646008ae9d 100644 --- a/.github/workflows/reusable-test-core-build-process.yml +++ b/.github/workflows/reusable-test-core-build-process.yml @@ -17,10 +17,15 @@ on: type: 'string' default: 'src' test-emoji: - description: 'Whether to run the grunt precommit:emoji script.' + description: 'Whether to run the precommit:emoji Grunt script.' required: false type: 'boolean' default: true + test-certificates: + description: 'Whether to run the certificate related Grunt scripts.' + required: false + type: 'boolean' + default: false save-build: description: 'Whether to save a ZIP of built WordPress as an artifact.' required: false @@ -69,6 +74,21 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} persist-credentials: false + # This date is used to ensure that the PHPCS cache is cleared at least once every week. + # http://man7.org/linux/man-pages/man1/date.1.html + - name: "Get last Monday's date" + id: get-date + if: ${{ inputs.test-certificates }} + run: echo "date=$(/bin/date -u --date='last Mon' "+%F")" >> "$GITHUB_OUTPUT" + + # Since Composer dependencies are installed using `composer update` and no lock file is in version control, + # passing a custom cache suffix ensures that the cache is flushed at least once per week. + - name: Install Composer dependencies + if: ${{ inputs.test-certificates }} + uses: ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520 # v3.1.1 + with: + custom-cache-suffix: ${{ steps.get-date.outputs.date }} + - name: Set up Node.js uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: @@ -91,6 +111,10 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Ensure certificates files are updated + if: ${{ inputs.test-certificates }} + run: npm run grunt copy:certificates && npm run grunt build:certificates + - name: Build WordPress to run from ${{ inputs.directory }} run: npm run ${{ inputs.directory == 'src' && 'build:dev' || 'build' }} diff --git a/.github/workflows/test-build-processes.yml b/.github/workflows/test-build-processes.yml index 587b6b02da290..9096d19838d84 100644 --- a/.github/workflows/test-build-processes.yml +++ b/.github/workflows/test-build-processes.yml @@ -59,6 +59,7 @@ jobs: matrix: os: [ 'ubuntu-24.04' ] directory: [ 'src', 'build' ] + test-certificates: [ true ] include: # Only prepare artifacts for Playground once. - os: 'ubuntu-24.04' @@ -68,6 +69,7 @@ jobs: with: os: ${{ matrix.os }} directory: ${{ matrix.directory }} + test-certificates: ${{ matrix.test-certificates && true || false }} save-build: ${{ matrix.save-build && matrix.save-build || false }} prepare-playground: ${{ matrix.prepare-playground && matrix.prepare-playground || false }} diff --git a/Gruntfile.js b/Gruntfile.js index 56862d96b9b50..729f1117522e4 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1584,10 +1584,69 @@ module.exports = function(grunt) { 'usebanner' ] ); - grunt.registerTask( 'certificates:update', 'Updates the Composer package responsible for root certificate updates.', function() { + grunt.registerTask( 'certificates:upgrade-package', 'Upgrades the package responsible for supplying the certificate authority certificate store bundled with WordPress.', function() { var done = this.async(); var flags = this.flags; - var args = [ 'update' ]; + var spawn = require( 'child_process' ).spawnSync; + var fs = require( 'fs' ); + + // Ensure that `composer update` has been run and the dependency is installed. + if ( ! fs.existsSync( 'vendor' ) || ! fs.existsSync( 'vendor/composer' ) || ! fs.existsSync( 'vendor/composer/ca-bundle' ) ) { + grunt.log.error( 'composer/ca-bundle dependency is missing. Please run `composer update` before attempting to upgrade the certificate bundle.' ); + done( false ); + return; + } + + /* + * Because the `composer/ca-bundle` is pinned to an exact version to ensure upgrades are applied intentionally, + * the `composer update` command will not upgrade the dependency. Instead, `composer require` must be called, + * but the specific version being upgraded to must be known and passed to the command. + */ + var outdatedResult = spawn( 'composer', [ 'outdated', 'composer/ca-bundle', '--format=json' ] ); + + if ( outdatedResult.status !== 0 ) { + grunt.log.error( 'Failed to get the package information for composer/ca-bundle.' ); + done( false ); + return; + } + + var packageInfo; + try { + var stdout = outdatedResult.stdout.toString().trim(); + if ( ! stdout ) { + grunt.log.writeln( 'The latest version is already installed.' ); + done( true ); + return; + } + packageInfo = JSON.parse( stdout ); + } catch ( e ) { + grunt.log.error( 'Failed to parse the package information for composer/ca-bundle.' ); + done( false ); + return; + } + + // Check for the version information needed to perform the necessary comparisons. + if ( ! packageInfo.versions || ! packageInfo.versions[0] || ! packageInfo.latest ) { + grunt.log.error( 'Could not determine version information for composer/ca-bundle.' ); + done( false ); + return; + } + + var currentVersion = packageInfo.versions[0]; + var latestVersion = packageInfo.latest; + + // Compare versions to ensure we actually need to update + if ( currentVersion === latestVersion ) { + grunt.log.writeln( 'The latest version is already installed: ' + latestVersion + '.' ); + done( true ); + return; + } + + grunt.log.writeln( 'Installed version: ' + currentVersion ); + grunt.log.writeln( 'New version found: ' + latestVersion ); + + // Upgrade to the latest version and change the pinned version in composer.json. + var args = [ 'require', 'composer/ca-bundle:' + latestVersion, '--dev' ]; grunt.util.spawn( { cmd: 'composer', @@ -1597,6 +1656,7 @@ module.exports = function(grunt) { if ( flags.error && error ) { done( false ); } else { + grunt.log.writeln( 'Successfully updated composer/ca-bundle to ' + latestVersion ); done( true ); } } ); @@ -1607,7 +1667,7 @@ module.exports = function(grunt) { ] ); grunt.registerTask( 'certificates:upgrade', [ - 'certificates:update', + 'certificates:upgrade-package', 'copy:certificates', 'build:certificates' ] );