Repository navigation
Expand file tree
/
Copy pathintegration-codeowner-writer.yml
More file actions
156 lines (137 loc) · 6.99 KB
/
Copy pathintegration-codeowner-writer.yml
File metadata and controls
156 lines (137 loc) · 6.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
name: Integration code owner prompt writer
on:
workflow_run:
workflows: ["Integration code owner automation"]
types: [completed]
permissions:
actions: read
issues: write
pull-requests: read
concurrency:
group: integration-codeowner-prompt-${{ github.event.workflow_run.head_repository.id || 'unknown-repo' }}-${{ github.event.workflow_run.head_branch || 'unknown-branch' }}
cancel-in-progress: true
jobs:
prompt-for-codeowner:
if: ${{ github.repository_owner == 'CommunityToolkit' && github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
steps:
- name: Download prompt result artifact
id: download-result
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: integration-codeowner-prompt
path: ${{ runner.temp }}/integration-codeowner-prompt
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ github.token }}
- name: Prompt for code owner interest
if: ${{ steps.download-result.outcome == 'success' }}
uses: actions/github-script@v9.0.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, 'integration-codeowner-prompt', 'result.json');
const result = JSON.parse(fs.readFileSync(resultPath, 'utf8'));
function fail(message) {
throw new Error(`Invalid integration code owner prompt artifact: ${message}`);
}
if (result.schema_version !== 'integration-codeowner-prompt/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 (typeof result.is_new_integration !== 'boolean') fail('invalid is_new_integration');
if (!/^[0-9a-f]{40}$/i.test(String(result.head_sha || ''))) fail('invalid head_sha');
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 (!result.is_new_integration) {
core.info('Skipping PR because it does not add a new integration.');
return;
}
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' || pr.draft) {
core.info(`Skipping non-open or draft 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 prompt 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 marker = '<!-- integration-codeowner-prompt -->';
const body = `${marker}
Thanks for opening a new integration PR, @${pr.user.login}.
If you want to become the code owner for this integration, reply with:
- \`/codeowner-accept\`
- \`/codeowner-reject\`
Only the PR author should use these commands.`;
const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
per_page: 100,
});
const existingComment = comments.find((comment) =>
comment.user?.login === 'github-actions[bot]' && String(comment.body || '').includes(marker)
);
if (existingComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body,
});
return;
}
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body,
});
- name: Note missing artifact
if: ${{ steps.download-result.outcome != 'success' }}
run: echo "No integration-codeowner-prompt artifact was available; nothing to synchronize."