diff --git a/.enforcer-scripts/validate-jbang-versions.groovy b/.enforcer-scripts/validate-jbang-versions.groovy new file mode 100644 index 000000000..b8f283319 --- /dev/null +++ b/.enforcer-scripts/validate-jbang-versions.groovy @@ -0,0 +1,37 @@ +// Fetch the property from the Maven project +def scriptName = project.properties['jbang.script.name'] + +// Fail if the script property is missing +if (scriptName == null) { + throw new IllegalStateException("[ERROR] JBang validator: No jbang.script.name set in properties") +} + +def jbangFile = new File(project.basedir, scriptName) +if (!jbangFile.exists()) { + // If a script name was explicitly provided but doesn't exist, fail. + // If using the fallback, we might want to just skip (return true). + throw new IllegalStateException("[ERROR] JBang validator: File not found: " + jbangFile.absolutePath) +} + +def expectedVersion = project.version +def groupPrefix = "//DEPS io.github.a2asdk:" +def success = true + +jbangFile.eachLine { line -> + if (line.trim().startsWith(groupPrefix)) { + def lastColon = line.lastIndexOf(":") + if (lastColon != -1) { + def actualVersion = line.substring(lastColon + 1).trim().tokenize()[0] + if (actualVersion != expectedVersion) { + System.err.println("[ERROR] JBang Version Mismatch in " + scriptName) + System.err.println(" Expected: " + expectedVersion) + System.err.println(" Found: " + actualVersion + " in line: \"" + line.trim() + "\"") + success = false + } + } + } +} + +if (!success) { + throw new IllegalStateException("[ERROR] JBang version validation failed") +} \ No newline at end of file diff --git a/.github/workflows/create-github-release.yml b/.github/workflows/create-github-release.yml new file mode 100644 index 000000000..15baee6c5 --- /dev/null +++ b/.github/workflows/create-github-release.yml @@ -0,0 +1,127 @@ +name: Create GitHub Release + +on: + push: + tags: + - 'v?[0-9]+.[0-9]+.[0-9]+*' # Trigger on tags like v1.0.0, 1.2.3, v1.2.3.Alpha1 etc. + +jobs: + create-release: + # Only run this job for the main repository, not for forks + if: github.repository == 'a2aproject/a2a-java' + runs-on: ubuntu-latest + permissions: + contents: write # Required to create releases + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history for changelog generation + + - name: Extract version from tag + id: version + run: | + # Remove 'v' prefix if present + VERSION=${GITHUB_REF_NAME#v} + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Version: $VERSION" + + - name: Generate release notes + id: release_notes + uses: actions/github-script@v7 + with: + script: | + const version = '${{ steps.version.outputs.version }}'; + + // Get the previous tag + let previousTag = ''; + try { + const { data: tags } = await github.rest.repos.listTags({ + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 100 + }); + + // Find current tag index + const currentIndex = tags.findIndex(tag => tag.name === context.ref.replace('refs/tags/', '')); + + // Get previous tag (next in list) + if (currentIndex >= 0 && currentIndex < tags.length - 1) { + previousTag = tags[currentIndex + 1].name; + } + } catch (error) { + console.log('Could not fetch previous tag:', error.message); + } + + // Build release notes + let releaseNotes = `## A2A Java SDK ${version}\n\n`; + + // Add Maven Central installation instructions + releaseNotes += `### Installation\n\n`; + releaseNotes += `**Maven**:\n\`\`\`xml\n\n`; + releaseNotes += ` io.github.a2asdk\n`; + releaseNotes += ` a2a-java-sdk-client\n`; + releaseNotes += ` ${version}\n`; + releaseNotes += `\n\`\`\`\n\n`; + + releaseNotes += `**Gradle**:\n\`\`\`gradle\n`; + releaseNotes += `implementation 'io.github.a2asdk:a2a-java-sdk-client:${version}'\n`; + releaseNotes += `\`\`\`\n\n`; + + // Add links + releaseNotes += `### Links\n\n`; + releaseNotes += `- [Maven Central](https://central.sonatype.com/artifact/io.github.a2asdk/a2a-java-sdk-parent/${version})\n`; + releaseNotes += `- [JavaDoc](https://javadoc.io/doc/io.github.a2asdk/a2a-java-sdk-parent/${version})\n`; + releaseNotes += `- [GitHub](https://github.com/a2aproject/a2a-java/tree/v${version})\n\n`; + + // Add changelog header + if (previousTag) { + releaseNotes += `### Changes since ${previousTag}\n\n`; + releaseNotes += `[Full Changelog](https://github.com/a2aproject/a2a-java/compare/${previousTag}...v${version})\n\n`; + } else { + releaseNotes += `### Changes\n\n`; + } + + return releaseNotes; + + - name: Create GitHub Release + uses: actions/github-script@v7 + with: + script: | + const version = '${{ steps.version.outputs.version }}'; + const tag = context.ref.replace('refs/tags/', ''); + const releaseNotes = ${{ steps.release_notes.outputs.result }}; + + // Determine if this is a pre-release + const isPrerelease = version.includes('Alpha') || + version.includes('Beta') || + version.includes('RC') || + version.includes('SNAPSHOT'); + + try { + const { data: release } = await github.rest.repos.createRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + tag_name: tag, + name: `v${version}`, + body: releaseNotes, + draft: false, + prerelease: isPrerelease, + generate_release_notes: true // GitHub will append auto-generated notes + }); + + console.log(`✅ Created release: ${release.html_url}`); + core.summary + .addHeading(`Release v${version} Created`) + .addLink('View Release', release.html_url) + .addLink('Maven Central', `https://central.sonatype.com/artifact/io.github.a2asdk/a2a-java-sdk-parent/${version}`) + .write(); + + } catch (error) { + if (error.status === 422 && error.message.includes('already_exists')) { + console.log('⚠️ Release already exists for this tag'); + } else { + throw error; + } + } diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 000000000..079a18e94 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,274 @@ +# Release Process + +This document describes the process for releasing a new version of the A2A Java SDK to Maven Central. + +## Overview + +The release process involves: +1. Updating version numbers across the project (automated) +2. Opening and merging a release PR +3. Tagging the release +4. Automatic deployment to Maven Central +5. Automatic GitHub release creation +6. Incrementing to next SNAPSHOT version + +## Prerequisites + +### Required Accounts & Access +- GitHub repository write access to `a2aproject/a2a-java` +- Maven Central account: namespace: `io.github.a2asdk` + +### Required Secrets (Repository Maintainers) +The following secrets must be configured in GitHub repository settings: +- `GPG_SIGNING_KEY`: Private GPG key for artifact signing +- `GPG_SIGNING_PASSPHRASE`: Passphrase for the GPG key +- `CENTRAL_TOKEN_USERNAME`: Maven Central username token +- `CENTRAL_TOKEN_PASSWORD`: Maven Central password token + +## Release Steps + +The examples below use versions like `0.4.0.Alpha1-SNAPSHOT` and `0.4.0.Alpha1` for demonstration. Be sure to substitute these with the actual versions for your release. + +### 1. Prepare Release Version + +Use the provided script to update all version numbers: + +```bash +# Preview changes (dry run) +./update-version.sh 0.4.0.Alpha1-SNAPSHOT 0.4.0.Alpha1 --dry-run + +# Apply version update +./update-version.sh 0.4.0.Alpha1-SNAPSHOT 0.4.0.Alpha1 +``` + +The script automatically updates: +- ✅ All `pom.xml` files +- ✅ All JBang script `//DEPS` declarations in `examples/` +- ✅ Validates the JBang update with built-in GMavenPlus validation + +**What gets updated**: +``` +pom.xml: 0.4.0.Alpha1-SNAPSHOT → 0.4.0.Alpha1 +//DEPS io.github...: 0.4.0.Alpha1-SNAPSHOT → 0.4.0.Alpha1 +``` + +### 2. Verify Changes + +Review the changes before committing: + +```bash +# Review all changes +git diff + +# Verify build works +mvn clean install +``` + +### 3. Create Release PR + +Create a pull request with the version update: + +```bash +git checkout -b release/0.4.0.Alpha1 +git add -A +git commit -m "chore: release 0.4.0.Alpha1" +git push origin release/0.4.0.Alpha1 +``` + +Open PR on GitHub with title: `chore: release 0.4.0.Alpha1` + +### 4. CI Verification + +The `build-with-release-profile.yml` workflow automatically verifies: +- ✅ Build succeeds with `-Prelease` profile +- ✅ All JavaDoc generation succeeds +- ✅ GPG signing works correctly +- ✅ JBang version validation passes +- ✅ No compilation or test failures + +**Important**: This workflow tests the actual PR branch (not main) to catch issues before merge. + +Wait for all CI checks to pass before proceeding. + +### 5. Merge Release PR + +Once all checks pass and the PR is approved: +- Merge the PR to `main` branch +- **Do NOT squash** - keep the release commit message intact for changelog + +### 6. Tag and Push + +After the PR is merged to main: + +```bash +# Switch to main and pull the merged changes +git checkout main +git pull origin main + +# Create annotated tag +git tag -a v0.4.0.Alpha1 -m "Release 0.4.0.Alpha1" + +# Push the tag (triggers deployment + GitHub release) +git push origin v0.4.0.Alpha1 +``` + +### 7. Automated Workflows Triggered + +Pushing the tag triggers **two workflows**: + +#### A. Maven Central Deployment (`release-to-maven-central.yml`) +1. Detects tag (pattern: `v?[0-9]+.[0-9]+.[0-9]+*`) +2. Checks out the tagged commit +3. Builds with `-Prelease -DskipTests` +4. Signs all artifacts with GPG +5. Deploys to Maven Central with auto-publish + +**⏱️ Deployment typically takes 30 minutes**, but can vary. + +#### B. GitHub Release Creation (`create-github-release.yml`) +1. Detects the same tag +2. Extracts version from tag name +3. Generates release notes from commits since last release +4. Creates GitHub release with: + - Auto-generated changelog + - Link to Maven Central artifacts + - Installation instructions + +### 8. Verify Deployment + +Check that artifacts are available: + +**Maven Central**: +``` +https://central.sonatype.com/artifact/io.github.a2asdk/a2a-java-sdk-parent/0.4.0.Alpha1 +``` + +**GitHub Release**: +``` +https://github.com/a2aproject/a2a-java/releases/tag/v0.4.0.Alpha1 +``` + +Artifacts should include: +- `.jar` files (main artifacts) +- `-sources.jar` (source code) +- `-javadoc.jar` (JavaDoc) +- `.pom` files +- `.asc` GPG signatures for all artifacts + +### 9. Increment to Next SNAPSHOT + +Prepare repository for next development cycle: + +```bash +# Update to next SNAPSHOT version +./update-version.sh 0.4.0.Alpha1 0.4.0.Alpha2-SNAPSHOT + +# Create and push PR +git checkout -b chore/bump-to-0.4.0.Alpha2-SNAPSHOT +git add -A +git commit -m "chore: bump version to 0.4.0.Alpha2-SNAPSHOT" +git push origin chore/bump-to-0.4.0.Alpha2-SNAPSHOT +``` + +Open PR, wait for CI, and merge. + +## Troubleshooting + +### Build fails with "JBang version mismatch" + +**Cause**: JBang script dependencies don't match POM version + +**Fix**: +```bash +# Re-run the update script to fix mismatches +./update-version.sh OLD_VERSION NEW_VERSION + +# Or manually check: +grep -r "//DEPS io.github.a2asdk:" examples/ +``` + +### GPG signing fails in workflow + +**Cause**: GPG secrets are missing or incorrect + +**Fix**: Repository maintainers - verify secrets in: +``` +Settings → Secrets and variables → Actions +``` +Check: `GPG_SIGNING_KEY`, `GPG_SIGNING_PASSPHRASE` + +### Maven Central deployment times out + +**Cause**: Normal Maven Central processing delays + +**Fix**: Wait (up to 2 hours). Check status: +``` +https://central.sonatype.com/publishing +``` + +### Deployment fails with authentication error + +**Cause**: Maven Central tokens expired or incorrect + +**Fix**: Repository maintainers: +1. Log in to Maven Central with the GitHub account for the a2asdk user. +2. Generate new tokens: `User → Generate User Token` +3. Update secrets: `CENTRAL_TOKEN_USERNAME` and `CENTRAL_TOKEN_PASSWORD` + +### GitHub release not created + +**Cause**: Workflow failed or tag pattern didn't match + +**Fix**: +```bash +# Check workflow runs +https://github.com/a2aproject/a2a-java/actions + +# Manually create release if needed +https://github.com/a2aproject/a2a-java/releases/new +``` + +### Need to rollback a release + +**Not possible** - Maven Central does not allow artifact deletion. + +**Mitigation**: +1. Release a patch version with fixes (e.g., `0.4.0.Alpha1` → `0.4.0.Alpha2`) +2. Document issues in GitHub release notes +3. Update documentation to recommend correct version + +## Version Numbering + +Follow semantic versioning with qualifiers: + +- **Major.Minor.Patch** - Standard releases (e.g., `1.0.0`) +- **Major.Minor.Patch.AlphaN** - Alpha releases (e.g., `0.4.0.Alpha1`) +- **Major.Minor.Patch.BetaN** - Beta releases (e.g., `0.3.0.Beta1`) +- **Major.Minor.Patch.RCN** - Release candidates (e.g., `1.0.0.RC1`) +- **-SNAPSHOT** - Development versions (e.g., `0.4.0.Alpha2-SNAPSHOT`) + +## Workflows Reference + +### build-with-release-profile.yml +- **Triggers**: All PRs, all pushes +- **Purpose**: Verify builds with `-Prelease` profile +- **Special**: Tests actual PR branch (not main) using `pull_request_target` with explicit checkout +- **Requires**: GPG and Maven Central secrets + +### release-to-maven-central.yml +- **Triggers**: Tags matching `v?[0-9]+.[0-9]+.[0-9]+*` +- **Purpose**: Deploy to Maven Central +- **Duration**: ~30 minutes +- **Requires**: GPG and Maven Central secrets + +### create-github-release.yml +- **Triggers**: Tags matching `v?[0-9]+.[0-9]+.[0-9]+*` +- **Purpose**: Create GitHub release with changelog +- **Features**: Auto-generated release notes, Maven Central links +- **Requires**: Default `GITHUB_TOKEN` (automatic) + +## Support + +For questions or issues with the release process: +- Open an issue: https://github.com/a2aproject/a2a-java/issues +- Reference: [Issue #532](https://github.com/a2aproject/a2a-java/issues/532) - Release process improvements diff --git a/examples/helloworld/client/pom.xml b/examples/helloworld/client/pom.xml index 2e8a3f3f0..c362ce9a9 100644 --- a/examples/helloworld/client/pom.xml +++ b/examples/helloworld/client/pom.xml @@ -15,6 +15,10 @@ Java SDK A2A Examples Examples for the Java SDK for the Agent2Agent Protocol (A2A) + + src/main/java/io/a2a/examples/helloworld/HelloWorldRunner.java + + io.github.a2asdk @@ -39,6 +43,24 @@ org.apache.maven.plugins maven-surefire-plugin + + org.codehaus.gmavenplus + gmavenplus-plugin + + + validate-jbang-versions + validate + + execute + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 8242e9462..c7987fd54 100644 --- a/pom.xml +++ b/pom.xml @@ -42,12 +42,15 @@ UTF-8 3.5.0 3.14.1 + 3.6.2 3.4.2 3.3.1 3.1.2 3.8.0 3.2.4 + 4.2.1 0.8.0 + 5.0.3 2.13.2 4.1.0 2.0.1 @@ -388,6 +391,25 @@ + + org.codehaus.gmavenplus + gmavenplus-plugin + ${gmavenplus-plugin.version} + + + org.apache.groovy + groovy + ${groovy.version} + runtime + + + org.apache.groovy + groovy-ant + ${groovy.version} + runtime + + + org.apache.maven.plugins maven-jar-plugin diff --git a/update-version.sh b/update-version.sh new file mode 100755 index 000000000..98a6cb3cd --- /dev/null +++ b/update-version.sh @@ -0,0 +1,132 @@ +#!/bin/bash + +# Update version across POMs and JBang scripts +# Usage: ./update-version.sh FROM_VERSION TO_VERSION [--dry-run] + +set -euo pipefail # Exit on error, unset var, and pipe failure + +FROM_VERSION=$1 +TO_VERSION=$2 + +# Validate arguments +if [ -z "$FROM_VERSION" ] || [ -z "$TO_VERSION" ]; then + echo "❌ Error: Missing version arguments." + echo "Usage: $0 FROM_VERSION TO_VERSION [--dry-run]" + echo "Example: $0 0.3.0.Beta1-SNAPSHOT 0.3.0.Beta1" + exit 1 +fi + +# Check if TO_VERSION looks like a flag +if [[ "$TO_VERSION" == --* ]]; then + echo "❌ Error: TO_VERSION cannot be a flag. Did you mean to provide both FROM_VERSION and TO_VERSION?" + echo "Usage: $0 FROM_VERSION TO_VERSION [--dry-run]" + echo "Example: $0 0.3.0.Beta1-SNAPSHOT 0.3.0.Beta1" + exit 1 +fi + +DRY_RUN=false +if [ "$3" = "--dry-run" ]; then + DRY_RUN=true +elif [ -n "$3" ]; then + echo "❌ Error: Invalid third argument. Only '--dry-run' is supported." + echo "Usage: $0 FROM_VERSION TO_VERSION [--dry-run]" + exit 1 +fi + +# Verify we're in the right directory +if [ ! -f "pom.xml" ]; then + echo "❌ Error: pom.xml not found. Run this script from the a2a-java root directory." + exit 1 +fi + +echo "🔍 Updating version from $FROM_VERSION → $TO_VERSION" +echo "" + +# Find all files to update +POM_FILES=$(find . -type f -name "pom.xml" | sort) +JBANG_FILES=$(find . -type f -name "*.java" -path "*/examples/*" -exec grep -l "//DEPS io.github.a2asdk:" {} \; | sort) + +POM_COUNT=$(echo "$POM_FILES" | wc -l | tr -d ' ') +JBANG_COUNT=$(echo "$JBANG_FILES" | wc -l | tr -d ' ') + +echo "📄 Found $POM_COUNT pom.xml files" +echo "📄 Found $JBANG_COUNT JBang script files" +echo "" + +# Show what will be changed +if [ "$DRY_RUN" = true ]; then + echo "🔎 DRY RUN - showing what would be changed:" + echo "" + + echo "=== POM files with version $FROM_VERSION ===" + for file in $POM_FILES; do + if grep -q "$FROM_VERSION" "$file"; then + echo " 📝 $file" + grep -n "$FROM_VERSION" "$file" | sed 's/^/ /' + fi + done + echo "" + + echo "=== JBang files with version $FROM_VERSION ===" + for file in $JBANG_FILES; do + if grep -q "//DEPS io.github.a2asdk:.*:$FROM_VERSION" "$file"; then + echo " 📝 $file" + grep -n "//DEPS io.github.a2asdk:.*:$FROM_VERSION" "$file" | sed 's/^/ /' + fi + done + echo "" + + echo "✅ Dry run complete. Run without --dry-run to apply changes." + exit 0 +fi + +# Perform actual updates +echo "🔄 Updating files..." +echo "" + +UPDATED_POMS=0 +UPDATED_JBANGS=0 + +# Update POM files +echo "Updating pom.xml files..." +for file in $POM_FILES; do + if grep -q "$FROM_VERSION" "$file"; then + if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS requires empty string after -i + sed -i "" -e "s|>$FROM_VERSION<|>$TO_VERSION<|g" "$file" + else + # Linux doesn't need it + sed -i "s|>$FROM_VERSION<|>$TO_VERSION<|g" "$file" + fi + echo " ✅ $file" + UPDATED_POMS=$((UPDATED_POMS + 1)) + fi +done +echo "" + +# Update JBang files +echo "Updating JBang script files..." +for file in $JBANG_FILES; do + if grep -q "//DEPS io.github.a2asdk:.*:$FROM_VERSION" "$file"; then + if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS requires empty string after -i + sed -i "" -e "s/\(\/\/DEPS io.github.a2asdk:.*:\)$FROM_VERSION/\1$TO_VERSION/g" "$file" + else + # Linux doesn't need it + sed -i "s/\(\/\/DEPS io.github.a2asdk:.*:\)$FROM_VERSION/\1$TO_VERSION/g" "$file" + fi + echo " ✅ $file" + UPDATED_JBANGS=$((UPDATED_JBANGS + 1)) + fi +done +echo "" + +# Summary +echo "✅ Version update complete!" +echo " Updated $UPDATED_POMS pom.xml files" +echo " Updated $UPDATED_JBANGS JBang script files" +echo "" +echo "📋 Next steps:" +echo " 1. Review changes: git diff" +echo " 2. Verify build: mvn clean install" +echo " 3. Commit changes: git commit -am 'chore: release $TO_VERSION'"