-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpull-requests.ts
More file actions
195 lines (175 loc) · 4.45 KB
/
pull-requests.ts
File metadata and controls
195 lines (175 loc) · 4.45 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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
import type { GitHubContext, PullRequest, PullRequestSearchOptions, RepoInfo } from './types.js';
/**
* Finds pull requests that have all of the specified labels
*/
export async function findPullRequestsByLabels({
ctx,
repo,
options,
}: {
ctx: GitHubContext;
repo: RepoInfo;
options: PullRequestSearchOptions;
}): Promise<PullRequest[]> {
const { labels, state = 'open', limit = 30, sort = 'created', direction = 'desc' } = options;
if (labels.length === 0) {
throw new Error('At least one label must be specified');
}
ctx.core.info(`Searching for PRs with labels: ${labels.join(', ')}`);
// Get pull requests with the specified state
const { data: pullRequests } = await ctx.github.rest.pulls.list({
...repo,
state,
per_page: Math.min(limit * 2, 100), // Get more than needed for filtering
sort,
direction,
});
// Filter PRs that have ALL specified labels
const matchingPRs = pullRequests.filter((pr) => {
const prLabels = pr.labels.map((label) => (typeof label === 'string' ? label : label.name));
return labels.every((requiredLabel) => prLabels.includes(requiredLabel));
});
ctx.core.info(`Found ${matchingPRs.length} PRs matching label criteria`);
return matchingPRs.slice(0, limit) as PullRequest[];
}
/**
* Gets a specific pull request by number
*/
export async function getPullRequest({
ctx,
repo,
pullNumber,
}: {
ctx: GitHubContext;
repo: RepoInfo;
pullNumber: number;
}): Promise<PullRequest> {
ctx.core.info(`Getting pull request #${pullNumber}`);
const { data: pullRequest } = await ctx.github.rest.pulls.get({
...repo,
pull_number: pullNumber,
});
return pullRequest as PullRequest;
}
/**
* Adds labels to a pull request
*/
export async function addLabelsToPullRequest({
ctx,
repo,
pullNumber,
labels,
}: {
ctx: GitHubContext;
repo: RepoInfo;
pullNumber: number;
labels: string[];
}): Promise<void> {
if (labels.length === 0) {
ctx.core.info('No labels to add');
return;
}
ctx.core.info(`Adding labels to PR #${pullNumber}: ${labels.join(', ')}`);
await ctx.github.rest.issues.addLabels({
...repo,
issue_number: pullNumber,
labels,
});
}
/**
* Removes labels from a pull request
*/
export async function removeLabelsFromPullRequest({
ctx,
repo,
pullNumber,
labels,
}: {
ctx: GitHubContext;
repo: RepoInfo;
pullNumber: number;
labels: string[];
}): Promise<void> {
if (labels.length === 0) {
ctx.core.info('No labels to remove');
return;
}
ctx.core.info(`Removing labels from PR #${pullNumber}: ${labels.join(', ')}`);
// Remove each label individually
for (const label of labels) {
try {
await ctx.github.rest.issues.removeLabel({
...repo,
issue_number: pullNumber,
name: label,
});
} catch (error: unknown) {
// Ignore 404 errors (label not found on PR)
if (error && typeof error === 'object' && 'status' in error && error.status === 404) {
ctx.core.info(`Label '${label}' was not present on PR #${pullNumber}`);
} else {
throw error;
}
}
}
}
/**
* Checks if a pull request has specific labels
*/
export async function pullRequestHasLabels({
ctx,
repo,
pullNumber,
labels,
requireAll = true,
}: {
ctx: GitHubContext;
repo: RepoInfo;
pullNumber: number;
labels: string[];
requireAll?: boolean; // If true, PR must have ALL labels; if false, just ANY label
}): Promise<boolean> {
const pr = await getPullRequest({ ctx, repo, pullNumber });
const prLabels = pr.labels.map((label) => label.name);
if (requireAll) {
return labels.every((label) => prLabels.includes(label));
} else {
return labels.some((label) => prLabels.includes(label));
}
}
/**
* Gets the files changed in a pull request
*/
export async function getPullRequestFiles({
ctx,
repo,
pullNumber,
}: {
ctx: GitHubContext;
repo: RepoInfo;
pullNumber: number;
}): Promise<
Array<{
filename: string;
status: string;
additions: number;
deletions: number;
changes: number;
patch?: string;
}>
> {
ctx.core.info(`Getting files for PR #${pullNumber}`);
const { data: files } = await ctx.github.rest.pulls.listFiles({
...repo,
pull_number: pullNumber,
per_page: 100,
});
return files.map((file) => ({
filename: file.filename,
status: file.status,
additions: file.additions,
deletions: file.deletions,
changes: file.changes,
...(file.patch && { patch: file.patch }),
}));
}