-
Notifications
You must be signed in to change notification settings - Fork 118
/
success.js
138 lines (122 loc) · 5.16 KB
/
success.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
const {isNil, uniqBy, template, flatten} = require('lodash');
const parseGithubUrl = require('parse-github-url');
const pFilter = require('p-filter');
const AggregateError = require('aggregate-error');
const issueParser = require('issue-parser');
const debug = require('debug')('semantic-release:github');
const resolveConfig = require('./resolve-config');
const getClient = require('./get-client');
const getSearchQueries = require('./get-search-queries');
const getSuccessComment = require('./get-success-comment');
const findSRIssues = require('./find-sr-issues');
module.exports = async (pluginConfig, context) => {
const {
options: {branch, repositoryUrl},
lastRelease,
commits,
nextRelease,
releases,
logger,
} = context;
const {
githubToken,
githubUrl,
githubApiPathPrefix,
proxy,
successComment,
failComment,
failTitle,
releasedLabels,
} = resolveConfig(pluginConfig, context);
const github = getClient({githubToken, githubUrl, githubApiPathPrefix, proxy});
let {name: repo, owner} = parseGithubUrl(repositoryUrl);
// In case the repo changed name, get the new `repo`/`owner` as the search API will not follow redirects
[owner, repo] = (await github.repos.get({repo, owner})).data.full_name.split('/');
const errors = [];
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 searchQueries = getSearchQueries(`repo:${owner}/${repo}+type:pr+is:merged`, shas).map(
async q => (await github.search.issues({q})).data.items
);
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)
);
debug('found pull requests: %O', prs.map(pr => pr.number));
// 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;
}, []);
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;
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);
if (releasedLabels) {
await github.issues.addLabels({owner, repo, number: issue.number, labels: releasedLabels});
logger.log('Added labels %O to issue #%d', releasedLabels, issue.number);
}
} else {
logger.log("Skip comment and labels 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 (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);
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);
}
};