@@ -41,24 +41,69 @@ async function updatePackageVersion(newVersion: string): Promise<void> {
4141 console . log ( `Updated: ${ pkgPath } ` )
4242}
4343
44- async function generateChangelog ( previous : string ) : Promise < string > {
44+ async function generateChangelog ( previous : string ) : Promise < string [ ] > {
45+ const notes : string [ ] = [ ]
46+
4547 try {
4648 const log = await $ `git log v${ previous } ..HEAD --oneline --format="%h %s"` . text ( )
4749 const commits = log
4850 . split ( "\n" )
4951 . filter ( ( line ) => line && ! line . match ( / ^ \w + ( i g n o r e : | t e s t : | c h o r e : | c i : | r e l e a s e : ) / i) )
5052
5153 if ( commits . length > 0 ) {
52- const changelog = commits . map ( ( c ) => `- ${ c } ` ) . join ( "\n" )
54+ for ( const commit of commits ) {
55+ notes . push ( `- ${ commit } ` )
56+ }
5357 console . log ( "\n--- Changelog ---" )
54- console . log ( changelog )
58+ console . log ( notes . join ( "\n" ) )
5559 console . log ( "-----------------\n" )
56- return changelog
5760 }
5861 } catch {
5962 console . log ( "No previous tags found, skipping changelog generation" )
6063 }
61- return ""
64+
65+ return notes
66+ }
67+
68+ async function getContributors ( previous : string ) : Promise < string [ ] > {
69+ const notes : string [ ] = [ ]
70+
71+ const team = [ "actions-user" , "github-actions[bot]" , "code-yeongyu" ]
72+
73+ try {
74+ const compare =
75+ await $ `gh api "/repos/code-yeongyu/oh-my-opencode/compare/v${ previous } ...HEAD" --jq '.commits[] | {login: .author.login, message: .commit.message}'` . text ( )
76+ const contributors = new Map < string , string [ ] > ( )
77+
78+ for ( const line of compare . split ( "\n" ) . filter ( Boolean ) ) {
79+ const { login, message } = JSON . parse ( line ) as { login : string | null ; message : string }
80+ const title = message . split ( "\n" ) [ 0 ] ?? ""
81+ if ( title . match ( / ^ ( i g n o r e : | t e s t : | c h o r e : | c i : | r e l e a s e : ) / i) ) continue
82+
83+ if ( login && ! team . includes ( login ) ) {
84+ if ( ! contributors . has ( login ) ) contributors . set ( login , [ ] )
85+ contributors . get ( login ) ?. push ( title )
86+ }
87+ }
88+
89+ if ( contributors . size > 0 ) {
90+ notes . push ( "" )
91+ notes . push ( `**Thank you to ${ contributors . size } community contributor${ contributors . size > 1 ? "s" : "" } :**` )
92+ for ( const [ username , userCommits ] of contributors ) {
93+ notes . push ( `- @${ username } :` )
94+ for ( const commit of userCommits ) {
95+ notes . push ( ` - ${ commit } ` )
96+ }
97+ }
98+ console . log ( "\n--- Contributors ---" )
99+ console . log ( notes . join ( "\n" ) )
100+ console . log ( "--------------------\n" )
101+ }
102+ } catch ( error ) {
103+ console . log ( "Failed to fetch contributors:" , error )
104+ }
105+
106+ return notes
62107}
63108
64109async function buildAndPublish ( ) : Promise < void > {
@@ -71,36 +116,32 @@ async function buildAndPublish(): Promise<void> {
71116 }
72117}
73118
74- async function gitTagAndRelease ( newVersion : string , changelog : string ) : Promise < void > {
119+ async function gitTagAndRelease ( newVersion : string , notes : string [ ] ) : Promise < void > {
75120 if ( ! process . env . CI ) return
76121
77122 console . log ( "\nCommitting and tagging..." )
78123 await $ `git config user.email "github-actions[bot]@users.noreply.github.com"`
79124 await $ `git config user.name "github-actions[bot]"`
80125 await $ `git add package.json`
81126
82- // Commit only if there are staged changes (idempotent)
83127 const hasStagedChanges = await $ `git diff --cached --quiet` . nothrow ( )
84128 if ( hasStagedChanges . exitCode !== 0 ) {
85129 await $ `git commit -m "release: v${ newVersion } "`
86130 } else {
87131 console . log ( "No changes to commit (version already updated)" )
88132 }
89133
90- // Tag only if it doesn't exist (idempotent)
91134 const tagExists = await $ `git rev-parse v${ newVersion } ` . nothrow ( )
92135 if ( tagExists . exitCode !== 0 ) {
93136 await $ `git tag v${ newVersion } `
94137 } else {
95138 console . log ( `Tag v${ newVersion } already exists` )
96139 }
97140
98- // Push (idempotent - git push is already idempotent)
99141 await $ `git push origin HEAD --tags`
100142
101- // Create release only if it doesn't exist (idempotent)
102143 console . log ( "\nCreating GitHub release..." )
103- const releaseNotes = changelog || "No notable changes"
144+ const releaseNotes = notes . length > 0 ? notes . join ( "\n" ) : "No notable changes"
104145 const releaseExists = await $ `gh release view v${ newVersion } ` . nothrow ( )
105146 if ( releaseExists . exitCode !== 0 ) {
106147 await $ `gh release create v${ newVersion } --title "v${ newVersion } " --notes ${ releaseNotes } `
@@ -130,8 +171,11 @@ async function main() {
130171
131172 await updatePackageVersion ( newVersion )
132173 const changelog = await generateChangelog ( previous )
174+ const contributors = await getContributors ( previous )
175+ const notes = [ ...changelog , ...contributors ]
176+
133177 await buildAndPublish ( )
134- await gitTagAndRelease ( newVersion , changelog )
178+ await gitTagAndRelease ( newVersion , notes )
135179
136180 console . log ( `\n=== Successfully published ${ PACKAGE_NAME } @${ newVersion } ===` )
137181}
0 commit comments