返回 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 cat >"${root}/web/scripts/derive-changelog.mjs" <<'EOF'
172 import { writeFileSync } from "node:fs";
173
174 writeFileSync(`${process.env.PREPARE_RELEASE_TEST_MARKERS}/derive-changelog`, "");
175 EOF
176
177 chmod +x \
178 "${root}/bin/cargo" \
179 "${root}/scripts/release/prepare-release.sh" \
180 "${root}/scripts/release/check-ohos-deps.sh" \
181 "${root}/scripts/release/check-versions.sh" \
182 "${root}/scripts/sync-changelog.sh"
183 }
184
185 success_root="${tmp_dir}/success"
186 success_markers="${tmp_dir}/success-markers"
187 make_fixture "${success_root}"
188 mkdir -p "${success_markers}"
189 printf 'Install from a release tag: --tag v0.8.68\n' >"${success_root}/README.md"
190
191 PREPARE_RELEASE_TEST_MARKERS="${success_markers}" \
192 PATH="${success_root}/bin:${PATH}" \
193 "${success_root}/scripts/release/prepare-release.sh" 0.9.0 >/dev/null
194
195 grep -Fq 'version = "0.9.0"' "${success_root}/Cargo.toml"
196 grep -Fq 'version = "0.9.0"' "${success_root}/crates/example/Cargo.toml"
197 grep -Fq '"version": "0.9.0"' "${success_root}/npm/codewhale/package.json"
198 grep -Fq '"codewhaleBinaryVersion": "0.9.0"' \
199 "${success_root}/npm/codewhale/package.json"
200 grep -Fq '"version": "0.9.0"' "${success_root}/npm/runtime-sdk/package.json"
201 grep -Fq '"version": "0.9.0"' "${success_root}/extensions/vscode/package.json"
202 [[ "$(grep -Fc '"version": "0.9.0"' "${success_root}/extensions/vscode/package-lock.json")" == "2" ]]
203 [[ "$(grep -Fc '"version": "0.9.0"' "${success_root}/package-lock.json")" == "2" ]]
204 grep -Fq 'RELEASE_TAG="${RELEASE_TAG:-v0.9.0}"' \
205 "${success_root}/scripts/remote-smoke/setup-vm.sh"
206 grep -Fq '"sourceCandidate": { "version": "0.9.0" }' \
207 "${success_root}/docs/public-surface-facts.json"
208 grep -Fq '"tag": "v0.8.67"' \
209 "${success_root}/docs/public-surface-facts.json"
210 grep -Fq '"sourceVersion": "0.8.67"' \
211 "${success_root}/docs/public-surface-facts.json"
212 grep -Fq 'prints the published version that was installed' \
213 "${success_root}/docs/INSTALL.md"
214 grep -Fq -- '--tag v0.9.0' "${success_root}/README.md"
215 if grep -R -E -- '--tag v[0-9]+\.[0-9]+\.[0-9]+' \
216 "${success_root}/README.zh-CN.md" \
217 "${success_root}/README.ja-JP.md" \
218 "${success_root}/README.vi.md" \
219 "${success_root}/README.ko-KR.md"; then
220 echo "tag-free localized README unexpectedly gained a release tag" >&2
221 exit 1
222 fi
223 for marker in cargo sync-changelog derive-facts derive-changelog check-versions check-ohos-deps; do
224 [[ -f "${success_markers}/${marker}" ]] || {
225 echo "prepare-release did not reach ${marker}" >&2
226 exit 1
227 }
228 done
229
230 same_root="${tmp_dir}/same"
231 same_markers="${tmp_dir}/same-markers"
232 same_log="${tmp_dir}/same.log"
233 make_fixture "${same_root}"
234 mkdir -p "${same_markers}"
235 cat >"${same_root}/CHANGELOG.md" <<'EOF'
236 ## [Unreleased]
237
238 ## [0.8.68] - 2026-07-18
239
240 ### Changed
241
242 - Test already-prepared release.
243 EOF
244
245 PREPARE_RELEASE_TEST_MARKERS="${same_markers}" \
246 PATH="${same_root}/bin:${PATH}" \
247 "${same_root}/scripts/release/prepare-release.sh" 0.8.68 \
248 >"${same_log}"
249
250 grep -Fq \
251 'Workspace is already at 0.8.68; refreshing generated release state and rerunning gates.' \
252 "${same_log}"
253 for marker in sync-changelog derive-facts derive-changelog check-versions check-ohos-deps; do
254 [[ -f "${same_markers}/${marker}" ]] || {
255 echo "same-version prepare-release did not reach ${marker}" >&2
256 exit 1
257 }
258 done
259 if [[ -f "${same_markers}/cargo" ]]; then
260 echo "same-version prepare-release unexpectedly mutated Cargo.lock" >&2
261 exit 1
262 fi
263 grep -Fq 'version = "0.8.68"' "${same_root}/Cargo.toml"
264 grep -Fq 'version = "0.8.68"' "${same_root}/crates/example/Cargo.toml"
265 grep -Fq '"version": "0.8.68"' "${same_root}/npm/codewhale/package.json"
266
267 same_stale_root="${tmp_dir}/same-stale"
268 same_stale_markers="${tmp_dir}/same-stale-markers"
269 same_stale_log="${tmp_dir}/same-stale.log"
270 make_fixture "${same_stale_root}"
271 mkdir -p "${same_stale_markers}"
272 python3 - "${same_stale_root}/docs/public-surface-facts.json" <<'PY'
273 import pathlib, sys
274
275 path = pathlib.Path(sys.argv[1])
276 path.write_text(path.read_text().replace(
277 '"sourceCandidate": { "version": "0.8.68" }',
278 '"sourceCandidate": { "version": "0.8.67" }',
279 ))
280 PY
281
282 if PREPARE_RELEASE_TEST_MARKERS="${same_stale_markers}" \
283 PATH="${same_stale_root}/bin:${PATH}" \
284 "${same_stale_root}/scripts/release/prepare-release.sh" 0.8.68 \
285 >"${same_stale_log}" 2>&1; then
286 echo "same-version stale source candidate unexpectedly passed" >&2
287 exit 1
288 fi
289 grep -Fq \
290 "sourceCandidate.version is '0.8.67'; expected '0.8.68'" \
291 "${same_stale_log}"
292 if find "${same_stale_markers}" -type f -print -quit | grep -q .; then
293 echo "same-version stale source candidate reached downstream release steps" >&2
294 exit 1
295 fi
296
297 legacy_root="${tmp_dir}/legacy"
298 legacy_markers="${tmp_dir}/legacy-markers"
299 make_fixture "${legacy_root}"
300 mkdir -p "${legacy_markers}"
301 cat >"${legacy_root}/docs/INSTALL.md" <<'EOF'
302 The npm wrapper is published at v0.8.68.
303 codewhale --version # 0.8.68
304 EOF
305
306 PREPARE_RELEASE_TEST_MARKERS="${legacy_markers}" \
307 PATH="${legacy_root}/bin:${PATH}" \
308 "${legacy_root}/scripts/release/prepare-release.sh" 0.9.0 >/dev/null
309
310 grep -Fq 'wrapper is published at v0.9.0' "${legacy_root}/docs/INSTALL.md"
311 grep -Fq 'codewhale --version # 0.9.0' "${legacy_root}/docs/INSTALL.md"
312
313 stale_root="${tmp_dir}/stale"
314 stale_markers="${tmp_dir}/stale-markers"
315 stale_log="${tmp_dir}/stale.log"
316 make_fixture "${stale_root}"
317 mkdir -p "${stale_markers}"
318 printf 'Stale install example: --tag v0.8.67\n' >"${stale_root}/README.ja-JP.md"
319
320 if PREPARE_RELEASE_TEST_MARKERS="${stale_markers}" \
321 PATH="${stale_root}/bin:${PATH}" \
322 "${stale_root}/scripts/release/prepare-release.sh" 0.9.0 \
323 >"${stale_log}" 2>&1; then
324 echo "stale README release tag unexpectedly passed" >&2
325 exit 1
326 fi
327
328 grep -Fq \
329 'README.ja-JP.md has release tag version(s) 0.8.67; expected 0.8.68' \
330 "${stale_log}"
331 grep -Fq 'version = "0.8.68"' "${stale_root}/Cargo.toml"
332 grep -Fq 'version = "0.8.68"' "${stale_root}/crates/example/Cargo.toml"
333 grep -Fq '"version": "0.8.68"' "${stale_root}/npm/codewhale/package.json"
334 if find "${stale_markers}" -type f -print -quit | grep -q .; then
335 echo "stale README validation mutated downstream release state" >&2
336 exit 1
337 fi
338
339 # A downstream command failure must restore every release-bearing file, not
340 # strand the fixture after the text mutation phase.
341 failure_root="${tmp_dir}/failure"
342 failure_snapshot="${tmp_dir}/failure-before"
343 failure_markers="${tmp_dir}/failure-markers"
344 failure_log="${tmp_dir}/failure.log"
345 make_fixture "${failure_root}"
346 mkdir -p "${failure_markers}"
347 cp -R "${failure_root}" "${failure_snapshot}"
348 cat >"${failure_root}/bin/cargo" <<'EOF'
349 #!/usr/bin/env bash
350 set -euo pipefail
351 exit 42
352 EOF
353 chmod +x "${failure_root}/bin/cargo"
354 # Snapshot the intentional failing cargo shim too; only release preparation's
355 # effects should be absent after rollback.
356 cp "${failure_root}/bin/cargo" "${failure_snapshot}/bin/cargo"
357
358 if PREPARE_RELEASE_TEST_MARKERS="${failure_markers}" \
359 PATH="${failure_root}/bin:${PATH}" \
360 "${failure_root}/scripts/release/prepare-release.sh" 0.9.0 \
361 >"${failure_log}" 2>&1; then
362 echo "failing cargo refresh unexpectedly passed" >&2
363 exit 1
364 fi
365
366 grep -Fq "restored the checkout's pre-run release files" "${failure_log}"
367 diff -ru "${failure_snapshot}" "${failure_root}"
368
369 echo "prepare-release tests passed"
370
370 lines BASH