From d2dff77971a3a1fa588cdd82836b3f1461e7cd72 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 15:44:44 +0000 Subject: [PATCH 01/23] Initial plan From b7c8fc66c657f61d16a78abaf7c5bf96d7194e9b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 15:50:19 +0000 Subject: [PATCH 02/23] Add manual release workflow and update documentation Co-authored-by: thomasturrell <1552612+thomasturrell@users.noreply.github.com> --- .github/workflows/manual-release.yml | 225 ++++++++++++++++++ ...e.yml => release-automated-deprecated.yml} | 27 +-- RELEASING.md | 170 ++++++++----- 3 files changed, 347 insertions(+), 75 deletions(-) create mode 100644 .github/workflows/manual-release.yml rename .github/workflows/{release.yml => release-automated-deprecated.yml} (88%) diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml new file mode 100644 index 00000000..3f79900c --- /dev/null +++ b/.github/workflows/manual-release.yml @@ -0,0 +1,225 @@ +# Manual Release Workflow +# +# This workflow enables manual releases with explicit version control. +# It must be manually triggered via the GitHub Actions UI. +# +# Usage: +# 1. Go to Actions > Manual Release > Run workflow +# 2. Select the target branch (typically main) +# 3. Enter the release version (e.g., 1.2.0) - do NOT include 'v' prefix +# 4. Click "Run workflow" +# 5. The workflow will: +# - Validate the version format +# - Run Maven release:prepare to update versions and create commits +# - Run Maven release:perform to build and deploy to Maven Central +# - Create a GitHub Release with the specified version +# - Push all commits and tags back to the target branch + +name: Manual Release + +on: + workflow_dispatch: + inputs: + version: + description: 'Release version (e.g., 1.2.0) - do NOT include v prefix' + required: true + type: string + skip_maven_central: + description: 'Skip deployment to Maven Central (for testing)' + required: false + type: boolean + default: false + create_github_release: + description: 'Create GitHub Release after successful deployment' + required: false + type: boolean + default: true + +permissions: + contents: write # Required to push commits, tags, and create releases + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - name: Validate version format + id: validate_version + run: | + VERSION="${{ github.event.inputs.version }}" + echo "Release version: $VERSION" + + # Validate version format (should be X.Y.Z) + if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::Invalid version format. Expected format: X.Y.Z (e.g., 1.2.0)" + echo "Do NOT include 'v' prefix. Just the version numbers." + exit 1 + fi + + # Create tag name with 'v' prefix + TAG_NAME="v${VERSION}" + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "tag_name=${TAG_NAME}" >> $GITHUB_OUTPUT + echo "Tag name: $TAG_NAME" + + - 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: ${{ github.ref }} + fetch-depth: 0 + token: ${{ steps.generate_token.outputs.token }} + + - name: Check if tag already exists + run: | + TAG_NAME="${{ steps.validate_version.outputs.tag_name }}" + + # Check if tag exists locally or remotely + if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then + echo "::error::Tag $TAG_NAME already exists locally" + exit 1 + fi + + if git ls-remote --tags origin "$TAG_NAME" | grep -q "$TAG_NAME"; then + echo "::error::Tag $TAG_NAME already exists on remote" + 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.validate_version.outputs.version }}" + TAG_NAME="${{ steps.validate_version.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 + if: ${{ !github.event.inputs.skip_maven_central }} + 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: Skip Maven Central deployment (dry run) + if: ${{ github.event.inputs.skip_maven_central }} + run: | + echo "::warning::Skipping Maven Central deployment as requested" + echo "This is a dry run. Artifacts will NOT be published to Maven Central." + echo "Release commits and tag will still be created and pushed." + + - name: Push changes to branch + run: | + TARGET_BRANCH="${{ github.ref_name }}" + TAG_NAME="${{ steps.validate_version.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 re-run this workflow." + exit 1 + fi + + # Push the tag created by release:prepare + git push origin "$TAG_NAME" + + echo "Pushed release commits and tag to $TARGET_BRANCH" + + - name: Create GitHub Release + if: ${{ github.event.inputs.create_github_release }} + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} + with: + tag_name: ${{ steps.validate_version.outputs.tag_name }} + release_name: Release ${{ steps.validate_version.outputs.version }} + body: | + ## Release ${{ steps.validate_version.outputs.version }} + + This release was created via the manual release workflow. + + ### Maven Central + Artifacts are available on Maven Central: + - [xapi-model](https://central.sonatype.com/artifact/dev.learning.xapi/xapi-model/${{ steps.validate_version.outputs.version }}) + - [xapi-client](https://central.sonatype.com/artifact/dev.learning.xapi/xapi-client/${{ steps.validate_version.outputs.version }}) + - [xapi-model-spring-boot-starter](https://central.sonatype.com/artifact/dev.learning.xapi/xapi-model-spring-boot-starter/${{ steps.validate_version.outputs.version }}) + + ### Changes + See commit history for details. + draft: false + prerelease: false + + - name: Release Summary + if: always() + run: | + echo "## Release Workflow Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Version:** ${{ steps.validate_version.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "**Tag:** ${{ steps.validate_version.outputs.tag_name }}" >> $GITHUB_STEP_SUMMARY + echo "**Branch:** ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY + echo "**Maven Central:** ${{ github.event.inputs.skip_maven_central && 'Skipped (dry run)' || 'Deployed' }}" >> $GITHUB_STEP_SUMMARY + echo "**GitHub Release:** ${{ github.event.inputs.create_github_release && 'Created' || 'Skipped' }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${{ job.status }}" == "success" ]; then + echo "✅ Release completed successfully!" >> $GITHUB_STEP_SUMMARY + else + echo "❌ Release failed. Check the logs for details." >> $GITHUB_STEP_SUMMARY + fi diff --git a/.github/workflows/release.yml b/.github/workflows/release-automated-deprecated.yml similarity index 88% rename from .github/workflows/release.yml rename to .github/workflows/release-automated-deprecated.yml index 4ce7e3c2..40d6fa57 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release-automated-deprecated.yml @@ -1,20 +1,19 @@ -# 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 +# DEPRECATED: This workflow has been replaced by manual-release.yml # -# 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 +# This workflow previously automated the release process when a release was created on GitHub. +# It has been deprecated in favor of a manual workflow dispatch approach for better control. +# +# DO NOT USE THIS WORKFLOW - Use the "Manual Release" workflow instead. +# See RELEASING.md for updated release instructions. +# +# This file is kept for reference but the trigger has been disabled. -name: Automated Release +name: Automated Release (DEPRECATED) -on: - release: - types: [created] # Trigger when release is created +# Trigger disabled - workflow will not run automatically +# on: +# release: +# types: [created] permissions: contents: write # Required to push commits and update tags diff --git a/RELEASING.md b/RELEASING.md index 4788f88b..10e6eb62 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,92 +1,110 @@ # Release Process -This document describes the automated release process for xAPI Java. +This document describes the manual 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 -4. Wait for workflow to complete ✅ +The release process is **manually controlled** via GitHub Actions workflow dispatch. Releases are initiated on-demand with explicit version control: +1. Go to Actions tab in GitHub +2. Select "Manual Release" workflow +3. Click "Run workflow" +4. Enter the release version +5. 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 Release Process -### Step 1: Create a GitHub Release +### Step 1: Trigger the Manual Release Workflow -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"** +1. Navigate to the [Actions tab](https://github.com/BerryCloud/xapi-java/actions) +2. Click on **"Manual Release"** workflow in the left sidebar +3. Click the **"Run workflow"** button (top right) +4. Fill in the workflow inputs: + - **Branch**: Select the branch to release from (typically `main`) + - **Release version**: Enter the version number in format `X.Y.Z` (e.g., `1.2.0`) + - **Important**: Do NOT include the `v` prefix - just the version numbers + - Example: `1.1.16`, `2.0.0`, `1.2.3` + - **Skip Maven Central**: Leave unchecked (or check for dry-run testing) + - **Create GitHub Release**: Leave checked to automatically create a GitHub Release +5. Click **"Run workflow"** to start the release process -### Step 2: Automated Workflow Execution +### Step 2: Workflow Execution -Once you publish the release, the "Automated Release" workflow will: +Once you trigger the workflow, the "Manual 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) -4. ✅ **Run Maven release:prepare** to: - - Update all `pom.xml` files to the release version +1. ✅ Validate the version format (must be `X.Y.Z`) +2. ✅ Check that the tag doesn't already exist +3. ✅ **Run Maven release:prepare** to: + - 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: +4. ✅ **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 +5. ✅ Push commits and tag back to the selected branch +6. ✅ Create a GitHub Release (if enabled) **Workflow Diagram:** ``` -User Action: Create Release (tag: v1.2.0, target: main) +User Action: Trigger "Manual Release" workflow + - Branch: main + - Version: 1.2.0 ↓ -GitHub: Creates tag v1.2.0 → commit A (from main) +Workflow: Validates version format (1.2.0) ↓ -Workflow: Detects target branch (main) +Workflow: Checks tag v1.2.0 doesn't exist ↓ -Workflow: Deletes user-created tag v1.2.0 +Workflow: Runs release:prepare on main branch ↓ -Workflow: Runs release:prepare - ↓ - - 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) + - 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: Creates GitHub Release for v1.2.0 ↓ 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 created ``` ### Step 3: 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 deployment 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 created at the [Releases page](https://github.com/BerryCloud/xapi-java/releases) +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 Release Benefits + +The manual release approach provides several advantages: + +- **Full Control**: Releases happen only when explicitly triggered, not automatically +- **Predictability**: No unexpected releases due to automation triggers +- **Testing**: Ability to run dry-run releases (skip Maven Central deployment) for testing +- **Flexibility**: Choose when to create GitHub Releases independently +- **Review**: Coordinate releases with code reviews and team schedules ## Release Branch Strategy @@ -95,16 +113,42 @@ 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 + +### Dry-Run Release (Testing) + +You can test the release process without deploying to Maven Central: + +1. Trigger the "Manual Release" workflow as described above +2. Check the **"Skip Maven Central"** option +3. The workflow will: + - Run all version updates and create commits/tags + - Skip the actual deployment to Maven Central + - Allow you to verify the release process works correctly + +This is useful for: +- Testing the release workflow +- Verifying version numbering +- Ensuring all commits and tags are created correctly + +### Release Without GitHub Release + +If you want to deploy to Maven Central but create the GitHub Release manually later: + +1. Trigger the "Manual Release" workflow +2. Uncheck the **"Create GitHub Release"** option +3. After verifying the Maven Central deployment, manually create a GitHub Release ## Troubleshooting ### Release Workflow Failed -If the automated release workflow fails: +If the manual 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 +156,26 @@ 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 version format | Use format `X.Y.Z` (e.g., `1.2.0`) - do NOT include `v` prefix | + | Tag already exists | The version has already been released. Use a different version number | | 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 version + - **If commits were pushed**: + - Delete the tag in GitHub UI (if it was created) + - 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 + ``` + - Re-run the workflow ### Workflow Stuck or Taking Too Long From 5d294e2ccd87f1229c82adffb7ef9b557ee3482b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 15:52:00 +0000 Subject: [PATCH 03/23] Update manual release workflow to use gh CLI and add migration guide Co-authored-by: thomasturrell <1552612+thomasturrell@users.noreply.github.com> --- .github/RELEASE_WORKFLOW_MIGRATION.md | 59 +++++++++++++++++++++++++++ .github/workflows/manual-release.yml | 46 ++++++++++++--------- 2 files changed, 85 insertions(+), 20 deletions(-) create mode 100644 .github/RELEASE_WORKFLOW_MIGRATION.md diff --git a/.github/RELEASE_WORKFLOW_MIGRATION.md b/.github/RELEASE_WORKFLOW_MIGRATION.md new file mode 100644 index 00000000..9b05deaa --- /dev/null +++ b/.github/RELEASE_WORKFLOW_MIGRATION.md @@ -0,0 +1,59 @@ +# Release Workflow Migration Guide + +## Summary of Changes + +This project has migrated from an **automated release workflow** to a **manual release workflow** for better control and predictability. + +## What Changed + +### Before (Automated Release) +- **Trigger**: Creating a GitHub Release automatically triggered the workflow +- **Process**: Release was created in GitHub UI → workflow ran automatically +- **Control**: Limited control over when releases happened + +### After (Manual Release) +- **Trigger**: Workflow must be manually triggered via GitHub Actions UI +- **Process**: Trigger workflow → enter version → workflow runs +- **Control**: Full control over release timing and options + +## Migration Benefits + +1. **Explicit Control**: Releases only happen when you explicitly trigger them +2. **No Surprises**: No accidental releases from automation +3. **Testing Options**: Dry-run capability to test releases without deployment +4. **Flexible**: Can skip GitHub Release creation or Maven Central deployment independently +5. **Review Process**: Better integration with code review and approval processes + +## How to Use the New Workflow + +See [RELEASING.md](../RELEASING.md) for complete instructions. + +**Quick Start:** +1. Go to [Actions tab](https://github.com/BerryCloud/xapi-java/actions) +2. Select "Manual Release" workflow +3. Click "Run workflow" +4. Enter version (e.g., `1.2.0` - no `v` prefix) +5. Click "Run workflow" button + +## Workflow Options + +| Option | Description | Default | +|--------|-------------|---------| +| **Release version** | Version number (X.Y.Z format, no 'v') | Required | +| **Skip Maven Central** | Skip deployment (dry-run) | false | +| **Create GitHub Release** | Create release after deployment | true | + +## Old Workflow + +The old automated workflow (`release.yml`) has been: +- Renamed to `release-automated-deprecated.yml` +- Trigger disabled to prevent automatic execution +- Kept for reference only + +**Do not use the old workflow.** It will not run automatically anymore. + +## Questions? + +- See [RELEASING.md](../RELEASING.md) for detailed documentation +- Check [manual-release.yml](workflows/manual-release.yml) for workflow implementation +- Open an issue if you encounter problems diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index 3f79900c..2f001276 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -184,27 +184,33 @@ jobs: - name: Create GitHub Release if: ${{ github.event.inputs.create_github_release }} - uses: actions/create-release@v1 env: - GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} - with: - tag_name: ${{ steps.validate_version.outputs.tag_name }} - release_name: Release ${{ steps.validate_version.outputs.version }} - body: | - ## Release ${{ steps.validate_version.outputs.version }} - - This release was created via the manual release workflow. - - ### Maven Central - Artifacts are available on Maven Central: - - [xapi-model](https://central.sonatype.com/artifact/dev.learning.xapi/xapi-model/${{ steps.validate_version.outputs.version }}) - - [xapi-client](https://central.sonatype.com/artifact/dev.learning.xapi/xapi-client/${{ steps.validate_version.outputs.version }}) - - [xapi-model-spring-boot-starter](https://central.sonatype.com/artifact/dev.learning.xapi/xapi-model-spring-boot-starter/${{ steps.validate_version.outputs.version }}) - - ### Changes - See commit history for details. - draft: false - prerelease: false + GH_TOKEN: ${{ steps.generate_token.outputs.token }} + run: | + TAG_NAME="${{ steps.validate_version.outputs.tag_name }}" + VERSION="${{ steps.validate_version.outputs.version }}" + + # Create release notes + cat > /tmp/release-notes.md << 'EOF' + ## Release ${{ steps.validate_version.outputs.version }} + + This release was created via the manual release workflow. + + ### Maven Central + Artifacts are available on Maven Central: + - [xapi-model](https://central.sonatype.com/artifact/dev.learning.xapi/xapi-model/${{ steps.validate_version.outputs.version }}) + - [xapi-client](https://central.sonatype.com/artifact/dev.learning.xapi/xapi-client/${{ steps.validate_version.outputs.version }}) + - [xapi-model-spring-boot-starter](https://central.sonatype.com/artifact/dev.learning.xapi/xapi-model-spring-boot-starter/${{ steps.validate_version.outputs.version }}) + + ### Changes + See commit history for details. + EOF + + # Create the release + gh release create "$TAG_NAME" \ + --title "Release ${VERSION}" \ + --notes-file /tmp/release-notes.md \ + --repo ${{ github.repository }} - name: Release Summary if: always() From db5e281fd5624ba034da4113e3dc4b0e1eccf4df Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 15:53:02 +0000 Subject: [PATCH 04/23] Fix release notes to conditionally mention Maven Central based on deployment Co-authored-by: thomasturrell <1552612+thomasturrell@users.noreply.github.com> --- .github/workflows/manual-release.yml | 30 ++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index 2f001276..c0bfa6bf 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -189,18 +189,36 @@ jobs: run: | TAG_NAME="${{ steps.validate_version.outputs.tag_name }}" VERSION="${{ steps.validate_version.outputs.version }}" + SKIP_MAVEN="${{ github.event.inputs.skip_maven_central }}" - # Create release notes - cat > /tmp/release-notes.md << 'EOF' - ## Release ${{ steps.validate_version.outputs.version }} + # Create release notes with conditional Maven Central section + cat > /tmp/release-notes.md << EOF + ## Release ${VERSION} This release was created via the manual release workflow. + EOF + + # Add Maven Central section only if deployment was not skipped + if [ "$SKIP_MAVEN" != "true" ]; then + cat >> /tmp/release-notes.md << EOF ### Maven Central Artifacts are available on Maven Central: - - [xapi-model](https://central.sonatype.com/artifact/dev.learning.xapi/xapi-model/${{ steps.validate_version.outputs.version }}) - - [xapi-client](https://central.sonatype.com/artifact/dev.learning.xapi/xapi-client/${{ steps.validate_version.outputs.version }}) - - [xapi-model-spring-boot-starter](https://central.sonatype.com/artifact/dev.learning.xapi/xapi-model-spring-boot-starter/${{ steps.validate_version.outputs.version }}) + - [xapi-model](https://central.sonatype.com/artifact/dev.learning.xapi/xapi-model/${VERSION}) + - [xapi-client](https://central.sonatype.com/artifact/dev.learning.xapi/xapi-client/${VERSION}) + - [xapi-model-spring-boot-starter](https://central.sonatype.com/artifact/dev.learning.xapi/xapi-model-spring-boot-starter/${VERSION}) + + Note: It may take up to 2 hours for artifacts to sync to Maven Central. + EOF + else + cat >> /tmp/release-notes.md << EOF + + ### Note + This is a dry-run release. Artifacts were NOT deployed to Maven Central. + EOF + fi + + cat >> /tmp/release-notes.md << EOF ### Changes See commit history for details. From 440d4b02c2353299519847dab2aed74a35875594 Mon Sep 17 00:00:00 2001 From: Thomas Turrell-Croft Date: Mon, 8 Dec 2025 17:51:34 +0000 Subject: [PATCH 05/23] my suggestions --- .github/workflows/manual-release.yml | 348 ++++++++++-------- .../release-automated-deprecated.yml | 160 -------- 2 files changed, 197 insertions(+), 311 deletions(-) delete mode 100644 .github/workflows/release-automated-deprecated.yml diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index c0bfa6bf..954ced64 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -1,98 +1,113 @@ -# Manual Release Workflow -# -# This workflow enables manual releases with explicit version control. -# It must be manually triggered via the GitHub Actions UI. -# -# Usage: -# 1. Go to Actions > Manual Release > Run workflow -# 2. Select the target branch (typically main) -# 3. Enter the release version (e.g., 1.2.0) - do NOT include 'v' prefix -# 4. Click "Run workflow" -# 5. The workflow will: -# - Validate the version format -# - Run Maven release:prepare to update versions and create commits -# - Run Maven release:perform to build and deploy to Maven Central -# - Create a GitHub Release with the specified version -# - Push all commits and tags back to the target branch - -name: Manual Release +name: Manual Draft Release on: workflow_dispatch: inputs: - version: - description: 'Release version (e.g., 1.2.0) - do NOT include v prefix' + title: + description: "Draft release title (e.g., v1.2.3)" required: true type: string - skip_maven_central: - description: 'Skip deployment to Maven Central (for testing)' - required: false - type: boolean - default: false - create_github_release: - description: 'Create GitHub Release after successful deployment' - required: false - type: boolean - default: true permissions: - contents: write # Required to push commits, tags, and create releases + contents: write jobs: - release: + draft-release: runs-on: ubuntu-latest - steps: - - name: Validate version format - id: validate_version + - name: Resolve release details + id: release + env: + GH_TOKEN: ${{ github.token }} run: | - VERSION="${{ github.event.inputs.version }}" - echo "Release version: $VERSION" - - # Validate version format (should be X.Y.Z) - if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "::error::Invalid version format. Expected format: X.Y.Z (e.g., 1.2.0)" - echo "Do NOT include 'v' prefix. Just the version numbers." + set -euo pipefail + + REPO_FULL_NAME="${{ github.repository }}" + TITLE_INPUT="${{ inputs.title }}" + + echo "Resolving draft release by title in $REPO_FULL_NAME..." + + # 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 - - # Create tag name with 'v' prefix - TAG_NAME="v${VERSION}" - echo "version=${VERSION}" >> $GITHUB_OUTPUT - echo "tag_name=${TAG_NAME}" >> $GITHUB_OUTPUT - echo "Tag name: $TAG_NAME" - - - name: Generate GitHub App Token - id: generate_token - uses: actions/create-github-app-token@v2 + + 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 "Resolved: tag=$TAG_NAME, name=$NAME, target_commitish=$TARGET_COMMITISH, draft=$DRAFT" + + - 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 }} + app-id: ${{ vars.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} - - - name: Checkout repository + + - name: Checkout target commitish with full history (needed to commit & tag) uses: actions/checkout@v6 with: - ref: ${{ github.ref }} + ref: ${{ steps.release.outputs.target_commitish }} + token: ${{ steps.app-token.outputs.token }} fetch-depth: 0 - token: ${{ steps.generate_token.outputs.token }} - - - name: Check if tag already exists - run: | - TAG_NAME="${{ steps.validate_version.outputs.tag_name }}" - - # Check if tag exists locally or remotely - if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then - echo "::error::Tag $TAG_NAME already exists locally" - exit 1 - fi - - if git ls-remote --tags origin "$TAG_NAME" | grep -q "$TAG_NAME"; then - echo "::error::Tag $TAG_NAME already exists on remote" - exit 1 - fi - - echo "Tag $TAG_NAME does not exist. Proceeding with release." - + persist-credentials: true + - name: Set up JDK 25 uses: actions/setup-java@v5 with: @@ -109,16 +124,16 @@ jobs: 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_version.outputs.version }}" - TAG_NAME="${{ steps.validate_version.outputs.tag_name }}" + VERSION="${{ steps.validate_tag.outputs.version }}" + TAG_NAME="${{ github.event.release.tag_name }}" echo "Preparing release version: $VERSION" echo "Tag name: $TAG_NAME" @@ -138,7 +153,6 @@ jobs: MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} - name: Run Maven release:perform - if: ${{ !github.event.inputs.skip_maven_central }} run: | echo "Performing release and deploying to Maven Central" @@ -154,17 +168,10 @@ jobs: MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} - - name: Skip Maven Central deployment (dry run) - if: ${{ github.event.inputs.skip_maven_central }} - run: | - echo "::warning::Skipping Maven Central deployment as requested" - echo "This is a dry run. Artifacts will NOT be published to Maven Central." - echo "Release commits and tag will still be created and pushed." - - - name: Push changes to branch + - name: Push changes to originating branch run: | - TARGET_BRANCH="${{ github.ref_name }}" - TAG_NAME="${{ steps.validate_version.outputs.tag_name }}" + TARGET_BRANCH="${{ steps.target_branch.outputs.target_branch }}" + TAG_NAME="${{ github.event.release.tag_name }}" echo "Pushing changes to branch: $TARGET_BRANCH" @@ -173,7 +180,7 @@ jobs: 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 re-run this workflow." + echo " 2. Rebase or merge as needed, then push again with --force-with-lease." exit 1 fi @@ -181,69 +188,108 @@ jobs: git push origin "$TAG_NAME" echo "Pushed release commits and tag to $TARGET_BRANCH" - - - name: Create GitHub Release - if: ${{ github.event.inputs.create_github_release }} + # This step updates the changelog and commits changes. + # In a Maven environment, you might run `mvn release:prepare` here. + # In an npm environment, you might run `npm version` or use semantic-release. + # For other build systems, integrate their release tooling at this stage. env: - GH_TOKEN: ${{ steps.generate_token.outputs.token }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} run: | - TAG_NAME="${{ steps.validate_version.outputs.tag_name }}" - VERSION="${{ steps.validate_version.outputs.version }}" - SKIP_MAVEN="${{ github.event.inputs.skip_maven_central }}" - - # Create release notes with conditional Maven Central section - cat > /tmp/release-notes.md << EOF - ## Release ${VERSION} - - This release was created via the manual release workflow. - EOF - - # Add Maven Central section only if deployment was not skipped - if [ "$SKIP_MAVEN" != "true" ]; then - cat >> /tmp/release-notes.md << EOF - - ### Maven Central - Artifacts are available on Maven Central: - - [xapi-model](https://central.sonatype.com/artifact/dev.learning.xapi/xapi-model/${VERSION}) - - [xapi-client](https://central.sonatype.com/artifact/dev.learning.xapi/xapi-client/${VERSION}) - - [xapi-model-spring-boot-starter](https://central.sonatype.com/artifact/dev.learning.xapi/xapi-model-spring-boot-starter/${VERSION}) - - Note: It may take up to 2 hours for artifacts to sync to Maven Central. - EOF + set -euo pipefail # Exit on error, undefined vars, and pipeline failures + + REPO_FULL_NAME="${{ github.repository }}" + TAG="${{ steps.release.outputs.tag_name }}" + RELEASE_NAME="${{ steps.release.outputs.name }}" + BODY_TEXT_RAW="${{ steps.release.outputs.body }}" + + echo "Repository: $REPO_FULL_NAME" + echo "Tag: $TAG" + echo "Release name: $RELEASE_NAME" + + # Ensure git safe directory & committer identity (github-actions[bot]) + git config --global --add safe.directory "$GITHUB_WORKSPACE" || true + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + # Ensure CHANGELOG.md exists + if [ ! -f CHANGELOG.md ]; then + echo "CHANGELOG.md not found — creating with heading" + printf "%s\n\n" "# Changelog" > CHANGELOG.md + git add CHANGELOG.md + git commit -m "chore(changelog): add CHANGELOG.md" + fi + + # Build changelog entry. Use the raw body but ensure it's not 'null' + if [ "$BODY_TEXT_RAW" = "null" ] || [ -z "$BODY_TEXT_RAW" ]; then + BODY_TEXT="No release notes provided." else - cat >> /tmp/release-notes.md << EOF - - ### Note - This is a dry-run release. Artifacts were NOT deployed to Maven Central. - EOF + BODY_TEXT="$BODY_TEXT_RAW" fi - - cat >> /tmp/release-notes.md << EOF - - ### Changes - See commit history for details. - EOF - - # Create the release - gh release create "$TAG_NAME" \ - --title "Release ${VERSION}" \ - --notes-file /tmp/release-notes.md \ - --repo ${{ github.repository }} - - - name: Release Summary - if: always() + + ENTRY_TMP="$(mktemp)" + printf "## %s - %s - %s\n\n%s\n\n" "$TAG" "$RELEASE_NAME" "$BODY_TEXT" > "$ENTRY_TMP" + # Prepend: write entry then existing file + cat CHANGELOG.md >> "$ENTRY_TMP" + mv "$ENTRY_TMP" CHANGELOG.md + + git add CHANGELOG.md + # Always commit; this workflow prepends a new entry for each draft release + git commit -m "chore(release): add changelog entry for $TAG" + + echo "Pushing commit..." + git push origin HEAD + + - name: Upload artifacts to draft release + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} run: | - echo "## Release Workflow Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Version:** ${{ steps.validate_version.outputs.version }}" >> $GITHUB_STEP_SUMMARY - echo "**Tag:** ${{ steps.validate_version.outputs.tag_name }}" >> $GITHUB_STEP_SUMMARY - echo "**Branch:** ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY - echo "**Maven Central:** ${{ github.event.inputs.skip_maven_central && 'Skipped (dry run)' || 'Deployed' }}" >> $GITHUB_STEP_SUMMARY - echo "**GitHub Release:** ${{ github.event.inputs.create_github_release && 'Created' || 'Skipped' }}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY + 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: Update draft release to point to new 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..." - if [ "${{ job.status }}" == "success" ]; then - echo "✅ Release completed successfully!" >> $GITHUB_STEP_SUMMARY - else - echo "❌ Release failed. Check the logs for details." >> $GITHUB_STEP_SUMMARY - fi + # Update the release to point to the new tag + # The release remains in draft state for manual review before publishing + gh release edit "$TAG" --repo "$REPO_FULL_NAME" --tag "$TAG" --draft + + echo "Draft release updated successfully!" + echo "Review the release and publish when ready at:" + echo "https://github.com/$REPO_FULL_NAME/releases/tag/$TAG" \ No newline at end of file diff --git a/.github/workflows/release-automated-deprecated.yml b/.github/workflows/release-automated-deprecated.yml deleted file mode 100644 index 40d6fa57..00000000 --- a/.github/workflows/release-automated-deprecated.yml +++ /dev/null @@ -1,160 +0,0 @@ -# DEPRECATED: This workflow has been replaced by manual-release.yml -# -# This workflow previously automated the release process when a release was created on GitHub. -# It has been deprecated in favor of a manual workflow dispatch approach for better control. -# -# DO NOT USE THIS WORKFLOW - Use the "Manual Release" workflow instead. -# See RELEASING.md for updated release instructions. -# -# This file is kept for reference but the trigger has been disabled. - -name: Automated Release (DEPRECATED) - -# Trigger disabled - workflow will not run automatically -# on: -# release: -# types: [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" From 46065bde95d06ce18ec411d3bb6af8f9320930fc Mon Sep 17 00:00:00 2001 From: Thomas Turrell-Croft Date: Mon, 8 Dec 2025 19:08:02 +0000 Subject: [PATCH 06/23] tip --- .github/workflows/manual-release.yml | 104 +++++++++++++-------------- 1 file changed, 48 insertions(+), 56 deletions(-) diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index 954ced64..a8374f91 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -26,6 +26,18 @@ jobs: 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 \ @@ -87,8 +99,9 @@ jobs: 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" + echo "Resolved: tag=$TAG_NAME, name=$NAME, target_commitish=$TARGET_COMMITISH, draft=$DRAFT version=$VERSION" - name: Manual dispatch triggered run: | @@ -132,12 +145,12 @@ jobs: - name: Run Maven release:prepare run: | - VERSION="${{ steps.validate_tag.outputs.version }}" - TAG_NAME="${{ github.event.release.tag_name }}" - + 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 @@ -170,8 +183,8 @@ jobs: - name: Push changes to originating branch run: | - TARGET_BRANCH="${{ steps.target_branch.outputs.target_branch }}" - TAG_NAME="${{ github.event.release.tag_name }}" + TARGET_BRANCH="${{ steps.release.outputs.target_commitish }}" + TAG_NAME="${{ steps.release.outputs.tag_name }}" echo "Pushing changes to branch: $TARGET_BRANCH" @@ -188,56 +201,8 @@ jobs: git push origin "$TAG_NAME" echo "Pushed release commits and tag to $TARGET_BRANCH" - # This step updates the changelog and commits changes. - # In a Maven environment, you might run `mvn release:prepare` here. - # In an npm environment, you might run `npm version` or use semantic-release. - # For other build systems, integrate their release tooling at this stage. env: GH_TOKEN: ${{ steps.app-token.outputs.token }} - run: | - set -euo pipefail # Exit on error, undefined vars, and pipeline failures - - REPO_FULL_NAME="${{ github.repository }}" - TAG="${{ steps.release.outputs.tag_name }}" - RELEASE_NAME="${{ steps.release.outputs.name }}" - BODY_TEXT_RAW="${{ steps.release.outputs.body }}" - - echo "Repository: $REPO_FULL_NAME" - echo "Tag: $TAG" - echo "Release name: $RELEASE_NAME" - - # Ensure git safe directory & committer identity (github-actions[bot]) - git config --global --add safe.directory "$GITHUB_WORKSPACE" || true - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - # Ensure CHANGELOG.md exists - if [ ! -f CHANGELOG.md ]; then - echo "CHANGELOG.md not found — creating with heading" - printf "%s\n\n" "# Changelog" > CHANGELOG.md - git add CHANGELOG.md - git commit -m "chore(changelog): add CHANGELOG.md" - fi - - # Build changelog entry. Use the raw body but ensure it's not 'null' - if [ "$BODY_TEXT_RAW" = "null" ] || [ -z "$BODY_TEXT_RAW" ]; then - BODY_TEXT="No release notes provided." - else - BODY_TEXT="$BODY_TEXT_RAW" - fi - - ENTRY_TMP="$(mktemp)" - printf "## %s - %s - %s\n\n%s\n\n" "$TAG" "$RELEASE_NAME" "$BODY_TEXT" > "$ENTRY_TMP" - # Prepend: write entry then existing file - cat CHANGELOG.md >> "$ENTRY_TMP" - mv "$ENTRY_TMP" CHANGELOG.md - - git add CHANGELOG.md - # Always commit; this workflow prepends a new entry for each draft release - git commit -m "chore(release): add changelog entry for $TAG" - - echo "Pushing commit..." - git push origin HEAD - name: Upload artifacts to draft release env: @@ -292,4 +257,31 @@ jobs: echo "Draft release updated successfully!" echo "Review the release and publish when ready at:" - echo "https://github.com/$REPO_FULL_NAME/releases/tag/$TAG" \ No newline at end of file + echo "https://github.com/$REPO_FULL_NAME/releases/tag/$TAG" + + - 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 From 224cc7ad5caddd84634db3106aa4a4eec9febe9f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 19:17:25 +0000 Subject: [PATCH 07/23] Update documentation to reflect draft release workflow approach Co-authored-by: thomasturrell <1552612+thomasturrell@users.noreply.github.com> --- .github/RELEASE_WORKFLOW_MIGRATION.md | 52 +++++----- RELEASING.md | 143 +++++++++++++++----------- 2 files changed, 108 insertions(+), 87 deletions(-) diff --git a/.github/RELEASE_WORKFLOW_MIGRATION.md b/.github/RELEASE_WORKFLOW_MIGRATION.md index 9b05deaa..4907d226 100644 --- a/.github/RELEASE_WORKFLOW_MIGRATION.md +++ b/.github/RELEASE_WORKFLOW_MIGRATION.md @@ -2,7 +2,7 @@ ## Summary of Changes -This project has migrated from an **automated release workflow** to a **manual release workflow** for better control and predictability. +This project has migrated from an **automated release workflow** to a **manual draft release workflow** for better control and predictability. ## What Changed @@ -11,46 +11,48 @@ This project has migrated from an **automated release workflow** to a **manual r - **Process**: Release was created in GitHub UI → workflow ran automatically - **Control**: Limited control over when releases happened -### After (Manual Release) -- **Trigger**: Workflow must be manually triggered via GitHub Actions UI -- **Process**: Trigger workflow → enter version → workflow runs -- **Control**: Full control over release timing and options +### After (Manual Draft Release) +- **Trigger**: Create draft release first, then manually trigger workflow via GitHub Actions UI +- **Process**: Create draft release → trigger workflow → enter draft title → workflow runs +- **Control**: Full control over release timing, review draft before publishing ## Migration Benefits 1. **Explicit Control**: Releases only happen when you explicitly trigger them 2. **No Surprises**: No accidental releases from automation -3. **Testing Options**: Dry-run capability to test releases without deployment -4. **Flexible**: Can skip GitHub Release creation or Maven Central deployment independently -5. **Review Process**: Better integration with code review and approval processes +3. **Draft Review**: Create and review draft releases before deployment +4. **Release Notes**: Prepare comprehensive release notes in advance +5. **Artifact Attachment**: JAR files automatically uploaded to GitHub Release +6. **Review Process**: Better integration with code review and approval processes ## How to Use the New Workflow See [RELEASING.md](../RELEASING.md) for complete instructions. **Quick Start:** -1. Go to [Actions tab](https://github.com/BerryCloud/xapi-java/actions) -2. Select "Manual Release" workflow -3. Click "Run workflow" -4. Enter version (e.g., `1.2.0` - no `v` prefix) -5. Click "Run workflow" button - -## Workflow Options - -| Option | Description | Default | -|--------|-------------|---------| -| **Release version** | Version number (X.Y.Z format, no 'v') | Required | -| **Skip Maven Central** | Skip deployment (dry-run) | false | -| **Create GitHub Release** | Create release after deployment | true | +1. Go to [Releases page](https://github.com/BerryCloud/xapi-java/releases) +2. Click "Draft a new release" +3. Fill in tag (e.g., `v1.2.0`), title (same as tag), and release notes +4. Click "Save draft" +5. Go to [Actions tab](https://github.com/BerryCloud/xapi-java/actions) +6. Select "Manual Draft Release" workflow +7. Click "Run workflow" +8. Enter draft release title (e.g., `v1.2.0`) +9. Click "Run workflow" button + +## Workflow Input + +| Option | Description | Format | +|--------|-------------|--------| +| **Draft release title** | Title of the draft release to process | vX.Y.Z (e.g., v1.2.0) - MUST include 'v' prefix | ## Old Workflow The old automated workflow (`release.yml`) has been: -- Renamed to `release-automated-deprecated.yml` -- Trigger disabled to prevent automatic execution -- Kept for reference only +- Completely removed from the repository +- Replaced with `manual-release.yml` (Manual Draft Release workflow) -**Do not use the old workflow.** It will not run automatically anymore. +**The automated release workflow is no longer available.** All releases must now use the manual draft release process. ## Questions? diff --git a/RELEASING.md b/RELEASING.md index 10e6eb62..fc683f00 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,64 +1,79 @@ # Release Process -This document describes the manual release process for xAPI Java. +This document describes the manual draft release process for xAPI Java. ## Overview -The release process is **manually controlled** via GitHub Actions workflow dispatch. Releases are initiated on-demand with explicit version control: -1. Go to Actions tab in GitHub -2. Select "Manual Release" workflow -3. Click "Run workflow" -4. Enter the release version -5. Wait for workflow to complete ✅ +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 in a controlled, manual process. -## Manual Release Process +## Manual Draft Release Process +### 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. 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 1: Trigger the Manual Release Workflow +### 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 Release"** workflow in the left sidebar +2. Click on **"Manual Draft Release"** workflow in the left sidebar 3. Click the **"Run workflow"** button (top right) -4. Fill in the workflow inputs: - - **Branch**: Select the branch to release from (typically `main`) - - **Release version**: Enter the version number in format `X.Y.Z` (e.g., `1.2.0`) - - **Important**: Do NOT include the `v` prefix - just the version numbers - - Example: `1.1.16`, `2.0.0`, `1.2.3` - - **Skip Maven Central**: Leave unchecked (or check for dry-run testing) - - **Create GitHub Release**: Leave checked to automatically create a GitHub Release +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 2: Workflow Execution +### Step 3: Workflow Execution -Once you trigger the workflow, the "Manual Release" workflow will: +Once you trigger the workflow, the "Manual Draft Release" workflow will: -1. ✅ Validate the version format (must be `X.Y.Z`) -2. ✅ Check that the tag doesn't already exist -3. ✅ **Run Maven release:prepare** to: +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 (e.g., 1.2.0) - Commit the version change - 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 -4. ✅ **Run Maven release:perform** to: +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 -5. ✅ Push commits and tag back to the selected branch -6. ✅ Create a GitHub Release (if enabled) +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: Trigger "Manual Release" workflow - - Branch: main - - Version: 1.2.0 +User Action: Create draft release in GitHub UI + - Title: v1.2.0 + - Tag: v1.2.0 + - Target: main ↓ -Workflow: Validates version format (1.2.0) +User Action: Trigger "Manual Draft Release" workflow + - Draft release title: v1.2.0 ↓ -Workflow: Checks tag v1.2.0 doesn't exist +Workflow: Validates title format (v1.2.0) + ↓ +Workflow: Finds draft release with title "v1.2.0" + ↓ +Workflow: Extracts version (1.2.0) and target branch (main) ↓ Workflow: Runs release:prepare on main branch ↓ @@ -76,35 +91,40 @@ Workflow: Pushes commits A & B to main ↓ Workflow: Pushes tag v1.2.0 → commit A ↓ -Workflow: Creates GitHub Release for v1.2.0 +Workflow: Uploads JAR artifacts to draft release + ↓ +Workflow: Publishes the GitHub Release ↓ Result: - 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 created + - 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 deployment status + - 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 the GitHub Release was created at the [Releases page](https://github.com/BerryCloud/xapi-java/releases) +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 Release Benefits +## Manual Draft Release Benefits -The manual release approach provides several advantages: +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 -- **Testing**: Ability to run dry-run releases (skip Maven Central deployment) for testing -- **Flexibility**: Choose when to create GitHub Releases independently -- **Review**: Coordinate releases with code reviews and team schedules +- **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 @@ -120,35 +140,30 @@ The manual release approach provides several advantages: ## Advanced Options -### Dry-Run Release (Testing) - -You can test the release process without deploying to Maven Central: +### Editing Draft Releases -1. Trigger the "Manual Release" workflow as described above -2. Check the **"Skip Maven Central"** option -3. The workflow will: - - Run all version updates and create commits/tags - - Skip the actual deployment to Maven Central - - Allow you to verify the release process works correctly +You can edit a draft release before triggering the workflow: -This is useful for: -- Testing the release workflow -- Verifying version numbering -- Ensuring all commits and tags are created correctly +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 Without GitHub Release +### Release Notes -If you want to deploy to Maven Central but create the GitHub Release manually later: +The workflow preserves the release notes you write in the draft release: -1. Trigger the "Manual Release" workflow -2. Uncheck the **"Create GitHub Release"** option -3. After verifying the Maven Central deployment, manually create a GitHub 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 manual 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 @@ -156,17 +171,20 @@ If the manual release workflow fails: | Issue | Solution | |-------|----------| - | Invalid version format | Use format `X.Y.Z` (e.g., `1.2.0`) - do NOT include `v` prefix | - | Tag already exists | The version has already been released. Use a different version number | + | 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 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:** - - **If commits were NOT pushed**: Simply re-run the workflow with the same version + - **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 @@ -175,6 +193,7 @@ If the manual release workflow fails: 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 From 98454ea55b1c970d6cb954f4c98cee19db406c20 Mon Sep 17 00:00:00 2001 From: Thomas Turrell-Croft Date: Mon, 8 Dec 2025 19:39:36 +0000 Subject: [PATCH 08/23] Delete .github/RELEASE_WORKFLOW_MIGRATION.md --- .github/RELEASE_WORKFLOW_MIGRATION.md | 61 --------------------------- 1 file changed, 61 deletions(-) delete mode 100644 .github/RELEASE_WORKFLOW_MIGRATION.md diff --git a/.github/RELEASE_WORKFLOW_MIGRATION.md b/.github/RELEASE_WORKFLOW_MIGRATION.md deleted file mode 100644 index 4907d226..00000000 --- a/.github/RELEASE_WORKFLOW_MIGRATION.md +++ /dev/null @@ -1,61 +0,0 @@ -# Release Workflow Migration Guide - -## Summary of Changes - -This project has migrated from an **automated release workflow** to a **manual draft release workflow** for better control and predictability. - -## What Changed - -### Before (Automated Release) -- **Trigger**: Creating a GitHub Release automatically triggered the workflow -- **Process**: Release was created in GitHub UI → workflow ran automatically -- **Control**: Limited control over when releases happened - -### After (Manual Draft Release) -- **Trigger**: Create draft release first, then manually trigger workflow via GitHub Actions UI -- **Process**: Create draft release → trigger workflow → enter draft title → workflow runs -- **Control**: Full control over release timing, review draft before publishing - -## Migration Benefits - -1. **Explicit Control**: Releases only happen when you explicitly trigger them -2. **No Surprises**: No accidental releases from automation -3. **Draft Review**: Create and review draft releases before deployment -4. **Release Notes**: Prepare comprehensive release notes in advance -5. **Artifact Attachment**: JAR files automatically uploaded to GitHub Release -6. **Review Process**: Better integration with code review and approval processes - -## How to Use the New Workflow - -See [RELEASING.md](../RELEASING.md) for complete instructions. - -**Quick Start:** -1. Go to [Releases page](https://github.com/BerryCloud/xapi-java/releases) -2. Click "Draft a new release" -3. Fill in tag (e.g., `v1.2.0`), title (same as tag), and release notes -4. Click "Save draft" -5. Go to [Actions tab](https://github.com/BerryCloud/xapi-java/actions) -6. Select "Manual Draft Release" workflow -7. Click "Run workflow" -8. Enter draft release title (e.g., `v1.2.0`) -9. Click "Run workflow" button - -## Workflow Input - -| Option | Description | Format | -|--------|-------------|--------| -| **Draft release title** | Title of the draft release to process | vX.Y.Z (e.g., v1.2.0) - MUST include 'v' prefix | - -## Old Workflow - -The old automated workflow (`release.yml`) has been: -- Completely removed from the repository -- Replaced with `manual-release.yml` (Manual Draft Release workflow) - -**The automated release workflow is no longer available.** All releases must now use the manual draft release process. - -## Questions? - -- See [RELEASING.md](../RELEASING.md) for detailed documentation -- Check [manual-release.yml](workflows/manual-release.yml) for workflow implementation -- Open an issue if you encounter problems From effaa95b34f05c7501d06dfccd62d7d67b215f77 Mon Sep 17 00:00:00 2001 From: Thomas Turrell-Croft Date: Mon, 8 Dec 2025 19:42:25 +0000 Subject: [PATCH 09/23] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/manual-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index a8374f91..d032cdc3 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -1,4 +1,4 @@ -name: Manual Draft Release +name: Manual Release on: workflow_dispatch: From 9ae841305c54941704f73d99657a174cc1cdd132 Mon Sep 17 00:00:00 2001 From: Thomas Turrell-Croft Date: Mon, 8 Dec 2025 19:43:11 +0000 Subject: [PATCH 10/23] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/manual-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index d032cdc3..7f9eb927 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -110,7 +110,7 @@ jobs: - uses: actions/create-github-app-token@v2 id: app-token with: - app-id: ${{ vars.APP_ID }} + app-id: ${{ secrets.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} - name: Checkout target commitish with full history (needed to commit & tag) From 157cd263d7f8520682d1866759e075470e6a2452 Mon Sep 17 00:00:00 2001 From: Thomas Turrell-Croft Date: Mon, 8 Dec 2025 19:44:19 +0000 Subject: [PATCH 11/23] Apply suggestion from @thomasturrell --- .github/workflows/manual-release.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index 7f9eb927..9d292d9c 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -252,7 +252,6 @@ jobs: echo "Updating draft release to point to tag $TAG..." # Update the release to point to the new tag - # The release remains in draft state for manual review before publishing gh release edit "$TAG" --repo "$REPO_FULL_NAME" --tag "$TAG" --draft echo "Draft release updated successfully!" From 4781dfe8947210afc58499290edb832e4a410925 Mon Sep 17 00:00:00 2001 From: Thomas Turrell-Croft Date: Mon, 8 Dec 2025 19:45:32 +0000 Subject: [PATCH 12/23] Apply suggestion from @thomasturrell --- .github/workflows/manual-release.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index 9d292d9c..e17d1b97 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -255,7 +255,6 @@ jobs: gh release edit "$TAG" --repo "$REPO_FULL_NAME" --tag "$TAG" --draft echo "Draft release updated successfully!" - echo "Review the release and publish when ready at:" echo "https://github.com/$REPO_FULL_NAME/releases/tag/$TAG" - name: Publish GitHub release From 5885082937d9c41b5efa0a53faec97fdc31dbcd6 Mon Sep 17 00:00:00 2001 From: Thomas Turrell-Croft Date: Mon, 8 Dec 2025 19:45:41 +0000 Subject: [PATCH 13/23] Apply suggestion from @thomasturrell --- .github/workflows/manual-release.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index e17d1b97..d5f9fa9d 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -255,7 +255,6 @@ jobs: gh release edit "$TAG" --repo "$REPO_FULL_NAME" --tag "$TAG" --draft echo "Draft release updated successfully!" - echo "https://github.com/$REPO_FULL_NAME/releases/tag/$TAG" - name: Publish GitHub release env: From b6db9ab668cb70b61491e49732d1886691ebd343 Mon Sep 17 00:00:00 2001 From: Thomas Turrell-Croft Date: Mon, 8 Dec 2025 19:47:31 +0000 Subject: [PATCH 14/23] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/manual-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index d5f9fa9d..c4d96c27 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -218,7 +218,7 @@ jobs: # 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" + echo "Processing module: $module" # Upload all jar files (main, sources, javadoc, etc.) for jar in "$module/target"/*.jar; do From 2f05ff0fc0b3197b643d6fa4268422c0f97f40a9 Mon Sep 17 00:00:00 2001 From: Thomas Turrell-Croft Date: Mon, 8 Dec 2025 19:47:44 +0000 Subject: [PATCH 15/23] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/manual-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index c4d96c27..b78fa289 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -227,8 +227,8 @@ jobs: # Skip SNAPSHOT jars if [[ "$jar" == *-SNAPSHOT.jar ]]; then - echo "Skipping SNAPSHOT jar: $jar" - continue + echo "Skipping SNAPSHOT jar: $jar" + continue fi echo "Uploading: $jar" From afb32ef34516494548ba4e13f5bb47549c125a9d Mon Sep 17 00:00:00 2001 From: Thomas Turrell-Croft Date: Mon, 8 Dec 2025 22:56:17 +0000 Subject: [PATCH 16/23] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/manual-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index b78fa289..db3992dc 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -47,7 +47,7 @@ jobs: > /tmp/releases_list.json 2>/dev/null; then : else - echo "ERROR: Failed to list releases for $REPO_FULL_NAME." + echo "::error::Failed to list releases for $REPO_FULL_NAME." exit 1 fi From 8556b347b3b53f0bc3c88193315b24d9bfc00ac3 Mon Sep 17 00:00:00 2001 From: Thomas Turrell-Croft Date: Mon, 8 Dec 2025 22:56:29 +0000 Subject: [PATCH 17/23] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/manual-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index db3992dc..fc26a788 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -72,7 +72,7 @@ jobs: # Ensure the release is a draft if [ "$DRAFT" != "true" ]; then - echo "ERROR: Release '$NAME' exists but is not a draft (draft=$DRAFT)." + 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 From cb88ffa2f4499c2dd25ba894a05f59a80468d0be Mon Sep 17 00:00:00 2001 From: Thomas Turrell-Croft Date: Mon, 8 Dec 2025 22:56:40 +0000 Subject: [PATCH 18/23] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/manual-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index fc26a788..194ef1c9 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -79,7 +79,7 @@ jobs: # 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 "::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 From 8915b546c36f25836e4a849cc61eab5aa9cb0b3c Mon Sep 17 00:00:00 2001 From: Thomas Turrell-Croft Date: Mon, 8 Dec 2025 22:57:35 +0000 Subject: [PATCH 19/23] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/manual-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index 194ef1c9..ed709c02 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -181,7 +181,7 @@ jobs: MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} - - name: Push changes to originating branch + - name: Push changes to target branch run: | TARGET_BRANCH="${{ steps.release.outputs.target_commitish }}" TAG_NAME="${{ steps.release.outputs.tag_name }}" From 7ba8bc6b5ed04e3aea18b092b2fd70eeb497c044 Mon Sep 17 00:00:00 2001 From: Thomas Turrell-Croft Date: Mon, 8 Dec 2025 22:57:55 +0000 Subject: [PATCH 20/23] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/manual-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index ed709c02..e519eab5 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -240,7 +240,7 @@ jobs: echo "All artifacts uploaded successfully!" - - name: Update draft release to point to new tag + - name: Associate draft release with created tag env: GH_TOKEN: ${{ steps.app-token.outputs.token }} run: | From 541c63c9694ad97a37cbe9f15adbe2f406e8ec13 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 10:33:19 +0000 Subject: [PATCH 21/23] Add verification to check if tag already exists before release Co-authored-by: thomasturrell <1552612+thomasturrell@users.noreply.github.com> --- .github/workflows/manual-release.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index e519eab5..bc212db7 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -121,6 +121,28 @@ jobs: 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 + if git rev-parse "$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 + if git ls-remote --tags origin "$TAG_NAME" | grep -q "$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: From 9001cd72628858346b5f3bf50d7d890e971646b0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 10:34:37 +0000 Subject: [PATCH 22/23] Fix tag existence check to use exact matching and prevent false positives Co-authored-by: thomasturrell <1552612+thomasturrell@users.noreply.github.com> --- .github/workflows/manual-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index bc212db7..c2da39fb 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -134,8 +134,8 @@ jobs: exit 1 fi - # Check if tag exists on remote - if git ls-remote --tags origin "$TAG_NAME" | grep -q "$TAG_NAME"; then + # Check if tag exists on remote (use exact matching to avoid false positives) + if git ls-remote --tags origin "$TAG_NAME" | grep -q "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 From 64cfb6909b7912500e41be71da5a7f7080258367 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Dec 2025 10:37:21 +0000 Subject: [PATCH 23/23] Improve tag existence check robustness with literal string matching Co-authored-by: thomasturrell <1552612+thomasturrell@users.noreply.github.com> --- .github/workflows/manual-release.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index c2da39fb..9083bfe2 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -127,15 +127,15 @@ jobs: echo "Checking if tag $TAG_NAME already exists..." - # Check if tag exists locally - if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then + # 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 exact matching to avoid false positives) - if git ls-remote --tags origin "$TAG_NAME" | grep -q "refs/tags/$TAG_NAME$"; then + # 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