From d31097c5f15a83551b6be2919ece199501cfddc0 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 16 Jan 2024 15:22:23 +0000 Subject: [PATCH 01/22] Initial script --- bin/generate-php-sync-issue.mjs | 455 ++++++++++++++++++++++++++++++++ 1 file changed, 455 insertions(+) create mode 100644 bin/generate-php-sync-issue.mjs diff --git a/bin/generate-php-sync-issue.mjs b/bin/generate-php-sync-issue.mjs new file mode 100644 index 00000000000000..5807b7bea7e42a --- /dev/null +++ b/bin/generate-php-sync-issue.mjs @@ -0,0 +1,455 @@ +/** + * External dependencies + */ + +import Octokit from '@octokit/rest'; +import fs from 'fs'; + +import { fileURLToPath } from 'url'; +import nodePath, { dirname } from 'path'; + +function getArg( argName ) { + const arg = process.argv.find( ( arg ) => + arg.startsWith( `--${ argName }=` ) + ); + return arg ? arg.split( '=' )[ 1 ] : null; +} + +const OWNER = 'wordpress'; +const REPO = 'gutenberg'; + +const IGNORED_PATHS = [ + 'lib/experiments-page.php', + 'packages/e2e-tests/plugins', +]; + +const DEBUG = false; + +const __filename = fileURLToPath( import.meta.url ); +const __dirname = dirname( __filename ); + +async function main() { + const authToken = getArg( 'token' ); + if ( ! authToken ) { + console.error( 'Aborted. The --token argument is required.' ); + process.exit( 1 ); + } + + const since = getArg( 'since' ); + if ( ! since ) { + console.error( 'Aborted. The --since argument is required.' ); + process.exit( 1 ); + } + + console.log( 'Welcome to the PHP Sync Issue Generator!' ); + const octokit = new Octokit( { + auth: authToken, + } ); + + console.log( '--------------------------------' ); + console.log( '• Running script...' ); + + // These should be paths where we expect to find PHP files that + // will require syncing to WordPress Core. This list should be + // extremely selective. + const paths = [ '/lib', '/packages/block-library', '/phpunit' ]; + + console.log( `• Fetching all commits made to ${ REPO } since: ${ since }` ); + let commits = await getAllCommitsFromPaths( + octokit, + OWNER, + REPO, + since, + paths + ); + + // Remove identical commits based on sha + commits = commits.reduce( ( acc, current ) => { + const x = acc.find( ( item ) => item.sha === current.sha ); + if ( ! x ) { + return acc.concat( [ current ] ); + } + return acc; + }, [] ); + + // Fetch the full commit data for each of the commits. + // This is because the /commits endpoint does not include the + // information about the `files` modified in the commit. + console.log( + `• Fetching full commit data for ${ commits.length } commits` + ); + const commitsWithCommitData = await Promise.all( + commits.map( async ( commit ) => { + const commitData = await getCommit( octokit, commit.sha ); + + // Our Issue links to the PRs associated with the commits so we must + // provide this data. We could also get the PR data from the commit data, + // using getPullRequestDataForCommit, but that requires yet another + // network request. Therefore we optimise for trying to build + // the PR URL from the commit data we have available. + const pullRequest = { + url: buildPRURL( commit ), + creator: commit?.author?.login || 'unknown', + }; + + if ( pullRequest ) { + commitData.pullRequest = pullRequest; + } + + return commitData; + } ) + ); + + const processResult = pipe( + processCommits, + removeNesting, + removeSinglePRLevels, + dedupePRsPerLevel, + removeEmptyLevels, + sortLevels + ); + + console.log( `• Processing ${ commitsWithCommitData.length } commits` ); + const result = processResult( commitsWithCommitData ); + + console.log( `• Generating Issue content` ); + const content = generateIssueContent( result ); + + // Write the Markdown content to a file + fs.writeFileSync( nodePath.join( __dirname, 'issueContent.md' ), content ); +} + +async function getAllCommitsFromPaths( octokit, owner, repo, since, paths ) { + let commits = []; + + for ( const path of paths ) { + const pathCommits = await getAllCommits( + octokit, + owner, + repo, + since, + path + ); + commits = [ ...commits, ...pathCommits ]; + } + + return commits; +} + +function buildPRURL( commit ) { + const prIdMatch = commit.commit.message.match( /\(#(\d+)\)/ ); + const prId = prIdMatch ? prIdMatch[ 1 ] : null; + return prId + ? `https://github.com/WordPress/gutenberg/pull/${ prId }` + : `[Commit](${ commit.html_url })`; +} + +function sortLevels( data ) { + function processLevel( levelData ) { + const processedData = {}; + + // Separate directories and files + const directories = {}; + const files = {}; + + for ( const [ key, value ] of Object.entries( levelData ) ) { + if ( key.endsWith( '.php' ) ) { + files[ key ] = Array.isArray( value ) + ? value + : processLevel( value ); + } else { + directories[ key ] = Array.isArray( value ) + ? value + : processLevel( value ); + } + } + + // Combine directories and files + Object.assign( processedData, directories, files ); + + return processedData; + } + + return processLevel( data ); +} + +function removeEmptyLevels( data ) { + function processLevel( levelData ) { + const processedData = {}; + + for ( const [ key, value ] of Object.entries( levelData ) ) { + if ( Array.isArray( value ) ) { + if ( value.length > 0 ) { + processedData[ key ] = value; + } + } else { + const processedLevel = processLevel( value ); + if ( Object.keys( processedLevel ).length > 0 ) { + processedData[ key ] = processedLevel; + } + } + } + + return processedData; + } + + return processLevel( data ); +} + +function dedupePRsPerLevel( data ) { + function processLevel( levelData ) { + const processedData = {}; + const prSet = new Set(); + + for ( const [ key, value ] of Object.entries( levelData ) ) { + if ( Array.isArray( value ) ) { + processedData[ key ] = value.filter( ( commit ) => { + if ( ! prSet.has( commit.pullRequest.url ) ) { + prSet.add( commit.pullRequest.url ); + return true; + } + return false; + } ); + } else { + processedData[ key ] = processLevel( value ); + } + } + + return processedData; + } + + return processLevel( data ); +} + +function removeSinglePRLevels( data ) { + function processLevel( levelData, parentData = null, parentKey = null ) { + const processedData = {}; + + for ( const [ key, value ] of Object.entries( levelData ) ) { + if ( Array.isArray( value ) ) { + if ( value.length === 1 && parentData && parentKey ) { + if ( ! Array.isArray( parentData[ parentKey ] ) ) { + parentData[ parentKey ] = []; + } + parentData[ parentKey ] = [ + ...parentData[ parentKey ], + ...value, + ]; + } else { + processedData[ key ] = value; + } + } else { + processedData[ key ] = processLevel( + value, + processedData, + key + ); + } + } + + return processedData; + } + + return processLevel( data ); +} + +function removeNesting( data, maxLevel = 3 ) { + function processLevel( levelData, level = 1 ) { + const processedData = {}; + + for ( const [ key, value ] of Object.entries( levelData ) ) { + if ( Array.isArray( value ) ) { + processedData[ key ] = value; + } else if ( level < maxLevel ) { + processedData[ key ] = processLevel( value, level + 1 ); + } else { + processedData[ key ] = flattenData( value ); + } + } + + return processedData; + } + + function flattenData( nestedData ) { + let flatData = []; + + for ( const value of Object.values( nestedData ) ) { + if ( Array.isArray( value ) ) { + flatData = [ ...flatData, ...value ]; + } else { + flatData = [ ...flatData, ...flattenData( value ) ]; + } + } + + return flatData; + } + + return processLevel( data ); +} + +function processCommits( commits ) { + const result = {}; + + commits.forEach( ( commit ) => { + // Skip commits without an associated pull request + if ( ! commit.pullRequest ) { + return; + } + commit.files.forEach( ( file ) => { + // Skip files that are not PHP files. + if ( ! file.filename.endsWith( '.php' ) ) { + return; + } + + // Skip files within specific packages. + if ( + IGNORED_PATHS.some( + ( path ) => + file.filename.startsWith( path ) || + file.filename === path + ) + ) { + return; + } + + const parts = file.filename.split( '/' ); + let current = result; + + // If the file is under 'phpunit', add it directly to the 'phpunit' key + // this is it's helpful to have a full list of commits that modify tests. + if ( parts.includes( 'phpunit' ) ) { + current.phpunit = current.phpunit || []; + current.phpunit = [ ...current.phpunit, commit ]; + return; + } + + for ( let i = 0; i < parts.length; i++ ) { + const part = parts[ i ]; + + // Skip 'src' part under 'block-library' + if ( part === 'src' && parts[ i - 1 ] === 'block-library' ) { + continue; + } + + if ( i === parts.length - 1 ) { + current[ part ] = current[ part ] || []; + current[ part ] = [ ...current[ part ], commit ]; + } else { + current[ part ] = current[ part ] || {}; + current = current[ part ]; + } + } + } ); + } ); + + return result; +} + +function formatPRLine( pr ) { + return `- [ ] ${ pr.url } - @/${ pr.creator } | Trac ticket | Core backport PR\n`; +} + +function formatHeading( level, key ) { + const emoji = key.endsWith( '.php' ) ? '📄' : '📁'; + return `${ '#'.repeat( level ) } ${ emoji } ${ key }\n\n`; +} + +function generateIssueContent( result, level = 1 ) { + let issueContent = ''; + let isFirstSection = true; + + for ( const [ key, value ] of Object.entries( result ) ) { + // Add horizontal rule divider between sections, but not before the first section + if ( level <= 2 && ! isFirstSection ) { + issueContent += '\n---\n'; + } + + issueContent += formatHeading( level, key ); + + if ( Array.isArray( value ) ) { + value.forEach( ( commit ) => { + issueContent += formatPRLine( commit.pullRequest ); + } ); + } else { + issueContent += generateIssueContent( value, level + 1 ); + } + + isFirstSection = false; + } + + return issueContent; +} + +async function getAllCommits( octokit, owner, repo, since, path ) { + let commits = []; + if ( DEBUG ) { + // just fetch the first 30 results using octokit.request + const { data } = await octokit.request( + 'GET /repos/{owner}/{repo}/commits', + { + owner, + repo, + since, + per_page: 30, + path, + } + ); + + commits = data; + } else { + // The paginate method will fetch all pages of results from the API. + // We limit the total results because we only fetch commits + // since a certain date (via the "since" param). + commits = await octokit.paginate( 'GET /repos/{owner}/{repo}/commits', { + owner, + repo, + since, + per_page: 100, + path, + } ); + } + + return commits; +} + +async function getCommit( octokit, sha ) { + const { data: commit } = await octokit.request( + 'GET /repos/{owner}/{repo}/commits/{sha}', + { + owner: OWNER, + repo: REPO, + sha, + } + ); + + return commit; +} + +async function getPullRequestDataForCommit( octokit, commitSha ) { + const { data: pullRequests } = await octokit.request( + 'GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls', + { + owner: OWNER, + repo: REPO, + commit_sha: commitSha, + } + ); + + // If a related Pull Request is found, return its URL and creator + if ( pullRequests.length > 0 ) { + const pullRequest = pullRequests[ 0 ]; + return { + url: pullRequest.html_url, + creator: pullRequest.user.login, + }; + } + + return null; +} + +const pipe = + ( ...fns ) => + ( x ) => + fns.reduce( ( v, f ) => f( v ), x ); + +main(); From 7a912c82a838751588db14e9348040309fbec153 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 16 Jan 2024 16:04:49 +0000 Subject: [PATCH 02/22] Linting fun --- bin/generate-php-sync-issue.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/generate-php-sync-issue.mjs b/bin/generate-php-sync-issue.mjs index 5807b7bea7e42a..080222f083d9f9 100644 --- a/bin/generate-php-sync-issue.mjs +++ b/bin/generate-php-sync-issue.mjs @@ -425,6 +425,7 @@ async function getCommit( octokit, sha ) { return commit; } +// eslint-disable-next-line no-unused-vars async function getPullRequestDataForCommit( octokit, commitSha ) { const { data: pullRequests } = await octokit.request( 'GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls', From 322c046579c384a33c13d8e7f9e0d2e1891d2295 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 16 Jan 2024 16:07:15 +0000 Subject: [PATCH 03/22] Add primitve docs to args --- bin/generate-php-sync-issue.mjs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bin/generate-php-sync-issue.mjs b/bin/generate-php-sync-issue.mjs index 080222f083d9f9..a2f350b2eafd46 100644 --- a/bin/generate-php-sync-issue.mjs +++ b/bin/generate-php-sync-issue.mjs @@ -31,13 +31,17 @@ const __dirname = dirname( __filename ); async function main() { const authToken = getArg( 'token' ); if ( ! authToken ) { - console.error( 'Aborted. The --token argument is required.' ); + console.error( + 'Aborted. The --token argument is required. See: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token' + ); process.exit( 1 ); } const since = getArg( 'since' ); if ( ! since ) { - console.error( 'Aborted. The --since argument is required.' ); + console.error( + 'Aborted. The --since argument is required (e.g. 2023-11-01).' + ); process.exit( 1 ); } From fbbddbfc53eac98e42c5bb27652bdaee24ace384 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 17 Jan 2024 10:04:32 +0000 Subject: [PATCH 04/22] Ensure since arg cannot be far into the past --- bin/generate-php-sync-issue.mjs | 36 ++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/bin/generate-php-sync-issue.mjs b/bin/generate-php-sync-issue.mjs index a2f350b2eafd46..200530006b91f7 100644 --- a/bin/generate-php-sync-issue.mjs +++ b/bin/generate-php-sync-issue.mjs @@ -23,7 +23,7 @@ const IGNORED_PATHS = [ 'packages/e2e-tests/plugins', ]; -const DEBUG = false; +const DEBUG = !! getArg( 'debug' ); const __filename = fileURLToPath( import.meta.url ); const __dirname = dirname( __filename ); @@ -32,20 +32,36 @@ async function main() { const authToken = getArg( 'token' ); if ( ! authToken ) { console.error( - 'Aborted. The --token argument is required. See: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token' + 'Error. The --token argument is required. See: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token' ); process.exit( 1 ); } - const since = getArg( 'since' ); - if ( ! since ) { + const sinceArg = getArg( 'since' ); + let since; + + if ( sinceArg ) { + if ( validateSince( sinceArg ) ) { + since = sinceArg; + } else { + console.error( + 'Error: The --since argument cannot be more than 2 months from the current date.' + ); + process.exit( 1 ); + } + } else { console.error( - 'Aborted. The --since argument is required (e.g. 2023-11-01).' + `Error. The --since argument is required (e.g. YYYY-MM-DD). This should be the date of the previous release's final RC.` ); process.exit( 1 ); } console.log( 'Welcome to the PHP Sync Issue Generator!' ); + + if ( DEBUG ) { + console.log( 'DEBUG MODE' ); + } + const octokit = new Octokit( { auth: authToken, } ); @@ -123,6 +139,16 @@ async function main() { fs.writeFileSync( nodePath.join( __dirname, 'issueContent.md' ), content ); } +function validateSince( sinceArg ) { + const maxMonths = 3; + + const sinceDate = new Date( sinceArg ); + const maxPreviousDate = new Date(); + maxPreviousDate.setMonth( maxPreviousDate.getMonth() - maxMonths ); + + return sinceDate >= maxPreviousDate; +} + async function getAllCommitsFromPaths( octokit, owner, repo, since, paths ) { let commits = []; From f949ebafc753ab7c637ac0ad8d697f7c267303c4 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 17 Jan 2024 10:25:50 +0000 Subject: [PATCH 05/22] Refactor to avoid passing around octokit vars --- bin/generate-php-sync-issue.mjs | 93 +++++++++++++-------------------- 1 file changed, 35 insertions(+), 58 deletions(-) diff --git a/bin/generate-php-sync-issue.mjs b/bin/generate-php-sync-issue.mjs index 200530006b91f7..b9c1d21bb0e6ec 100644 --- a/bin/generate-php-sync-issue.mjs +++ b/bin/generate-php-sync-issue.mjs @@ -28,8 +28,9 @@ const DEBUG = !! getArg( 'debug' ); const __filename = fileURLToPath( import.meta.url ); const __dirname = dirname( __filename ); +const authToken = getArg( 'token' ); + async function main() { - const authToken = getArg( 'token' ); if ( ! authToken ) { console.error( 'Error. The --token argument is required. See: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token' @@ -75,13 +76,7 @@ async function main() { const paths = [ '/lib', '/packages/block-library', '/phpunit' ]; console.log( `• Fetching all commits made to ${ REPO } since: ${ since }` ); - let commits = await getAllCommitsFromPaths( - octokit, - OWNER, - REPO, - since, - paths - ); + let commits = await getAllCommitsFromPaths( since, paths ); // Remove identical commits based on sha commits = commits.reduce( ( acc, current ) => { @@ -100,7 +95,7 @@ async function main() { ); const commitsWithCommitData = await Promise.all( commits.map( async ( commit ) => { - const commitData = await getCommit( octokit, commit.sha ); + const commitData = await getCommit( commit.sha ); // Our Issue links to the PRs associated with the commits so we must // provide this data. We could also get the PR data from the commit data, @@ -149,17 +144,30 @@ function validateSince( sinceArg ) { return sinceDate >= maxPreviousDate; } -async function getAllCommitsFromPaths( octokit, owner, repo, since, paths ) { +async function octokitPaginate( method, params ) { + return octokitRequest( method, params, { paginate: true } ); +} + +async function octokitRequest( method = '', params = {}, settings = {} ) { + const octokit = new Octokit( { auth: authToken } ); + params.owner = OWNER; + params.repo = REPO; + + const requestType = settings?.paginate ? 'paginate' : 'request'; + + const result = await octokit[ requestType ]( method, params ); + + if ( requestType === 'paginate' ) { + return result; + } + return result.data; +} + +async function getAllCommitsFromPaths( since, paths ) { let commits = []; for ( const path of paths ) { - const pathCommits = await getAllCommits( - octokit, - owner, - repo, - since, - path - ); + const pathCommits = await getAllCommits( since, path ); commits = [ ...commits, ...pathCommits ]; } @@ -410,49 +418,18 @@ function generateIssueContent( result, level = 1 ) { return issueContent; } -async function getAllCommits( octokit, owner, repo, since, path ) { - let commits = []; - if ( DEBUG ) { - // just fetch the first 30 results using octokit.request - const { data } = await octokit.request( - 'GET /repos/{owner}/{repo}/commits', - { - owner, - repo, - since, - per_page: 30, - path, - } - ); - - commits = data; - } else { - // The paginate method will fetch all pages of results from the API. - // We limit the total results because we only fetch commits - // since a certain date (via the "since" param). - commits = await octokit.paginate( 'GET /repos/{owner}/{repo}/commits', { - owner, - repo, - since, - per_page: 100, - path, - } ); - } - - return commits; +async function getAllCommits( since, path ) { + return await octokitPaginate( 'GET /repos/{owner}/{repo}/commits', { + since, + per_page: 30, + path, + } ); } -async function getCommit( octokit, sha ) { - const { data: commit } = await octokit.request( - 'GET /repos/{owner}/{repo}/commits/{sha}', - { - owner: OWNER, - repo: REPO, - sha, - } - ); - - return commit; +async function getCommit( sha ) { + return octokitRequest( 'GET /repos/{owner}/{repo}/commits/{sha}', { + sha, + } ); } // eslint-disable-next-line no-unused-vars From 803baa70d07c706c733c69bd5279a030351ad2a1 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 17 Jan 2024 10:27:50 +0000 Subject: [PATCH 06/22] Handle network errors --- bin/generate-php-sync-issue.mjs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/bin/generate-php-sync-issue.mjs b/bin/generate-php-sync-issue.mjs index b9c1d21bb0e6ec..ad2aeb1ab0974e 100644 --- a/bin/generate-php-sync-issue.mjs +++ b/bin/generate-php-sync-issue.mjs @@ -155,12 +155,19 @@ async function octokitRequest( method = '', params = {}, settings = {} ) { const requestType = settings?.paginate ? 'paginate' : 'request'; - const result = await octokit[ requestType ]( method, params ); + try { + const result = await octokit[ requestType ]( method, params ); - if ( requestType === 'paginate' ) { - return result; + if ( requestType === 'paginate' ) { + return result; + } + return result.data; + } catch ( error ) { + console.error( + `Error making request to ${ method }: ${ error.message }` + ); + process.exit( 1 ); } - return result.data; } async function getAllCommitsFromPaths( since, paths ) { From c7def9dfba801c5e733020382d2c8f7429c54af7 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 17 Jan 2024 10:31:35 +0000 Subject: [PATCH 07/22] =?UTF-8?q?Refactor=20method=20names=20to=20use=20?= =?UTF-8?q?=E2=80=9Cfetch=E2=80=9D=20terminology?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/generate-php-sync-issue.mjs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bin/generate-php-sync-issue.mjs b/bin/generate-php-sync-issue.mjs index ad2aeb1ab0974e..afbcd652d01c07 100644 --- a/bin/generate-php-sync-issue.mjs +++ b/bin/generate-php-sync-issue.mjs @@ -76,7 +76,7 @@ async function main() { const paths = [ '/lib', '/packages/block-library', '/phpunit' ]; console.log( `• Fetching all commits made to ${ REPO } since: ${ since }` ); - let commits = await getAllCommitsFromPaths( since, paths ); + let commits = await fetchAllCommitsFromPaths( since, paths ); // Remove identical commits based on sha commits = commits.reduce( ( acc, current ) => { @@ -95,7 +95,7 @@ async function main() { ); const commitsWithCommitData = await Promise.all( commits.map( async ( commit ) => { - const commitData = await getCommit( commit.sha ); + const commitData = await fetchCommit( commit.sha ); // Our Issue links to the PRs associated with the commits so we must // provide this data. We could also get the PR data from the commit data, @@ -170,11 +170,11 @@ async function octokitRequest( method = '', params = {}, settings = {} ) { } } -async function getAllCommitsFromPaths( since, paths ) { +async function fetchAllCommitsFromPaths( since, paths ) { let commits = []; for ( const path of paths ) { - const pathCommits = await getAllCommits( since, path ); + const pathCommits = await fetchAllCommits( since, path ); commits = [ ...commits, ...pathCommits ]; } @@ -425,7 +425,7 @@ function generateIssueContent( result, level = 1 ) { return issueContent; } -async function getAllCommits( since, path ) { +async function fetchAllCommits( since, path ) { return await octokitPaginate( 'GET /repos/{owner}/{repo}/commits', { since, per_page: 30, @@ -433,7 +433,7 @@ async function getAllCommits( since, path ) { } ); } -async function getCommit( sha ) { +async function fetchCommit( sha ) { return octokitRequest( 'GET /repos/{owner}/{repo}/commits/{sha}', { sha, } ); From ace8c8578cae917028e0ad5dc7aaa1c627fd714f Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 17 Jan 2024 10:40:29 +0000 Subject: [PATCH 08/22] Ignore block-library as these changes are handled automatically --- bin/generate-php-sync-issue.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/generate-php-sync-issue.mjs b/bin/generate-php-sync-issue.mjs index afbcd652d01c07..f07cf2fae7410c 100644 --- a/bin/generate-php-sync-issue.mjs +++ b/bin/generate-php-sync-issue.mjs @@ -21,6 +21,7 @@ const REPO = 'gutenberg'; const IGNORED_PATHS = [ 'lib/experiments-page.php', 'packages/e2e-tests/plugins', + 'packages/block-library', // this is handled automatically. ]; const DEBUG = !! getArg( 'debug' ); @@ -73,7 +74,7 @@ async function main() { // These should be paths where we expect to find PHP files that // will require syncing to WordPress Core. This list should be // extremely selective. - const paths = [ '/lib', '/packages/block-library', '/phpunit' ]; + const paths = [ '/lib', '/phpunit' ]; console.log( `• Fetching all commits made to ${ REPO } since: ${ since }` ); let commits = await fetchAllCommitsFromPaths( since, paths ); From 4567103d70c72b4cb82c0a0927d4214c686bf7e5 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 17 Jan 2024 11:27:43 +0000 Subject: [PATCH 09/22] Allow sections with a single PR to remain --- bin/generate-php-sync-issue.mjs | 36 ++------------------------------- 1 file changed, 2 insertions(+), 34 deletions(-) diff --git a/bin/generate-php-sync-issue.mjs b/bin/generate-php-sync-issue.mjs index f07cf2fae7410c..bae1224557e812 100644 --- a/bin/generate-php-sync-issue.mjs +++ b/bin/generate-php-sync-issue.mjs @@ -119,7 +119,6 @@ async function main() { const processResult = pipe( processCommits, removeNesting, - removeSinglePRLevels, dedupePRsPerLevel, removeEmptyLevels, sortLevels @@ -267,38 +266,6 @@ function dedupePRsPerLevel( data ) { return processLevel( data ); } -function removeSinglePRLevels( data ) { - function processLevel( levelData, parentData = null, parentKey = null ) { - const processedData = {}; - - for ( const [ key, value ] of Object.entries( levelData ) ) { - if ( Array.isArray( value ) ) { - if ( value.length === 1 && parentData && parentKey ) { - if ( ! Array.isArray( parentData[ parentKey ] ) ) { - parentData[ parentKey ] = []; - } - parentData[ parentKey ] = [ - ...parentData[ parentKey ], - ...value, - ]; - } else { - processedData[ key ] = value; - } - } else { - processedData[ key ] = processLevel( - value, - processedData, - key - ); - } - } - - return processedData; - } - - return processLevel( data ); -} - function removeNesting( data, maxLevel = 3 ) { function processLevel( levelData, level = 1 ) { const processedData = {}; @@ -347,7 +314,6 @@ function processCommits( commits ) { return; } - // Skip files within specific packages. if ( IGNORED_PATHS.some( ( path ) => @@ -355,10 +321,12 @@ function processCommits( commits ) { file.filename === path ) ) { + // Skip files within specific packages. return; } const parts = file.filename.split( '/' ); + let current = result; // If the file is under 'phpunit', add it directly to the 'phpunit' key From 66537fb48911c8c5a04027e891be972a03a3d232 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 17 Jan 2024 11:29:20 +0000 Subject: [PATCH 10/22] Allow any commit that modifies tests to be also under another section --- bin/generate-php-sync-issue.mjs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bin/generate-php-sync-issue.mjs b/bin/generate-php-sync-issue.mjs index bae1224557e812..0b0ba7fff46d0c 100644 --- a/bin/generate-php-sync-issue.mjs +++ b/bin/generate-php-sync-issue.mjs @@ -329,12 +329,11 @@ function processCommits( commits ) { let current = result; - // If the file is under 'phpunit', add it directly to the 'phpunit' key - // this is it's helpful to have a full list of commits that modify tests. + // If the file is under 'phpunit', always add it to the 'phpunit' key + // as it's helpful to have a full list of commits that modify tests. if ( parts.includes( 'phpunit' ) ) { current.phpunit = current.phpunit || []; current.phpunit = [ ...current.phpunit, commit ]; - return; } for ( let i = 0; i < parts.length; i++ ) { From 71cb3ef2dda063bebf8fe6db4814033e8646c4b7 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 17 Jan 2024 11:43:54 +0000 Subject: [PATCH 11/22] Remove block-library specific handling --- bin/generate-php-sync-issue.mjs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/bin/generate-php-sync-issue.mjs b/bin/generate-php-sync-issue.mjs index 0b0ba7fff46d0c..afaa1097cc11b2 100644 --- a/bin/generate-php-sync-issue.mjs +++ b/bin/generate-php-sync-issue.mjs @@ -339,11 +339,6 @@ function processCommits( commits ) { for ( let i = 0; i < parts.length; i++ ) { const part = parts[ i ]; - // Skip 'src' part under 'block-library' - if ( part === 'src' && parts[ i - 1 ] === 'block-library' ) { - continue; - } - if ( i === parts.length - 1 ) { current[ part ] = current[ part ] || []; current[ part ] = [ ...current[ part ], commit ]; From 5e05e7cf6c787022c408cbba1057245c30fe6266 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 18 Jan 2024 09:30:56 +0000 Subject: [PATCH 12/22] Use const to define max nesting level --- bin/generate-php-sync-issue.mjs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bin/generate-php-sync-issue.mjs b/bin/generate-php-sync-issue.mjs index afaa1097cc11b2..843bb236eb24aa 100644 --- a/bin/generate-php-sync-issue.mjs +++ b/bin/generate-php-sync-issue.mjs @@ -24,6 +24,8 @@ const IGNORED_PATHS = [ 'packages/block-library', // this is handled automatically. ]; +const MAX_NESTING_LEVEL = 3; + const DEBUG = !! getArg( 'debug' ); const __filename = fileURLToPath( import.meta.url ); @@ -118,7 +120,7 @@ async function main() { const processResult = pipe( processCommits, - removeNesting, + reduceNesting, dedupePRsPerLevel, removeEmptyLevels, sortLevels @@ -266,14 +268,14 @@ function dedupePRsPerLevel( data ) { return processLevel( data ); } -function removeNesting( data, maxLevel = 3 ) { +function reduceNesting( data ) { function processLevel( levelData, level = 1 ) { const processedData = {}; for ( const [ key, value ] of Object.entries( levelData ) ) { if ( Array.isArray( value ) ) { processedData[ key ] = value; - } else if ( level < maxLevel ) { + } else if ( level < MAX_NESTING_LEVEL ) { processedData[ key ] = processLevel( value, level + 1 ); } else { processedData[ key ] = flattenData( value ); From 780b8424e61219230434bb57dc2ae679470db16d Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 18 Jan 2024 09:35:49 +0000 Subject: [PATCH 13/22] Automate ignore of previous release compat dir --- bin/generate-php-sync-issue.mjs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/bin/generate-php-sync-issue.mjs b/bin/generate-php-sync-issue.mjs index 843bb236eb24aa..25985422125102 100644 --- a/bin/generate-php-sync-issue.mjs +++ b/bin/generate-php-sync-issue.mjs @@ -32,6 +32,7 @@ const __filename = fileURLToPath( import.meta.url ); const __dirname = dirname( __filename ); const authToken = getArg( 'token' ); +const stableWPRelease = getArg( 'wpstable' ); async function main() { if ( ! authToken ) { @@ -41,6 +42,13 @@ async function main() { process.exit( 1 ); } + if ( ! stableWPRelease ) { + console.error( + 'Error. The --wpstable argument is required. It should be the current stable WordPress release (e.g. 6.4).' + ); + process.exit( 1 ); + } + const sinceArg = getArg( 'since' ); let since; @@ -305,6 +313,12 @@ function reduceNesting( data ) { function processCommits( commits ) { const result = {}; + // This dir sholud be ignored, since whatever is in there is already in core. + // It exists to provide compatibility for older releases, because we have to + // support the current and the previous WP versions. + // See: https://github.com/WordPress/gutenberg/pull/57890#pullrequestreview-1828994247. + const prevReleaseCompatDirToIgnore = `lib/compat/wordpress-${ stableWPRelease }`; + commits.forEach( ( commit ) => { // Skip commits without an associated pull request if ( ! commit.pullRequest ) { @@ -317,7 +331,7 @@ function processCommits( commits ) { } if ( - IGNORED_PATHS.some( + [ ...IGNORED_PATHS, prevReleaseCompatDirToIgnore ].some( ( path ) => file.filename.startsWith( path ) || file.filename === path From a9872a1d4d41d705644a20b91a0156c2a63f5788 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 18 Jan 2024 09:40:39 +0000 Subject: [PATCH 14/22] Exclude load.php as it is Plugin specific --- bin/generate-php-sync-issue.mjs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bin/generate-php-sync-issue.mjs b/bin/generate-php-sync-issue.mjs index 25985422125102..1f12224a1206f7 100644 --- a/bin/generate-php-sync-issue.mjs +++ b/bin/generate-php-sync-issue.mjs @@ -19,8 +19,9 @@ const OWNER = 'wordpress'; const REPO = 'gutenberg'; const IGNORED_PATHS = [ - 'lib/experiments-page.php', - 'packages/e2e-tests/plugins', + 'lib/load.php', // plugin specific code. + 'lib/experiments-page.php', // experiments are plugin specific. + 'packages/e2e-tests/plugins', // PHP files related to e2e tests only. 'packages/block-library', // this is handled automatically. ]; From d6cdb4f421d3f563f5812f41234229e3b6903767 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 18 Jan 2024 09:57:37 +0000 Subject: [PATCH 15/22] Remove ping debugging --- bin/generate-php-sync-issue.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/generate-php-sync-issue.mjs b/bin/generate-php-sync-issue.mjs index 1f12224a1206f7..b0d6a5dad1f880 100644 --- a/bin/generate-php-sync-issue.mjs +++ b/bin/generate-php-sync-issue.mjs @@ -371,7 +371,7 @@ function processCommits( commits ) { } function formatPRLine( pr ) { - return `- [ ] ${ pr.url } - @/${ pr.creator } | Trac ticket | Core backport PR\n`; + return `- [ ] ${ pr.url } - @${ pr.creator } | Trac ticket | Core backport PR\n`; } function formatHeading( level, key ) { From 9fe1f10ecb19263c8643133c3091d116d1948a18 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 18 Jan 2024 10:19:06 +0000 Subject: [PATCH 16/22] Prepare for requiring full PR label data --- bin/generate-php-sync-issue.mjs | 40 +++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/bin/generate-php-sync-issue.mjs b/bin/generate-php-sync-issue.mjs index b0d6a5dad1f880..151c5e8fb480f4 100644 --- a/bin/generate-php-sync-issue.mjs +++ b/bin/generate-php-sync-issue.mjs @@ -9,14 +9,15 @@ import { fileURLToPath } from 'url'; import nodePath, { dirname } from 'path'; function getArg( argName ) { - const arg = process.argv.find( ( arg ) => - arg.startsWith( `--${ argName }=` ) + const arg = process.argv.find( ( _arg ) => + _arg.startsWith( `--${ argName }=` ) ); return arg ? arg.split( '=' )[ 1 ] : null; } const OWNER = 'wordpress'; const REPO = 'gutenberg'; +const MAX_MONTHS_TO_QUERY = 4; const IGNORED_PATHS = [ 'lib/load.php', // plugin specific code. @@ -58,7 +59,7 @@ async function main() { since = sinceArg; } else { console.error( - 'Error: The --since argument cannot be more than 2 months from the current date.' + `Error: The --since argument cannot be more than ${ MAX_MONTHS_TO_QUERY } months from the current date.` ); process.exit( 1 ); } @@ -109,14 +110,24 @@ async function main() { commits.map( async ( commit ) => { const commitData = await fetchCommit( commit.sha ); + // In the future we will want to exclude PRs based on label + // so we will need to fetch the full PR data for each commit. + // For now we can just set this to null. + const fullPRData = null; + // const fullPRData = getPullRequestDataForCommit( commit.sha ); + // Our Issue links to the PRs associated with the commits so we must // provide this data. We could also get the PR data from the commit data, // using getPullRequestDataForCommit, but that requires yet another // network request. Therefore we optimise for trying to build // the PR URL from the commit data we have available. const pullRequest = { - url: buildPRURL( commit ), - creator: commit?.author?.login || 'unknown', + url: fullPRData?.html_url || buildPRURL( commit ), + creator: + fullPRData?.user?.login || + commit?.author?.login || + 'unknown', + labels: fullPRData?.labels || [], }; if ( pullRequest ) { @@ -146,11 +157,11 @@ async function main() { } function validateSince( sinceArg ) { - const maxMonths = 3; - const sinceDate = new Date( sinceArg ); const maxPreviousDate = new Date(); - maxPreviousDate.setMonth( maxPreviousDate.getMonth() - maxMonths ); + maxPreviousDate.setMonth( + maxPreviousDate.getMonth() - MAX_MONTHS_TO_QUERY + ); return sinceDate >= maxPreviousDate; } @@ -322,7 +333,7 @@ function processCommits( commits ) { commits.forEach( ( commit ) => { // Skip commits without an associated pull request - if ( ! commit.pullRequest ) { + if ( ! commit?.pullRequest ) { return; } commit.files.forEach( ( file ) => { @@ -420,12 +431,10 @@ async function fetchCommit( sha ) { } // eslint-disable-next-line no-unused-vars -async function getPullRequestDataForCommit( octokit, commitSha ) { - const { data: pullRequests } = await octokit.request( +async function getPullRequestDataForCommit( commitSha ) { + const pullRequests = octokitRequest( 'GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls', { - owner: OWNER, - repo: REPO, commit_sha: commitSha, } ); @@ -433,10 +442,7 @@ async function getPullRequestDataForCommit( octokit, commitSha ) { // If a related Pull Request is found, return its URL and creator if ( pullRequests.length > 0 ) { const pullRequest = pullRequests[ 0 ]; - return { - url: pullRequest.html_url, - creator: pullRequest.user.login, - }; + return pullRequest; } return null; From a8cbfef484da54786ce6e5d2839955f72b2e4916 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 18 Jan 2024 10:43:24 +0000 Subject: [PATCH 17/22] Flag PRs that may have already been backported --- bin/generate-php-sync-issue.mjs | 63 ++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/bin/generate-php-sync-issue.mjs b/bin/generate-php-sync-issue.mjs index 151c5e8fb480f4..62c4b8fd4f5748 100644 --- a/bin/generate-php-sync-issue.mjs +++ b/bin/generate-php-sync-issue.mjs @@ -55,7 +55,7 @@ async function main() { let since; if ( sinceArg ) { - if ( validateSince( sinceArg ) ) { + if ( validateDate( sinceArg ) ) { since = sinceArg; } else { console.error( @@ -70,16 +70,34 @@ async function main() { process.exit( 1 ); } + const lastRcDateArg = getArg( 'lastrcdate' ); + let lastRcDate; + + if ( lastRcDateArg ) { + if ( + validateDate( lastRcDateArg ) && + isAfter( lastRcDateArg, since ) + ) { + lastRcDate = lastRcDateArg; + } else { + console.error( + `Error: The --lastrcdate argument must be a date after the --since date.` + ); + process.exit( 1 ); + } + } else { + console.error( + `Error: The --lastrcdate argument is required (e.g. YYYY-MM-DD).` + ); + process.exit( 1 ); + } + console.log( 'Welcome to the PHP Sync Issue Generator!' ); if ( DEBUG ) { console.log( 'DEBUG MODE' ); } - const octokit = new Octokit( { - auth: authToken, - } ); - console.log( '--------------------------------' ); console.log( '• Running script...' ); @@ -134,6 +152,18 @@ async function main() { commitData.pullRequest = pullRequest; } + // This is temporarily required because PRs merged between Beta 1 (since) + // and the final RC may have already been manually backported to Core. + // This is however no reliable way to identify these PRs as the `Cackport to WP beta/RC` + // label is manually removed once the PR has been backported. + // In future releases we will instead retain the label and add a **new** label + // to indicate the PR has been backported to Core. + // As a result, in the future we will be able to exclude any PRs that have + // the `Backport to WP beta/RC` label. + if ( isAfter( lastRcDate, commitData.commit.committer.date ) ) { + commitData.isBeforeLastRCDate = true; + } + return commitData; } ) ); @@ -156,7 +186,18 @@ async function main() { fs.writeFileSync( nodePath.join( __dirname, 'issueContent.md' ), content ); } -function validateSince( sinceArg ) { +/** + * Checks if the first date is after the second date. + * + * @param {string} date1 - The first date. + * @param {string} date2 - The second date. + * @return {boolean} - Returns true if the first date is after the second date, false otherwise. + */ +function isAfter( date1, date2 ) { + return new Date( date1 ) > new Date( date2 ); +} + +function validateDate( sinceArg ) { const sinceDate = new Date( sinceArg ); const maxPreviousDate = new Date(); maxPreviousDate.setMonth( @@ -381,8 +422,12 @@ function processCommits( commits ) { return result; } -function formatPRLine( pr ) { - return `- [ ] ${ pr.url } - @${ pr.creator } | Trac ticket | Core backport PR\n`; +function formatPRLine( { pullRequest: pr, isBeforeLastRCDate } ) { + return `- [ ] ${ pr.url } - @${ + pr.creator + } | Trac ticket | Core backport PR ${ + isBeforeLastRCDate && '(⚠️ Check for existing backport in Trac)' + }\n`; } function formatHeading( level, key ) { @@ -404,7 +449,7 @@ function generateIssueContent( result, level = 1 ) { if ( Array.isArray( value ) ) { value.forEach( ( commit ) => { - issueContent += formatPRLine( commit.pullRequest ); + issueContent += formatPRLine( commit ); } ); } else { issueContent += generateIssueContent( value, level + 1 ); From 6bfb96c749a0d0e3db9544898168bd111a7a29ea Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 18 Jan 2024 10:51:43 +0000 Subject: [PATCH 18/22] Fix output of `undefined` --- bin/generate-php-sync-issue.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/generate-php-sync-issue.mjs b/bin/generate-php-sync-issue.mjs index 62c4b8fd4f5748..3d6d00633fb1d8 100644 --- a/bin/generate-php-sync-issue.mjs +++ b/bin/generate-php-sync-issue.mjs @@ -426,7 +426,7 @@ function formatPRLine( { pullRequest: pr, isBeforeLastRCDate } ) { return `- [ ] ${ pr.url } - @${ pr.creator } | Trac ticket | Core backport PR ${ - isBeforeLastRCDate && '(⚠️ Check for existing backport in Trac)' + isBeforeLastRCDate ? '(⚠️ Check for existing backport in Trac)' : '' }\n`; } From 39ec158a3f34d83ea683cf20b2dcbb8f8c7c420d Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 18 Jan 2024 10:52:50 +0000 Subject: [PATCH 19/22] Tweak warning --- bin/generate-php-sync-issue.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/generate-php-sync-issue.mjs b/bin/generate-php-sync-issue.mjs index 3d6d00633fb1d8..a11730297c51e2 100644 --- a/bin/generate-php-sync-issue.mjs +++ b/bin/generate-php-sync-issue.mjs @@ -426,7 +426,7 @@ function formatPRLine( { pullRequest: pr, isBeforeLastRCDate } ) { return `- [ ] ${ pr.url } - @${ pr.creator } | Trac ticket | Core backport PR ${ - isBeforeLastRCDate ? '(⚠️ Check for existing backport in Trac)' : '' + isBeforeLastRCDate ? '(⚠️ Check for existing WP Core backport)' : '' }\n`; } From 5bff9d08c4ecd14cc5ee02ba17aa91186da53dbb Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 19 Jan 2024 10:01:20 +0000 Subject: [PATCH 20/22] Ignore PRs with given labels --- bin/generate-php-sync-issue.mjs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/bin/generate-php-sync-issue.mjs b/bin/generate-php-sync-issue.mjs index a11730297c51e2..30b04b5ac68ac9 100644 --- a/bin/generate-php-sync-issue.mjs +++ b/bin/generate-php-sync-issue.mjs @@ -19,6 +19,7 @@ const OWNER = 'wordpress'; const REPO = 'gutenberg'; const MAX_MONTHS_TO_QUERY = 4; +// The following paths will be ignored when generating the issue content. const IGNORED_PATHS = [ 'lib/load.php', // plugin specific code. 'lib/experiments-page.php', // experiments are plugin specific. @@ -26,9 +27,10 @@ const IGNORED_PATHS = [ 'packages/block-library', // this is handled automatically. ]; -const MAX_NESTING_LEVEL = 3; +// PRs containing the following labels will be ignored when generating the issue content. +const LABELS_TO_IGNORE = [ 'Backport from WordPress Core' ]; -const DEBUG = !! getArg( 'debug' ); +const MAX_NESTING_LEVEL = 3; const __filename = fileURLToPath( import.meta.url ); const __dirname = dirname( __filename ); @@ -94,10 +96,6 @@ async function main() { console.log( 'Welcome to the PHP Sync Issue Generator!' ); - if ( DEBUG ) { - console.log( 'DEBUG MODE' ); - } - console.log( '--------------------------------' ); console.log( '• Running script...' ); @@ -139,7 +137,7 @@ async function main() { // using getPullRequestDataForCommit, but that requires yet another // network request. Therefore we optimise for trying to build // the PR URL from the commit data we have available. - const pullRequest = { + commitData.pullRequest = { url: fullPRData?.html_url || buildPRURL( commit ), creator: fullPRData?.user?.login || @@ -148,8 +146,14 @@ async function main() { labels: fullPRData?.labels || [], }; - if ( pullRequest ) { - commitData.pullRequest = pullRequest; + // if the PR labels contain any of the labels to ignore, skip this commit + // by returning null. + if ( + commitData.pullRequest.labels.some( ( label ) => + LABELS_TO_IGNORE.includes( label.name ) + ) + ) { + return null; } // This is temporarily required because PRs merged between Beta 1 (since) From cf8e370e366e8b351ab7e4e4427dbbf01a357029 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 19 Jan 2024 10:04:43 +0000 Subject: [PATCH 21/22] Update instructions for new label --- bin/generate-php-sync-issue.mjs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bin/generate-php-sync-issue.mjs b/bin/generate-php-sync-issue.mjs index 30b04b5ac68ac9..d910d86101edbf 100644 --- a/bin/generate-php-sync-issue.mjs +++ b/bin/generate-php-sync-issue.mjs @@ -158,12 +158,12 @@ async function main() { // This is temporarily required because PRs merged between Beta 1 (since) // and the final RC may have already been manually backported to Core. - // This is however no reliable way to identify these PRs as the `Cackport to WP beta/RC` + // This is however no reliable way to identify these PRs as the `Backport to WP beta/RC` // label is manually removed once the PR has been backported. - // In future releases we will instead retain the label and add a **new** label - // to indicate the PR has been backported to Core. + // In future releases we will add a **new** label `Backported` + // to indicate the PR was backported to Core. // As a result, in the future we will be able to exclude any PRs that have - // the `Backport to WP beta/RC` label. + // already been backported using the `Backported` label. if ( isAfter( lastRcDate, commitData.commit.committer.date ) ) { commitData.isBeforeLastRCDate = true; } From 655af32c1f1f930a63f0d7bab90d597ae53dd9ec Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 19 Jan 2024 10:08:11 +0000 Subject: [PATCH 22/22] Update since arg description --- bin/generate-php-sync-issue.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/generate-php-sync-issue.mjs b/bin/generate-php-sync-issue.mjs index d910d86101edbf..a45d77d354107c 100644 --- a/bin/generate-php-sync-issue.mjs +++ b/bin/generate-php-sync-issue.mjs @@ -67,7 +67,7 @@ async function main() { } } else { console.error( - `Error. The --since argument is required (e.g. YYYY-MM-DD). This should be the date of the previous release's final RC.` + `Error. The --since argument is required (e.g. YYYY-MM-DD). This should be the date of the final Gutenberg release that was included in the last stable WP Core release (see https://developer.wordpress.org/block-editor/contributors/versions-in-wordpress/).` ); process.exit( 1 ); }