File size: 10,951 Bytes
2e1ab99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
#!/usr/bin/env bash

# Ensure we're running in bash
if [ -z "$BASH_VERSION" ]; then
    echo "This script requires bash. Please run with: bash $0" >&2
    exit 1
fi

# Ensure we're using bash 4.0 or later for associative arrays
if ((BASH_VERSINFO[0] < 4)); then
    echo "This script requires bash version 4 or later" >&2
    echo "Current bash version: $BASH_VERSION" >&2
    exit 1
fi

# Set default values for required environment variables if not in GitHub Actions
if [ -z "$GITHUB_ACTIONS" ]; then
    : "${GITHUB_SERVER_URL:=https://github.com}"
    : "${GITHUB_REPOSITORY:=stackblitz-labs/bolt.diy}"
    : "${GITHUB_OUTPUT:=/tmp/github_output}"
    touch "$GITHUB_OUTPUT"

    # Running locally
    echo "Running locally - checking for upstream remote..."
    MAIN_REMOTE="origin"
    if git remote -v | grep -q "upstream"; then
        MAIN_REMOTE="upstream"
    fi
    MAIN_BRANCH="main"  # or "master" depending on your repository
    
    # Ensure we have latest tags
    git fetch ${MAIN_REMOTE} --tags
    
    # Use the remote reference for git log
    GITLOG_REF="${MAIN_REMOTE}/${MAIN_BRANCH}"
else
    # Running in GitHub Actions
    GITLOG_REF="HEAD"
fi

# Get the latest tag
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")

# Start changelog file
echo "# πŸš€ Release v${NEW_VERSION}" > changelog.md
echo "" >> changelog.md
echo "## What's Changed 🌟" >> changelog.md
echo "" >> changelog.md

if [ -z "$LATEST_TAG" ]; then
    echo "### πŸŽ‰ First Release" >> changelog.md
    echo "" >> changelog.md
    echo "Exciting times! This marks our first release. Thanks to everyone who contributed! πŸ™Œ" >> changelog.md
    echo "" >> changelog.md
    COMPARE_BASE="$(git rev-list --max-parents=0 HEAD)"
else
    echo "### πŸ”„ Changes since $LATEST_TAG" >> changelog.md
    echo "" >> changelog.md
    COMPARE_BASE="$LATEST_TAG"
fi

# Function to extract conventional commit type and associated emoji
get_commit_type() {
    local msg="$1"
    if [[ $msg =~ ^feat(\(.+\))?:|^feature(\(.+\))?: ]]; then echo "✨ Features"
    elif [[ $msg =~ ^fix(\(.+\))?: ]]; then echo "πŸ› Bug Fixes"
    elif [[ $msg =~ ^docs(\(.+\))?: ]]; then echo "πŸ“š Documentation"
    elif [[ $msg =~ ^style(\(.+\))?: ]]; then echo "πŸ’Ž Styles"
    elif [[ $msg =~ ^refactor(\(.+\))?: ]]; then echo "♻️ Code Refactoring"
    elif [[ $msg =~ ^perf(\(.+\))?: ]]; then echo "⚑ Performance Improvements"
    elif [[ $msg =~ ^test(\(.+\))?: ]]; then echo "πŸ§ͺ Tests"
    elif [[ $msg =~ ^build(\(.+\))?: ]]; then echo "πŸ› οΈ Build System"
    elif [[ $msg =~ ^ci(\(.+\))?: ]]; then echo "βš™οΈ CI"
    elif [[ $msg =~ ^chore(\(.+\))?: ]]; then echo ""  # Skip chore commits
    else echo "πŸ” Other Changes"  # Default category with emoji
    fi
}

# Initialize associative arrays
declare -A CATEGORIES
declare -A COMMITS_BY_CATEGORY
declare -A ALL_AUTHORS
declare -A NEW_CONTRIBUTORS

# Get all historical authors before the compare base
while IFS= read -r author; do
    ALL_AUTHORS["$author"]=1
done < <(git log "${COMPARE_BASE}" --pretty=format:"%ae" | sort -u)

