Skip to content

Commit

Permalink
feat: allow to disable opening and commenting on issues
Browse files Browse the repository at this point in the history
  • Loading branch information
pvdlg committed Oct 8, 2018
1 parent a5c4384 commit c15ca0b
Show file tree
Hide file tree
Showing 8 changed files with 236 additions and 119 deletions.
22 changes: 11 additions & 11 deletions README.md
Expand Up @@ -62,17 +62,17 @@ Follow the [Creating a personal access token for the command line](https://help.

### Options

| Option | Description | Default |
|-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|
| `githubUrl` | The GitHub Enterprise endpoint. | `GH_URL` or `GITHUB_URL` environment variable. |
| `githubApiPathPrefix` | The GitHub Enterprise API prefix. | `GH_PREFIX` or `GITHUB_PREFIX` environment variable. |
| `proxy` | The proxy to use to access the GitHub API. See [proxy](#proxy). | `HTTP_PROXY` environment variable. |
| `assets` | An array of files to upload to the release. See [assets](#assets). | - |
| `successComment` | The comment added to each issue and pull request resolved by the release. See [successComment](#successcomment). | `:tada: This issue has been resolved in version ${nextRelease.version} :tada:\n\nThe release is available on [GitHub release](<github_release_url>)` |
| `failComment` | The content of the issue created when a release fails. See [failComment](#failcomment). | Friendly message with links to **semantic-release** documentation and support, with the list of errors that caused the release to fail. |
| `failTitle` | The title of the issue created when a release fails. | `The automated release is failing 🚨` |
| `labels` | The [labels](https://help.github.com/articles/about-labels) to add to the issue created when a release fails. | `['semantic-release']` |
| `assignees` | The [assignees](https://help.github.com/articles/assigning-issues-and-pull-requests-to-other-github-users) to add to the issue created when a release fails. | - |
| Option | Description | Default |
|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|
| `githubUrl` | The GitHub Enterprise endpoint. | `GH_URL` or `GITHUB_URL` environment variable. |
| `githubApiPathPrefix` | The GitHub Enterprise API prefix. | `GH_PREFIX` or `GITHUB_PREFIX` environment variable. |
| `proxy` | The proxy to use to access the GitHub API. See [proxy](#proxy). | `HTTP_PROXY` environment variable. |
| `assets` | An array of files to upload to the release. See [assets](#assets). | - |
| `successComment` | The comment added to each issue and pull request resolved by the release. Set to `false` to disable commenting on issues and pull requests. See [successComment](#successcomment). | `:tada: This issue has been resolved in version ${nextRelease.version} :tada:\n\nThe release is available on [GitHub release](<github_release_url>)` |
| `failComment` | The content of the issue created when a release fails. Set to `false` to disable opening an issue when a release fails. See [failComment](#failcomment). | Friendly message with links to **semantic-release** documentation and support, with the list of errors that caused the release to fail. |
| `failTitle` | The title of the issue created when a release fails. Set to `false` to disable opening an issue when a release fails. | `The automated release is failing 🚨` |
| `labels` | The [labels](https://help.github.com/articles/about-labels) to add to the issue created when a release fails. Set to `false` to not add any label. | `['semantic-release']` |
| `assignees` | The [assignees](https://help.github.com/articles/assigning-issues-and-pull-requests-to-other-github-users) to add to the issue created when a release fails. | - |

#### proxy

Expand Down
41 changes: 23 additions & 18 deletions lib/fail.js
Expand Up @@ -17,25 +17,30 @@ module.exports = async (pluginConfig, context) => {
pluginConfig,
context
);
const {name: repo, owner} = parseGithubUrl(repositoryUrl);
const github = getClient({githubToken, githubUrl, githubApiPathPrefix, proxy});
const body = failComment ? template(failComment)({branch, errors}) : getFailComment(branch, errors);
const [srIssue] = await findSRIssues(github, failTitle, owner, repo);

if (srIssue) {
logger.log('Found existing semantic-release issue #%d.', srIssue.number);
const comment = {owner, repo, number: srIssue.number, body};
debug('create comment: %O', comment);
const {
data: {html_url: url},
} = await github.issues.createComment(comment);
logger.log('Added comment to issue #%d: %s.', srIssue.number, url);
if (failComment === false || failTitle === false) {
logger.log('Skip issue creation.');
} else {
const newIssue = {owner, repo, title: failTitle, body: `${body}\n\n${ISSUE_ID}`, labels, assignees};
debug('create issue: %O', newIssue);
const {
data: {html_url: url, number},
} = await github.issues.create(newIssue);
logger.log('Created issue #%d: %s.', number, url);
const {name: repo, owner} = parseGithubUrl(repositoryUrl);
const github = getClient({githubToken, githubUrl, githubApiPathPrefix, proxy});
const body = failComment ? template(failComment)({branch, errors}) : getFailComment(branch, errors);
const [srIssue] = await findSRIssues(github, failTitle, owner, repo);

if (srIssue) {
logger.log('Found existing semantic-release issue #%d.', srIssue.number);
const comment = {owner, repo, number: srIssue.number, body};
debug('create comment: %O', comment);
const {
data: {html_url: url},
} = await github.issues.createComment(comment);
logger.log('Added comment to issue #%d: %s.', srIssue.number, url);
} else {
const newIssue = {owner, repo, title: failTitle, body: `${body}\n\n${ISSUE_ID}`, labels: labels || [], assignees};
debug('create issue: %O', newIssue);
const {
data: {html_url: url, number},
} = await github.issues.create(newIssue);
logger.log('Created issue #%d: %s.', number, url);
}
}
};
6 changes: 3 additions & 3 deletions lib/resolve-config.js
@@ -1,4 +1,4 @@
const {isUndefined, castArray} = require('lodash');
const {isNil, castArray} = require('lodash');

module.exports = (
{githubUrl, githubApiPathPrefix, proxy, assets, successComment, failTitle, failComment, labels, assignees},
Expand All @@ -10,8 +10,8 @@ module.exports = (
proxy: proxy || env.HTTP_PROXY,
assets: assets ? castArray(assets) : assets,
successComment,
failTitle: isUndefined(failTitle) || failTitle === false ? 'The automated release is failing 🚨' : failTitle,
failTitle: isNil(failTitle) ? 'The automated release is failing 🚨' : failTitle,
failComment,
labels: isUndefined(labels) ? ['semantic-release'] : labels === false ? [] : castArray(labels),
labels: isNil(labels) ? ['semantic-release'] : labels === false ? false : castArray(labels),
assignees: assignees ? castArray(assignees) : assignees,
});
154 changes: 81 additions & 73 deletions lib/success.js
@@ -1,4 +1,4 @@
const {isUndefined, uniqBy, template, flatten} = require('lodash');
const {isNil, uniqBy, template, flatten} = require('lodash');
const parseGithubUrl = require('parse-github-url');
const pFilter = require('p-filter');
const AggregateError = require('aggregate-error');
Expand All @@ -19,95 +19,103 @@ module.exports = async (pluginConfig, context) => {
releases,
logger,
} = context;
const {githubToken, githubUrl, githubApiPathPrefix, proxy, successComment, failTitle} = resolveConfig(
const {githubToken, githubUrl, githubApiPathPrefix, proxy, successComment, failComment, failTitle} = resolveConfig(
pluginConfig,
context
);
const {name: repo, owner} = parseGithubUrl(repositoryUrl);
const github = getClient({githubToken, githubUrl, githubApiPathPrefix, proxy});
const parser = issueParser('github', githubUrl ? {hosts: [githubUrl]} : {});
const releaseInfos = releases.filter(release => Boolean(release.name));
const shas = commits.map(({hash}) => hash);
const errors = [];

const searchQueries = getSearchQueries(`repo:${owner}/${repo}+type:pr+is:merged`, shas).map(
async q => (await github.search.issues({q})).data.items
);
if (successComment === false) {
logger.log('Skip commenting on issues and pull requests.');
} else {
const parser = issueParser('github', githubUrl ? {hosts: [githubUrl]} : {});
const releaseInfos = releases.filter(release => Boolean(release.name));
const shas = commits.map(({hash}) => hash);

const prs = await pFilter(
uniqBy(flatten(await Promise.all(searchQueries)), 'number'),
async ({number}) =>
(await github.pullRequests.getCommits({owner, repo, number})).data.find(({sha}) => shas.includes(sha)) ||
shas.includes((await github.pullRequests.get({owner, repo, number})).data.merge_commit_sha)
);
const searchQueries = getSearchQueries(`repo:${owner}/${repo}+type:pr+is:merged`, shas).map(
async q => (await github.search.issues({q})).data.items
);

debug('found pull requests: %O', prs.map(pr => pr.number));
const prs = await pFilter(
uniqBy(flatten(await Promise.all(searchQueries)), 'number'),
async ({number}) =>
(await github.pullRequests.getCommits({owner, repo, number})).data.find(({sha}) => shas.includes(sha)) ||
shas.includes((await github.pullRequests.get({owner, repo, number})).data.merge_commit_sha)
);

// Parse the release commits message and PRs body to find resolved issues/PRs via comment keyworkds
const issues = [...prs.map(pr => pr.body), ...commits.map(commit => commit.message)].reduce((issues, message) => {
return message
? issues.concat(
parser(message)
.actions.close.filter(action => isUndefined(action.slug) || action.slug === `${owner}/${repo}`)
.map(action => ({number: parseInt(action.issue, 10)}))
)
: issues;
}, []);
debug('found pull requests: %O', prs.map(pr => pr.number));

debug('found issues via comments: %O', issues);
// Parse the release commits message and PRs body to find resolved issues/PRs via comment keyworkds
const issues = [...prs.map(pr => pr.body), ...commits.map(commit => commit.message)].reduce((issues, message) => {
return message
? issues.concat(
parser(message)
.actions.close.filter(action => isNil(action.slug) || action.slug === `${owner}/${repo}`)
.map(action => ({number: parseInt(action.issue, 10)}))
)
: issues;
}, []);

const errors = [];
debug('found issues via comments: %O', issues);

await Promise.all(
uniqBy([...prs, ...issues], 'number').map(async issue => {
const body = successComment
? template(successComment)({branch, lastRelease, commits, nextRelease, releases, issue})
: getSuccessComment(issue, releaseInfos, nextRelease);
try {
const state = issue.state || (await github.issues.get({owner, repo, number: issue.number})).data.state;
await Promise.all(
uniqBy([...prs, ...issues], 'number').map(async issue => {
const body = successComment
? template(successComment)({branch, lastRelease, commits, nextRelease, releases, issue})
: getSuccessComment(issue, releaseInfos, nextRelease);
try {
const state = issue.state || (await github.issues.get({owner, repo, number: issue.number})).data.state;

if (state === 'closed') {
const comment = {owner, repo, number: issue.number, body};
debug('create comment: %O', comment);
const {
data: {html_url: url},
} = await github.issues.createComment(comment);
logger.log('Added comment to issue #%d: %s', issue.number, url);
} else {
logger.log("Skip comment on issue #%d as it's open: %s", issue.number);
}
} catch (error) {
if (error.code === 404) {
logger.error("Failed to add a comment to the issue #%d as it doesn't exists.", issue.number);
} else {
errors.push(error);
logger.error('Failed to add a comment to the issue #%d.', issue.number);
// Don't throw right away and continue to update other issues
if (state === 'closed') {
const comment = {owner, repo, number: issue.number, body};
debug('create comment: %O', comment);
const {
data: {html_url: url},
} = await github.issues.createComment(comment);
logger.log('Added comment to issue #%d: %s', issue.number, url);
} else {
logger.log("Skip comment on issue #%d as it's open: %s", issue.number);
}
} catch (error) {
if (error.code === 404) {
logger.error("Failed to add a comment to the issue #%d as it doesn't exists.", issue.number);
} else {
errors.push(error);
logger.error('Failed to add a comment to the issue #%d.', issue.number);
// Don't throw right away and continue to update other issues
}
}
}
})
);
})
);
}

const srIssues = await findSRIssues(github, failTitle, owner, repo);
if (failComment === false || failTitle === false) {
logger.log('Skip closing issue.');
} else {
const srIssues = await findSRIssues(github, failTitle, owner, repo);

debug('found semantic-release issues: %O', srIssues);
debug('found semantic-release issues: %O', srIssues);

await Promise.all(
srIssues.map(async issue => {
debug('close issue: %O', issue);
try {
const updateIssue = {owner, repo, number: issue.number, state: 'closed'};
debug('closing issue: %O', updateIssue);
const {
data: {html_url: url},
} = await github.issues.edit(updateIssue);
logger.log('Closed issue #%d: %s.', issue.number, url);
} catch (error) {
errors.push(error);
logger.error('Failed to close the issue #%d.', issue.number);
// Don't throw right away and continue to close other issues
}
})
);
await Promise.all(
srIssues.map(async issue => {
debug('close issue: %O', issue);
try {
const updateIssue = {owner, repo, number: issue.number, state: 'closed'};
debug('closing issue: %O', updateIssue);
const {
data: {html_url: url},
} = await github.issues.edit(updateIssue);
logger.log('Closed issue #%d: %s.', issue.number, url);
} catch (error) {
errors.push(error);
logger.error('Failed to close the issue #%d.', issue.number);
// Don't throw right away and continue to close other issues
}
})
);
}

if (errors.length > 0) {
throw new AggregateError(errors);
Expand Down

0 comments on commit c15ca0b

Please sign in to comment.