22
33set -euo pipefail
44
5+ export GIT_PAGER=cat
6+ export PAGER=cat
7+ export LESS=-F
8+
9+ ORIG_BRANCH=$( git rev-parse --abbrev-ref HEAD 2> /dev/null || echo " " )
10+ ORIG_COMMIT=" "
11+ if [ " $ORIG_BRANCH " = " HEAD" ]; then
12+ ORIG_COMMIT=$( git rev-parse HEAD 2> /dev/null || echo " " )
13+ fi
14+ AUTO_STASHES=()
15+ RESTORE_COMPLETED=0
16+ ORIG_UNTRACKED=()
17+ while IFS=$' \0' read -r -d ' ' path; do
18+ ORIG_UNTRACKED+=(" $path " )
19+ done < <( git ls-files --others --exclude-standard -z 2> /dev/null || printf ' ' )
20+
21+ restore_workspace () {
22+ local restore_messages=()
23+
24+ if [ -n " $ORIG_BRANCH " ] && [ " $ORIG_BRANCH " != " HEAD" ]; then
25+ local current_branch
26+ current_branch=$( git rev-parse --abbrev-ref HEAD 2> /dev/null || echo " " )
27+ if [ -n " $current_branch " ] && [ " $current_branch " != " $ORIG_BRANCH " ]; then
28+ if git checkout " $ORIG_BRANCH " > /dev/null 2>&1 ; then
29+ restore_messages+=(" Returned to original branch $ORIG_BRANCH ." )
30+ else
31+ echo " Warning: failed to return to branch $ORIG_BRANCH . Current branch: $current_branch ."
32+ fi
33+ fi
34+ elif [ -n " $ORIG_COMMIT " ]; then
35+ local current_ref
36+ current_ref=$( git rev-parse HEAD 2> /dev/null || echo " " )
37+ if [ -n " $current_ref " ] && [ " $current_ref " != " $ORIG_COMMIT " ]; then
38+ if git checkout --detach " $ORIG_COMMIT " > /dev/null 2>&1 ; then
39+ restore_messages+=(" Returned to detached HEAD at $ORIG_COMMIT ." )
40+ else
41+ echo " Warning: failed to return to original commit $ORIG_COMMIT ."
42+ fi
43+ fi
44+ fi
45+
46+ for (( idx= ${# AUTO_STASHES[@]} - 1 ; idx>= 0 ; idx-- )) ; do
47+ local stash_ref=${AUTO_STASHES[idx]}
48+ if [ -n " $stash_ref " ]; then
49+ if git stash pop " $stash_ref " > /dev/null 2>&1 ; then
50+ restore_messages+=(" Restored auto-stash $stash_ref ." )
51+ else
52+ echo " Warning: failed to restore auto-stash $stash_ref . Run 'git stash pop $stash_ref ' manually."
53+ fi
54+ fi
55+ done
56+
57+ if [ ${# ORIG_UNTRACKED[@]} -gt 0 ]; then
58+ for path in " ${ORIG_UNTRACKED[@]} " ; do
59+ if git ls-files --error-unmatch -- " $path " > /dev/null 2>&1 ; then
60+ git update-index --force-remove -- " $path " > /dev/null 2>&1
61+ fi
62+ done
63+ fi
64+
65+ if [ ${# restore_messages[@]} -gt 0 ]; then
66+ printf ' %s\n' " ${restore_messages[@]} "
67+ fi
68+
69+ RESTORE_COMPLETED=1
70+ }
71+
72+ cleanup_release_context () {
73+ local exit_code=$?
74+ set +e
75+
76+ if [ " $RESTORE_COMPLETED " -ne 1 ]; then
77+ restore_workspace
78+ fi
79+
80+ exit $exit_code
81+ }
82+ trap cleanup_release_context EXIT
83+
584ask_consent () {
685 local explanation=$1
786 shift
@@ -29,15 +108,72 @@ require_clean_worktree() {
29108 fi
30109}
31110
111+
32112ensure_checkout_safe () {
33113 local target=$1
34- if ! git checkout --dry-run " $target " > /dev/null 2>&1 ; then
35- echo " Checkout preview detected conflicts (tracked files or untracked files would be overwritten)."
36- echo " Resolve or stash those files before continuing."
114+
115+ if ! git rev-parse --verify " $target " > /dev/null 2>&1 ; then
116+ echo " Unable to verify target ref $target . Ensure the branch exists locally."
117+ exit 1
118+ fi
119+
120+ local tracked_blockers
121+ tracked_blockers=$( git status --porcelain --untracked-files=no)
122+ if [ -n " $tracked_blockers " ]; then
123+ echo " Tracked changes detected that would block switching to $target ."
124+ echo " Tracked files (showing up to 20):"
125+ printf ' %s\n' " $tracked_blockers " | head -n 20 | sed ' s/^/ - /'
126+ if [ " $( printf ' %s\n' " $tracked_blockers " | wc -l | tr -d ' ' ) " -gt 20 ]; then
127+ echo " - ... (additional files omitted)"
128+ fi
129+ echo " Clean or stash the tracked changes before continuing."
130+ exit 1
131+ fi
132+
133+ local untracked_blockers=()
134+ while IFS= read -r -d ' ' path; do
135+ untracked_blockers+=(" $path " )
136+ done < <( git ls-files --others --exclude-standard -z)
137+
138+ if [ ${# untracked_blockers[@]} -eq 0 ]; then
139+ return 0
140+ fi
141+
142+ echo " Checkout preview detected untracked files that could be overwritten when switching to $target ."
143+ echo " Affected untracked files (showing up to 20):"
144+ local idx=0
145+ for path in " ${untracked_blockers[@]} " ; do
146+ idx=$(( idx + 1 ))
147+ if [ $idx -le 20 ]; then
148+ echo " - $path "
149+ fi
150+ done
151+ if [ $idx -gt 20 ]; then
152+ echo " - ... ($idx total files)"
153+ fi
154+
155+ echo " Auto-stashing untracked files before retrying checkout."
156+ local stash_marker
157+ stash_marker=" $( date +%s) -$$ -$RANDOM "
158+ local stash_message=" release.sh:auto-stash:$target :$stash_marker "
159+ if git stash push --include-untracked --message " $stash_message " > /dev/null 2>&1 ; then
160+ local stash_ref
161+ stash_ref=$( git stash list | awk -F: -v msg=" $stash_message " ' $0 ~ msg {print $1; exit}' )
162+ if [ -n " $stash_ref " ]; then
163+ AUTO_STASHES+=(" $stash_ref " )
164+ else
165+ echo " Warning: auto-stashed files recorded but stash reference could not be determined."
166+ fi
167+ echo " Untracked files stashed temporarily; they will be restored after release.sh completes."
168+ ensure_checkout_safe " $target "
169+ return
170+ else
171+ echo " Failed to auto-stash untracked files. Resolve them manually and rerun the script."
37172 exit 1
38173 fi
39174}
40175
176+
41177# Step 1: Get the first argument from the command line
42178echo " ==== Read Tag ===="
43179TAG=${1:- }
@@ -68,13 +204,34 @@ if [ -z "$TAG" ]; then
68204 MINOR_TAG=" v${MAJOR} .${MINOR_SUGGESTED} .0"
69205 PATCH_TAG=" v${MAJOR} .${MINOR} .${PATCH_SUGGESTED} "
70206 echo " Suggested tags:"
71- echo " - Minor bump (includes these features): $MINOR_TAG "
72- echo " - Patch bump: $PATCH_TAG "
73- read -r -p " Enter desired tag (copy one of the suggestions or provide a custom value): " TAG
74- if [ -z " $TAG " ]; then
75- TAG=$PATCH_TAG
76- echo " Defaulting to patch bump: $TAG "
77- fi
207+ echo " 1) Minor bump (includes these features): $MINOR_TAG "
208+ echo " 2) Patch bump: $PATCH_TAG "
209+ echo " 3) Enter custom tag"
210+ while true ; do
211+ read -r -p " Selection [1-3, default 2]: " TAG_CHOICE
212+ case " $TAG_CHOICE " in
213+ " " |" 2" )
214+ TAG=$PATCH_TAG
215+ echo " Using patch bump: $TAG "
216+ break
217+ ;;
218+ " 1" )
219+ TAG=$MINOR_TAG
220+ echo " Using minor bump: $TAG "
221+ break
222+ ;;
223+ " 3" )
224+ read -r -p " Enter custom tag (vX.Y.Z): " TAG
225+ if [ -n " $TAG " ]; then
226+ break
227+ fi
228+ echo " Custom tag cannot be empty."
229+ ;;
230+ * )
231+ echo " Invalid selection. Enter 1, 2, or 3."
232+ ;;
233+ esac
234+ done
78235 else
79236 TAG=" v${MAJOR} .${MINOR} .${PATCH_SUGGESTED} "
80237 read -r -p " Suggested tag is $TAG (no feature commits detected). Is this appropriate? (y/n): " CONFIRM
@@ -95,6 +252,23 @@ VERSION=${TAG#v}
95252
96253require_clean_worktree " Resolve tracked changes before starting the release (common causes: leftover ./version.sh runs or generated dist files)."
97254
255+ # Step 4: Run dependency audit, update dependencies, update versions, and rebuild bundles
256+ echo " ==== Dependency audit ===="
257+ ask_consent " Run depcheck across all actions to verify dependency health." ./depcheck.sh
258+ require_clean_worktree " Depcheck introduced tracked changes. Review and commit or stash them before continuing."
259+
260+ echo " ==== Dependency updates ===="
261+ ask_consent " Run update-dependencies to refresh package versions before release." ./update-dependencies.sh
262+ require_clean_worktree " Dependency updates introduced tracked changes. Review and commit or stash them before continuing."
263+
264+ echo " ==== Update versions ===="
265+ ask_consent " Update package.json versions to $VERSION across all actions." ./version.sh " $VERSION "
266+ require_clean_worktree " Version update introduced tracked changes. Review and commit or stash them before continuing."
267+
268+ echo " ==== Rebuild bundles ===="
269+ ask_consent " Regenerate dist outputs so release $TAG contains fresh builds." ./build.sh
270+ require_clean_worktree " Build step introduced tracked changes. Review and commit or stash them before continuing."
271+
98272# Step 4: Check out the develop branch locally and check if it matches the remote
99273echo " ==== Develop matches remote ===="
100274ask_consent " Fetch latest refs from origin to ensure develop is up to date before tagging." git fetch origin
@@ -104,6 +278,7 @@ if [ "$LOCAL_DEVELOP" != "$REMOTE_DEVELOP" ]; then
104278 echo " Local develop branch is not up to date with remote. "
105279 echo " Local develop: $LOCAL_DEVELOP ."
106280 git log -1 --pretty=format:" %s" refs/heads/develop
281+ echo " \n"
107282 echo " Remote develop: $REMOTE_DEVELOP ."
108283 git log -1 --pretty=format:" %s" origin/develop
109284 echo " Exiting"
134309
135310require_clean_worktree " Rebase introduced tracked changes (e.g., conflict resolution artifacts). Review and resolve them before proceeding."
136311
137- echo " ==== Dependency audit ===="
138- ask_consent " Run depcheck across all actions to verify dependency health." ./depcheck.sh
139-
140- echo " ==== Dependency updates ===="
141- ask_consent " Run update-dependencies to refresh package versions before release." ./update-dependencies.sh
142-
143- echo " ==== Update versions ===="
144- ask_consent " Update package.json versions to $VERSION across all actions." ./version.sh " $VERSION "
145-
146- echo " ==== Rebuild bundles ===="
147- ask_consent " Regenerate dist outputs so release $TAG contains fresh builds." ./build.sh
148-
149312echo " ==== Stage release artifacts ===="
150313if [ -z " $( git status --porcelain) " ]; then
151314 echo " No changes detected after dependency updates and rebuild. Exiting to avoid an empty release commit."
@@ -170,4 +333,8 @@ ask_consent "Create local tag $TAG so the release can be pushed." git tag "$TAG"
170333echo " ==== Push tag ===="
171334ask_consent " Push tag $TAG to origin to make the release available to users." git push origin " $TAG "
172335
336+ # Step 9: Restore original branch
337+ echo " ==== Restore original branch ===="
338+ restore_workspace
339+
173340echo " Tag $TAG has been created and pushed to remote."
0 commit comments