8
8
import os
9
9
import re
10
10
import subprocess
11
- from datetime import datetime
12
11
from pathlib import Path
13
12
14
13
import click
15
- import toml
16
- import tomllib
17
14
18
15
# Import from changeset.py to reuse logic
19
16
from changeset .changeset import (
20
- determine_version_bump ,
21
17
bump_version ,
18
+ determine_version_bump ,
22
19
find_project_pyproject ,
23
- get_changesets as get_changesets_from_changeset ,
24
20
get_current_version ,
25
- parse_changeset ,
21
+ )
22
+ from changeset .changeset import (
23
+ get_changesets as get_changesets_from_changeset ,
26
24
)
27
25
28
26
CHANGESET_DIR = Path (".changeset" )
@@ -42,7 +40,7 @@ def load_config() -> dict:
42
40
def get_git_info () -> dict :
43
41
"""Get git information for the current commit/PR."""
44
42
info = {}
45
-
43
+
46
44
# Get the current commit hash
47
45
try :
48
46
result = subprocess .run (
@@ -52,9 +50,9 @@ def get_git_info() -> dict:
52
50
check = True
53
51
)
54
52
info ["commit" ] = result .stdout .strip ()[:7 ] # Short hash
55
- except :
53
+ except Exception :
56
54
info ["commit" ] = None
57
-
55
+
58
56
# Get GitHub repo info
59
57
try :
60
58
result = subprocess .run (
@@ -70,9 +68,9 @@ def get_git_info() -> dict:
70
68
info ["owner" ] = match .group (1 )
71
69
info ["repo" ] = match .group (2 )
72
70
info ["repo_url" ] = f"https://github.com/{ info ['owner' ]} /{ info ['repo' ]} "
73
- except :
71
+ except Exception :
74
72
pass
75
-
73
+
76
74
return info
77
75
78
76
@@ -83,17 +81,17 @@ def get_pr_metadata() -> dict:
83
81
"pr_author" : os .environ .get ("PR_AUTHOR" ),
84
82
"commit_hash" : os .environ .get ("COMMIT_SHA" , "" ),
85
83
}
86
-
84
+
87
85
# Always get git info for repo URL
88
86
git_info = get_git_info ()
89
-
87
+
90
88
# Use git commit if not in environment
91
89
if not metadata ["commit_hash" ]:
92
90
metadata ["commit_hash" ] = git_info .get ("commit" , "" )
93
-
91
+
94
92
# Always use repo URL from git
95
93
metadata ["repo_url" ] = git_info .get ("repo_url" , "" )
96
-
94
+
97
95
return metadata
98
96
99
97
@@ -108,25 +106,25 @@ def format_changelog_entry(
108
106
pr_author = pr_metadata .get ("pr_author" )
109
107
commit_hash = pr_metadata .get ("commit_hash" , "" )[:7 ]
110
108
repo_url = pr_metadata .get ("repo_url" , "" )
111
-
109
+
112
110
# Build the entry
113
111
parts = []
114
-
112
+
115
113
# Add PR link if available
116
114
if pr_number and repo_url :
117
115
parts .append (f"[#{ pr_number } ]({ repo_url } /pull/{ pr_number } )" )
118
-
116
+
119
117
# Add commit link if available
120
118
if commit_hash and repo_url :
121
119
parts .append (f"[`{ commit_hash } `]({ repo_url } /commit/{ commit_hash } )" )
122
-
120
+
123
121
# Add author thanks if available
124
122
if pr_author :
125
123
parts .append (f"Thanks @{ pr_author } !" )
126
-
124
+
127
125
# Add description
128
126
parts .append (f"- { description } " )
129
-
127
+
130
128
return " " .join (parts )
131
129
132
130
@@ -139,40 +137,40 @@ def generate_changelog_section(
139
137
) -> str :
140
138
"""Generate changelog section for a package version."""
141
139
lines = []
142
-
140
+
143
141
# Add version header
144
142
lines .append (f"## { new_version } " )
145
143
lines .append ("" )
146
-
144
+
147
145
# Group entries by change type
148
146
grouped = {}
149
147
for entry in entries :
150
148
change_type = entry ["type" ]
151
149
if change_type not in grouped :
152
150
grouped [change_type ] = []
153
151
grouped [change_type ].append (entry )
154
-
152
+
155
153
# Add sections for each change type
156
154
for change_type in ["major" , "minor" , "patch" ]:
157
155
if change_type not in grouped :
158
156
continue
159
-
157
+
160
158
# Get the change type label
161
159
type_label = {
162
160
"major" : "Major Changes" ,
163
161
"minor" : "Minor Changes" ,
164
162
"patch" : "Patch Changes"
165
163
}.get (change_type , f"{ change_type .capitalize ()} Changes" )
166
-
164
+
167
165
lines .append (f"### { type_label } " )
168
166
lines .append ("" )
169
-
167
+
170
168
# Add each entry
171
169
for entry in grouped [change_type ]:
172
170
lines .append (format_changelog_entry (entry , config , pr_metadata ))
173
-
171
+
174
172
lines .append ("" )
175
-
173
+
176
174
return "\n " .join (lines ).strip ()
177
175
178
176
@@ -187,11 +185,11 @@ def update_or_create_changelog(
187
185
else :
188
186
# Create new changelog with package name header
189
187
content = f"# { package_name } \n \n "
190
-
188
+
191
189
# Insert the new section after the package name header
192
190
lines = content .split ("\n " )
193
191
insert_index = None
194
-
192
+
195
193
# Find where to insert (after header, before first version)
196
194
for i , line in enumerate (lines ):
197
195
if line .startswith ("# " ):
@@ -203,7 +201,7 @@ def update_or_create_changelog(
203
201
if insert_index is None :
204
202
insert_index = i + 1
205
203
break
206
-
204
+
207
205
if insert_index is None :
208
206
# No header found, just prepend
209
207
new_content = new_section + "\n \n " + content
@@ -212,7 +210,7 @@ def update_or_create_changelog(
212
210
lines .insert (insert_index , new_section )
213
211
lines .insert (insert_index + 1 , "" )
214
212
new_content = "\n " .join (lines )
215
-
213
+
216
214
# Write the updated content
217
215
changelog_path .write_text (new_content )
218
216
return True
@@ -221,26 +219,26 @@ def update_or_create_changelog(
221
219
def generate_pr_description (package_updates : list [dict ]) -> str :
222
220
"""Generate a combined PR description for all package updates."""
223
221
lines = ["# Releases" , "" ]
224
-
222
+
225
223
for update in package_updates :
226
224
package = update ["package" ]
227
225
version = update ["version" ]
228
226
changelog_content = update ["changelog_content" ]
229
-
227
+
230
228
# Add package header
231
229
lines .append (f"## { package } @{ version } " )
232
230
lines .append ("" )
233
-
231
+
234
232
# Add the changelog content (without the package header)
235
233
# Skip the first line if it's a version header
236
234
changelog_lines = changelog_content .split ("\n " )
237
235
start_index = 0
238
236
if changelog_lines and changelog_lines [0 ].startswith ("## " ):
239
237
start_index = 1
240
-
238
+
241
239
lines .extend (changelog_lines [start_index :])
242
240
lines .append ("" )
243
-
241
+
244
242
return "\n " .join (lines )
245
243
246
244
@@ -251,16 +249,16 @@ def process_changesets_for_changelog() -> tuple[list[dict], str]:
251
249
"""
252
250
config = load_config ()
253
251
pr_metadata = get_pr_metadata ()
254
-
252
+
255
253
# Get all changesets
256
254
changesets = get_changesets_from_changeset ()
257
255
if not changesets :
258
256
return [], ""
259
-
257
+
260
258
# Group changesets by package
261
259
package_changes = {}
262
260
changeset_files = set ()
263
-
261
+
264
262
for filepath , package , change_type , desc in changesets :
265
263
changeset_files .add (filepath )
266
264
if package not in package_changes :
@@ -271,23 +269,23 @@ def process_changesets_for_changelog() -> tuple[list[dict], str]:
271
269
"description" : desc ,
272
270
"changeset" : filepath .name
273
271
})
274
-
272
+
275
273
# Process each package
276
274
package_updates = []
277
-
275
+
278
276
for package , info in package_changes .items ():
279
277
# Find pyproject.toml
280
278
try :
281
279
pyproject_path = find_project_pyproject (package )
282
280
except ValueError as e :
283
281
click .echo (click .style (f"⚠️ { e } " , fg = "yellow" ))
284
282
continue
285
-
283
+
286
284
# Determine new version
287
285
bump_type = determine_version_bump (info ["changes" ])
288
286
current_version = get_current_version (pyproject_path )
289
287
new_version = bump_version (current_version , bump_type )
290
-
288
+
291
289
# Generate changelog content
292
290
changelog_content = generate_changelog_section (
293
291
package ,
@@ -296,10 +294,10 @@ def process_changesets_for_changelog() -> tuple[list[dict], str]:
296
294
config ,
297
295
pr_metadata
298
296
)
299
-
297
+
300
298
# Find changelog path (same directory as pyproject.toml)
301
299
changelog_path = pyproject_path .parent / "CHANGELOG.md"
302
-
300
+
303
301
package_updates .append ({
304
302
"package" : package ,
305
303
"version" : new_version ,
@@ -308,68 +306,88 @@ def process_changesets_for_changelog() -> tuple[list[dict], str]:
308
306
"changelog_content" : changelog_content ,
309
307
"pyproject_path" : pyproject_path ,
310
308
})
311
-
309
+
312
310
# Generate PR description
313
311
pr_description = generate_pr_description (package_updates )
314
-
312
+
315
313
return package_updates , pr_description
316
314
317
315
318
316
@click .command ()
319
- @click .option ("--dry-run" , is_flag = True , help = "Show what would be done without making changes" )
317
+ @click .option (
318
+ "--dry-run" , is_flag = True , help = "Show what would be done without making changes"
319
+ )
320
320
@click .option ("--output-pr-description" , help = "File to write PR description to" )
321
321
def main (dry_run : bool , output_pr_description : str ):
322
322
"""Generate changelogs from changesets with version bumping."""
323
-
323
+
324
324
click .echo (click .style ("📜 Generating changelogs...\n " , fg = "cyan" , bold = True ))
325
-
325
+
326
326
# Process changesets
327
327
package_updates , pr_description = process_changesets_for_changelog ()
328
-
328
+
329
329
if not package_updates :
330
330
click .echo (click .style ("No changesets found. Nothing to do!" , fg = "yellow" ))
331
331
return
332
-
332
+
333
333
# Show what will be done
334
- click .echo (click .style (f"Found updates for { len (package_updates )} package(s):" , fg = "green" ))
334
+ click .echo (
335
+ click .style (f"Found updates for { len (package_updates )} package(s):" , fg = "green" )
336
+ )
335
337
for update in package_updates :
336
- click .echo (f" 📦 { update ['package' ]} : { update ['current_version' ]} → { update ['version' ]} " )
337
-
338
+ current = update ['current_version' ]
339
+ new = update ['version' ]
340
+ click .echo (f" 📦 { update ['package' ]} : { current } → { new } " )
341
+
338
342
if dry_run :
339
- click .echo (click .style ("\n 🔍 Dry run mode - no changes will be made" , fg = "yellow" ))
343
+ click .echo (
344
+ click .style ("\n 🔍 Dry run mode - no changes will be made" , fg = "yellow" )
345
+ )
340
346
click .echo ("\n " + "=" * 60 )
341
347
click .echo (click .style ("PR Description:" , fg = "cyan" ))
342
348
click .echo ("=" * 60 )
343
349
click .echo (pr_description )
344
350
click .echo ("=" * 60 )
345
-
351
+
346
352
for update in package_updates :
347
- click .echo (click .style (f"\n Changelog for { update ['changelog_path' ]} :" , fg = "cyan" ))
353
+ click .echo (
354
+ click .style (f"\n Changelog for { update ['changelog_path' ]} :" , fg = "cyan" )
355
+ )
348
356
click .echo ("-" * 60 )
349
357
click .echo (update ["changelog_content" ])
350
358
click .echo ("-" * 60 )
351
359
return
352
-
360
+
353
361
# Update changelog files
354
362
for update in package_updates :
355
363
success = update_or_create_changelog (
356
364
update ["changelog_path" ],
357
365
update ["package" ],
358
366
update ["changelog_content" ]
359
367
)
360
-
368
+
361
369
if success :
362
- click .echo (click .style (f"✅ Updated { update ['changelog_path' ]} " , fg = "green" ))
370
+ click .echo (
371
+ click .style (f"✅ Updated { update ['changelog_path' ]} " , fg = "green" )
372
+ )
363
373
else :
364
- click .echo (click .style (f"❌ Failed to update { update ['changelog_path' ]} " , fg = "red" ))
365
-
374
+ click .echo (
375
+ click .style (f"❌ Failed to update { update ['changelog_path' ]} " , fg = "red" )
376
+ )
377
+
366
378
# Write PR description if requested
367
379
if output_pr_description :
368
380
Path (output_pr_description ).write_text (pr_description )
369
- click .echo (click .style (f"✅ Wrote PR description to { output_pr_description } " , fg = "green" ))
370
-
371
- click .echo (click .style ("\n ✅ Changelog generation complete!" , fg = "green" , bold = True ))
381
+ click .echo (
382
+ click .style (
383
+ f"✅ Wrote PR description to { output_pr_description } " , fg = "green"
384
+ )
385
+ )
386
+
387
+ click .echo (
388
+ click .style ("\n ✅ Changelog generation complete!" , fg = "green" , bold = True )
389
+ )
372
390
373
391
374
392
if __name__ == "__main__" :
375
- main ()
393
+ main ()
0 commit comments