Skip to content

Instantly share code, notes, and snippets.

@freehuntx
Created July 8, 2025 16:25
Show Gist options
  • Save freehuntx/ce14609040ccaaabae170dc8e20164e8 to your computer and use it in GitHub Desktop.
Save freehuntx/ce14609040ccaaabae170dc8e20164e8 to your computer and use it in GitHub Desktop.
Release workflow bash file
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'
# Get the latest tag
LAST_TAG="$(git describe --tags --abbrev=0 2>/dev/null || true)"
# Get commits since the last tag, or all commits if no tag exists
if [[ -z "$LAST_TAG" ]]; then
COMMITS=$(git log --pretty=format:"%s")
else
COMMITS=$(git log "$LAST_TAG"..HEAD --pretty=format:"%s")
fi
# Check if there are any new commits
if [ -z "$COMMITS" ]; then
echo "No new commits since ${LAST_TAG:-beginning of history}. Exiting."
exit 0
fi
CURRENT_VERSION=0.0.0
if [ -n "$LAST_TAG" ]; then
CURRENT_VERSION=${LAST_TAG#v}
CURRENT_VERSION=${CURRENT_VERSION%%+*}
CURRENT_VERSION=${CURRENT_VERSION%%-*}
fi
IFS='.' read -r MAJOR MINOR PATCH <<<"$CURRENT_VERSION"
MAJOR=$((10#$MAJOR))
MINOR=$((10#$MINOR))
PATCH=$((10#$PATCH))
# Check for breaking changes (type! or type(scope)!: ...)
if echo "$COMMITS" | grep -Eq "^(feat|fix|chore|docs|style|refactor|perf|test|build|ci)(\([^)]+\))?!:"; then
MAJOR=$((MAJOR + 1))
MINOR=0
PATCH=0
elif echo "$COMMITS" | grep -q "^feat"; then
MINOR=$((MINOR + 1))
PATCH=0
else
PATCH=$((PATCH + 1))
fi
NEXT_VERSION="v$MAJOR.$MINOR.$PATCH"
CURRENT_DATE=$(date +%Y-%m-%d)
# Generate categorized changelog for this release
NEW_CHANGELOG=$(echo "$COMMITS" | \
grep -E "^(feat|fix|chore|docs|style|refactor|perf|test|build|ci)(\([^)]+\))?!?:" | \
awk -v ver="$NEXT_VERSION" -v date="$CURRENT_DATE" '
BEGIN { print "## " ver " - " date "\n" }
/^feat/ { feats = feats $0 "\n" }
/^fix/ { fixes = fixes $0 "\n" }
/^chore/ { chores = chores $0 "\n" }
/^docs/ { docs = docs $0 "\n" }
/^style/ { styles = styles $0 "\n" }
/^refactor/ { refactors = refactors $0 "\n" }
/^perf/ { perfs = perfs $0 "\n" }
/^test/ { tests = tests $0 "\n" }
/^build/ { builds = builds $0 "\n" }
/^ci/ { cis = cis $0 "\n" }
END {
if (feats) print "### Features\n" feats
if (fixes) print "### Fixes\n" fixes
if (chores) print "### Chores\n" chores
if (docs) print "### Documentation\n" docs
if (styles) print "### Styles\n" styles
if (refactors) print "### Refactoring\n" refactors
if (perfs) print "### Performance\n" perfs
if (tests) print "### Tests\n" tests
if (builds) print "### Build\n" builds
if (cis) print "### CI\n" cis
print "---\n"
}
')
# Confirmation before pushing changelog commit and tag
read -p "$(echo -e "$NEW_CHANGELOG\n\nAbout to push the changelog commit and tag $NEXT_VERSION (prev. $CURRENT_VERSION).\nContinue? [y/N]: ")" CONFIRM
if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then
echo "Aborted by user."
exit 0
fi
# If there's no CHANGELOG.md then create one with a placeholder
CHANGELOG="CHANGELOG.md"
PLACEHOLDER="<!-- changelog-placeholder -->"
if [ ! -f "$CHANGELOG" ]; then
cat <<EOF >"$CHANGELOG"
# Changelog
$PLACEHOLDER
EOF
fi
# Insert new changelog entry after the placeholder
awk -v entry="$NEW_CHANGELOG" -v ph="$PLACEHOLDER" '
BEGIN { done=0 }
{
print
if (!done && index($0, ph)) {
print entry
done=1
}
}
' "$CHANGELOG" > "$CHANGELOG.tmp" && mv "$CHANGELOG.tmp" "$CHANGELOG"
# Create a commit with the updated changelog and push it
git add "$CHANGELOG"
git commit -m "docs(changelog): $NEXT_VERSION"
git push
# Create a new tag with the next version and changelog as description
git tag -a "$NEXT_VERSION" -m "$NEW_CHANGELOG"
# Push the new tag
git push origin "$NEXT_VERSION"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment