conventional-changelog-techor
Version:
Beautiful changelog based on Techor's conventional commits
271 lines (255 loc) • 7.92 kB
JavaScript
;
var compareFunc = require('compare-func');
var techorConventionalCommits = require('techor-conventional-commits');
var https = require('node:https');
var parserOpts = {
headerPattern: /^([A-Z]\w*)(?:\(([0-9A-Z`_#~].*)\))?: ([0-9A-Z`_#~].*)$/,
headerCorrespondence: [
'type',
'scope',
'subject'
],
revertPattern: /^(?:Revert|Revert:)\s"?([\s\S]+?)"?\s*This reverts commit (\w*)\./i,
revertCorrespondence: [
'header',
'hash'
]
};
var mainTemplate = `{{#each commitGroups}}
{{#if title}}
### {{title}}
{{/if}}
{{#each scopes}}
{{#if title}}
###### {{title}}
{{/if}}
{{#each commits}}
{{> commit root=@root}}
{{/each}}
{{/each}}
{{/each}}
{{> footer}}`;
var footerPartial = `{{#if noteGroups}}
{{#each noteGroups}}
### {{title}}
{{#each notes}}
* {{#if commit.scope}}**{{commit.scope}}:** {{/if}}{{text}}
{{/each}}
{{/each}}
{{/if}}`;
var commitPartial = `- {{#if subject}}
{{~subject}}
{{~else}}
{{~header}}
{{~/if}}
{{~!-- commit link --}} {{#if @root.linkReferences~}}
<sub><sup>@{{author.login}} [{{shortHash}}](
{{~#if @root.repository}}
{{~#if @root.host}}
{{~@root.host}}/
{{~/if}}
{{~#if @root.owner}}
{{~@root.owner}}/
{{~/if}}
{{~@root.repository}}
{{~else}}
{{~@root.repoUrl}}
{{~/if}}/
{{~@root.commit}}/{{hash}})</sup></sub>
{{~else}}
<sub><sup>{{~shortHash}}</sup></sub>
{{~/if}}
{{~!-- commit references --}}
{{~#if references~}}
<sub><sup>{{~#each references}} {{#if @root.linkReferences~}}
[
{{~#if this.owner}}
{{~this.owner}}/
{{~/if}}
{{~this.repository}}#{{this.issue}}](
{{~#if @root.repository}}
{{~#if @root.host}}
{{~@root.host}}/
{{~/if}}
{{~#if this.repository}}
{{~#if this.owner}}
{{~this.owner}}/
{{~/if}}
{{~this.repository}}
{{~else}}
{{~#if @root.owner}}
{{~@root.owner}}/
{{~/if}}
{{~@root.repository}}
{{~/if}}
{{~else}}
{{~@root.repoUrl}}
{{~/if}}/
{{~@root.issue}}/{{this.issue}})
{{~else}}
{{~#if this.owner}}
{{~this.owner}}/
{{~/if}}
{{~this.repository}}#{{this.issue}}
{{~/if}}{{/each}}</sup></sub>
{{~/if}}
`;
var writerOpts = {
transform: async (commit, context)=>{
const issues = [];
if (commit.header) {
commit.header = commit.header.replace(/->/g, '→').replace(/<-/g, '←');
}
const conventionalCommit = techorConventionalCommits.commits.find(({ type })=>commit.type === type);
if (commit.type === 'Revert' || commit.revert) {
commit.type = techorConventionalCommits.commits.find(({ type })=>type === 'Revert').group;
/**
* From Revert: "Feat(Scope): First feature"
* To Revert:《 Feat(Scope): First feature 》
*/ commit.header = commit.header.replace(/(Revert|Revert:)\s"([\s\S]+?)"(.*)/, '$1 ``` $2 `text` ``` $3');
} else if (conventionalCommit && !conventionalCommit.hidden && conventionalCommit.group) {
commit.type = conventionalCommit.group;
} else {
return;
}
if (commit.scope === '*') {
commit.scope = '';
}
if (typeof commit.hash === 'string') {
commit.shortHash = commit.hash.substring(0, 7);
}
if (typeof commit.subject === 'string') {
if (process.env.NODE_ENV !== 'test') {
// get author by commit hash
try {
const response = await new Promise((resolve)=>{
const url = `https://api.github.com/repos/${context.owner}/${context.repository}/commits/${commit.hash}`;
const headers = {
'User-Agent': context.owner
};
if (process.env.GITHUB_TOKEN) {
headers['Authorization'] = `token ${process.env.GITHUB_TOKEN}`;
}
https.get(url, {
headers
}, (response)=>{
let data = '';
response.on('data', (chunk)=>data += chunk);
response.on('end', async ()=>{
resolve(data);
});
});
});
commit.author = JSON.parse(response).author;
} catch (error) {
console.log(new Error(`Can't get author by commit hash ${commit.hash}`, {
cause: error
}));
}
}
}
// remove references that already appear in the subject
commit.references = commit.references.filter((reference)=>{
if (issues.indexOf(reference.issue) === -1) {
return true;
}
return false;
});
return commit;
},
groupBy: 'type',
commitGroupsSort: (a, b)=>{
const commitGroupOrder = techorConventionalCommits.commits.map(({ group })=>group).reverse();
const gRankA = commitGroupOrder.indexOf(a.title);
const gRankB = commitGroupOrder.indexOf(b.title);
if (gRankA >= gRankB) {
return -1;
} else {
return 1;
}
},
commitsSort: [
'scope',
'subject'
],
noteGroupsSort: 'title',
notesSort: compareFunc,
mainTemplate,
commitPartial,
headerPartial: '',
footerPartial,
finalizeContext: (context, options, commits)=>{
context.commitGroups.forEach((commitGroup)=>{
const scopes = [];
commitGroup.commits.forEach((commit)=>{
const { scope } = commit;
if (!scopes.includes(scope)) {
scopes.push(scope);
}
});
commitGroup.scopes = scopes.map((eachScope)=>{
return {
title: eachScope || '',
commits: commitGroup.commits.filter((commit)=>commit.scope === eachScope)
};
}).sort((a, b)=>{
if (a.title === '') {
return -1 // 将 title 为 '' 的项排在前面
;
}
if (b.title === '') {
return 1 // 将 title 为 '' 的项排在前面
;
}
return a.title.localeCompare(b.title) // 按照 name 的默认排序顺序排列
;
});
});
return context;
}
};
var conventionalChangelog = {
parserOpts,
writerOpts
};
var recommendedBumpOpts = {
parserOpts,
whatBump: (commits)=>{
let level = null;
let majorCount = 0;
let MinorCount = 0;
let patchCount = 0;
commits.forEach(({ type })=>{
const conventionalCommit = techorConventionalCommits.commits.find(({ type: eachType })=>eachType === type);
if (conventionalCommit) {
switch(conventionalCommit.release){
case 'major':
level = 0;
majorCount++;
break;
case 'minor':
level = 1;
MinorCount++;
break;
case 'patch':
level = 2;
patchCount++;
break;
}
}
});
return {
level: level,
reason: `Major: ${majorCount}, Minor: ${MinorCount}, Patch: ${patchCount}`
};
}
};
async function createPreset() {
return {
conventionalChangelog,
parserOpts,
recommendedBumpOpts,
writerOpts
};
}
module.exports = createPreset;