Changelog CLI: Add command to make entry from PR description (#38357)
This commit is contained in:
parent
2f6ff0f3e6
commit
0cfbf0a653
937
pnpm-lock.yaml
937
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -18,6 +18,7 @@
|
|||
"commander": "^10.0.1",
|
||||
"dotenv": "^10.0.0",
|
||||
"figlet": "^1.6.0",
|
||||
"glob": "^10.2.4",
|
||||
"graphql": "^16.6.0",
|
||||
"octokit": "^2.0.14",
|
||||
"ora": "^5.4.1",
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
# Changelog CLI Utility
|
||||
|
||||
CLI for creating changelog entries from pull request details.
|
||||
|
||||
Usage: `pnpm utils changefile <pr-number>`
|
|
@ -0,0 +1,196 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Command } from '@commander-js/extra-typings';
|
||||
import { execSync } from 'child_process';
|
||||
import simpleGit from 'simple-git';
|
||||
import nodePath from 'path';
|
||||
import { existsSync } from 'fs';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Logger } from '../core/logger';
|
||||
import { isGithubCI } from '../core/environment';
|
||||
import { cloneAuthenticatedRepo, checkoutRemoteBranch } from '../core/git';
|
||||
import {
|
||||
getPullRequestData,
|
||||
shouldAutomateChangelog,
|
||||
getChangelogDetails,
|
||||
} from './lib/github';
|
||||
import {
|
||||
getAllProjectPaths,
|
||||
getTouchedProjectsRequiringChangelog,
|
||||
} from './lib/projects';
|
||||
|
||||
const program = new Command( 'changefile' )
|
||||
.description( 'Changelog utilities' )
|
||||
.option(
|
||||
'-o --owner <owner>',
|
||||
'Repository owner. Default: woocommerce',
|
||||
'woocommerce'
|
||||
)
|
||||
.option(
|
||||
'-n --name <name>',
|
||||
'Repository name. Default: woocommerce',
|
||||
'woocommerce'
|
||||
)
|
||||
.option(
|
||||
'-d --dev-repo-path <devRepoPath>',
|
||||
'Path to existing repo. Use this option to avoid cloning a fresh repo for development purposes. Note that using this option assumes dependencies are already installed.'
|
||||
)
|
||||
.argument( '<pr-number>', 'Pull request number' )
|
||||
.action(
|
||||
async (
|
||||
prNumber: string,
|
||||
options: {
|
||||
owner: string;
|
||||
name: string;
|
||||
devRepoPath?: string;
|
||||
}
|
||||
) => {
|
||||
const { owner, name, devRepoPath } = options;
|
||||
Logger.startTask(
|
||||
`Getting pull request data for PR number ${ prNumber }`
|
||||
);
|
||||
const { prBody, headOwner, branch, fileName, head, base } =
|
||||
await getPullRequestData( { owner, name }, prNumber );
|
||||
|
||||
Logger.endTask();
|
||||
|
||||
if ( ! shouldAutomateChangelog( prBody ) ) {
|
||||
Logger.notice(
|
||||
`PR #${ prNumber } does not have the "Automatically create a changelog entry from the details" checkbox checked. No changelog will be created.`
|
||||
);
|
||||
|
||||
process.exit( 0 );
|
||||
}
|
||||
|
||||
const { significance, type, message, comment } =
|
||||
getChangelogDetails( prBody );
|
||||
|
||||
Logger.startTask(
|
||||
`Making a temporary clone of '${ headOwner }/${ name }'`
|
||||
);
|
||||
const tmpRepoPath = devRepoPath
|
||||
? devRepoPath
|
||||
: await cloneAuthenticatedRepo(
|
||||
{ owner: headOwner, name },
|
||||
true
|
||||
);
|
||||
|
||||
Logger.endTask();
|
||||
|
||||
Logger.notice(
|
||||
`Temporary clone of '${ headOwner }/${ name }' created at ${ tmpRepoPath }`
|
||||
);
|
||||
|
||||
// When a devRepoPath is provided, assume that the dependencies are already installed.
|
||||
if ( ! devRepoPath ) {
|
||||
Logger.notice( `Installing dependencies in ${ tmpRepoPath }` );
|
||||
execSync( 'pnpm install', {
|
||||
cwd: tmpRepoPath,
|
||||
stdio: 'inherit',
|
||||
} );
|
||||
}
|
||||
|
||||
Logger.notice( `Checking out remote branch ${ branch }` );
|
||||
await checkoutRemoteBranch( tmpRepoPath, branch );
|
||||
|
||||
Logger.notice(
|
||||
`Getting all touched projects requiring a changelog`
|
||||
);
|
||||
|
||||
const touchedProjectsRequiringChangelog =
|
||||
await getTouchedProjectsRequiringChangelog(
|
||||
tmpRepoPath,
|
||||
base,
|
||||
head,
|
||||
fileName
|
||||
);
|
||||
|
||||
try {
|
||||
const allProjectPaths = await getAllProjectPaths( tmpRepoPath );
|
||||
|
||||
// Remove any already existing changelog files in case a change is reverted and the entry is no longer needed.
|
||||
allProjectPaths.forEach( ( projectPath ) => {
|
||||
const path = nodePath.join(
|
||||
tmpRepoPath,
|
||||
projectPath,
|
||||
'changelog',
|
||||
fileName
|
||||
);
|
||||
|
||||
if ( existsSync( path ) ) {
|
||||
Logger.notice(
|
||||
`Remove existing changelog file ${ path }`
|
||||
);
|
||||
|
||||
execSync( `rm ${ path }`, {
|
||||
cwd: tmpRepoPath,
|
||||
stdio: 'inherit',
|
||||
} );
|
||||
}
|
||||
} );
|
||||
|
||||
touchedProjectsRequiringChangelog.forEach( ( project ) => {
|
||||
Logger.notice(
|
||||
`Running changelog command for ${ project }`
|
||||
);
|
||||
const messageExpression = message
|
||||
? `-e "${ message }"`
|
||||
: '';
|
||||
const commentExpression = comment
|
||||
? `-c "${ comment }"`
|
||||
: '';
|
||||
const cmd = `pnpm --filter=${ project } run changelog add -f ${ fileName } -s ${ significance } -t ${ type } ${ messageExpression } ${ commentExpression } -n`;
|
||||
execSync( cmd, { cwd: tmpRepoPath, stdio: 'inherit' } );
|
||||
} );
|
||||
} catch ( e ) {
|
||||
Logger.error( e );
|
||||
}
|
||||
|
||||
Logger.notice(
|
||||
`Changelogs created for ${ touchedProjectsRequiringChangelog.join(
|
||||
', '
|
||||
) }`
|
||||
);
|
||||
|
||||
const git = simpleGit( {
|
||||
baseDir: tmpRepoPath,
|
||||
config: [ 'core.hooksPath=/dev/null' ],
|
||||
} );
|
||||
|
||||
if ( isGithubCI() ) {
|
||||
await git.raw(
|
||||
'config',
|
||||
'--global',
|
||||
'user.email',
|
||||
'github-actions@github.com'
|
||||
);
|
||||
await git.raw(
|
||||
'config',
|
||||
'--global',
|
||||
'user.name',
|
||||
'github-actions'
|
||||
);
|
||||
}
|
||||
|
||||
const shortStatus = await git.raw( [ 'status', '--short' ] );
|
||||
|
||||
if ( shortStatus.length === 0 ) {
|
||||
Logger.notice(
|
||||
`No changes in changelog files. Skipping commit and push.`
|
||||
);
|
||||
process.exit( 0 );
|
||||
}
|
||||
|
||||
Logger.notice( `Adding and committing changes` );
|
||||
await git.add( '.' );
|
||||
await git.commit( 'Adding changelog from automation.' );
|
||||
await git.push( 'origin', branch );
|
||||
Logger.notice( `Pushed changes to ${ branch }` );
|
||||
}
|
||||
);
|
||||
|
||||
export default program;
|
|
@ -0,0 +1,401 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
getChangelogSignificance,
|
||||
getChangelogType,
|
||||
getChangelogDetails,
|
||||
} from '../github';
|
||||
import { Logger } from '../../../core/logger';
|
||||
|
||||
jest.mock( '../../../core/logger', () => {
|
||||
return {
|
||||
Logger: {
|
||||
error: jest.fn(),
|
||||
},
|
||||
};
|
||||
} );
|
||||
|
||||
describe( 'getChangelogSignificance', () => {
|
||||
it( 'should return the selected significance', () => {
|
||||
const body =
|
||||
'### Changelog entry\r\n' +
|
||||
'\r\n' +
|
||||
'<!-- You can optionally choose to enter a changelog entry by checking the box and supplying data. -->\r\n' +
|
||||
'\r\n' +
|
||||
'- [x] Automatically create a changelog entry from the details below.\r\n' +
|
||||
'\r\n' +
|
||||
'<details>\r\n' +
|
||||
'\r\n' +
|
||||
'#### Significance\r\n' +
|
||||
'<!-- Choose only one -->\r\n' +
|
||||
'- [x] Patch\r\n' +
|
||||
'- [ ] Minor\r\n' +
|
||||
'- [ ] Major\r\n' +
|
||||
'\r\n' +
|
||||
'#### Type\r\n' +
|
||||
'<!-- Choose only one -->\r\n' +
|
||||
'- [x] Fix - Fixes an existing bug\r\n' +
|
||||
'- [ ] Add - Adds functionality\r\n' +
|
||||
'- [ ] Update - Update existing functionality\r\n' +
|
||||
'- [ ] Dev - Development related task\r\n' +
|
||||
'- [ ] Tweak - A minor adjustment to the codebase\r\n' +
|
||||
'- [ ] Performance - Address performance issues\r\n' +
|
||||
'- [ ] Enhancement\r\n' +
|
||||
'\r\n' +
|
||||
'#### Message\r\n' +
|
||||
'<!-- Add a changelog message here -->\r\n' +
|
||||
'This is a very useful fix.\r\n' +
|
||||
'\r\n' +
|
||||
'#### Comment\r\n' +
|
||||
`<!-- If the changes in this pull request don't warrant a changelog entry, you can alternatively supply a comment here. Note that comments are only accepted with a significance of "Patch" -->\r\n` +
|
||||
'\r\n' +
|
||||
'</details>';
|
||||
|
||||
const significance = getChangelogSignificance( body );
|
||||
expect( significance ).toBe( 'patch' );
|
||||
} );
|
||||
|
||||
it( 'should error when no significance selected', () => {
|
||||
const body =
|
||||
'### Changelog entry\r\n' +
|
||||
'\r\n' +
|
||||
'<!-- You can optionally choose to enter a changelog entry by checking the box and supplying data. -->\r\n' +
|
||||
'\r\n' +
|
||||
'- [x] Automatically create a changelog entry from the details below.\r\n' +
|
||||
'\r\n' +
|
||||
'<details>\r\n' +
|
||||
'\r\n' +
|
||||
'#### Significance\r\n' +
|
||||
'<!-- Choose only one -->\r\n' +
|
||||
'- [ ] Patch\r\n' +
|
||||
'- [ ] Minor\r\n' +
|
||||
'- [ ] Major\r\n' +
|
||||
'\r\n' +
|
||||
'#### Type\r\n' +
|
||||
'<!-- Choose only one -->\r\n' +
|
||||
'- [x] Fix - Fixes an existing bug\r\n' +
|
||||
'- [ ] Add - Adds functionality\r\n' +
|
||||
'- [ ] Update - Update existing functionality\r\n' +
|
||||
'- [ ] Dev - Development related task\r\n' +
|
||||
'- [ ] Tweak - A minor adjustment to the codebase\r\n' +
|
||||
'- [ ] Performance - Address performance issues\r\n' +
|
||||
'- [ ] Enhancement\r\n' +
|
||||
'\r\n' +
|
||||
'#### Message\r\n' +
|
||||
'<!-- Add a changelog message here -->\r\n' +
|
||||
'This is a very useful fix.\r\n' +
|
||||
'\r\n' +
|
||||
'#### Comment\r\n' +
|
||||
`<!-- If the changes in this pull request don't warrant a changelog entry, you can alternatively supply a comment here. Note that comments are only accepted with a significance of "Patch" -->\r\n` +
|
||||
'\r\n' +
|
||||
'</details>';
|
||||
|
||||
const significance = getChangelogSignificance( body );
|
||||
expect( significance ).toBeUndefined();
|
||||
expect( Logger.error ).toHaveBeenCalledWith(
|
||||
'No changelog significance found'
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'should error when more than one significance selected', () => {
|
||||
const body =
|
||||
'### Changelog entry\r\n' +
|
||||
'\r\n' +
|
||||
'<!-- You can optionally choose to enter a changelog entry by checking the box and supplying data. -->\r\n' +
|
||||
'\r\n' +
|
||||
'- [x] Automatically create a changelog entry from the details below.\r\n' +
|
||||
'\r\n' +
|
||||
'<details>\r\n' +
|
||||
'\r\n' +
|
||||
'#### Significance\r\n' +
|
||||
'<!-- Choose only one -->\r\n' +
|
||||
'- [x] Patch\r\n' +
|
||||
'- [x] Minor\r\n' +
|
||||
'- [ ] Major\r\n' +
|
||||
'\r\n' +
|
||||
'#### Type\r\n' +
|
||||
'<!-- Choose only one -->\r\n' +
|
||||
'- [x] Fix - Fixes an existing bug\r\n' +
|
||||
'- [ ] Add - Adds functionality\r\n' +
|
||||
'- [ ] Update - Update existing functionality\r\n' +
|
||||
'- [ ] Dev - Development related task\r\n' +
|
||||
'- [ ] Tweak - A minor adjustment to the codebase\r\n' +
|
||||
'- [ ] Performance - Address performance issues\r\n' +
|
||||
'- [ ] Enhancement\r\n' +
|
||||
'\r\n' +
|
||||
'#### Message\r\n' +
|
||||
'<!-- Add a changelog message here -->\r\n' +
|
||||
'This is a very useful fix.\r\n' +
|
||||
'\r\n' +
|
||||
'#### Comment\r\n' +
|
||||
`<!-- If the changes in this pull request don't warrant a changelog entry, you can alternatively supply a comment here. Note that comments are only accepted with a significance of "Patch" -->\r\n` +
|
||||
'\r\n' +
|
||||
'</details>';
|
||||
|
||||
const significance = getChangelogSignificance( body );
|
||||
expect( significance ).toBeUndefined();
|
||||
expect( Logger.error ).toHaveBeenCalledWith(
|
||||
'Multiple changelog significances found. Only one can be entered'
|
||||
);
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'getChangelogType', () => {
|
||||
it( 'should return the selected changelog type', () => {
|
||||
const body =
|
||||
'### Changelog entry\r\n' +
|
||||
'\r\n' +
|
||||
'<!-- You can optionally choose to enter a changelog entry by checking the box and supplying data. -->\r\n' +
|
||||
'\r\n' +
|
||||
'- [x] Automatically create a changelog entry from the details below.\r\n' +
|
||||
'\r\n' +
|
||||
'<details>\r\n' +
|
||||
'\r\n' +
|
||||
'#### Significance\r\n' +
|
||||
'<!-- Choose only one -->\r\n' +
|
||||
'- [x] Patch\r\n' +
|
||||
'- [ ] Minor\r\n' +
|
||||
'- [ ] Major\r\n' +
|
||||
'\r\n' +
|
||||
'#### Type\r\n' +
|
||||
'<!-- Choose only one -->\r\n' +
|
||||
'- [x] Fix - Fixes an existing bug\r\n' +
|
||||
'- [ ] Add - Adds functionality\r\n' +
|
||||
'- [ ] Update - Update existing functionality\r\n' +
|
||||
'- [ ] Dev - Development related task\r\n' +
|
||||
'- [ ] Tweak - A minor adjustment to the codebase\r\n' +
|
||||
'- [ ] Performance - Address performance issues\r\n' +
|
||||
'- [ ] Enhancement\r\n' +
|
||||
'\r\n' +
|
||||
'#### Message\r\n' +
|
||||
'<!-- Add a changelog message here -->\r\n' +
|
||||
'This is a very useful fix.\r\n' +
|
||||
'\r\n' +
|
||||
'#### Comment\r\n' +
|
||||
`<!-- If the changes in this pull request don't warrant a changelog entry, you can alternatively supply a comment here. Note that comments are only accepted with a significance of "Patch" -->\r\n` +
|
||||
'\r\n' +
|
||||
'</details>';
|
||||
|
||||
const type = getChangelogType( body );
|
||||
expect( type ).toBe( 'fix' );
|
||||
} );
|
||||
|
||||
it( 'should error when no type selected', () => {
|
||||
const body =
|
||||
'### Changelog entry\r\n' +
|
||||
'\r\n' +
|
||||
'<!-- You can optionally choose to enter a changelog entry by checking the box and supplying data. -->\r\n' +
|
||||
'\r\n' +
|
||||
'- [x] Automatically create a changelog entry from the details below.\r\n' +
|
||||
'\r\n' +
|
||||
'<details>\r\n' +
|
||||
'\r\n' +
|
||||
'#### Significance\r\n' +
|
||||
'<!-- Choose only one -->\r\n' +
|
||||
'- [x] Patch\r\n' +
|
||||
'- [ ] Minor\r\n' +
|
||||
'- [ ] Major\r\n' +
|
||||
'\r\n' +
|
||||
'#### Type\r\n' +
|
||||
'<!-- Choose only one -->\r\n' +
|
||||
'- [ ] Fix - Fixes an existing bug\r\n' +
|
||||
'- [ ] Add - Adds functionality\r\n' +
|
||||
'- [ ] Update - Update existing functionality\r\n' +
|
||||
'- [ ] Dev - Development related task\r\n' +
|
||||
'- [ ] Tweak - A minor adjustment to the codebase\r\n' +
|
||||
'- [ ] Performance - Address performance issues\r\n' +
|
||||
'- [ ] Enhancement\r\n' +
|
||||
'\r\n' +
|
||||
'#### Message\r\n' +
|
||||
'<!-- Add a changelog message here -->\r\n' +
|
||||
'This is a very useful fix.\r\n' +
|
||||
'\r\n' +
|
||||
'#### Comment\r\n' +
|
||||
`<!-- If the changes in this pull request don't warrant a changelog entry, you can alternatively supply a comment here. Note that comments are only accepted with a significance of "Patch" -->\r\n` +
|
||||
'\r\n' +
|
||||
'</details>';
|
||||
|
||||
const type = getChangelogType( body );
|
||||
expect( type ).toBeUndefined();
|
||||
expect( Logger.error ).toHaveBeenCalledWith(
|
||||
'No changelog type found'
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'should error more than one type selected', () => {
|
||||
const body =
|
||||
'### Changelog entry\r\n' +
|
||||
'\r\n' +
|
||||
'<!-- You can optionally choose to enter a changelog entry by checking the box and supplying data. -->\r\n' +
|
||||
'\r\n' +
|
||||
'- [x] Automatically create a changelog entry from the details below.\r\n' +
|
||||
'\r\n' +
|
||||
'<details>\r\n' +
|
||||
'\r\n' +
|
||||
'#### Significance\r\n' +
|
||||
'<!-- Choose only one -->\r\n' +
|
||||
'- [x] Patch\r\n' +
|
||||
'- [ ] Minor\r\n' +
|
||||
'- [ ] Major\r\n' +
|
||||
'\r\n' +
|
||||
'#### Type\r\n' +
|
||||
'<!-- Choose only one -->\r\n' +
|
||||
'- [ ] Fix - Fixes an existing bug\r\n' +
|
||||
'- [ ] Add - Adds functionality\r\n' +
|
||||
'- [x] Update - Update existing functionality\r\n' +
|
||||
'- [x] Dev - Development related task\r\n' +
|
||||
'- [x] Tweak - A minor adjustment to the codebase\r\n' +
|
||||
'- [ ] Performance - Address performance issues\r\n' +
|
||||
'- [ ] Enhancement\r\n' +
|
||||
'\r\n' +
|
||||
'#### Message\r\n' +
|
||||
'<!-- Add a changelog message here -->\r\n' +
|
||||
'This is a very useful fix.\r\n' +
|
||||
'\r\n' +
|
||||
'#### Comment\r\n' +
|
||||
`<!-- If the changes in this pull request don't warrant a changelog entry, you can alternatively supply a comment here. Note that comments are only accepted with a significance of "Patch" -->\r\n` +
|
||||
'\r\n' +
|
||||
'</details>';
|
||||
|
||||
const type = getChangelogType( body );
|
||||
expect( type ).toBeUndefined();
|
||||
expect( Logger.error ).toHaveBeenCalledWith(
|
||||
'Multiple changelog types found. Only one can be entered'
|
||||
);
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'getChangelogDetails', () => {
|
||||
it( 'should return the changelog details', () => {
|
||||
const body =
|
||||
'### Changelog entry\r\n' +
|
||||
'\r\n' +
|
||||
'<!-- You can optionally choose to enter a changelog entry by checking the box and supplying data. -->\r\n' +
|
||||
'\r\n' +
|
||||
'- [x] Automatically create a changelog entry from the details below.\r\n' +
|
||||
'\r\n' +
|
||||
'<details>\r\n' +
|
||||
'\r\n' +
|
||||
'#### Significance\r\n' +
|
||||
'<!-- Choose only one -->\r\n' +
|
||||
'- [x] Patch\r\n' +
|
||||
'- [ ] Minor\r\n' +
|
||||
'- [ ] Major\r\n' +
|
||||
'\r\n' +
|
||||
'#### Type\r\n' +
|
||||
'<!-- Choose only one -->\r\n' +
|
||||
'- [x] Fix - Fixes an existing bug\r\n' +
|
||||
'- [ ] Add - Adds functionality\r\n' +
|
||||
'- [ ] Update - Update existing functionality\r\n' +
|
||||
'- [ ] Dev - Development related task\r\n' +
|
||||
'- [ ] Tweak - A minor adjustment to the codebase\r\n' +
|
||||
'- [ ] Performance - Address performance issues\r\n' +
|
||||
'- [ ] Enhancement\r\n' +
|
||||
'\r\n' +
|
||||
'#### Message\r\n' +
|
||||
'<!-- Add a changelog message here -->\r\n' +
|
||||
'This is a very useful fix.\r\n' +
|
||||
'\r\n' +
|
||||
'#### Comment\r\n' +
|
||||
`<!-- If the changes in this pull request don't warrant a changelog entry, you can alternatively supply a comment here. Note that comments are only accepted with a significance of "Patch" -->\r\n` +
|
||||
'\r\n' +
|
||||
'</details>';
|
||||
|
||||
const details = getChangelogDetails( body );
|
||||
expect( details.significance ).toEqual( 'patch' );
|
||||
expect( details.type ).toEqual( 'fix' );
|
||||
expect( details.message ).toEqual( 'This is a very useful fix.' );
|
||||
expect( details.comment ).toEqual( '' );
|
||||
} );
|
||||
|
||||
it( 'should error if a comment and message are added', () => {
|
||||
const body =
|
||||
'### Changelog entry\r\n' +
|
||||
'\r\n' +
|
||||
'<!-- You can optionally choose to enter a changelog entry by checking the box and supplying data. -->\r\n' +
|
||||
'\r\n' +
|
||||
'- [x] Automatically create a changelog entry from the details below.\r\n' +
|
||||
'\r\n' +
|
||||
'<details>\r\n' +
|
||||
'\r\n' +
|
||||
'#### Significance\r\n' +
|
||||
'<!-- Choose only one -->\r\n' +
|
||||
'- [x] Patch\r\n' +
|
||||
'- [ ] Minor\r\n' +
|
||||
'- [ ] Major\r\n' +
|
||||
'\r\n' +
|
||||
'#### Type\r\n' +
|
||||
'<!-- Choose only one -->\r\n' +
|
||||
'- [x] Fix - Fixes an existing bug\r\n' +
|
||||
'- [ ] Add - Adds functionality\r\n' +
|
||||
'- [ ] Update - Update existing functionality\r\n' +
|
||||
'- [ ] Dev - Development related task\r\n' +
|
||||
'- [ ] Tweak - A minor adjustment to the codebase\r\n' +
|
||||
'- [ ] Performance - Address performance issues\r\n' +
|
||||
'- [ ] Enhancement\r\n' +
|
||||
'\r\n' +
|
||||
'#### Message\r\n' +
|
||||
'<!-- Add a changelog message here -->\r\n' +
|
||||
'This is a very useful fix.\r\n' +
|
||||
'\r\n' +
|
||||
'#### Comment\r\n' +
|
||||
`<!-- If the changes in this pull request don't warrant a changelog entry, you can alternatively supply a comment here. Note that comments are only accepted with a significance of "Patch" -->\r\n` +
|
||||
'This is a very useful comment.\r\n' +
|
||||
'\r\n' +
|
||||
'</details>';
|
||||
|
||||
const details = getChangelogDetails( body );
|
||||
expect( details ).toBeUndefined();
|
||||
expect( Logger.error ).toHaveBeenCalledWith(
|
||||
'Both a message and comment were found. Only one can be entered'
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'should error if a comment is entered with a significance other than patch', () => {
|
||||
const body =
|
||||
'### Changelog entry\r\n' +
|
||||
'\r\n' +
|
||||
'<!-- You can optionally choose to enter a changelog entry by checking the box and supplying data. -->\r\n' +
|
||||
'\r\n' +
|
||||
'- [x] Automatically create a changelog entry from the details below.\r\n' +
|
||||
'\r\n' +
|
||||
'<details>\r\n' +
|
||||
'\r\n' +
|
||||
'#### Significance\r\n' +
|
||||
'<!-- Choose only one -->\r\n' +
|
||||
'- [ ] Patch\r\n' +
|
||||
'- [x] Minor\r\n' +
|
||||
'- [ ] Major\r\n' +
|
||||
'\r\n' +
|
||||
'#### Type\r\n' +
|
||||
'<!-- Choose only one -->\r\n' +
|
||||
'- [x] Fix - Fixes an existing bug\r\n' +
|
||||
'- [ ] Add - Adds functionality\r\n' +
|
||||
'- [ ] Update - Update existing functionality\r\n' +
|
||||
'- [ ] Dev - Development related task\r\n' +
|
||||
'- [ ] Tweak - A minor adjustment to the codebase\r\n' +
|
||||
'- [ ] Performance - Address performance issues\r\n' +
|
||||
'- [ ] Enhancement\r\n' +
|
||||
'\r\n' +
|
||||
'#### Message\r\n' +
|
||||
'<!-- Add a changelog message here -->\r\n' +
|
||||
'\r\n' +
|
||||
'#### Comment\r\n' +
|
||||
`<!-- If the changes in this pull request don't warrant a changelog entry, you can alternatively supply a comment here. Note that comments are only accepted with a significance of "Patch" -->\r\n` +
|
||||
'This is a very useful comment.\r\n' +
|
||||
'\r\n' +
|
||||
'</details>';
|
||||
|
||||
const details = getChangelogDetails( body );
|
||||
expect( details ).toBeUndefined();
|
||||
expect( Logger.error ).toHaveBeenCalledWith(
|
||||
'Only patch changes can have a comment. Please change the significance to patch or remove the comment'
|
||||
);
|
||||
} );
|
||||
} );
|
|
@ -0,0 +1,138 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
getAllProjectsPathsFromWorkspace,
|
||||
getChangeloggerProjectPaths,
|
||||
getTouchedChangeloggerProjectsPathsMappedToProjects,
|
||||
} from '../projects';
|
||||
|
||||
const sampleWorkspaceYaml = `
|
||||
packages:
|
||||
- 'folder-with-lots-of-projects/*'
|
||||
- 'projects/cool-project'
|
||||
- 'projects/very-cool-project'
|
||||
- 'interesting-project'
|
||||
`;
|
||||
const tmpRepoPath = path.join( __dirname, 'test-repo' );
|
||||
|
||||
describe( 'Changelog project functions', () => {
|
||||
it( 'getAllProjectsPathsFromWorkspace should provide a list of all projects supplied by pnpm-workspace.yml', async () => {
|
||||
const projects = await getAllProjectsPathsFromWorkspace(
|
||||
tmpRepoPath,
|
||||
sampleWorkspaceYaml
|
||||
);
|
||||
const expectedProjects = [
|
||||
'folder-with-lots-of-projects/project-b',
|
||||
'folder-with-lots-of-projects/project-a',
|
||||
'projects/cool-project',
|
||||
'projects/very-cool-project',
|
||||
'interesting-project',
|
||||
];
|
||||
|
||||
expectedProjects.forEach( ( expectedProject ) => {
|
||||
expect( projects ).toContain( expectedProject );
|
||||
} );
|
||||
|
||||
expect( projects ).toHaveLength( expectedProjects.length );
|
||||
} );
|
||||
|
||||
it( 'getChangeloggerProjectPaths should provide a list of all projects that use Jetpack changelogger', async () => {
|
||||
const projects = await getAllProjectsPathsFromWorkspace(
|
||||
tmpRepoPath,
|
||||
sampleWorkspaceYaml
|
||||
);
|
||||
const changeloggerProjects = await getChangeloggerProjectPaths(
|
||||
tmpRepoPath,
|
||||
projects
|
||||
);
|
||||
|
||||
const expectedChangeLoggerProjects = [
|
||||
'folder-with-lots-of-projects/project-b',
|
||||
'folder-with-lots-of-projects/project-a',
|
||||
'projects/very-cool-project',
|
||||
];
|
||||
|
||||
expectedChangeLoggerProjects.forEach(
|
||||
( expectedChangeLoggerProject ) => {
|
||||
expect( changeloggerProjects ).toContain(
|
||||
expectedChangeLoggerProject
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
expect( changeloggerProjects ).toHaveLength(
|
||||
expectedChangeLoggerProjects.length
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'getTouchedChangeloggerProjectsPathsMappedToProjects should combine touched and changelogger projects and return a list that is a subset of both', async () => {
|
||||
const touchedFiles = [
|
||||
'folder-with-lots-of-projects/project-b/src/index.js',
|
||||
'projects/very-cool-project/src/index.js',
|
||||
];
|
||||
const changeLoggerProjects = [
|
||||
'folder-with-lots-of-projects/project-b',
|
||||
'folder-with-lots-of-projects/project-a',
|
||||
'projects/very-cool-project',
|
||||
];
|
||||
const intersectedProjects =
|
||||
getTouchedChangeloggerProjectsPathsMappedToProjects(
|
||||
touchedFiles,
|
||||
changeLoggerProjects
|
||||
);
|
||||
|
||||
expect( intersectedProjects ).toHaveLength( 2 );
|
||||
expect( intersectedProjects ).toContain(
|
||||
'folder-with-lots-of-projects/project-b'
|
||||
);
|
||||
expect( intersectedProjects ).toContain( 'projects/very-cool-project' );
|
||||
} );
|
||||
|
||||
it( 'getTouchedChangeloggerProjectsPathsMappedToProjects should map plugins and js packages to the correct name', async () => {
|
||||
const touchedFiles = [
|
||||
'plugins/beta-tester/src/index.js',
|
||||
'plugins/woocommerce/src/index.js',
|
||||
'packages/js/components/src/index.js',
|
||||
'packages/js/data/src/index.js',
|
||||
];
|
||||
const changeLoggerProjects = [
|
||||
'plugins/woocommerce',
|
||||
'plugins/beta-tester',
|
||||
'packages/js/data',
|
||||
'packages/js/components',
|
||||
];
|
||||
const intersectedProjects =
|
||||
getTouchedChangeloggerProjectsPathsMappedToProjects(
|
||||
touchedFiles,
|
||||
changeLoggerProjects
|
||||
);
|
||||
|
||||
expect( intersectedProjects ).toHaveLength( 4 );
|
||||
expect( intersectedProjects ).toContain( 'woocommerce' );
|
||||
expect( intersectedProjects ).toContain( 'beta-tester' );
|
||||
expect( intersectedProjects ).toContain( '@woocommerce/components' );
|
||||
expect( intersectedProjects ).toContain( '@woocommerce/data' );
|
||||
} );
|
||||
|
||||
it( 'getTouchedChangeloggerProjectsPathsMappedToProjects should handle woocommerce-admin projects mapped to woocommerce core', async () => {
|
||||
const touchedFiles = [
|
||||
'plugins/beta-tester/src/index.js',
|
||||
'plugins/woocommerce-admin/src/index.js',
|
||||
];
|
||||
const changeLoggerProjects = [ 'plugins/woocommerce' ];
|
||||
const intersectedProjects =
|
||||
getTouchedChangeloggerProjectsPathsMappedToProjects(
|
||||
touchedFiles,
|
||||
changeLoggerProjects
|
||||
);
|
||||
|
||||
expect( intersectedProjects ).toHaveLength( 1 );
|
||||
expect( intersectedProjects ).toContain( 'woocommerce' );
|
||||
} );
|
||||
} );
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"require": {
|
||||
"automattic/jetpack-changelogger": "99.99"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"require-dev": {
|
||||
"automattic/jetpack-changelogger": "99.99"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"require": {
|
||||
"automattic/jetpack-changelogger": "99.99"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getPullRequest, isCommunityPullRequest } from '../../core/github/repo';
|
||||
import { Logger } from '../../core/logger';
|
||||
|
||||
/**
|
||||
* Get relevant data from a pull request.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {string} options.owner repository owner.
|
||||
* @param {string} options.name repository name.
|
||||
* @param {string} prNumber pull request number.
|
||||
* @return {Promise<object>} pull request data.
|
||||
*/
|
||||
export const getPullRequestData = async (
|
||||
options: { owner: string; name: string },
|
||||
prNumber: string
|
||||
) => {
|
||||
const { owner, name } = options;
|
||||
const prData = await getPullRequest( { owner, name, prNumber } );
|
||||
const isCommunityPR = isCommunityPullRequest( prData, owner, name );
|
||||
const headOwner = isCommunityPR ? prData.head.repo.owner.login : owner;
|
||||
const branch = prData.head.ref;
|
||||
const fileName = branch.replace( /\//g, '-' );
|
||||
const prBody = prData.body;
|
||||
const head = prData.head.sha;
|
||||
const base = prData.base.sha;
|
||||
return {
|
||||
prBody,
|
||||
isCommunityPR,
|
||||
headOwner,
|
||||
branch,
|
||||
fileName,
|
||||
head,
|
||||
base,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine if a pull request description activates the changelog automation.
|
||||
*
|
||||
* @param {string} body pull request description.
|
||||
* @return {boolean} if the pull request description activates the changelog automation.
|
||||
*/
|
||||
export const shouldAutomateChangelog = ( body: string ) => {
|
||||
const regex =
|
||||
/\[x\] Automatically create a changelog entry from the details/gm;
|
||||
return regex.test( body );
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the changelog significance from a pull request description.
|
||||
*
|
||||
* @param {string} body pull request description.
|
||||
* @return {void|string} changelog significance.
|
||||
*/
|
||||
export const getChangelogSignificance = ( body: string ) => {
|
||||
const regex = /\[x\] (Patch|Minor|Major)\r\n/gm;
|
||||
const matches = body.match( regex );
|
||||
|
||||
if ( matches === null ) {
|
||||
Logger.error( 'No changelog significance found' );
|
||||
// Logger.error has a process.exit( 1 ) call, this return is purely for testing purposes.
|
||||
return;
|
||||
}
|
||||
|
||||
if ( matches.length > 1 ) {
|
||||
Logger.error(
|
||||
'Multiple changelog significances found. Only one can be entered'
|
||||
);
|
||||
// Logger.error has a process.exit( 1 ) call, this return is purely for testing purposes.
|
||||
return;
|
||||
}
|
||||
const significance = regex.exec( body );
|
||||
|
||||
return significance[ 1 ].toLowerCase();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the changelog type from a pull request description.
|
||||
*
|
||||
* @param {string} body pull request description.
|
||||
* @return {void|string} changelog type.
|
||||
*/
|
||||
export const getChangelogType = ( body: string ) => {
|
||||
const regex =
|
||||
/\[x\] (Fix|Add|Update|Dev|Tweak|Performance|Enhancement) -/gm;
|
||||
const matches = body.match( regex );
|
||||
|
||||
if ( matches === null ) {
|
||||
Logger.error( 'No changelog type found' );
|
||||
// Logger.error has a process.exit( 1 ) call, this return is purely for testing purposes.
|
||||
return;
|
||||
}
|
||||
|
||||
if ( matches.length > 1 ) {
|
||||
Logger.error(
|
||||
'Multiple changelog types found. Only one can be entered'
|
||||
);
|
||||
// Logger.error has a process.exit( 1 ) call, this return is purely for testing purposes.
|
||||
return;
|
||||
}
|
||||
|
||||
const type = regex.exec( body );
|
||||
return type[ 1 ].toLowerCase();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the changelog message from a pull request description.
|
||||
*
|
||||
* @param {string} body pull request description.
|
||||
* @return {void|string} changelog message.
|
||||
*/
|
||||
export const getChangelogMessage = ( body: string ) => {
|
||||
const messageRegex = /#### Message\r\n(<!--(.*)-->)?(.*)#### Comment/gms;
|
||||
const match = messageRegex.exec( body );
|
||||
|
||||
if ( ! match ) {
|
||||
Logger.error( 'No changelog message found' );
|
||||
}
|
||||
|
||||
return match[ 3 ].trim();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the changelog comment from a pull request description.
|
||||
*
|
||||
* @param {string} body pull request description.
|
||||
* @return {void|string} changelog comment.
|
||||
*/
|
||||
export const getChangelogComment = ( body: string ) => {
|
||||
const commentRegex = /#### Comment\r\n(<!--(.*)-->)?(.*)<\/details>/gms;
|
||||
const match = commentRegex.exec( body );
|
||||
|
||||
return match ? match[ 3 ].trim() : '';
|
||||
};
|
||||
|
||||
export const getChangelogDetails = ( body: string ) => {
|
||||
const message = getChangelogMessage( body );
|
||||
const comment = getChangelogComment( body );
|
||||
|
||||
if ( comment && message ) {
|
||||
Logger.error(
|
||||
'Both a message and comment were found. Only one can be entered'
|
||||
);
|
||||
// Logger.error has a process.exit( 1 ) call, this return is purely for testing purposes.
|
||||
return;
|
||||
}
|
||||
|
||||
const significance = getChangelogSignificance( body );
|
||||
|
||||
if ( comment && significance !== 'patch' ) {
|
||||
Logger.error(
|
||||
'Only patch changes can have a comment. Please change the significance to patch or remove the comment'
|
||||
);
|
||||
// Logger.error has a process.exit( 1 ) call, this return is purely for testing purposes.
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
significance,
|
||||
type: getChangelogType( body ),
|
||||
message,
|
||||
comment,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,192 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { existsSync, readFileSync } from 'fs';
|
||||
import { readFile } from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { glob } from 'glob';
|
||||
import simpleGit from 'simple-git';
|
||||
|
||||
/**
|
||||
* Get all projects listed in the workspace yaml file.
|
||||
*
|
||||
* @param {string} tmpRepoPath Path to the temporary repository.
|
||||
* @param {string} workspaceYaml Contents of the workspace yaml file.
|
||||
* @return {Array<string>} List of projects.
|
||||
*/
|
||||
export const getAllProjectsPathsFromWorkspace = async (
|
||||
tmpRepoPath: string,
|
||||
workspaceYaml: string
|
||||
) => {
|
||||
const rawProjects = workspaceYaml.split( '- ' );
|
||||
// remove heading
|
||||
rawProjects.shift();
|
||||
|
||||
const globbedProjects = await Promise.all(
|
||||
rawProjects
|
||||
.map( ( project ) => project.replace( /'/g, '' ).trim() )
|
||||
.map( async ( project ) => {
|
||||
if ( project.includes( '*' ) ) {
|
||||
return await glob( project, { cwd: tmpRepoPath } );
|
||||
}
|
||||
return project;
|
||||
} )
|
||||
);
|
||||
return globbedProjects.flat();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all projects that have Jetpack changelogger enabled
|
||||
*
|
||||
* @param {string} tmpRepoPath Path to the temporary repository.
|
||||
* @param {Array<string>} projects all projects listed in the workspace yaml file
|
||||
* @return {Array<string>} List of projects that have Jetpack changelogger enabled.
|
||||
*/
|
||||
export const getChangeloggerProjectPaths = async (
|
||||
tmpRepoPath: string,
|
||||
projects: Array< string >
|
||||
) => {
|
||||
const projectsWithComposer = projects.filter( ( project ) => {
|
||||
return existsSync( `${ tmpRepoPath }/${ project }/composer.json` );
|
||||
} );
|
||||
return projectsWithComposer.filter( ( project ) => {
|
||||
const composer = JSON.parse(
|
||||
readFileSync(
|
||||
`${ tmpRepoPath }/${ project }/composer.json`,
|
||||
'utf8'
|
||||
)
|
||||
);
|
||||
return (
|
||||
( composer.require &&
|
||||
composer.require[ 'automattic/jetpack-changelogger' ] ) ||
|
||||
( composer[ 'require-dev' ] &&
|
||||
composer[ 'require-dev' ][ 'automattic/jetpack-changelogger' ] )
|
||||
);
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Get an array of all files changed in a PR.
|
||||
*
|
||||
* @param {string} tmpRepoPath Path to the temporary repository.
|
||||
* @param {string} base base hash
|
||||
* @param {string} head head hash
|
||||
* @param {string} fileName changelog file name
|
||||
* @return {Array<string>} List of files changed in a PR.
|
||||
*/
|
||||
export const getTouchedFilePaths = async (
|
||||
tmpRepoPath: string,
|
||||
base: string,
|
||||
head: string,
|
||||
fileName: string
|
||||
) => {
|
||||
const git = simpleGit( {
|
||||
baseDir: tmpRepoPath,
|
||||
config: [ 'core.hooksPath=/dev/null' ],
|
||||
} );
|
||||
|
||||
// make sure base sha is available.
|
||||
await git.raw( [
|
||||
'remote',
|
||||
'add',
|
||||
'woocommerce',
|
||||
'git@github.com:woocommerce/woocommerce.git',
|
||||
] );
|
||||
await git.raw( [ 'fetch', 'woocommerce', base ] );
|
||||
const diff = await git.raw( [
|
||||
'diff',
|
||||
'--name-only',
|
||||
`${ base }...${ head }`,
|
||||
] );
|
||||
return (
|
||||
diff
|
||||
.split( '\n' )
|
||||
.filter( ( item ) => item.trim() )
|
||||
// Don't count changelogs themselves as touched files.
|
||||
.filter( ( item ) => ! item.includes( `/changelog/${ fileName }` ) )
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get an array of projects that have Jetpack changelogger enabled and have files changed in a PR. This function also maps names of projects that have been renamed in the monorepo from their paths.
|
||||
*
|
||||
* @param {Array<string>} touchedFiles List of files changed in a PR. touchedFiles
|
||||
* @param {Array<string>} changeloggerProjects List of projects that have Jetpack changelogger enabled.
|
||||
* @return {Array<string>} List of projects that have Jetpack changelogger enabled and have files changed in a PR.
|
||||
*/
|
||||
export const getTouchedChangeloggerProjectsPathsMappedToProjects = (
|
||||
touchedFiles: Array< string >,
|
||||
changeloggerProjects: Array< string >
|
||||
) => {
|
||||
const mappedTouchedFiles = touchedFiles.map( ( touchedProject ) => {
|
||||
if ( touchedProject.includes( 'plugins/woocommerce-admin' ) ) {
|
||||
return touchedProject.replace(
|
||||
'plugins/woocommerce-admin',
|
||||
'plugins/woocommerce'
|
||||
);
|
||||
}
|
||||
return touchedProject;
|
||||
} );
|
||||
const touchedProjectPathsRequiringChangelog = changeloggerProjects.filter(
|
||||
( project ) => {
|
||||
return mappedTouchedFiles.some( ( touchedProject ) =>
|
||||
touchedProject.includes( project + '/' )
|
||||
);
|
||||
}
|
||||
);
|
||||
return touchedProjectPathsRequiringChangelog.map( ( project ) => {
|
||||
if ( project.includes( 'plugins/' ) ) {
|
||||
return project.replace( 'plugins/', '' );
|
||||
} else if ( project.includes( 'packages/js/' ) ) {
|
||||
return project.replace( 'packages/js/', '@woocommerce/' );
|
||||
}
|
||||
return project;
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all projects listed in the workspace yaml file.
|
||||
*
|
||||
* @param {string} tmpRepoPath Path to the temporary repository.
|
||||
* @return {Array<string>} List of projects.
|
||||
*/
|
||||
export const getAllProjectPaths = async ( tmpRepoPath: string ) => {
|
||||
const workspaceYaml = await readFile(
|
||||
path.join( tmpRepoPath, 'pnpm-workspace.yaml' ),
|
||||
'utf8'
|
||||
);
|
||||
return await getAllProjectsPathsFromWorkspace( tmpRepoPath, workspaceYaml );
|
||||
};
|
||||
|
||||
/**
|
||||
* Get an array of projects that have Jetpack changelogger enabled and have files changed in a PR.
|
||||
*
|
||||
* @param {string} tmpRepoPath Path to the temporary repository.
|
||||
* @param {string} base base hash
|
||||
* @param {string} head head hash
|
||||
* @param {string} fileName changelog file name
|
||||
* @return {Array<string>} List of projects that have Jetpack changelogger enabled and have files changed in a PR.
|
||||
*/
|
||||
export const getTouchedProjectsRequiringChangelog = async (
|
||||
tmpRepoPath: string,
|
||||
base: string,
|
||||
head: string,
|
||||
fileName: string
|
||||
) => {
|
||||
const allProjectPaths = await getAllProjectPaths( tmpRepoPath );
|
||||
const changeloggerProjectsPaths = await getChangeloggerProjectPaths(
|
||||
tmpRepoPath,
|
||||
allProjectPaths
|
||||
);
|
||||
const touchedFilePaths = await getTouchedFilePaths(
|
||||
tmpRepoPath,
|
||||
base,
|
||||
head,
|
||||
fileName
|
||||
);
|
||||
|
||||
return getTouchedChangeloggerProjectsPathsMappedToProjects(
|
||||
touchedFilePaths,
|
||||
changeloggerProjectsPaths
|
||||
);
|
||||
};
|
|
@ -7,7 +7,10 @@ import { Repository } from '@octokit/graphql-schema';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { graphqlWithAuth, octokitWithAuth } from './api';
|
||||
import { PullRequestEndpointResponse } from './types';
|
||||
import {
|
||||
CreatePullRequestEndpointResponse,
|
||||
GetPullRequestEndpointResponse,
|
||||
} from './types';
|
||||
|
||||
export const getLatestGithubReleaseVersion = async ( options: {
|
||||
owner?: string;
|
||||
|
@ -150,7 +153,7 @@ export const createPullRequest = async ( options: {
|
|||
name: string;
|
||||
title: string;
|
||||
body: string;
|
||||
} ): Promise< PullRequestEndpointResponse[ 'data' ] > => {
|
||||
} ): Promise< CreatePullRequestEndpointResponse[ 'data' ] > => {
|
||||
const { head, base, owner, name, title, body } = options;
|
||||
const pullRequest = await octokitWithAuth().request(
|
||||
'POST /repos/{owner}/{repo}/pulls',
|
||||
|
@ -167,3 +170,52 @@ export const createPullRequest = async ( options: {
|
|||
//@ts-ignore There is a type mismatch between the graphql schema and the response. pullRequest.data.head.repo.has_discussions is a boolean, but the graphql schema doesn't have that field.
|
||||
return pullRequest.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a pull request from GitHub.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {string} options.owner repository owner.
|
||||
* @param {string} options.name repository name.
|
||||
* @param prNumber pull request number.
|
||||
* @return {Promise<object>} pull request data.
|
||||
*/
|
||||
export const getPullRequest = async ( options: {
|
||||
owner: string;
|
||||
name: string;
|
||||
prNumber: string;
|
||||
} ): Promise< GetPullRequestEndpointResponse[ 'data' ] > => {
|
||||
const { owner, name, prNumber } = options;
|
||||
const pr = await octokitWithAuth().request(
|
||||
'GET /repos/{owner}/{repo}/pulls/{pull_number}',
|
||||
{
|
||||
owner,
|
||||
repo: name,
|
||||
pull_number: Number( prNumber ),
|
||||
}
|
||||
);
|
||||
|
||||
//@ts-ignore Not sure why this error comes up. All versions are up to date and the schema is correct.
|
||||
return pr.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine if a pull request is coming from a community contribution, i.e., not from a member of the WooCommerce organization.
|
||||
*
|
||||
* @param {Object} pullRequestData pull request data.
|
||||
* @param {string} owner repository owner.
|
||||
* @param {string} name repository name.
|
||||
* @return {boolean} if a pull request is coming from a community contribution.
|
||||
*/
|
||||
export const isCommunityPullRequest = (
|
||||
pullRequestData: GetPullRequestEndpointResponse[ 'data' ],
|
||||
owner: string,
|
||||
name: string
|
||||
) => {
|
||||
return (
|
||||
pullRequestData.author_association === 'CONTRIBUTOR' ||
|
||||
// It's possible a MEMBER can open a PR from their own fork.
|
||||
( pullRequestData.author_association === 'MEMBER' &&
|
||||
pullRequestData.head.repo.full_name !== `${ owner }/${ name }` )
|
||||
);
|
||||
};
|
||||
|
|
|
@ -3,5 +3,8 @@
|
|||
*/
|
||||
import { Endpoints } from '@octokit/types';
|
||||
|
||||
export type PullRequestEndpointResponse =
|
||||
export type CreatePullRequestEndpointResponse =
|
||||
Endpoints[ 'POST /repos/{owner}/{repo}/pulls' ][ 'response' ];
|
||||
|
||||
export type GetPullRequestEndpointResponse =
|
||||
Endpoints[ 'GET /repos/{owner}/{repo}/pulls/{pull_number}' ][ 'response' ];
|
||||
|
|
|
@ -10,6 +10,7 @@ import chalk from 'chalk';
|
|||
*/
|
||||
import CodeFreeze from './code-freeze/commands';
|
||||
import Slack from './slack/commands/slack';
|
||||
import Changefile from './changefile';
|
||||
import { Logger } from './core/logger';
|
||||
import { isGithubCI } from './core/environment';
|
||||
|
||||
|
@ -25,7 +26,8 @@ const program = new Command()
|
|||
.name( 'utils' )
|
||||
.description( 'Monorepo utilities' )
|
||||
.addCommand( CodeFreeze )
|
||||
.addCommand( Slack );
|
||||
.addCommand( Slack )
|
||||
.addCommand( Changefile );
|
||||
|
||||
program.exitOverride();
|
||||
|
||||
|
|
Loading…
Reference in New Issue