mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-12 15:53:46 -04:00
168 lines
4.5 KiB
Bash
Executable File
168 lines
4.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
|
||
CHANGE_DIRECTORY=.changes
|
||
TYPES=(added changed deprecated removed fixed security maintenance)
|
||
SECTIONS=('Breaking Changes' Security Added Changed Deprecated Removed Fixed Maintenance)
|
||
|
||
BOLD=$'\033[1m' GREEN=$'\033[32m' RED=$'\033[31m' DIM=$'\033[2m' RESET=$'\033[0m'
|
||
[ -t 1 ] || BOLD='' GREEN='' RED='' DIM='' RESET=''
|
||
|
||
heading() { printf '\n %s%s%s\n' "$BOLD" "$*" "$RESET"; }
|
||
ok() { printf ' %s✔%s %s\n' "$GREEN" "$RESET" "$*"; }
|
||
die() {
|
||
printf ' %s✖%s %s\n' "$RED" "$RESET" "$*" >&2
|
||
exit 1
|
||
}
|
||
confirm() {
|
||
local answer
|
||
printf '\n %s [y/N] ' "$*"
|
||
read -r answer
|
||
[[ "$answer" =~ ^[Yy]$ ]]
|
||
}
|
||
|
||
cd "$(dirname "$0")/.."
|
||
trap 'tput cnorm 2> /dev/null || true' EXIT
|
||
|
||
main() {
|
||
read_arguments "$@"
|
||
if [ -n "$notes" ]; then
|
||
print_notes
|
||
return
|
||
fi
|
||
[ -n "$kind" ] && [ -n "$summary" ] || ask_questions
|
||
write_change
|
||
}
|
||
|
||
read_arguments() {
|
||
kind='' summary='' commit='' breaking='' notes='' details=()
|
||
while [ $# -gt 0 ]; do
|
||
case "$1" in
|
||
--notes) notes=true ;;
|
||
--breaking) breaking=true ;;
|
||
--type | --summary | --detail | --commit)
|
||
[ $# -ge 2 ] || die "$1 has no value, expected one"
|
||
case "$1" in
|
||
--type) kind="$2" ;;
|
||
--summary) summary="$2" ;;
|
||
--detail) details+=("$2") ;;
|
||
--commit) commit="$2" ;;
|
||
esac
|
||
shift
|
||
;;
|
||
*) die "'$1' is not an option, expected --type, --summary, --detail, --breaking, --commit or --notes" ;;
|
||
esac
|
||
shift
|
||
done
|
||
}
|
||
|
||
ask_questions() {
|
||
local selected detail
|
||
[ -t 0 ] || die "--type and --summary are required without a terminal"
|
||
if [ -z "$kind" ]; then
|
||
heading "Select the type"
|
||
selected="$(menu 0 "${TYPES[@]}")"
|
||
kind="${TYPES[$selected]}"
|
||
fi
|
||
if [ -z "$summary" ]; then
|
||
heading "Summary, one sentence with the heading"
|
||
printf ' '
|
||
read -r summary
|
||
fi
|
||
heading "Details, an empty line ends them"
|
||
while printf ' - ' && read -r detail && [ -n "$detail" ]; do
|
||
details+=("$detail")
|
||
done
|
||
if confirm "Breaking change?"; then
|
||
breaking=true
|
||
fi
|
||
printf '\n'
|
||
}
|
||
|
||
write_change() {
|
||
local path detail
|
||
printf '%s\n' "${TYPES[@]}" | grep -qx -- "$kind" || die "the type is '$kind', expected ${TYPES[*]}"
|
||
[ -n "$summary" ] || die "the summary is empty, expected one sentence"
|
||
if [ -n "$commit" ]; then
|
||
git rev-parse --verify --quiet "$commit^{commit}" > /dev/null || die "$commit is not a commit of this repository"
|
||
commit="$(git rev-parse --short "$commit")"
|
||
fi
|
||
mkdir -p "$CHANGE_DIRECTORY"
|
||
path="$CHANGE_DIRECTORY/$(file_name).md"
|
||
{
|
||
printf -- '---\ntype: %s\n' "$kind"
|
||
[ -z "$breaking" ] || printf 'breaking: true\n'
|
||
[ -z "$commit" ] || printf 'commit: %s\n' "$commit"
|
||
printf -- '---\n%s\n' "$summary"
|
||
[ "${#details[@]}" = 0 ] || printf '\n'
|
||
for detail in ${details[@]+"${details[@]}"}; do
|
||
printf -- '- %s\n' "$detail"
|
||
done
|
||
} > "$path"
|
||
ok "$path describes the $BOLD$kind$RESET change"
|
||
}
|
||
|
||
print_notes() {
|
||
local section entries
|
||
compgen -G "$CHANGE_DIRECTORY/*.md" > /dev/null || die "$CHANGE_DIRECTORY has no change files"
|
||
for section in "${SECTIONS[@]}"; do
|
||
entries="$(awk -v section="$section" '
|
||
FNR == 1 { fences = 0; type = ""; breaking = 0 }
|
||
fences < 2 {
|
||
if ($0 == "---") fences++
|
||
else if ($1 == "type:") type = $2
|
||
else if ($0 == "breaking: true") breaking = 1
|
||
next
|
||
}
|
||
NF && tolower(section) == (breaking ? "breaking changes" : type) {
|
||
prefix = /^- / ? " " : "- "
|
||
print prefix $0
|
||
}
|
||
' "$CHANGE_DIRECTORY"/*.md)"
|
||
[ -z "$entries" ] || printf '### %s\n\n%s\n\n' "$section" "$entries"
|
||
done
|
||
}
|
||
|
||
file_name() {
|
||
local stem name number=2
|
||
stem="$(printf '%s' "$summary" | LC_ALL=C tr -cs '[:alnum:]' '\n' | LC_ALL=C tr '[:upper:]' '[:lower:]' | awk 'NF && ++count <= 6' | paste -sd - -)"
|
||
name="$stem"
|
||
while [ -e "$CHANGE_DIRECTORY/$name.md" ]; do
|
||
name="$stem-$number"
|
||
number=$((number + 1))
|
||
done
|
||
echo "$name"
|
||
}
|
||
|
||
menu() {
|
||
local selected="$1" options key i
|
||
shift
|
||
options=("$@")
|
||
tput civis >&2 2> /dev/null || true
|
||
while true; do
|
||
for i in "${!options[@]}"; do
|
||
if [ "$i" = "$selected" ]; then
|
||
printf ' %s❯ %s%s\n' "$GREEN" "${options[$i]}" "$RESET" >&2
|
||
else
|
||
printf ' %s %s%s\n' "$DIM" "${options[$i]}" "$RESET" >&2
|
||
fi
|
||
done
|
||
IFS= read -rsn1 key
|
||
if [ "$key" = $'\x1b' ]; then
|
||
IFS= read -rsn2 key
|
||
if [ "$key" = '[A' ] && [ "$selected" -gt 0 ]; then
|
||
selected=$((selected - 1))
|
||
elif [ "$key" = '[B' ] && [ "$selected" -lt $((${#options[@]} - 1)) ]; then
|
||
selected=$((selected + 1))
|
||
fi
|
||
elif [ -z "$key" ]; then
|
||
break
|
||
fi
|
||
printf '\033[%dA\033[J' "${#options[@]}" >&2
|
||
done
|
||
tput cnorm >&2 2> /dev/null || true
|
||
echo "$selected"
|
||
}
|
||
|
||
main "$@"
|