diff --git a/scripts/changelog.sh b/scripts/changelog.sh index 0cf190a..99a3563 100755 --- a/scripts/changelog.sh +++ b/scripts/changelog.sh @@ -1,6 +1,15 @@ #!/usr/bin/env sh config_file=.version.json +changelog_file=CHANGELOG.md +changelog_header="# Changelog + +All notable changes to this project will be documented in this file. See [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for commit guidelines. + +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +---- +" reverse() { if which tac >/dev/null 2>&1; then @@ -11,18 +20,88 @@ reverse() { } generate_config() { + jq --null-input --arg version "0.0.0" --arg sha "" '{ "version": $version, "sha": $sha}' > $config_file +} + +update_config() { sha="$(git rev-parse --short HEAD)" - ( - echo '{' - echo '"version": "0.0.0",' - echo '"sha": "'"$sha"'"' - echo '}' - ) | jq . > $config_file + config=$(jq '.sha |= $sha | .version |= $version' --arg sha "$sha" --arg version "$1" $config_file) + echo "$config" > $config_file +} + +calculate_version() { + version_number=$(echo "$1" | cut -d'-' -f1 | cut -d'+' -f1) + pre_release=$(echo "$1" | cut -d'-' -f2 | cut -d'+' -f1) + build=$(echo "$1" |cut -d'-' -f2 | cut -d'+' -f2) + if [ "$pre_release" = "$1" ]; then + pre_release="" + fi + if [ "$build" = "$1" ] || [ "$build" = "$pre_release" ]; then + build="" + fi + + major=$(echo "$version_number" | cut -d'.' -f1) + minor=$(echo "$version_number" | cut -d'.' -f2) + patch=$(echo "$version_number" | cut -d'.' -f3) + pre_release_identifier=$(echo "$pre_release." | cut -d'.' -f2) + + pre_release="" + build="" + + case "$2" in + major) + if [ -n "$major" ]; then + major=$((major+1)) + minor=0 + patch=0 + fi + ;; + + minor) + if [ -n "$minor" ]; then + minor=$((minor+1)) + patch=0 + fi + ;; + + patch) + if [ -n "$patch" ]; then + patch=$((patch+1)) + fi + ;; + + rc) + if [ -z "$pre_release_identifier" ]; then + pre_release_identifier=1 + else + pre_release_identifier=$((pre_release_identifier+1)) + fi + pre_release="rc.$pre_release_identifier" + ;; + esac + + if [ "$3" = "build" ]; then + build="$(git rev-parse --short HEAD)" + fi + + version_number="${major}.${minor}.${patch}" + if [ -n "$pre_release" ]; then + pre_release="-$pre_release" + fi + if [ -n "$build" ]; then + build="+$build" + fi + + echo "${version_number}${pre_release}${build}" } get_git_commits() { since_hash="$(jq '.sha' < $config_file | tr -d '"')" - git rev-list "$since_hash..HEAD" + if [ -n "$since_hash" ]; then + git rev-list "$since_hash..HEAD" + else + git rev-list HEAD + fi } get_git_commit_subject() { @@ -34,7 +113,7 @@ get_git_commit_body() { } get_commit_type() { - echo "$1" | sed -r -n 's/([a-z]+)(\(?|:?).*/\1/p' + echo "$1" | sed -r -n 's/([a-z]+)(\(?|:?).*/\1/p' | tr '[:upper:]' '[:lower:]' } get_commit_scope() { @@ -48,8 +127,11 @@ get_commit_description() { get_commit_footer() { with_colon=$(echo "$1" | sed -n '/^[A-Za-z-]\{1,\}: /,//{p;}') with_hash=$(echo "$1" | sed -n '/^[A-Za-z-]\{1,\} #/,//{p;}') + fallback=$(echo "$1" | reverse | awk '/^$/{exit}1' | reverse) - if [ ${#with_colon} -gt ${#with_hash} ]; then + if [ -z "$with_colon" ] && [ -z "$with_hash" ]; then + echo "$fallback" + elif [ ${#with_colon} -gt ${#with_hash} ]; then echo "$with_colon" else echo "$with_hash" @@ -67,9 +149,9 @@ get_commit_body() { is_breaking_change() { if echo "$1" | grep -q '!' || echo "$2" | grep -q 'BREAKING[ -]CHANGE: '; then - echo "yes" + echo 1 else - echo "no" + echo 0 fi } @@ -84,9 +166,19 @@ get_breaking_change() { } generate_changelog_entry() { - is_patch=0 - is_minor=0 is_major=0 + is_minor=0 + is_patch=0 + + version="$(jq '.version' < $config_file | tr -d '"')" + + breakingchanges="### BREAKING CHANGES\n" + features="### Features\n" + bugfixes="### Bug fixes\n" + builds="### Build System" + chores="### Chores\n" + test="### Chores\n" + for commit in $(get_git_commits); do git_subject=$(get_git_commit_subject "$commit") git_body=$(get_git_commit_body "$commit") @@ -97,42 +189,124 @@ generate_changelog_entry() { body=$(get_commit_body "$git_body") footer=$(get_commit_footer "$git_body") breakingchange=$(get_breaking_change "$footer") - isbreaking=$(is_breaking_change "$git_subject" "$footer") - if [ "$isbreaking" = "yes" ] && [ -z "$breakingchange" ]; then + is_breaking=$(is_breaking_change "$git_subject" "$footer") + + if [ -n "$scope" ]; then + entry="**$scope:** $description" + else + entry="$description" + fi + entry=$(echo "$entry" | sed -e '2,$s/^[ ]*/ /') + + if [ "$is_breaking" -eq 1 ] && [ -z "$breakingchange" ]; then breakingchange="$body" fi - echo "--------------------------------" - echo "Subject : $git_subject" - echo "Body: $git_body" - echo - echo "********************************" - echo - echo " - type : $type" - echo " - scope : $scope" - echo " - description : $description" - echo " - body : $body" - echo " - footer : $footer" - echo " - breakingchange : $breakingchange" - echo " - isbreaking : $isbreaking" - echo - echo "--------------------------------" - echo - echo - done; + if [ "$is_breaking" -eq 1 ]; then + is_major=1 + breakingchange=$(echo "$breakingchange" | sed -e '2,$s/^[ ]*/ /') + breakingchanges="$breakingchanges\n* $breakingchange\n" + fi + + case "$type" in + feat) + is_minor=1 + features="$features\n* $entry\n" + ;; + + fix) + is_patch=1 + bugfixes="$bugfixes\n* $entry\n" + ;; + + build) + builds="$builds\n* $entry\n" + ;; + + chore) + chores="$chores\n* $entry\n" + ;; + + ci) + ;; + + docs) + ;; + + style) + ;; + + refactor) + ;; + + perf) + ;; + + test) + ;; + + revert) + ;; + esac + done + + if [ $is_major -eq 1 ]; then + version=$(calculate_version "$version" "major") + elif [ $is_minor -eq 1 ]; then + version=$(calculate_version "$version" "minor") + elif [ $is_patch -eq 1 ]; then + version=$(calculate_version "$version" "patch") + fi + + update_config "$version" + + entry="" + + if [ "$breakingchanges" != "### BREAKING CHANGES\n" ]; then + entry="$entry$breakingchanges\n\n" + fi + if [ "$bugfixes" != "### Bug fixes\n" ]; then + entry="$entry$bugfixes\n\n" + fi + if [ "$features" != "### Features\n" ]; then + entry="$entry$features\n\n" + fi + + if [ -n "$entry" ]; then + entry="## [$version] ($(date '+%Y-%m-%d'))\n\n\n$entry" + fi + + echo "$entry" } +new_changelog() { + echo "$changelog_header" > $changelog_file +} + +new_changelog_entry() { + entry=$(generate_changelog_entry) + if [ -n "$entry" ]; then + changelog=$(sed -n '/^----/,//{/^----/!p;}' < $changelog_file) + if [ -n "$changelog" ]; then + changelog="$changelog_header\n$entry\n$changelog" + else + changelog="$changelog_header\n$entry" + fi + echo "$changelog" > $changelog_file + fi +} + init() { if [ ! -f "$config_file" ]; then echo "Generating a config ... " generate_config - echo "Done" + new_changelog else echo "Using existing config" fi - generate_changelog_entry + new_changelog_entry } init \ No newline at end of file