Files
awesome-copilot/.github/workflows/external-plugin-intake.yml
T
Aaron Powell e66aa80240 feat: add public external plugin workflows (#1723)
* feat: add external plugin submission workflows

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* minor adjustment to contributing guide

* fix: address external plugin review feedback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Reverting some changes to the readme.agents.md file

* fix: address follow-up review feedback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: tighten external plugin workflows

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-15 15:37:18 +10:00

173 lines
6.1 KiB
YAML

name: External Plugin Intake
on:
issues:
types: [opened, edited, reopened]
permissions:
contents: read
issues: write
jobs:
validate-submission:
runs-on: ubuntu-latest
if: >-
contains(github.event.issue.labels.*.name, 'external-plugin') ||
contains(github.event.issue.body, '<!-- external-plugin-submission -->')
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Evaluate submission
id: evaluation
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
result=$(node ./eng/external-plugin-intake.mjs "$GITHUB_EVENT_PATH")
{
echo 'result<<EOF'
echo "$result"
echo 'EOF'
} >> "$GITHUB_OUTPUT"
- name: Sync labels and comment
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
env:
RESULT_JSON: ${{ steps.evaluation.outputs.result }}
with:
script: |
const managedLabels = {
'external-plugin': {
color: 'FEF2C0',
description: 'Public external plugin submission'
},
'awaiting-review': {
color: 'FBCA04',
description: 'Submission is waiting for automated intake validation'
},
'ready-for-review': {
color: '0E8A16',
description: 'Submission passed intake validation and is ready for maintainer review'
},
'approved': {
color: '1D76DB',
description: 'Submission was approved by a maintainer'
},
'rejected': {
color: 'B60205',
description: 'Submission was rejected or failed intake validation'
}
};
async function ensureLabel(name, config) {
try {
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name,
color: config.color,
description: config.description
});
} catch (error) {
if (error.status !== 422) {
throw error;
}
}
}
async function syncManagedLabels(issueNumber, desiredLabels) {
await Promise.all(Object.entries(managedLabels).map(([name, config]) => ensureLabel(name, config)));
const managedForSync = ['external-plugin', 'awaiting-review', 'ready-for-review', 'rejected'];
const currentLabels = await github.paginate(github.rest.issues.listLabelsOnIssue, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
per_page: 100
});
const currentManagedLabels = currentLabels
.map((label) => label.name)
.filter((name) => managedForSync.includes(name));
const labelsToAdd = [...desiredLabels].filter((name) => !currentManagedLabels.includes(name));
const labelsToRemove = currentManagedLabels.filter((name) => !desiredLabels.has(name));
if (labelsToAdd.length > 0) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
labels: labelsToAdd
});
}
for (const name of labelsToRemove) {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
name
});
}
}
const result = JSON.parse(process.env.RESULT_JSON);
const issueNumber = context.issue.number;
const issueState = context.payload.issue.state;
const action = context.payload.action;
const existingLabelNames = (context.payload.issue.labels || []).map((label) => label.name);
if (existingLabelNames.includes('approved')) {
core.info('Issue is already approved; skipping intake synchronization.');
return;
}
if (issueState === 'closed' && action !== 'reopened') {
core.info('Issue is closed; waiting for reopen before rerunning intake synchronization.');
return;
}
const desiredLabels = result.valid
? new Set(['external-plugin', 'ready-for-review'])
: new Set(['external-plugin', 'rejected']);
await syncManagedLabels(issueNumber, desiredLabels);
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
per_page: 100
});
const existingComment = comments.find((comment) =>
comment.user?.login === 'github-actions[bot]' &&
comment.body?.includes(result.commentMarker)
);
if (existingComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body: result.commentBody
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body: result.commentBody
});
}
if (!result.valid && issueState === 'open') {
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
state: 'closed'
});
}