Skip to content

Commit

Permalink
Add github.comments feature to add comments to merged pull requests a…
Browse files Browse the repository at this point in the history
…nd closed issues
  • Loading branch information
webpro committed Mar 31, 2023
1 parent 1d7e3c2 commit b22ac20
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 7 deletions.
7 changes: 6 additions & 1 deletion config/release-it.json
Expand Up @@ -47,7 +47,12 @@
"timeout": 0,
"proxy": null,
"skipChecks": false,
"web": false
"web": false,
"comments": {
"submit": false,
"issue": ":rocket: _This issue has been resolved in v${version}. See [${releaseName}](${releaseUrl}) for release notes._",
"pr": ":rocket: _This pull request is included in v${version}. See [${releaseName}](${releaseUrl}) for release notes._"
}
},
"gitlab": {
"release": false,
Expand Down
34 changes: 29 additions & 5 deletions docs/github-releases.md
Expand Up @@ -69,8 +69,7 @@ the GitHub release. This will be invoked just before the actual GitHub release i
The value can either be a string or a function but a function is only supported when configuring release-it using
`.release-it.js` or `.release-it.cjs` file.

When the value is a string, it's executed as a shell script. Make sure it outputs to `stdout`.
An example:
When the value is a string, it's executed as a shell script. Make sure it outputs to `stdout`. An example:

```json
{
Expand All @@ -81,9 +80,8 @@ An example:
}
```

When the value is a function, it's executed with a single `context` parameter that contains the plugin context.
The function can also be `async`. Make sure that it returns a string value.
An example:
When the value is a function, it's executed with a single `context` parameter that contains the plugin context. The
function can also be `async`. Make sure that it returns a string value. An example:

```js
{
Expand Down Expand Up @@ -156,3 +154,29 @@ Example command to add assets and explicitly toggle the draft status to "publish
```bash
release-it --no-increment --no-git --github.release --github.update --github.assets=*.zip --no-github.draft
```

## Comments

To add a comment on each merged pull requests and closed issue that is part of the release, set `github.comments` to
`true`. Here are the default settings:

```json
{
"github": {
"comments": {
"submit": false,
"issue": ":rocket: _This issue has been resolved in v${version}. See [${releaseName}](${releaseUrl}) for release notes._",
"pr": ":rocket: _This pull request is included in v${version}. See [${releaseName}](${releaseUrl}) for release notes._"
}
}
}
```

Example comment:

:rocket: _This issue has been resolved in v15.10.0. See
[Release 15.10.0](https://github.com/release-it/release-it/releases/tag/15.10.0) for release notes._

This only works with `github.release: true` and not with [manual release via the web interface](#manual).

Since this is an experimental feature, it's disabled by default for now. Set `github.comments: true` to enable.
35 changes: 34 additions & 1 deletion lib/plugin/github/GitHub.js
Expand Up @@ -12,6 +12,7 @@ import ProxyAgent from 'proxy-agent';
import { format, parseVersion, readJSON, e } from '../../util.js';
import Release from '../GitRelease.js';
import prompts from './prompts.js';
import { getCommitsFromChangelog, getResolvedIssuesFromChangelog, searchQueries } from './util.js';

const pkg = readJSON(new URL('../../../package.json', import.meta.url));

Expand Down Expand Up @@ -142,7 +143,8 @@ class GitHub extends Release {
} else {
const release = async () => {
await this[publishMethod]();
return this.uploadAssets();
await this.uploadAssets();
return isUpdate ? Promise.resolve() : this.commentOnResolvedItems();
};
return this.step({ task: release, label: `GitHub ${type} release`, prompt: 'release' });
}
Expand Down Expand Up @@ -362,6 +364,37 @@ class GitHub extends Release {
}
});
}

async commentOnResolvedItems() {
const { isDryRun } = this.config;
const { owner, project: repo } = this.getContext('repo');
const { changelog } = this.config.getContext();
const { comments } = this.options;
const { submit, issue, pr } = comments;
const context = this.getContext();

if (!submit || !changelog || isDryRun) return;

const shas = getCommitsFromChangelog(changelog);
const searchResults = await Promise.all(searchQueries(this.client, owner, repo, shas));
const mergedPullRequests = searchResults.flatMap(items => items.map(item => ({ type: 'pr', number: item.number })));

const host = 'https://' + (this.options.host || this.getContext('repo.host'));
const resolvedIssues = getResolvedIssuesFromChangelog(host, owner, repo, changelog);

for (const item of [...resolvedIssues, ...mergedPullRequests]) {
const { type, number } = item;
const comment = format(format(type === 'pr' ? pr : issue, context), context);
const url = `${host}/${owner}/${repo}/${type === 'pr' ? 'pull' : 'issues'}/${number}`;

try {
await this.client.issues.createComment({ owner, repo, issue_number: number, body: comment });
this.log.log(`● Commented on ${url}`);
} catch (error) {
this.log.log(`✕ Failed to comment on ${url}`);
}
}
}
}

export default GitHub;
39 changes: 39 additions & 0 deletions lib/plugin/github/util.js
@@ -0,0 +1,39 @@
// Totally much borrowed from https://github.com/semantic-release/github/blob/master/lib/success.js
import issueParser from 'issue-parser';

const getSearchQueries = (base, commits, separator = '+') => {
return commits.reduce((searches, commit) => {
const lastSearch = searches[searches.length - 1];
if (lastSearch && lastSearch.length + commit.length <= 256 - separator.length) {
searches[searches.length - 1] = `${lastSearch}${separator}${commit}`;
} else {
searches.push(`${base}${separator}${commit}`);
}

return searches;
}, []);
};

export const searchQueries = (client, owner, repo, shas) =>
getSearchQueries(`repo:${owner}/${repo}+type:pr+is:merged`, shas).map(
async q => (await client.search.issuesAndPullRequests({ q })).data.items
);

export const getCommitsFromChangelog = changelog => {
const regex = /\(([a-f0-9]{7,})\)/i;
return changelog.split('\n').flatMap(message => {
const match = message.match(regex);
if (match) return match[1];
return [];
});
};

export const getResolvedIssuesFromChangelog = (host, owner, repo, changelog) => {
const parser = issueParser('github', { hosts: [host] });
return changelog
.split('\n')
.map(parser)
.flatMap(parsed => parsed.actions.close)
.filter(action => !action.slug || action.slug === `${owner}/${repo}`)
.map(action => ({ type: 'issue', number: parseInt(action.issue, 10) }));
};
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -70,6 +70,7 @@
"got": "12.6.0",
"inquirer": "9.1.5",
"is-ci": "3.0.1",
"issue-parser": "6.0.0",
"lodash": "4.17.21",
"mime-types": "2.1.35",
"new-github-release-url": "2.0.0",
Expand Down

0 comments on commit b22ac20

Please sign in to comment.