Forge can publish new-library PRs below the review dynamic-access coverage threshold #1885
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Issue triage: labels eligible library-new-request issues, validates coordinates, closes | |
| # invalid/duplicate/supported requests, and uses open-dependency-issues-and-link-blockers.js | |
| # (§CI-shared-scripts) for blockers. §CI-triage-new-issues; §FS-repository-functional-spec.4. | |
| name: "Triage new issues" | |
| on: | |
| issues: | |
| types: [opened] | |
| permissions: | |
| contents: read | |
| issues: write | |
| jobs: | |
| verify-new-issue: | |
| name: "🔎 Verify new issue" | |
| if: ${{ github.event.issue.pull_request == null }} | |
| runs-on: ubuntu-latest | |
| outputs: | |
| coords: ${{ steps.extract.outputs.coords }} | |
| should_expand: ${{ steps.expand.outputs.should_expand }} | |
| steps: | |
| - name: "Ensure unlabeled library request has triage labels" | |
| id: classify | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 | |
| with: | |
| script: | | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const issue_number = context.issue.number; | |
| const issue = context.payload.issue; | |
| const labelNames = (issue.labels || []).map((label) => | |
| typeof label === "string" ? label : label?.name | |
| ).filter(Boolean); | |
| const hasLibraryNewRequestLabel = labelNames.includes("library-new-request"); | |
| function extractCoordinatesFromTitle(title) { | |
| const match = String(title || "").match(/^Support for ([A-Za-z0-9._-]+:[A-Za-z0-9._-]+:[A-Za-z0-9._-]+)$/); | |
| return match ? match[1] : null; | |
| } | |
| const coords = extractCoordinatesFromTitle(issue.title); | |
| const hasLibraryRequestTitle = Boolean(coords) && issue.title === `Support for ${coords}`; | |
| const isUnlabeledLibraryRequest = labelNames.length === 0 && hasLibraryRequestTitle; | |
| if (isUnlabeledLibraryRequest) { | |
| await github.rest.issues.addLabels({ | |
| owner, | |
| repo, | |
| issue_number, | |
| labels: ["library-new-request", "priority"] | |
| }); | |
| core.info("Added library-new-request and priority labels to unlabeled library request."); | |
| } | |
| core.setOutput( | |
| "should_triage", | |
| hasLibraryNewRequestLabel || isUnlabeledLibraryRequest ? "true" : "false" | |
| ); | |
| - name: "Checkout repository" | |
| if: ${{ steps.classify.outputs.should_triage == 'true' }} | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: "Extract Maven coordinates" | |
| if: ${{ steps.classify.outputs.should_triage == 'true' }} | |
| id: extract | |
| env: | |
| ISSUE_TITLE: ${{ github.event.issue.title }} | |
| run: | | |
| set -Eeuo pipefail | |
| if [[ "$ISSUE_TITLE" =~ ^Support[[:space:]]for[[:space:]]([A-Za-z0-9._-]+:[A-Za-z0-9._-]+:[A-Za-z0-9._-]+)$ ]]; then | |
| COORDS="${BASH_REMATCH[1]}" | |
| else | |
| COORDS="" | |
| fi | |
| echo "coords=$COORDS" >> "$GITHUB_OUTPUT" | |
| - name: "Validate coordinates format" | |
| if: ${{ steps.classify.outputs.should_triage == 'true' }} | |
| id: validate | |
| env: | |
| COORDS: ${{ steps.extract.outputs.coords }} | |
| run: | | |
| set -Eeuo pipefail | |
| if [[ "$COORDS" =~ ^[A-Za-z0-9._-]+:[A-Za-z0-9._-]+:[A-Za-z0-9._-]+$ ]]; then | |
| echo "invalid=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "Invalid coordinates format: $COORDS" | |
| echo "invalid=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: "Close issue - invalid coordinates format" | |
| if: ${{ steps.classify.outputs.should_triage == 'true' && steps.validate.outputs.invalid == 'true' }} | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 | |
| env: | |
| COORDS: ${{ steps.extract.outputs.coords }} | |
| with: | |
| script: | | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const issue_number = context.issue.number; | |
| const coords = process.env.COORDS || ""; | |
| const reason = `Maven coordinates in the issue title are invalid: '${coords}'.`; | |
| const body = [ | |
| "Automated triage: " + reason, | |
| "", | |
| "Expected format:", | |
| "", | |
| "```", | |
| "groupId:artifactId:version", | |
| "```", | |
| "", | |
| "Example:", | |
| "", | |
| "```", | |
| "org.hibernate.orm:hibernate-core:1.2.3", | |
| "```", | |
| "", | |
| "This issue will be closed. Please open a new issue with the correct format in the issue title.", | |
| "Reopening will not re-run automation; maintainers will review manually." | |
| ].join("\n"); | |
| await github.rest.issues.createComment({ owner, repo, issue_number, body }); | |
| await github.rest.issues.update({ owner, repo, issue_number, state: "closed" }); | |
| - name: "Close issue - duplicate request for exact coordinates" | |
| if: ${{ steps.classify.outputs.should_triage == 'true' && steps.validate.outputs.invalid != 'true' }} | |
| id: duplicate | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 | |
| env: | |
| COORDS: ${{ steps.extract.outputs.coords }} | |
| with: | |
| script: | | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const issue_number = context.issue.number; | |
| const coords = process.env.COORDS || ""; | |
| const exactTitle = `Support for ${coords}`; | |
| function extractCoordinatesFromTitle(title) { | |
| const match = String(title || "").match(/^Support for ([A-Za-z0-9._-]+:[A-Za-z0-9._-]+:[A-Za-z0-9._-]+)$/); | |
| return match ? match[1] : null; | |
| } | |
| const openIssues = await github.paginate(github.rest.issues.listForRepo, { | |
| owner, | |
| repo, | |
| state: "open", | |
| per_page: 100 | |
| }); | |
| const duplicateIssue = openIssues.find((issue) => { | |
| if (issue.pull_request || issue.number === issue_number) { | |
| return false; | |
| } | |
| return issue.title === exactTitle || extractCoordinatesFromTitle(issue.title) === coords; | |
| }); | |
| if (!duplicateIssue) { | |
| core.setOutput("duplicate", "false"); | |
| return; | |
| } | |
| core.setOutput("duplicate", "true"); | |
| core.setOutput("duplicate_issue_number", String(duplicateIssue.number)); | |
| const body = [ | |
| `Automated triage: A request for the exact coordinates (${coords}) already exists in #${duplicateIssue.number}.`, | |
| "", | |
| "Closing this issue as a duplicate.", | |
| "If you have additional context, please add it to the existing issue.", | |
| "Note: Reopening will not re-run automation; maintainers will review manually." | |
| ].join("\n"); | |
| await github.rest.issues.createComment({ owner, repo, issue_number, body }); | |
| await github.rest.issues.update({ owner, repo, issue_number, state: "closed" }); | |
| - name: "Check if library/version is already supported by the repository" | |
| if: ${{ steps.classify.outputs.should_triage == 'true' && steps.validate.outputs.invalid != 'true' && steps.duplicate.outputs.duplicate != 'true' }} | |
| id: support | |
| env: | |
| COORDS: ${{ steps.extract.outputs.coords }} | |
| run: | | |
| set -Eeuo pipefail | |
| echo "Checking support for $COORDS" | |
| set +e | |
| OUTPUT="$(./check-library-support.sh "$COORDS" 2>&1)" | |
| STATUS=$? | |
| set -e | |
| echo "$OUTPUT" | |
| if grep -qF "is supported" <<<"$OUTPUT"; then | |
| echo "supported=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "supported=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| echo "exit_code=$STATUS" >> "$GITHUB_OUTPUT" | |
| - name: "Close issue - already supported by the Reachability Metadata Repository" | |
| if: ${{ steps.classify.outputs.should_triage == 'true' && steps.support.outputs.supported == 'true' }} | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 | |
| env: | |
| COORDS: ${{ steps.extract.outputs.coords }} | |
| with: | |
| script: | | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const issue_number = context.issue.number; | |
| const coords = process.env.COORDS || ""; | |
| const body = [ | |
| `Automated triage: The requested library (${coords}) is already supported by the GraalVM Reachability Metadata repository.`, | |
| "", | |
| "Closing this issue. If you believe this is not correct, please reopen the issue with additional details.", | |
| "Note: Reopening will not re-run automation; maintainers will review manually." | |
| ].join("\n"); | |
| await github.rest.issues.createComment({ owner, repo, issue_number, body }); | |
| await github.rest.issues.update({ owner, repo, issue_number, state: "closed" }); | |
| - name: "Mark issue eligible for dependency graph expansion" | |
| if: ${{ steps.classify.outputs.should_triage == 'true' && steps.validate.outputs.invalid != 'true' && steps.duplicate.outputs.duplicate != 'true' && steps.support.outputs.supported != 'true' }} | |
| id: expand | |
| run: | | |
| set -Eeuo pipefail | |
| echo "should_expand=true" >> "$GITHUB_OUTPUT" | |
| triage-transitive-dependency-issues: | |
| needs: verify-new-issue | |
| name: "✏️ Create issues for unsupported transitive dependencies" | |
| if: ${{ needs.verify-new-issue.result == 'success' && needs.verify-new-issue.outputs.should_expand == 'true' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: "Checkout repository" | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: "Setup Java (GraalVM) to run Gradle" | |
| uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4 | |
| with: | |
| distribution: 'graalvm' | |
| java-version: '25' | |
| - name: "Run deps.dev graph and capture raw dependency graph" | |
| env: | |
| COORDS: ${{ needs.verify-new-issue.outputs.coords }} | |
| run: | | |
| set -Eeuo pipefail | |
| DEPS_GRAPH_JSON="$(./gradlew -q generateDependencyGraph -Pcoordinates="$COORDS" --console=plain --no-daemon | tail -n 1)" | |
| jq -e . >/dev/null <<<"$DEPS_GRAPH_JSON" | |
| { | |
| echo 'DEPS_GRAPH_JSON<<EOF' | |
| echo "$DEPS_GRAPH_JSON" | |
| echo 'EOF' | |
| } >> "$GITHUB_ENV" | |
| - name: "Open dependency issues and link blockers" | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 | |
| with: | |
| script: | | |
| const path = require('path'); | |
| const scriptPath = path.join( | |
| process.env.GITHUB_WORKSPACE, | |
| '.github', | |
| 'workflows', | |
| 'scripts', | |
| 'open-dependency-issues-and-link-blockers.js' | |
| ); | |
| const openDependencyIssuesAndLinkBlockers = require(scriptPath); | |
| await openDependencyIssuesAndLinkBlockers({ github, context }); |