Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion communicate-on-pull-request-merged/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ steps:

# License

The scripts and documentation in this project are released under the [MIT License](LICENSE)
The scripts and documentation in this project are released under the [MIT License](../LICENSE)
26 changes: 26 additions & 0 deletions communicate-on-pull-request-merged/__tests__/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,32 @@ describe('action test suite', () => {

expect(api.isDone()).toBeTruthy();
});

it(`It does not crash when labels are empty for (${scenario.response})`, async () => {
process.env['INPUT_REPO-TOKEN'] = 'token';
process.env['INPUT_PR-COMMENT'] = 'message';
process.env['INPUT_PR-LABEL-TO-ADD'] = '';
process.env['INPUT_PR-LABEL-TO-REMOVE'] = '';

process.env['GITHUB_REPOSITORY'] = 'foo/bar';
process.env['GITHUB_EVENT_PATH'] = path.join(__dirname, scenario.response);

const {run} = await import('../src/main.js');

const api = nock('https://api.github.com')
.post('/repos/foo/bar/pulls/10/reviews', '{"body":"message","event":"COMMENT"}')
.reply(200);

// These should NOT be called
const labelsGet = nock('https://api.github.com').get('/repos/foo/bar/issues/10/labels').reply(200, []);
const labelsPost = nock('https://api.github.com').post('/repos/foo/bar/issues/10/labels').reply(200);

await run();

expect(api.isDone()).toBeTruthy();
expect(labelsGet.isDone()).toBeFalsy();
expect(labelsPost.isDone()).toBeFalsy();
});
}

