From 74d7ceea1bd3b9801563134d948f172b015558ab Mon Sep 17 00:00:00 2001 From: Rick Barenthin Date: Wed, 1 Jun 2022 20:41:56 +0200 Subject: [PATCH] wip(build): moved changelog into build as a container --- build/conventi/conventi.sh | 423 +++++++++++++++++++++++++++++++++++++ scripts/changelog.sh | 379 --------------------------------- 2 files changed, 423 insertions(+), 379 deletions(-) create mode 100755 build/conventi/conventi.sh delete mode 100755 scripts/changelog.sh diff --git a/build/conventi/conventi.sh b/build/conventi/conventi.sh new file mode 100755 index 0000000..10170e6 --- /dev/null +++ b/build/conventi/conventi.sh @@ -0,0 +1,423 @@ +#!/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 + tac + else + tail -r + fi +} + +generate_config() { + jq --null-input \ + --arg version "0.0.0" \ + --arg sha "" \ + --arg url "$1" \ + '{ "version": $version, "sha": $sha, "url": $url}' >$config_file +} + +update_config() { + # store in a var as reading and writing in one go to one file is not good + config=$(jq '.sha |= $sha | .version |= $version' --arg sha "$1" --arg version "$2" $config_file) + echo "$config" >$config_file +} + +get_config_version() { + jq '.version' <$config_file | tr -d '"' +} + +get_config_sha() { + jq '.sha' <$config_file | tr -d '"' +} + +get_config_url() { + jq '.url' <$config_file | tr -d '"' +} + +split_version_string() { + 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_id=$(echo "$pre_release." | cut -d'.' -f2) + + echo "${major:-0} ${minor:-0} ${patch:-0} $build $pre_release_id" +} + +calculate_version() { + version_string=$(split_version_string "$1") + major=$(echo "$version_string" | cut -d' ' -f1) + minor=$(echo "$version_string" | cut -d' ' -f2) + patch=$(echo "$version_string" | cut -d' ' -f3) + pre_release_id=$(echo "$version_string" | cut -d' ' -f5) + pre_release="" + build="" + + if [ "$2" -ge 100 ]; then + major=$((major + 1)) + minor=0 + patch=0 + elif [ "$2" -ge 10 ]; then + minor=$((minor + 1)) + patch=0 + elif [ "$2" -ge 1 ]; then + patch=$((patch + 1)) + fi + + if [ "$3" = "rc" ]; then + 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" + fi + + if [ "$4" = "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_url() { + url=$(git config --get remote.origin.url) + if ! echo "$url" | grep -q "^http"; then + url=$(echo "$url" | cut -d@ -f2 | sed 's/:/\//') + url="https://${url:-localhost}" + fi + + echo "$url" | sed 's/\.git$//' +} + +get_git_commits() { + since_hash=$(get_config_sha) + if [ -n "$since_hash" ]; then + git rev-list "$since_hash..HEAD" + else + git rev-list HEAD + fi +} + +get_git_commit_hash() { + git rev-parse --short "$1" +} + +get_git_first_commit_hash() { + git rev-list --max-parents=0 --abbrev-commit HEAD +} + +get_git_commit_subject() { + git show -s --format=%s "$1" +} + +get_git_commit_body() { + git show -s --format=%b "$1" +} + +get_commit_type() { + echo "$1" | sed -r -n 's/([a-z]+)(\(?|:?).*/\1/p' | tr '[:upper:]' '[:lower:]' +} + +get_commit_scope() { + echo "$1" | sed -r -n 's/[a-z]+\(([^)]+)\).*/\1/p' +} + +get_commit_description() { + echo "$1" | sed -r -n 's/.*:[ ]+(.*)/\1/p' +} + +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 [ -z "$with_colon" ] && [ -z "$with_hash" ]; then + echo "$fallback" + elif [ ${#with_colon} -gt ${#with_hash} ]; then + echo "$with_colon" + else + echo "$with_hash" + fi +} + +get_commit_body() { + pattern=$(get_commit_footer "$1" | head -n 1) + if [ -n "$pattern" ]; then + echo "$1" | sed -e "/$pattern/,\$d" + else + echo "$1" + fi +} + +is_breaking_change() { + if echo "$1" | grep -q '!' || echo "$2" | grep -q 'BREAKING[ -]CHANGE: '; then + echo 1 + else + echo 0 + fi +} + +get_breaking_change() { + with_colon=$(echo "$1" | sed -n '/^BREAKING[ -]CHANGE: /,/^[A-Za-z-]\{1,\}: /{ s/BREAKING[ -]CHANGE: \(.*\)/\1/; /^[A-Za-z-]\{1,\}: /!p;}') + with_hash=$(echo "$1" | sed -n '/^BREAKING[ -]CHANGE: /,/^[A-Za-z-]\{1,\} #/{ s/BREAKING[ -]CHANGE: \(.*\)/\1/; /^[A-Za-z-]\{1,\} #/!p;}') + if [ -n "$with_colon" ] && ! echo "$with_colon" | grep -q '[A-Za-z-]\{1,\} #'; then + echo "$with_colon" + else + echo "$with_hash" + fi +} + +format_entry_line() { + line="" + if [ -n "$2" ]; then + line="**$2:**" + fi + line="$line $1" + + echo "$line ([$3]($(get_config_url)/commit/$3))" | sed -e '2,$s/^[ ]*/ /' +} + +breaking_change_lines="" +feature_lines="" +bugfix_lines="" +build_lines="" +chore_lines="" +ci_lines="" +doc_lines="" +style_lines="" +refactor_lines="" +perf_lines="" +test_lines="" +revert_lines="" + +add_changelog_entry_line() { + case "$1" in + feat) + feature_lines=$(printf '%s\n* %s\n' "$feature_lines" "$2") + ;; + + fix) + bugfix_lines=$(printf '%s\n* %s\n' "$bugfix_lines" "$2") + ;; + + build) + build_lines="$build_lines\n* $2\n" + ;; + + chore) + chore_lines="$chore_lines\n* $2\n" + ;; + + ci) + ci_lines="$ci_lines\n* $2\n" + ;; + + doc_lines) + doc_lines="$doc_lines\n* $2\n" + ;; + + style) + style_lines="$style_lines\n* $2\n" + ;; + + refactor) + refactor_lines="$refactor_lines\n* $2\n" + ;; + + perf) + perf_lines="$perf_lines\n* $2\n" + ;; + + test) + test_lines="$test_lines\n* $2\n" + ;; + + revert) + revert_lines="$revert_lines\n* $2\n" + ;; + esac +} + +format_entry() { + entry="" + if [ -n "$breaking_change_lines" ]; then + entry="$entry### BREAKING CHANGES\n$breaking_change_lines\n\n" + fi + if [ -n "$bugfix_lines" ]; then + entry="$entry### Bug fixes\n$bugfix_lines\n\n" + fi + if [ -n "$feature_lines" ]; then + #entry="$entry### Features\n$feature_lines\n\n" + entry=$(printf '%s### Features\n%s\n\n' "$entry" "$feature_lines") + fi + if [ -n "$revert_lines" ]; then + entry="$entry### Reverts\n$revert_lines\n\n" + fi + if [ -n "$build_lines" ]; then + entry="$entry### Build System\n$build_lines\n\n" + fi + if [ -n "$chore_lines" ]; then + entry="$entry### Chores\n$chore_lines\n\n" + fi + if [ -n "$ci_lines" ]; then + entry="$entry### CIs\n$ci_lines\n\n" + fi + if [ -n "$doc_lines" ]; then + entry="$entry### Docs\n$doc_lines\n\n" + fi + if [ -n "$test_lines" ]; then + entry="$entry### Tests\n$test_lines\n\n" + fi + if [ -n "$style_lines" ]; then + entry="$entry### Styles\n$style_lines\n\n" + fi + if [ -n "$refactor_lines" ]; then + entry="$entry### Refactors\n$refactor_lines\n\n" + fi + if [ -n "$perf_lines" ]; then + entry="$entry### Perfs\n$perf_lines\n\n" + fi + if [ -n "$entry" ]; then + entry="## [$version]($(get_config_url)/compare/$from_hash...$to_hash) ($(date '+%Y-%m-%d'))\n\n\n$entry" + fi + echo "$entry" +} + +generate_changelog_entry() { + is_major=0 + is_minor=0 + is_patch=0 + + version=$(get_config_version) + from_hash=$(get_config_sha) + if [ -z "$from_hash" ]; then + from_hash=$(get_git_first_commit_hash) + fi + to_hash=$(get_git_commit_hash HEAD) + + for commit in $(get_git_commits); do + git_subject=$(get_git_commit_subject "$commit") + git_body=$(get_git_commit_body "$commit") + hash=$(get_git_commit_hash "$commit") + + type=$(get_commit_type "$git_subject") + scope=$(get_commit_scope "$git_subject") + description=$(get_commit_description "$git_subject") + body=$(get_commit_body "$git_body") + footer=$(get_commit_footer "$git_body") + breaking_change=$(get_breaking_change "$footer") + is_breaking=$(is_breaking_change "$git_subject" "$footer") + line=$(format_entry_line "$description" "$scope" "$hash") + + if [ "$is_breaking" -eq 1 ] && [ -z "$breaking_change" ]; then + breaking_change="$body" + fi + + if [ "$is_breaking" -eq 1 ]; then + is_major=1 + breaking_change=$(echo "$breaking_change" | sed -e '2,$s/^[ ]*/ /') + breaking_change_lines="$breaking_change_lines\n* $breaking_change\n" + fi + + case "$type" in + feat) + is_minor=1 + ;; + + fix) + is_patch=1 + ;; + esac + + add_changelog_entry_line "$type" "$line" + done + + version_update=$((is_major * 100 + is_minor * 10 + is_patch)) + if [ $version_update -gt 0 ]; then + version=$(calculate_version "$version" "$version_update") + else + # no version change, no need to generate changelog + return + fi + + update_config "$to_hash" "$version" + + format_entry +} + +new_changelog() { + echo "$changelog_header" >$changelog_file +} + +new_changelog_entry() { + entry=$(generate_changelog_entry) + if [ -z "$entry" ]; then + return + fi + + 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 +} + +init() { + if [ "$1" = "--help" ]; then + echo "$(basename "$0") [--help] [init|get_version]" + exit 0 + elif [ "$1" = "init" ]; then + echo "Generating a config ... " + echo "We guessed that the url for git links will be: $(get_git_url)" + echo "Please change inside the '${config_file}' file if needed!" + generate_config "$(get_git_url)" + new_changelog + exit 0 + fi + + if [ ! -f "$config_file" ]; then + echo >&2 "ERROR: config file is missing to generate changelog entry" + exit 1 + fi + + if [ "$1" = "get_version" ]; then + get_config_version + exit 0 + fi + + echo "Using existing config to generate changelog entry" + new_changelog_entry +} + +init "$@" diff --git a/scripts/changelog.sh b/scripts/changelog.sh deleted file mode 100755 index 8cceaff..0000000 --- a/scripts/changelog.sh +++ /dev/null @@ -1,379 +0,0 @@ -#!/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 - tac - else - tail -r - fi -} - -generate_config() { - jq --null-input --arg version "0.0.0" --arg sha "" --arg url "$1" '{ "version": $version, "sha": $sha, "url": $url}' > $config_file -} - -update_config() { - config=$(jq '.sha |= $sha | .version |= $version' --arg sha "$1" --arg version "$2" $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_url() { - url=$(git config --get remote.origin.url) - if ! echo "$url" | grep -q "^http"; then - url=$(echo "$url" | cut -d@ -f2 | sed 's/:/\//') - url="https://$url" - fi - - echo "$url" | sed 's/\.git$//' -} - -get_git_commits() { - since_hash="$(jq '.sha' < $config_file | tr -d '"')" - if [ -n "$since_hash" ]; then - git rev-list "$since_hash..HEAD" - else - git rev-list HEAD - fi -} - -get_git_commit_hash() { - git rev-parse --short "$1" -} - -get_git_first_commit_hash() { - git rev-list --max-parents=0 --abbrev-commit HEAD -} - -get_git_commit_subject() { - git show -s --format=%s "$1" -} - -get_git_commit_body() { - git show -s --format=%b "$1" -} - -get_commit_type() { - echo "$1" | sed -r -n 's/([a-z]+)(\(?|:?).*/\1/p' | tr '[:upper:]' '[:lower:]' -} - -get_commit_scope() { - echo "$1" | sed -r -n 's/[a-z]+\(([^)]+)\).*/\1/p' -} - -get_commit_description() { - echo "$1" | sed -r -n 's/.*:[ ]+(.*)/\1/p' -} - -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 [ -z "$with_colon" ] && [ -z "$with_hash" ]; then - echo "$fallback" - elif [ ${#with_colon} -gt ${#with_hash} ]; then - echo "$with_colon" - else - echo "$with_hash" - fi -} - -get_commit_body() { - pattern=$(get_commit_footer "$1" | head -n 1) - if [ -n "$pattern" ]; then - echo "$1" | sed -e "/$pattern/,\$d" - else - echo "$1" - fi -} - -is_breaking_change() { - if echo "$1" | grep -q '!' || echo "$2" | grep -q 'BREAKING[ -]CHANGE: '; then - echo 1 - else - echo 0 - fi -} - -get_breaking_change() { - with_colon=$(echo "$1" | sed -n '/^BREAKING[ -]CHANGE: /,/^[A-Za-z-]\{1,\}: /{ s/BREAKING[ -]CHANGE: \(.*\)/\1/; /^[A-Za-z-]\{1,\}: /!p;}') - with_hash=$(echo "$1" | sed -n '/^BREAKING[ -]CHANGE: /,/^[A-Za-z-]\{1,\} #/{ s/BREAKING[ -]CHANGE: \(.*\)/\1/; /^[A-Za-z-]\{1,\} #/!p;}') - if [ -n "$with_colon" ] && ! echo "$with_colon" | grep -q '[A-Za-z-]\{1,\} #'; then - echo "$with_colon" - else - echo "$with_hash" - fi -} - -format_entry_line() { - if [ -n "$2" ]; then - line="**$2:** $1" - else - line="$1" - fi - - url="$(jq '.url' < $config_file | tr -d '"')" - - echo "$line ([$3]($url/commit/$3))" | sed -e '2,$s/^[ ]*/ /' -} - -generate_changelog_entry() { - is_major=0 - is_minor=0 - is_patch=0 - - version=$(jq '.version' < $config_file | tr -d '"') - from_hash=$(jq '.sha' < $config_file | tr -d '"') - if [ -z "$from_hash" ]; then - from_hash=$(get_git_first_commit_hash) - fi - to_hash=$(get_git_commit_hash HEAD) - - breakingchanges="" - features="" - bugfixes="" - builds="" - chores="" - cis="" - docs="" - styles="" - refactors="" - perfs="" - tests="" - reverts="" - - for commit in $(get_git_commits); do - git_subject=$(get_git_commit_subject "$commit") - git_body=$(get_git_commit_body "$commit") - hash=$(get_git_commit_hash "$commit") - - type=$(get_commit_type "$git_subject") - scope=$(get_commit_scope "$git_subject") - description=$(get_commit_description "$git_subject") - body=$(get_commit_body "$git_body") - footer=$(get_commit_footer "$git_body") - breakingchange=$(get_breaking_change "$footer") - is_breaking=$(is_breaking_change "$git_subject" "$footer") - line=$(format_entry_line "$description" "$scope" "$hash") - - if [ "$is_breaking" -eq 1 ] && [ -z "$breakingchange" ]; then - breakingchange="$body" - fi - - 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* $line\n" - ;; - - fix) - is_patch=1 - bugfixes="$bugfixes\n* $line\n" - ;; - - build) - builds="$builds\n* $line\n" - ;; - - chore) - chores="$chores\n* $line\n" - ;; - - ci) - cis="$cis\n* $line\n" - ;; - - docs) - docs="$docs\n* $line\n" - ;; - - style) - styles="$styles\n* $line\n" - ;; - - refactor) - refactors="$refactors\n* $line\n" - ;; - - perf) - perfs="$perfs\n* $line\n" - ;; - - test) - tests="$tests\n* $line\n" - ;; - - revert) - reverts="$reverts\n* $line\n" - ;; - 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 "$to_hash" "$version" - - entry="" - if [ -n "$breakingchanges" ]; then - entry="$entry### BREAKING CHANGES\n$breakingchanges\n\n" - fi - if [ -n "$bugfixes" ]; then - entry="$entry### Bug fixes\n$bugfixes\n\n" - fi - if [ -n "$features" ]; then - entry="$entry### Features\n$features\n\n" - fi - if [ -n "$reverts" ]; then - entry="$entry### Reverts\n$reverts\n\n" - fi - if [ -n "$builds" ]; then - entry="$entry### Build System\n$builds\n\n" - fi - if [ -n "$chores" ]; then - entry="$entry### Chores\n$chores\n\n" - fi - if [ -n "$cis" ]; then - entry="$entry### CIs\n$cis\n\n" - fi - if [ -n "$docs" ]; then - entry="$entry### Docs\n$docs\n\n" - fi - if [ -n "$tests" ]; then - entry="$entry### Tests\n$tests\n\n" - fi - if [ -n "$styles" ]; then - entry="$entry### Styles\n$styles\n\n" - fi - if [ -n "$refactors" ]; then - entry="$entry### Refactors\n$refactors\n\n" - fi - if [ -n "$perfs" ]; then - entry="$entry### Perfs\n$perfs\n\n" - fi - if [ -n "$entry" ]; then - url="$(jq '.url' < $config_file | tr -d '"')" - entry="## [$version]($url/compare/$from_hash...$to_hash) ($(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 "$(get_git_url)" - new_changelog - else - echo "Using existing config" - fi - - new_changelog_entry -} - -init \ No newline at end of file