From 8c1980a1eee4def2f8628b79390d40243395a63e Mon Sep 17 00:00:00 2001 From: leemour Date: Fri, 16 Oct 2026 02:06:44 +0200 Subject: [PATCH] fix(release): land the fold and the version raise through pull requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit main takes changes only through a pull request (GH013), so bin/release's direct 'git push origin main' was refused and then misreported as 'main moved on origin while folding the changelog — run bin/release again'. - The Unreleased fold now runs the in-repo 'cli-dev changelog fold --land', which folds on a fresh origin/main, opens a PR, checks for conflicts, refolds once and merges — the path app scripts use since 0.36.0. - Raising the version when npm already has it lands through a land() helper (branch, PR, wait for checks, squash-merge, fast-forward main), as max-cli's bin/release does. A refused push or merge now says what happened and that nothing was published. Co-Authored-By: Claude Opus 5.5 (1M context) --- bin/release | 48 ++++++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/bin/release b/bin/release index b11becb..96db85f 100755 --- a/bin/release +++ b/bin/release @@ -1,8 +1,8 @@ #!/usr/bin/env sh # Publishes the version in package.json and tags it. Run on a clean `main`. When npm already has that -# version, or a higher one, it commits the next free version to `main`, with the CHANGELOG heading -# renamed to match, and publishes that. An Unreleased section left above the dated, unpublished version is -# folded into it first. +# version, or a higher one, it lands the next free version on `main` through a pull request, with the +# CHANGELOG heading renamed to match, and publishes that. An Unreleased section left above the dated, +# unpublished version is folded into it first, through a pull request too. # # bin/release # publishes from GitHub Actions (.github/workflows/release.yml) # bin/release --local # publishes from this machine with the npm token from the keyring @@ -38,18 +38,10 @@ published() { npm view "@wirecat/cli-base@$version" version --prefer-online >/dev/null 2>&1 } # A session that merges after the version was dated, but before it was published, adds Unreleased above it. +# --land folds on a fresh main through its own pull request, and folds again once if main moves under it. if ! published; then - folded=$(pnpm build >/dev/null && node dist/dev/bin.js changelog fold --version "$version") - if [ -n "$folded" ]; then - echo "$folded" - git commit -q -am "chore(release): $folded" \ - -m "bin/release: $version is dated but not on npm. Only CHANGELOG.md changes." - git push -q origin main || { - git reset -q --hard HEAD~1 - echo "main moved on origin while folding the changelog — run bin/release again" >&2 - exit 1 - } - fi + folded=$(pnpm build >/dev/null && node dist/dev/bin.js changelog fold --land --version "$version") + [ -z "$folded" ] || echo "$folded" fi # Only the top section: the rename below must never touch one that is already published. top=$(grep -m1 '^## ' CHANGELOG.md || true) @@ -58,6 +50,27 @@ case "$top" in *) echo "CHANGELOG.md's top section is \"$top\" — date it as $version first" >&2; exit 1 ;; esac +# main takes changes only through a pull request (the repository's ruleset). +land() { + branch=$1 title=$2 body=$3 + git switch -q -c "$branch" + git commit -q -am "$title" -m "$body" + git push -q -u origin "$branch" || { echo "could not push $branch — nothing is published" >&2; exit 1; } + git switch -q main + pr=$(gh pr create --base main --head "$branch" --title "$title" --body "$body") + # Checks register a few seconds after the pull request; --watch with none exits at once. + for _ in $(seq 30); do + [ "$(gh pr checks "$pr" --json name --jq length 2>/dev/null || echo 0)" -gt 0 ] && break + sleep 2 + done + gh pr checks "$pr" --watch --fail-fast >/dev/null || + { echo "checks failed on $pr — nothing is published" >&2; exit 1; } + gh pr merge "$pr" --squash --delete-branch --subject "$title (#${pr##*/})" || + { echo "$pr did not merge (main moved under it?) — close it and run bin/release again" >&2; exit 1; } + git fetch -q origin + git merge -q --ff-only origin/main +} + # Two sessions preparing the same number is normal; the second takes the next free one (2026-09-29). latest=$(npm view @wirecat/cli-base version --prefer-online 2>/dev/null || true) versions=$(npm view @wirecat/cli-base versions --json --prefer-online 2>/dev/null || true) @@ -73,12 +86,7 @@ if [ "$next" != "$version" ]; then return text }) swaps.forEach(([file, from, to], i) => fs.writeFileSync(file, texts[i].replace(from, to)))' "$version" "$next" - git commit -q -am "chore: release $next" -m "bin/release: $why." - git push -q origin main || { - git reset -q --hard HEAD~1 - echo "main moved on origin while raising the version — run bin/release again" >&2 - exit 1 - } + land "release/$next" "chore(release): $next" "bin/release: $why. Only package.json and the CHANGELOG heading change." version=$next fi if published; then