返回 CodeWhale
prepare-release.test.sh
根目录 / scripts / release / prepare-release.test.sh
1 #!/usr/bin/env bash
2 set -euo pipefail
3
4 repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
5 tmp_dir="$(mktemp -d)"
6 trap 'rm -rf "${tmp_dir}"' EXIT
7
8 make_fixture() {
9 local root="$1"
10 mkdir -p \
11 "${root}/bin" \
12 "${root}/crates/example" \
13 "${root}/crates/tui" \
14 "${root}/docs" \
15 "${root}/extensions/vscode" \
16 "${root}/npm/codewhale" \
17 "${root}/npm/runtime-sdk" \
18 "${root}/scripts/remote-smoke" \
19 "${root}/scripts/release" \
20 "${root}/web/lib" \
21 "${root}/web/scripts"
22
23 cp "${repo_root}/scripts/release/prepare-release.sh" \
24 "${root}/scripts/release/prepare-release.sh"
25
26 cat >"${root}/Cargo.toml" <<'EOF'
27 [workspace]
28 members = ["crates/example"]
29
30 [workspace.package]
31 version = "0.8.68"
32 EOF
33
34 cat >"${root}/crates/example/Cargo.toml" <<'EOF'
35 [package]
36 name = "codewhale-example"
37 version.workspace = true
38
39 [dependencies]
40 codewhale-core = { path = "../core", version = "0.8.68" }
41 EOF
42
43 cat >"${root}/npm/codewhale/package.json" <<'EOF'
44 {
45 "name": "codewhale",
46 "version": "0.8.68",
47 "codewhaleBinaryVersion": "0.8.68"
48 }
49 EOF
50
51 cat >"${root}/npm/runtime-sdk/package.json" <<'EOF'
52 {
53 "name": "@codewhale/runtime-sdk",
54 "version": "0.8.68"
55 }
56 EOF
57
58 cat >"${root}/extensions/vscode/package.json" <<'EOF'
59 {
60 "name": "codewhale-vscode",
61 "version": "0.8.68"
62 }
63 EOF
64
65 cat >"${root}/extensions/vscode/package-lock.json" <<'EOF'
66 {
67 "name": "codewhale-vscode",
68 "version": "0.8.68",
69 "lockfileVersion": 3,
70 "packages": {
71 "": {
72 "name": "codewhale-vscode",
73 "version": "0.8.68"
74 }
75 }
76 }
77 EOF
78
79 cat >"${root}/package-lock.json" <<'EOF'
80 {
81 "name": "fixture",
82 "lockfileVersion": 3,
83 "packages": {
84 "": { "name": "fixture" },
85 "npm/codewhale": {
86 "version": "0.8.68",
87 "license": "MIT"
88 },
89 "npm/runtime-sdk": {
90 "version": "0.8.68",
91 "license": "MIT"
92 }
93 }
94 }
95 EOF
96
97 cat >"${root}/Cargo.lock" <<'EOF'
98 # fixture lock
99 EOF
100
101 cat >"${root}/CHANGELOG.md" <<'EOF'
102 ## [Unreleased]
103
104 ## [0.9.0] - 2026-07-15
105
106 ### Changed
107
108 - Test release.
109 EOF
110
111 cat >"${root}/docs/INSTALL.md" <<'EOF'
112 The npm wrapper installs the matching published binaries.
113
114 codewhale --version # prints the published version that was installed
115 EOF
116
117 cat >"${root}/docs/public-surface-facts.json" <<'EOF'
118 {
119 "sourceCandidate": { "version": "0.8.68" },
120 "latestPublishedRelease": {
121 "tag": "v0.8.67",
122 "version": "0.8.67",
123 "url": "https://example.invalid/v0.8.67"
124 },
125 "screenshot": { "sourceVersion": "0.8.67" }
126 }
127 EOF
128
129 cat >"${root}/scripts/remote-smoke/setup-vm.sh" <<'EOF'
130 #!/usr/bin/env bash
131 RELEASE_TAG="${RELEASE_TAG:-v0.8.68}"
132 EOF
133
134 printf 'fixture packaged changelog\n' >"${root}/crates/tui/CHANGELOG.md"
135 printf 'fixture generated facts\n' >"${root}/web/lib/facts.generated.ts"
136
137 for readme in README.md README.zh-CN.md README.ja-JP.md README.vi.md README.ko-KR.md; do
138 printf 'Install Codewhale from the package manager.\n' >"${root}/${readme}"
139 done
140
141 cat >"${root}/bin/cargo" <<'EOF'
142 #!/usr/bin/env bash
143 set -euo pipefail
144 : >"${PREPARE_RELEASE_TEST_MARKERS}/cargo"
145 EOF
146
147 cat >"${root}/scripts/sync-changelog.sh" <<'EOF'
148 #!/usr/bin/env bash
149 set -euo pipefail
150 : >"${PREPARE_RELEASE_TEST_MARKERS}/sync-changelog"
151 EOF
152
153 cat >"${root}/scripts/release/check-versions.sh" <<'EOF'
154 #!/usr/bin/env bash
155 set -euo pipefail
156 : >"${PREPARE_RELEASE_TEST_MARKERS}/check-versions"
157 EOF
158
159 cat >"${root}/scripts/release/check-ohos-deps.sh" <<'EOF'
160 #!/usr/bin/env bash
161 set -euo pipefail
162 : >"${PREPARE_RELEASE_TEST_MARKERS}/check-ohos-deps"
163 EOF
164
165 cat >"${root}/web/scripts/derive-facts.mjs" <<'EOF'
166 import { writeFileSync } from "node:fs";
167
168 writeFileSync(`${process.env.PREPARE_RELEASE_TEST_MARKERS}/derive-facts`, "");
169 EOF
170
171 chmod +x \
172 "${root}/bin/cargo" \
173 "${root}/scripts/release/prepare-release.sh" \
174 "${root}/scripts/release/check-ohos-deps.sh" \
175 "${root}/scripts/release/check-versions.sh" \
176 "${root}/scripts/sync-changelog.sh"
177 }
178
179 success_root="${tmp_dir}/success"
180 success_markers="${tmp_dir}/success-markers"
181 make_fixture "${success_root}"
182 mkdir -p "${success_markers}"
183 printf 'Install from a release tag: --tag v0.8.68\n' >"${success_root}/README.md"
184
185 PREPARE_RELEASE_TEST_MARKERS="${success_markers}" \
186 PATH="${success_root}/bin:${PATH}" \
187 "${success_root}/scripts/release/prepare-release.sh" 0.9.0 >/dev/null
188
189 grep -Fq 'version = "0.9.0"' "${success_root}/Cargo.toml"
190 grep -Fq 'version = "0.9.0"' "${success_root}/crates/example/Cargo.toml"
191 grep -Fq '"version": "0.9.0"' "${success_root}/npm/codewhale/package.json"
192 grep -Fq '"codewhaleBinaryVersion": "0.9.0"' \
193 "${success_root}/npm/codewhale/package.json"
194 grep -Fq '"version": "0.9.0"' "${success_root}/npm/runtime-sdk/package.json"
195 grep -Fq '"version": "0.9.0"' "${success_root}/extensions/vscode/package.json"
196 [[ "$(grep -Fc '"version": "0.9.0"' "${success_root}/extensions/vscode/package-lock.json")" == "2" ]]
197 [[ "$(grep -Fc '"version": "0.9.0"' "${success_root}/package-lock.json")" == "2" ]]
198 grep -Fq 'RELEASE_TAG="${RELEASE_TAG:-v0.9.0}"' \
199 "${success_root}/scripts/remote-smoke/setup-vm.sh"
200 grep -Fq '"sourceCandidate": { "version": "0.9.0" }' \
201 "${success_root}/docs/public-surface-facts.json"
202 grep -Fq '"tag": "v0.8.67"' \
203 "${success_root}/docs/public-surface-facts.json"
204 grep -Fq '"sourceVersion": "0.8.67"' \
205 "${success_root}/docs/public-surface-facts.json"
206 grep -Fq 'prints the published version that was installed' \
207 "${success_root}/docs/INSTALL.md"
208 grep -Fq -- '--tag v0.9.0' "${success_root}/README.md"
209 if grep -R -E -- '--tag v[0-9]+\.[0-9]+\.[0-9]+' \
210 "${success_root}/README.zh-CN.md" \
211 "${success_root}/README.ja-JP.md" \
212 "${success_root}/README.vi.md" \
213 "${success_root}/README.ko-KR.md"; then
214 echo "tag-free localized README unexpectedly gained a release tag" >&2
215 exit 1
216 fi
217 for marker in cargo sync-changelog derive-facts check-versions check-ohos-deps; do
218 [[ -f "${success_markers}/${marker}" ]] || {
219 echo "prepare-release did not reach ${marker}" >&2
220 exit 1
221 }
222 done
223
224 same_root="${tmp_dir}/same"
225 same_markers="${tmp_dir}/same-markers"
226 same_log="${tmp_dir}/same.log"
227 make_fixture "${same_root}"
228 mkdir -p "${same_markers}"
229 cat >"${same_root}/CHANGELOG.md" <<'EOF'
230 ## [Unreleased]
231
232 ## [0.8.68] - 2026-07-18
233
234 ### Changed
235
236 - Test already-prepared release.
237 EOF
238
239 PREPARE_RELEASE_TEST_MARKERS="${same_markers}" \
240 PATH="${same_root}/bin:${PATH}" \
241 "${same_root}/scripts/release/prepare-release.sh" 0.8.68 \
242 >"${same_log}"
243
244 grep -Fq \
245 'Workspace is already at 0.8.68; refreshing generated release state and rerunning gates.' \
246 "${same_log}"
247 for marker in sync-changelog derive-facts check-versions check-ohos-deps; do
248 [[ -f "${same_markers}/${marker}" ]] || {
249 echo "same-version prepare-release did not reach ${marker}" >&2
250 exit 1
251 }
252 done
253 if [[ -f "${same_markers}/cargo" ]]; then
254 echo "same-version prepare-release unexpectedly mutated Cargo.lock" >&2
255 exit 1
256 fi
257 grep -Fq 'version = "0.8.68"' "${same_root}/Cargo.toml"
258 grep -Fq 'version = "0.8.68"' "${same_root}/crates/example/Cargo.toml"
259 grep -Fq '"version": "0.8.68"' "${same_root}/npm/codewhale/package.json"
260
261 same_stale_root="${tmp_dir}/same-stale"
262 same_stale_markers="${tmp_dir}/same-stale-markers"
263 same_stale_log="${tmp_dir}/same-stale.log"
264 make_fixture "${same_stale_root}"
265 mkdir -p "${same_stale_markers}"
266 python3 - "${same_stale_root}/docs/public-surface-facts.json" <<'PY'
267 import pathlib, sys
268
269 path = pathlib.Path(sys.argv[1])
270 path.write_text(path.read_text().replace(
271 '"sourceCandidate": { "version": "0.8.68" }',
272 '"sourceCandidate": { "version": "0.8.67" }',
273 ))
274 PY
275
276 if PREPARE_RELEASE_TEST_MARKERS="${same_stale_markers}" \
277 PATH="${same_stale_root}/bin:${PATH}" \
278 "${same_stale_root}/scripts/release/prepare-release.sh" 0.8.68 \
279 >"${same_stale_log}" 2>&1; then
280 echo "same-version stale source candidate unexpectedly passed" >&2
281 exit 1
282 fi
283 grep -Fq \
284 "sourceCandidate.version is '0.8.67'; expected '0.8.68'" \
285 "${same_stale_log}"
286 if find "${same_stale_markers}" -type f -print -quit | grep -q .; then
287 echo "same-version stale source candidate reached downstream release steps" >&2
288 exit 1
289 fi
290
291 legacy_root="${tmp_dir}/legacy"
292 legacy_markers="${tmp_dir}/legacy-markers"
293 make_fixture "${legacy_root}"
294 mkdir -p "${legacy_markers}"
295 cat >"${legacy_root}/docs/INSTALL.md" <<'EOF'
296 The npm wrapper is published at v0.8.68.
297 codewhale --version # 0.8.68
298 EOF
299
300 PREPARE_RELEASE_TEST_MARKERS="${legacy_markers}" \
301 PATH="${legacy_root}/bin:${PATH}" \
302 "${legacy_root}/scripts/release/prepare-release.sh" 0.9.0 >/dev/null
303
304 grep -Fq 'wrapper is published at v0.9.0' "${legacy_root}/docs/INSTALL.md"
305 grep -Fq 'codewhale --version # 0.9.0' "${legacy_root}/docs/INSTALL.md"
306
307 stale_root="${tmp_dir}/stale"
308 stale_markers="${tmp_dir}/stale-markers"
309 stale_log="${tmp_dir}/stale.log"
310 make_fixture "${stale_root}"
311 mkdir -p "${stale_markers}"
312 printf 'Stale install example: --tag v0.8.67\n' >"${stale_root}/README.ja-JP.md"
313
314 if PREPARE_RELEASE_TEST_MARKERS="${stale_markers}" \
315 PATH="${stale_root}/bin:${PATH}" \
316 "${stale_root}/scripts/release/prepare-release.sh" 0.9.0 \
317 >"${stale_log}" 2>&1; then
318 echo "stale README release tag unexpectedly passed" >&2
319 exit 1
320 fi
321
322 grep -Fq \
323 'README.ja-JP.md has release tag version(s) 0.8.67; expected 0.8.68' \
324 "${stale_log}"
325 grep -Fq 'version = "0.8.68"' "${stale_root}/Cargo.toml"
326 grep -Fq 'version = "0.8.68"' "${stale_root}/crates/example/Cargo.toml"
327 grep -Fq '"version": "0.8.68"' "${stale_root}/npm/codewhale/package.json"
328 if find "${stale_markers}" -type f -print -quit | grep -q .; then
329 echo "stale README validation mutated downstream release state" >&2
330 exit 1
331 fi
332
333 # A downstream command failure must restore every release-bearing file, not
334 # strand the fixture after the text mutation phase.
335 failure_root="${tmp_dir}/failure"
336 failure_snapshot="${tmp_dir}/failure-before"
337 failure_markers="${tmp_dir}/failure-markers"
338 failure_log="${tmp_dir}/failure.log"
339 make_fixture "${failure_root}"
340 mkdir -p "${failure_markers}"
341 cp -R "${failure_root}" "${failure_snapshot}"
342 cat >"${failure_root}/bin/cargo" <<'EOF'
343 #!/usr/bin/env bash
344 set -euo pipefail
345 exit 42
346 EOF
347 chmod +x "${failure_root}/bin/cargo"
348 # Snapshot the intentional failing cargo shim too; only release preparation's
349 # effects should be absent after rollback.
350 cp "${failure_root}/bin/cargo" "${failure_snapshot}/bin/cargo"
351
352 if PREPARE_RELEASE_TEST_MARKERS="${failure_markers}" \
353 PATH="${failure_root}/bin:${PATH}" \
354 "${failure_root}/scripts/release/prepare-release.sh" 0.9.0 \
355 >"${failure_log}" 2>&1; then
356 echo "failing cargo refresh unexpectedly passed" >&2
357 exit 1
358 fi
359
360 grep -Fq "restored the checkout's pre-run release files" "${failure_log}"
361 diff -ru "${failure_snapshot}" "${failure_root}"
362
363 echo "prepare-release tests passed"
364
364 lines BASH