Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 100 additions & 89 deletions scripts/release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,93 +12,103 @@ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

# --- Helpers ---

die() { echo "error: $*" >&2; exit 1; }
die() {
echo "error: $*" >&2
exit 1
}

sedi() {
local expr="$1"
local file="$2"
sed -i.bak -e "$expr" "$file"
rm -f "${file}.bak"
}

get_current_version() {
grep '^version = ' "$PROJECT_ROOT/pyproject.toml" | sed 's/version = "\(.*\)"/\1/'
grep '^version = ' "$PROJECT_ROOT/pyproject.toml" | sed 's/version = "\(.*\)"/\1/'
}

# Convert Python version (0.1.0rc1) to npm semver (0.1.0-rc.1)
to_npm_version() {
local v="$1"
if [[ "$v" =~ ^([0-9]+\.[0-9]+\.[0-9]+)rc([0-9]+)$ ]]; then
echo "${BASH_REMATCH[1]}-rc.${BASH_REMATCH[2]}"
else
echo "$v"
fi
local v="$1"
if [[ "$v" =~ ^([0-9]+\.[0-9]+\.[0-9]+)rc([0-9]+)$ ]]; then
echo "${BASH_REMATCH[1]}-rc.${BASH_REMATCH[2]}"
else
echo "$v"
fi
}

calculate_next_version() {
local current="$1"
local bump_type="$2"

case "$bump_type" in
rc)
if [[ "$current" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)rc([0-9]+)$ ]]; then
# Already an RC: increment RC number
local major="${BASH_REMATCH[1]}"
local minor="${BASH_REMATCH[2]}"
local patch="${BASH_REMATCH[3]}"
local rc="${BASH_REMATCH[4]}"
echo "${major}.${minor}.${patch}rc$((rc + 1))"
elif [[ "$current" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
# Stable: bump minor, start RC1
local major="${BASH_REMATCH[1]}"
local minor="${BASH_REMATCH[2]}"
local patch="${BASH_REMATCH[3]}"
echo "${major}.$((minor + 1)).${patch}rc1"
else
die "cannot parse version: $current"
fi
;;
stable)
if [[ "$current" =~ ^([0-9]+\.[0-9]+\.[0-9]+)rc[0-9]+$ ]]; then
echo "${BASH_REMATCH[1]}"
else
die "current version ($current) is not an RC — nothing to promote"
fi
;;
*)
# Explicit version provided
if [[ "$bump_type" =~ ^[0-9]+\.[0-9]+\.[0-9]+(rc[0-9]+)?$ ]]; then
echo "$bump_type"
else
die "invalid version format: $bump_type (expected X.Y.Z or X.Y.Zrc#)"
fi
;;
esac
local current="$1"
local bump_type="$2"

case "$bump_type" in
rc)
if [[ "$current" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)rc([0-9]+)$ ]]; then
# Already an RC: increment RC number
local major="${BASH_REMATCH[1]}"
local minor="${BASH_REMATCH[2]}"
local patch="${BASH_REMATCH[3]}"
local rc="${BASH_REMATCH[4]}"
echo "${major}.${minor}.${patch}rc$((rc + 1))"
elif [[ "$current" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
# Stable: bump minor, start RC1
local major="${BASH_REMATCH[1]}"
local minor="${BASH_REMATCH[2]}"
local patch="${BASH_REMATCH[3]}"
echo "${major}.$((minor + 1)).${patch}rc1"
else
die "cannot parse version: $current"
fi
;;
stable)
if [[ "$current" =~ ^([0-9]+\.[0-9]+\.[0-9]+)rc[0-9]+$ ]]; then
echo "${BASH_REMATCH[1]}"
else
die "current version ($current) is not an RC — nothing to promote"
fi
;;
*)
# Explicit version provided
if [[ "$bump_type" =~ ^[0-9]+\.[0-9]+\.[0-9]+(rc[0-9]+)?$ ]]; then
echo "$bump_type"
else
die "invalid version format: $bump_type (expected X.Y.Z or X.Y.Zrc#)"
fi
;;
esac
}

update_version_files() {
local new_version="$1"
local npm_version
npm_version="$(to_npm_version "$new_version")"
local new_version="$1"
local npm_version
npm_version="$(to_npm_version "$new_version")"

echo "updating versions to $new_version (npm: $npm_version)"
echo "updating versions to $new_version (npm: $npm_version)"

# 1. pyproject.toml
sed -i '' "s/^version = \".*\"/version = \"$new_version\"/" "$PROJECT_ROOT/pyproject.toml"
# 1. pyproject.toml
sedi "s/^version = \".*\"/version = \"$new_version\"/" "$PROJECT_ROOT/pyproject.toml"

# 2. parallel_web_tools/__init__.py
sed -i '' "s/__version__ = \".*\"/__version__ = \"$new_version\"/" "$PROJECT_ROOT/parallel_web_tools/__init__.py"
# 2. parallel_web_tools/__init__.py
sedi "s/__version__ = \".*\"/__version__ = \"$new_version\"/" "$PROJECT_ROOT/parallel_web_tools/__init__.py"

# 3. bigquery cloud function requirements.txt
sed -i '' "s/parallel-web-tools>=.*/parallel-web-tools>=$new_version/" \
"$PROJECT_ROOT/parallel_web_tools/integrations/bigquery/cloud_function/requirements.txt"
# 3. bigquery cloud function requirements.txt
sedi "s/parallel-web-tools>=.*/parallel-web-tools>=$new_version/" \
"$PROJECT_ROOT/parallel_web_tools/integrations/bigquery/cloud_function/requirements.txt"

# 5. npm/package.json
sed -i '' "s/\"version\": \".*\"/\"version\": \"$npm_version\"/" "$PROJECT_ROOT/npm/package.json"
# 5. npm/package.json
sedi "s/\"version\": \".*\"/\"version\": \"$npm_version\"/" "$PROJECT_ROOT/npm/package.json"
}

