1 | 'use strict'
|
2 |
|
3 | const path = require('path')
|
4 | const fs = require('fs')
|
5 | const assert = require('assert')
|
6 |
|
7 |
|
8 | const hbsPartials = ['commit', 'header', 'main', 'notes'].reduce((acc, filename) => {
|
9 | acc[filename] = fs.readFileSync(
|
10 | path.resolve(__dirname, `../templates/${filename}.hbs`),
|
11 | { encoding: 'utf-8' },
|
12 | )
|
13 | return acc
|
14 | }, {})
|
15 |
|
16 |
|
17 |
|
18 |
|
19 | const formatRepoUrlBase = (host, owner, repository) =>
|
20 | `${host ? `${host}/` : ''}${owner ? `${owner}/` : ''}${repository}`
|
21 |
|
22 |
|
23 |
|
24 |
|
25 | const formatReleaseHeader = ({
|
26 | currentTag,
|
27 | date,
|
28 | host,
|
29 | isPatch,
|
30 | owner,
|
31 | previousTag,
|
32 | repository,
|
33 | title,
|
34 | version,
|
35 | }) => {
|
36 | const repoUrlBase = formatRepoUrlBase(host, owner, repository)
|
37 | const headerLevel = isPatch ? '###' : '##'
|
38 |
|
39 | let header = `${headerLevel} [${version}](${repoUrlBase}/compare/${previousTag}...${currentTag})`
|
40 | if (title) header += ` ${title}`
|
41 | if (date) header += ` (${date})`
|
42 | return header
|
43 | }
|
44 |
|
45 |
|
46 |
|
47 |
|
48 | const decorateReference = (ref, { host, owner, repository }) => {
|
49 |
|
50 |
|
51 |
|
52 | const referenceBase = `${ref.owner ? `${ref.owner}/` : ''}${ref.repository || ''}`
|
53 | let referenceRepoUrlBase
|
54 | if (ref.owner || ref.repository) {
|
55 | referenceRepoUrlBase = formatRepoUrlBase(host, ref.owner, ref.repository)
|
56 | } else {
|
57 | referenceRepoUrlBase = formatRepoUrlBase(host, owner, repository)
|
58 | }
|
59 |
|
60 | return {
|
61 | ...ref,
|
62 | completeReference: `${referenceBase}#${ref.issue}`,
|
63 | referenceUrl: `${referenceRepoUrlBase}/issue/${ref.issue}`,
|
64 | }
|
65 | }
|
66 |
|
67 |
|
68 |
|
69 |
|
70 | const decorateCommit = (commit, { host, owner, repository }) => {
|
71 | const { hash, message, references } = commit
|
72 |
|
73 |
|
74 | if (!message) console.error(`Commit is missing a message!`, commit)
|
75 |
|
76 | return {
|
77 | ...commit,
|
78 | commitUrl: `${formatRepoUrlBase(host, owner, repository)}/commit/${hash}`,
|
79 |
|
80 | shortHash: hash.slice(0, 7),
|
81 |
|
82 | message: message.slice(0, 1).toUpperCase() + message.slice(1),
|
83 | references: references
|
84 | ? references.map((ref) => decorateReference(ref, { host, owner, repository }))
|
85 | : null,
|
86 | }
|
87 | }
|
88 |
|
89 | const commitGroupOrder = [
|
90 | 'Breaking',
|
91 | 'New',
|
92 | 'Update',
|
93 | 'Fix',
|
94 | 'Chore',
|
95 | 'Docs',
|
96 | 'Upgrade',
|
97 | 'Build',
|
98 | ]
|
99 |
|
100 |
|
101 |
|
102 |
|
103 | const mergeCommitGroups = (commitGroups) => {
|
104 | const primaryGroupTitles = ['Breaking', 'New', 'Update', 'Fix']
|
105 | const decoratedCommitGroups = []
|
106 | const secondaryCommitGroup = { title: '', commits: [] }
|
107 |
|
108 |
|
109 | commitGroups.sort(
|
110 | (a, b) => commitGroupOrder.indexOf(a.title) - commitGroupOrder.indexOf(b.title),
|
111 | )
|
112 |
|
113 |
|
114 |
|
115 |
|
116 | commitGroups.forEach((commitGroup) => {
|
117 | if (primaryGroupTitles.indexOf(commitGroup.title) !== -1) {
|
118 | decoratedCommitGroups.push(commitGroup)
|
119 | } else {
|
120 | const { title, commits } = commitGroup
|
121 | secondaryCommitGroup.title += secondaryCommitGroup.title.length
|
122 | ? `, ${title}`
|
123 | : title
|
124 | secondaryCommitGroup.commits = secondaryCommitGroup.commits.concat(commits)
|
125 | }
|
126 | })
|
127 |
|
128 | decoratedCommitGroups.push(secondaryCommitGroup)
|
129 | return decoratedCommitGroups
|
130 | }
|
131 |
|
132 |
|
133 |
|
134 |
|
135 | const decorateCommitGroupTitle = (title) => {
|
136 | switch (title.toLowerCase()) {
|
137 | case 'breaking':
|
138 | return '💥 Breaking'
|
139 | case 'new':
|
140 | return '💖 New'
|
141 | case 'update':
|
142 | return '✨ Update'
|
143 | case 'fix':
|
144 | return '🛠Fix'
|
145 | default:
|
146 | return title
|
147 | }
|
148 | }
|
149 |
|
150 |
|
151 |
|
152 |
|
153 | const decorateNoteGroupTitle = (title) => {
|
154 | switch (title.toLowerCase()) {
|
155 | case 'breaking change':
|
156 | return '💥 Breaking Changes!'
|
157 | case 'release notes':
|
158 | return '🔖 Release Notes'
|
159 | default:
|
160 | return title
|
161 | }
|
162 | }
|
163 |
|
164 |
|
165 | const groupsOrder = ['Breaking','New','Update','Fix','Docs','Build','Upgrade','Chore']
|
166 |
|
167 |
|
168 |
|
169 |
|
170 | const writerOpts = {
|
171 |
|
172 | groupBy: 'tag',
|
173 |
|
174 | commitGroupsSort: (a, b) => groupsOrder.indexOf(a.title) > groupsOrder.indexOf(b.title),
|
175 |
|
176 | commitsSort: ['tag', 'message'],
|
177 |
|
178 |
|
179 | transform: (commit) => {
|
180 | if (!commit.tag || typeof commit.tag !== `string`) return false
|
181 | return commit
|
182 | },
|
183 |
|
184 |
|
185 | finalizeContext: (context /* , options, commits, keyCommit */) => {
|
186 | const { host, owner, repository } = context
|
187 |
|
188 | assert(Array.isArray(context.commitGroups), 'Commit groups must be an array')
|
189 | assert(Array.isArray(context.noteGroups), 'Note groups must be an array')
|
190 |
|
191 | const mergedCommitGroups = mergeCommitGroups(context.commitGroups)
|
192 |
|
193 | return {
|
194 | ...context,
|
195 | releaseHeader: formatReleaseHeader(context),
|
196 | commitGroups: mergedCommitGroups.map((commitGroup) => ({
|
197 | ...commitGroup,
|
198 | title: decorateCommitGroupTitle(commitGroup.title),
|
199 | commits: commitGroup.commits.map((commit) =>
|
200 | decorateCommit(commit, { host, owner, repository }),
|
201 | ),
|
202 | })),
|
203 | noteGroups: context.noteGroups.map((noteGroup) => ({
|
204 | ...noteGroup,
|
205 | title: decorateNoteGroupTitle(noteGroup.title),
|
206 | })),
|
207 | }
|
208 | },
|
209 |
|
210 | mainTemplate: hbsPartials.main,
|
211 | headerPartial: hbsPartials.header,
|
212 | commitPartial: hbsPartials.commit,
|
213 |
|
214 | partials: {
|
215 | notes: hbsPartials.notes,
|
216 | },
|
217 | }
|
218 |
|
219 |
|
220 |
|
221 | const parserOpts = { noteKeywords: ['BREAKING CHANGE', 'RELEASE NOTES'] }
|
222 |
|
223 | module.exports = { writerOpts, parserOpts }
|