11#! /usr/bin/env bash
22#  Script to generate workspace-grouped changelog for rust agents
3+ # 
4+ #  Usage:
5+ #    generate-workspace-changelog.sh [COMMIT_RANGE] [WORKSPACE_FILTER] [FLAGS]
6+ # 
7+ #  Arguments:
8+ #    COMMIT_RANGE      - Git commit range (e.g., "v1.4.0..HEAD"). Defaults to latest tag..HEAD
9+ #    WORKSPACE_FILTER  - Optional comma-separated list of workspaces to include (e.g., "agents/relayer,agents/validator")
10+ #                        If omitted, all workspaces are included
11+ # 
12+ #  Flags:
13+ #    --no-header                      - Omit the "## What's Changed" header (useful for composing changelogs)
14+ #    --write-to-workspace VERSION - Update CHANGELOG.md files in each workspace directory
15+ # 
16+ #  Examples:
17+ #    ./generate-workspace-changelog.sh                           # All workspaces, latest tag..HEAD
18+ #    ./generate-workspace-changelog.sh "v1.4.0..v1.5.0"          # All workspaces, specific range
19+ #    ./generate-workspace-changelog.sh "v1.4.0..v1.5.0" "agents/relayer"   # Single workspace
20+ #    ./generate-workspace-changelog.sh "v1.4.0..v1.5.0" "agents/relayer,agents/validator"   # Multiple workspaces
21+ #    ./generate-workspace-changelog.sh "v1.4.0..v1.5.0" "agents/relayer" --no-header  # No header
22+ #    ./generate-workspace-changelog.sh "v1.4.0..v1.5.0" "" --write-to-workspace "1.5.0"  # Update workspace CHANGELOGs
23+ # 
324set  -euo pipefail
425
526#  Determine script directory and repo structure
627SCRIPT_DIR=" $( cd " $( dirname " ${BASH_SOURCE[0]} " ) "   &&  pwd) " 
728RUST_MAIN_DIR=" $SCRIPT_DIR /../main" 
829
30+ #  Parse arguments
31+ COMMIT_RANGE=" " 
32+ WORKSPACE_FILTER=" " 
33+ SHOW_HEADER=true
34+ UPDATE_CHANGELOGS=false
35+ VERSION=" " 
36+ 
37+ #  Parse all arguments
38+ i=1
39+ while  [ $i  -le  $#  ];  do 
40+     arg=" ${! i} " 
41+ 
42+     if  [ " $arg "   =  " --no-header"   ];  then 
43+         SHOW_HEADER=false
44+     elif  [ " $arg "   =  " --write-to-workspace"   ];  then 
45+         UPDATE_CHANGELOGS=true
46+         #  Get next argument as version
47+         i=$(( i +  1 )) 
48+         if  [ $i  -le  $#  ];  then 
49+             VERSION=" ${! i} " 
50+         else 
51+             echo  " Error: --write-to-workspace requires a VERSION argument"   >&2 
52+             exit  1
53+         fi 
54+     elif  [ -z  " $COMMIT_RANGE "   ];  then 
55+         COMMIT_RANGE=" $arg " 
56+     elif  [ -z  " $WORKSPACE_FILTER "   ];  then 
57+         WORKSPACE_FILTER=" $arg " 
58+     fi 
59+ 
60+     i=$(( i +  1 )) 
61+ done 
62+ 
963#  Get the commit range
10- if  [ $#   -eq  0  ];  then 
11-     #  No arguments  - use unreleased commits
64+ if  [ -z   " $COMMIT_RANGE "   ];  then 
65+     #  No commit range specified  - use unreleased commits
1266    LATEST_TAG=$( git tag -l " agents-v*"   --sort=-version:refname |  grep -E " ^agents-v[0-9]+\.[0-9]+\.[0-9]+$"   |  head -1 ||  echo  " " ) 
1367    if  [ -z  " $LATEST_TAG "   ];  then 
1468        COMMIT_RANGE=" HEAD" 
1569    else 
1670        COMMIT_RANGE=" ${LATEST_TAG} ..HEAD" 
1771    fi 
72+ fi 
73+ 
74+ #  Parse workspace filter into array
75+ if  [ -n  " $WORKSPACE_FILTER "   ];  then 
76+     IFS=' ,'   read  -ra FILTER_ARRAY <<<  " $WORKSPACE_FILTER" 
1877else 
19-     COMMIT_RANGE= " $1 " 
78+     FILTER_ARRAY=() 
2079fi 
2180
2281#  Temporary directory for categorization
@@ -26,6 +85,25 @@ trap "rm -rf $TEMP_DIR" EXIT
2685#  Extract workspace members from rust/main/Cargo.toml
2786WORKSPACE_MEMBERS=$( grep -A 100 ' ^\[workspace\]'   " $RUST_MAIN_DIR /Cargo.toml"   |  sed -n ' /^members = \[/,/^\]/p'   |  grep ' "'   |  sed ' s/[", ]//g' ) 
2887
88+ #  Helper function to check if a workspace should be included
89+ should_include_workspace () {
90+     local  workspace=" $1 " 
91+ 
92+     #  If no filter specified, include all
93+     if  [ ${# FILTER_ARRAY[@]}  -eq  0 ];  then 
94+         return  0
95+     fi 
96+ 
97+     #  Check if workspace is in filter list
98+     for  filter  in  " ${FILTER_ARRAY[@]} " ;  do 
99+         if  [ " $workspace "   =  " $filter "   ];  then 
100+             return  0
101+         fi 
102+     done 
103+ 
104+     return  1
105+ }
106+ 
29107#  Get all commits in the range (filter to rust/main directory)
30108git log --no-merges --format=" %H"   $COMMIT_RANGE  -- rust/main |  while  read  -r commit_hash;  do 
31109    #  Get commit message
@@ -50,9 +128,9 @@ git log --no-merges --format="%H" $COMMIT_RANGE -- rust/main | while read -r com
50128        done 
51129    done 
52130
53-     #  Default to "Other " if no workspace found
131+     #  Default to "other " if no workspace found
54132    if  [ -z  " $workspace "   ];  then 
55-         workspace=" Other " 
133+         workspace=" other " 
56134    fi 
57135
58136    #  Sanitize workspace name for file system (replace / with __)
@@ -62,20 +140,110 @@ git log --no-merges --format="%H" $COMMIT_RANGE -- rust/main | while read -r com
62140    echo  " $workspace |$commit_msg |$short_hash "   >>  " $TEMP_DIR /$workspace_file " 
63141done 
64142
65- #  Process workspace members in the order they appear in Cargo.toml, then "Other"
66- for  workspace  in  $WORKSPACE_MEMBERS  " Other" ;  do 
67-     workspace_file=$( echo " $workspace "   |  tr ' /'   ' _' ) 
143+ #  Function to generate changelog for a specific workspace
144+ generate_workspace_changelog () {
145+     local  workspace=" $1 " 
146+     local  workspace_file=$( echo " $workspace "   |  tr ' /'   ' _' ) 
68147
69148    if  [ -f  " $TEMP_DIR /$workspace_file "   ];  then 
70149        echo  " ### $workspace " 
71150        echo  " " 
72- 
73-         #  Sort and deduplicate commits (extract workspace name, msg, hash)
74151        sort -u " $TEMP_DIR /$workspace_file "   |  while  IFS=' |'   read  -r ws msg hash ;  do 
75152            echo  " * $msg  (#$hash )" 
76153        done 
77-         echo  " " 
154+     fi 
155+ }
156+ 
157+ #  If updating workspace changelogs, update files and exit
158+ if  [ " $UPDATE_CHANGELOGS "   =  true  ];  then 
159+     if  [ -z  " $VERSION "   ];  then 
160+         echo  " Error: VERSION is required for --write-to-workspace"   >&2 
161+         exit  1
162+     fi 
163+ 
164+     echo  " Updating workspace CHANGELOG.md files for version $VERSION ..." 
165+     UPDATED_COUNT=0
166+ 
167+     for  workspace  in  $WORKSPACE_MEMBERS ;  do 
168+         #  Skip if workspace is filtered out
169+         if  !  should_include_workspace " $workspace " ;  then 
170+             continue 
171+         fi 
172+ 
173+         workspace_file=$( echo " $workspace "   |  tr ' /'   ' _' ) 
174+ 
175+         #  Skip if no changes for this workspace
176+         if  [ !  -f  " $TEMP_DIR /$workspace_file "   ];  then 
177+             continue 
178+         fi 
179+ 
180+         #  Generate changelog content for this workspace (no header)
181+         WORKSPACE_CHANGELOG=$( generate_workspace_changelog " $workspace " ) 
182+ 
183+         if  [ -z  " $WORKSPACE_CHANGELOG "   ];  then 
184+             continue 
185+         fi 
186+ 
187+         WORKSPACE_CHANGELOG_FILE=" $RUST_MAIN_DIR /$workspace /CHANGELOG.md" 
188+         WORKSPACE_DIR=$( dirname " $WORKSPACE_CHANGELOG_FILE " ) 
189+ 
190+         #  Ensure directory exists
191+         mkdir -p " $WORKSPACE_DIR " 
192+ 
193+         #  Read existing changelog if it exists
194+         if  [ -f  " $WORKSPACE_CHANGELOG_FILE "   ];  then 
195+             CURRENT_WORKSPACE_CHANGELOG=$( cat " $WORKSPACE_CHANGELOG_FILE " ) 
196+         else 
197+             CURRENT_WORKSPACE_CHANGELOG=" " 
198+         fi 
199+ 
200+         #  Prepend new version to workspace changelog
201+         cat >  " $WORKSPACE_CHANGELOG_FILE "   << EOF 
202+ # Changelog 
203+ 
204+ ## [$VERSION ] - $( date +%Y-%m-%d)  
205+ 
206+ $WORKSPACE_CHANGELOG 
207+ 
208+ $CURRENT_WORKSPACE_CHANGELOG 
209+ EOF 
210+ 
211+         echo  " Updated $workspace /CHANGELOG.md" 
212+         UPDATED_COUNT=$(( UPDATED_COUNT +  1 )) 
213+     done 
214+ 
215+     echo  " Updated $UPDATED_COUNT  workspace changelog(s)" 
216+     exit  0
217+ fi 
218+ 
219+ #  Generate output (for display/PR body)
220+ if  [ " $SHOW_HEADER "   =  true  ];  then 
221+     echo  " ## What's Changed" 
222+     echo  " " 
223+ fi 
224+ 
225+ #  Process workspace members in the order they appear in Cargo.toml, then "other"
226+ FIRST_WORKSPACE=true
227+ for  workspace  in  $WORKSPACE_MEMBERS  " other" ;  do 
228+     #  Skip if workspace is filtered out
229+     if  !  should_include_workspace " $workspace " ;  then 
230+         continue 
231+     fi 
232+ 
233+     workspace_file=$( echo " $workspace "   |  tr ' /'   ' _' ) 
234+ 
235+     if  [ -f  " $TEMP_DIR /$workspace_file "   ];  then 
236+         #  Add separator between workspaces (except before first one)
237+         if  [ " $FIRST_WORKSPACE "   =  false  ];  then 
238+             echo  " " 
239+         fi 
240+         FIRST_WORKSPACE=false
241+ 
242+         generate_workspace_changelog " $workspace " 
78243    fi 
79244done 
80245
81- echo  " <!-- generated by workspace changelog script -->" 
246+ if  [ " $SHOW_HEADER "   =  true  ];  then 
247+     echo  " " 
248+     echo  " <!-- generated by workspace changelog script -->" 
249+ fi 
0 commit comments