返回 CodeWhale
verify-release-assets.sh
根目录 / scripts / release / verify-release-assets.sh
1 #!/usr/bin/env bash
2 set -euo pipefail
3
4 script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5 repo_root="$(cd "${script_dir}/../.." && pwd)"
6
7 usage() {
8 cat <<'EOF'
9 usage: scripts/release/verify-release-assets.sh [--allow-npm-binary-mismatch] [VERSION]
10
11 Proves the public GitHub Release assets for VERSION were built from the same
12 tag commit that will be published to Cargo/npm.
13
14 Checks:
15 - local tag vVERSION exists
16 - remote tag vVERSION resolves to the same commit SHA
17 - GitHub Release vVERSION exists
18 - a successful asset-publishing job in a Release workflow run used that SHA
19 - npm/codewhale release:check sees the fresh binary/archive/installer matrix
20 and both required checksum manifests
21
22 Set GH_BIN=/path/to/gh to choose a GitHub CLI binary. Set
23 CODEWHALE_GITHUB_REPO=owner/repo or CODEWHALE_RELEASE_REMOTE=remote to override
24 the default Hmbown/CodeWhale origin check.
25 EOF
26 }
27
28 allow_npm_binary_mismatch=0
29 version=""
30
31 while (($# > 0)); do
32 case "$1" in
33 --allow-npm-binary-mismatch)
34 allow_npm_binary_mismatch=1
35 ;;
36 -h|--help)
37 usage
38 exit 0
39 ;;
40 *)
41 if [[ -n "${version}" ]]; then
42 usage >&2
43 exit 2
44 fi
45 version="$1"
46 ;;
47 esac
48 shift
49 done
50
51 cd "${repo_root}"
52
53 if [[ -z "${version}" ]]; then
54 version="$(grep -E '^version = "' Cargo.toml | head -n1 | sed -E 's/^version = "([^"]+)".*/\1/')"
55 fi
56 version="${version#v}"
57 tag="v${version}"
58
59 if [[ -z "${version}" ]]; then
60 echo "Could not determine release version." >&2
61 exit 1
62 fi
63
64 repo="${CODEWHALE_GITHUB_REPO:-Hmbown/CodeWhale}"
65 remote="${CODEWHALE_RELEASE_REMOTE:-origin}"
66 gh_bin="${GH_BIN:-gh}"
67
68 if ! command -v "${gh_bin}" >/dev/null 2>&1; then
69 echo "GitHub CLI not found: ${gh_bin}" >&2
70 echo "Install gh or set GH_BIN=/path/to/gh." >&2
71 exit 1
72 fi
73
74 local_sha="$(git rev-list -n 1 "${tag}" 2>/dev/null || true)"
75 if [[ -z "${local_sha}" ]]; then
76 echo "Local tag ${tag} does not exist." >&2
77 exit 1
78 fi
79
80 remote_sha="$(git ls-remote --tags "${remote}" "refs/tags/${tag}^{}" | awk 'NR == 1 {print $1}')"
81 if [[ -z "${remote_sha}" ]]; then
82 remote_sha="$(git ls-remote --tags "${remote}" "refs/tags/${tag}" | awk 'NR == 1 {print $1}')"
83 fi
84 if [[ -z "${remote_sha}" ]]; then
85 echo "Remote tag ${tag} does not exist on ${remote}." >&2
86 exit 1
87 fi
88 if [[ "${local_sha}" != "${remote_sha}" ]]; then
89 echo "Tag SHA mismatch for ${tag}:" >&2
90 echo " local : ${local_sha}" >&2
91 echo " remote: ${remote_sha}" >&2
92 exit 1
93 fi
94 echo "Tag check OK: ${tag} -> ${local_sha}"
95
96 release_url="$("${gh_bin}" release view "${tag}" --repo "${repo}" --json url --jq '.url')"
97 if [[ -z "${release_url}" ]]; then
98 echo "GitHub Release ${tag} was not found in ${repo}." >&2
99 exit 1
100 fi
101 echo "GitHub Release OK: ${release_url}"
102
103 run_candidates="$(
104 TAG_SHA="${local_sha}" "${gh_bin}" run list \
105 --repo "${repo}" \
106 --workflow "Release" \
107 --limit 100 \
108 --json databaseId,headSha,headBranch,event,conclusion,status,createdAt,updatedAt,url \
109 --jq 'map(select(.headSha == env.TAG_SHA and (.event == "push" or .event == "workflow_dispatch"))) | sort_by(.updatedAt) | reverse | .[] | "\(.databaseId)\t\(.headBranch)\t\(.event)\t\(.url)"'
110 )"
111
112 run_summary=""
113 while IFS=$'\t' read -r run_id head_branch run_event run_url; do
114 if [[ -z "${run_id}" ]]; then
115 continue
116 fi
117
118 release_job_id="$(
119 "${gh_bin}" run view "${run_id}" \
120 --repo "${repo}" \
121 --json jobs \
122 --jq '.jobs[] | select(.name == "release" and .conclusion == "success") | .databaseId' \
123 | head -n 1
124 )"
125 # Freshness vs the release job's own started_at (not the run-level
126 # run_started_at, which job-level reruns bump past the uploads) is enforced
127 # in npm/codewhale/scripts/verify-release-assets.js — see #5429. This check
128 # only proves a successful release job exists for the tag SHA.
129 if [[ -n "${release_job_id}" ]]; then
130 run_summary="${run_id}\t${head_branch}\t${run_event}\t${run_url}\trelease job ${release_job_id}"
131 break
132 fi
133 done <<<"${run_candidates}"
134
135 if [[ -z "${run_summary}" ]]; then
136 echo "No successful asset-publishing job found in the last 100 Release workflow runs for ${tag} at ${local_sha}." >&2
137 echo "Rerun the Release workflow before publishing Cargo/npm." >&2
138 exit 1
139 fi
140 printf 'Release asset job OK: %b\n' "${run_summary}"
141
142 npm_package_version="$(node -p "require('./npm/codewhale/package.json').version")"
143 npm_binary_version="$(
144 node -p "const p=require('./npm/codewhale/package.json'); p.codewhaleBinaryVersion || p.deepseekBinaryVersion || p.version"
145 )"
146 if [[ "${npm_package_version}" != "${version}" ]]; then
147 echo "npm/codewhale package version ${npm_package_version} does not match ${version}." >&2
148 exit 1
149 fi
150 if [[ "${npm_binary_version}" != "${version}" && "${allow_npm_binary_mismatch}" != "1" ]]; then
151 echo "npm/codewhale codewhaleBinaryVersion ${npm_binary_version} does not match ${version}." >&2
152 echo "Use --allow-npm-binary-mismatch only for an intentional packaging-only npm release." >&2
153 exit 1
154 fi
155
156 (
157 cd npm/codewhale
158 env \
159 -u CODEWHALE_RELEASE_BASE_URL \
160 -u DEEPSEEK_TUI_RELEASE_BASE_URL \
161 -u DEEPSEEK_RELEASE_BASE_URL \
162 -u CODEWHALE_USE_CNB_MIRROR \
163 DEEPSEEK_TUI_VERSION="${version}" \
164 DEEPSEEK_TUI_GITHUB_REPO="${repo}" \
165 CODEWHALE_ALLOW_NPM_BINARY_MISMATCH="${allow_npm_binary_mismatch}" \
166 npm run release:check
167 )
168
169 echo "Release asset gate OK: ${tag} assets match ${local_sha} and npm/codewhale is ready for publish."
170
170 lines BASH