@reviewflow/sdk 0.0.0-stage → 6.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/README.md +86 -2
- package/build/files-node.mjs +70 -0
- package/build/git-node.mjs +1792 -0
- package/build/index-browser.es.js +3328 -0
- package/build/index-node.mjs +3328 -0
- package/package.json +135 -3
- package/src/api/pullRequest.ts +617 -0
- package/src/config/insertCheckoutEntry.ts +54 -0
- package/src/diff/diffPaths.ts +78 -0
- package/src/diff/impacts.ts +47 -0
- package/src/diff/readingOrder.ts +428 -0
- package/src/diff/themes.ts +105 -0
- package/src/diff/types.ts +141 -0
- package/src/files/editableFile.ts +163 -0
- package/src/files.ts +13 -0
- package/src/git/groupDiffPaths.ts +134 -0
- package/src/git/manifests.ts +96 -0
- package/src/git/moduleBindings.ts +571 -0
- package/src/git/pullRequestDiff.ts +159 -0
- package/src/git/readBranchDiff.ts +359 -0
- package/src/git/readImpacts.ts +797 -0
- package/src/git/readingOrder.ts +190 -0
- package/src/git/run.ts +213 -0
- package/src/git.ts +38 -0
- package/src/github/applySuggestions.ts +399 -0
- package/src/github/editPullRequest.ts +790 -0
- package/src/github/fetchCompareDiff.ts +21 -0
- package/src/github/imageProxies.ts +43 -0
- package/src/github/lineDiff.ts +145 -0
- package/src/github/prQuery.ts +298 -0
- package/src/github/pullRequestDetail.ts +1347 -0
- package/src/github/pullRequestHead.ts +65 -0
- package/src/github/pullRequestRevisions.ts +124 -0
- package/src/github/transport.ts +171 -0
- package/src/index.ts +35 -0
- package/src/reviewflow/board.ts +333 -0
- package/src/reviewflow/changedParts.ts +80 -0
- package/src/reviewflow/checksFromReviewflow.ts +75 -0
- package/src/reviewflow/client.ts +589 -0
- package/src/reviewflow/follow.ts +345 -0
- package/src/reviewflow/status.ts +17 -0
- package/src/reviewflow/stream.ts +199 -0
- package/src/types/entities.ts +8 -0
- package/src/types/ids.ts +1 -0
- package/src/types/reviewflow.ts +32 -0
- package/src/utils/pullRequestEdit.ts +108 -0
- package/src/utils/pullRequestPart.ts +12 -0
- package/src/utils/pullRequestReaction.ts +19 -0
- package/src/utils/pullRequestSuggestion.ts +148 -0
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
import type { PrReviewDecision, PrState } from "../types/entities.ts";
|
|
2
|
+
import type { IsoDateTime } from "../types/ids.ts";
|
|
3
|
+
import type { ReviewflowReviewState } from "../types/reviewflow.ts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* One pull request, named the way GitHub names it rather than by a local
|
|
7
|
+
* record: the screen opens colleagues' pull requests too.
|
|
8
|
+
*/
|
|
9
|
+
export interface PullRequestRef {
|
|
10
|
+
/** `owner/repo` */
|
|
11
|
+
ownerRepo: string;
|
|
12
|
+
number: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface PullRequestPerson {
|
|
16
|
+
login: string;
|
|
17
|
+
avatarUrl: string | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface PullRequestLabel {
|
|
21
|
+
name: string;
|
|
22
|
+
/** six hex digits, no `#`, as GitHub stores it */
|
|
23
|
+
color: string;
|
|
24
|
+
description: string | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface PullRequestTeam {
|
|
28
|
+
slug: string;
|
|
29
|
+
name: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type PullRequestRequestedReviewer =
|
|
33
|
+
| { kind: "user"; login: string; avatarUrl: string | null }
|
|
34
|
+
| (PullRequestTeam & { kind: "team" });
|
|
35
|
+
|
|
36
|
+
export type PullRequestReviewState =
|
|
37
|
+
| "approved"
|
|
38
|
+
| "changesRequested"
|
|
39
|
+
| "commented"
|
|
40
|
+
| "dismissed"
|
|
41
|
+
| "pending";
|
|
42
|
+
|
|
43
|
+
/** The latest review of each reviewer, as GitHub folds them. */
|
|
44
|
+
export interface PullRequestReview {
|
|
45
|
+
author: string;
|
|
46
|
+
state: PullRequestReviewState;
|
|
47
|
+
submittedAt: IsoDateTime | null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type PullRequestCheckOutcome = "failed" | "passed" | "pending";
|
|
51
|
+
|
|
52
|
+
export interface PullRequestCheck {
|
|
53
|
+
name: string;
|
|
54
|
+
outcome: PullRequestCheckOutcome;
|
|
55
|
+
/**
|
|
56
|
+
* A check run GitHub reports in progress. Always false for a status context:
|
|
57
|
+
* its `PENDING` is the same whether the job runs or waits on an approval.
|
|
58
|
+
*/
|
|
59
|
+
running: boolean;
|
|
60
|
+
/** what the check says about itself: a status's description, a check run's title */
|
|
61
|
+
description: string | null;
|
|
62
|
+
url: string | null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type PullRequestFileChange =
|
|
66
|
+
| "added"
|
|
67
|
+
| "changed"
|
|
68
|
+
| "copied"
|
|
69
|
+
| "deleted"
|
|
70
|
+
| "modified"
|
|
71
|
+
| "renamed";
|
|
72
|
+
|
|
73
|
+
/** `dismissed`: marked viewed, then changed by a later push. */
|
|
74
|
+
export type PullRequestFileViewed = "dismissed" | "unviewed" | "viewed";
|
|
75
|
+
|
|
76
|
+
export interface PullRequestFile {
|
|
77
|
+
path: string;
|
|
78
|
+
additions: number;
|
|
79
|
+
deletions: number;
|
|
80
|
+
change: PullRequestFileChange;
|
|
81
|
+
viewed: PullRequestFileViewed;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export type PullRequestMergeable = "conflicting" | "mergeable" | "unknown";
|
|
85
|
+
|
|
86
|
+
/** GitHub's `ReactionContent`, camel-cased. */
|
|
87
|
+
export type PullRequestReactionContent =
|
|
88
|
+
| "confused"
|
|
89
|
+
| "eyes"
|
|
90
|
+
| "heart"
|
|
91
|
+
| "hooray"
|
|
92
|
+
| "laugh"
|
|
93
|
+
| "rocket"
|
|
94
|
+
| "thumbsDown"
|
|
95
|
+
| "thumbsUp";
|
|
96
|
+
|
|
97
|
+
export interface PullRequestReaction {
|
|
98
|
+
content: PullRequestReactionContent;
|
|
99
|
+
/** at least 1: a content nobody reacted with is left out */
|
|
100
|
+
count: number;
|
|
101
|
+
viewerHasReacted: boolean;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** What can be reacted to: the description, a comment, a review. */
|
|
105
|
+
export interface PullRequestReactable {
|
|
106
|
+
/** GraphQL node id, what a reaction names */
|
|
107
|
+
id: string;
|
|
108
|
+
reactions: PullRequestReaction[];
|
|
109
|
+
viewerCanReact: boolean;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface PullRequestComment extends PullRequestReactable {
|
|
113
|
+
/** REST id, what an edit or a reply names */
|
|
114
|
+
databaseId: number;
|
|
115
|
+
author: PullRequestPerson;
|
|
116
|
+
/** GitHub-flavoured markdown, verbatim */
|
|
117
|
+
body: string;
|
|
118
|
+
createdAt: IsoDateTime;
|
|
119
|
+
/** null when never edited; the edits are `prRevisions`, read on a press */
|
|
120
|
+
lastEditedAt: IsoDateTime | null;
|
|
121
|
+
url: string;
|
|
122
|
+
viewerCanUpdate: boolean;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export type PullRequestDiffSide = "LEFT" | "RIGHT";
|
|
126
|
+
|
|
127
|
+
/** A thread on the code; `line`/`startLine` are in `side`'s file, as GitHub numbers them. */
|
|
128
|
+
export interface PullRequestThread {
|
|
129
|
+
kind: "thread";
|
|
130
|
+
/** GraphQL node id, what resolving names */
|
|
131
|
+
id: string;
|
|
132
|
+
path: string;
|
|
133
|
+
/** null when the line is gone from the diff (an outdated thread) */
|
|
134
|
+
line: number | null;
|
|
135
|
+
/** the first line of a multi-line comment; null for a single line */
|
|
136
|
+
startLine: number | null;
|
|
137
|
+
side: PullRequestDiffSide;
|
|
138
|
+
/** the side of `startLine`; null for a single line */
|
|
139
|
+
startSide: PullRequestDiffSide | null;
|
|
140
|
+
/** the line when the thread was opened, kept for an outdated thread */
|
|
141
|
+
originalLine: number | null;
|
|
142
|
+
originalStartLine: number | null;
|
|
143
|
+
resolved: boolean;
|
|
144
|
+
outdated: boolean;
|
|
145
|
+
viewerCanResolve: boolean;
|
|
146
|
+
viewerCanUnresolve: boolean;
|
|
147
|
+
viewerCanReply: boolean;
|
|
148
|
+
/** the diff lines above the comment, as GitHub kept them */
|
|
149
|
+
diffHunk: string;
|
|
150
|
+
comments: PullRequestComment[];
|
|
151
|
+
/** every comment of the thread, `comments` included */
|
|
152
|
+
commentsTotal: number;
|
|
153
|
+
createdAt: IsoDateTime;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** What happened to the pull request, as GitHub's page lists it between comments. */
|
|
157
|
+
export type PullRequestEventAction =
|
|
158
|
+
| "assigned"
|
|
159
|
+
| "autoMergeDisabled"
|
|
160
|
+
| "autoMergeEnabled"
|
|
161
|
+
| "baseRefChanged"
|
|
162
|
+
| "closed"
|
|
163
|
+
| "committed"
|
|
164
|
+
| "convertedToDraft"
|
|
165
|
+
| "headRefForcePushed"
|
|
166
|
+
| "labeled"
|
|
167
|
+
| "merged"
|
|
168
|
+
| "readyForReview"
|
|
169
|
+
| "renamed"
|
|
170
|
+
| "reopened"
|
|
171
|
+
| "reviewDismissed"
|
|
172
|
+
| "reviewRequested"
|
|
173
|
+
| "reviewRequestRemoved"
|
|
174
|
+
| "unassigned"
|
|
175
|
+
| "unlabeled";
|
|
176
|
+
|
|
177
|
+
export interface PullRequestEvent {
|
|
178
|
+
kind: "event";
|
|
179
|
+
action: PullRequestEventAction;
|
|
180
|
+
/** null for a deleted account, or a push with no GitHub user */
|
|
181
|
+
actor: PullRequestPerson | null;
|
|
182
|
+
/**
|
|
183
|
+
* What the action names, ready to read: the reviewer or team asked, the
|
|
184
|
+
* assignee, the label, the commit's headline, the new title, the new base.
|
|
185
|
+
*/
|
|
186
|
+
subject: string | null;
|
|
187
|
+
/** `labeled` and `unlabeled`: the label, for its colour */
|
|
188
|
+
label: PullRequestLabel | null;
|
|
189
|
+
/**
|
|
190
|
+
* The second thing some actions name: a commit's or a force-push's short
|
|
191
|
+
* sha, the previous title of `renamed`, the previous base of `baseRefChanged`.
|
|
192
|
+
*/
|
|
193
|
+
detail: string | null;
|
|
194
|
+
createdAt: IsoDateTime;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export interface PullRequestReviewItem extends PullRequestReactable {
|
|
198
|
+
kind: "review";
|
|
199
|
+
databaseId: number;
|
|
200
|
+
author: PullRequestPerson;
|
|
201
|
+
state: PullRequestReviewState;
|
|
202
|
+
body: string;
|
|
203
|
+
createdAt: IsoDateTime;
|
|
204
|
+
lastEditedAt: IsoDateTime | null;
|
|
205
|
+
url: string;
|
|
206
|
+
viewerCanUpdate: boolean;
|
|
207
|
+
/** the head the review was made on */
|
|
208
|
+
commitOid: string | null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* The conversation as GitHub's page shows it, oldest first: comments, reviews
|
|
213
|
+
* that said something (a verdict or a body), threads on the code, and events.
|
|
214
|
+
*/
|
|
215
|
+
export type PullRequestTimelineItem =
|
|
216
|
+
| PullRequestEvent
|
|
217
|
+
| PullRequestReviewItem
|
|
218
|
+
| PullRequestThread
|
|
219
|
+
| (PullRequestComment & { kind: "comment" });
|
|
220
|
+
|
|
221
|
+
export interface PullRequestCommit {
|
|
222
|
+
oid: string;
|
|
223
|
+
/** the first parent: with `oid`, the commit's own diff; null for a root commit */
|
|
224
|
+
parentOid: string | null;
|
|
225
|
+
/** more than one parent: its diff against `parentOid` carries what was merged in */
|
|
226
|
+
merge: boolean;
|
|
227
|
+
/** the message's first line */
|
|
228
|
+
headline: string;
|
|
229
|
+
/** the rest of the message, empty when there is none */
|
|
230
|
+
body: string;
|
|
231
|
+
/** the GitHub user, else the git author's name with no avatar */
|
|
232
|
+
author: PullRequestPerson | null;
|
|
233
|
+
committedAt: IsoDateTime;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** GitHub's `mergeStateStatus`, lowercased: why a merge is or is not offered. */
|
|
237
|
+
export type PullRequestMergeState =
|
|
238
|
+
| "behind"
|
|
239
|
+
| "blocked"
|
|
240
|
+
| "clean"
|
|
241
|
+
| "dirty"
|
|
242
|
+
| "draft"
|
|
243
|
+
| "hasHooks"
|
|
244
|
+
| "unknown"
|
|
245
|
+
| "unstable";
|
|
246
|
+
|
|
247
|
+
export type PullRequestMergeMethod = "merge" | "rebase" | "squash";
|
|
248
|
+
|
|
249
|
+
/** A rebase replays the commits as they are: it writes no commit of its own. */
|
|
250
|
+
export type PullRequestCommittingMergeMethod = Exclude<
|
|
251
|
+
PullRequestMergeMethod,
|
|
252
|
+
"rebase"
|
|
253
|
+
>;
|
|
254
|
+
|
|
255
|
+
export interface PullRequestMergeCommitMessage {
|
|
256
|
+
title: string;
|
|
257
|
+
body: string;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** `id` is also what the draft and viewed mutations name; the reactions are the description's. */
|
|
261
|
+
export interface PullRequestDetail
|
|
262
|
+
extends PullRequestRef, PullRequestReactable {
|
|
263
|
+
url: string;
|
|
264
|
+
title: string;
|
|
265
|
+
/** GitHub-flavoured markdown, verbatim */
|
|
266
|
+
body: string;
|
|
267
|
+
state: PrState;
|
|
268
|
+
author: PullRequestPerson | null;
|
|
269
|
+
headRef: string;
|
|
270
|
+
baseRef: string;
|
|
271
|
+
headSha: string;
|
|
272
|
+
/** the base branch's tip as GitHub holds it; with `headSha`, what the diff is */
|
|
273
|
+
baseSha: string;
|
|
274
|
+
createdAt: IsoDateTime;
|
|
275
|
+
updatedAt: IsoDateTime;
|
|
276
|
+
/** the description's last edit, null when never edited */
|
|
277
|
+
lastEditedAt: IsoDateTime | null;
|
|
278
|
+
additions: number;
|
|
279
|
+
deletions: number;
|
|
280
|
+
changedFiles: number;
|
|
281
|
+
mergeable: PullRequestMergeable;
|
|
282
|
+
mergeState: PullRequestMergeState;
|
|
283
|
+
/** what the repository allows, GitHub's default first */
|
|
284
|
+
mergeMethods: PullRequestMergeMethod[];
|
|
285
|
+
/** what GitHub prefills in its own merge box for the viewer, per method */
|
|
286
|
+
mergeCommitMessages: Record<
|
|
287
|
+
PullRequestCommittingMergeMethod,
|
|
288
|
+
PullRequestMergeCommitMessage
|
|
289
|
+
>;
|
|
290
|
+
/** title, body, labels, assignees, state: GitHub's own answer for the viewer */
|
|
291
|
+
viewerCanUpdate: boolean;
|
|
292
|
+
/** "Update branch" is offered: the base moved and the head can take it */
|
|
293
|
+
viewerCanUpdateBranch: boolean;
|
|
294
|
+
/** the viewer may commit a suggestion to the head branch */
|
|
295
|
+
viewerCanApplySuggestion: boolean;
|
|
296
|
+
reviewDecision: PrReviewDecision;
|
|
297
|
+
labels: PullRequestLabel[];
|
|
298
|
+
assignees: PullRequestPerson[];
|
|
299
|
+
requestedReviewers: PullRequestRequestedReviewer[];
|
|
300
|
+
reviews: PullRequestReview[];
|
|
301
|
+
commitsTotal: number;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** One read of one part of a pull request. */
|
|
305
|
+
export interface PullRequestRead {
|
|
306
|
+
/** when the source answered: the age of what is shown */
|
|
307
|
+
fetchedAt: IsoDateTime;
|
|
308
|
+
/**
|
|
309
|
+
* when the app last knew it current: the answer itself, or later, reviewflow
|
|
310
|
+
* reporting nothing this part shows changed since
|
|
311
|
+
*/
|
|
312
|
+
checkedAt: IsoDateTime;
|
|
313
|
+
/** `reviewflow`: read from reviewflow's copy (the checks), not from GitHub */
|
|
314
|
+
source: "github" | "reviewflow";
|
|
315
|
+
/** what GitHub refused next to a usable answer, verbatim */
|
|
316
|
+
errors: string[];
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** The header, the sidebar and the merge box: what every tab shows. */
|
|
320
|
+
/**
|
|
321
|
+
* Each image url a body wrote, to the camo url GitHub serves it from: see
|
|
322
|
+
* `imageProxiesOf`. Absent from a source that does not render GitHub's html.
|
|
323
|
+
*/
|
|
324
|
+
export type PullRequestImageProxies = Record<string, string>;
|
|
325
|
+
|
|
326
|
+
export interface PullRequestDetailResponse extends PullRequestRead {
|
|
327
|
+
pullRequest: PullRequestDetail;
|
|
328
|
+
/** the `gh` login, whose turn the screen reads gestures for */
|
|
329
|
+
viewer: string;
|
|
330
|
+
/** the images of the description */
|
|
331
|
+
images?: PullRequestImageProxies;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export interface PullRequestChecksResponse extends PullRequestRead {
|
|
335
|
+
/** the head commit's checks, up to the first page */
|
|
336
|
+
checks: PullRequestCheck[];
|
|
337
|
+
/** every check of the head commit, `checks` included */
|
|
338
|
+
checksTotal: number;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export interface PullRequestTimelineResponse extends PullRequestRead {
|
|
342
|
+
timeline: PullRequestTimelineItem[];
|
|
343
|
+
/** the images of every comment and review of `timeline` */
|
|
344
|
+
images?: PullRequestImageProxies;
|
|
345
|
+
/** older items past the page read, left out of `timeline` (the newest are kept) */
|
|
346
|
+
timelineTruncated: boolean;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export interface PullRequestCommitsResponse extends PullRequestRead {
|
|
350
|
+
/** oldest first, up to the first page; `commitsTotal` is the total */
|
|
351
|
+
commits: PullRequestCommit[];
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** The viewer's newest submitted review, whatever its verdict. */
|
|
355
|
+
export interface PullRequestViewerLastReview {
|
|
356
|
+
/** the head it was submitted on: "changes since my last review" start there */
|
|
357
|
+
oid: string;
|
|
358
|
+
submittedAt: string | null;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export interface PullRequestFilesResponse extends PullRequestRead {
|
|
362
|
+
/** up to the first page; `changedFiles` is the total */
|
|
363
|
+
files: PullRequestFile[];
|
|
364
|
+
viewerLastReview: PullRequestViewerLastReview | null;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export interface PullRequestPartResponses {
|
|
368
|
+
detail: PullRequestDetailResponse;
|
|
369
|
+
checks: PullRequestChecksResponse;
|
|
370
|
+
timeline: PullRequestTimelineResponse;
|
|
371
|
+
commits: PullRequestCommitsResponse;
|
|
372
|
+
files: PullRequestFilesResponse;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export type PullRequestPart = keyof PullRequestPartResponses;
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* `GET /api/prs/detail?repo=<owner/repo>&number=<n>&part=<part>` — one part of
|
|
379
|
+
* a pull request, read from GitHub when the screen asks for it and never
|
|
380
|
+
* polled. Each is its own query so the screen waits for none it
|
|
381
|
+
* does not show: GitHub resolves a query's fields one after the other.
|
|
382
|
+
*
|
|
383
|
+
* The answer is kept as the pull request's last read, served by `prCached`.
|
|
384
|
+
*/
|
|
385
|
+
export type PullRequestPartResponse<Part extends PullRequestPart> =
|
|
386
|
+
PullRequestPartResponses[Part];
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* `GET /api/prs/cached?repo=<owner/repo>&number=<n>` — the last read of each
|
|
390
|
+
* part, from the daemon's database, shown while GitHub is read again. Never
|
|
391
|
+
* served as current: `fetchedAt` says how old each part is.
|
|
392
|
+
*/
|
|
393
|
+
export interface PullRequestCachedResponse {
|
|
394
|
+
parts: Partial<PullRequestPartResponses>;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* A `pullRequestChanged` frame on `/api/events`: reviewflow reported a change
|
|
399
|
+
* of this pull request, and these parts of its screen are stale.
|
|
400
|
+
*/
|
|
401
|
+
export interface PullRequestChange extends PullRequestRef {
|
|
402
|
+
parts: PullRequestPart[];
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* `POST /api/prs/cache/clear` — drops the last read of every pull request:
|
|
407
|
+
* the next opening of each waits for GitHub, as a first one does.
|
|
408
|
+
*/
|
|
409
|
+
export interface ClearPullRequestCacheResponse {
|
|
410
|
+
/** pull requests whose last read was dropped */
|
|
411
|
+
cleared: number;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* What reviewflow holds of a pull request, enough to head the screen while
|
|
416
|
+
* GitHub is read. Never a substitute for the detail: no body, no timeline.
|
|
417
|
+
*/
|
|
418
|
+
export interface PullRequestSummary extends PullRequestRef {
|
|
419
|
+
title: string;
|
|
420
|
+
url: string;
|
|
421
|
+
state: PrState | null;
|
|
422
|
+
reviewState: ReviewflowReviewState | null;
|
|
423
|
+
author: string | null;
|
|
424
|
+
headRef: string | null;
|
|
425
|
+
baseRef: string | null;
|
|
426
|
+
/** null until reviewflow knows it; with `baseSha`, enough to read the diff early */
|
|
427
|
+
headSha: string | null;
|
|
428
|
+
baseSha: string | null;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* `GET /api/prs/summary?repo=<owner/repo>&number=<n>` — reviewflow's copy, null
|
|
433
|
+
* without a token, for a pull request reviewflow never handled, or when it
|
|
434
|
+
* cannot be reached: the screen then waits for the detail, as it always did.
|
|
435
|
+
*/
|
|
436
|
+
export interface PullRequestSummaryResponse {
|
|
437
|
+
summary: PullRequestSummary | null;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* `GET /api/prs/candidates?repo=<owner/repo>&number=<n>` — what the editors
|
|
442
|
+
* offer, read apart from the detail so the screen does not wait for them: the
|
|
443
|
+
* repository's assignable users alone cost GitHub about half a second.
|
|
444
|
+
*/
|
|
445
|
+
export interface PullRequestCandidatesResponse {
|
|
446
|
+
/** what the label editor offers */
|
|
447
|
+
repoLabels: PullRequestLabel[];
|
|
448
|
+
/**
|
|
449
|
+
* GitHub's suggested reviewers, in GitHub's order. Neither list holds the
|
|
450
|
+
* author — GitHub refuses a review request to the author.
|
|
451
|
+
*/
|
|
452
|
+
suggestedReviewers: PullRequestPerson[];
|
|
453
|
+
/** the repository's assignable users not suggested, by login */
|
|
454
|
+
reviewerCandidates: PullRequestPerson[];
|
|
455
|
+
/**
|
|
456
|
+
* The organisation's teams the viewer can see, by name; empty for a
|
|
457
|
+
* repository a user owns.
|
|
458
|
+
*/
|
|
459
|
+
teamCandidates: PullRequestTeam[];
|
|
460
|
+
/** the repository's assignable users, the author included */
|
|
461
|
+
assigneeCandidates: PullRequestPerson[];
|
|
462
|
+
/** what GitHub refused next to a usable answer, verbatim */
|
|
463
|
+
errors: string[];
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export interface PullRequestTextLine {
|
|
467
|
+
kind: "added" | "context" | "removed";
|
|
468
|
+
text: string;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/** One version of a description, a comment or a review, as GitHub keeps it. */
|
|
472
|
+
export interface PullRequestRevision {
|
|
473
|
+
editedAt: IsoDateTime;
|
|
474
|
+
/** null for a deleted account */
|
|
475
|
+
editor: PullRequestPerson | null;
|
|
476
|
+
/** the text as first posted */
|
|
477
|
+
original: boolean;
|
|
478
|
+
/** the whole text of this version; null once its author deleted it from the history */
|
|
479
|
+
body: string | null;
|
|
480
|
+
deletedBy: string | null;
|
|
481
|
+
/**
|
|
482
|
+
* Against the next older revision listed with a text, every line kept; null
|
|
483
|
+
* when there is none (the original, or the oldest listed).
|
|
484
|
+
*/
|
|
485
|
+
changes: PullRequestTextLine[] | null;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* `GET /api/prs/revisions?repo=<owner/repo>&number=<n>&subject=<id>` — the
|
|
490
|
+
* edit history of a `PullRequestReactable`'s `id`, read from GitHub on a press
|
|
491
|
+
* and never kept: the timeline carries only `lastEditedAt`, since every
|
|
492
|
+
* version of every comment would weigh the query far more than it is opened.
|
|
493
|
+
*/
|
|
494
|
+
export interface PullRequestRevisionsResponse {
|
|
495
|
+
/** newest first, up to the first page */
|
|
496
|
+
revisions: PullRequestRevision[];
|
|
497
|
+
/** every revision, the original included when GitHub kept it */
|
|
498
|
+
revisionsTotal: number;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* One change per request, so a refusal names the change it refused. Labels and
|
|
503
|
+
* reviewers carry both directions because the editor submits a selection, and
|
|
504
|
+
* the difference is what GitHub is asked for.
|
|
505
|
+
*/
|
|
506
|
+
export type PullRequestEdit =
|
|
507
|
+
| {
|
|
508
|
+
kind: "applySuggestions";
|
|
509
|
+
/** refused when the head moved since the screen read it */
|
|
510
|
+
expectedHeadSha: string;
|
|
511
|
+
/** one commit for all of them, as GitHub's batch */
|
|
512
|
+
suggestions: PullRequestSuggestionRef[];
|
|
513
|
+
headline: string;
|
|
514
|
+
/** the daemon appends a `Co-authored-by` trailer per suggester */
|
|
515
|
+
body: string;
|
|
516
|
+
}
|
|
517
|
+
| {
|
|
518
|
+
kind: "editComment";
|
|
519
|
+
target: "issueComment" | "review" | "reviewComment";
|
|
520
|
+
databaseId: number;
|
|
521
|
+
body: string;
|
|
522
|
+
}
|
|
523
|
+
| {
|
|
524
|
+
kind: "inlineComment";
|
|
525
|
+
commitId: string;
|
|
526
|
+
comment: PullRequestDraftComment;
|
|
527
|
+
}
|
|
528
|
+
| {
|
|
529
|
+
kind: "merge";
|
|
530
|
+
method: PullRequestMergeMethod;
|
|
531
|
+
/** refused by GitHub when the head moved since the screen read it */
|
|
532
|
+
expectedHeadSha: string;
|
|
533
|
+
/** absent: GitHub writes its own; never with a rebase */
|
|
534
|
+
commitMessage?: PullRequestMergeCommitMessage;
|
|
535
|
+
}
|
|
536
|
+
| {
|
|
537
|
+
kind: "reaction";
|
|
538
|
+
/** a `PullRequestReactable`'s `id` */
|
|
539
|
+
subjectId: string;
|
|
540
|
+
content: PullRequestReactionContent;
|
|
541
|
+
/** false removes the viewer's own reaction */
|
|
542
|
+
reacted: boolean;
|
|
543
|
+
}
|
|
544
|
+
| {
|
|
545
|
+
kind: "reply";
|
|
546
|
+
/** the `databaseId` of a comment of the thread */
|
|
547
|
+
inReplyTo: number;
|
|
548
|
+
body: string;
|
|
549
|
+
}
|
|
550
|
+
| {
|
|
551
|
+
kind: "review";
|
|
552
|
+
event: PullRequestReviewEvent;
|
|
553
|
+
body: string;
|
|
554
|
+
/** the head the review is about; GitHub refuses one that is not the PR's */
|
|
555
|
+
commitId: string;
|
|
556
|
+
comments: PullRequestDraftComment[];
|
|
557
|
+
}
|
|
558
|
+
| {
|
|
559
|
+
kind: "reviewers";
|
|
560
|
+
/** a login that already reviewed is asked again: GitHub's re-request */
|
|
561
|
+
add: string[];
|
|
562
|
+
remove: string[];
|
|
563
|
+
/** team slugs */
|
|
564
|
+
addTeams: string[];
|
|
565
|
+
removeTeams: string[];
|
|
566
|
+
}
|
|
567
|
+
| { kind: "assignees"; add: string[]; remove: string[] }
|
|
568
|
+
| { kind: "body"; body: string }
|
|
569
|
+
| { kind: "comment"; body: string }
|
|
570
|
+
| { kind: "draft"; draft: boolean }
|
|
571
|
+
| { kind: "labels"; add: string[]; remove: string[] }
|
|
572
|
+
| { kind: "resolveThread"; threadId: string; resolved: boolean }
|
|
573
|
+
| { kind: "state"; state: "closed" | "open" }
|
|
574
|
+
| { kind: "title"; title: string }
|
|
575
|
+
| { kind: "updateBranch"; expectedHeadSha: string }
|
|
576
|
+
| { kind: "viewed"; path: string; viewed: boolean };
|
|
577
|
+
|
|
578
|
+
export type PullRequestReviewEvent = "approve" | "comment" | "requestChanges";
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* A comment on lines of the diff, before it is sent: alone (`inlineComment`)
|
|
582
|
+
* or with a review. A suggestion is a ```` ```suggestion ```` block in `body`.
|
|
583
|
+
*/
|
|
584
|
+
export interface PullRequestDraftComment {
|
|
585
|
+
path: string;
|
|
586
|
+
side: PullRequestDiffSide;
|
|
587
|
+
line: number;
|
|
588
|
+
/** the first line of a range, on `startSide`; null for one line */
|
|
589
|
+
startLine: number | null;
|
|
590
|
+
startSide: PullRequestDiffSide | null;
|
|
591
|
+
body: string;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* `POST /api/prs/edit` — writes to GitHub under the operator's `gh` login, the
|
|
596
|
+
* one route that does. The answer carries no pull request: the screen reads it
|
|
597
|
+
* again, so what it shows is GitHub's state rather than the request echoed.
|
|
598
|
+
*/
|
|
599
|
+
export interface EditPullRequestRequest extends PullRequestRef {
|
|
600
|
+
edit: PullRequestEdit;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/** A suggestion is committed from its comment, on its thread's lines. */
|
|
604
|
+
export interface PullRequestSuggestionRef {
|
|
605
|
+
threadId: string;
|
|
606
|
+
/** the comment's GraphQL node id */
|
|
607
|
+
commentId: string;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
export interface EditPullRequestResponse {
|
|
611
|
+
edited: PullRequestEdit["kind"];
|
|
612
|
+
/**
|
|
613
|
+
* What failed past GitHub's write, which landed: resolving the threads of
|
|
614
|
+
* committed suggestions. Null otherwise.
|
|
615
|
+
*/
|
|
616
|
+
followUpError: string | null;
|
|
617
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export interface InsertedCheckoutEntry {
|
|
2
|
+
/** the config text with the entry in `repos` */
|
|
3
|
+
source: string;
|
|
4
|
+
/** where the path goes: between the quotes of the entry's value */
|
|
5
|
+
cursor: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
9
|
+
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* `"owner/repo": ""` added to `repos` of the app's config text, so an editor
|
|
13
|
+
* can open on it with the cursor where the checkout path is typed. Nothing is
|
|
14
|
+
* saved here. An entry already present (names are compared case-insensitively,
|
|
15
|
+
* as GitHub's are) is left as it is, with the cursor in its value. The text is
|
|
16
|
+
* rewritten as two-space json, other keys kept; a blank text becomes a new
|
|
17
|
+
* config. `null` when the text is not a json object whose `repos` is an object:
|
|
18
|
+
* the user's own text is not guessed at.
|
|
19
|
+
*/
|
|
20
|
+
export const insertCheckoutEntry = (
|
|
21
|
+
source: string,
|
|
22
|
+
ownerRepo: string,
|
|
23
|
+
): InsertedCheckoutEntry | null => {
|
|
24
|
+
let parsed: unknown;
|
|
25
|
+
if (source.trim() === "") {
|
|
26
|
+
parsed = {};
|
|
27
|
+
} else {
|
|
28
|
+
try {
|
|
29
|
+
parsed = JSON.parse(source);
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (!isRecord(parsed)) return null;
|
|
35
|
+
const repos = parsed.repos ?? {};
|
|
36
|
+
if (!isRecord(repos)) return null;
|
|
37
|
+
|
|
38
|
+
const wanted = ownerRepo.toLowerCase();
|
|
39
|
+
const existing = Object.keys(repos).find(
|
|
40
|
+
(key) => key.toLowerCase() === wanted,
|
|
41
|
+
);
|
|
42
|
+
const key = existing ?? ownerRepo;
|
|
43
|
+
const next = existing === undefined ? { ...repos, [key]: "" } : repos;
|
|
44
|
+
const text = `${JSON.stringify({ ...parsed, repos: next }, null, 2)}\n`;
|
|
45
|
+
|
|
46
|
+
// the value follows the key on the same line; the same key may appear
|
|
47
|
+
// earlier under another top-level key
|
|
48
|
+
const marker = `${JSON.stringify(key)}: "`;
|
|
49
|
+
const reposAt = text.indexOf('\n "repos": ');
|
|
50
|
+
const at = text.indexOf(marker, reposAt);
|
|
51
|
+
if (at === -1) return null;
|
|
52
|
+
const value = at + marker.length;
|
|
53
|
+
return { source: text, cursor: value };
|
|
54
|
+
};
|