Skip to content

Commit ecb3d71

Browse files
committed
build: release.sh has smart checkouts
1 parent 2170767 commit ecb3d71

File tree

4 files changed

+224
-30
lines changed

4 files changed

+224
-30
lines changed

depcheck.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ done
2020
# Include shared helpers in common/*
2121
while IFS= read -r common_dir; do
2222
projects_with_package+=("$common_dir")
23-
done < <(find common -mindepth 1 -maxdepth 1 -type d -exec test -f "{}/package.json" \; -printf '%p/\n')
23+
done < <(find common -mindepth 1 -maxdepth 1 -type d -exec sh -c 'if [ -f "$1/package.json" ]; then printf "%s/\n" "$1"; fi' _ {} \;)
2424

2525
echo "==== Composite actions ===="
2626
if ((${#projects_with_action[@]})); then

release.sh

Lines changed: 216 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,85 @@
22

33
set -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+
584
ask_consent() {
685
local explanation=$1
786
shift
@@ -29,15 +108,86 @@ require_clean_worktree() {
29108
fi
30109
}
31110

111+
32112
ensure_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+
local stash_choice
156+
while true; do
157+
read -r -p "Auto-stash these untracked files and continue? (y/n): " stash_choice
158+
case "$stash_choice" in
159+
y|Y) break ;;
160+
n|N)
161+
echo "Aborting at user request; resolve untracked files before rerunning."
162+
exit 1
163+
;;
164+
"") echo "Please answer y or n." ;;
165+
*) echo "Invalid selection. Enter y or n." ;;
166+
esac
167+
done
168+
169+
echo "Auto-stashing untracked files before retrying checkout."
170+
local stash_marker
171+
stash_marker="$(date +%s)-$$-$RANDOM"
172+
local stash_message="release.sh:auto-stash:$target:$stash_marker"
173+
if git stash push --include-untracked --message "$stash_message" >/dev/null 2>&1; then
174+
local stash_ref
175+
stash_ref=$(git stash list | awk -F: -v msg="$stash_message" '$0 ~ msg {print $1; exit}')
176+
if [ -n "$stash_ref" ]; then
177+
AUTO_STASHES+=("$stash_ref")
178+
else
179+
echo "Warning: auto-stashed files recorded but stash reference could not be determined."
180+
fi
181+
echo "Untracked files stashed temporarily; they will be restored after release.sh completes."
182+
ensure_checkout_safe "$target"
183+
return
184+
else
185+
echo "Failed to auto-stash untracked files. Resolve them manually and rerun the script."
37186
exit 1
38187
fi
39188
}
40189

