mirror of
https://github.com/community-scripts/ProxmoxVE.git
synced 2026-11-04 04:24:31 +01:00
bffc871aa9
close-invalid-pr-template.yml now comments and labels (missing pr template, stale) instead of closing immediately, clears itself once fixed. stale_pr_close.yml gains the engagement-gated/keep-open/maintainer-reset behavior already used in DevScripts, so a template-stale PR only closes via its own re-check. request-script.yml discussion template gets an optional Justification field; a new workflow verifies stars/age against GitHub for the submitted repo and either does nothing (eligible), labels investigation (ineligible + justified), or closes the discussion (ineligible, no justification). Moved the AI Assistance section to the end of pull_request_template.md.
142 lines
6.4 KiB
YAML
Generated
142 lines
6.4 KiB
YAML
Generated
name: Request Script Eligibility Check
|
|
|
|
# category.slug is the same string GitHub uses in the "new discussion" URL
|
|
# (?category=request-script, linked from every repo's issue chooser), so this
|
|
# guard matches the live category without needing its numeric id.
|
|
on:
|
|
discussion:
|
|
types: [created, edited, reopened]
|
|
|
|
jobs:
|
|
check-eligibility:
|
|
if: github.repository == 'community-scripts/ProxmoxVE' && github.event.discussion.category.slug == 'request-script'
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
discussions: write
|
|
issues: write
|
|
contents: read
|
|
steps:
|
|
- name: Verify stars/age, flag or close
|
|
uses: actions/github-script@v9
|
|
with:
|
|
script: |
|
|
const owner = context.repo.owner;
|
|
const repo = context.repo.repo;
|
|
const discussion = context.payload.discussion;
|
|
|
|
if (!discussion || discussion.state === "closed") {
|
|
core.info("Discussion missing or already closed — skipping.");
|
|
return;
|
|
}
|
|
|
|
const body = discussion.body || "";
|
|
const lines = body.split("\n");
|
|
const fields = {};
|
|
let current = null;
|
|
for (const line of lines) {
|
|
const m = line.match(/^###\s+(.+?)\s*$/);
|
|
if (m) {
|
|
current = m[1].trim();
|
|
fields[current] = [];
|
|
} else if (current) {
|
|
fields[current].push(line);
|
|
}
|
|
}
|
|
function fieldText(label) {
|
|
const raw = (fields[label] || []).join("\n").trim();
|
|
return raw === "_No response_" ? "" : raw;
|
|
}
|
|
|
|
const sourceRepo = fieldText("Source Repository");
|
|
const justification = fieldText("Justification for an exception");
|
|
|
|
let eligible = false;
|
|
let verified = false;
|
|
const ghMatch = sourceRepo.match(/^https?:\/\/(?:www\.)?github\.com\/([^\/\s]+)\/([^\/\s#?]+?)(?:\.git)?\/?(?:[#?].*)?$/i);
|
|
|
|
if (ghMatch) {
|
|
const [, repoOwner, repoName] = ghMatch;
|
|
try {
|
|
const { data: repoData } = await github.rest.repos.get({ owner: repoOwner, repo: repoName });
|
|
const stars = repoData.stargazers_count;
|
|
const createdAt = new Date(repoData.created_at);
|
|
const now = new Date();
|
|
const ageMonths =
|
|
(now.getFullYear() - createdAt.getFullYear()) * 12 +
|
|
(now.getMonth() - createdAt.getMonth()) -
|
|
(now.getDate() < createdAt.getDate() ? 1 : 0);
|
|
eligible = stars >= 1000 && ageMonths >= 6;
|
|
verified = true;
|
|
core.info(`Verified ${repoOwner}/${repoName}: ${stars} stars, ${ageMonths} months old, eligible=${eligible}`);
|
|
} catch (err) {
|
|
core.warning(`Could not verify ${repoOwner}/${repoName}: ${err.message} — treating as unverifiable.`);
|
|
}
|
|
} else {
|
|
core.info("Source Repository is not a github.com URL — cannot auto-verify, treating as unverifiable.");
|
|
}
|
|
|
|
if (eligible) {
|
|
core.info(`Discussion #${discussion.number} meets the requirements — no action.`);
|
|
return;
|
|
}
|
|
|
|
const labels = (discussion.labels || []).map(l => l.name);
|
|
const reasonLine = verified
|
|
? "the source repository does not meet the 1,000-star / 6-month minimum adoption requirement"
|
|
: "the source repository's star count and age could not be verified automatically";
|
|
|
|
if (justification) {
|
|
if (labels.includes("investigation")) {
|
|
core.info(`Discussion #${discussion.number} already flagged for investigation — skipping.`);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await github.rest.issues.createLabel({
|
|
owner, repo, name: "investigation",
|
|
color: "fbca04",
|
|
description: "Below the normal script-request threshold; a justification was given and needs maintainer review.",
|
|
});
|
|
} catch (err) {
|
|
if (err.status !== 422) throw err;
|
|
}
|
|
|
|
const labelQuery = await github.graphql(
|
|
`query($owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { label(name: "investigation") { id } } }`,
|
|
{ owner, repo }
|
|
);
|
|
const labelNodeId = labelQuery.repository.label.id;
|
|
|
|
await github.graphql(
|
|
`mutation($labelableId: ID!, $labelIds: [ID!]!) { addLabelsToLabelable(input: { labelableId: $labelableId, labelIds: $labelIds }) { clientMutationId } }`,
|
|
{ labelableId: discussion.node_id, labelIds: [labelNodeId] }
|
|
);
|
|
|
|
await github.graphql(
|
|
`mutation($discussionId: ID!, $body: String!) { addDiscussionComment(input: { discussionId: $discussionId, body: $body }) { comment { id } } }`,
|
|
{
|
|
discussionId: discussion.node_id,
|
|
body:
|
|
`Thanks for the justification — ${reasonLine}, so this has been flagged \`investigation\` for a maintainer ` +
|
|
`to review manually rather than closed automatically.`,
|
|
}
|
|
);
|
|
} else {
|
|
await github.graphql(
|
|
`mutation($discussionId: ID!, $body: String!) { addDiscussionComment(input: { discussionId: $discussionId, body: $body }) { comment { id } } }`,
|
|
{
|
|
discussionId: discussion.node_id,
|
|
body:
|
|
`This request doesn't meet the minimum requirements (1,000 stars, project at least 6 months old) — ` +
|
|
`${reasonLine}, and no justification was given. Closing automatically.\n\n` +
|
|
`If you believe this project qualifies for an exception, edit this discussion to fill in ` +
|
|
`**Justification for an exception** and reopen it — it will then be flagged for manual review instead of closed again.`,
|
|
}
|
|
);
|
|
|
|
await github.graphql(
|
|
`mutation($discussionId: ID!) { closeDiscussion(input: { discussionId: $discussionId, reason: OUTDATED }) { discussion { id } } }`,
|
|
{ discussionId: discussion.node_id }
|
|
);
|
|
}
|