diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml new file mode 100644 index 00000000..9083bfe2 --- /dev/null +++ b/.github/workflows/manual-release.yml @@ -0,0 +1,306 @@ +name: Manual Release + +on: + workflow_dispatch: + inputs: + title: + description: "Draft release title (e.g., v1.2.3)" + required: true + type: string + +permissions: + contents: write + +jobs: + draft-release: + runs-on: ubuntu-latest + steps: + - name: Resolve release details + id: release + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + + REPO_FULL_NAME="${{ github.repository }}" + TITLE_INPUT="${{ inputs.title }}" + + echo "Resolving draft release by title in $REPO_FULL_NAME..." + + # Validate title format (should be vX.Y.Z where X.Y.Z is a semver version) + if [[ ! "$TITLE_INPUT" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::Invalid title format. Expected format: vX.Y.Z (e.g., v1.2.3)" + echo "Title must be a semver version number prepended with 'v'." + exit 1 + fi + + echo "Title format validated: $TITLE_INPUT" + + # Get the version number without the 'v' prefix + VERSION="${TITLE_INPUT:1}" + + # Find an existing draft release by exact title match + if gh api \ + -H "Accept: application/vnd.github+json" \ + --paginate \ + "/repos/$REPO_FULL_NAME/releases" \ + > /tmp/releases_list.json 2>/dev/null; then + : + else + echo "::error::Failed to list releases for $REPO_FULL_NAME." + exit 1 + fi + + jq -r --arg TITLE "$TITLE_INPUT" \ + '[ .[] | select(.draft == true and .name == $TITLE) ] | first // empty' \ + /tmp/releases_list.json > /tmp/release.json + + if [ ! -s /tmp/release.json ]; then + echo "ERROR: No draft release found with title '$TITLE_INPUT' in $REPO_FULL_NAME." + exit 1 + fi + + echo "Release JSON:" + cat /tmp/release.json + + # Extract fields with jq + TAG_NAME=$(jq -r '.tag_name // .tagName' /tmp/release.json) + NAME=$(jq -r '.name' /tmp/release.json) + TARGET_COMMITISH=$(jq -r '.target_commitish // .targetCommitish' /tmp/release.json) + BODY=$(jq -r '.body' /tmp/release.json) + DRAFT=$(jq -r '.draft' /tmp/release.json) + + # Ensure the release is a draft + if [ "$DRAFT" != "true" ]; then + echo "::error::Release '$NAME' exists but is not a draft (draft=$DRAFT)." + echo "Please set the release to draft and rerun this workflow." + exit 1 + fi + + # Ensure the draft has a tag name we can use downstream + if [ -z "$TAG_NAME" ] || [ "$TAG_NAME" = "null" ]; then + echo "::error::Draft release '$NAME' has no tag name set." + echo "Please set a tag name on the draft release and rerun this workflow." + exit 1 + fi + + # Fallback for target_commitish: default to repository default branch if missing + if [ -z "$TARGET_COMMITISH" ] || [ "$TARGET_COMMITISH" = "null" ]; then + DEFAULT_BRANCH=$(gh repo view "$REPO_FULL_NAME" --json defaultBranchRef -q '.defaultBranchRef.name') + TARGET_COMMITISH="$DEFAULT_BRANCH" + fi + + # Fallbacks + if [ -z "$NAME" ] || [ "$NAME" = "null" ]; then NAME="$TAG_NAME"; fi + if [ -z "$BODY" ] || [ "$BODY" = "null" ]; then BODY="No release notes provided."; fi + + echo "tag_name=$TAG_NAME" >> "$GITHUB_OUTPUT" + echo "name=$NAME" >> "$GITHUB_OUTPUT" + echo "target_commitish=$TARGET_COMMITISH" >> "$GITHUB_OUTPUT" + echo "body=$BODY" >> "$GITHUB_OUTPUT" + echo "draft=$DRAFT" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + echo "Resolved: tag=$TAG_NAME, name=$NAME, target_commitish=$TARGET_COMMITISH, draft=$DRAFT version=$VERSION" + + - name: Manual dispatch triggered + run: | + echo "Manual draft release for tag: ${{ steps.release.outputs.tag_name }}" + + - uses: actions/create-github-app-token@v2 + id: app-token + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + + - name: Checkout target commitish with full history (needed to commit & tag) + uses: actions/checkout@v6 + with: + ref: ${{ steps.release.outputs.target_commitish }} + token: ${{ steps.app-token.outputs.token }} + fetch-depth: 0 + persist-credentials: true + + - name: Verify tag does not already exist + run: | + TAG_NAME="${{ steps.release.outputs.tag_name }}" + + echo "Checking if tag $TAG_NAME already exists..." + + # Check if tag exists locally (use show-ref to check specifically for tags) + if git show-ref --tags "$TAG_NAME" >/dev/null 2>&1; then + echo "::error::Tag $TAG_NAME already exists in the repository." + echo "Please use a different version number or delete the existing tag first." + exit 1 + fi + + # Check if tag exists on remote (use grep -F for literal string matching) + if git ls-remote --tags origin "$TAG_NAME" | grep -qF "refs/tags/$TAG_NAME"; then + echo "::error::Tag $TAG_NAME already exists on remote." + echo "Please use a different version number or delete the existing tag first." + exit 1 + fi + + echo "Tag $TAG_NAME does not exist. Proceeding with release." + + - name: Set up JDK 25 + uses: actions/setup-java@v5 + with: + java-version: "25" + distribution: "temurin" + cache: maven + cache-dependency-path: | + pom.xml + xapi-model/pom.xml + xapi-client/pom.xml + xapi-model-spring-boot-starter/pom.xml + server-id: central + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} + gpg-passphrase: MAVEN_GPG_PASSPHRASE + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Run Maven release:prepare + run: | + VERSION="${{ steps.release.outputs.version }}" + TAG_NAME="${{ steps.release.outputs.tag_name }}" + + echo "Preparing release version: $VERSION" + echo "Tag name: $TAG_NAME" + + # Run release:prepare with explicit release version + # Maven will automatically calculate the next development version + # Only prepare production modules, exclude all sample modules + # Pass -pl/-am to forked Maven invocations via -Darguments + ./mvnw -B release:prepare \ + -DreleaseVersion="${VERSION}" \ + -Dtag="${TAG_NAME}" \ + -DpushChanges=false \ + -Darguments="-pl xapi-model,xapi-client,xapi-model-spring-boot-starter -am" + env: + MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} + + - name: Run Maven release:perform + run: | + echo "Performing release and deploying to Maven Central" + + # Run release:perform to build and deploy + # Only release production modules, exclude all sample modules + # Pass -pl/-am to forked Maven invocations via -Darguments + ./mvnw -B release:perform \ + -DlocalCheckout=true \ + -DeployAtEnd=true \ + -Darguments="-pl xapi-model,xapi-client,xapi-model-spring-boot-starter -am" + env: + MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} + + - name: Push changes to target branch + run: | + TARGET_BRANCH="${{ steps.release.outputs.target_commitish }}" + TAG_NAME="${{ steps.release.outputs.tag_name }}" + + echo "Pushing changes to branch: $TARGET_BRANCH" + + # Push the commits created by release:prepare + if ! git push --force-with-lease origin "HEAD:${TARGET_BRANCH}"; then + echo "::error::Failed to push release commits to ${TARGET_BRANCH} due to branch divergence." + echo "The remote branch may have new commits. Please resolve the conflict manually:" + echo " 1. Fetch the latest changes: git fetch origin" + echo " 2. Rebase or merge as needed, then push again with --force-with-lease." + exit 1 + fi + + # Push the tag created by release:prepare + git push origin "$TAG_NAME" + + echo "Pushed release commits and tag to $TARGET_BRANCH" + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + + - name: Upload artifacts to draft release + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + set -euo pipefail + + REPO_FULL_NAME="${{ github.repository }}" + TAG="${{ steps.release.outputs.tag_name }}" + + echo "Uploading artifacts to draft release $TAG..." + + # Find and upload jar files from target directories + # Exclude SNAPSHOT jars, only include release artifacts + for module in xapi-client xapi-model xapi-model-spring-boot-starter; do + echo "Processing module: $module" + + # Upload all jar files (main, sources, javadoc, etc.) + for jar in "$module/target"/*.jar; do + # Skip if glob didn't match anything + [ -e "$jar" ] || continue + + # Skip SNAPSHOT jars + if [[ "$jar" == *-SNAPSHOT.jar ]]; then + echo "Skipping SNAPSHOT jar: $jar" + continue + fi + + echo "Uploading: $jar" + gh release upload "$TAG" "$jar" \ + --repo "$REPO_FULL_NAME" \ + --clobber + done + done + + echo "All artifacts uploaded successfully!" + + - name: Associate draft release with created tag + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + set -euo pipefail + + REPO_FULL_NAME="${{ github.repository }}" + TAG="${{ steps.release.outputs.tag_name }}" + + echo "Updating draft release to point to tag $TAG..." + + # Update the release to point to the new tag + gh release edit "$TAG" --repo "$REPO_FULL_NAME" --tag "$TAG" --draft + + echo "Draft release updated successfully!" + + - name: Publish GitHub release + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + set -euo pipefail + + REPO_FULL_NAME="${{ github.repository }}" + TAG="${{ steps.release.outputs.tag_name }}" + VERSION="${{ steps.release.outputs.version }}" + + echo "Publishing GitHub release for $TAG..." + + # Publish the release (remove draft status) + gh release edit "$TAG" --repo "$REPO_FULL_NAME" --draft=false + + echo "✅ Release $VERSION published successfully!" + echo "View at: https://github.com/$REPO_FULL_NAME/releases/tag/$TAG" + + - name: Workflow Summary + if: always() + run: | + echo "## Draft Release Workflow Summary" >> $GITHUB_STEP_SUMMARY + echo "**Version:** ${{ steps.release.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "**Tag:** ${{ steps.release.outputs.tag_name }}" >> $GITHUB_STEP_SUMMARY + echo "**Branch:** ${{ steps.release.outputs.target_commitish }}" >> $GITHUB_STEP_SUMMARY + echo "**Status:** ${{ job.status }}" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 4ce7e3c2..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,161 +0,0 @@ -# This workflow automates the release process when a release is created on GitHub -# It uses Maven release plugin to manage versions and deploys to Maven Central -# -# Usage: -# 1. Create a new release in GitHub UI with tag format: vX.Y.Z (e.g., v1.2.0) -# - Select the target branch (typically main) -# 2. This workflow will automatically: -# - Use Maven release:prepare to update versions and create release commits -# - Use Maven release:perform to build and deploy artifacts to Maven Central -# - Move the tag to point to the actual release commit -# - Push commits back to the originating branch - -name: Automated Release - -on: - release: - types: [created] # Trigger when release is created - -permissions: - contents: write # Required to push commits and update tags - -jobs: - release: - runs-on: ubuntu-latest - - steps: - - name: Validate release tag format - id: validate_tag - run: | - TAG_NAME="${{ github.event.release.tag_name }}" - echo "Release tag: $TAG_NAME" - - # Validate tag format (should be vX.Y.Z) - if [[ ! "$TAG_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "::error::Invalid tag format. Expected format: vX.Y.Z (e.g., v1.2.0)" - exit 1 - fi - - # Remove 'v' prefix to get the version number - VERSION="${TAG_NAME#v}" - echo "version=${VERSION}" >> $GITHUB_OUTPUT - echo "Extracted version: $VERSION" - - - name: Determine target branch - id: target_branch - run: | - # Use target_commitish to determine the originating branch - TARGET="${{ github.event.release.target_commitish }}" - - # If target_commitish is empty or a SHA, default to main - if [[ -z "$TARGET" ]] || [[ "$TARGET" =~ ^[0-9a-f]{40}$ ]]; then - TARGET="main" - fi - - echo "target_branch=${TARGET}" >> $GITHUB_OUTPUT - echo "Target branch: $TARGET" - - - name: Generate GitHub App Token - id: generate_token - uses: actions/create-github-app-token@v2 - with: - app-id: ${{ secrets.APP_ID }} - private-key: ${{ secrets.APP_PRIVATE_KEY }} - - - name: Checkout repository - uses: actions/checkout@v6 - with: - ref: ${{ steps.target_branch.outputs.target_branch }} - fetch-depth: 0 - token: ${{ steps.generate_token.outputs.token }} - - - name: Delete user-created tag - run: | - TAG_NAME="${{ github.event.release.tag_name }}" - - # Delete the tag created by the user (will be recreated by release:prepare) - git tag -d "$TAG_NAME" || true - git push origin ":refs/tags/$TAG_NAME" || true - - echo "Deleted user-created tag $TAG_NAME" - - - name: Set up JDK 25 - uses: actions/setup-java@v5 - with: - java-version: "25" - distribution: "temurin" - cache: maven - cache-dependency-path: | - pom.xml - xapi-model/pom.xml - xapi-client/pom.xml - xapi-model-spring-boot-starter/pom.xml - server-id: central - server-username: MAVEN_USERNAME - server-password: MAVEN_PASSWORD - gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} - gpg-passphrase: MAVEN_GPG_PASSPHRASE - - - name: Configure Git - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - - name: Run Maven release:prepare - run: | - VERSION="${{ steps.validate_tag.outputs.version }}" - TAG_NAME="${{ github.event.release.tag_name }}" - - echo "Preparing release version: $VERSION" - echo "Tag name: $TAG_NAME" - - # Run release:prepare with explicit release version - # Maven will automatically calculate the next development version - # Only prepare production modules, exclude all sample modules - # Pass -pl/-am to forked Maven invocations via -Darguments - ./mvnw -B release:prepare \ - -DreleaseVersion="${VERSION}" \ - -Dtag="${TAG_NAME}" \ - -DpushChanges=false \ - -Darguments="-pl xapi-model,xapi-client,xapi-model-spring-boot-starter -am" - env: - MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} - MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} - MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} - - - name: Run Maven release:perform - run: | - echo "Performing release and deploying to Maven Central" - - # Run release:perform to build and deploy - # Only release production modules, exclude all sample modules - # Pass -pl/-am to forked Maven invocations via -Darguments - ./mvnw -B release:perform \ - -DlocalCheckout=true \ - -DeployAtEnd=true \ - -Darguments="-pl xapi-model,xapi-client,xapi-model-spring-boot-starter -am" - env: - MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} - MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} - MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} - - - name: Push changes to originating branch - run: | - TARGET_BRANCH="${{ steps.target_branch.outputs.target_branch }}" - TAG_NAME="${{ github.event.release.tag_name }}" - - echo "Pushing changes to branch: $TARGET_BRANCH" - - # Push the commits created by release:prepare - if ! git push --force-with-lease origin "HEAD:${TARGET_BRANCH}"; then - echo "::error::Failed to push release commits to ${TARGET_BRANCH} due to branch divergence." - echo "The remote branch may have new commits. Please resolve the conflict manually:" - echo " 1. Fetch the latest changes: git fetch origin" - echo " 2. Rebase or merge as needed, then push again with --force-with-lease." - exit 1 - fi - - # Push the tag created by release:prepare - git push origin "$TAG_NAME" - - echo "Pushed release commits and tag to $TARGET_BRANCH" diff --git a/RELEASING.md b/RELEASING.md index 4788f88b..fc683f00 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,92 +1,130 @@ # Release Process -This document describes the automated release process for xAPI Java. +This document describes the manual draft release process for xAPI Java. ## Overview -The release process is **fully automated** via GitHub Actions. Creating a release is as simple as: -1. Click "Create Release" in GitHub UI -2. Enter tag (e.g., `v1.2.0`) -3. Publish +The release process is **manually controlled** via draft releases and GitHub Actions workflow dispatch. Releases are initiated on-demand with explicit version control: +1. Create a draft release in GitHub UI with a semver tag +2. Go to Actions tab and trigger the "Manual Draft Release" workflow +3. Enter the draft release title 4. Wait for workflow to complete ✅ -All version management, building, testing, and deployment happens automatically. +All version management, building, testing, and deployment happens in a controlled, manual process. -## Automated Release Process +## Manual Draft Release Process - - -### Step 1: Create a GitHub Release +### Step 1: Create a Draft Release 1. Navigate to the [Releases page](https://github.com/BerryCloud/xapi-java/releases) 2. Click **"Draft a new release"** -3. **Choose a tag**: Enter the tag version in the format: `vX.Y.Z` (e.g., `v1.2.0`) - - The tag **must** start with `v` followed by semantic version numbers - - Example: `v1.1.16`, `v2.0.0`, `v1.2.3` -4. **Target**: Select the branch to release from (typically `main`) - - The workflow will automatically detect and use this branch -5. Enter a release title (e.g., "Release 1.2.0") -6. Add release notes describing the changes -7. Click **"Publish release"** - -### Step 2: Automated Workflow Execution - -Once you publish the release, the "Automated Release" workflow will: - -1. ✅ Validate the tag format (must be `vX.Y.Z`) -2. ✅ Detect the target branch (auto-detected from release) -3. ✅ Delete the user-created tag (will be recreated properly) +3. Fill in the release details: + - **Tag**: Enter a semver tag in format `vX.Y.Z` (e.g., `v1.2.0`) + - **Important**: Tag MUST include the `v` prefix followed by semantic version numbers + - Example: `v1.1.20`, `v2.0.0`, `v1.2.3` + - **Release title**: Use the same tag name (e.g., `v1.2.0`) + - **Target**: Select the branch to release from (typically `main`) + - **Description**: Add release notes describing the changes +4. Click **"Save draft"** - do NOT publish yet + +### Step 2: Trigger the Manual Draft Release Workflow + +1. Navigate to the [Actions tab](https://github.com/BerryCloud/xapi-java/actions) +2. Click on **"Manual Draft Release"** workflow in the left sidebar +3. Click the **"Run workflow"** button (top right) +4. Fill in the workflow input: + - **Draft release title**: Enter the exact title of your draft release (e.g., `v1.2.0`) + - Must match the title from Step 1 exactly + - Format: `vX.Y.Z` with the `v` prefix +5. Click **"Run workflow"** to start the release process + +### Step 3: Workflow Execution + +Once you trigger the workflow, the "Manual Draft Release" workflow will: + +1. ✅ Validate the draft release title format (must be `vX.Y.Z`) +2. ✅ Find and validate the draft release in GitHub +3. ✅ Extract version information from the draft release 4. ✅ **Run Maven release:prepare** to: - - Update all `pom.xml` files to the release version + - Update all `pom.xml` files to the release version (e.g., 1.2.0) - Commit the version change - - Create the release tag pointing to the release commit - - Update `pom.xml` files to the next SNAPSHOT version + - Create the release tag (e.g., v1.2.0) pointing to the release commit + - Update `pom.xml` files to the next SNAPSHOT version (e.g., 1.2.1-SNAPSHOT) - Commit the next development iteration 5. ✅ **Run Maven release:perform** to: - Check out the release tag - Build and test the release version - Deploy artifacts to Maven Central with GPG signatures -6. ✅ Push commits and tag back to the originating branch +6. ✅ Push commits and tag back to the target branch +7. ✅ Upload release artifacts (JAR files) to the draft release +8. ✅ Publish the GitHub Release (remove draft status) **Workflow Diagram:** ``` -User Action: Create Release (tag: v1.2.0, target: main) +User Action: Create draft release in GitHub UI + - Title: v1.2.0 + - Tag: v1.2.0 + - Target: main ↓ -GitHub: Creates tag v1.2.0 → commit A (from main) +User Action: Trigger "Manual Draft Release" workflow + - Draft release title: v1.2.0 ↓ -Workflow: Detects target branch (main) +Workflow: Validates title format (v1.2.0) ↓ -Workflow: Deletes user-created tag v1.2.0 +Workflow: Finds draft release with title "v1.2.0" ↓ -Workflow: Runs release:prepare +Workflow: Extracts version (1.2.0) and target branch (main) ↓ - - Commit B: pom.xml → 1.2.0 (release version) - - Creates tag v1.2.0 → commit B - - Commit C: pom.xml → 1.2.1-SNAPSHOT (next dev version) +Workflow: Runs release:prepare on main branch + ↓ + - Commit A: pom.xml → 1.2.0 (release version) + - Creates tag v1.2.0 → commit A + - Commit B: pom.xml → 1.2.1-SNAPSHOT (next dev version) ↓ Workflow: Runs release:perform ↓ - - Checks out tag v1.2.0 (commit B) + - Checks out tag v1.2.0 (commit A) - Builds and tests - Deploys to Maven Central ↓ -Workflow: Pushes commits B & C to main +Workflow: Pushes commits A & B to main + ↓ +Workflow: Pushes tag v1.2.0 → commit A ↓ -Workflow: Pushes tag v1.2.0 → commit B +Workflow: Uploads JAR artifacts to draft release + ↓ +Workflow: Publishes the GitHub Release ↓ Result: - - Tag v1.2.0 → commit B (release version) - - Main branch → commit C (next SNAPSHOT: 1.2.1-SNAPSHOT) + - Tag v1.2.0 → commit A (release version: 1.2.0) + - Main branch → commit B (next SNAPSHOT: 1.2.1-SNAPSHOT) - Artifacts deployed to Maven Central + - GitHub Release published with JAR files ``` -### Step 3: Verify Release +### Step 4: Verify Release 1. Check the [Actions tab](https://github.com/BerryCloud/xapi-java/actions) to ensure the workflow completed successfully + - The workflow will show a summary of the release including version, tag, and status 2. Verify the target branch (e.g., `main`) has two new commits: - Release commit: `[maven-release-plugin] prepare release vX.Y.Z` - Development commit: `[maven-release-plugin] prepare for next development iteration` -3. Verify artifacts are available on [Maven Central](https://central.sonatype.com/artifact/dev.learning.xapi/xapi-model) +3. Verify the GitHub Release was published at the [Releases page](https://github.com/BerryCloud/xapi-java/releases) + - The release should no longer be in draft state + - JAR artifacts should be attached to the release +4. Verify artifacts are available on [Maven Central](https://central.sonatype.com/artifact/dev.learning.xapi/xapi-model) + - Note: It may take up to 2 hours for artifacts to sync to Maven Central + +## Manual Draft Release Benefits + +The manual draft release approach provides several advantages: + +- **Full Control**: Releases happen only when explicitly triggered, not automatically +- **Predictability**: No unexpected releases due to automation triggers +- **Review First**: Create and review draft releases before triggering deployment +- **Release Notes**: Prepare release notes in advance as part of the draft +- **Artifact Attachment**: Release artifacts (JARs) are automatically uploaded to the GitHub Release +- **Coordination**: Coordinate releases with code reviews and team schedules ## Release Branch Strategy @@ -95,16 +133,37 @@ Result: 1. Release commit: Version update to release version (X.Y.Z) 2. Development commit: Version update to next development version (X.Y.Z+1-SNAPSHOT) - The HEAD always points to the next development version -- **Release tags (`vX.Y.Z`)**: Created by Maven release plugin +- **Release tags (`vX.Y.Z`)**: Created by Maven release plugin during workflow execution - Points to the release version commit (first commit) - Used for reproducible builds and deployments -- **No separate release branches**: The release workflow pushes directly to the originating branch +- **No separate release branches**: The release workflow pushes directly to the selected branch + +## Advanced Options + +### Editing Draft Releases + +You can edit a draft release before triggering the workflow: + +1. Navigate to the [Releases page](https://github.com/BerryCloud/xapi-java/releases) +2. Find your draft release +3. Click **"Edit"** +4. Update release notes, title, or target branch as needed +5. Click **"Save draft"** +6. Then trigger the workflow with the updated title + +### Release Notes + +The workflow preserves the release notes you write in the draft release: + +1. When creating the draft release, write detailed release notes +2. The workflow will keep these notes when publishing the release +3. After publication, the release will show your notes plus attached JAR artifacts ## Troubleshooting ### Release Workflow Failed -If the automated release workflow fails: +If the manual draft release workflow fails: 1. **Check the workflow logs** in the [Actions tab](https://github.com/BerryCloud/xapi-java/actions) 2. **Identify the failed step** and review the error message @@ -112,22 +171,30 @@ If the automated release workflow fails: | Issue | Solution | |-------|----------| - | Invalid tag format | Use format `vX.Y.Z` (e.g., `v1.2.0`, not `1.2.0` or `v1.2`) | + | Invalid title format | Use format `vX.Y.Z` (e.g., `v1.2.0`) - MUST include `v` prefix | + | Draft release not found | Ensure you created a draft release with the exact title you entered | + | Release is not a draft | The release must be in draft state - edit it and set to draft | + | No tag name set | Ensure the draft release has a tag name configured | | Missing secrets | Ensure GPG keys and Maven credentials are configured in repository secrets | - | Build failures | Fix build issues on main branch first, then retry release | - | Test failures | Fix failing tests on main branch first, then retry release | + | Build failures | Fix build issues on target branch first, then retry release | + | Test failures | Fix failing tests on target branch first, then retry release | + | Branch divergence | Someone pushed to the branch during release. Check the error and retry | 4. **After fixing issues:** - - Delete the failed release and tag in GitHub UI - - **Important**: Reset your target branch if commits were already pushed: - ```bash - # If release:prepare pushed commits before failure - git fetch origin - git checkout main # or your target branch - git reset --hard origin/main~2 # Remove the 2 release commits - git push -f origin main - ``` - - Create a new release with the same tag + - **If commits were NOT pushed**: Simply re-run the workflow with the same title + - **If commits were pushed**: + - Delete the tag in GitHub UI (if it was created) + - Delete the published release (if it was published) + - Reset your target branch if needed: + ```bash + # If release:prepare pushed commits before failure + git fetch origin + git checkout main # or your target branch + git reset --hard origin/main~2 # Remove the 2 release commits + git push -f origin main + ``` + - Create a new draft release + - Re-run the workflow ### Workflow Stuck or Taking Too Long