fix(cli): compute publish baseline from origin tags only

A failed `git push origin cli-vX.Y.Z` after a successful local tag
leaves an orphan tag locally. The previous `git tag --list` baseline
would then treat it as the latest release, causing skipped versions or
publishes based on an unreleased tag.

Switch to `git ls-remote --tags --refs origin 'cli-v*' | sort -V` so
the baseline reflects only what is actually on origin. Local orphan
tags can still collide with the computed target tag, which fails fast
with a clear message as before.

Adds test 12 covering the orphan-tag scenario.
This commit is contained in:
Cheney 2026-05-19 17:34:32 +08:00
parent 7847e0aa33
commit 239cd2ed1a
3 changed files with 33 additions and 11 deletions

View file

@ -5,7 +5,7 @@
CLI releases use a PR-based flow. Running `make publish-cli` on a clean `main` branch:
1. Runs local build-and-test (lint, typecheck, test, build)
2. Computes the next version from the latest `cli-v*` tag
2. Computes the next version from the latest `cli-v*` tag on `origin`
3. Creates a `release/cli-vX.Y.Z` branch with the version bump committed
4. Pushes the branch and opens a PR to `main`
@ -64,7 +64,7 @@ make publish-cli-major # major: 0.1.5 -> 1.0.0
2. Verify the working tree is clean and on `main`
3. `git pull --ff-only` from `origin/main`
4. Run full local build-and-test (lint, typecheck, test, build)
5. Compute the next version from the latest `cli-v*` tag
5. Compute the next version from the latest `cli-v*` tag on `origin` (via `git ls-remote`, so local orphan tags from a failed `git push origin cli-vX.Y.Z` are ignored)
6. Verify the tag and release branch don't already exist
7. After interactive confirmation: create release branch, commit version bump, push, and open PR

View file

@ -131,26 +131,31 @@ ORIGINAL_BRANCH="$CURRENT_BRANCH"
log_stage "pulling latest from origin/$CURRENT_BRANCH"
git -C "$REPO_ROOT" pull --ff-only origin "$CURRENT_BRANCH"
log_stage "fetching tags from origin"
git -C "$REPO_ROOT" fetch --tags --prune origin
# --- Compute version & pre-flight checks (cheap, run before build) ---
#
# Baseline is read from origin via `ls-remote`, not from local tags. A local
# orphan tag left over from a failed `git push origin cli-vX.Y.Z` must not
# influence the next version — otherwise we'd skip versions or release on top
# of something that was never published.
log_stage "computing next version from latest cli-v* tag"
LATEST_TAG="$(git -C "$REPO_ROOT" tag --list 'cli-v*' --sort=-version:refname | head -n1)"
log_stage "computing next version from latest origin cli-v* tag"
LATEST_TAG="$(git -C "$REPO_ROOT" ls-remote --tags --refs origin 'cli-v*' \
| awk '{sub(/^refs\/tags\//, "", $2); print $2}' \
| sort -V \
| tail -n1)"
if [[ -n "$LATEST_TAG" ]]; then
BASE_VERSION="${LATEST_TAG#cli-v}"
# Reject prerelease tags (e.g., cli-v0.2.0-rc.1). Only pure X.Y.Z is supported.
if [[ "$BASE_VERSION" =~ [^0-9.] ]]; then
echo "latest tag $LATEST_TAG contains prerelease suffix: $BASE_VERSION" >&2
echo "latest origin tag $LATEST_TAG contains prerelease suffix: $BASE_VERSION" >&2
echo "this script only supports pure X.Y.Z versions" >&2
echo "skip prerelease tags manually or use a different baseline" >&2
exit 1
fi
log_stage "baseline: $BASE_VERSION (from $LATEST_TAG)"
log_stage "baseline: $BASE_VERSION (from origin $LATEST_TAG)"
else
BASE_VERSION="$(PACKAGE_JSON="$PACKAGE_JSON" node -p "require(process.env.PACKAGE_JSON).version")"
log_stage "no cli-v* tags found, baseline: $BASE_VERSION (from package.json)"
log_stage "no cli-v* tags on origin, baseline: $BASE_VERSION (from package.json)"
fi
NEW_VERSION="$(BASE_VERSION="$BASE_VERSION" BUMP_TYPE="$BUMP_TYPE" node -e "

View file

@ -342,7 +342,7 @@ REPO8="$(new_tmp)"
init_repo "$REPO8" "0.1.0" "cli-v0.2.0"
status="$(run_publish "$REPO8" "patch" $'y\n')"
[[ "$status" -eq 0 ]] || { cat "$REPO8/stderr.log" >&2; fail "expected success, got $status"; }
grep -F "baseline: 0.2.0 (from cli-v0.2.0)" "$REPO8/stdout.log" >/dev/null \
grep -F "baseline: 0.2.0 (from origin cli-v0.2.0)" "$REPO8/stdout.log" >/dev/null \
|| fail "baseline log missing — version computed from wrong source"
git -C "$REPO8.origin.git" rev-parse "refs/heads/release/cli-v0.2.1" >/dev/null \
|| fail "expected branch release/cli-v0.2.1 on origin"
@ -424,4 +424,21 @@ grep -F "contains prerelease suffix" "$REPO11/stderr.log" >/dev/null \
grep -F "only supports pure X.Y.Z" "$REPO11/stderr.log" >/dev/null \
|| fail "expected X.Y.Z hint in error"
# ----------------------------------------------------------------------------
# Test 12: local-only orphan tag must not influence baseline
# ----------------------------------------------------------------------------
# Simulates the failure mode where `git push origin cli-vX.Y.Z` failed after
# `git tag cli-vX.Y.Z origin/main` succeeded locally. The orphan tag exists
# locally but not on origin. Baseline must come from origin only.
echo "[test] local-only orphan tag ignored for baseline"
REPO12="$(new_tmp)"
init_repo "$REPO12" "0.1.0" "cli-v0.1.0"
git -C "$REPO12" tag "cli-v0.3.0"
status="$(run_publish "$REPO12" "patch" $'y\n')"
[[ "$status" -eq 0 ]] || { cat "$REPO12/stderr.log" >&2; fail "expected success, got $status"; }
grep -F "baseline: 0.1.0 (from origin cli-v0.1.0)" "$REPO12/stdout.log" >/dev/null \
|| { cat "$REPO12/stdout.log" >&2; fail "baseline must come from origin (0.1.0), not local orphan (0.3.0)"; }
git -C "$REPO12.origin.git" rev-parse "refs/heads/release/cli-v0.1.1" >/dev/null \
|| fail "expected branch release/cli-v0.1.1 on origin (orphan should not have shifted target to 0.3.1)"
echo "all tests passed"