Skip to content

Forge can publish new-library PRs below the review dynamic-access coverage threshold #1885

Forge can publish new-library PRs below the review dynamic-access coverage threshold

Forge can publish new-library PRs below the review dynamic-access coverage threshold #1885

# 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 });