# --- Main ---

if [[ $# -lt 1 ]]; then
echo "usage: ./scripts/release.sh <rc|stable|X.Y.Z>"
echo ""
echo " rc bump to next release candidate"
echo " stable promote current RC to stable"
echo " X.Y.Z set explicit version"
exit 1
echo "usage: ./scripts/release.sh <rc|stable|X.Y.Z>"
echo ""
echo " rc bump to next release candidate"
echo " stable promote current RC to stable"
echo " X.Y.Z set explicit version"
exit 1
fi

BUMP_TYPE="$1"
Expand All @@ -109,7 +119,7 @@ NPM_VERSION="$(to_npm_version "$NEW_VERSION")"
# Determine if this is a prerelease
IS_PRERELEASE=false
if [[ "$NEW_VERSION" =~ rc ]]; then
IS_PRERELEASE=true
IS_PRERELEASE=true
fi

echo ""
Expand All @@ -121,24 +131,24 @@ echo ""

# Safety checks
if [[ -n "$(git status --porcelain)" ]]; then
die "working tree is not clean — commit or stash changes first"
die "working tree is not clean — commit or stash changes first"
fi

CURRENT_BRANCH="$(git branch --show-current)"
if [[ "$CURRENT_BRANCH" != "main" ]]; then
die "must be on main branch (currently on $CURRENT_BRANCH)"
die "must be on main branch (currently on $CURRENT_BRANCH)"
fi

# Check if tag already exists
if git rev-parse "v$NEW_VERSION" >/dev/null 2>&1; then
die "tag v$NEW_VERSION already exists"
die "tag v$NEW_VERSION already exists"
fi

# Confirm
read -r -p "proceed? [y/N] " confirm
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
echo "aborted."
exit 0
echo "aborted."
exit 0
fi

# Update files
Expand All @@ -148,23 +158,23 @@ update_version_files "$NEW_VERSION"
BRANCH="release/v$NEW_VERSION"
git checkout -b "$BRANCH"
git add \
pyproject.toml \
parallel_web_tools/__init__.py \
parallel_web_tools/integrations/bigquery/cloud_function/requirements.txt \
npm/package.json
pyproject.toml \
parallel_web_tools/__init__.py \
parallel_web_tools/integrations/bigquery/cloud_function/requirements.txt \
npm/package.json

# Commit — if pre-commit hooks modify files (e.g. uv.lock), re-stage and retry
if ! git commit -m "chore: bump version to $NEW_VERSION"; then
echo "pre-commit hooks modified files, re-staging and retrying..."
git add \
pyproject.toml \
parallel_web_tools/__init__.py \
tests/test_cli.py \
parallel_web_tools/integrations/bigquery/cloud_function/requirements.txt \
npm/package.json
# Also stage any lock files updated by hooks
git diff --name-only | xargs -r git add
git commit -m "chore: bump version to $NEW_VERSION"
echo "pre-commit hooks modified files, re-staging and retrying..."
git add \
pyproject.toml \
parallel_web_tools/__init__.py \
tests/test_cli.py \
parallel_web_tools/integrations/bigquery/cloud_function/requirements.txt \
npm/package.json
# Also stage any lock files updated by hooks
git diff --name-only | xargs -r git add
git commit -m "chore: bump version to $NEW_VERSION"
fi

echo ""
Expand All @@ -173,12 +183,13 @@ git push -u origin "$BRANCH"

PRERELEASE_NOTE=""
if [[ "$IS_PRERELEASE" == "true" ]]; then
PRERELEASE_NOTE=" (pre-release)"
PRERELEASE_NOTE=" (pre-release)"
fi

gh pr create \
--title "chore: bump version to $NEW_VERSION" \
--body "$(cat <<EOF
--title "chore: bump version to $NEW_VERSION" \
--body "$(
cat <<EOF
## Release $NEW_VERSION${PRERELEASE_NOTE}

Bumps version from $CURRENT_VERSION to $NEW_VERSION.
Expand All @@ -190,7 +201,7 @@ When this PR is merged to main, the release workflow will automatically:
- Publish to PyPI
- Publish to npm
EOF
)"
)"

echo ""
echo "done! merge the PR to trigger the release."
Loading