190+
41191
# Step 1: Get the first argument from the command line
42192
echo "==== Read Tag ===="
43193
TAG=${1:-}
@@ -68,13 +218,34 @@ if [ -z "$TAG" ]; then
68218
MINOR_TAG="v${MAJOR}.${MINOR_SUGGESTED}.0"
69219
PATCH_TAG="v${MAJOR}.${MINOR}.${PATCH_SUGGESTED}"
70220
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
221+
echo " 1) Minor bump (includes these features): $MINOR_TAG"
222+
echo " 2) Patch bump: $PATCH_TAG"
223+
echo " 3) Enter custom tag"
224+
while true; do
225+
read -r -p "Selection [1-3, default 2]: " TAG_CHOICE
226+
case "$TAG_CHOICE" in
227+
""|"2")
228+
TAG=$PATCH_TAG
229+
echo "Using patch bump: $TAG"
230+
break
231+
;;
232+
"1")
233+
TAG=$MINOR_TAG
234+
echo "Using minor bump: $TAG"
235+
break
236+
;;
237+
"3")
238+
read -r -p "Enter custom tag (vX.Y.Z): " TAG
239+
if [ -n "$TAG" ]; then
240+
break
241+
fi
242+
echo "Custom tag cannot be empty."
243+
;;
244+
*)
245+
echo "Invalid selection. Enter 1, 2, or 3."
246+
;;
247+
esac
248+
done
78249
else
79250
TAG="v${MAJOR}.${MINOR}.${PATCH_SUGGESTED}"
80251
read -r -p "Suggested tag is $TAG (no feature commits detected). Is this appropriate? (y/n): " CONFIRM
@@ -95,6 +266,23 @@ VERSION=${TAG#v}
95266

96267
require_clean_worktree "Resolve tracked changes before starting the release (common causes: leftover ./version.sh runs or generated dist files)."
97268

269+
# Step 4: Run dependency audit, update dependencies, update versions, and rebuild bundles
270+
echo "==== Update versions ===="
271+
ask_consent "Update package.json versions to $VERSION across all actions." bash ./version.sh "$VERSION"
272+
require_clean_worktree "Version update introduced tracked changes. Review and commit or stash them before continuing."
273+
274+
echo "==== Rebuild bundles ===="
275+
ask_consent "Regenerate dist outputs so release $TAG contains fresh builds." bash ./build.sh
276+
require_clean_worktree "Build step introduced tracked changes. Review and commit or stash them before continuing."
277+
278+
echo "==== Dependency audit ===="
279+
ask_consent "Run depcheck across all actions to verify dependency health." bash ./depcheck.sh
280+
require_clean_worktree "Depcheck introduced tracked changes. Review and commit or stash them before continuing."
281+
282+
echo "==== Dependency updates ===="
283+
ask_consent "Run update-dependencies to refresh package versions before release." bash ./update-dependencies.sh
284+
require_clean_worktree "Dependency updates introduced tracked changes. Review and commit or stash them before continuing."
285+
98286
# Step 4: Check out the develop branch locally and check if it matches the remote
99287
echo "==== Develop matches remote ===="
100288
ask_consent "Fetch latest refs from origin to ensure develop is up to date before tagging." git fetch origin
@@ -104,6 +292,7 @@ if [ "$LOCAL_DEVELOP" != "$REMOTE_DEVELOP" ]; then
104292
echo "Local develop branch is not up to date with remote. "
105293
echo "Local develop: $LOCAL_DEVELOP."
106294
git log -1 --pretty=format:"%s" refs/heads/develop
295+
echo "\n"
107296
echo "Remote develop: $REMOTE_DEVELOP."
108297
git log -1 --pretty=format:"%s" origin/develop
109298
echo "Exiting"
@@ -112,18 +301,26 @@ fi
112301

113302
# Step 5: Check out the master branch locally and check if it matches the remote develop
114303
echo "==== Master matches develop ===="
115-
ensure_checkout_safe master
116-
ask_consent "Check out the master branch locally to prepare for release tagging." git checkout master
117-
require_clean_worktree "Checkout left tracked changes (frequently produced by post-checkout hooks). Clean them up before continuing."
304+
LOCAL_MASTER=$(git rev-parse refs/heads/master 2>/dev/null || echo "")
305+
if [ -z "$LOCAL_MASTER" ]; then
306+
echo "Local master branch not found; fetch or create it before running the release."
307+
exit 1
308+
fi
118309

119-
LOCAL_MASTER=$(git rev-parse refs/heads/master)
120310
if [ "$LOCAL_MASTER" != "$REMOTE_DEVELOP" ]; then
311+
ensure_checkout_safe master
312+
ask_consent "Check out the master branch locally to prepare for release tagging." git checkout master
313+
require_clean_worktree "Checkout left tracked changes (frequently produced by post-checkout hooks). Clean them up before continuing."
314+
315+
LOCAL_MASTER=$(git rev-parse refs/heads/master)
121316
echo "Local master branch is not up to date with remote develop."
122317
echo "Local master: $LOCAL_MASTER."
123318
git log -1 --pretty=format:"%s" refs/heads/master
319+
printf "\n"
124320
echo "Remote develop: $REMOTE_DEVELOP."
125321
git log -1 --pretty=format:"%s" origin/develop
126-
git log refs/head/master..origin/develop
322+
printf "\nCommits on remote develop not in local master:\n"
323+
git log refs/heads/master..origin/develop
127324
read -r -p "Do you want to rebase local master on top of remote develop? (y/n): " REBASE_CONFIRM
128325
if [ "$REBASE_CONFIRM" != "y" ]; then
129326
echo "Exiting."
@@ -134,18 +331,6 @@ fi
134331

135332
require_clean_worktree "Rebase introduced tracked changes (e.g., conflict resolution artifacts). Review and resolve them before proceeding."
136333

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-
149334
echo "==== Stage release artifacts ===="
150335
if [ -z "$(git status --porcelain)" ]; then
151336
echo "No changes detected after dependency updates and rebuild. Exiting to avoid an empty release commit."
@@ -170,4 +355,8 @@ ask_consent "Create local tag $TAG so the release can be pushed." git tag "$TAG"
170355
echo "==== Push tag ===="
171356
ask_consent "Push tag $TAG to origin to make the release available to users." git push origin "$TAG"
172357

358+
# Step 9: Restore original branch
359+
echo "==== Restore original branch ===="
360+
restore_workspace
361+
173362
echo "Tag $TAG has been created and pushed to remote."

update-dependencies.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ done
1616

1717
while IFS= read -r common_dir; do
1818
projects_with_package+=("$common_dir")
19-
done < <(find common -mindepth 1 -maxdepth 1 -type d -exec test -f "{}/package.json" \; -printf '%p/\n')
19+
done < <(find common -mindepth 1 -maxdepth 1 -type d -exec sh -c 'if [ -f "$1/package.json" ]; then printf "%s/\n" "$1"; fi' _ {} \;)
2020

2121
for project in "${projects_with_package[@]}"; do
2222
(

version.sh

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ done
2828

2929
while IFS= read -r common_dir; do
3030
package_dirs+=("$common_dir")
31-
done < <(find common -mindepth 1 -maxdepth 1 -type d -exec test -f "{}/package.json" \; -printf '%p/\n')
31+
done < <(find common -mindepth 1 -maxdepth 1 -type d -exec sh -c 'if [ -f "$1/package.json" ]; then printf "%s/\n" "$1"; fi' _ {} \;)
3232

3333
if ((${#package_dirs[@]} == 0)); then
3434
echo "No package.json files found. Exiting."
@@ -39,6 +39,11 @@ for dir in "${package_dirs[@]}"; do
3939
(
4040
cd "$dir"
4141
echo "==== Updating version in $dir ===="
42+
current_version=$(node -pe "require('./package.json').version" 2>/dev/null || echo "")
43+
if [ "$current_version" = "$version" ]; then
44+
echo "Version already $version; skipping."
45+
exit 0
46+
fi
4247
npm version "$version" --no-git-tag-version
4348
)
4449
done

0 commit comments

Comments
 (0)