diff --git a/.github/scripts/release/test-update-changelog.sh b/.github/scripts/release/test-update-changelog.sh new file mode 100755 index 000000000..c5fb23792 --- /dev/null +++ b/.github/scripts/release/test-update-changelog.sh @@ -0,0 +1,270 @@ +#!/bin/bash +# Test script for update-changelog.sh + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +UPDATE_SCRIPT="${SCRIPT_DIR}/update-changelog.sh" +TEST_DIR="/tmp/changelog-test-$$" + +# Colors for output +GREEN='\033[0;32m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +passed=0 +failed=0 + +# Setup test directory +mkdir -p "$TEST_DIR" + +# Cleanup on exit +cleanup() { + rm -rf "$TEST_DIR" +} +trap cleanup EXIT + +# Helper function to run a test +run_test() { + local test_name="$1" + local test_func="$2" + + echo -n "Testing: $test_name ... " + + if $test_func; then + echo -e "${GREEN}PASSED${NC}" + passed=$((passed + 1)) + else + echo -e "${RED}FAILED${NC}" + failed=$((failed + 1)) + fi +} + +# Test 1: Basic functionality - Replace Unreleased with version +test_basic_replace() { + local test_file="${TEST_DIR}/test1.md" + cat > "$test_file" << 'EOF' +# Changelog + +## [Unreleased] + +### Added +- New feature + +## [1.0.0] - 2026-01-01 + +### Added +- Initial release + +[1.0.0]: https://github.com/test/repo/releases/tag/1.0.0 +EOF + + # Run the script + CHANGELOG_FILE="$test_file" bash "$UPDATE_SCRIPT" 1.0.1 > /dev/null 2>&1 + + # Verify the changes + if grep -q "## \[Unreleased\]" "$test_file" && \ + grep -q "## \[1.0.1\] - $(date +%Y-%m-%d)" "$test_file" && \ + grep -q "\[Unreleased\]: https://github.com/test/repo/compare/java/v1.0.1...HEAD" "$test_file" && \ + grep -q "\[1.0.1\]: https://github.com/test/repo/compare/java/v1.0.0...java/v1.0.1" "$test_file"; then + return 0 + else + return 1 + fi +} + +# Test 2: Handle CHANGELOG without Unreleased link +test_no_unreleased_link() { + local test_file="${TEST_DIR}/test2.md" + cat > "$test_file" << 'EOF' +# Changelog + +## [Unreleased] + +### Added +- New feature + +## [1.0.0] - 2026-01-01 + +[1.0.0]: https://github.com/test/repo/releases/tag/1.0.0 +EOF + + CHANGELOG_FILE="$test_file" bash "$UPDATE_SCRIPT" 1.0.1 > /dev/null 2>&1 + + # Should add both Unreleased and version links + if grep -q "\[Unreleased\]: https://github.com/test/repo/compare/java/v1.0.1...HEAD" "$test_file" && \ + grep -q "\[1.0.1\]: https://github.com/test/repo/compare/java/v1.0.0...java/v1.0.1" "$test_file"; then + return 0 + else + return 1 + fi +} + +# Test 3: Preserve content structure +test_preserve_content() { + local test_file="${TEST_DIR}/test3.md" + cat > "$test_file" << 'EOF' +# Changelog + +## [Unreleased] + +### Added +- Feature A +- Feature B + +### Fixed +- Bug fix + +## [1.0.0] - 2026-01-01 + +[1.0.0]: https://github.com/test/repo/releases/tag/1.0.0 +EOF + + CHANGELOG_FILE="$test_file" bash "$UPDATE_SCRIPT" 1.0.1 > /dev/null 2>&1 + + # Verify content is preserved under the new version + if grep -A 6 "## \[1.0.1\]" "$test_file" | grep -q "Feature A" && \ + grep -A 6 "## \[1.0.1\]" "$test_file" | grep -q "Bug fix"; then + return 0 + else + return 1 + fi +} + +# Test 4: Error handling - no Unreleased section +test_no_unreleased_section() { + local test_file="${TEST_DIR}/test4.md" + cat > "$test_file" << 'EOF' +# Changelog + +## [1.0.0] - 2026-01-01 + +[1.0.0]: https://github.com/test/repo/releases/tag/1.0.0 +EOF + + # Should fail because there's no Unreleased section + if ! CHANGELOG_FILE="$test_file" bash "$UPDATE_SCRIPT" 1.0.1 > /dev/null 2>&1; then + return 0 + else + return 1 + fi +} + +# Test 5: Multiple version handling +test_multiple_versions() { + local test_file="${TEST_DIR}/test5.md" + cat > "$test_file" << 'EOF' +# Changelog + +## [Unreleased] + +### Added +- New feature + +## [1.0.1] - 2026-02-01 + +## [1.0.0] - 2026-01-01 + +[1.0.1]: https://github.com/test/repo/compare/v1.0.0...v1.0.1 +[1.0.0]: https://github.com/test/repo/releases/tag/1.0.0 +EOF + + CHANGELOG_FILE="$test_file" bash "$UPDATE_SCRIPT" 1.0.2 > /dev/null 2>&1 + + # Verify the new version is added and links are updated + if grep -q "## \[1.0.2\] - $(date +%Y-%m-%d)" "$test_file" && \ + grep -q "\[1.0.2\]: https://github.com/test/repo/compare/java/v1.0.1...java/v1.0.2" "$test_file"; then + return 0 + else + return 1 + fi +} + +# Test 6: Beta-java version format (e.g., 1.0.0-beta-java.N) +test_beta_java_version() { + local test_file="${TEST_DIR}/test6.md" + cat > "$test_file" << 'EOF' +# Changelog + +## [Unreleased] + +### Added +- New feature + +## [1.0.0-beta-java.1] - 2026-05-01 + +[Unreleased]: https://github.com/test/repo/compare/v1.0.0-beta-java.1...HEAD +[1.0.0-beta-java.1]: https://github.com/test/repo/compare/v0.3.0-java.2...v1.0.0-beta-java.1 +[0.3.0-java.2]: https://github.com/test/repo/releases/tag/0.3.0-java.2 +EOF + + CHANGELOG_FILE="$test_file" bash "$UPDATE_SCRIPT" 1.0.0-beta-java.2 > /dev/null 2>&1 + + # The [Unreleased] link should now point to java/v1.0.0-beta-java.2 + # [1.0.0-beta-java.2] should compare from java/v1.0.0-beta-java.1 + if grep -q "\[Unreleased\]: https://github.com/test/repo/compare/java/v1.0.0-beta-java.2...HEAD" "$test_file" && \ + grep -q "\[1.0.0-beta-java.2\]: https://github.com/test/repo/compare/java/v1.0.0-beta-java.1...java/v1.0.0-beta-java.2" "$test_file"; then + return 0 + else + return 1 + fi +} + +# Test 7: No duplicate [Unreleased] links when existing [Unreleased] link is present +test_no_duplicate_unreleased_links() { + local test_file="${TEST_DIR}/test7.md" + cat > "$test_file" << 'EOF' +# Changelog + +## [Unreleased] + +### Added +- New feature + +## [1.0.0-beta-java.2] - 2026-05-08 + +## [1.0.0-beta-java.1] - 2026-05-05 + +[Unreleased]: https://github.com/test/repo/compare/v1.0.0-beta-java.2...HEAD +[1.0.0-beta-java.2]: https://github.com/test/repo/compare/v1.0.0-beta-java.1...v1.0.0-beta-java.2 +[1.0.0-beta-java.1]: https://github.com/test/repo/compare/v0.3.0-java.2...v1.0.0-beta-java.1 +[0.3.0-java.2]: https://github.com/test/repo/releases/tag/0.3.0-java.2 +EOF + + CHANGELOG_FILE="$test_file" bash "$UPDATE_SCRIPT" 1.0.0-beta-java.3 > /dev/null 2>&1 + + # Count [Unreleased] link definitions - there should be exactly one + local unreleased_count + unreleased_count=$(grep -c "^\[Unreleased\]:" "$test_file") + if [ "$unreleased_count" -eq 1 ] && \ + grep -q "\[Unreleased\]: https://github.com/test/repo/compare/java/v1.0.0-beta-java.3...HEAD" "$test_file" && \ + grep -q "\[1.0.0-beta-java.3\]: https://github.com/test/repo/compare/java/v1.0.0-beta-java.2...java/v1.0.0-beta-java.3" "$test_file"; then + return 0 + else + return 1 + fi +} + +# Run all tests +echo "Running CHANGELOG update script tests..." +echo "" + +run_test "Basic functionality - Replace Unreleased with version" test_basic_replace +run_test "Handle CHANGELOG without Unreleased link" test_no_unreleased_link +run_test "Preserve content structure" test_preserve_content +run_test "Error handling - no Unreleased section" test_no_unreleased_section +run_test "Multiple version handling" test_multiple_versions +run_test "Beta-java version format (e.g., 1.0.0-beta-java.N)" test_beta_java_version +run_test "No duplicate [Unreleased] links when existing link is present" test_no_duplicate_unreleased_links + +echo "" +echo "==========================================" +echo -e "Tests passed: ${GREEN}${passed}${NC}" +echo -e "Tests failed: ${RED}${failed}${NC}" +echo "==========================================" + +if [ $failed -eq 0 ]; then + exit 0 +else + exit 1 +fi diff --git a/.github/scripts/release/update-changelog.sh b/.github/scripts/release/update-changelog.sh new file mode 100755 index 000000000..6624f81dc --- /dev/null +++ b/.github/scripts/release/update-changelog.sh @@ -0,0 +1,125 @@ +#!/bin/bash +set -e + +# Script to update CHANGELOG.md during release process +# Usage: ./update-changelog.sh [reference-impl-hash] +# Example: ./update-changelog.sh 1.0.8 +# Example: ./update-changelog.sh 1.0.8 05e3c46c8c23130c9c064dc43d00ec78f7a75eab + +if [ -z "$1" ]; then + echo "Error: Version argument required" + echo "Usage: $0 [reference-impl-hash]" + exit 1 +fi + +VERSION="$1" +REFERENCE_IMPL_HASH="${2:-}" +CHANGELOG_FILE="${CHANGELOG_FILE:-CHANGELOG.md}" +RELEASE_DATE=$(date +%Y-%m-%d) + +echo "Updating CHANGELOG.md for version ${VERSION} (${RELEASE_DATE})" +if [ -n "$REFERENCE_IMPL_HASH" ]; then + echo " Reference implementation SDK sync: ${REFERENCE_IMPL_HASH:0:7}" +fi + +# Check if CHANGELOG.md exists +if [ ! -f "$CHANGELOG_FILE" ]; then + echo "Error: CHANGELOG.md not found" + exit 1 +fi + +# Check if there's an [Unreleased] section +if ! grep -q "## \[Unreleased\]" "$CHANGELOG_FILE"; then + echo "Error: No [Unreleased] section found in CHANGELOG.md" + exit 1 +fi + +# Create a temporary file +TEMP_FILE=$(mktemp) + +# Process the CHANGELOG +awk -v version="$VERSION" -v date="$RELEASE_DATE" -v REFERENCE_IMPL_HASH="$REFERENCE_IMPL_HASH" ' +BEGIN { + unreleased_found = 0 + content_found = 0 + links_section = 0 + first_version_link = "" + repo_url = "" + unreleased_link_handled = 0 +} + +# Track if we are in the links section at the bottom +/^\[/ { + links_section = 1 +} + +# Capture the repository URL from the first version link +links_section && repo_url == "" && /^\[[0-9]+\.[0-9]+\.[0-9]+(-(beta-)?java(-preview)?\.[0-9]+)?\]:/ { + match($0, /(https:\/\/github\.com\/[^\/]+\/[^\/]+)\//, arr) + if (arr[1] != "") { + repo_url = arr[1] + } +} + +# Replace [Unreleased] with the version and date +/^## \[Unreleased\]/ { + if (!unreleased_found) { + print "## [Unreleased]" + print "" + if (REFERENCE_IMPL_HASH != "") { + short_hash = substr(REFERENCE_IMPL_HASH, 1, 7) + print "> **Reference implementation sync:** [`github/copilot-sdk@" short_hash "`](https://github.com/github/copilot-sdk/commit/" REFERENCE_IMPL_HASH ")" + print "" + } + print "## [" version "] - " date + if (REFERENCE_IMPL_HASH != "") { + print "" + print "> **Reference implementation sync:** [`github/copilot-sdk@" short_hash "`](https://github.com/github/copilot-sdk/commit/" REFERENCE_IMPL_HASH ")" + } + unreleased_found = 1 + skip_old_reference_impl = 1 + next + } +} + +# Skip the old Reference implementation sync line and surrounding blank lines from the previous [Unreleased] section +skip_old_reference_impl && /^[[:space:]]*$/ { next } +skip_old_reference_impl && /^> \*\*Reference implementation sync:\*\*/ { next } +skip_old_reference_impl && !/^[[:space:]]*$/ && !/^> \*\*Reference implementation sync:\*\*/ { skip_old_reference_impl = 0 } + +# Update existing [Unreleased] link if present (must be checked before the first-version-link block) +links_section && /^\[Unreleased\]:/ { + # Get the previous version and repo URL from the existing link (handles both v and java/v prefix) + match($0, /(https:\/\/github\.com\/[^\/]+\/[^\/]+)\/compare\/(java\/)?v([0-9]+\.[0-9]+\.[0-9]+(-(beta-)?java(-preview)?\.[0-9]+)?)\.\.\.HEAD/, arr) + if (arr[1] != "" && arr[3] != "") { + print "[Unreleased]: " arr[1] "/compare/java/v" version "...HEAD" + print "[" version "]: " arr[1] "/compare/java/v" arr[3] "...java/v" version + unreleased_link_handled = 1 + next + } +} + +# Capture the first version link to get the previous version +# Only fires if the [Unreleased] link was not already handled above +links_section && first_version_link == "" && !unreleased_link_handled && /^\[[0-9]+\.[0-9]+\.[0-9]+(-(beta-)?java(-preview)?\.[0-9]+)?\]:/ { + match($0, /\[([0-9]+\.[0-9]+\.[0-9]+(-(beta-)?java(-preview)?\.[0-9]+)?)\]:/, arr) + if (arr[1] != "" && repo_url != "") { + first_version_link = arr[1] + # Insert Unreleased and new version links before first version link + print "[Unreleased]: " repo_url "/compare/java/v" version "...HEAD" + print "[" version "]: " repo_url "/compare/java/v" arr[1] "...java/v" version + } +} + +# Print all other lines unchanged +{ print } +' "$CHANGELOG_FILE" > "$TEMP_FILE" + +# Replace the original file +mv "$TEMP_FILE" "$CHANGELOG_FILE" + +echo "✓ CHANGELOG.md updated successfully" +echo " - Added version ${VERSION} with date ${RELEASE_DATE}" +echo " - Created new [Unreleased] section" +echo " - Updated version comparison links" + diff --git a/.github/workflows/java-publish-maven.yml b/.github/workflows/java-publish-maven.yml index 1c92e0109..ece7463fb 100644 --- a/.github/workflows/java-publish-maven.yml +++ b/.github/workflows/java-publish-maven.yml @@ -1,4 +1,4 @@ -name: Publish to Maven Central +name: "Java Publish to Maven Central" env: # Disable Husky Git hooks in CI to prevent local development hooks @@ -35,6 +35,10 @@ jobs: publish-maven: name: Publish Java SDK to Maven Central runs-on: ubuntu-latest + defaults: + run: + shell: bash + working-directory: ./java outputs: version: ${{ steps.versions.outputs.release_version }} steps: @@ -121,7 +125,7 @@ jobs: echo "Reference implementation SDK sync: ${REFERENCE_IMPL_SHORT} (${REFERENCE_IMPL_URL})" # Update CHANGELOG.md with release version and Reference implementation sync hash - ../.github/scripts/release/update-changelog.sh "${VERSION}" "${REFERENCE_IMPL_HASH}" + $GITHUB_WORKSPACE/.github/scripts/release/update-changelog.sh "${VERSION}" "${REFERENCE_IMPL_HASH}" # Update version in README.md (supports any version qualifier like -java.N, -java-preview.N, -beta-java.N) sed -i "s|[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-[a-z][a-z0-9-]*\.[0-9][0-9]*\)*|${VERSION}|g" README.md @@ -150,7 +154,7 @@ jobs: mvn -B release:prepare \ -DreleaseVersion=${{ steps.versions.outputs.release_version }} \ -DdevelopmentVersion=${{ steps.versions.outputs.dev_version }} \ - -DtagNameFormat=v@{project.version} \ + -DtagNameFormat=java/v@{project.version} \ -DpushChanges=true \ -Darguments="-DskipTests" env: @@ -185,6 +189,10 @@ jobs: needs: publish-maven if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest + defaults: + run: + shell: bash + working-directory: ./java steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -194,7 +202,7 @@ jobs: VERSION="${{ needs.publish-maven.outputs.version }}" GROUP_ID="com.github" ARTIFACT_ID="copilot-sdk-java" - CURRENT_TAG="v${VERSION}" + CURRENT_TAG="java/v${VERSION}" if gh release view "${CURRENT_TAG}" >/dev/null 2>&1; then echo "Release ${CURRENT_TAG} already exists. Skipping creation." @@ -203,11 +211,10 @@ jobs: # Generate release notes from template export VERSION GROUP_ID ARTIFACT_ID - RELEASE_NOTES=$(envsubst < .github/workflows/notes.template) + RELEASE_NOTES=$(envsubst < $GITHUB_WORKSPACE/.github/workflows/java.notes.template) # Get the previous tag for generating notes - PREV_TAG=$(git tag --list 'v*' --sort=-version:refname \ - | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+(-java(-preview)?\.[0-9]+)?$' \ + PREV_TAG=$(git tag --list 'java/v*' --sort=-version:refname \ | grep -Fxv "${CURRENT_TAG}" \ | head -n 1) @@ -229,28 +236,3 @@ jobs: gh release create "${GH_ARGS[@]}" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Move 'latest' tag to new release - run: | - VERSION="${{ needs.publish-maven.outputs.version }}" - git tag -f latest "v${VERSION}" - git push origin latest --force - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - deploy-site: - name: Deploy Documentation - needs: [publish-maven, github-release] - runs-on: ubuntu-latest - permissions: - actions: write - contents: read - steps: - - name: Trigger site deployment - run: | - gh workflow run deploy-site.yml \ - --repo ${{ github.repository }} \ - -f version="${{ needs.publish-maven.outputs.version }}" \ - -f publish_as_latest=true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/java-publish-snapshot.yml b/.github/workflows/java-publish-snapshot.yml index ddc04c40b..7bc231c73 100644 --- a/.github/workflows/java-publish-snapshot.yml +++ b/.github/workflows/java-publish-snapshot.yml @@ -1,4 +1,4 @@ -name: Publish Snapshot to Maven Central +name: Java Publish Snapshot to Maven Central env: HUSKY: 0 @@ -19,6 +19,10 @@ jobs: publish-snapshot: name: Publish SNAPSHOT to Maven Central runs-on: ubuntu-latest + defaults: + run: + shell: bash + working-directory: ./java steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: diff --git a/.github/workflows/java-smoke-test.yml b/.github/workflows/java-smoke-test.yml new file mode 100644 index 000000000..cffef0a35 --- /dev/null +++ b/.github/workflows/java-smoke-test.yml @@ -0,0 +1,161 @@ +name: "Java smoke test" + +on: + workflow_dispatch: + workflow_call: + secrets: + COPILOT_GITHUB_TOKEN: + required: true + +permissions: + contents: read + +jobs: + smoke-test-jdk17: + name: Build SDK and run smoke test (JDK 17) + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + defaults: + run: + shell: bash + working-directory: ./java + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Set up JDK 17 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + java-version: "17" + distribution: "microsoft" + cache: "maven" + + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v6 + with: + node-version: 22 + + - name: Read pinned @github/copilot version from pom.xml + id: cli-version + run: | + PROP="readonly-copilot-sdk-ref-impl-version-from-lastmerge-file-updated-by-reference-impl-sync" + VERSION=$(sed -n "s|.*<${PROP}>\(.*\).*|\1|p" pom.xml | head -n 1 | tr -d '[:space:]') + if [[ -z "$VERSION" || "$VERSION" == "PRIMER_TO_REPLACE" ]]; then + echo "::error::Could not read pinned @github/copilot version from pom.xml property <${PROP}>" >&2 + exit 1 + fi + echo "Pinned @github/copilot version: $VERSION" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - name: Install Copilot CLI globally (pinned to pom.xml version) + run: npm install -g "@github/copilot@${{ steps.cli-version.outputs.version }}" + + - name: Verify CLI works + run: copilot --version + + - name: Build SDK and install to local repo + run: mvn -DskipTests -Pskip-test-harness clean install + + - name: Create and run smoke test via Copilot CLI + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + run: | + cat > /tmp/smoke-test-prompt.txt << 'PROMPT_EOF' + You are running inside the copilot-sdk monorepo, in the java/ subdirectory. + The SDK has already been built and installed into the local Maven repository. + JDK 17 and Maven are already installed and on PATH. + + Execute the prompt at `src/test/prompts/PROMPT-smoke-test.md` with the following critical overrides: + + **Critical override — disable SNAPSHOT updates (but allow downloads):** The goal of this workflow is to validate the SDK SNAPSHOT that was just built and installed locally, not any newer SNAPSHOT that might exist in a remote repository. To ensure Maven does not download a newer timestamped SNAPSHOT of the SDK while still allowing it to download any missing plugins or dependencies, you must run the smoke-test Maven build without `-U` and with `--no-snapshot-updates`, so that it uses the locally installed SDK artifact. Use `mvn --no-snapshot-updates clean package` instead of `mvn -U clean package` or `mvn -o clean package`. + + **Critical override — do NOT run the jar:** Stop after the `mvn --no-snapshot-updates clean package` build succeeds. Do NOT execute Step 4 (java -jar) or Step 5 (verify exit code) from the prompt. The workflow will run the jar in a separate deterministic step to guarantee the exit code propagates correctly. + + Follow steps 1-3 only: create the `smoke-test/` directory, create `pom.xml` and the Java source file exactly as specified, and build with `mvn --no-snapshot-updates clean package` (no SNAPSHOT updates and without `-U`). + + If any step fails, exit with a non-zero exit code. Do not silently fix errors. + PROMPT_EOF + + copilot --yolo --prompt "$(cat /tmp/smoke-test-prompt.txt)" + + - name: Run smoke test jar + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + run: | + cd smoke-test + java -jar ./target/copilot-sdk-smoketest-1.0-SNAPSHOT.jar + echo "Smoke test passed (exit code 0)" + + smoke-test-java25: + name: Build SDK and run smoke test (JDK 25) + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + defaults: + run: + shell: bash + working-directory: ./java + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Set up JDK 25 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + java-version: "25" + distribution: "microsoft" + cache: "maven" + + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v6 + with: + node-version: 22 + + - name: Read pinned @github/copilot version from pom.xml + id: cli-version + run: | + PROP="readonly-copilot-sdk-ref-impl-version-from-lastmerge-file-updated-by-reference-impl-sync" + VERSION=$(sed -n "s|.*<${PROP}>\(.*\).*|\1|p" pom.xml | head -n 1 | tr -d '[:space:]') + if [[ -z "$VERSION" || "$VERSION" == "PRIMER_TO_REPLACE" ]]; then + echo "::error::Could not read pinned @github/copilot version from pom.xml property <${PROP}>" >&2 + exit 1 + fi + echo "Pinned @github/copilot version: $VERSION" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - name: Install Copilot CLI globally (pinned to pom.xml version) + run: npm install -g "@github/copilot@${{ steps.cli-version.outputs.version }}" + + - name: Verify CLI works + run: copilot --version + + - name: Build SDK and install to local repo + run: mvn -DskipTests -Pskip-test-harness clean install + + - name: Create and run smoke test via Copilot CLI + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + run: | + cat > /tmp/smoke-test-prompt.txt << 'PROMPT_EOF' + You are running inside the copilot-sdk monorepo, in the java/ subdirectory. + The SDK has already been built and installed into the local Maven repository. + JDK 25 and Maven are already installed and on PATH. + + Execute the prompt at `src/test/prompts/PROMPT-smoke-test.md` with the following critical overrides: + + **Critical override — disable SNAPSHOT updates (but allow downloads):** The goal of this workflow is to validate the SDK SNAPSHOT that was just built and installed locally, not any newer SNAPSHOT that might exist in a remote repository. To ensure Maven does not download a newer timestamped SNAPSHOT of the SDK while still allowing it to download any missing plugins or dependencies, you must run the smoke-test Maven build without `-U` and with `--no-snapshot-updates`, so that it uses the locally installed SDK artifact. Use `mvn --no-snapshot-updates clean package` instead of `mvn -U clean package` or `mvn -o clean package`. + + **Critical override — do NOT run the jar:** Stop after the `mvn --no-snapshot-updates clean package` build succeeds. Do NOT execute Step 4 (java -jar) or Step 5 (verify exit code) from the prompt. The workflow will run the jar in a separate deterministic step to guarantee the exit code propagates correctly. + + **Critical override — enable Virtual Threads for JDK 25:** After creating the Java source file from the README "Quick Start" section but BEFORE building, you must modify the source file to enable virtual thread support. The Quick Start code contains inline comments that start with `// JDK 25+:` — these are instructions. Find every such comment and follow what it says (comment out lines it says to comment out, uncomment lines it says to uncomment). Add any imports required by the newly uncommented code (e.g. `java.util.concurrent.Executors`). + Also set `maven.compiler.source` and `maven.compiler.target` to `25` in the `pom.xml`. + + Follow steps 1-3 only: create the `smoke-test/` directory, create `pom.xml` and the Java source file exactly as specified, apply the JDK 25 virtual thread modifications described above, and build with `mvn --no-snapshot-updates clean package` (no SNAPSHOT updates and without `-U`). + + If any step fails, exit with a non-zero exit code. Do not silently fix errors. + PROMPT_EOF + + copilot --yolo --prompt "$(cat /tmp/smoke-test-prompt.txt)" + + - name: Run smoke test jar + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + run: | + cd smoke-test + java -jar ./target/copilot-sdk-smoketest-1.0-SNAPSHOT.jar + echo "Smoke test passed (exit code 0)" diff --git a/.github/workflows/java.notes.template b/.github/workflows/java.notes.template new file mode 100644 index 000000000..bec50a91b --- /dev/null +++ b/.github/workflows/java.notes.template @@ -0,0 +1,26 @@ +# Installation + +ℹ️ **Public Preview:** This is the official Java SDK for GitHub Copilot. This repository treats the official .NET and Node.js SDKs for GitHub Copilot as reference implementations. These SDKs are all officially supported as GitHub open source projects. The Java implementation follows the backward compatibility guarantees offered by the reference implementations. + +⚠️ **Artifact versioning plan:** Releases of this implementation track releases of the reference implementation. For each release of the reference implementation, there may follow a corresponding release of this implementation with the same number as the reference implementation. Release identifiers of the reference implementation are in the form `vMaj.Min.Micro`. For example v0.1.32. The corresponding maven version for the release will be `Maj.Min.Micro-java.N`, where `Maj`, `Min` and `Micro` are the corresponding numbers for the reference implementation release, and `N` is a monotonically increasing sequence number starting with 0 for each release. See the corresponding architectural decision record for more information in the `docs/adr` directory of the source code. + +📦 [View on Maven Central](https://central.sonatype.com/artifact/${GROUP_ID}/${ARTIFACT_ID}/${VERSION}) + +## Maven +```xml + + ${GROUP_ID} + ${ARTIFACT_ID} + ${VERSION} + +``` + +## Gradle (Kotlin DSL) +```kotlin +implementation("${GROUP_ID}:${ARTIFACT_ID}:${VERSION}") +``` + +## Gradle (Groovy DSL) +```groovy +implementation '${GROUP_ID}:${ARTIFACT_ID}:${VERSION}' +``` diff --git a/java/src/test/java/com/github/copilot/sdk/TestUtil.java b/java/src/test/java/com/github/copilot/sdk/TestUtil.java index d9462af87..af4474590 100644 --- a/java/src/test/java/com/github/copilot/sdk/TestUtil.java +++ b/java/src/test/java/com/github/copilot/sdk/TestUtil.java @@ -36,9 +36,9 @@ public static String tempPath(String filename) { *

* Resolution order: *

    - *
  1. Search the system PATH using {@code where.exe} (Windows) or {@code which} - * (Linux/macOS).
  2. - *
  3. Fall back to the {@code COPILOT_CLI_PATH} environment variable.
  4. + *
  5. Use the {@code COPILOT_CLI_PATH} environment variable when set.
  6. + *
  7. Otherwise search the system PATH using {@code where.exe} (Windows) or + * {@code which} (Linux/macOS).
  8. *
  9. Walk parent directories looking for * {@code nodejs/node_modules/@github/copilot/index.js}.
  10. *
@@ -55,16 +55,16 @@ public static String tempPath(String filename) { * {@code null} if none was found */ static String findCliPath() { - String copilotInPath = findCopilotInPath(); - if (copilotInPath != null) { - return copilotInPath; - } - String envPath = System.getenv("COPILOT_CLI_PATH"); if (envPath != null && !envPath.isEmpty()) { return envPath; } + String copilotInPath = findCopilotInPath(); + if (copilotInPath != null) { + return copilotInPath; + } + Path current = Paths.get(System.getProperty("user.dir")); while (current != null) { Path cliPath = current.resolve("nodejs/node_modules/@github/copilot/index.js");