diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index db4d34e1..206bbaae 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -10,16 +10,16 @@ on: branches: [ "main" ] permissions: - contents: write # required for dependency submission + contents: read jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up JDK 21 - uses: actions/setup-java@v4 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: java-version: '21' distribution: 'temurin' @@ -27,11 +27,3 @@ jobs: - name: Build with Maven run: mvn -B -f SimpleAPI/pom.xml package - - # Only run dependency graph submission on push (not pull_request) - - name: Submit Dependency Snapshot - if: github.event_name == 'push' - uses: advanced-security/maven-dependency-submission-action@v5 - with: - directory: SimpleAPI # path to pom.xml - # optional: maven-args: "-DskipTests" diff --git a/.github/workflows/publish-javadoc.yml b/.github/workflows/publish-javadoc.yml index 6044a4fe..285710c5 100644 --- a/.github/workflows/publish-javadoc.yml +++ b/.github/workflows/publish-javadoc.yml @@ -2,23 +2,91 @@ name: Deploy Javadoc on: release: - types: [published] - branches: - - master - - main + types: [published, released] + +permissions: + contents: read + +concurrency: + group: github-pages + cancel-in-progress: false jobs: - publish: + verify-release: + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + eligible: ${{ steps.eligibility.outputs.eligible }} + commit: ${{ steps.eligibility.outputs.commit }} + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: main + fetch-depth: 0 + persist-credentials: false + + - name: Verify release tag is on main + id: eligibility + env: + PRERELEASE: ${{ github.event.release.prerelease }} + TAG_NAME: ${{ github.event.release.tag_name }} + run: | + set -euo pipefail + if [[ "$PRERELEASE" == "true" ]]; then + echo "eligible=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + git fetch --no-tags origin "refs/tags/${TAG_NAME}" + tag_commit="$(git rev-parse --verify 'FETCH_HEAD^{commit}')" + if git merge-base --is-ancestor "$tag_commit" origin/main; then + echo "eligible=true" >> "$GITHUB_OUTPUT" + echo "commit=$tag_commit" >> "$GITHUB_OUTPUT" + else + echo "eligible=false" >> "$GITHUB_OUTPUT" + fi + + build: + needs: verify-release + if: needs.verify-release.outputs.eligible == 'true' runs-on: ubuntu-latest + permissions: + contents: read steps: - - name: Deploy JavaDoc 🚀 - uses: MathieuSoysal/Javadoc-publisher.yml@main + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - javadoc-branch: gh-pages - java-version: 21 - target-folder: '' - project: maven # or gradle - # subdirectories: moduleA moduleB #for subdirectories support, needs to be run with custom command - custom-command: mvn -f SimpleAPI/pom.xml deploy -P javadoc javadoc:aggregate - javadoc-source-folder: 'SimpleAPI/target/reports/apidocs' + ref: ${{ needs.verify-release.outputs.commit }} + persist-credentials: false + + - name: Set up JDK 21 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + with: + java-version: '21' + distribution: temurin + cache: maven + + - name: Build Javadoc + run: mvn -B -f SimpleAPI/pom.xml clean package -P javadoc javadoc:aggregate + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1 + with: + path: SimpleAPI/target/reports/apidocs + + deploy: + needs: [verify-release, build] + if: >- + needs.verify-release.outputs.eligible == 'true' && + needs.build.result == 'success' + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + permissions: + pages: write + id-token: write + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 diff --git a/SimpleAPI/src/test/java/com/bencodez/simpleapi/build/BuildInputPinningTest.java b/SimpleAPI/src/test/java/com/bencodez/simpleapi/build/BuildInputPinningTest.java new file mode 100644 index 00000000..a17d89f0 --- /dev/null +++ b/SimpleAPI/src/test/java/com/bencodez/simpleapi/build/BuildInputPinningTest.java @@ -0,0 +1,48 @@ +package com.bencodez.simpleapi.build; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.junit.jupiter.api.Test; + +class BuildInputPinningTest { + + @Test + void documentationUsesVerifiedReleaseCommitAndAggregateOutput() throws IOException { + String workflow = Files.readString(Path.of("..", ".github", "workflows", "publish-javadoc.yml")); + String verificationJob = job(workflow, "verify-release"); + String buildJob = job(workflow, "build"); + String deployJob = job(workflow, "deploy"); + + assertTrue(workflow.contains("types: [published, released]")); + assertTrue(verificationJob.contains("git merge-base --is-ancestor \"$tag_commit\" origin/main")); + assertFalse(workflow.contains("target_commitish")); + assertTrue(verificationJob.contains("FETCH_HEAD^{commit}")); + assertFalse(verificationJob.contains("refs/tags/release")); + assertTrue(buildJob.contains("ref: ${{ needs.verify-release.outputs.commit }}")); + assertTrue(buildJob.contains("path: SimpleAPI/target/reports/apidocs")); + assertTrue(deployJob.contains("needs.build.result == 'success'")); + } + + @Test + void ordinaryBuildHasNoWriteScopedDependencySubmission() throws IOException { + String workflow = Files.readString(Path.of("..", ".github", "workflows", "maven.yml")); + + assertTrue(Pattern.compile("(?m)^permissions:\\R contents: read$").matcher(workflow).find()); + assertFalse(workflow.contains("contents: write")); + assertFalse(workflow.contains("maven-dependency-submission-action")); + } + + private static String job(String workflow, String name) { + Matcher matcher = Pattern.compile("(?ms)^ " + Pattern.quote(name) + + ":\\R(?.*?)(?=^ [A-Za-z0-9_-]+:\\R|\\z)").matcher(workflow); + assertTrue(matcher.find(), () -> "Missing " + name + " job"); + return matcher.group("job"); + } +}