Files
actions-ansi-to-html/node_modules/@semantic-release/github/lib/success.js
T
2025-11-11 06:53:11 -05:00

165 lines
6.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const {isNil, uniqBy, template, flatten, isEmpty} = require('lodash');
const pFilter = require('p-filter');
const AggregateError = require('aggregate-error');
const issueParser = require('issue-parser');
const debug = require('debug')('semantic-release:github');
const parseGithubUrl = require('./parse-github-url');
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');
const {RELEASE_NAME} = require('./definitions/constants');
const getReleaseLinks = require('./get-release-links');
module.exports = async (pluginConfig, context) => {
const {
options: {repositoryUrl},
commits,
nextRelease,
releases,
logger,
} = context;
const {
githubToken,
githubUrl,
githubApiPathPrefix,
proxy,
successComment,
failComment,
failTitle,
releasedLabels,
addReleases,
} = resolveConfig(pluginConfig, context);
const github = getClient({githubToken, githubUrl, githubApiPathPrefix, proxy});
// In case the repo changed name, get the new `repo`/`owner` as the search API will not follow redirects
const [owner, repo] = (await github.repos.get(parseGithubUrl(repositoryUrl))).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.issuesAndPullRequests({q})).data.items
);
const prs = await pFilter(
uniqBy(flatten(await Promise.all(searchQueries)), 'number'),
async ({number}) =>
(await github.pulls.listCommits({owner, repo, pull_number: number})).data.find(({sha}) => shas.includes(sha)) ||
shas.includes((await github.pulls.get({owner, repo, pull_number: 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: 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)({...context, issue})
: getSuccessComment(issue, releaseInfos, nextRelease);
try {
const comment = {owner, repo, issue_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) {
const labels = releasedLabels.map((label) => template(label)(context));
// Dont use .issues.addLabels for GHE < 2.16 support
// https://github.com/semantic-release/github/issues/138
await github.request('POST /repos/:owner/:repo/issues/:number/labels', {
owner,
repo,
number: issue.number,
data: labels,
});
logger.log('Added labels %O to issue #%d', labels, issue.number);
}
} catch (error) {
if (error.status === 403) {
logger.error('Not allowed to add a comment to the issue #%d.', issue.number);
} else if (error.status === 404) {
logger.error("Failed to add a comment to the issue #%d as it doesn't exist.", 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, issue_number: issue.number, state: 'closed'};
debug('closing issue: %O', updateIssue);
const {
data: {html_url: url},
} = await github.issues.update(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 (addReleases !== false && errors.length === 0) {
const ghRelease = releases.find((release) => release.name && release.name === RELEASE_NAME);
if (!isNil(ghRelease)) {
const ghRelaseId = ghRelease.id;
const additionalReleases = getReleaseLinks(releases);
if (!isEmpty(additionalReleases) && !isNil(ghRelaseId)) {
const newBody =
addReleases === 'top'
? additionalReleases.concat('\n---\n', nextRelease.notes)
: nextRelease.notes.concat('\n---\n', additionalReleases);
await github.repos.updateRelease({owner, repo, release_id: ghRelaseId, body: newBody});
}
}
}
if (errors.length > 0) {
throw new AggregateError(errors);
}
};