返回 DeepSeek-Reasonix
validate-release-candidate-source.sh
根目录 / scripts / validate-release-candidate-source.sh
1 #!/usr/bin/env bash
2 # Validate an untagged Stable candidate without requiring it to remain the tip
3 # of main-v2 or to be the commit that introduced its reviewed release notes.
4 set -euo pipefail
5
6 if [ "$#" -lt 2 ] || [ "$#" -gt 3 ] || [[ ! "$1" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]] || [[ ! "$2" =~ ^[0-9a-f]{40}$ ]] || [[ ! "${3:-release}" =~ ^(release|rehearsal)$ ]]; then
7 echo "usage: validate-release-candidate-source.sh MAJOR.MINOR.PATCH FULL_COMMIT_SHA [release|rehearsal]" >&2
8 exit 2
9 fi
10
11 version="$1"
12 candidate="$2"
13 remote="${RELEASE_REMOTE:-origin}"
14 purpose="${3:-release}"
15
16 git fetch --quiet "$remote" main-v2 --tags
17 main_sha="$(git rev-parse "$remote/main-v2^{commit}")"
18 git cat-file -e "$candidate^{commit}" 2>/dev/null || {
19 echo "candidate commit is unavailable: $candidate" >&2
20 exit 1
21 }
22 git merge-base --is-ancestor "$candidate" "$main_sha" || {
23 echo "candidate $candidate is not on main-v2 history at $main_sha" >&2
24 exit 1
25 }
26
27 for tag in "v$version" "npm-v$version" "desktop-v$version"; do
28 if [ "$purpose" = release ] && git show-ref --verify --quiet "refs/tags/$tag"; then
29 echo "candidate preparation refuses existing release tag: $tag" >&2
30 exit 1
31 fi
32 done
33
34 catalog="$(mktemp "${TMPDIR:-/tmp}/reasonix-candidate-catalog.XXXXXX")"
35 trap 'rm -f -- "$catalog"' EXIT
36 git show "$candidate:release-notes/releases.json" >"$catalog"
37 jq -e --arg version "$version" '
38 [.releases[] | select(.version == $version and .channel == "stable" and .status == "reviewed")] | length == 1
39 ' "$catalog" >/dev/null || {
40 echo "candidate must contain exactly one reviewed Stable release record for $version" >&2
41 exit 1
42 }
43
44 echo "candidate source verified: version=$version sha=$candidate main=$main_sha"
45
45 lines BASH