@neferbyte/cherry-release-cli 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -43,19 +43,13 @@ trap cleanup EXIT
43
43
 
44
44
  step "Running pre-flight checks"
45
45
 
46
- # 1. At least one commit hash argument
47
- if [[ $# -eq 0 ]]; then
48
- fail "No commit hashes provided.\nUsage: cherry-release <hash1> [hash2] ..."
49
- fi
50
- check_ok "Received $# commit hash(es)"
51
-
52
- # 2. Working tree is clean
46
+ # 1. Working tree is clean
53
47
  if [[ -n "$(git status --porcelain)" ]]; then
54
48
  fail "Working tree is dirty. Commit or stash your changes first."
55
49
  fi
56
50
  check_ok "Working tree is clean"
57
51
 
58
- # 3. Required tools available
52
+ # 2. Required tools available
59
53
  for tool in jq sed commit-and-tag-version; do
60
54
  if ! command -v "$tool" &>/dev/null; then
61
55
  fail "Required tool '$tool' is not installed or not in PATH."
@@ -63,36 +57,19 @@ for tool in jq sed commit-and-tag-version; do
63
57
  done
64
58
  check_ok "Required tools available (jq, sed, commit-and-tag-version)"
65
59
 
66
- # 4. Remote write access
60
+ # 3. Remote write access
67
61
  if ! git push --dry-run origin HEAD &>/dev/null; then
68
62
  fail "No write access to the remote repository."
69
63
  fi
70
64
  check_ok "Remote write access confirmed"
71
65
 
72
- # 5. Fetch latest remote refs
66
+ # 4. Fetch latest remote refs
73
67
  if ! git fetch origin development staging production 2>/dev/null; then
74
68
  fail "Failed to fetch remote branches (development, staging, production)."
75
69
  fi
76
70
  check_ok "Fetched latest remote refs"
77
71
 
78
- # 6. All commit hashes are valid commit objects
79
- for hash in "$@"; do
80
- obj_type=$(git cat-file -t "$hash" 2>/dev/null || true)
81
- if [[ "$obj_type" != "commit" ]]; then
82
- fail "Invalid commit hash: $hash (type: ${obj_type:-not found})"
83
- fi
84
- done
85
- check_ok "All commit hashes are valid commit objects"
86
-
87
- # 7. All commits are reachable from origin/development
88
- for hash in "$@"; do
89
- if ! git merge-base --is-ancestor "$hash" origin/development 2>/dev/null; then
90
- fail "Commit $hash is not reachable from origin/development.\nOnly deploy commits that are already merged to development."
91
- fi
92
- done
93
- check_ok "All commits are reachable from origin/development"
94
-
95
- # 8. development is not diverged from origin (can fast-forward)
72
+ # 5. development is not diverged from origin (can fast-forward)
96
73
  local_dev=$(git rev-parse development 2>/dev/null)
97
74
  remote_dev=$(git rev-parse origin/development 2>/dev/null)
98
75
  merge_base_dev=$(git merge-base development origin/development 2>/dev/null)
@@ -101,13 +78,13 @@ if [[ "$local_dev" != "$remote_dev" && "$merge_base_dev" != "$local_dev" ]]; the
101
78
  fi
102
79
  check_ok "development can fast-forward to origin"
103
80
 
104
- # 9. staging and production have no local-only commits ahead of origin
81
+ # 6. staging and production have no local-only commits ahead of origin
105
82
  for branch in "${BRANCHES[@]}"; do
106
83
  local_ref=$(git rev-parse "$branch" 2>/dev/null || true)
107
84
  remote_ref=$(git rev-parse "origin/$branch" 2>/dev/null || true)
108
85
 
109
86
  if [[ -z "$local_ref" || -z "$remote_ref" ]]; then
110
- continue # branch may not exist locally yet — that's fine, reset --hard will create it
87
+ continue
111
88
  fi
112
89
 
113
90
  ahead=$(git rev-list --count "origin/$branch..$branch" 2>/dev/null || echo "0")
@@ -117,12 +94,43 @@ for branch in "${BRANCHES[@]}"; do
117
94
  done
118
95
  check_ok "No local-only commits on staging/production"
119
96
 
120
- # 10. Dry-run cherry-picks in temporary worktrees
97
+ # 7. Auto-discover commits to promote (per target branch)
98
+ declare -A COMMITS_FOR
99
+ for branch in "${BRANCHES[@]}"; do
100
+ hashes=$(git cherry "origin/$branch" origin/development | grep '^+' | awk '{print $2}')
101
+
102
+ # Filter out version bump commits (commit-and-tag-version)
103
+ filtered=()
104
+ for hash in $hashes; do
105
+ msg=$(git log -1 --format="%s" "$hash")
106
+ if [[ ! "$msg" =~ ^chore\(release\): ]]; then
107
+ filtered+=("$hash")
108
+ fi
109
+ done
110
+
111
+ COMMITS_FOR[$branch]="${filtered[*]}"
112
+ done
113
+
114
+ staging_count=$(echo "${COMMITS_FOR[staging]}" | wc -w | tr -d ' ')
115
+ production_count=$(echo "${COMMITS_FOR[production]}" | wc -w | tr -d ' ')
116
+
117
+ if [[ "$staging_count" -eq 0 && "$production_count" -eq 0 ]]; then
118
+ echo -e "\n\033[1;32mNothing to promote.\033[0m staging and production are up to date."
119
+ exit 0
120
+ fi
121
+
122
+ check_ok "Discovered commits to promote (staging: $staging_count, production: $production_count)"
123
+
124
+ # 8. Dry-run cherry-picks in temporary worktrees
121
125
  for branch in "${BRANCHES[@]}"; do
126
+ commits=(${COMMITS_FOR[$branch]})
127
+ if [[ ${#commits[@]} -eq 0 ]]; then
128
+ continue
129
+ fi
130
+
122
131
  wt_path="${WORKTREE_DIR}-${branch}"
123
132
  WORKTREES_TO_CLEAN+=("$wt_path")
124
133
 
125
- # Clean up any leftover worktree from a previous failed run
126
134
  if [[ -d "$wt_path" ]]; then
127
135
  git worktree remove --force "$wt_path" 2>/dev/null || true
128
136
  fi
@@ -130,7 +138,7 @@ for branch in "${BRANCHES[@]}"; do
130
138
  git worktree add "$wt_path" "origin/$branch" --detach --quiet 2>/dev/null \
131
139
  || fail "Failed to create test worktree for $branch."
132
140
 
133
- for hash in "$@"; do
141
+ for hash in "${commits[@]}"; do
134
142
  short=$(git rev-parse --short "$hash")
135
143
  if ! git -C "$wt_path" cherry-pick "$hash" &>/dev/null; then
136
144
  git -C "$wt_path" cherry-pick --abort 2>/dev/null || true
@@ -140,7 +148,7 @@ for branch in "${BRANCHES[@]}"; do
140
148
 
141
149
  git worktree remove --force "$wt_path" 2>/dev/null || true
142
150
  done
143
- check_ok "Dry-run cherry-picks succeeded on staging and production"
151
+ check_ok "Dry-run cherry-picks succeeded"
144
152
 
145
153
  echo -e "\n\033[1;32mAll pre-flight checks passed.\033[0m"
146
154
 
@@ -156,27 +164,33 @@ git push --follow-tags origin development
156
164
  echo -e " \033[1;32m✓\033[0m development released"
157
165
 
158
166
  # --- staging ---
159
- step "Deploying to staging"
167
+ staging_commits=(${COMMITS_FOR[staging]})
168
+ step "Deploying to staging (${#staging_commits[@]} commit(s))"
160
169
 
161
170
  git checkout staging
162
171
  git reset --hard origin/staging --quiet
163
- for hash in "$@"; do
164
- git cherry-pick "$hash"
165
- done
166
- git push origin staging
172
+ if [[ ${#staging_commits[@]} -gt 0 ]]; then
173
+ for hash in "${staging_commits[@]}"; do
174
+ git cherry-pick "$hash"
175
+ done
176
+ git push origin staging
177
+ fi
167
178
  "$SCRIPT_DIR/release.sh" patch
168
179
  git push --follow-tags origin staging
169
180
  echo -e " \033[1;32m✓\033[0m staging released"
170
181
 
171
182
  # --- production ---
172
- step "Deploying to production"
183
+ production_commits=(${COMMITS_FOR[production]})
184
+ step "Deploying to production (${#production_commits[@]} commit(s))"
173
185
 
174
186
  git checkout production
175
187
  git reset --hard origin/production --quiet
176
- for hash in "$@"; do
177
- git cherry-pick "$hash"
178
- done
179
- git push origin production
188
+ if [[ ${#production_commits[@]} -gt 0 ]]; then
189
+ for hash in "${production_commits[@]}"; do
190
+ git cherry-pick "$hash"
191
+ done
192
+ git push origin production
193
+ fi
180
194
  "$SCRIPT_DIR/release.sh" patch
181
195
  git push --follow-tags origin production
182
196
  echo -e " \033[1;32m✓\033[0m production released"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neferbyte/cherry-release-cli",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Cherry-pick commits across environment branches and tag releases",
5
5
  "license": "MIT",
6
6
  "bin": {