Skip to content

Commit f9e6585

Browse files
✨ feat(changelog): add changelog style configs (#669)
* ✨ feat(changelog): add configs `showAuthorAvatar` `showSummary` `reduceHeadingLevel` `newlineTimestamp` `addBackToTop` * ✅ test(changelog): fix changelog test * 🛠️ chore(changelog): clean * ✨ feat(changelog): better summary style * ✨ feat(changelog): add summary avatar list * 💄 style(changelog): better summary layout * 🐛 fix(changelog): footer position * 🐛 fix(changelog): fix summary spacing * 💄 style(changelog): fix typo * 📝 docs(changlog): update README.md * ✅ test(changelog): add `finalizeContext` and `transformer` tests
1 parent 678d734 commit f9e6585

17 files changed

+446
-51
lines changed

packages/changelog/.changelogrc.js

+3
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@ const base = require('../../.changelogrc');
22

33
module.exports = {
44
...base,
5+
scopeDisplayName: {
6+
'*': 'misc',
7+
},
58
};

packages/changelog/README.md

+34-1
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,48 @@ interface ChangelogConfig {
4747
* title language
4848
* @default en-US
4949
*/
50-
titleLanguage?: 'en-US' | 'zh-CN';
50+
titleLanguage?: 'en-US' | 'zh-CN' | 'mix';
5151
/**
5252
* whether to show author
5353
* @default false
5454
*/
5555
showAuthor?: boolean;
56+
/**
57+
* whether to show author avatar
58+
* @default false
59+
*/
60+
showAuthorAvatar?: boolean;
61+
/**
62+
* whether to show summary
63+
* @default false
64+
*/
65+
showSummary?: boolean;
66+
/**
67+
* Reduce heading level from # to ##
68+
* @default false
69+
*/
70+
reduceHeadingLevel?: boolean;
71+
/**
72+
* put timestamp to second line
73+
* @default false
74+
*/
75+
newlineTimestamp?: boolean;
76+
/**
77+
* add back to top button
78+
* @default false
79+
*/
80+
addBackToTop?: boolean;
5681
}
5782
```
5883

84+
> 👉 Tip: If turn on `back to top` button, should edit `CHANGELOG.md` first like below:
85+
86+
```markdown
87+
<a name="readme-top"></a>
88+
89+
# Changelog
90+
```
91+
5992
## License
6093

6194
[MIT](../../LICENSE) ® Arvin Xu

packages/changelog/package.json

+8-7
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,18 @@
3333
"dependencies": {
3434
"@gitmoji/commit-types": "1.1.5",
3535
"@gitmoji/parser-opts": "1.4.0",
36-
"cosmiconfig": "^7.0.0"
36+
"cosmiconfig": "^7"
3737
},
3838
"devDependencies": {
39-
"@types/conventional-changelog-core": "^4.1.1",
40-
"better-than-before": "^1.0.0",
41-
"conventional-changelog-core": "^4.2.2",
39+
"@types/conventional-changelog-core": "^4",
40+
"better-than-before": "^1",
41+
"conventional-changelog-core": "^4",
4242
"conventional-changelog-writer": "^5",
4343
"conventional-commits-parser": "^3",
44-
"git-dummy-commit": "^1.3.0",
45-
"shelljs": "^0.8.4",
46-
"through2": "^4.0.2"
44+
"git-dummy-commit": "^1",
45+
"pangu": "^4",
46+
"shelljs": "^0",
47+
"through2": "^4"
4748
},
4849
"publishConfig": {
4950
"access": "public",

packages/changelog/src/customConfig.ts

+20
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ export interface CustomConfig {
1818
* 是否显示作者
1919
*/
2020
showAuthor?: boolean;
21+
/**
22+
* 是否显示作者头像
23+
*/
24+
showAuthorAvatar?: boolean;
25+
/**
26+
* 是否显示摘要
27+
*/
28+
showSummary?: boolean;
2129
/**
2230
* whether to include emoji in title
2331
*/
@@ -26,6 +34,18 @@ export interface CustomConfig {
2634
* title language
2735
*/
2836
titleLanguage?: 'en-US' | 'zh-CN';
37+
/**
38+
* Reduce heading level from # to ##
39+
*/
40+
reduceHeadingLevel?: boolean;
41+
/**
42+
* put timestamp to second line
43+
*/
44+
newlineTimestamp?: boolean;
45+
/**
46+
* add back to top button
47+
*/
48+
addBackToTop?: boolean;
2949
}
3050

3151
const explorer = cosmiconfigSync('changelog');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`test transformCommitGroups should transform commitGroups correctly 1`] = `
4+
{
5+
"authors": [
6+
{
7+
"authorEmail": "test@test.com",
8+
"authorName": "test",
9+
"authorNameEncode": "test",
10+
},
11+
],
12+
"commitGroups": [
13+
{
14+
"commits": [
15+
{
16+
"authorEmail": "test@test.com",
17+
"authorName": "test",
18+
"authorNameEncode": "test",
19+
"first": true,
20+
"hash": "1234",
21+
"last": true,
22+
"scope": "test",
23+
"subject": "test commit",
24+
"title": "✨ Features",
25+
},
26+
],
27+
"subtitle": "What's improved",
28+
"title": "✨ Features",
29+
},
30+
],
31+
}
32+
`;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import type { Context } from 'conventional-changelog-writer';
2+
import { CustomConfig } from '../customConfig';
3+
import { typeMap } from '../transformer/typeDisplayName';
4+
5+
describe('test transformCommitGroups', () => {
6+
const customConfig: CustomConfig = {
7+
scopeDisplayName: {
8+
'*': 'all',
9+
},
10+
};
11+
const context: Context = {
12+
commitGroups: [
13+
{
14+
title: '✨ Features',
15+
commits: [
16+
{
17+
hash: '1234',
18+
subject: 'test commit',
19+
scope: 'test',
20+
title: '✨ Features',
21+
authorName: 'test',
22+
authorEmail: 'test@test.com',
23+
authorNameEncode: 'test',
24+
},
25+
],
26+
},
27+
],
28+
authors: [],
29+
};
30+
31+
it('should transform commitGroups correctly', () => {
32+
const subCommitScope = customConfig?.scopeDisplayName?.['*'] || '';
33+
const authors = {};
34+
context.commitGroups = context.commitGroups.map((item) => {
35+
const subtitle = Object.values(typeMap).find(
36+
(i) =>
37+
item.title.includes(i['emoji']) ||
38+
item.title.includes(i['en-US']) ||
39+
item.title.includes(i['zh-CN']),
40+
).subtitle;
41+
let group;
42+
let commits = item.commits.sort((a, b) => {
43+
if (a.scope === subCommitScope && b.scope === subCommitScope) {
44+
return 0;
45+
} else if (a.scope === subCommitScope) {
46+
return 1;
47+
} else if (b.scope === subCommitScope) {
48+
return -1;
49+
} else {
50+
return 0;
51+
}
52+
});
53+
commits = commits.map((c, index) => {
54+
if (group !== c.scope) {
55+
group = c.scope;
56+
c.first = true;
57+
} else {
58+
c.first = false;
59+
}
60+
if (!commits[index + 1] || group !== commits[index + 1].scope) {
61+
c.last = true;
62+
} else {
63+
c.last = false;
64+
}
65+
if (c.authorNameEncode && !authors[c.authorNameEncode]) {
66+
authors[c.authorNameEncode] = {
67+
authorName: c.authorName,
68+
authorEmail: c.authorEmail,
69+
authorNameEncode: c.authorNameEncode,
70+
};
71+
}
72+
return c;
73+
});
74+
return {
75+
title: item.title,
76+
subtitle,
77+
commits,
78+
};
79+
});
80+
context.authors = Object.values(authors);
81+
expect(context).toMatchSnapshot();
82+
});
83+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import type { Context } from 'conventional-changelog-writer';
2+
import { CustomConfig } from '../customConfig';
3+
import { typeMap } from '../transformer/typeDisplayName';
4+
export default (customConfig: CustomConfig) => (context: Context): Context => {
5+
const subCommitScope = customConfig?.scopeDisplayName?.['*'] || '';
6+
const authors = {};
7+
context.commitGroups = context.commitGroups.map((item) => {
8+
const subtitle = Object.values(typeMap).find(
9+
(i) =>
10+
item.title.includes(i['emoji']) ||
11+
item.title.includes(i['en-US']) ||
12+
item.title.includes(i['zh-CN']),
13+
).subtitle;
14+
let group;
15+
let commits = item.commits.sort((a, b) => {
16+
if (a.scope === subCommitScope && b.scope === subCommitScope) {
17+
return 0;
18+
} else if (a.scope === subCommitScope) {
19+
return 1;
20+
} else if (b.scope === subCommitScope) {
21+
return -1;
22+
} else {
23+
return 0;
24+
}
25+
});
26+
commits = commits.map((c, index) => {
27+
if (group !== c.scope) {
28+
group = c.scope;
29+
c.first = true;
30+
} else {
31+
c.first = false;
32+
}
33+
if (!commits[index + 1] || group !== commits[index + 1].scope) {
34+
c.last = true;
35+
} else {
36+
c.last = false;
37+
}
38+
if (c.authorNameEncode && !authors[c.authorNameEncode]) {
39+
authors[c.authorNameEncode] = {
40+
authorName: c.authorName,
41+
authorEmail: c.authorEmail,
42+
authorNameEncode: c.authorNameEncode,
43+
};
44+
}
45+
return c;
46+
});
47+
return {
48+
title: item.title,
49+
subtitle,
50+
commits,
51+
};
52+
});
53+
context.authors = Object.values(authors);
54+
return context;
55+
};
+34-15
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,47 @@
11
import type { Options } from 'conventional-changelog-writer';
22
import { readFileSync } from 'fs';
33
import { resolve } from 'path';
4-
54
import type { CustomConfig } from './customConfig';
6-
5+
import finalizeContext from './finalizeContext';
76
import transformer from './transformer';
87

98
const basePath = resolve(__dirname, './templates');
109

1110
const template = readFileSync(`${basePath}/template.hbs`, 'utf-8');
11+
const summaryTemplate = readFileSync(`${basePath}/summary-template.hbs`, 'utf-8');
1212
const header = readFileSync(`${basePath}/header.hbs`, 'utf-8');
13+
const headerNewlineTimestamp = readFileSync(`${basePath}/header-newline-timestamp.hbs`, 'utf-8');
1314
const commit = readFileSync(`${basePath}/commit.hbs`, 'utf-8');
1415
const footer = readFileSync(`${basePath}/footer.hbs`, 'utf-8');
1516
const author = readFileSync(`${basePath}/author.hbs`, 'utf-8');
16-
17-
export default (customConfig: CustomConfig): Options => ({
18-
transform: transformer(customConfig),
19-
groupBy: 'type',
20-
commitGroupsSort: 'title',
21-
commitsSort: ['scope', 'subject'],
22-
noteGroupsSort: 'title',
23-
mainTemplate: template,
24-
headerPartial: header,
25-
// 替换 commit.hbs 模板中的 gitUserInfo
26-
commitPartial: commit.replace(/{{gitUserInfo}}/g, customConfig.showAuthor ? author : ''),
27-
footerPartial: footer,
28-
});
17+
const authorAvatar = readFileSync(`${basePath}/author-avatar.hbs`, 'utf-8');
18+
const backToTop = readFileSync(`${basePath}/back-to-top.hbs`, 'utf-8');
19+
const reduceHeadingLevel = (skip: boolean, template: string): string => {
20+
if (skip) return template;
21+
return template.replace(/(^|\n)(#+)/g, (match, p1, p2) => p1 + '#' + p2);
22+
};
23+
export default (customConfig: CustomConfig): Options => {
24+
const mainTemplate = customConfig.showSummary ? summaryTemplate : template;
25+
const commitPartial = commit.replace(
26+
/{{gitUserInfo}}/g,
27+
customConfig.showAuthor ? (customConfig.showAuthorAvatar ? authorAvatar : author) : '',
28+
);
29+
const headerPartial = customConfig.newlineTimestamp ? headerNewlineTimestamp : header;
30+
const footerPartial = footer.replace(
31+
/{{backToTop}}/g,
32+
customConfig.addBackToTop ? backToTop : '',
33+
);
34+
return {
35+
transform: transformer(customConfig),
36+
groupBy: 'type',
37+
commitGroupsSort: 'title',
38+
commitsSort: ['scope', 'subject'],
39+
noteGroupsSort: 'title',
40+
mainTemplate: reduceHeadingLevel(!customConfig.reduceHeadingLevel, mainTemplate),
41+
headerPartial: reduceHeadingLevel(!customConfig.reduceHeadingLevel, headerPartial),
42+
// 替换 commit.hbs 模板中的 gitUserInfo
43+
commitPartial: reduceHeadingLevel(!customConfig.reduceHeadingLevel, commitPartial),
44+
footerPartial: reduceHeadingLevel(!customConfig.reduceHeadingLevel, footerPartial),
45+
finalizeContext: finalizeContext(customConfig),
46+
};
47+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{{#if authorName}} - by [<img width="16" height="16" src="https://avatars.githubusercontent.com/{{authorNameEncode}}" /> **{{authorName}}**](https://github.com/{{authorNameEncode}}){{/if}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<div align="right">
2+
3+
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
4+
5+
</div>

0 commit comments

Comments
 (0)