#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd -P)" BUMP_VERSION="$SCRIPT_DIR/bump-version.sh" REMOTE="${RELEASE_REMOTE:-origin}" REPO="" DRY_RUN=0 SKIP_CHECKS=0 RUN_TESTS=0 SKIP_PUSH=0 SKIP_RELEASE=0 PUBLISH=0 BUMP_ARGS=() usage() { cat <<'EOF' Usage: scripts/release/create-github-release.sh [options] scripts/release/create-github-release.sh --set-version X.Y.Z [options] Runs the repo release-prep flow: 1. calls scripts/release/bump-version.sh 2. bundles generated skill/plugin outputs 3. runs generated-output and plugin checks 4. commits the release version bump 5. creates and pushes the git tag 6. creates a draft GitHub Release for that tag Options: --from-version X.Y.Z Pass through to bump-version.sh. --set-version X.Y.Z Pass through to bump-version.sh instead of bumping a part. --dry-run Show the planned bump and release steps without writing. --skip-checks Skip bundle/check scripts after bumping. --run-tests Run scripts/test/test.sh before committing. --skip-push Do not push the branch or tag. --skip-release Do not create the GitHub Release. --publish Publish the GitHub Release immediately instead of creating a draft. --remote NAME Git remote to push to. Defaults to origin. --repo OWNER/REPO GitHub repository for gh release commands. -h, --help Show this help. The worktree must be clean before a non-dry-run release prep starts. This is a manual all-in-one fallback. Prefer the Release GitHub Actions workflow for normal releases. EOF } die() { echo "error: $*" >&2 exit 1 } require_command() { command -v "$1" >/dev/null 2>&1 || die "$1 is required" } while [ "$#" -gt 0 ]; do case "$1" in major|minor|patch) BUMP_ARGS+=("$1") ;; --from-version|--set-version|--remote|--repo) [ "$#" -ge 2 ] || die "$1 requires a value" case "$1" in --remote) REMOTE="$2" ;; --repo) REPO="$2" ;; *) BUMP_ARGS+=("$1" "$2") ;; esac shift ;; --dry-run) DRY_RUN=1 ;; --skip-checks) SKIP_CHECKS=1 ;; --run-tests) RUN_TESTS=1 ;; --skip-push) SKIP_PUSH=1 ;; --skip-release) SKIP_RELEASE=1 ;; --publish) PUBLISH=1 ;; -h|--help) usage exit 0 ;; *) die "unknown argument: $1" ;; esac shift done cd "$REPO_ROOT" require_command git [ -x "$BUMP_VERSION" ] || die "missing executable bump-version script: $BUMP_VERSION" if [ "$SKIP_PUSH" -eq 1 ] && [ "$SKIP_RELEASE" -eq 0 ]; then die "--skip-push requires --skip-release because gh release create --verify-tag needs the remote tag" fi bump_output="$("$BUMP_VERSION" "${BUMP_ARGS[@]}" --dry-run)" version_pair="$(printf '%s\n' "$bump_output" | sed -nE 's/^Version bump: ([0-9]+\.[0-9]+\.[0-9]+) -> ([0-9]+\.[0-9]+\.[0-9]+)$/\1 \2/p')" [ -n "$version_pair" ] || die "could not determine target version from bump-version.sh" read -r current_version next_version <<<"$version_pair" if [ "$DRY_RUN" -eq 1 ]; then printf '%s\n' "$bump_output" echo "" echo "Release prep dry run for $current_version -> $next_version:" echo "- Bundle generated outputs with scripts/bundle/bundle.sh" if [ "$SKIP_CHECKS" -eq 0 ]; then echo "- Check generated outputs and plugin metadata with scripts/bundle/bundle.sh --check" fi if [ "$RUN_TESTS" -eq 1 ]; then echo "- Run scripts/test/test.sh" fi echo "- Commit canonical release version and bundled outputs as: Release $next_version" echo "- Create git tag: $next_version" if [ "$SKIP_PUSH" -eq 0 ]; then echo "- Push current branch and tag to $REMOTE" fi if [ "$SKIP_RELEASE" -eq 0 ]; then if [ "$PUBLISH" -eq 1 ]; then echo "- Create published GitHub Release: $next_version" else echo "- Create draft GitHub Release: $next_version" fi fi exit 0 fi branch="$(git symbolic-ref -q --short HEAD || true)" [ -n "$branch" ] || die "release prep must run on a named branch, not detached HEAD" if ! git diff --quiet || ! git diff --cached --quiet || [ -n "$(git ls-files --others --exclude-standard)" ]; then die "working tree must be clean before release prep starts" fi if git rev-parse -q --verify "refs/tags/$next_version" >/dev/null; then die "local tag already exists: $next_version" fi if git ls-remote --exit-code --tags "$REMOTE" "refs/tags/$next_version" >/dev/null 2>&1; then die "remote tag already exists on $REMOTE: $next_version" else ls_remote_status=$? if [ "$ls_remote_status" -ne 2 ]; then die "failed to check remote tags on $REMOTE" fi fi if [ "$SKIP_RELEASE" -eq 0 ]; then require_command gh gh_repo_args=() if [ -n "$REPO" ]; then gh_repo_args=(-R "$REPO") fi if gh "${gh_repo_args[@]}" release view "$next_version" >/dev/null 2>&1; then die "GitHub Release already exists: $next_version" fi fi "$BUMP_VERSION" "${BUMP_ARGS[@]}" "$REPO_ROOT/scripts/bundle/bundle.sh" if [ "$SKIP_CHECKS" -eq 0 ]; then "$REPO_ROOT/scripts/bundle/bundle.sh" --check fi if [ "$RUN_TESTS" -eq 1 ]; then "$REPO_ROOT/scripts/test/test.sh" fi git add -A if git diff --cached --quiet; then die "release prep did not produce any committed changes" fi git commit -m "Release $next_version" git tag "$next_version" if [ "$SKIP_PUSH" -eq 0 ]; then git push "$REMOTE" "$branch" git push "$REMOTE" "$next_version" fi if [ "$SKIP_RELEASE" -eq 0 ]; then release_args=(release create "$next_version" --verify-tag --generate-notes --title "$next_version") if [ "$PUBLISH" -eq 0 ]; then release_args+=(--draft) fi gh "${gh_repo_args[@]}" "${release_args[@]}" fi echo "Prepared release $next_version."