Skip to content

Commit 5861b4b

Browse files
committed
feat(create-changelog): support commit tags
Besides footers (`[footer]: [msg]`), this commit includes support for tags (`#<tag-str>`) in commit messages. If tags match common commit types, such as `feat`, they are also used to set the type when absent.
1 parent 7fbfe2f commit 5861b4b

File tree

5 files changed

+119
-108
lines changed

5 files changed

+119
-108
lines changed

create-changelog/CHANGELOG.md

Lines changed: 0 additions & 68 deletions
This file was deleted.

create-changelog/create-changelog.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,23 @@ const {
22
main,
33
set_trace_commands,
44
toIntegerInput,
5-
normalizePath,
65
run
76
} = require('./index')
7+
const trace_commands = require('trace-commands')
8+
9+
function normalizePath(path) {
10+
let p = path
11+
if (p.startsWith('~/') || p.startsWith('~\\')) {
12+
const isWindows = process.platform === 'win32'
13+
if (isWindows) {
14+
p = p.replace('~', process.env.HOME || process.env.USERPROFILE)
15+
} else {
16+
p = p.replace('~', process.env.HOME)
17+
}
18+
}
19+
p = p.replace(/\\/g, '/')
20+
return p
21+
}
822

923
function parseArgs() {
1024
const args = process.argv.slice(2)
@@ -83,7 +97,7 @@ function parseArgs() {
8397

8498
async function runLocal() {
8599
const inputs = parseArgs()
86-
set_trace_commands(true)
100+
trace_commands.set_trace_commands(true)
87101
try {
88102
await main(inputs)
89103
} catch (error) {

create-changelog/dist/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

create-changelog/dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

create-changelog/index.js

Lines changed: 101 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,17 @@ class Commit {
2727
this.footers = {}
2828
this.breaking = false
2929

30+
// Extensions to conventional fields (#<tag-expr>)
31+
this.tags = []
32+
3033
// whether the commit is conventional or not
3134
this.conventional = true
3235

3336
// issue info
3437
this.issue = null
3538
this.gh_issue_username = null
3639

37-
// delimiter
40+
// delimiter (git tag or version pattern)
3841
this.tag = null
3942
this.is_parent_release = false
4043
}
@@ -168,9 +171,9 @@ async function adjustParameters(inputs) {
168171
console.log(`Repository Branch ${inputs.repo_branch} from local path`)
169172
}
170173
}
171-
if (!inputs.access_token) {
172-
inputs.access_token = process.env['GITHUB_TOKEN']
173-
if (inputs.access_token) {
174+
if (!inputs.github_token) {
175+
inputs.github_token = process.env['GITHUB_TOKEN']
176+
if (inputs.github_token) {
174177
console.log(`Access token **** from GITHUB_TOKEN`)
175178
}
176179
}
@@ -357,6 +360,44 @@ async function getIssueAuthor(repoUrl, issueNumber, accessToken) {
357360
return null
358361
}
359362

363+
function isValidType(s) {
364+
// A valid type according to `normalizeType`
365+
// Used to identify tags that can be converted to types
366+
const recognizedTypes = [
367+
'doc',
368+
'docs',
369+
'documentation',
370+
'fix',
371+
'fixes',
372+
'bugfix',
373+
'chore',
374+
'work',
375+
'chores',
376+
'maintenance',
377+
'feat',
378+
'feature',
379+
'refactor',
380+
'cleanup',
381+
'perf',
382+
'performance',
383+
'test',
384+
'testing',
385+
'tests',
386+
'release',
387+
'version',
388+
'ci',
389+
'integration',
390+
'breaking',
391+
'break',
392+
'revert',
393+
'undo',
394+
'style',
395+
'build',
396+
'improvement'
397+
]
398+
return recognizedTypes.includes(s)
399+
}
400+
360401
function normalizeType(s) {
361402
if (!s) {
362403
return 'other'
@@ -407,31 +448,47 @@ async function populateConventional(commit, repoUrl, versionPattern, tags) {
407448
commit.breaking = commit.subject.includes('BREAKING')
408449
commit.conventional = false
409450
}
410-
} else {
411-
// Is body or footer
412-
const m = line.match(/(([^ ]+): )|(([^ ]+) #)|((BREAKING CHANGE): )/)
413-
if (m) {
414-
// is a footer
415-
const footerKey = m[1] ? m[2] : m[3] ? m[4] : m[5] ? m[6] : null
416-
if (footerKey) {
417-
const offset = m[1] || m[5] ? 2 : 1
418-
commit.footers[footerKey] = line.slice(footerKey.length + offset).trim()
419-
if (footerKey.toLowerCase().startsWith('breaking')) {
420-
commit.breaking = true
421-
}
422-
}
423-
} else if (['breaking', 'breaking-change', 'breaking change'].includes(line.toLowerCase())) {
424-
// footer with no key and value
425-
// -> the whole message is breaking change footer
426-
commit.breaking = true
427-
} else {
428-
// this is a line from the body
429-
if (!commit.body) {
430-
commit.body += line
431-
} else {
432-
commit.body += '\n' + line
451+
continue
452+
}
453+
454+
// Subject populated: parse as body, footer, or tag
455+
const footerRegex = /(([^ ]+): )|(([^ ]+) #)|((BREAKING CHANGE): )/
456+
const m = line.match(footerRegex)
457+
if (m) {
458+
// is a footer
459+
const footerKey = m[1] ? m[2] : m[3] ? m[4] : m[5] ? m[6] : null
460+
if (footerKey) {
461+
const offset = m[1] || m[5] ? 2 : 1
462+
commit.footers[footerKey] = line.slice(footerKey.length + offset).trim()
463+
if (footerKey.toLowerCase().startsWith('breaking')) {
464+
commit.breaking = true
433465
}
434466
}
467+
continue
468+
}
469+
470+
if (['breaking', 'breaking-change', 'breaking change'].includes(line.toLowerCase())) {
471+
// footer with no key and value
472+
// -> the whole message is breaking change footer
473+
commit.breaking = true
474+
continue
475+
}
476+
477+
// #<tag-expr>
478+
// The commit can contain a tag, which is just a string identifier
479+
// for whatever purpose the user wants to use it for.
480+
const tagRegex = /#(\S+)/
481+
const tagMatch = line.match(tagRegex)
482+
if (tagMatch) {
483+
commit.tags.push(tagMatch[1])
484+
continue
485+
}
486+
487+
// No special syntax: this is a line from the body
488+
if (!commit.body) {
489+
commit.body += line
490+
} else {
491+
commit.body += '\n' + line
435492
}
436493
}
437494

@@ -453,6 +510,16 @@ async function populateConventional(commit, repoUrl, versionPattern, tags) {
453510
}
454511
}
455512

513+
// Attribute type from one of the tags if possible
514+
if (!commit.type || commit.type === 'other') {
515+
for (const tag of commit.tags) {
516+
if (isValidType(tag)) {
517+
commit.type = normalizeType(tag)
518+
break
519+
}
520+
}
521+
}
522+
456523
commit.is_parent_release = false
457524
if (commit.tag !== null) {
458525
console.log(`Stopping at commit id ${commit.hash.slice(0, 8)} (tag ${commit.tag})`)
@@ -489,7 +556,6 @@ async function getLocalCommits(projectPath, repoUrl, versionPattern, tags) {
489556

490557
const commitLogLines = commitLogOutput.split('\n')
491558
let commit = new Commit()
492-
let msg = ''
493559

494560
for (const line of commitLogLines) {
495561
if (line === '') continue
@@ -1078,8 +1144,6 @@ function generateOutput(changes, changeTypePriority, args, repoUrl, authors, par
10781144

10791145
for (const changeType of changeTypePriority) {
10801146
if (changes.hasOwnProperty(changeType)) {
1081-
const scopeChanges = changes[changeType] || {}
1082-
10831147
// Title
10841148
if (Object.keys(changes).length > 1 || (Object.keys(changes).length > 0 && changeType !== 'other')) {
10851149
if (output) {
@@ -1093,7 +1157,8 @@ function generateOutput(changes, changeTypePriority, args, repoUrl, authors, par
10931157
}
10941158

10951159
// Scopes
1096-
for (const [scope, scopedChanges] of Object.entries(scopeChanges)) {
1160+
const typeChanges = changes[changeType] || {}
1161+
for (const [scope, scopedChanges] of Object.entries(typeChanges)) {
10971162
const indentedScope = (scope !== null && scope !== 'null' && scope !== 'undefined') && scopedChanges.length > 1
10981163
if (indentedScope) {
10991164
output += `- ${scope}:\n`
@@ -1112,7 +1177,7 @@ function generateOutput(changes, changeTypePriority, args, repoUrl, authors, par
11121177
}
11131178

11141179
// Scope prefix
1115-
if (scope !== null && !indentedScope) {
1180+
if (indentedScope) {
11161181
output += `${scope}: `
11171182
}
11181183

@@ -1218,7 +1283,7 @@ async function main(inputs) {
12181283
core.endGroup()
12191284

12201285
core.startGroup('🏷️ Identifying tags')
1221-
let tags = await processTags(inputs.source_dir, inputs.tag_pattern, inputs.repoUrl, inputs.access_token)
1286+
let tags = await processTags(inputs.source_dir, inputs.tag_pattern, inputs.repoUrl, inputs.github_token)
12221287
core.endGroup()
12231288

12241289
core.startGroup('📜 Identifying commits')
@@ -1228,7 +1293,7 @@ async function main(inputs) {
12281293
inputs.version_pattern,
12291294
tags,
12301295
inputs.repo_branch,
1231-
inputs.access_token,
1296+
inputs.github_token,
12321297
inputs.check_unconventional)
12331298

12341299
// Limit the number of commits
@@ -1239,12 +1304,12 @@ async function main(inputs) {
12391304
core.endGroup()
12401305

12411306
core.startGroup('👤 Populating GitHub usernames')
1242-
await populateGithubUsernames(commits, inputs.access_token)
1307+
await populateGithubUsernames(commits, inputs.github_token)
12431308
core.endGroup()
12441309

12451310
// Populate issue data
12461311
core.startGroup('🔗 Populating issue data')
1247-
const authors = await populateIssueData(commits, inputs.repoUrl, inputs.repoOwner, inputs.access_token)
1312+
const authors = await populateIssueData(commits, inputs.repoUrl, inputs.repoOwner, inputs.github_token)
12481313
core.endGroup()
12491314

12501315
// Identify non-regular contributors

0 commit comments

Comments
 (0)