release.sh 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. #!/usr/bin/env bash
  2. # ------------------------------------------------------------
  3. # release.sh – semi-automatic release helper
  4. # ------------------------------------------------------------
  5. set -euo pipefail
  6. set -o errtrace
  7. trap 'echo "❌ Error – aborting"; exit 1' ERR
  8. # -------- configurable -----------------
  9. APP_NAME="${1:-LoopFollow}"
  10. SECOND_DIR="${APP_NAME}_Second"
  11. THIRD_DIR="${APP_NAME}_Third"
  12. VERSION_FILE="Config.xcconfig"
  13. MARKETING_KEY="LOOP_FOLLOW_MARKETING_VERSION"
  14. DEV_BRANCH="dev"
  15. MAIN_BRANCH="main"
  16. # ---------------------------------------
  17. # --- functions here ---
  18. pause() { read -rp "▶▶ Press Enter to continue (Ctrl-C to abort)…"; }
  19. echo_run() { echo "+ $*"; "$@"; }
  20. push_cmds=()
  21. queue_push() { push_cmds+=("git -C \"$(pwd)\" $*"); echo "+ [queued] (in $(pwd)) git $*"; }
  22. update_follower () {
  23. local DIR="$1"
  24. local build_minute="$2" # staggered Sunday-build minute (see calls below)
  25. local suffix=".${DIR#${APP_NAME}_}" # LoopFollow_Second -> .Second
  26. local display="$DIR" # LoopFollow_Second
  27. local upstream="loopandlearn/${DIR}" # loopandlearn/LoopFollow_Second
  28. echo; echo "🔄 Updating $DIR …"
  29. cd "$DIR"
  30. # 1 · Make sure we’re on a clean, up-to-date main
  31. echo_run git switch "$MAIN_BRANCH"
  32. echo_run git fetch
  33. echo_run git pull
  34. # 2 · Full mirror of the release tree from the primary repo.
  35. # Every tracked file (including the overlay files) is synced; only git
  36. # metadata and local/build dirs are protected. --delete makes the tree an
  37. # exact mirror, auto-correcting any drift.
  38. echo_run rsync -a --delete \
  39. --exclude='.git/' \
  40. --exclude='.claude/' \
  41. --exclude='build/' \
  42. "$PRIMARY_ABS_PATH"/ ./
  43. # 3 · Re-apply this instance's overlay on top of the mirror
  44. perl -i -pe "s|^app_suffix\s*=.*|app_suffix = ${suffix}|" LoopFollowDisplayNameConfig.xcconfig
  45. perl -i -pe "s|^display_name\s*=.*|display_name = ${display}|" LoopFollowDisplayNameConfig.xcconfig
  46. perl -i -pe "s|^(\s*)UPSTREAM_REPO:.*|\${1}UPSTREAM_REPO: ${upstream}|" .github/workflows/build_LoopFollow.yml
  47. perl -i -pe "s|^(\s*)- cron:.*|\${1}- cron: \"${build_minute} 10 * * 0\" # Sunday at UTC 10:${build_minute}|" .github/workflows/build_LoopFollow.yml
  48. # 4 · Rename the synced workspace to this instance's name
  49. rm -rf "${DIR}.xcworkspace"
  50. if [ -d "${APP_NAME}.xcworkspace" ]; then
  51. mv "${APP_NAME}.xcworkspace" "${DIR}.xcworkspace"
  52. fi
  53. # 5 · Single commit capturing the mirror + overlay
  54. git add -A
  55. if git diff --cached --quiet; then
  56. echo "✓ $DIR already up to date — nothing to commit."
  57. else
  58. echo_run git commit -m "transfer v${new_ver} updates from LF to ${DIR}"
  59. fi
  60. echo_run git status
  61. echo "💻 Build & test $DIR now."; pause # build & test checkpoint
  62. queue_push push origin "$MAIN_BRANCH"
  63. cd ..
  64. }
  65. # ---------- PRIMARY REPO ----------
  66. PRIMARY_ABS_PATH="$(pwd -P)"
  67. echo "🏁 Working in $PRIMARY_ABS_PATH …"
  68. # --- guard: make sure we're running the latest release.sh from origin/dev ----
  69. # The release flow assumes the newest script. If this copy differs from the one
  70. # on origin/dev, we're likely running a stale version — abort and tell the user.
  71. SCRIPT_PATH="${BASH_SOURCE[0]}"
  72. echo_run git fetch origin "$DEV_BRANCH"
  73. if ! diff -q <(git show "origin/${DEV_BRANCH}:release.sh") "$SCRIPT_PATH" >/dev/null 2>&1; then
  74. echo "❌ This release.sh does not match origin/${DEV_BRANCH}:release.sh."
  75. echo " You're likely running an outdated copy of the release script."
  76. echo " Switch to the latest '${DEV_BRANCH}' (git switch ${DEV_BRANCH} && git pull) and re-run. Aborting."
  77. exit 1
  78. fi
  79. # --- start out in main to capture old_ver ----
  80. echo_run git switch "$MAIN_BRANCH"
  81. echo_run git fetch
  82. echo_run git pull
  83. # -------- version bump logic (unchanged) -----------
  84. old_ver=$(grep -E "^${MARKETING_KEY}[[:space:]]*=" "$VERSION_FILE" | awk '{print $3}')
  85. major_candidate="$(awk -F. '{printf "%d.0.0", $1 + 1}' <<<"$old_ver")"
  86. minor_candidate="$(awk -F. '{printf "%d.%d.0", $1, $2 + 1}' <<<"$old_ver")"
  87. echo
  88. echo "Which version bump do you want?"
  89. echo " 1) Major → $major_candidate"
  90. echo " 2) Minor → $minor_candidate"
  91. read -rp "Enter 1 or 2 (default = 2): " choice
  92. echo
  93. case "$choice" in
  94. 1) new_ver="$major_candidate" ;; ""|2) new_ver="$minor_candidate" ;;
  95. *) echo "❌ Invalid choice – aborting."; exit 1 ;;
  96. esac
  97. echo "🔢 Bumping version: $old_ver → $new_ver"
  98. # --- switch to dev so the release branch is cut from latest dev ----
  99. echo_run git switch "$DEV_BRANCH"
  100. echo_run git fetch
  101. echo_run git pull
  102. # --- create release branch from dev's tip ----
  103. RELEASE_BRANCH="release/v${new_ver}"
  104. echo_run git switch -c "$RELEASE_BRANCH"
  105. # --- bump version on the release branch ----
  106. sed -i '' "s/${MARKETING_KEY}[[:space:]]*=.*/${MARKETING_KEY} = ${new_ver}/" "$VERSION_FILE"
  107. echo_run git diff "$VERSION_FILE"; pause
  108. # No [skip ci] here on purpose: it would skip the required SwiftFormat lint check
  109. # (leaving the release PRs blocked) and the tag_on_main workflow (no auto-tag).
  110. # auto_version_dev already avoids re-bumping via its "Config.xcconfig changed" guard.
  111. echo_run git commit -m "update version to ${new_ver}" "$VERSION_FILE"
  112. echo "💻 Build & test release branch now."; pause
  113. queue_push push origin "$RELEASE_BRANCH"
  114. # --- mirror the release tree into the sister repos ----
  115. # Second arg = each app's scheduled browser-build minute (Sundays at 10:xx UTC),
  116. # staggered from the main app (:17) so a user who forked all three apps doesn't
  117. # trigger three simultaneous builds.
  118. cd ..
  119. update_follower "$SECOND_DIR" "27"
  120. update_follower "$THIRD_DIR" "40"
  121. # ---------- GitHub Actions Test ---------
  122. echo;
  123. echo "💻 Test GitHub Build Actions for all three repositories and then continue.";
  124. pause
  125. # --- return to primary path
  126. cd ${PRIMARY_ABS_PATH}
  127. # ---------- push queue ----------
  128. echo; echo "🚀 Ready to push changes upstream and open the release PR."
  129. echo_run git log --oneline -2
  130. read -rp "▶▶ Push everything now? (y/n): " confirm
  131. if [[ $confirm =~ ^[Yy]$ ]]; then
  132. for cmd in "${push_cmds[@]}"; do echo "+ $cmd"; bash -c "$cmd"; done
  133. echo "🎉 All pushes completed."
  134. # --- resolve the GitHub repo explicitly so gh doesn't depend on a default ----
  135. # Clones with multiple remotes have no inferred default repo, which makes
  136. # `gh pr create` fail. Derive owner/repo from the origin remote URL.
  137. # Handles both https://github.com/owner/repo(.git) and git@github.com:owner/repo(.git)
  138. REPO_SLUG="$(git remote get-url origin | sed -E -e 's#\.git$##' -e 's#^.*github\.com[:/]##')"
  139. echo; echo "📝 Opening sync PR ${RELEASE_BRANCH} → ${DEV_BRANCH} …"
  140. gh pr create \
  141. --repo "$REPO_SLUG" \
  142. --base "$DEV_BRANCH" \
  143. --head "$RELEASE_BRANCH" \
  144. --title "Sync v${new_ver} version bump to dev" \
  145. --body "Syncs the v${new_ver} version bump from the release branch back to \`dev\` so subsequent auto-bumps on \`dev\` continue from the released minor.
  146. \`auto_version_dev\` detects that \`Config.xcconfig\` was changed in this push and skips re-bumping.
  147. ⚠️ **Use \"Create a merge commit\"** (not squash or rebase) so \`dev\` and \`main\` share the same release lineage."
  148. echo; echo "📝 Opening release PR ${RELEASE_BRANCH} → ${MAIN_BRANCH} …"
  149. gh pr create \
  150. --repo "$REPO_SLUG" \
  151. --base "$MAIN_BRANCH" \
  152. --head "$RELEASE_BRANCH" \
  153. --title "Release v${new_ver}" \
  154. --body "Release v${new_ver}.
  155. Merging this PR triggers the tagging workflow, which creates tag \`v${new_ver}\` from \`LOOP_FOLLOW_MARKETING_VERSION\` in \`Config.xcconfig\`.
  156. ⚠️ **Use \"Create a merge commit\"** (not squash or rebase) so \`main\` keeps the original feature-PR commits."
  157. echo; echo "🎉 All repos updated to v${new_ver} (local). Release PRs opened (sync → dev, release → main)."
  158. echo "👉 Review and merge both PRs — the tag will be created automatically by .github/workflows/tag_on_main.yml."
  159. echo "👉 Remember to create a GitHub release for tag v${new_ver} after the tag exists."
  160. else
  161. echo "🚫 Pushes skipped. Run manually if needed:"; printf ' %s\n' "${push_cmds[@]}"
  162. echo "🚫 Release not completed, pushes to GitHub were skipped"
  163. fi