Spaces:
Runtime error
Runtime error
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 "===================" |