220 lines
4.6 KiB
Bash
Executable File
220 lines
4.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
IFS=$'\n\t'
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage: scripts/release.sh <version> [options]
|
|
|
|
Options:
|
|
--repo <slug> Gitea repo slug (default: auto-detect)
|
|
--login <name> tea login name (default: tea default)
|
|
--notes <text> Release notes (single line)
|
|
--notes-file <file> Release notes file (applies to all apps)
|
|
--notes-dir <dir> Per-app notes dir; uses <app>.md or common.md
|
|
--no-push Skip git push
|
|
--dry-run Print actions without executing
|
|
-h, --help Show this help
|
|
EOF
|
|
}
|
|
|
|
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
usage
|
|
exit 0
|
|
fi
|
|
|
|
version="${1:-}"
|
|
shift || true
|
|
|
|
if [[ -z "$version" ]]; then
|
|
usage
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
echo "error: version must be semver (e.g., 1.2.3)"
|
|
exit 1
|
|
fi
|
|
|
|
repo=""
|
|
login=""
|
|
notes=""
|
|
notes_file=""
|
|
notes_dir=""
|
|
no_push=0
|
|
dry_run=0
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--repo)
|
|
repo="$2"
|
|
shift 2
|
|
;;
|
|
--login)
|
|
login="$2"
|
|
shift 2
|
|
;;
|
|
--notes)
|
|
notes="$2"
|
|
shift 2
|
|
;;
|
|
--notes-file)
|
|
notes_file="$2"
|
|
shift 2
|
|
;;
|
|
--notes-dir)
|
|
notes_dir="$2"
|
|
shift 2
|
|
;;
|
|
--no-push)
|
|
no_push=1
|
|
shift
|
|
;;
|
|
--dry-run)
|
|
dry_run=1
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "error: unknown option: $1"
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
run() {
|
|
if (( dry_run )); then
|
|
echo "+ $*"
|
|
else
|
|
"$@"
|
|
fi
|
|
}
|
|
|
|
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
echo "error: not inside a git repository"
|
|
exit 1
|
|
fi
|
|
|
|
if ! git diff --quiet || ! git diff --cached --quiet; then
|
|
echo "error: working tree not clean; commit or stash first"
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v tea >/dev/null 2>&1; then
|
|
echo "error: tea is not installed or not on PATH"
|
|
exit 1
|
|
fi
|
|
|
|
mapfile -t apps < <(python - <<'PY'
|
|
from pathlib import Path
|
|
apps = sorted({p.parent.name for p in Path("templates").rglob("meta.yaml")})
|
|
print("\n".join(apps))
|
|
PY
|
|
)
|
|
|
|
changed_apps=()
|
|
for app in "${apps[@]}"; do
|
|
last_tag="$(git tag --list "${app}-v*" --sort=-version:refname | head -n 1)"
|
|
if [[ -z "$last_tag" ]]; then
|
|
changed_apps+=("$app")
|
|
continue
|
|
fi
|
|
if git diff --name-only "${last_tag}..HEAD" | grep -Eq "^(src/|build.py$|templates/${app}/)"; then
|
|
changed_apps+=("$app")
|
|
fi
|
|
done
|
|
|
|
if (( ${#changed_apps[@]} == 0 )); then
|
|
echo "no app changes detected since last per-app tags; nothing to release"
|
|
exit 0
|
|
fi
|
|
|
|
if (( dry_run )); then
|
|
echo "+ update meta.yaml for: ${changed_apps[*]}"
|
|
else
|
|
APP_LIST="$(printf "%s\n" "${changed_apps[@]}")" APP_VERSION="${version}" python - <<'PY'
|
|
import os
|
|
from pathlib import Path
|
|
|
|
new_version = os.environ["APP_VERSION"]
|
|
targets = set(filter(None, os.environ["APP_LIST"].splitlines()))
|
|
for path in Path("templates").rglob("meta.yaml"):
|
|
if path.parent.name not in targets:
|
|
continue
|
|
text = path.read_text()
|
|
lines = text.splitlines()
|
|
out = []
|
|
changed = False
|
|
for line in lines:
|
|
if line.startswith("version:"):
|
|
out.append(f"version: {new_version}")
|
|
changed = True
|
|
else:
|
|
out.append(line)
|
|
if changed:
|
|
path.write_text("\\n".join(out) + "\\n")
|
|
PY
|
|
fi
|
|
|
|
if ! git diff --quiet; then
|
|
run git add templates/*/meta.yaml
|
|
run git commit -m "chore: release apps v${version}"
|
|
fi
|
|
|
|
run uv run build.py
|
|
run mkdir -p dist/releases
|
|
|
|
for app in "${changed_apps[@]}"; do
|
|
run tar -czf "dist/releases/apex-${app}-v${version}.tar.gz" -C dist "$app"
|
|
done
|
|
|
|
if (( no_push == 0 )); then
|
|
run git push origin HEAD
|
|
fi
|
|
|
|
commit="$(git rev-parse HEAD)"
|
|
|
|
note_args=()
|
|
pick_note_args() {
|
|
local app="$1"
|
|
note_args=()
|
|
if [[ -n "$notes_file" ]]; then
|
|
note_args=(--note-file "$notes_file")
|
|
return
|
|
fi
|
|
if [[ -n "$notes" ]]; then
|
|
note_args=(--note "$notes")
|
|
return
|
|
fi
|
|
if [[ -n "$notes_dir" ]]; then
|
|
if [[ -f "$notes_dir/$app.md" ]]; then
|
|
note_args=(--note-file "$notes_dir/$app.md")
|
|
return
|
|
fi
|
|
if [[ -f "$notes_dir/common.md" ]]; then
|
|
note_args=(--note-file "$notes_dir/common.md")
|
|
return
|
|
fi
|
|
fi
|
|
note_args=(--note "Release v${version}")
|
|
}
|
|
|
|
for app in "${changed_apps[@]}"; do
|
|
tag="${app}-v${version}"
|
|
title="Apex ${app} v${version}"
|
|
asset="dist/releases/apex-${app}-v${version}.tar.gz"
|
|
|
|
args=(tea releases create)
|
|
[[ -n "$repo" ]] && args+=(--repo "$repo")
|
|
[[ -n "$login" ]] && args+=(--login "$login")
|
|
args+=(--tag "$tag" --target "$commit" --title "$title")
|
|
pick_note_args "$app"
|
|
args+=("${note_args[@]}" --asset "$asset")
|
|
|
|
run "${args[@]}"
|
|
done
|