Skip to content

Commit 8f76070

Browse files
authored
Merge pull request #564 from linear-b/plugin-auto-description
new plugin that generates PR desc from conventional commits
2 parents dd2d46b + 7d0dce4 commit 8f76070

File tree

7 files changed

+204
-0
lines changed

7 files changed

+204
-0
lines changed

docs/filter-function-plugins.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ JavaScript plugins that enable custom filter functions for gitStream. To learn h
1818

1919
--8<-- "plugins/filters/extractOrcaFindings/README.md"
2020

21+
--8<-- "plugins/filters/generateDescription/README.md"
22+
2123
--8<-- "plugins/filters/getCodeowners/README.md"
2224

2325
--8<-- "plugins/filters/hasJiraIssue/README.md"
Loading
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2023 LinearB
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
2+
--8<-- "plugins/filters/generateDescription/reference.md"
3+
4+
5+
??? note "Plugin Code: generateDescription"
6+
```javascript
7+
--8<-- "plugins/filters/generateDescription/index.js"
8+
```
9+
<div class="result" markdown>
10+
<span>
11+
</span>
12+
</div>
13+
14+
15+
??? example "gitStream CM Example: generateDescription"
16+
```yaml+jinja
17+
--8<-- "plugins/filters/generateDescription/auto_pr_description.cm"
18+
```
19+
<div class="result" markdown>
20+
<span>
21+
</span>
22+
</div>
23+
24+
[Download Source Code](https://github.com/linear-b/gitstream/tree/main/plugins/filters/generateDescription)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
triggers:
2+
exclude:
3+
branch:
4+
- r/dependabot/
5+
6+
automations:
7+
generate_pr_desc:
8+
if:
9+
- true
10+
run:
11+
- action: update-description@v1
12+
args:
13+
description: |
14+
{{ branch | generateDescription(pr, repo, source) }}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/**
2+
* @module generateDescription
3+
* @description A gitStream plugin to auto-generate pull request descriptions based on commit messages and other criteria.
4+
* @param {Object} branch - The current branch object.
5+
* @param {Object} pr - The pull request object.
6+
* @param {Object} repo - The repository object.
7+
* @param {Object} source - The source object containing diff information.
8+
* @param {function} callback - The callback function.
9+
* @returns {Object} Returns the generated PR description.
10+
* @example {{ branch | generateDescription(pr, repo, source) }}
11+
* @license MIT
12+
**/
13+
14+
15+
// Parse commit messages
16+
function parseCommitMessages(messages) {
17+
const commitTypes = {
18+
feat: [],
19+
fix: [],
20+
chore: [],
21+
docs: [],
22+
style: [],
23+
refactor: [],
24+
perf: [],
25+
test: [],
26+
build: [],
27+
ci: [],
28+
other: [],
29+
};
30+
31+
messages
32+
.filter((message) => !message.includes("Merge branch"))
33+
.forEach((message) => {
34+
const match = message.match(
35+
/^(feat|fix|chore|docs|style|refactor|perf|test|build|ci):/,
36+
);
37+
if (match) {
38+
commitTypes[match[1]].push(message.replace(`${match[1]}:`, "").trim());
39+
} else {
40+
commitTypes.other.push(message);
41+
}
42+
});
43+
44+
return commitTypes;
45+
}
46+
47+
// Format commit section
48+
function formatCommitSection(type, commits) {
49+
return commits.length
50+
? `> - **${type}:**\n${commits.map((msg) => `> - ${msg}`).join("\n")}\n`
51+
: "";
52+
}
53+
54+
function containsNewTests(files) {
55+
const testPattern = /(test_|spec_|__tests__|_test|_tests|\.test|\.spec)/i;
56+
const testDirectoryPattern = /[\\/]?(tests|test|__tests__)[\\/]/i;
57+
const testKeywords = /describe\(|it\(|test\(|expect\(/i; // Common test keywords for JavaScript
58+
59+
for (const file of files) {
60+
const { new_file, diff, new_content } = file;
61+
62+
// Check if the filename indicates it's a test file
63+
if (testPattern.test(new_file) || testDirectoryPattern.test(new_file)) {
64+
return true;
65+
}
66+
67+
// Check if the diff or new content contains test-related code
68+
if (testKeywords.test(diff) || testKeywords.test(new_content)) {
69+
return true;
70+
}
71+
}
72+
73+
return false;
74+
}
75+
76+
function extractUserAdditions(description) {
77+
const match = description.match(
78+
/<!--- user additions start --->([\s\S]*?)<!--- user additions end --->/,
79+
);
80+
return match ? match[1].trim() : description.trim();
81+
}
82+
83+
// Generate PR description
84+
async function generateDescription(branch, pr, repo, source, callback) {
85+
if (process.env[__filename]) {
86+
return callback(null, process.env[__filename]);
87+
}
88+
89+
const commitTypes = parseCommitMessages(branch.commits.messages);
90+
91+
const addTests = containsNewTests(source.diff.files) ? "X" : " ";
92+
const codeApproved = pr.approvals > 0 ? "X" : " ";
93+
94+
const changes = Object.entries(commitTypes)
95+
.map(([type, commits]) => formatCommitSection(type, commits))
96+
.join("");
97+
const changesWithoutLastBr = changes.slice(0, -1);
98+
const userAdditions = extractUserAdditions(pr.description);
99+
100+
const result = `
101+
<!--- user additions start --->
102+
${userAdditions}
103+
<!--- user additions end --->
104+
105+
106+
**PR description below is managed by gitStream**
107+
<!--- Auto-generated by gitStream--->
108+
> #### Commits Summary
109+
> This pull request includes the following changes:
110+
${changesWithoutLastBr}
111+
> #### Checklist
112+
> - [${addTests}] Add tests
113+
> - [${codeApproved}] Code Reviewed and approved
114+
<!--- Auto-generated by gitStream end --->
115+
`;
116+
117+
process.env[__filename] = result.split("\n").join("\n ");
118+
return callback(null, process.env[__filename]);
119+
}
120+
121+
module.exports = { filter: generateDescription, async: true };
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<a name="module_generateDescription"></a>
2+
3+
## generateDescription
4+
A gitStream plugin to auto-generate pull request descriptions based on commit messages and other criteria.
5+
6+
![Example PR description](screenshots/generate-pr-description-example.png)
7+
8+
**Returns**: <code>Object</code> - Returns the generated PR description.
9+
**License**: MIT
10+
11+
| Param | Type | Description |
12+
| --- | --- | --- |
13+
| branch | <code>Object</code> | The current branch object. |
14+
| pr | <code>Object</code> | The pull request object. |
15+
| repo | <code>Object</code> | The repository object. |
16+
| source | <code>Object</code> | The source object containing diff information. |
17+
| callback | <code>function</code> | The callback function. |
18+
19+
**Example**
20+
```js
21+
{{ branch | generateDescription(pr, repo, source) }}
22+
```

0 commit comments

Comments
 (0)