Skip to content

Commit 9a78df1

Browse files
committed
feat(publish): add contributors section to release notes
Tag community contributors with @username in GitHub releases, following opencode's publish.ts pattern. 🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
1 parent ab522af commit 9a78df1

File tree

1 file changed

+56
-12
lines changed

1 file changed

+56
-12
lines changed

script/publish.ts

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -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+ (ignore:|test:|chore:|ci:|release:)/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(/^(ignore:|test:|chore:|ci:|release:)/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

64109
async 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

Comments
 (0)