for (const scenario of invalidScenarios) {
Expand Down
6 changes: 4 additions & 2 deletions communicate-on-pull-request-merged/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ inputs:
pr-label-to-add:
description: 'The label to apply when a pull request is merged'
default: 'status: included-in-next-release'
required: false
pr-label-to-remove:
description: 'The label to remove when a pull request is merged'
default: 'status: needs-attention'
description: 'The label to remove when a pull request is merged'
default: 'status: needs-attention'
required: false
runs:
using: node24
main: lib/index.js
13 changes: 9 additions & 4 deletions communicate-on-pull-request-merged/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36722,11 +36722,16 @@ async function run() {
return;
}
const labelToRemove = core.getInput('pr-label-to-remove');
const canRemoveLabel = await canRemoveLabelFromIssue(client, prNumber, labelToRemove);
if (canRemoveLabel) {
await removeLabel(client, prNumber, labelToRemove);
if (labelToRemove) {
const canRemoveLabel = await canRemoveLabelFromIssue(client, prNumber, labelToRemove);
if (canRemoveLabel) {
await removeLabel(client, prNumber, labelToRemove);
}
}
const labelToAdd = core.getInput('pr-label-to-add');
if (labelToAdd) {
await addLabels(client, prNumber, [labelToAdd]);
}
await addLabels(client, prNumber, [core.getInput('pr-label-to-add')]);
await addComment(client, prNumber, core.getInput('pr-comment', { required: true }));
}
catch (error) {
Expand Down
13 changes: 9 additions & 4 deletions communicate-on-pull-request-merged/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,17 @@ export async function run() {
}

const labelToRemove = core.getInput('pr-label-to-remove');
const canRemoveLabel = await canRemoveLabelFromIssue(client, prNumber, labelToRemove);
if (canRemoveLabel) {
await removeLabel(client, prNumber, labelToRemove);
if (labelToRemove) {
const canRemoveLabel = await canRemoveLabelFromIssue(client, prNumber, labelToRemove);
if (canRemoveLabel) {
await removeLabel(client, prNumber, labelToRemove);
}
}

await addLabels(client, prNumber, [core.getInput('pr-label-to-add')]);
const labelToAdd = core.getInput('pr-label-to-add');
if (labelToAdd) {
await addLabels(client, prNumber, [labelToAdd]);
}
await addComment(client, prNumber, core.getInput('pr-comment', {required: true}));
} catch (error) {
if (error instanceof Error) {
Expand Down
2 changes: 1 addition & 1 deletion communicate-on-pull-request-released/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ steps:

# License

The scripts and documentation in this project are released under the [MIT License](LICENSE)
The scripts and documentation in this project are released under the [MIT License](../LICENSE)
31 changes: 31 additions & 0 deletions communicate-on-pull-request-released/__tests__/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,37 @@ describe('action test suite', () => {

expect(api.isDone()).toBeTruthy();
});

it(`It does not crash when labels are empty for (${scenario.response})`, async () => {
process.env['INPUT_REPO-TOKEN'] = 'token';
process.env['INPUT_PR-LABEL-TO-ADD'] = '';
process.env['INPUT_PR-LABEL-TO-REMOVE'] = '';

process.env['GITHUB_REPOSITORY'] = 'foo/bar';
process.env['GITHUB_EVENT_NAME'] = scenario.event_name;
process.env['GITHUB_EVENT_PATH'] = path.join(__dirname, scenario.response);

const {run} = await import('../src/main.js');

const api = nock('https://api.github.com')
.persist()
.post(
'/repos/foo/bar/pulls/999/reviews',
'{"body":"Congratulations! :tada: This was released as part of [_fastlane_ 2.134.1](https://github.com/Codertocat/Hello-World/runs/128620228) :rocket:","event":"COMMENT"}'
)
.reply(200)
.get('/repos/foo/bar/pulls/999')
.reply(200, JSON.parse('{"body":"closes #10"}'))
.post(
'/repos/foo/bar/issues/10/comments',
'{"body":"The pull request #999 that closed this issue was merged and released as part of [_fastlane_ 2.134.1](https://github.com/Codertocat/Hello-World/runs/128620228) :rocket:\\nPlease let us know if the functionality works as expected as a reply here. If it does not, please open a new issue. Thanks!"}'
)
.reply(200);

await run();

expect(api.isDone()).toBeTruthy();
});
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ describe('pull request parser test suite', () => {
issueNumber: 15,
owner: 'foo',
repo: 'bar'
},
{
prBody: 'Fixes #456 and closes #456',
issueNumber: 456,
owner: 'foo',
repo: 'bar'
}
];

Expand Down Expand Up @@ -120,13 +126,13 @@ describe('pull request parser test suite', () => {

for (const scenario of validScenarios) {
it(`It detects referenced issue for (${scenario.prBody})`, async () => {
expect(getReferencedIssue(scenario.owner, scenario.repo, scenario.prBody)).toEqual(scenario.issueNumber);
expect(getReferencedIssue(scenario.owner, scenario.repo, scenario.prBody)).toEqual([scenario.issueNumber]);
});
}

for (const scenario of invalidScenarios) {
it(`It does not detect referenced issue for (${scenario.prBody})`, async () => {
expect(getReferencedIssue(scenario.owner, scenario.repo, scenario.prBody)).toBeUndefined();
expect(getReferencedIssue(scenario.owner, scenario.repo, scenario.prBody)).toEqual([]);
});
}
});
2 changes: 2 additions & 0 deletions communicate-on-pull-request-released/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ inputs:
pr-label-to-add:
description: 'The label to apply when a pull request is released'
default: 'status: released'
required: false
pr-label-to-remove:
description: 'The label to remove when a pull request is released'
default: 'status: included-in-next-release'
required: false
runs:
using: 'node24'
main: 'lib/index.js'
1 change: 1 addition & 0 deletions communicate-on-pull-request-released/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export default {
preset: 'ts-jest',
reporters: ['default'],
resolver: 'ts-jest-resolver',
resetModules: true,
setupFilesAfterEnv: ['./__tests__/setup.js'],
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
Expand Down
144 changes: 89 additions & 55 deletions communicate-on-pull-request-released/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36712,30 +36712,35 @@ let ISSUE_CLOSING_KEYWORDS = [
];
function getReferencedIssue(owner, repo, prBody) {
if (!prBody || prBody.trim() === '') {
return undefined;
return [];
}
let issueNumbers = [];
// Searching for issue closing keywords and issue identifier in pull request's description,
// i.e. `fixes #1234`, `close #444`, `resolved #1`
let regex = new RegExp(`(${ISSUE_CLOSING_KEYWORDS.join('|')}) #\\d{1,}`, 'i');
let regex = new RegExp(`(${ISSUE_CLOSING_KEYWORDS.join('|')}) #\\d{1,}`, 'gi');
let matched = prBody.match(regex);
if (matched && matched.length > 0) {
const issueMatch = matched[0].match(/#\d{1,}/i);
if (issueMatch && issueMatch.length > 0) {
const issueNumber = issueMatch[0].replace('#', '');
return Number(issueNumber);
}
matched.forEach(match => {
const issueMatch = match.match(/#\d{1,}/i);
if (issueMatch && issueMatch.length > 0) {
const issueNumber = issueMatch[0].replace('#', '');
issueNumbers.push(Number(issueNumber));
}
});
}
// Searching for issue closing keywords and issue URL in pull request's description,
// i.e. `closes https://github.com/REPOSITORY_OWNER/REPOSITORY_NAME/issues/1234`
regex = new RegExp(`(${ISSUE_CLOSING_KEYWORDS.join('|')}) https:\\/\\/github.com\\/${owner}\\/${repo}\\/issues\\/\\d{1,}`, 'i');
regex = new RegExp(`(${ISSUE_CLOSING_KEYWORDS.join('|')}) https:\\/\\/github.com\\/${owner}\\/${repo}\\/issues\\/\\d{1,}`, 'gi');
matched = prBody.match(regex);
if (matched && matched.length > 0) {
const issue = matched[0].split('/').pop();
if (issue) {
return Number(issue);
}
matched.forEach(match => {
const issue = match.split('/').pop();
if (issue) {
issueNumbers.push(Number(issue));
}
});
}
return undefined;
return [...new Set(issueNumbers)];
}

;// CONCATENATED MODULE: ./src/release-parser.ts
Expand Down Expand Up @@ -36792,11 +36797,16 @@ async function run() {
for (const prNumber of prNumbers) {
await addCommentToPullRequest(client, prNumber, `Congratulations! :tada: This was released as part of [_fastlane_ ${release.tag}](${release.htmlURL}) :rocket:`);
const labelToRemove = core.getInput('pr-label-to-remove');
const canRemoveLabel = await canRemoveLabelFromIssue(client, prNumber, labelToRemove);
if (canRemoveLabel) {
await removeLabel(client, prNumber, labelToRemove);
if (labelToRemove) {
const canRemoveLabel = await canRemoveLabelFromIssue(client, prNumber, labelToRemove);
if (canRemoveLabel) {
await removeLabel(client, prNumber, labelToRemove);
}
}
const labelToAdd = core.getInput('pr-label-to-add');
if (labelToAdd) {
await addLabels(client, prNumber, [labelToAdd]);
}
await addLabels(client, prNumber, [core.getInput('pr-label-to-add')]);
await addCommentToReferencedIssue(client, prNumber, release);
}
}
Expand All @@ -36810,26 +36820,36 @@ async function run() {
}
}
async function addCommentToPullRequest(client, prNumber, comment) {
await client.rest.pulls.createReview({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
pull_number: prNumber,
body: comment,
event: 'COMMENT'
});
try {
await client.rest.pulls.createReview({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
pull_number: prNumber,
body: comment,
event: 'COMMENT'
});
}
catch (error) {
console.log(`Failed to add comment to pull request #${prNumber}: ${error instanceof Error ? error.message : error}`);
}
}
async function addCommentToReferencedIssue(client, prNumber, release) {
const pullRequest = await getPullRequest(client, prNumber);
if (pullRequest.body) {
const issueNumber = getReferencedIssue(github.context.repo.owner, github.context.repo.repo, pullRequest.body);
if (issueNumber) {
const message = [
`The pull request #${prNumber} that closed this issue was merged and released as part of [_fastlane_ ${release.tag}](${release.htmlURL}) :rocket:`,
`Please let us know if the functionality works as expected as a reply here. If it does not, please open a new issue. Thanks!`
];
await addIssueComment(client, issueNumber, message.join('\n'));
try {
const pullRequest = await getPullRequest(client, prNumber);
if (pullRequest.body) {
const issueNumbers = getReferencedIssue(github.context.repo.owner, github.context.repo.repo, pullRequest.body);
for (const issueNumber of issueNumbers) {
const message = [
`The pull request #${prNumber} that closed this issue was merged and released as part of [_fastlane_ ${release.tag}](${release.htmlURL}) :rocket:`,
`Please let us know if the functionality works as expected as a reply here. If it does not, please open a new issue. Thanks!`
];
await addIssueComment(client, issueNumber, message.join('\n'));
}
}
}
catch (error) {
console.log(`Failed to add comment to referenced issue for PR #${prNumber}: ${error instanceof Error ? error.message : error}`);
}
}
async function getPullRequest(client, prNumber) {
const response = await client.rest.pulls.get({
Expand All @@ -36840,34 +36860,49 @@ async function getPullRequest(client, prNumber) {
return response.data;
}
async function canRemoveLabelFromIssue(client, prNumber, label) {
const response = await client.rest.issues.listLabelsOnIssue({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
issue_number: prNumber
});
const issueLabels = response.data;
for (const issueLabel of issueLabels) {
if (issueLabel.name === label) {
return true;
try {
const response = await client.rest.issues.listLabelsOnIssue({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
issue_number: prNumber
});
const issueLabels = response.data;
for (const issueLabel of issueLabels) {
if (issueLabel.name === label) {
return true;
}
}
}
catch (error) {
console.log(`Failed to list labels on issue #${prNumber}: ${error instanceof Error ? error.message : error}`);
}
return false;
}
async function addLabels(client, prNumber, labels) {
await client.rest.issues.addLabels({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
issue_number: prNumber,
labels: labels
});
try {
await client.rest.issues.addLabels({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
issue_number: prNumber,
labels: labels
});
}
catch (error) {
console.log(`Failed to add labels to PR #${prNumber}: ${error instanceof Error ? error.message : error}`);
}
}
async function removeLabel(client, prNumber, label) {
await client.rest.issues.removeLabel({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
issue_number: prNumber,
name: label
});
try {
await client.rest.issues.removeLabel({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
issue_number: prNumber,
name: label
});
}
catch (error) {
console.log(`Failed to remove label '${label}' from PR #${prNumber}: ${error instanceof Error ? error.message : error}`);
}
}
async function addIssueComment(client, issueNumber, message) {
await client.rest.issues.createComment({
Expand Down Expand Up @@ -36918,7 +36953,6 @@ async function resolveReleaseByVersion(client, version) {
return undefined;
}
}
run();

;// CONCATENATED MODULE: ./src/index.ts

Expand Down
Loading
Loading