Label PR Intent Writer #78
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
| name: Label PR Intent Writer | |
| on: | |
| workflow_run: | |
| workflows: ["Label PR Intent"] | |
| types: [completed] | |
| permissions: | |
| actions: read | |
| issues: write | |
| pull-requests: write | |
| concurrency: | |
| group: lpiw-${{ github.event.workflow_run.head_repository.id || 'unknown-repo' }}-${{ github.event.workflow_run.head_branch || 'unknown-branch' }} | |
| cancel-in-progress: true | |
| jobs: | |
| apply-labels: | |
| runs-on: ubuntu-latest | |
| if: github.event.workflow_run.event == 'pull_request' | |
| steps: | |
| - name: Download desired label artifact | |
| id: download-result | |
| continue-on-error: true | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: label-pr-intent-result | |
| path: ${{ runner.temp }}/label-pr-intent-result | |
| run-id: ${{ github.event.workflow_run.id }} | |
| github-token: ${{ github.token }} | |
| - name: Apply intent labels | |
| if: steps.download-result.outcome == 'success' | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const workflowRun = context.payload.workflow_run; | |
| const resultPath = path.join(process.env.RUNNER_TEMP, 'label-pr-intent-result', 'result.json'); | |
| const result = JSON.parse(fs.readFileSync(resultPath, 'utf8')); | |
| const managedLabels = new Set([ | |
| 'skills', | |
| 'plugin', | |
| 'agent', | |
| 'instructions', | |
| 'new-submission', | |
| 'website-update', | |
| 'external-plugin', | |
| 'hooks', | |
| 'workflow', | |
| 'canvas-extension', | |
| ]); | |
| function fail(message) { | |
| throw new Error(`Invalid label intent artifact: ${message}`); | |
| } | |
| if (result.schema_version !== 'label-pr-intent-result/v1') fail('unexpected schema_version'); | |
| if (result.event !== 'pull_request') fail('unexpected event'); | |
| if (!Number.isInteger(result.pr_number) || result.pr_number < 1) fail('invalid pr_number'); | |
| if (!/^[0-9a-f]{40}$/i.test(String(result.head_sha || ''))) fail('invalid head_sha'); | |
| if (workflowRun.event !== 'pull_request') fail('unexpected workflow_run event'); | |
| if (String(result.run_id || '') !== String(workflowRun.id)) fail('run_id did not match workflow_run'); | |
| if (result.head_sha !== workflowRun.head_sha) fail('head_sha did not match workflow_run'); | |
| if (!Array.isArray(result.desired_labels) || result.desired_labels.length > managedLabels.size) fail('invalid desired_labels'); | |
| if (!Array.isArray(result.managed_labels)) fail('invalid managed_labels'); | |
| for (const label of result.managed_labels) { | |
| if (!managedLabels.has(label)) fail(`unexpected managed label ${label}`); | |
| } | |
| for (const label of result.desired_labels) { | |
| if (!managedLabels.has(label)) fail(`unexpected desired label ${label}`); | |
| } | |
| const { data: pr } = await github.rest.pulls.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: result.pr_number, | |
| }); | |
| if (pr.state !== 'open') { | |
| core.info(`Skipping non-open PR #${result.pr_number}.`); | |
| return; | |
| } | |
| const expectedBaseRepository = `${context.repo.owner}/${context.repo.repo}`.toLowerCase(); | |
| const runHeadRepository = String(workflowRun.head_repository?.full_name || ''); | |
| const runHeadRepositoryParts = runHeadRepository.split('/'); | |
| const runHeadRef = String(workflowRun.head_branch || ''); | |
| if (String(pr.base?.repo?.full_name || '').toLowerCase() !== expectedBaseRepository) { | |
| fail(`PR #${result.pr_number} does not target this repository`); | |
| } | |
| if (pr.head.sha !== workflowRun.head_sha) { | |
| core.warning(`Skipping stale label intent result for PR #${result.pr_number}: artifact head ${result.head_sha}, current head ${pr.head.sha}`); | |
| return; | |
| } | |
| if ( | |
| runHeadRepositoryParts.length !== 2 || | |
| !runHeadRepositoryParts[0] || | |
| !runHeadRepositoryParts[1] || | |
| !runHeadRef || | |
| String(pr.head?.repo?.full_name || '').toLowerCase() !== runHeadRepository.toLowerCase() || | |
| String(pr.head?.ref || '') !== runHeadRef | |
| ) { | |
| fail(`PR #${result.pr_number} head did not match workflow_run`); | |
| } | |
| const workflowRunPullRequests = Array.isArray(workflowRun.pull_requests) ? workflowRun.pull_requests : []; | |
| if (workflowRunPullRequests.length > 0) { | |
| if (!workflowRunPullRequests.some((pullRequest) => pullRequest.number === result.pr_number)) { | |
| fail(`PR #${result.pr_number} was not present in workflow_run.pull_requests`); | |
| } | |
| } else { | |
| const candidatePullRequests = await github.paginate(github.rest.pulls.list, { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| state: 'open', | |
| head: `${runHeadRepositoryParts[0]}:${runHeadRef}`, | |
| per_page: 100, | |
| }); | |
| const trustedMatches = candidatePullRequests.filter((candidate) => | |
| candidate.head?.sha === workflowRun.head_sha && | |
| String(candidate.head?.ref || '') === runHeadRef && | |
| String(candidate.head?.repo?.full_name || '').toLowerCase() === runHeadRepository.toLowerCase() && | |
| String(candidate.base?.repo?.full_name || '').toLowerCase() === expectedBaseRepository | |
| ); | |
| if (trustedMatches.length !== 1 || trustedMatches[0].number !== result.pr_number) { | |
| fail(`PR #${result.pr_number} could not be uniquely associated with workflow_run`); | |
| } | |
| } | |
| const desiredLabels = new Set(result.desired_labels); | |
| const currentLabels = await github.paginate(github.rest.issues.listLabelsOnIssue, { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: result.pr_number, | |
| per_page: 100, | |
| }); | |
| const currentManagedLabels = currentLabels | |
| .map((label) => label.name) | |
| .filter((name) => managedLabels.has(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: result.pr_number, | |
| labels: labelsToAdd, | |
| }); | |
| } | |
| for (const name of labelsToRemove) { | |
| await github.rest.issues.removeLabel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: result.pr_number, | |
| name, | |
| }); | |
| } | |
| core.info(`Managed labels: ${[...desiredLabels].sort().join(', ') || 'none'}`); | |
| - name: Note missing artifact | |
| if: steps.download-result.outcome != 'success' | |
| run: echo "No label-pr-intent-result artifact was available; nothing to synchronize." |