diff --git a/.claude/skills/prepare-release/SKILL.md b/.claude/skills/prepare-release/SKILL.md new file mode 100644 index 0000000..873fd47 --- /dev/null +++ b/.claude/skills/prepare-release/SKILL.md @@ -0,0 +1,35 @@ +--- +name: prepare-release +description: Prepare release branch with version updates and create PR for Optimizely Flutter SDK +compatibility: Requires Flutter SDK project +--- + +# Prepare Release - Step 1 of Release Workflow + +Creates prep branch, updates versions, fetches PR summaries, and creates PR. + +## Usage + +```bash +/prepare-release [tickets or PRs...] +``` + +## Examples + +```bash +/prepare-release patch #103 #105 +/prepare-release minor FSSDK-12345 FSSDK-12346 +/prepare-release 3.5.0 #103 +``` + +## Execution + +Runs: `./.claude/skills/prepare-release/skill.sh [refs...]` + +## Features + +- ✅ Fetches PR titles automatically for CHANGELOG +- ✅ Updates 4 files: pubspec.yaml, package_info.dart, README.md, CHANGELOG.md +- ✅ Creates prep branch: `{username}/prep-{version}` +- ✅ Creates PR with proper formatting + diff --git a/.claude/skills/prepare-release/skill.sh b/.claude/skills/prepare-release/skill.sh new file mode 100755 index 0000000..63aeda9 --- /dev/null +++ b/.claude/skills/prepare-release/skill.sh @@ -0,0 +1,175 @@ +#!/bin/bash +set -euo pipefail + +# Color codes +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +error() { echo -e "${RED}✗ Error: $1${NC}" >&2; exit 1; } +success() { echo -e "${GREEN}✓ $1${NC}"; } +info() { echo -e "${YELLOW}ℹ $1${NC}"; } + +# Parse arguments +VERSION_OR_BUMP="${1:-}" +shift || true + +# Collect references (tickets or PRs) +REFERENCES=() +REF_TYPE="" + +for arg in "$@"; do + arg=$(echo "$arg" | xargs) + if [[ "$arg" =~ ^#?[0-9]+$ ]]; then + REFERENCES+=("${arg#\#}") + REF_TYPE="pr" + elif [[ "$arg" =~ ^[A-Z]+-[0-9]+$ ]]; then + REFERENCES+=("$arg") + REF_TYPE="ticket" + fi +done + +[[ -z "$VERSION_OR_BUMP" ]] && error "Usage: /prepare-release [tickets or PRs...]" + +# Get current version and calculate new version +CURRENT_VERSION=$(grep '^version:' pubspec.yaml | awk '{print $2}') +info "Current version: $CURRENT_VERSION" + +if [[ "$VERSION_OR_BUMP" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + NEW_VERSION="$VERSION_OR_BUMP" +else + IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION" + case "$VERSION_OR_BUMP" in + major) NEW_VERSION="$((MAJOR + 1)).0.0" ;; + minor) NEW_VERSION="${MAJOR}.$((MINOR + 1)).0" ;; + patch) NEW_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))" ;; + *) error "Invalid version: $VERSION_OR_BUMP" ;; + esac +fi + +info "New version: $NEW_VERSION" + +# Pre-flight checks +info "Running pre-flight checks..." +[[ "$(git branch --show-current)" != "master" ]] && error "Must be on master branch" +git diff-index --quiet HEAD -- || error "Working tree has uncommitted changes. Please commit or stash first." +success "Git status clean" + +# Create prep branch +GIT_USERNAME=$(git config user.name | tr '[:upper:]' '[:lower:]' | tr ' ' '-') +PREP_BRANCH="${GIT_USERNAME}/prep-${NEW_VERSION}" +git show-ref --verify --quiet "refs/heads/$PREP_BRANCH" && error "Branch $PREP_BRANCH already exists" +git checkout -b "$PREP_BRANCH" +success "Branch created: $PREP_BRANCH" + +# Update version files +sed -i.bak "s/^version: .*/version: $NEW_VERSION/" pubspec.yaml && rm pubspec.yaml.bak +sed -i.bak "s/static const String version = '.*';/static const String version = '$NEW_VERSION';/" lib/package_info.dart && rm lib/package_info.dart.bak +sed -i.bak "s/optimizely_flutter_sdk: \^.*/optimizely_flutter_sdk: ^$NEW_VERSION/" README.md && rm README.md.bak + +# Update CHANGELOG +CURRENT_DATE=$(date "+%B %d, %Y") + +# Build CHANGELOG entries from PRs +CHANGELOG_ENTRIES="" +if [[ ${#REFERENCES[@]} -gt 0 && "$REF_TYPE" == "pr" ]]; then + info "Fetching PR details for CHANGELOG..." + for pr in "${REFERENCES[@]}"; do + PR_TITLE=$(gh pr view "$pr" --json title --jq '.title' 2>/dev/null || echo "") + if [[ -n "$PR_TITLE" ]]; then + # Remove ticket prefix from title if present (e.g., [FSSDK-12503]) + PR_TITLE=$(echo "$PR_TITLE" | sed 's/^\[[A-Z]*-[0-9]*\] //') + # Capitalize first letter + PR_TITLE="$(echo ${PR_TITLE:0:1} | tr '[:lower:]' '[:upper:]')${PR_TITLE:1}" + CHANGELOG_ENTRIES="${CHANGELOG_ENTRIES}* ${PR_TITLE} ([#${pr}](https://github.com/optimizely/optimizely-flutter-sdk/pull/${pr})) +" + fi + done +fi + +# If no PRs or PR fetch failed, use placeholder +if [[ -z "$CHANGELOG_ENTRIES" ]]; then + CHANGELOG_ENTRIES="* Update with actual release notes +" +fi + +# Detect section header (Bug Fixes, New Features, etc.) from PR titles +SECTION_HEADER="### Bug Fixes" +if echo "$CHANGELOG_ENTRIES" | grep -qi "feat:"; then + SECTION_HEADER="### New Features" +elif echo "$CHANGELOG_ENTRIES" | grep -qi "enhancement"; then + SECTION_HEADER="### Enhancements" +fi + +cat > /tmp/changelog_new.md << EOF +# Optimizely Flutter SDK Changelog + +## ${NEW_VERSION} +${CURRENT_DATE} + +${SECTION_HEADER} +${CHANGELOG_ENTRIES} +EOF +tail -n +2 CHANGELOG.md >> /tmp/changelog_new.md +mv /tmp/changelog_new.md CHANGELOG.md + +success "Updated all version files with CHANGELOG" + +# Commit and push +git add pubspec.yaml lib/package_info.dart README.md CHANGELOG.md +git commit -m "chore: prep for release ${NEW_VERSION} + +Co-Authored-By: Claude Sonnet 4.5 " +git push -u origin "$PREP_BRANCH" +success "Pushed to origin/$PREP_BRANCH" + +# Create PR +PR_TITLE="prep for release ${NEW_VERSION}" +[[ ${#REFERENCES[@]} -gt 0 && "$REF_TYPE" == "ticket" ]] && PR_TITLE="[$(IFS=,; echo "${REFERENCES[*]}")] $PR_TITLE" + +PR_BODY="## Summary + +Prepare for release ${NEW_VERSION} + +**Version Updates:** +- ✅ pubspec.yaml +- ✅ package_info.dart +- ✅ README.md +- ✅ CHANGELOG.md + +⚠️ Please update CHANGELOG.md with actual release notes before merging. + +## Test Plan +- All CI checks must pass (4 workflows) +- Verify version numbers match across all files" + +if [[ ${#REFERENCES[@]} -gt 0 ]]; then + if [[ "$REF_TYPE" == "ticket" ]]; then + PR_BODY="${PR_BODY} + +## Issues" + for ref in "${REFERENCES[@]}"; do PR_BODY="${PR_BODY} +- ${ref}"; done + else + PR_BODY="${PR_BODY} + +## Related PRs" + for ref in "${REFERENCES[@]}"; do PR_BODY="${PR_BODY} +- #${ref}"; done + fi +fi + +PR_URL=$(gh pr create --title "$PR_TITLE" --body "$PR_BODY" --base master --head "$PREP_BRANCH") +success "Pull request created: $PR_URL" + +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo -e "${GREEN}✓ Release preparation complete!${NC}" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" +echo "Version: $CURRENT_VERSION → $NEW_VERSION" +echo "Branch: $PREP_BRANCH" +[[ ${#REFERENCES[@]} -gt 0 ]] && echo "References: ${REFERENCES[*]}" +echo "PR: $PR_URL" +echo "" diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md new file mode 100644 index 0000000..c629639 --- /dev/null +++ b/.claude/skills/release/SKILL.md @@ -0,0 +1,33 @@ +--- +name: release +description: Publish to pub.dev and create GitHub release (Step 2 of release workflow) +compatibility: Requires Flutter SDK and merged prepare-release PR +--- + +# Release - Step 2 of Release Workflow + +Publishes package to pub.dev and creates draft GitHub release. + +## Usage + +```bash +/release [--prerelease] +``` + +## Prerequisites + +- `/prepare-release` PR merged into master +- On master branch with clean working tree + +## Execution + +Runs: `./.claude/skills/release/skill.sh [--prerelease]` + +## What It Does + +1. ✅ Validates version matches pubspec.yaml +2. ✅ Runs `flutter packages pub publish --dry-run` +3. ✅ Publishes to pub.dev +4. ✅ Extracts CHANGELOG for this version +5. ✅ Creates draft GitHub release with tag `vX.Y.Z` + diff --git a/.claude/skills/release/skill.sh b/.claude/skills/release/skill.sh new file mode 100755 index 0000000..590c90e --- /dev/null +++ b/.claude/skills/release/skill.sh @@ -0,0 +1,79 @@ +#!/bin/bash +set -euo pipefail + +# Optimizely Flutter SDK - Release (Step 2) +# Publishes to pub.dev and creates GitHub release + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +error() { echo -e "${RED}✗ Error: $1${NC}" >&2; exit 1; } +success() { echo -e "${GREEN}✓ $1${NC}"; } +info() { echo -e "${YELLOW}ℹ $1${NC}"; } + +VERSION="${1:-}" +PRERELEASE="" +[[ "${2:-}" == "--prerelease" ]] && PRERELEASE="--prerelease" + +[[ -z "$VERSION" ]] && error "Usage: /release [--prerelease]" +[[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-z0-9]+)?$ ]] && error "Invalid version: $VERSION" + +# Pre-flight checks +info "Running pre-flight checks..." +[[ "$(git branch --show-current)" != "master" ]] && error "Must be on master branch" +git diff-index --quiet HEAD -- || error "Working tree has uncommitted changes" + +PUBSPEC_VERSION=$(grep '^version:' pubspec.yaml | awk '{print $2}') +[[ "$PUBSPEC_VERSION" != "$VERSION" ]] && error "Version mismatch! pubspec.yaml: $PUBSPEC_VERSION, requested: $VERSION" + +success "Pre-flight checks passed" + +# Dry run +info "Running pub publish dry-run..." +if ! flutter packages pub publish --dry-run 2>&1 | tee /tmp/pub-dry-run.log; then + echo "" + read -p "Dry-run found warnings. Continue? (y/N) " -n 1 -r + echo + [[ ! $REPLY =~ ^[Yy]$ ]] && error "Aborted by user" +fi + +# Publish +info "Publishing to pub.dev..." +flutter packages pub publish || error "Publishing failed" +success "Published to pub.dev!" + +# Extract CHANGELOG +CHANGELOG_CONTENT=$(awk "/^## $VERSION/,/^## / {print}" CHANGELOG.md | sed '$d' | sed '1d') +[[ -z "$CHANGELOG_CONTENT" ]] && CHANGELOG_CONTENT="Release $VERSION\n\nSee CHANGELOG.md for details." + +RELEASE_NOTES="## $VERSION + +$CHANGELOG_CONTENT" + +# Create GitHub release +info "Creating GitHub draft release..." +GH_RELEASE_URL=$(gh release create "v${VERSION}" \ + --title "Release ${VERSION}" \ + --notes "$RELEASE_NOTES" \ + --target master \ + --draft \ + $PRERELEASE) + +success "GitHub draft release created!" + +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo -e "${GREEN}✓ Release complete!${NC}" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" +echo "Version: $VERSION" +echo "Pub.dev: https://pub.dev/packages/optimizely_flutter_sdk/versions/$VERSION" +echo "GitHub Release: $GH_RELEASE_URL" +echo "" +echo "Next Steps:" +echo "1. 🔍 Verify package on pub.dev (1-3 minutes)" +echo "2. ✏️ Review GitHub draft release" +echo "3. ✅ Publish GitHub release" +echo ""