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