# Process all commits since last tag
while IFS= read -r commit_line; do
    if [[ ! $commit_line =~ ^[a-f0-9]+\| ]]; then
        echo "WARNING: Skipping invalid commit line format: $commit_line" >&2
        continue
    fi
    
    HASH=$(echo "$commit_line" | cut -d'|' -f1)
    COMMIT_MSG=$(echo "$commit_line" | cut -d'|' -f2)
    BODY=$(echo "$commit_line" | cut -d'|' -f3)
    # Skip if hash doesn't match the expected format
    if [[ ! $HASH =~ ^[a-f0-9]{40}$ ]]; then
        continue
    fi

    HASH=$(echo "$commit_line" | cut -d'|' -f1)
    COMMIT_MSG=$(echo "$commit_line" | cut -d'|' -f2)
    BODY=$(echo "$commit_line" | cut -d'|' -f3)

    
    # Validate hash format
    if [[ ! $HASH =~ ^[a-f0-9]{40}$ ]]; then
        echo "WARNING: Invalid commit hash format: $HASH" >&2
        continue
    fi
    
    # Check if it's a merge commit
    if [[ $COMMIT_MSG =~ Merge\ pull\ request\ #([0-9]+) ]]; then
        # echo "Processing as merge commit" >&2
        PR_NUM="${BASH_REMATCH[1]}"
        
        # Extract the PR title from the merge commit body
        PR_TITLE=$(echo "$BODY" | grep -v "^Merge pull request" | head -n 1)
        
        # Only process if it follows conventional commit format
        CATEGORY=$(get_commit_type "$PR_TITLE")
        
        if [ -n "$CATEGORY" ]; then  # Only process if it's a conventional commit
            # Get PR author's GitHub username
            GITHUB_USERNAME=$(gh pr view "$PR_NUM" --json author --jq '.author.login')
            
            if [ -n "$GITHUB_USERNAME" ]; then
                # Check if this is a first-time contributor
                AUTHOR_EMAIL=$(git show -s --format='%ae' "$HASH")
                if [ -z "${ALL_AUTHORS[$AUTHOR_EMAIL]}" ]; then
                    NEW_CONTRIBUTORS["$GITHUB_USERNAME"]=1
                    ALL_AUTHORS["$AUTHOR_EMAIL"]=1
                fi

                CATEGORIES["$CATEGORY"]=1
                COMMITS_BY_CATEGORY["$CATEGORY"]+="* ${PR_TITLE#*: } ([#$PR_NUM](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/pull/$PR_NUM)) by @$GITHUB_USERNAME"$'\n'
            else
                COMMITS_BY_CATEGORY["$CATEGORY"]+="* ${PR_TITLE#*: } ([#$PR_NUM](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/pull/$PR_NUM))"$'\n'
            fi
        fi
    # Check if it's a squash merge by looking for (#NUMBER) pattern
    elif [[ $COMMIT_MSG =~ \(#([0-9]+)\) ]]; then
        # echo "Processing as squash commit" >&2
        PR_NUM="${BASH_REMATCH[1]}"
        
        # Only process if it follows conventional commit format
        CATEGORY=$(get_commit_type "$COMMIT_MSG")
        
        if [ -n "$CATEGORY" ]; then  # Only process if it's a conventional commit
            # Get PR author's GitHub username
            GITHUB_USERNAME=$(gh pr view "$PR_NUM" --json author --jq '.author.login')
            
            if [ -n "$GITHUB_USERNAME" ]; then
                # Check if this is a first-time contributor
                AUTHOR_EMAIL=$(git show -s --format='%ae' "$HASH")
                if [ -z "${ALL_AUTHORS[$AUTHOR_EMAIL]}" ]; then
                    NEW_CONTRIBUTORS["$GITHUB_USERNAME"]=1
                    ALL_AUTHORS["$AUTHOR_EMAIL"]=1
                fi

                CATEGORIES["$CATEGORY"]=1
                COMMIT_TITLE=${COMMIT_MSG%% (#*}  # Remove the PR number suffix
                COMMIT_TITLE=${COMMIT_TITLE#*: }  # Remove the type prefix
                COMMITS_BY_CATEGORY["$CATEGORY"]+="* $COMMIT_TITLE ([#$PR_NUM](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/pull/$PR_NUM)) by @$GITHUB_USERNAME"$'\n'
            else
                COMMIT_TITLE=${COMMIT_MSG%% (#*}  # Remove the PR number suffix
                COMMIT_TITLE=${COMMIT_TITLE#*: }  # Remove the type prefix
                COMMITS_BY_CATEGORY["$CATEGORY"]+="* $COMMIT_TITLE ([#$PR_NUM](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/pull/$PR_NUM))"$'\n'
            fi
        fi
    
    else 
        # echo "Processing as regular commit" >&2
        # Process conventional commits without PR numbers
        CATEGORY=$(get_commit_type "$COMMIT_MSG")
        
        if [ -n "$CATEGORY" ]; then  # Only process if it's a conventional commit
            # Get commit author info
            AUTHOR_EMAIL=$(git show -s --format='%ae' "$HASH")
            
            # Try to get GitHub username using gh api
            if [ -n "$GITHUB_ACTIONS" ] || command -v gh >/dev/null 2>&1; then
                GITHUB_USERNAME=$(gh api "/repos/${GITHUB_REPOSITORY}/commits/${HASH}" --jq '.author.login' 2>/dev/null)
            fi
            
            if [ -n "$GITHUB_USERNAME" ]; then
                # If we got GitHub username, use it
                if [ -z "${ALL_AUTHORS[$AUTHOR_EMAIL]}" ]; then
                    NEW_CONTRIBUTORS["$GITHUB_USERNAME"]=1
                    ALL_AUTHORS["$AUTHOR_EMAIL"]=1
                fi

                CATEGORIES["$CATEGORY"]=1
                COMMIT_TITLE=${COMMIT_MSG#*: }  # Remove the type prefix
                COMMITS_BY_CATEGORY["$CATEGORY"]+="* $COMMIT_TITLE (${HASH:0:7}) by @$GITHUB_USERNAME"$'\n'
            else
                # Fallback to git author name if no GitHub username found
                AUTHOR_NAME=$(git show -s --format='%an' "$HASH")
                
                if [ -z "${ALL_AUTHORS[$AUTHOR_EMAIL]}" ]; then
                    NEW_CONTRIBUTORS["$AUTHOR_NAME"]=1
                    ALL_AUTHORS["$AUTHOR_EMAIL"]=1
                fi

                CATEGORIES["$CATEGORY"]=1
                COMMIT_TITLE=${COMMIT_MSG#*: }  # Remove the type prefix
                COMMITS_BY_CATEGORY["$CATEGORY"]+="* $COMMIT_TITLE (${HASH:0:7}) by $AUTHOR_NAME"$'\n'
            fi
        fi
    fi
    
done < <(git log "${COMPARE_BASE}..${GITLOG_REF}" --pretty=format:"%H|%s|%b" --reverse --first-parent)

# Write categorized commits to changelog with their emojis
for category in "✨ Features" "πŸ› Bug Fixes" "πŸ“š Documentation" "πŸ’Ž Styles" "♻️ Code Refactoring" "⚑ Performance Improvements" "πŸ§ͺ Tests" "πŸ› οΈ Build System" "βš™οΈ CI" "πŸ” Other Changes"; do
    if [ -n "${COMMITS_BY_CATEGORY[$category]}" ]; then
        echo "### $category" >> changelog.md
        echo "" >> changelog.md
        echo "${COMMITS_BY_CATEGORY[$category]}" >> changelog.md
        echo "" >> changelog.md
    fi
done

# Add first-time contributors section if there are any
if [ ${#NEW_CONTRIBUTORS[@]} -gt 0 ]; then
    echo "## ✨ First-time Contributors" >> changelog.md
    echo "" >> changelog.md
    echo "A huge thank you to our amazing new contributors! Your first contribution marks the start of an exciting journey! 🌟" >> changelog.md
    echo "" >> changelog.md
    # Use readarray to sort the keys
    readarray -t sorted_contributors < <(printf '%s\n' "${!NEW_CONTRIBUTORS[@]}" | sort)
    for github_username in "${sorted_contributors[@]}"; do
        echo "* 🌟 [@$github_username](https://github.com/$github_username)" >> changelog.md
    done
    echo "" >> changelog.md
fi

# Add compare link if not first release
if [ -n "$LATEST_TAG" ]; then
    echo "## πŸ“ˆ Stats" >> changelog.md
    echo "" >> changelog.md
    echo "**Full Changelog**: [\`$LATEST_TAG..v${NEW_VERSION}\`](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/compare/$LATEST_TAG...v${NEW_VERSION})" >> changelog.md
fi

# Output the changelog content
CHANGELOG_CONTENT=$(cat changelog.md)
{
    echo "content<<EOF"
    echo "$CHANGELOG_CONTENT"
    echo "EOF"
} >> "$GITHUB_OUTPUT"

# Also print to stdout for local testing
echo "Generated changelog:"
echo "==================="
cat changelog.md
echo "==================="