You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
iTi-Flask/scripts/release.sh

203 lines
4.6 KiB
Bash

#!/usr/bin/env sh
set -eu
ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
cd "$ROOT_DIR"
ABOUT_FILE="$ROOT_DIR/iti/__about__.py"
COPIER_FILE="$ROOT_DIR/copier.yml"
README_FILE="$ROOT_DIR/README.md"
die() {
printf '%s\n' "$*" >&2
exit 1
}
parse_version() {
raw=$1
version=${raw#v}
old_ifs=$IFS
IFS=.
set -- $version
IFS=$old_ifs
if [ "$#" -ne 3 ]; then
die "invalid version: $raw"
fi
for part in "$@"; do
case $part in
''|*[!0-9]*)
die "invalid version: $raw"
;;
esac
done
printf '%s %s %s\n' "$1" "$2" "$3"
}
normalize_version() {
set -- $(parse_version "$1")
printf '%s.%s.%s\n' "$1" "$2" "$3"
}
bump_patch() {
set -- $(parse_version "$1")
printf '%s.%s.%s\n' "$1" "$2" "$(( $3 + 1 ))"
}
version_is_newer() {
current_parts=$(parse_version "$1")
set -- $current_parts
current_major=$1
current_minor=$2
current_patch=$3
target_parts=$(parse_version "$2")
set -- $target_parts
target_major=$1
target_minor=$2
target_patch=$3
if [ "$target_major" -gt "$current_major" ]; then
return 0
fi
if [ "$target_major" -lt "$current_major" ]; then
return 1
fi
if [ "$target_minor" -gt "$current_minor" ]; then
return 0
fi
if [ "$target_minor" -lt "$current_minor" ]; then
return 1
fi
[ "$target_patch" -gt "$current_patch" ]
}
read_current_version() {
current_version=$(sed -n 's/^__version__[[:space:]]*=[[:space:]]*"\([^"]*\)".*$/\1/p' "$ABOUT_FILE" | sed -n '1p')
[ -n "$current_version" ] || die "version not found in $ABOUT_FILE"
printf '%s\n' "$current_version"
}
replace_first_exact_line() {
file=$1
old_line=$2
new_line=$3
tmp_file=$(mktemp)
if awk -v old="$old_line" -v new="$new_line" '
BEGIN { done = 0 }
{
if (!done && $0 == old) {
print new
done = 1
} else {
print
}
}
END { if (!done) exit 1 }
' "$file" >"$tmp_file"; then
mv "$tmp_file" "$file"
else
rm -f "$tmp_file"
die "line not updated in $file"
fi
}
write_about_version() {
current_version=$1
target_version=$2
replace_first_exact_line "$ABOUT_FILE" "__version__ = \"$current_version\"" "__version__ = \"$target_version\""
}
write_copier_tag() {
current_version=$1
target_version=$2
tmp_file=$(mktemp)
if awk -v current="$current_version" -v target="$target_version" '
BEGIN { in_framework = 0; done = 0 }
/^framework_tag:[[:space:]]*$/ { in_framework = 1; print; next }
in_framework && /^[^[:space:]]/ { in_framework = 0 }
in_framework && !done && $0 == " default: v" current {
print " default: v" target
done = 1
next
}
{ print }
END { if (!done) exit 1 }
' "$COPIER_FILE" >"$tmp_file"; then
mv "$tmp_file" "$COPIER_FILE"
else
rm -f "$tmp_file"
die "framework_tag default not updated in $COPIER_FILE"
fi
}
write_readme_tag() {
current_version=$1
target_version=$2
old_line=' "iti-flask @ git+https://git.noahlan.cn/iti-framework/iTi-Flask.git@v'"$current_version"'",'
new_line=' "iti-flask @ git+https://git.noahlan.cn/iti-framework/iTi-Flask.git@v'"$target_version"'",'
replace_first_exact_line "$README_FILE" "$old_line" "$new_line"
}
ensure_clean_tree() {
if [ -n "$(git status --porcelain)" ]; then
die "working tree is not clean"
fi
}
ensure_branch() {
if branch=$(git symbolic-ref --quiet --short HEAD 2>/dev/null); then
[ -n "$branch" ] || die "release requires a branch checkout"
printf '%s\n' "$branch"
else
die "release requires a branch checkout"
fi
}
ensure_tag_absent() {
tag=$1
if git rev-parse --verify --quiet "refs/tags/$tag" >/dev/null; then
die "tag already exists: $tag"
fi
}
main() {
version_arg=${1:-}
current_version=$(read_current_version)
if [ -n "$version_arg" ]; then
target_version=$(normalize_version "$version_arg")
else
target_version=$(bump_patch "$current_version")
fi
target_tag="v$target_version"
if [ "$target_version" = "$current_version" ]; then
die "version already set to $target_version"
fi
if ! version_is_newer "$current_version" "$target_version"; then
die "target version must be newer than $current_version"
fi
ensure_clean_tree
branch=$(ensure_branch)
ensure_tag_absent "$target_tag"
uv run pytest -q
write_about_version "$current_version" "$target_version"
write_copier_tag "$current_version" "$target_version"
write_readme_tag "$current_version" "$target_version"
git add iti/__about__.py copier.yml README.md
git commit -m "chore: release $target_tag"
git tag -a "$target_tag" -m "release $target_tag"
git push origin "$branch" "$target_tag"
printf 'released %s\n' "$target_tag"
}
main "$@"