diff --git a/.github/workflows/check-built-files.yml b/.github/workflows/check-built-files.yml index 5f62f825c243e..bb21adf729c46 100644 --- a/.github/workflows/check-built-files.yml +++ b/.github/workflows/check-built-files.yml @@ -1,4 +1,4 @@ -# Checks for uncommitted changes to built files in pull requests. +# Checks for uncommitted or unexpected changes to built files within pull requests. name: Check Built Files (PRs) on: @@ -23,11 +23,13 @@ on: - '.nvmrc' - 'Gruntfile.js' - 'webpack.config.js' - - 'tools/webpack/**' + - 'tools/**' # These files configure Composer. Changes could affect the outcome. - 'composer.*' # Confirm any changes to relevant workflow files. - '.github/workflows/check-built-files.yml' + - '.github/workflows/reusable-check-built-files.yml' + - '.github/workflows/reusable-compare-built-files-*.yml' # Changes to the default themes should be handled by the themes workflows. - '!src/wp-content/themes/twenty**' @@ -43,9 +45,18 @@ concurrency: permissions: {} jobs: + # Checks built files for uncommitted changes. check-for-built-file-changes: - name: Check built files - if: ${{ github.repository == 'wordpress/wordpress-develop' }} + name: Check for uncommitted changes + if: ${{ github.repository == 'WordPress/wordpress-develop' || github.event_name == 'pull_request' }} uses: ./.github/workflows/reusable-check-built-files.yml permissions: contents: read + + # Compares the build directory with the WordPress/WordPress repository. + compare-with-build-server: + name: Compare built files to WordPress/WordPress + uses: ./.github/workflows/reusable-compare-built-files-v1.yml + permissions: + contents: read + if: ${{ github.repository == 'WordPress/wordpress-develop' || github.event_name == 'pull_request' }} diff --git a/.github/workflows/pull-request-comments.yml b/.github/workflows/pull-request-comments.yml index da30e2feb7f11..b8c9593cdce28 100644 --- a/.github/workflows/pull-request-comments.yml +++ b/.github/workflows/pull-request-comments.yml @@ -5,7 +5,7 @@ on: pull_request_target: types: [ 'opened', 'synchronize', 'reopened', 'edited' ] workflow_run: - workflows: [ 'Test Build Processes' ] + workflows: [ 'Check Built Files (PRs)', 'Test Build Processes' ] types: - completed @@ -22,6 +22,7 @@ permissions: {} jobs: # Comments on a pull request when the author is a first time contributor. post-welcome-message: + name: Contributor welcome message runs-on: ubuntu-24.04 permissions: issues: write @@ -79,7 +80,7 @@ jobs: # Leaves a comment on a pull request with a link to test the changes in a WordPress Playground instance. playground-details: - name: Comment on a pull request with Playground details + name: Leave Playground testing details runs-on: ubuntu-24.04 permissions: issues: write @@ -87,10 +88,13 @@ jobs: if: > github.repository == 'WordPress/wordpress-develop' && github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.name == 'Test Build Processes' && github.event.workflow_run.conclusion == 'success' steps: - name: Download artifact uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + RUN_ID: ${{ github.event.workflow_run.id }} with: script: | const artifacts = await github.rest.actions.listWorkflowRunArtifacts( { @@ -117,8 +121,6 @@ jobs: const fs = require( 'fs' ); fs.writeFileSync( '${{github.workspace}}/pr-number.zip', Buffer.from( download.data ) ) - env: - RUN_ID: ${{ github.event.workflow_run.id }} - name: Unzip the artifact containing the PR number run: unzip pr-number.zip @@ -180,6 +182,148 @@ jobs: github.rest.issues.createComment( commentInfo ); + # Leaves a comment on a pull request noting differences between the PR and the build server. + build-server-comparison: + name: Note differences with the build server + runs-on: ubuntu-24.04 + permissions: + issues: write + pull-requests: write + if: > + github.repository == 'WordPress/wordpress-develop' && + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.name == 'Check Built Files (PRs)' && + github.event.workflow_run.conclusion == 'success' + steps: + - name: Download artifact + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + RUN_ID: ${{ github.event.workflow_run.id }} + with: + script: | + const artifacts = await github.rest.actions.listWorkflowRunArtifacts( { + owner: context.repo.owner, + repo: context.repo.repo, + run_id: process.env.RUN_ID, + } ); + + const matchArtifact = artifacts.data.artifacts.filter( ( artifact ) => { + return artifact.name === 'build-server-comparison' + } )[0]; + + if ( ! matchArtifact ) { + core.setFailed( 'No artifact found!' ); + return; + } + + const download = await github.rest.actions.downloadArtifact( { + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: matchArtifact.id, + archive_format: 'zip', + } ); + + const fs = require( 'fs' ); + fs.writeFileSync( '${{github.workspace}}/build-server-comparison.zip', Buffer.from( download.data ) ) + + - name: Unzip the artifact containing the comparison info + run: unzip build-server-comparison.zip + + - name: Leave a comment with any differences noticed + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + script: | + const fs = require( 'fs' ); + const issue_number = Number( fs.readFileSync( './NR' ) ); + const fileChanges = fs.readFileSync( './file-changes.txt', 'utf8' ); + const diffContents = fs.readFileSync( './changes.diff', 'utf8' ); + const MAX_DIFF_LENGTH = 50000; // GitHub has a 65,536 character limit for comments. + + core.info( `Checking pull request #${issue_number}.` ); + + // Confirm that the pull request is still open before leaving a comment. + const pr = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: issue_number, + }); + + if ( pr.data.state !== 'open' ) { + core.info( 'The pull request has been closed. No comment will be left.' ); + return; + } + + // Comments are only added after the first successful build. Check for the presence of a comment and bail early. + const commentInfo = { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number, + }; + + const comments = ( await github.rest.issues.listComments( commentInfo ) ).data; + + for ( const currentComment of comments ) { + if ( currentComment.user.type === 'Bot' && currentComment.body.includes( 'Build Server Comparison' ) ) { + commentInfo.comment_id = currentComment.id; + break; + } + }; + + commentInfo.body = "## Build Server Comparison\n\n"; + + // Post or update the comment. + if ( fileChanges.trim() === '' ) { + commentInfo.body += 'The contents of the `build` directory after running `npm run build` matches the contents of the WordPress/WordPress repository. No differences were found.'; + } else { + commentInfo.body += `The contents of the \`build\` directory after running \`npm run build\` has been compared with the contents of the WordPress/WordPress repository. + + **Review these differences carefully for any unexpected results.** + + ### List of Modified Files + + \`\`\` + ${ fileChanges } + \`\`\` + + ### Full Diff File + `; + + if ( diffContents.length > MAX_DIFF_LENGTH ) { + const cutoff = diffContents.lastIndexOf( '\n', MAX_DIFF_LENGTH ); + const truncated = diffContents.substring( 0, cutoff ); + + commentInfo.body += `
+ Click to expand (truncated) + + \`\`\`diff + ${ truncated } + \`\`\` + + ⚠️ The diff was too large to display in full. + +
`; + } else { + commentInfo.body += `
+ Click to expand + + \`\`\`diff + ${ diffContents } + \`\`\` + +
`; + } + + const sha = context.payload.workflow_run.head_sha; + commentInfo.body += `\n\n_Comment updated using [${ sha.slice( 0, 7 ) }](https://github.com/${ context.repo.owner }/${ context.repo.repo }/commit/${ sha })._ ([commenting workflow run](https://github.com/${ context.repo.owner }/${ context.repo.repo }/actions/runs/${ context.runId })).`; + commentInfo.body += `\n\n[Download the complete .diff file from the workflow run](https://github.com/${ context.repo.owner }/${ context.repo.repo }/actions/runs/${ context.payload.workflow_run.id }).`; + } + + if ( commentInfo.comment_id ) { + github.rest.issues.updateComment( commentInfo ); + } else { + github.rest.issues.createComment( commentInfo ); + } + # Manages comments reminding contributors to include a Trac ticket link when opening a pull request. trac-ticket-check: name: Manage Trac ticket reminders for pull requests diff --git a/.github/workflows/reusable-check-built-files.yml b/.github/workflows/reusable-check-built-files.yml index 11d97639a30fc..30bb3ed5021b4 100644 --- a/.github/workflows/reusable-check-built-files.yml +++ b/.github/workflows/reusable-check-built-files.yml @@ -1,7 +1,7 @@ ## # A reusable workflow that checks for uncommitted changes to built files in pull requests. ## -name: Check Built Files (PRs) +name: Check for Changes to Versioned Files (reusable) on: workflow_call: @@ -9,7 +9,7 @@ on: permissions: {} jobs: - # Checks a PR for uncommitted changes to built files. + # Checks a PR for uncommitted changes to versioned files. # # When changes are detected, the patch is stored as an artifact for processing by the Commit Built File Changes # workflow. @@ -29,8 +29,8 @@ jobs: # - Displays the result of git diff for debugging purposes. # - Saves the diff to a patch file. # - Uploads the patch file as an artifact. - update-built-files: - name: Check and update built files + check-versioned-files: + name: Check for changes runs-on: ubuntu-24.04 timeout-minutes: 10 permissions: diff --git a/.github/workflows/reusable-compare-built-files-v1.yml b/.github/workflows/reusable-compare-built-files-v1.yml new file mode 100644 index 0000000000000..59fa79dc5e111 --- /dev/null +++ b/.github/workflows/reusable-compare-built-files-v1.yml @@ -0,0 +1,110 @@ +## +# A reusable workflow that compares the results of the build script with the most recent commit to WordPress/WordPress. +## +name: Compare Built Files (reusable) + +on: + workflow_call: + +# Disable permissions for all available scopes by default. +# Any needed permissions should be configured at the job level. +permissions: {} + +jobs: + # Runs the PHP coding standards checks. + # + # Violations are reported inline with annotations. + # + # Performs the following steps: + # - Checks out the repository. + # - Sets up Node.js. + # - Sets up PHP. + # - Installs Composer dependencies. + # - Logs debug information about the GitHub Action runner. + # - Installs npm dependencies. + # - Builds WordPress to run from the build directory. + # - Ensures version-controlled files are not modified or deleted. + # - Checks out the WordPress/WordPress repository. + # - Creates a directory for text files to be uploaded as an artifact. + # - Stores a list of files that differ in the build directory from WordPress/WordPress. + # - Stores a diff file comparing the build directory to WordPress/WordPress. + # - Saves the pull request number to a text file. + # - Uploads the generated files as an artifact. + compare-built-files: + name: Compare built files to WordPress/WordPress + runs-on: ubuntu-24.04 + permissions: + contents: read + timeout-minutes: 10 + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} + persist-credentials: false + + - name: Set up Node.js + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version-file: '.nvmrc' + cache: npm + + - name: Set up PHP + uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.37.0 + with: + php-version: '8.4' + coverage: none + + # 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 + uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # v4.0.0 + with: + custom-cache-suffix: $(/bin/date -u --date='last Mon' "+%F") + + - name: Log debug information + run: | + npm --version + node --version + git --version + + - name: Install npm Dependencies + run: npm ci + + - name: Build WordPress to run from build directory + run: npm run build + + - name: Ensure version-controlled files are not modified or deleted + run: git diff --exit-code + + - name: Checkout WordPress/WordPress + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + repository: 'WordPress/WordPress' + ref: ${{ github.base_ref == 'trunk' && 'master' || format( '{0}-branch', github.base_ref ) }} + path: ${{ github.workspace }}/build-server + show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} + persist-credentials: false + + - name: Create directory for artifacts + run: mkdir artifacts + + - name: Create a list of files that have changed + run: diff -rq ${{ github.workspace }}/build ${{ github.workspace }}/build-server | sed "s|${{ github.workspace }}/||g" > artifacts/file-changes.txt + + - name: Create a list of files that have changed + run: diff -r ${{ github.workspace }}/build ${{ github.workspace }}/build-server | sed "s|${{ github.workspace }}/||g" > artifacts/changes.diff + + - name: Save PR number + run: echo "${EVENT_NUMBER}" > ./artifacts/NR + env: + EVENT_NUMBER: ${{ github.event.number }} + + # Uploads the associated text files as an artifact. + - name: Upload comparison results as an artifact + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + if: ${{ github.repository == 'WordPress/wordpress-develop' && github.event_name == 'pull_request' }} + with: + name: build-server-comparison + path: artifacts/