
Cli Release
- 1 installs
- Updated July 2, 2026
- evcraddock/erikcraddock.me
Creates a new CLI release tag from conventional commits by analyzing the bump level, updating cli/package.json, tagging, and pushing.
About
Determines the semantic version bump from conventional commits touching cli/, then runs a release script that bumps, commits, tags, pushes, and verifies the tag on remote. A developer uses it to cut a new cli-v* release that triggers GitHub Actions binary builds.
- Maps feat/fix/BREAKING commits to major/minor/patch bumps
- Verifies the tag reached remote to catch silent push failures
Cli Release by the numbers
- 1 all-time installs (skills.sh)
- Ranked #210 of 248 Release Management skills by installs in the Skillselion catalog
- Data as of Jul 8, 2026 (Skillselion catalog sync)
npx skills add https://github.com/evcraddock/erikcraddock.me --skill cli-releaseAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| Last updated | July 2, 2026 |
| Repository | evcraddock/erikcraddock.me ↗ |
What it does
Creates a new CLI release tag from conventional commits by analyzing the bump level, updating cli/package.json, tagging, and pushing.
Files
CLI Release
Create a new CLI release tag (cli-v*) based on conventional commits since the last release.
Quick Reference
# The script handles: version bump, commit, tag, push, verify
# Path: ./scripts/release.sh (relative to this skill directory)
.pi/skills/cli-release/scripts/release.sh patch # Bug fixes
.pi/skills/cli-release/scripts/release.sh minor # New features
.pi/skills/cli-release/scripts/release.sh major # Breaking changes
.pi/skills/cli-release/scripts/release.sh 1.2.3 # Explicit versionInstructions
1. Verify on main branch
git branch --show-current
git fetch origin main
git log origin/main..HEAD --oneline- Must be on
main - No unpushed commits (if any exist, stop and tell user to push or create PR)
2. Get changes since last release
LATEST_TAG=$(git tag --list 'cli-v*' --sort=-v:refname | head -1)
git log ${LATEST_TAG}..HEAD --oneline -- cli/Look at commits that touch the cli/ directory.
If no CLI commits since last tag, inform user and stop.
3. Analyze commits for version bump
MAJOR (breaking): BREAKING CHANGE: in message, or feat!:, fix!: MINOR (feature): feat: prefix PATCH (fix): fix:, perf: prefix
Other types (docs, style, refactor, test, chore, ci, build) don't bump alone.
Priority: MAJOR > MINOR > PATCH. Default to PATCH if unclear.
4. Present options to user
Show:
- Current version
- CLI commits since last release
- Recommended bump and why
Ask user to choose:
- Recommended version (e.g., "0.5.0 - Minor (Recommended)")
- Alternative bumps if applicable
- Cancel
5. Run the release script
Once user chooses, run ./scripts/release.sh from this skill's directory:
.pi/skills/cli-release/scripts/release.sh <patch|minor|major>The script handles:
1. Validates on main with no unpushed commits 2. Updates cli/package.json 3. Commits the version bump 4. Creates annotated tag 5. Pushes commit and tag 6. Verifies tag exists on remote (fixes silent push failures)
6. Confirm success
After the script completes, note that GitHub Actions will:
- Build CLI binaries for linux-x64, darwin-x64, darwin-arm64
- Create GitHub release with binaries
- Update install script availability
Important Notes
- NEVER force push or use
--forceflags - The script creates annotated tags automatically
- The script verifies the tag was pushed (checks remote)
- If script fails, read the error and do not retry blindly
- CLI version lives in
cli/package.json, not root package.json
#!/usr/bin/env bash
#
# Create a CLI release with proper version bumping and verification.
#
# Usage:
# ./scripts/release.sh patch|minor|major
# ./scripts/release.sh <version> (e.g., 1.2.3)
#
# This script:
# 1. Validates we're on main with no unpushed commits
# 2. Calculates or validates the new version
# 3. Updates cli/package.json
# 4. Commits the version bump
# 5. Creates an annotated tag
# 6. Pushes commit and tag to origin
# 7. VERIFIES tag exists on remote (catches silent push failures)
#
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m' # No Color
die() {
echo -e "${RED}Error: $1${NC}" >&2
exit 1
}
info() {
echo -e "${GREEN}$1${NC}"
}
warn() {
echo -e "${YELLOW}$1${NC}"
}
# Validate arguments
if [[ $# -ne 1 ]]; then
echo "Usage: $0 patch|minor|major|<version>"
echo ""
echo "Examples:"
echo " $0 patch # 0.4.0 -> 0.4.1"
echo " $0 minor # 0.4.0 -> 0.5.0"
echo " $0 major # 0.4.0 -> 1.0.0"
echo " $0 0.5.0 # Explicit version"
exit 1
fi
BUMP_ARG="$1"
# Step 1: Validate we're on main
CURRENT_BRANCH=$(git branch --show-current)
if [[ "$CURRENT_BRANCH" != "main" ]]; then
die "Must be on main branch (currently on '$CURRENT_BRANCH')"
fi
# Step 2: Fetch and check for unpushed commits
git fetch origin main --quiet
UNPUSHED=$(git log origin/main..HEAD --oneline)
if [[ -n "$UNPUSHED" ]]; then
die "Unpushed commits on main:\n$UNPUSHED\n\nPush these or create a PR first."
fi
# Step 3: Get current version from latest tag
LATEST_TAG=$(git tag --list 'cli-v*' --sort=-v:refname | head -1)
if [[ -z "$LATEST_TAG" ]]; then
CURRENT_VERSION="0.0.0"
else
CURRENT_VERSION="${LATEST_TAG#cli-v}"
fi
info "Current version: $CURRENT_VERSION"
# Parse current version
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION"
# Step 4: Calculate new version
if [[ "$BUMP_ARG" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
# Explicit version provided
NEW_VERSION="$BUMP_ARG"
elif [[ "$BUMP_ARG" == "patch" ]]; then
NEW_VERSION="$MAJOR.$MINOR.$((PATCH + 1))"
elif [[ "$BUMP_ARG" == "minor" ]]; then
NEW_VERSION="$MAJOR.$((MINOR + 1)).0"
elif [[ "$BUMP_ARG" == "major" ]]; then
NEW_VERSION="$((MAJOR + 1)).0.0"
else
die "Invalid argument: $BUMP_ARG (expected patch|minor|major or X.Y.Z)"
fi
NEW_TAG="cli-v$NEW_VERSION"
info "New version: $NEW_VERSION (tag: $NEW_TAG)"
# Check if tag already exists locally
if git tag --list | grep -q "^$NEW_TAG$"; then
die "Tag $NEW_TAG already exists locally"
fi
# Check if tag already exists on remote
if git ls-remote --tags origin | grep -q "refs/tags/$NEW_TAG$"; then
die "Tag $NEW_TAG already exists on remote"
fi
# Step 5: Update cli/package.json
if [[ ! -f cli/package.json ]]; then
die "cli/package.json not found"
fi
CURRENT_PKG_VERSION=$(jq -r .version cli/package.json)
if [[ "$CURRENT_PKG_VERSION" != "$NEW_VERSION" ]]; then
info "Updating cli/package.json: $CURRENT_PKG_VERSION -> $NEW_VERSION"
jq --arg v "$NEW_VERSION" '.version = $v' cli/package.json > cli/package.json.tmp
mv cli/package.json.tmp cli/package.json
else
warn "cli/package.json already at $NEW_VERSION"
fi
# Step 6: Commit the version bump (if there are changes)
if ! git diff --quiet cli/package.json; then
git add cli/package.json
git commit -m "chore(cli): bump version to $NEW_VERSION"
info "Committed version bump"
else
warn "No changes to commit (cli/package.json unchanged)"
fi
# Step 7: Create annotated tag
git tag -a "$NEW_TAG" -m "CLI Release $NEW_TAG"
info "Created tag: $NEW_TAG"
# Step 8: Push commit and tag
info "Pushing to origin..."
git push origin main --follow-tags
# Step 9: VERIFY tag exists on remote
# This catches silent push failures (e.g., SSH timeout during pre-push hooks)
info "Verifying tag on remote..."
sleep 2 # Brief pause to allow remote to update
if ! git ls-remote --tags origin | grep -q "refs/tags/$NEW_TAG$"; then
die "Tag $NEW_TAG was NOT pushed to remote!\n\nThis can happen if pre-push hooks timeout.\nManually push with: git push origin $NEW_TAG"
fi
info ""
info "✅ Released $NEW_TAG"
info "✅ Verified tag exists on remote"
info ""
info "GitHub Actions will now build CLI binaries and create a release."