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, '') 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<> "$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' }); }