Last active
May 4, 2021 01:07
-
-
Save rlespinasse/78a136cae7d19e83c3e9232b42734d35 to your computer and use it in GitHub Desktop.
Generate changelog of Lerna independant packages as Slack message
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
set -eo pipefail | |
set -u | |
IFS=$'\n\t' | |
command -v fzf >/dev/null 2>&1 || { | |
echo >&2 "I require fzf but it's not installed. Aborting." | |
exit 1 | |
} | |
bot_name="${1:-Changelog BOT}" | |
print_types="${2:-all}" | |
spacer=" " | |
get_packages() { | |
npx lerna list | fzf -m -1 --header="Select packages" | |
} | |
get_package_tags() { | |
local package="$1" | |
git tag -l "$package@*" | |
} | |
get_last_package_version() { | |
local package="$1" | |
latest_tag=$(git describe --abbrev=0 HEAD --match "$package@*" | sed 's/.*@//') | |
get_package_tags "$package" | grep -v "$latest_tag" | \ | |
sed 's/.*@//;s/$/_/' | sort -V | sed 's/_$//' | \ | |
fzf -1 --tac --header="Select last '$package' version (from x to $latest_tag)" | |
} | |
get_changelog() { | |
local package="$1" | |
local version="$2" | |
echo "$package@$version" >> generate-changelog-message.tags.log | |
while read -r commit | |
do | |
if [ ! "$(git diff-tree --no-commit-id --name-only -r "$commit" | grep "/$package/")" == "" ] | |
then | |
git show -s --format="%s" "$commit" | \ | |
# Remove stash commit from github like '(#000)' | |
sed 's/(#[0-9]*)$//;s/\s*([^)]*:/:/;s/\s*(/(/' | \ | |
# Trim | |
sed 's/^\s*//;s/\s*$//' | |
fi | |
done < <(git log "$package@$version"..HEAD --oneline --format="%H") | |
} | |
correct_commit_type() { | |
local commit_type="$1" | |
local commit_context="$2" | |
local end_of_message="$3" | |
local options=() | |
local types | |
if [ ! "$commit_context" == "" ]; then | |
commit_context="($commit_context)" | |
fi | |
types=( | |
"feat" | |
"fix" | |
"docs" | |
"refactor" | |
"perf" | |
"test" | |
"style" | |
"build" | |
"ci" | |
) | |
for type in "${types[@]}" | |
do | |
if [[ "$type" == "${commit_type}" ]]; then | |
echo "${commit_type}${commit_context}${end_of_message}" | |
return | |
fi | |
done | |
if [ "$commit_context" == "" ] && [ ! "$commit_type" == "" ]; then | |
commit_context="($commit_type)" | |
fi | |
for type in "${types[@]}" | |
do | |
options+=("$type$commit_context$end_of_message") | |
done | |
printf -- "%s" "${options[*]}" | \ | |
fzf --header="Choose valid commit type instead of '$commit_type'" | |
} | |
get_clean_commit_type() { | |
local message="$1" | |
echo "$message" | \ | |
# Extract commit type before '(' or ':' | |
# also manage message without commit type | |
sed 's/:.*/#/;s/(.*#$/#/;s/$/#/;s/##$//;s/.*#$//' | \ | |
# Commit type is always in lower case | |
tr '[:upper:]' '[:lower:]' | \ | |
# Clean values | |
sed 's/feature/feat/;s/bugs/fix/' | |
} | |
get_clean_commit_context() { | |
local message="$1" | |
# shellcheck disable=SC2001 | |
echo "$message" | sed 's/^.*(\(.*\)).*$/\1#/;s/^.*[^#]//;s/#$//' | |
} | |
get_clean_end_of_message() { | |
local message="$1" | |
echo "$message" | \ | |
# Remove commit type | |
sed 's/^.*:/:/' | \ | |
# Remove reverted mode | |
sed 's/#REVERTED$//' | \ | |
# Add ':' on message without commit type | |
sed 's/^/#/;s/^#:/:/;s/^#/: /' | |
} | |
manage_revert() { | |
local commits="$1" | |
local clean_commits | |
local reverted_commits | |
clean_commits="$commits" | |
reverted_commits="" | |
# Remove reverted commits | |
while read -r reverted_message | |
do | |
clean_commits=$(echo "$clean_commits" | grep -v "^$reverted_message$") | |
reverted_commits=$(printf "%s\n%s" "$reverted_commits" "Revert \"$reverted_message\"") | |
done < <(echo "$commits" | grep "Revert" | sed 's/^Revert\s"//;s/"$//') | |
# Remove revert commits | |
while read -r revert_commit | |
do | |
clean_commits=$(echo "$clean_commits" | grep -v "^$revert_commit$") | |
done < <(echo "$reverted_commits") | |
# Transform revert message | |
while read -r revert_message | |
do | |
clean_commits=$(printf "%s\n%s%s" "$clean_commits" "$revert_message" "#REVERTED") | |
done < <(echo "$clean_commits" | grep "Revert" | sed 's/^Revert\s"//;s/"$//') | |
echo "$clean_commits" | grep -v "Revert" | |
} | |
manage_unknown_commit_type() { | |
local commits="$1" | |
local known_types="^build:\|^ci:\|^docs:\|^feat:\|^fix:\|^perf:\|^refactor:\|^style:\|^test:\|^build(\|^ci(\|^docs(\|^feat(\|^fix(\|^perf(\|^refactor(\|^style(\|^test(" | |
local clean_commits | |
# Get all commits with valid type | |
clean_commits="$(echo "$commits" | grep "$known_types")" | |
# Correct all commits with invalid type | |
while read -r message | |
do | |
local commit_type | |
local commit_context | |
local end_of_message | |
local revert_mode | |
local clean_message | |
commit_type=$(get_clean_commit_type "$message") | |
commit_context=$(get_clean_commit_context "$message") | |
end_of_message=$(get_clean_end_of_message "$message") | |
revert_mode="" | |
if [[ "$message" == *"#REVERTED"* ]]; then | |
revert_mode="#REVERTED" | |
fi | |
clean_message=$(correct_commit_type "$commit_type" "$commit_context" "$end_of_message") | |
clean_commits=$(printf "%s\n%s%s" "$clean_commits" "$clean_message" "$revert_mode") | |
done < <(echo "$commits" | grep -v "$known_types") | |
echo "$clean_commits" | |
} | |
get_clean_changelog() { | |
local package="$1" | |
local commits | |
commits=$(get_changelog "$package" "$(get_last_package_version "$package")" | \ | |
grep -v "build: release" | \ | |
grep -v "root commit" | \ | |
grep -v "^Merge") | |
commits=$(manage_revert "$commits") | |
commits=$(manage_unknown_commit_type "$commits") | |
echo "$commits" | |
} | |
tocsv() { | |
local package="$1" | |
while read -r data | |
do | |
local subjectcsv | |
subjectcsv=$(echo "$data" | \ | |
# Manage subject | |
sed 's/:/#;/' | \ | |
# Manage context | |
sed 's/(/;/;s/)#;/;/;s/#;/;;/' | \ | |
# Manage reverted commit status | |
sed 's/$/#/;s/#REVERTED#$/;revert/;s/#$/;/' | \ | |
# Trim each field | |
sed 's/\s*;/;/g;s/;\s*/;/g' | |
) | |
echo "$package;$subjectcsv" | |
done | |
} | |
get_changelogs() { | |
while read -r package | |
do | |
get_clean_changelog "$package" | tocsv "$package" | |
done < <(get_packages) | |
} | |
toslackmarkdown() { | |
local current_package="" | |
local current_type="" | |
while read -r data | |
do | |
local package | |
local type | |
local context | |
local subject | |
local revert | |
if [ "$data" == "" ]; then | |
continue | |
fi | |
package="$(echo "${data}" | cut -d';' -f1)" | |
type="$(echo "${data}" | cut -d';' -f2)" | |
context="$(echo "${data}" | cut -d';' -f3)" | |
subject="$(echo "${data}" | cut -d';' -f4)" | |
revert="$(echo "${data}" | cut -d';' -f5)" | |
if [ ! "$print_types" == "all" ] && [[ ! "$print_types" == *"$type"* ]]; then | |
continue | |
fi | |
if [ ! "$package" == "$current_package" ]; then | |
current_type="" | |
current_package="$package" | |
echo "${spacer}" | |
latest_tag=$(git describe --tags --abbrev=0 --match "$package@*" | sed 's/.*@//') | |
since_tag=$(grep "$package@" generate-changelog-message.tags.log | sed 's/.*@//') | |
echo ":package: Changelog of $current_package v$latest_tag (since v$since_tag)" | |
fi | |
if [ ! "$type" == "$current_type" ]; then | |
current_type="$type" | |
echo "${spacer}" | |
if [ "$type" == "feat" ]; then | |
echo "${spacer}${spacer}:sparkles: Features" | |
elif [ "$type" == "fix" ]; then | |
echo "${spacer}${spacer}:bug: Corrections" | |
elif [ "$type" == "docs" ]; then | |
echo "${spacer}${spacer}:pencil: Documentation" | |
elif [ "$type" == "build" ]; then | |
echo "${spacer}${spacer}:building_construction: Build System" | |
elif [ "$type" == "ci" ]; then | |
echo "${spacer}${spacer}:construction_worker: Continuous Delivery System" | |
elif [ "$type" == "perf" ]; then | |
echo "${spacer}${spacer}:zap: Performance" | |
elif [ "$type" == "refactor" ]; then | |
echo "${spacer}${spacer}:wrench: Refactoring feature" | |
elif [ "$type" == "style" ]; then | |
echo "${spacer}${spacer}:art: Code style" | |
elif [ "$type" == "test" ]; then | |
echo "${spacer}${spacer}:white_check_mark: Tests" | |
fi | |
fi | |
if [ "$revert" == "revert" ]; then | |
revert=":rewind: " | |
fi | |
if [ ! "$subject" == "" ]; then | |
if [ "$context" == "" ]; then | |
echo "${spacer}${spacer}${spacer}${spacer}${revert}$subject" | |
else | |
echo "${spacer}${spacer}${spacer}${spacer}${revert}[$context] $subject" | |
fi | |
fi | |
done | |
} | |
get_markdown_changelog() { | |
local content | |
content=$(get_changelogs | sort) | |
echo ":robot_face: $bot_name" | |
echo "$content" | toslackmarkdown | |
} | |
cleanup() { | |
rm -f generate-changelog-message.tags.log || true | |
} | |
trap cleanup EXIT | |
cleanup | |
get_markdown_changelog |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment