find-node-modules
Version:
Return an array of all parent node_modules directories
147 lines (130 loc) • 5.62 kB
JavaScript
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.issuesAndPullRequests({q})).data.items
);
const prs = await pFilter(
uniqBy(flatten(await Promise.all(searchQueries)), 'number'),
async ({number}) =>
(await github.pullRequests.listCommits({owner, repo, pull_number: number})).data.find(({sha}) =>
shas.includes(sha)
) || shas.includes((await github.pullRequests.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: 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, issue_number: issue.number})).data.state;
if (state === 'closed') {
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) {
// Don’t 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: 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.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 (errors.length > 0) {
throw new AggregateError(errors);
}
};