diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index b71677f..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -github: MarketDataApp diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 5418c2f..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,31 +0,0 @@ -# Please see the documentation for all configuration options: -# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates - -version: 2 -updates: - - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - # Bundle all action bumps into a single PR. - groups: - github-actions: - patterns: - - "*" - labels: - - "dependencies" - - - package-ecosystem: "gradle" - directory: "/" - schedule: - interval: "weekly" - # One PR for minor/patch bumps; majors stay separate PRs since they can - # break consumers and need individual review. - groups: - gradle-minor-patch: - update-types: - - "minor" - - "patch" - labels: - - "dependencies" diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml deleted file mode 100644 index 0631c23..0000000 --- a/.github/workflows/dependabot-auto-merge.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: dependabot-auto-merge -on: pull_request_target - -permissions: - pull-requests: write - contents: write - -jobs: - dependabot: - runs-on: ubuntu-latest - timeout-minutes: 5 - if: ${{ github.actor == 'dependabot[bot]' }} - steps: - - - name: Dependabot metadata - id: metadata - uses: dependabot/fetch-metadata@v3.1.0 - with: - github-token: "${{ secrets.GITHUB_TOKEN }}" - - - name: Auto-merge Dependabot PRs for semver-minor updates - if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor'}} - run: gh pr merge --auto --merge "$PR_URL" - env: - PR_URL: ${{github.event.pull_request.html_url}} - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} - - - name: Auto-merge Dependabot PRs for semver-patch updates - if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch'}} - run: gh pr merge --auto --merge "$PR_URL" - env: - PR_URL: ${{github.event.pull_request.html_url}} - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 2fa86e2..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,124 +0,0 @@ -name: Main - -# Runs only on push to main (i.e. when a PR is merged or someone pushes -# directly). This is where the full forward-compat JDK matrix runs and -# where we publish the canonical coverage snapshot that Codecov uses as -# the base for PR diffs. -on: - push: - branches: ['main'] - -permissions: - contents: read - -# Don't cancel main runs against each other — we want every merge to -# produce a coverage baseline. Sequential is fine; main pushes are rare. -concurrency: - group: main - cancel-in-progress: false - -jobs: - verify: - name: Verify (JDK ${{ matrix.java }}) - runs-on: ubuntu-latest - strategy: - # Don't cancel siblings: if JDK 21 fails, we still want to know - # whether 17 and 25 pass. - fail-fast: false - matrix: - # ADR-002: tests run on JDK 17, 21, 25 to catch forward-compat - # regressions. Compilation is always pinned to --release 17. - java: ['17', '21', '25'] - - steps: - - name: Checkout - uses: actions/checkout@v7 - - # Install both the matrix JDK (test execution) and JDK 17 (compile + - # Gradle daemon runtime). Order matters: setup-java sets JAVA_HOME - # to the LAST entry, and Gradle 8.12 only supports JDKs up to 23 as - # its own runtime — so JDK 17 must be last for matrix.java = 25. - # setup-java still exports JAVA_HOME__ for both, and - # setup-gradle registers them as toolchain candidates. - - name: Set up JDKs (test=${{ matrix.java }}, compile/daemon=17) - uses: actions/setup-java@v5 - with: - distribution: temurin - java-version: | - ${{ matrix.java }} - 17 - - - name: Set up Gradle - uses: gradle/actions/setup-gradle@v4 - - - name: Build, test, lint, coverage - run: ./gradlew build -PtestJdk=${{ matrix.java }} --stacktrace - - - name: Upload test reports on failure - if: failure() - uses: actions/upload-artifact@v4 - with: - name: test-reports-jdk${{ matrix.java }} - path: | - build/reports/tests/ - build/test-results/ - retention-days: 14 - - # The JDK 17 entry of the matrix is the canonical run for coverage: - # its JaCoCo XML is uploaded to Codecov and becomes the base that - # subsequent PR runs compare against (see codecov.yml). - - name: Upload coverage to Codecov (JDK 17 only) - if: success() && matrix.java == '17' - uses: codecov/codecov-action@v7 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: build/reports/jacoco/test/jacocoTestReport.xml - fail_ci_if_error: true - - # SDK requirements §13: integration tests run mandatorily on every push - # to main (release-pipeline gate) on the full forward-compat matrix. - # On PRs they're triggered on demand instead — see - # pr-integration-on-demand.yml. - integration-tests: - name: Integration tests (live API, JDK ${{ matrix.java }}) - runs-on: ubuntu-latest - needs: verify - strategy: - fail-fast: false - matrix: - java: ['17', '21', '25'] - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Set up JDKs (test=${{ matrix.java }}, compile/daemon=17) - uses: actions/setup-java@v5 - with: - distribution: temurin - java-version: | - ${{ matrix.java }} - 17 - - - name: Set up Gradle - uses: gradle/actions/setup-gradle@v4 - - - name: Run integration tests against live API - env: - MARKETDATA_TOKEN: ${{ secrets.MARKETDATA_TOKEN }} - MARKETDATA_RUN_INTEGRATION_TESTS: 'true' - run: | - if [ -z "$MARKETDATA_TOKEN" ]; then - echo "::error::MARKETDATA_TOKEN secret is required on main; integration tests must run on every merge." - exit 1 - fi - ./gradlew integrationTest -PtestJdk=${{ matrix.java }} --stacktrace - - - name: Upload integration-test reports on failure - if: failure() - uses: actions/upload-artifact@v4 - with: - name: integration-test-reports-jdk${{ matrix.java }} - path: | - build/reports/tests/integrationTest/ - build/test-results/integrationTest/ - retention-days: 14 diff --git a/.github/workflows/pr-integration-on-demand.yml b/.github/workflows/pr-integration-on-demand.yml deleted file mode 100644 index 35bee1a..0000000 --- a/.github/workflows/pr-integration-on-demand.yml +++ /dev/null @@ -1,226 +0,0 @@ -name: Integration tests on demand - -# Manually triggered by commenting on an open PR with a slash-command on -# the FIRST line of the comment body (everything after the first line is -# ignored): -# /integrationtest → JDK 17 only -# /integrationtestfull → full matrix {17, 21, 25} -# -# The slash + first-line constraint prevents accidental triggers from -# review comments that mention the workflow by name, quoted replies -# (`> /integrationtest`), pasted documentation, or stack traces. The -# Guard job below filters on `startsWith(... '/integrationtest')` and -# then a strict bash `case` validates the exact command — anything that -# slips through is rejected before any live-API request is made. -# -# Integration tests hit the live Market Data API, so we don't run them -# automatically on every PR open/sync (saves API quota + CI minutes). -# They ARE required for merge — branch protection on `main` should list -# "Integration tests pass" as a required status check, which is the -# aggregator job below. PRs cannot merge until a reviewer comments one -# of the two slash-commands AND the resulting run is green. -# -# Important security note: workflows triggered by `issue_comment` always -# run from the *default branch's* version of the workflow file, not from -# the PR. Adding/changing this file on a feature branch has no effect -# until it lands on main. -on: - issue_comment: - types: [created] - -permissions: - contents: read - pull-requests: write # to react and post the result comment - -# Multiple trigger comments on the same PR cancel earlier runs. -concurrency: - group: pr-integration-on-demand-${{ github.event.issue.number }} - cancel-in-progress: true - -jobs: - guard: - name: Guard - runs-on: ubuntu-latest - # Only fire on PR comments whose body starts with `/integrationtest`. - # `startsWith` rejects comments that merely mention the command in - # passing (quoted replies start with `>`, prose with anything else, - # so they don't match). The strict `case` in the matrix step below - # rejects anything that slips through (e.g. `/integrationtest-foo`) - # before any live-API request fires. - if: | - github.event.issue.pull_request != null && - startsWith(github.event.comment.body, '/integrationtest') - outputs: - head_sha: ${{ steps.pr.outputs.head_sha }} - jdks: ${{ steps.matrix.outputs.jdks }} - mode: ${{ steps.matrix.outputs.mode }} - steps: - - name: Verify commenter has write permission - uses: actions/github-script@v9 - with: - script: | - const { data: perm } = await github.rest.repos.getCollaboratorPermissionLevel({ - owner: context.repo.owner, - repo: context.repo.repo, - username: context.payload.comment.user.login, - }); - const allowed = ['write', 'maintain', 'admin'].includes(perm.permission); - if (!allowed) { - core.setFailed( - `@${context.payload.comment.user.login} (${perm.permission}) ` + - `cannot trigger integration tests; write access required.` - ); - } - - - name: React 👀 to the trigger comment - uses: actions/github-script@v9 - with: - script: | - await github.rest.reactions.createForIssueComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: context.payload.comment.id, - content: 'eyes', - }); - - - name: Resolve PR head SHA - id: pr - uses: actions/github-script@v9 - with: - script: | - const { data: pr } = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.payload.issue.number, - }); - if (pr.state !== 'open') { - core.setFailed(`PR #${pr.number} is ${pr.state}; refusing to run.`); - return; - } - core.setOutput('head_sha', pr.head.sha); - - - name: Decide JDK matrix from comment body - id: matrix - env: - BODY: ${{ github.event.comment.body }} - run: | - # The if: filter above only guarantees the body starts with - # '/integrationtest'. We still need to disambiguate single vs - # full and reject anything that just shares the prefix - # (e.g. '/integrationtest-foo' or '/integrationtestlong'). - # Match on the first line only — trailing context in the - # comment body is ignored. - first_line=$(printf '%s' "$BODY" | head -n 1 | tr -d '[:space:]') - case "$first_line" in - /integrationtest) - echo 'jdks=["17"]' >> "$GITHUB_OUTPUT" - echo 'mode=single' >> "$GITHUB_OUTPUT" - echo "Trigger: /integrationtest → JDK 17" - ;; - /integrationtestfull) - echo 'jdks=["17","21","25"]' >> "$GITHUB_OUTPUT" - echo 'mode=full' >> "$GITHUB_OUTPUT" - echo "Trigger: /integrationtestfull → matrix {17, 21, 25}" - ;; - *) - echo "::error::Unrecognized command on first line: '$first_line' (expected '/integrationtest' or '/integrationtestfull')" - exit 1 - ;; - esac - - integration-tests: - name: Integration tests (JDK ${{ matrix.java }}) - needs: guard - runs-on: ubuntu-latest - strategy: - # Don't cancel siblings: if JDK 21 fails, we still want 17 and 25 - # results to surface. - fail-fast: false - matrix: - java: ${{ fromJSON(needs.guard.outputs.jdks) }} - - steps: - # Check out exactly the PR's HEAD commit so we test the proposed - # change, not the merge ref. - - name: Checkout PR head - uses: actions/checkout@v7 - with: - ref: ${{ needs.guard.outputs.head_sha }} - - # Order matters: JDK 17 must be last so JAVA_HOME=17 (Gradle 8.12 - # only supports JDKs up to 23 as its daemon runtime). The matrix - # JDK is still installed and registered as a toolchain target. - - name: Set up JDKs (test=${{ matrix.java }}, compile/daemon=17) - uses: actions/setup-java@v5 - with: - distribution: temurin - java-version: | - ${{ matrix.java }} - 17 - - - name: Set up Gradle - uses: gradle/actions/setup-gradle@v4 - - - name: Run integration tests against live API - env: - MARKETDATA_TOKEN: ${{ secrets.MARKETDATA_TOKEN }} - MARKETDATA_RUN_INTEGRATION_TESTS: 'true' - run: | - if [ -z "$MARKETDATA_TOKEN" ]; then - echo "::error::MARKETDATA_TOKEN secret missing — cannot run integration tests." - exit 1 - fi - ./gradlew integrationTest -PtestJdk=${{ matrix.java }} --stacktrace - - - name: Upload integration-test reports on failure - if: failure() - uses: actions/upload-artifact@v4 - with: - name: integration-test-reports-jdk${{ matrix.java }} - path: | - build/reports/tests/integrationTest/ - build/test-results/integrationTest/ - retention-days: 14 - - # Aggregator job. Branch protection on `main` should require this - # check name ("Integration tests pass") so a single required check - # covers both `integrationtest` (matrix=[17]) and `integrationtestfull` - # (matrix=[17,21,25]) modes uniformly. Without this, branch protection - # would have to list the per-matrix-entry check names which only exist - # in the `full` mode. - required-check: - name: Integration tests pass - needs: [guard, integration-tests] - if: always() && needs.guard.result == 'success' - runs-on: ubuntu-latest - steps: - - name: Aggregate matrix outcome - env: - MATRIX_RESULT: ${{ needs.integration-tests.result }} - MODE: ${{ needs.guard.outputs.mode }} - run: | - echo "Mode: $MODE" - echo "Matrix outcome: $MATRIX_RESULT" - if [[ "$MATRIX_RESULT" != "success" ]]; then - echo "::error::One or more integration-test JDK entries failed." - exit 1 - fi - echo "All integration tests passed." - - - name: Comment outcome on the PR - if: always() - uses: actions/github-script@v9 - with: - script: | - const ok = '${{ needs.integration-tests.result }}' === 'success'; - const mode = '${{ needs.guard.outputs.mode }}'; - const emoji = ok ? '✅' : '❌'; - const status = ok ? 'passed' : 'failed'; - const matrix = mode === 'full' ? '`{17, 21, 25}`' : '`17`'; - const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.payload.issue.number, - body: `${emoji} On-demand integration tests on JDK ${matrix} ${status}. [View run](${runUrl}).`, - }); diff --git a/.github/workflows/pr-matrix-on-demand.yml b/.github/workflows/pr-matrix-on-demand.yml deleted file mode 100644 index b1f590f..0000000 --- a/.github/workflows/pr-matrix-on-demand.yml +++ /dev/null @@ -1,161 +0,0 @@ -name: PR matrix (on demand) - -# Manually triggered by commenting one of these slash commands on an -# open PR: -# /run-all-jdks -# /jdk-matrix -# /test-all -# Runs the forward-compat matrix (JDK 21, 25) — JDK 17 already runs -# automatically on every PR open/sync via pull-request.yml. -# -# Important security note: workflows triggered by `issue_comment` always -# run from the *default branch's* version of the workflow file, not from -# the PR. So adding/changing this file on a feature branch has no effect -# until it lands on main. -on: - issue_comment: - types: [created] - -permissions: - contents: read - pull-requests: write # to react to the trigger comment - -# Multiple "run all versions" comments on the same PR cancel earlier runs. -concurrency: - group: pr-on-demand-${{ github.event.issue.number }} - cancel-in-progress: true - -jobs: - guard: - name: Guard - runs-on: ubuntu-latest - # Only fire on PR comments (not generic issue comments) that contain - # one of the three accepted slash commands. `contains` is substring - # match — false positives are possible but unlikely in practice given - # the leading slash and hyphen-rich shape of these tokens. - if: | - github.event.issue.pull_request != null && ( - contains(github.event.comment.body, '/run-all-jdks') || - contains(github.event.comment.body, '/jdk-matrix') || - contains(github.event.comment.body, '/test-all') - ) - outputs: - head_sha: ${{ steps.pr.outputs.head_sha }} - steps: - # Reject comments from anyone without write access. Otherwise an - # external user commenting on a fork PR could burn our CI minutes - # and potentially exfiltrate secrets via a malicious build. - - name: Verify commenter has write permission - uses: actions/github-script@v9 - with: - script: | - const { data: perm } = await github.rest.repos.getCollaboratorPermissionLevel({ - owner: context.repo.owner, - repo: context.repo.repo, - username: context.payload.comment.user.login, - }); - const allowed = ['write', 'maintain', 'admin'].includes(perm.permission); - if (!allowed) { - core.setFailed( - `@${context.payload.comment.user.login} (${perm.permission}) ` + - `cannot trigger CI; write access required.` - ); - } - - # Visible feedback to the commenter that we picked up the trigger. - - name: React 👀 to the trigger comment - uses: actions/github-script@v9 - with: - script: | - await github.rest.reactions.createForIssueComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: context.payload.comment.id, - content: 'eyes', - }); - - # The issue_comment event payload doesn't include the PR's head SHA, - # so look it up via the pulls API. We also confirm the PR is open; - # firing on closed PRs is almost always a mistake. - - name: Resolve PR head SHA - id: pr - uses: actions/github-script@v9 - with: - script: | - const { data: pr } = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.payload.issue.number, - }); - if (pr.state !== 'open') { - core.setFailed(`PR #${pr.number} is ${pr.state}; refusing to run.`); - return; - } - core.setOutput('head_sha', pr.head.sha); - - verify: - name: Verify (JDK ${{ matrix.java }}) - needs: guard - runs-on: ubuntu-latest - strategy: - # If JDK 21 fails, we still want to know whether 25 passes. - fail-fast: false - matrix: - java: ['21', '25'] - - steps: - # Check out exactly the PR's HEAD commit, not the merge ref. - - name: Checkout PR head - uses: actions/checkout@v7 - with: - ref: ${{ needs.guard.outputs.head_sha }} - - # Order matters: JDK 17 must be last so JAVA_HOME=17 (Gradle 8.12 - # doesn't support JDK 24+ as its daemon runtime). The matrix JDK - # is still installed and registered as a toolchain target. - - name: Set up JDKs (test=${{ matrix.java }}, compile/daemon=17) - uses: actions/setup-java@v5 - with: - distribution: temurin - java-version: | - ${{ matrix.java }} - 17 - - - name: Set up Gradle - uses: gradle/actions/setup-gradle@v4 - - - name: Test on JDK ${{ matrix.java }} - run: ./gradlew test -PtestJdk=${{ matrix.java }} --stacktrace - - - name: Upload test reports on failure - if: failure() - uses: actions/upload-artifact@v4 - with: - name: test-reports-jdk${{ matrix.java }} - path: | - build/reports/tests/ - build/test-results/ - retention-days: 14 - - # Post a single comment summarizing the on-demand matrix result so it's - # visible on the PR without diving into the Actions tab. - report: - name: Report - needs: verify - if: always() && needs.guard.result == 'success' - runs-on: ubuntu-latest - steps: - - name: Comment outcome - uses: actions/github-script@v9 - with: - script: | - const ok = '${{ needs.verify.result }}' === 'success'; - const emoji = ok ? '✅' : '❌'; - const status = ok ? 'passed' : 'failed'; - const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.payload.issue.number, - body: `${emoji} On-demand JDK matrix \`{21, 25}\` ${status}. [View run](${runUrl}).`, - }); diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index 3a8c65d..0000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,101 +0,0 @@ -name: Publish to Maven Central - -on: - # Manual: publish any version on demand. - workflow_dispatch: - inputs: - version: - description: "Version to publish (e.g. 1.0.0, without the 'v')" - required: true - type: string - ref: - description: "Commit SHA to build & publish from (defaults to the launch commit)" - required: false - default: '' - type: string - release: - description: "Publish and release. Off = upload to the Portal and stop at VALIDATED for manual review." - required: true - type: boolean - default: false - # Chained: invoked by tag-and-release.yml after a release is cut. - workflow_call: - inputs: - version: - description: "Version to publish (e.g. 1.0.0, without the 'v')" - required: true - type: string - ref: - description: "Commit SHA to build & publish from (defaults to the launch commit)" - required: false - default: '' - type: string - release: - description: "Publish and release. Off = upload to the Portal and stop at VALIDATED." - required: false - default: false - type: boolean - -jobs: - guard: - name: Require green main.yml for this commit - runs-on: ubuntu-latest - permissions: - actions: read - steps: - - name: Check latest main.yml conclusion - env: - GH_TOKEN: ${{ github.token }} - REPO: ${{ github.repository }} - # Build/guard against the explicit commit when given (chained path); - # otherwise the commit the workflow was launched from. - SHA: ${{ inputs.ref || github.sha }} - run: | - conclusion=$(gh api \ - "repos/${REPO}/actions/workflows/main.yml/runs?head_sha=${SHA}&status=completed" \ - --jq '.workflow_runs[0].conclusion // "missing"') - echo "main.yml conclusion for ${SHA}: $conclusion" - if [ "$conclusion" != "success" ]; then - echo "::error::main.yml is not green for ${SHA} (got: $conclusion). Run publish from a commit on main whose CI passed." - exit 1 - fi - - publish: - name: Publish to Maven Central - needs: guard - runs-on: ubuntu-latest - environment: - name: maven-central - url: https://central.sonatype.com/artifact/app.marketdata/marketdata-sdk-java - env: - VERSION: ${{ inputs.version }} - ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} - ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} - ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }} - ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }} - steps: - - name: Checkout - uses: actions/checkout@v7 - with: - # Build from the explicit commit when chained; else the launch commit. - ref: ${{ inputs.ref || github.sha }} - - - name: Set up JDK 17 - uses: actions/setup-java@v5 - with: - distribution: temurin - java-version: "17" - - - name: Set up Gradle - uses: gradle/actions/setup-gradle@v4 - - - name: Build and test - run: ./gradlew clean build -PsdkVersion="$VERSION" - - - name: Upload to Portal (validate only) - if: ${{ !inputs.release }} - run: ./gradlew publishToMavenCentral -PsdkVersion="$VERSION" - - - name: Publish and release - if: ${{ inputs.release }} - run: ./gradlew publishAndReleaseToMavenCentral -PsdkVersion="$VERSION" diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml deleted file mode 100644 index d84a13d..0000000 --- a/.github/workflows/pull-request.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: Pull Request - -# Triggers only on pull request lifecycle events: -# - opened (PR creation) -# - synchronize (push to the PR branch while the PR is open) -# - reopened -# These are the default `pull_request` activity types — listed explicitly -# here for clarity. Pre-PR pushes don't run CI by design (saves minutes -# during early WIP commits). -on: - pull_request: - types: [opened, synchronize, reopened] - branches: ['**'] - -permissions: - contents: read - -# Cancel an in-progress run when a new commit lands on the same PR. -concurrency: - group: pr-${{ github.event.pull_request.number }} - cancel-in-progress: true - -jobs: - verify: - name: Verify (JDK 17) - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v7 - - # PRs only run on JDK 17 (the minimum target). Forward-compat - # regressions on JDK 21/25 are caught post-merge by main.yml. - - name: Set up JDK 17 - uses: actions/setup-java@v5 - with: - distribution: temurin - java-version: '17' - - # Validates the wrapper jar hash and caches Gradle home + wrapper - # dists between runs. - - name: Set up Gradle - uses: gradle/actions/setup-gradle@v4 - - - name: Build, test, lint, coverage - run: ./gradlew build --stacktrace - - - name: Upload test reports on failure - if: failure() - uses: actions/upload-artifact@v4 - with: - name: test-reports - path: | - build/reports/tests/ - build/test-results/ - retention-days: 14 - - # Coverage ratchet lives in Codecov: codecov.yml at the repo root - # configures `threshold: 5%` so a PR fails the Codecov status check - # if line coverage drops more than 5 pp vs the base branch. - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v7 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: build/reports/jacoco/test/jacocoTestReport.xml - fail_ci_if_error: true - - # NOTE: integration tests are NOT run automatically on PR open/sync. - # They are required for merge but only fire when a reviewer comments - # `integrationtest` (JDK 17) or `integrationtestfull` (matrix 17/21/25) - # — see .github/workflows/pr-integration-on-demand.yml. Branch-protection - # rules should require the "Integration tests pass" check name produced - # by that workflow before allowing merge to main. diff --git a/.github/workflows/tag-and-release.yml b/.github/workflows/tag-and-release.yml deleted file mode 100644 index c5f5d90..0000000 --- a/.github/workflows/tag-and-release.yml +++ /dev/null @@ -1,202 +0,0 @@ -name: Tag and Release - -# Cuts the git tag and GitHub Release for a version, then (by default) chains -# straight into the Maven Central publish. Each stage gates the next: the JDK -# matrix must pass before the tag is cut, and the publish step independently -# refuses to run unless main.yml is green for the commit. Set -# publish_to_central=false to stop after the GitHub Release and publish later. - -on: - workflow_dispatch: - inputs: - version: - description: "Release version without v prefix (example: 1.0.0)" - required: true - type: string - ref: - description: "Git ref to release (branch, tag, or commit SHA)" - required: false - default: "main" - type: string - prerelease: - description: "Mark GitHub Release as prerelease" - required: false - default: false - type: boolean - publish_to_central: - description: "Publish to Maven Central automatically after the release is cut" - required: false - default: true - type: boolean - confirm: - description: "Type RELEASE to confirm publish" - required: true - type: string - -permissions: - contents: write - actions: read - -concurrency: - group: release-${{ github.event.inputs.version }} - cancel-in-progress: false - -jobs: - gate: - name: Gate / JDK ${{ matrix.java }} - runs-on: ubuntu-latest - timeout-minutes: 20 - strategy: - fail-fast: false - matrix: - # ADR-002: tests run on JDK 17, 21, 25 to catch forward-compat - # regressions. Compilation is always pinned to --release 17. - java: ['17', '21', '25'] - - steps: - - name: Validate release request - if: matrix.java == '17' - shell: bash - env: - CONFIRM: ${{ inputs.confirm }} - VERSION: ${{ inputs.version }} - run: | - test "$CONFIRM" = "RELEASE" || { - echo "::error::confirm input must be exactly RELEASE" - exit 1 - } - [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]] || { - echo "::error::version must be semver-like (for example: 1.0.0)" - exit 1 - } - - - name: Checkout code - uses: actions/checkout@v7 - with: - ref: ${{ inputs.ref }} - - # Mirror main.yml: install the matrix JDK (test execution) plus JDK 17 - # (compile + Gradle daemon runtime). Order matters — setup-java points - # JAVA_HOME at the LAST entry, so 17 must be last for matrix.java = 25. - - name: Set up JDKs (test=${{ matrix.java }}, compile/daemon=17) - uses: actions/setup-java@v5 - with: - distribution: temurin - java-version: | - ${{ matrix.java }} - 17 - - - name: Set up Gradle - uses: gradle/actions/setup-gradle@v4 - - - name: Build, test, lint, coverage - env: - VERSION: ${{ inputs.version }} - run: ./gradlew build -PtestJdk=${{ matrix.java }} -PsdkVersion="$VERSION" --stacktrace - - - name: Upload test reports on failure - if: failure() - uses: actions/upload-artifact@v4 - with: - name: release-test-reports-jdk${{ matrix.java }} - path: | - build/reports/tests/ - build/test-results/ - retention-days: 14 - - release: - name: Tag and GitHub Release - runs-on: ubuntu-latest - needs: gate - outputs: - sha: ${{ steps.resolve.outputs.sha }} - steps: - - name: Checkout code - uses: actions/checkout@v7 - with: - fetch-depth: 0 - ref: ${{ inputs.ref }} - - # Resolve inputs.ref to a concrete commit SHA so the tag, the GitHub - # Release, and the chained Maven Central build all point at the SAME - # commit — even if the workflow was launched from a different branch. - - name: Resolve commit SHA - id: resolve - run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" - - - name: Verify tag does not already exist - shell: bash - env: - VERSION: ${{ inputs.version }} - run: | - TAG="v${VERSION}" - if git ls-remote --exit-code --tags origin "refs/tags/${TAG}" >/dev/null 2>&1; then - echo "::error::Tag ${TAG} already exists on origin" - exit 1 - fi - - - name: Extract release notes from CHANGELOG - env: - VERSION: ${{ inputs.version }} - shell: bash - run: | - # Extract the section for this version from CHANGELOG.md. - # Keep a Changelog format: matches from "## [X.Y.Z]" until the next - # "## [" heading or the trailing "[x]: url" link-reference block. - awk -v ver="$VERSION" ' - $0 ~ "^## \\[" ver "\\]" { found=1; next } - found && /^## \[/ { exit } - found && /^\[[^]]+\]: / { exit } - found { print } - ' CHANGELOG.md > release-notes.md - - # Verify we extracted something - if [ ! -s release-notes.md ]; then - echo "::error::Could not extract release notes for v${VERSION} from CHANGELOG.md" - exit 1 - fi - - echo "=== Extracted release notes ===" - cat release-notes.md - - - name: Create and publish release - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - VERSION: ${{ inputs.version }} - TARGET_REF: ${{ steps.resolve.outputs.sha }} - IS_PRERELEASE: ${{ inputs.prerelease }} - shell: bash - run: | - TAG="v${VERSION}" - FLAGS="" - if [ "$IS_PRERELEASE" = "true" ]; then - FLAGS="${FLAGS} --prerelease" - fi - - gh release create "${TAG}" \ - --target "${TARGET_REF}" \ - --title "Version ${VERSION}" \ - --notes-file release-notes.md \ - ${FLAGS} - - - name: Note next step (chaining disabled) - if: ${{ ! inputs.publish_to_central }} - env: - VERSION: ${{ inputs.version }} - run: echo "::notice::Release v${VERSION} created. Chaining is off — run the 'Publish to Maven Central' workflow (publish.yml) for this commit to push the artifact." - - # Chained Maven Central publish. Reuses publish.yml so there is one source of - # truth for publishing; its guard job still independently requires main.yml to - # be green for this commit before anything is pushed. - publish-central: - name: Publish to Maven Central - needs: release - if: ${{ inputs.publish_to_central }} - uses: ./.github/workflows/publish.yml - with: - version: ${{ inputs.version }} - # Build & publish the exact commit that was tagged/released, not the - # workflow's launch commit (github.sha) — Maven Central is immutable. - ref: ${{ needs.release.outputs.sha }} - release: true - secrets: inherit diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..b373a0d --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,39 @@ +name: Tests + +# The only automation in this repository: the unit tests, on every pull +# request. Nothing publishes, nothing merges, nothing runs on a schedule. +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: ['**'] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: tests-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + test: + name: Tests (JDK 17) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: '17' + - uses: gradle/actions/setup-gradle@v4 + - name: Test + run: ./gradlew test --no-daemon --stacktrace + - name: Upload test reports on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: test-reports + path: | + build/reports/tests/ + build/test-results/ + retention-days: 14 diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index 3d0e6bc..0000000 --- a/codecov.yml +++ /dev/null @@ -1,29 +0,0 @@ -# Codecov configuration. -# Docs: https://docs.codecov.com/docs/codecov-yaml - -coverage: - status: - project: - default: - # Compare against the base branch's coverage automatically. - target: auto - # Allow up to 5 percentage points of drop before failing the - # status check. Mirrors the project's "no >5pp regression vs main" - # rule that previously lived in .github/scripts/check-coverage-delta.py. - threshold: 5% - # Don't run on draft commits / forks of forks. - if_ci_failed: error - patch: - default: - # Patch coverage = coverage of the lines this PR added/changed. - # Require at least 70% coverage on new code to nudge contributors - # toward writing tests for new logic, while leaving room for - # mechanical / boilerplate diffs. - target: 70% - threshold: 5% - -# Pull request comment from the Codecov bot. -comment: - layout: "header, diff, files, footer" - behavior: default - require_changes: false