@@ -2,10 +2,19 @@ import { getInput } from '@actions/core'
2
2
import { getExecOutput } from '@actions/exec'
3
3
import { getOctokit } from '@actions/github'
4
4
import type { Context } from '@actions/github/lib/context'
5
+ import type { ReleaseType } from './version'
6
+ import { getReleaseTypeFromCommitMessage } from './version'
7
+
8
+ type CommitsByReleaseType = Record < ReleaseType , { message : string ; url : string ; author : string } [ ] >
9
+ type CommitList = Awaited <
10
+ ReturnType <
11
+ ReturnType < typeof getOctokit > [ 'rest' ] [ 'repos' ] [ 'listCommits' ]
12
+ >
13
+ > [ 'data' ]
5
14
6
15
const run = async ( command : string ) => ( await getExecOutput ( command ) ) . stdout
7
16
8
- export const generateChangelog = async ( context : Context ) => {
17
+ const getLastCommits = async ( context : Context ) => {
9
18
const githubToken = getInput ( 'github-token' ) || process . env . GH_TOKEN
10
19
if ( githubToken === '' || githubToken === undefined )
11
20
throw new Error ( 'GitHub token is required' )
@@ -21,13 +30,73 @@ export const generateChangelog = async(context: Context) => {
21
30
repo : context . repo . repo ,
22
31
} )
23
32
24
- // Get all commits since last tag
25
33
const lastCommits = [ ]
26
34
for ( const commit of commits ) {
27
35
if ( commit . sha === lastTaggedCommitSha )
28
36
break
29
37
lastCommits . push ( commit )
30
38
}
31
39
32
- return lastCommits . map ( commit => `- ${ commit . commit . message } ` ) . join ( '\n' )
40
+ return lastCommits
41
+ }
42
+
43
+ const groupCommitsByReleaseType = ( commits : CommitList ) => {
44
+ return commits
45
+ . map ( ( commit ) => {
46
+ const { message, url, author } = commit . commit
47
+ const type = getReleaseTypeFromCommitMessage ( message )
48
+ return { message, type, url, author : String ( author ?. name ) }
49
+ } )
50
+ . reduce ( ( commitsByType , commit ) => {
51
+ if ( commit . type === null )
52
+ return commitsByType
53
+ if ( ! ( commit . type in commitsByType ) )
54
+ commitsByType [ commit . type ] = [ ]
55
+ commitsByType [ commit . type ] . push ( commit )
56
+ return commitsByType
57
+ } , { } as ( CommitsByReleaseType ) )
58
+ }
59
+
60
+ const formatCommitsByType = ( commitsByType : CommitsByReleaseType ) => {
61
+ let changelog = ''
62
+ const getCommitInfo = ( commit : { message : string ; url : string ; author : string } ) => {
63
+ const message = commit . message . split ( ':' ) [ 1 ] . trim ( )
64
+ const scope = commit . message . match ( / ^ ( .* ?) : / ) ?. [ 1 ] ?? ''
65
+ const commitSha = commit . url . split ( '/' ) . pop ( ) ?. slice ( 0 , 8 )
66
+ return { message, scope, commitSha }
67
+ }
68
+ if ( commitsByType . major ) {
69
+ changelog += `${ [
70
+ '## ⚠️ This release introduces breaking changes' ,
71
+ '### Features' ,
72
+ ] . join ( '\n' ) } \n`
73
+ }
74
+ if ( commitsByType . minor ) {
75
+ if ( ! commitsByType . major )
76
+ changelog += '### Features\n'
77
+
78
+ const featureCommits = [
79
+ ...( commitsByType . major || [ ] ) ,
80
+ ...( commitsByType . minor || [ ] ) ,
81
+ ]
82
+ for ( const commit of featureCommits ) {
83
+ const { message, scope, commitSha } = getCommitInfo ( commit )
84
+ changelog += `- **(${ scope } )** ${ message } ([${ commitSha } ](${ commit . url } ))\n`
85
+ }
86
+ }
87
+ if ( commitsByType . patch ) {
88
+ changelog += '### Bug Fixes\n'
89
+ for ( const commit of commitsByType . patch ) {
90
+ const { message, scope, commitSha } = getCommitInfo ( commit )
91
+ changelog += `- **(${ scope } )** ${ message } ([${ commitSha } ](${ commit . url } ))\n`
92
+ }
93
+ }
94
+
95
+ return changelog
96
+ }
97
+
98
+ export const generateChangelog = async ( context : Context ) => {
99
+ const lastCommits = await getLastCommits ( context )
100
+ const commitsByType = groupCommitsByReleaseType ( lastCommits )
101
+ return formatCommitsByType ( commitsByType )
33
102
}
0 commit comments