@kitlangton/ghui 0.2.1 → 0.3.2
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/README.md +1 -4
- package/bin/ghui.js +6 -1
- package/dist/index.js +9419 -0
- package/package.json +7 -4
- package/src/App.tsx +0 -2604
- package/src/appCommands.ts +0 -384
- package/src/commands.ts +0 -73
- package/src/config.ts +0 -28
- package/src/date.ts +0 -20
- package/src/domain.ts +0 -139
- package/src/errors.ts +0 -10
- package/src/index.tsx +0 -50
- package/src/keyboard/opentuiAdapter.ts +0 -43
- package/src/keymap/all.ts +0 -103
- package/src/keymap/closeModal.ts +0 -13
- package/src/keymap/commandPalette.ts +0 -16
- package/src/keymap/commentModal.ts +0 -73
- package/src/keymap/commentThreadModal.ts +0 -24
- package/src/keymap/detailView.ts +0 -30
- package/src/keymap/diffView.ts +0 -91
- package/src/keymap/filterMode.ts +0 -13
- package/src/keymap/helpers.ts +0 -24
- package/src/keymap/labelModal.ts +0 -16
- package/src/keymap/listNav.ts +0 -119
- package/src/keymap/mergeModal.ts +0 -23
- package/src/keymap/openRepositoryModal.ts +0 -13
- package/src/keymap/themeModal.ts +0 -49
- package/src/mergeActions.ts +0 -88
- package/src/observability.ts +0 -46
- package/src/pullRequestCache.ts +0 -19
- package/src/pullRequestViews.ts +0 -45
- package/src/services/BrowserOpener.ts +0 -22
- package/src/services/Clipboard.ts +0 -46
- package/src/services/CommandRunner.ts +0 -91
- package/src/services/GitHubService.ts +0 -741
- package/src/services/MockGitHubService.ts +0 -181
- package/src/themeStore.ts +0 -60
- package/src/ui/CommandPalette.tsx +0 -216
- package/src/ui/DetailsPane.tsx +0 -656
- package/src/ui/FooterHints.tsx +0 -90
- package/src/ui/LoadingLogo.tsx +0 -75
- package/src/ui/PullRequestDiffPane.tsx +0 -248
- package/src/ui/PullRequestList.tsx +0 -210
- package/src/ui/colors.ts +0 -814
- package/src/ui/commentEditor.ts +0 -126
- package/src/ui/comments.tsx +0 -143
- package/src/ui/diff.ts +0 -650
- package/src/ui/diffStats.tsx +0 -25
- package/src/ui/modals.tsx +0 -614
- package/src/ui/primitives.tsx +0 -205
- package/src/ui/pullRequests.ts +0 -106
- package/src/ui/singleLineInput.ts +0 -26
- package/src/ui/spinner.ts +0 -1
|
@@ -1,741 +0,0 @@
|
|
|
1
|
-
import { Context, Effect, Layer, Schema } from "effect"
|
|
2
|
-
import { config } from "../config.js"
|
|
3
|
-
import { DiffCommentSide, pullRequestQueueSearchQualifier, type CheckItem, type CreatePullRequestCommentInput, type ListPullRequestPageInput, type Mergeable, type PullRequestConversationItem, type PullRequestItem, type PullRequestMergeAction, type PullRequestMergeInfo, type PullRequestPage, type PullRequestQueueMode, type PullRequestReviewComment, type ReviewStatus } from "../domain.js"
|
|
4
|
-
import { getMergeActionDefinition } from "../mergeActions.js"
|
|
5
|
-
import { CommandError, CommandRunner, type JsonParseError } from "./CommandRunner.js"
|
|
6
|
-
|
|
7
|
-
const NullableString = Schema.NullOr(Schema.String)
|
|
8
|
-
const OptionalNullableString = Schema.optionalKey(NullableString)
|
|
9
|
-
const OptionalNullableNumber = Schema.optionalKey(Schema.NullOr(Schema.Number))
|
|
10
|
-
|
|
11
|
-
const RawCheckContextSchema = Schema.Union([
|
|
12
|
-
Schema.Struct({
|
|
13
|
-
__typename: Schema.tag("CheckRun"),
|
|
14
|
-
name: OptionalNullableString,
|
|
15
|
-
status: OptionalNullableString,
|
|
16
|
-
conclusion: OptionalNullableString,
|
|
17
|
-
}),
|
|
18
|
-
Schema.Struct({
|
|
19
|
-
__typename: Schema.tag("StatusContext"),
|
|
20
|
-
context: OptionalNullableString,
|
|
21
|
-
state: OptionalNullableString,
|
|
22
|
-
}),
|
|
23
|
-
]).pipe(Schema.toTaggedUnion("__typename"))
|
|
24
|
-
|
|
25
|
-
const RawAuthorSchema = Schema.Struct({ login: Schema.String })
|
|
26
|
-
const RawRepositorySchema = Schema.Struct({ nameWithOwner: Schema.String })
|
|
27
|
-
const RawLabelSchema = Schema.Struct({
|
|
28
|
-
name: Schema.String,
|
|
29
|
-
color: OptionalNullableString,
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
const RawStatusCheckRollupSchema = Schema.Struct({
|
|
33
|
-
contexts: Schema.Struct({ nodes: Schema.Array(RawCheckContextSchema) }),
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
const RawPullRequestSummaryFields = {
|
|
37
|
-
number: Schema.Number,
|
|
38
|
-
title: Schema.String,
|
|
39
|
-
isDraft: Schema.Boolean,
|
|
40
|
-
reviewDecision: NullableString,
|
|
41
|
-
autoMergeRequest: Schema.NullOr(Schema.Unknown),
|
|
42
|
-
state: Schema.String,
|
|
43
|
-
merged: Schema.Boolean,
|
|
44
|
-
createdAt: Schema.String,
|
|
45
|
-
closedAt: OptionalNullableString,
|
|
46
|
-
url: Schema.String,
|
|
47
|
-
author: RawAuthorSchema,
|
|
48
|
-
headRefOid: Schema.String,
|
|
49
|
-
repository: RawRepositorySchema,
|
|
50
|
-
} as const
|
|
51
|
-
|
|
52
|
-
const RawPullRequestSummaryNodeSchema = Schema.Struct({
|
|
53
|
-
...RawPullRequestSummaryFields,
|
|
54
|
-
statusCheckRollup: Schema.optionalKey(Schema.NullOr(RawStatusCheckRollupSchema)),
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
const RawPullRequestNodeSchema = Schema.Struct({
|
|
58
|
-
...RawPullRequestSummaryFields,
|
|
59
|
-
body: Schema.String,
|
|
60
|
-
labels: Schema.Struct({ nodes: Schema.Array(RawLabelSchema) }),
|
|
61
|
-
additions: Schema.Number,
|
|
62
|
-
deletions: Schema.Number,
|
|
63
|
-
changedFiles: Schema.Number,
|
|
64
|
-
statusCheckRollup: Schema.optionalKey(Schema.NullOr(RawStatusCheckRollupSchema)),
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
const PullRequestDetailResponseSchema = Schema.Struct({
|
|
68
|
-
data: Schema.Struct({
|
|
69
|
-
repository: Schema.NullOr(Schema.Struct({
|
|
70
|
-
pullRequest: Schema.NullOr(RawPullRequestNodeSchema),
|
|
71
|
-
})),
|
|
72
|
-
}),
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
const PageInfoSchema = Schema.Struct({
|
|
76
|
-
hasNextPage: Schema.Boolean,
|
|
77
|
-
endCursor: NullableString,
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
const SearchResponseSchema = <Item extends Schema.Top>(item: Item) =>
|
|
81
|
-
Schema.Struct({
|
|
82
|
-
data: Schema.Struct({
|
|
83
|
-
search: Schema.Struct({
|
|
84
|
-
nodes: Schema.Array(Schema.NullOr(item)),
|
|
85
|
-
pageInfo: PageInfoSchema,
|
|
86
|
-
}),
|
|
87
|
-
}),
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
const RepositoryPullRequestsResponseSchema = Schema.Struct({
|
|
91
|
-
data: Schema.Struct({
|
|
92
|
-
repository: Schema.NullOr(Schema.Struct({
|
|
93
|
-
pullRequests: Schema.Struct({
|
|
94
|
-
nodes: Schema.Array(Schema.NullOr(RawPullRequestSummaryNodeSchema)),
|
|
95
|
-
pageInfo: PageInfoSchema,
|
|
96
|
-
}),
|
|
97
|
-
})),
|
|
98
|
-
}),
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
const ViewerSchema = Schema.Struct({ login: Schema.String })
|
|
102
|
-
|
|
103
|
-
const MergeInfoResponseSchema = Schema.Struct({
|
|
104
|
-
number: Schema.Number,
|
|
105
|
-
title: Schema.String,
|
|
106
|
-
state: Schema.String,
|
|
107
|
-
isDraft: Schema.Boolean,
|
|
108
|
-
mergeable: Schema.String,
|
|
109
|
-
reviewDecision: NullableString,
|
|
110
|
-
autoMergeRequest: Schema.NullOr(Schema.Unknown),
|
|
111
|
-
statusCheckRollup: Schema.Array(RawCheckContextSchema),
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
const PullRequestCommentSchema = Schema.Struct({
|
|
115
|
-
id: Schema.optionalKey(Schema.NullOr(Schema.Union([Schema.Number, Schema.String]))),
|
|
116
|
-
node_id: OptionalNullableString,
|
|
117
|
-
body: OptionalNullableString,
|
|
118
|
-
html_url: OptionalNullableString,
|
|
119
|
-
url: OptionalNullableString,
|
|
120
|
-
created_at: OptionalNullableString,
|
|
121
|
-
user: Schema.optionalKey(Schema.NullOr(Schema.Struct({
|
|
122
|
-
login: OptionalNullableString,
|
|
123
|
-
}))),
|
|
124
|
-
path: OptionalNullableString,
|
|
125
|
-
line: OptionalNullableNumber,
|
|
126
|
-
original_line: OptionalNullableNumber,
|
|
127
|
-
side: Schema.optionalKey(Schema.NullOr(DiffCommentSide)),
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
const PullRequestFileSchema = Schema.Struct({
|
|
131
|
-
filename: Schema.String,
|
|
132
|
-
previous_filename: OptionalNullableString,
|
|
133
|
-
status: OptionalNullableString,
|
|
134
|
-
patch: OptionalNullableString,
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
const CommentsResponseSchema = Schema.Union([
|
|
138
|
-
Schema.Array(PullRequestCommentSchema),
|
|
139
|
-
Schema.Array(Schema.Array(PullRequestCommentSchema)),
|
|
140
|
-
])
|
|
141
|
-
|
|
142
|
-
const PullRequestFilesResponseSchema = Schema.Union([
|
|
143
|
-
Schema.Array(PullRequestFileSchema),
|
|
144
|
-
Schema.Array(Schema.Array(PullRequestFileSchema)),
|
|
145
|
-
])
|
|
146
|
-
|
|
147
|
-
const RepoLabelsResponseSchema = Schema.Array(Schema.Struct({
|
|
148
|
-
name: Schema.String,
|
|
149
|
-
color: Schema.String,
|
|
150
|
-
}))
|
|
151
|
-
|
|
152
|
-
type RawPullRequestSummaryNode = Schema.Schema.Type<typeof RawPullRequestSummaryNodeSchema>
|
|
153
|
-
type RawPullRequestNode = Schema.Schema.Type<typeof RawPullRequestNodeSchema>
|
|
154
|
-
type RawCheckContext = Schema.Schema.Type<typeof RawCheckContextSchema>
|
|
155
|
-
type RawPullRequestComment = Schema.Schema.Type<typeof PullRequestCommentSchema>
|
|
156
|
-
type RawPullRequestFile = Schema.Schema.Type<typeof PullRequestFileSchema>
|
|
157
|
-
|
|
158
|
-
type SearchResponse<Item> = {
|
|
159
|
-
readonly data: {
|
|
160
|
-
readonly search: {
|
|
161
|
-
readonly nodes: readonly (Item | null)[]
|
|
162
|
-
readonly pageInfo: {
|
|
163
|
-
readonly hasNextPage: boolean
|
|
164
|
-
readonly endCursor: string | null
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
type PullRequestConnection<Item> = {
|
|
171
|
-
readonly nodes: readonly (Item | null)[]
|
|
172
|
-
readonly pageInfo: {
|
|
173
|
-
readonly hasNextPage: boolean
|
|
174
|
-
readonly endCursor: string | null
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const STATUS_CHECK_FRAGMENT = `
|
|
179
|
-
statusCheckRollup {
|
|
180
|
-
contexts(first: 100) {
|
|
181
|
-
nodes {
|
|
182
|
-
__typename
|
|
183
|
-
... on CheckRun { name status conclusion }
|
|
184
|
-
... on StatusContext { context state }
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}`
|
|
188
|
-
|
|
189
|
-
const SUMMARY_FIELDS_FRAGMENT = `
|
|
190
|
-
number
|
|
191
|
-
title
|
|
192
|
-
isDraft
|
|
193
|
-
reviewDecision
|
|
194
|
-
autoMergeRequest { enabledAt }
|
|
195
|
-
state
|
|
196
|
-
merged
|
|
197
|
-
createdAt
|
|
198
|
-
closedAt
|
|
199
|
-
url
|
|
200
|
-
author { login }
|
|
201
|
-
headRefOid
|
|
202
|
-
repository { nameWithOwner }${STATUS_CHECK_FRAGMENT}`
|
|
203
|
-
|
|
204
|
-
const DETAIL_FIELDS_FRAGMENT = `
|
|
205
|
-
number
|
|
206
|
-
title
|
|
207
|
-
body
|
|
208
|
-
isDraft
|
|
209
|
-
reviewDecision
|
|
210
|
-
autoMergeRequest { enabledAt }
|
|
211
|
-
additions
|
|
212
|
-
deletions
|
|
213
|
-
changedFiles
|
|
214
|
-
state
|
|
215
|
-
merged
|
|
216
|
-
createdAt
|
|
217
|
-
closedAt
|
|
218
|
-
url
|
|
219
|
-
author { login }
|
|
220
|
-
headRefOid
|
|
221
|
-
repository { nameWithOwner }
|
|
222
|
-
labels(first: 20) { nodes { name color } }${STATUS_CHECK_FRAGMENT}`
|
|
223
|
-
|
|
224
|
-
const pullRequestSearchQuery = `
|
|
225
|
-
query PullRequests($searchQuery: String!, $first: Int!, $after: String) {
|
|
226
|
-
search(query: $searchQuery, type: ISSUE, first: $first, after: $after) {
|
|
227
|
-
nodes {
|
|
228
|
-
... on PullRequest {${DETAIL_FIELDS_FRAGMENT}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
pageInfo { hasNextPage endCursor }
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
`
|
|
235
|
-
|
|
236
|
-
const pullRequestDetailQuery = `
|
|
237
|
-
query PullRequest($owner: String!, $name: String!, $number: Int!) {
|
|
238
|
-
repository(owner: $owner, name: $name) {
|
|
239
|
-
pullRequest(number: $number) {${DETAIL_FIELDS_FRAGMENT}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
`
|
|
244
|
-
|
|
245
|
-
const pullRequestSummarySearchQuery = `
|
|
246
|
-
query PullRequests($searchQuery: String!, $first: Int!, $after: String) {
|
|
247
|
-
search(query: $searchQuery, type: ISSUE, first: $first, after: $after) {
|
|
248
|
-
nodes {
|
|
249
|
-
... on PullRequest {${SUMMARY_FIELDS_FRAGMENT}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
pageInfo { hasNextPage endCursor }
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
`
|
|
256
|
-
|
|
257
|
-
const repositoryPullRequestsQuery = `
|
|
258
|
-
query RepositoryPullRequests($owner: String!, $name: String!, $first: Int!, $after: String) {
|
|
259
|
-
repository(owner: $owner, name: $name) {
|
|
260
|
-
pullRequests(states: OPEN, first: $first, after: $after, orderBy: { field: UPDATED_AT, direction: DESC }) {
|
|
261
|
-
nodes {${SUMMARY_FIELDS_FRAGMENT}
|
|
262
|
-
}
|
|
263
|
-
pageInfo { hasNextPage endCursor }
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
`
|
|
268
|
-
|
|
269
|
-
const normalizeDate = (value: string | null | undefined) => {
|
|
270
|
-
if (!value || value.startsWith("0001-01-01")) return null
|
|
271
|
-
return new Date(value)
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const getPullRequestState = (item: { readonly state: string; readonly merged: boolean }): PullRequestItem["state"] =>
|
|
275
|
-
item.merged ? "merged" : item.state.toLowerCase() === "open" ? "open" : "closed"
|
|
276
|
-
|
|
277
|
-
const REVIEW_STATUS_BY_DECISION: Record<string, ReviewStatus> = {
|
|
278
|
-
APPROVED: "approved",
|
|
279
|
-
CHANGES_REQUESTED: "changes",
|
|
280
|
-
REVIEW_REQUIRED: "review",
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const getReviewStatus = (item: { readonly isDraft: boolean; readonly reviewDecision: string | null }): ReviewStatus => {
|
|
284
|
-
if (item.isDraft) return "draft"
|
|
285
|
-
if (item.reviewDecision) return REVIEW_STATUS_BY_DECISION[item.reviewDecision] ?? "none"
|
|
286
|
-
return "none"
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const CHECK_STATUS_BY_RAW: Record<string, CheckItem["status"]> = {
|
|
290
|
-
COMPLETED: "completed",
|
|
291
|
-
IN_PROGRESS: "in_progress",
|
|
292
|
-
QUEUED: "queued",
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const CHECK_CONCLUSION_BY_RAW: Record<string, NonNullable<CheckItem["conclusion"]>> = {
|
|
296
|
-
SUCCESS: "success",
|
|
297
|
-
FAILURE: "failure",
|
|
298
|
-
ERROR: "failure",
|
|
299
|
-
NEUTRAL: "neutral",
|
|
300
|
-
SKIPPED: "skipped",
|
|
301
|
-
CANCELLED: "cancelled",
|
|
302
|
-
TIMED_OUT: "timed_out",
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const normalizeCheckStatus = (raw: string | null | undefined): CheckItem["status"] =>
|
|
306
|
-
raw ? CHECK_STATUS_BY_RAW[raw] ?? "pending" : "pending"
|
|
307
|
-
|
|
308
|
-
const normalizeCheckConclusion = (raw: string | null | undefined): CheckItem["conclusion"] =>
|
|
309
|
-
raw ? CHECK_CONCLUSION_BY_RAW[raw] ?? null : null
|
|
310
|
-
|
|
311
|
-
const getContextStatus = (context: RawCheckContext): CheckItem["status"] =>
|
|
312
|
-
RawCheckContextSchema.match(context, {
|
|
313
|
-
CheckRun: (run) => normalizeCheckStatus(run.status),
|
|
314
|
-
StatusContext: (status) => status.state === "PENDING" ? "in_progress" : "completed",
|
|
315
|
-
})
|
|
316
|
-
|
|
317
|
-
const STATUS_CONTEXT_CONCLUSION: Record<string, NonNullable<CheckItem["conclusion"]>> = {
|
|
318
|
-
SUCCESS: "success",
|
|
319
|
-
FAILURE: "failure",
|
|
320
|
-
ERROR: "failure",
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const getContextConclusion = (context: RawCheckContext): CheckItem["conclusion"] =>
|
|
324
|
-
RawCheckContextSchema.match(context, {
|
|
325
|
-
CheckRun: (run) => normalizeCheckConclusion(run.conclusion),
|
|
326
|
-
StatusContext: (status) => (status.state ? STATUS_CONTEXT_CONCLUSION[status.state] : null) ?? null,
|
|
327
|
-
})
|
|
328
|
-
|
|
329
|
-
const getCheckInfoFromContexts = (contexts: readonly RawCheckContext[]): Pick<PullRequestItem, "checkStatus" | "checkSummary" | "checks"> => {
|
|
330
|
-
if (contexts.length === 0) {
|
|
331
|
-
return { checkStatus: "none", checkSummary: null, checks: [] }
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
let completed = 0
|
|
335
|
-
let successful = 0
|
|
336
|
-
let pending = false
|
|
337
|
-
let failing = false
|
|
338
|
-
const checks: CheckItem[] = []
|
|
339
|
-
|
|
340
|
-
for (const check of contexts) {
|
|
341
|
-
const name = check.__typename === "CheckRun" ? check.name ?? "check" : check.context ?? "check"
|
|
342
|
-
const status = getContextStatus(check)
|
|
343
|
-
const conclusion = getContextConclusion(check)
|
|
344
|
-
|
|
345
|
-
checks.push({ name, status, conclusion })
|
|
346
|
-
|
|
347
|
-
if (status === "completed") {
|
|
348
|
-
completed += 1
|
|
349
|
-
} else {
|
|
350
|
-
pending = true
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if (conclusion === "success" || conclusion === "neutral" || conclusion === "skipped") {
|
|
354
|
-
successful += 1
|
|
355
|
-
} else if (conclusion) {
|
|
356
|
-
failing = true
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
if (pending) {
|
|
361
|
-
return { checkStatus: "pending", checkSummary: `checks ${completed}/${contexts.length}`, checks }
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
if (failing) {
|
|
365
|
-
return { checkStatus: "failing", checkSummary: `checks ${successful}/${contexts.length}`, checks }
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
return { checkStatus: "passing", checkSummary: `checks ${successful}/${contexts.length}`, checks }
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
const parsePullRequestSummary = (item: RawPullRequestSummaryNode): PullRequestItem => {
|
|
372
|
-
const checkInfo = getCheckInfoFromContexts(item.statusCheckRollup?.contexts.nodes ?? [])
|
|
373
|
-
return {
|
|
374
|
-
repository: item.repository.nameWithOwner,
|
|
375
|
-
author: item.author.login,
|
|
376
|
-
headRefOid: item.headRefOid,
|
|
377
|
-
number: item.number,
|
|
378
|
-
title: item.title,
|
|
379
|
-
body: "",
|
|
380
|
-
labels: [],
|
|
381
|
-
additions: 0,
|
|
382
|
-
deletions: 0,
|
|
383
|
-
changedFiles: 0,
|
|
384
|
-
state: getPullRequestState(item),
|
|
385
|
-
reviewStatus: getReviewStatus(item),
|
|
386
|
-
checkStatus: checkInfo.checkStatus,
|
|
387
|
-
checkSummary: checkInfo.checkSummary,
|
|
388
|
-
checks: checkInfo.checks,
|
|
389
|
-
autoMergeEnabled: item.autoMergeRequest !== null,
|
|
390
|
-
detailLoaded: false,
|
|
391
|
-
createdAt: new Date(item.createdAt),
|
|
392
|
-
closedAt: normalizeDate(item.closedAt),
|
|
393
|
-
url: item.url,
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
const parsePullRequest = (item: RawPullRequestNode): PullRequestItem => {
|
|
398
|
-
const checkInfo = getCheckInfoFromContexts(item.statusCheckRollup?.contexts.nodes ?? [])
|
|
399
|
-
return {
|
|
400
|
-
...parsePullRequestSummary(item),
|
|
401
|
-
body: item.body,
|
|
402
|
-
labels: item.labels.nodes.map((label) => ({
|
|
403
|
-
name: label.name,
|
|
404
|
-
color: label.color ? `#${label.color}` : null,
|
|
405
|
-
})),
|
|
406
|
-
additions: item.additions,
|
|
407
|
-
deletions: item.deletions,
|
|
408
|
-
changedFiles: item.changedFiles,
|
|
409
|
-
checkStatus: checkInfo.checkStatus,
|
|
410
|
-
checkSummary: checkInfo.checkSummary,
|
|
411
|
-
checks: checkInfo.checks,
|
|
412
|
-
detailLoaded: true,
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
const searchQuery = (mode: PullRequestQueueMode, author: string, repository: string | null) => {
|
|
417
|
-
const sort = mode === "repository" ? "sort:updated-desc" : "sort:created-desc"
|
|
418
|
-
return `${pullRequestQueueSearchQualifier(mode, author, repository)} is:pr is:open ${sort}`
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
const pullRequestPage = <Item>(connection: PullRequestConnection<Item>, parse: (node: Item) => PullRequestItem): PullRequestPage => ({
|
|
422
|
-
items: connection.nodes.flatMap((node) => node ? [parse(node)] : []),
|
|
423
|
-
endCursor: connection.pageInfo.endCursor,
|
|
424
|
-
hasNextPage: connection.pageInfo.hasNextPage && connection.pageInfo.endCursor !== null,
|
|
425
|
-
})
|
|
426
|
-
|
|
427
|
-
const repositoryParts = (repository: string) => {
|
|
428
|
-
const [owner, name] = repository.split("/")
|
|
429
|
-
return owner && name ? { owner, name } : null
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
const rawCommentFields = (comment: RawPullRequestComment, fallbackId: string) => ({
|
|
433
|
-
id: String(comment.id ?? comment.node_id ?? fallbackId),
|
|
434
|
-
author: comment.user?.login ?? "unknown",
|
|
435
|
-
body: comment.body ?? "",
|
|
436
|
-
createdAt: comment.created_at ? new Date(comment.created_at) : null,
|
|
437
|
-
url: comment.html_url ?? comment.url ?? null,
|
|
438
|
-
})
|
|
439
|
-
|
|
440
|
-
const parsePullRequestComment = (comment: RawPullRequestComment): PullRequestReviewComment | null => {
|
|
441
|
-
const line = comment.line ?? comment.original_line
|
|
442
|
-
if (!comment.path || !line || (comment.side !== "LEFT" && comment.side !== "RIGHT")) return null
|
|
443
|
-
return {
|
|
444
|
-
...rawCommentFields(comment, `${comment.path}:${comment.side}:${line}:${comment.created_at ?? ""}:${comment.body ?? ""}`),
|
|
445
|
-
path: comment.path,
|
|
446
|
-
line,
|
|
447
|
-
side: comment.side,
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
const parsePullRequestComments = (response: Schema.Schema.Type<typeof CommentsResponseSchema>): readonly PullRequestReviewComment[] => {
|
|
452
|
-
return flattenSlurpedPages(response).flatMap((comment) => {
|
|
453
|
-
const parsed = parsePullRequestComment(comment)
|
|
454
|
-
return parsed ? [parsed] : []
|
|
455
|
-
})
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
const parseIssueComment = (comment: RawPullRequestComment): PullRequestConversationItem => ({
|
|
459
|
-
_tag: "comment",
|
|
460
|
-
...rawCommentFields(comment, `${comment.created_at ?? ""}:${comment.body ?? ""}`),
|
|
461
|
-
})
|
|
462
|
-
|
|
463
|
-
const reviewCommentConversationItem = (comment: PullRequestReviewComment): PullRequestConversationItem => ({
|
|
464
|
-
_tag: "review-comment",
|
|
465
|
-
...comment,
|
|
466
|
-
})
|
|
467
|
-
|
|
468
|
-
const conversationItemTime = (item: PullRequestConversationItem) => item.createdAt?.getTime() ?? Number.MAX_SAFE_INTEGER
|
|
469
|
-
|
|
470
|
-
const sortConversationItems = (items: readonly PullRequestConversationItem[]) =>
|
|
471
|
-
[...items].sort((left, right) => conversationItemTime(left) - conversationItemTime(right) || left.id.localeCompare(right.id))
|
|
472
|
-
|
|
473
|
-
const parseIssueComments = (response: Schema.Schema.Type<typeof CommentsResponseSchema>): readonly PullRequestConversationItem[] =>
|
|
474
|
-
flattenSlurpedPages(response).map(parseIssueComment)
|
|
475
|
-
|
|
476
|
-
const flattenSlurpedPages = <Item>(response: readonly Item[] | readonly (readonly Item[])[]): readonly Item[] =>
|
|
477
|
-
Array.isArray(response[0]) ? (response as readonly (readonly Item[])[]).flat() : response as readonly Item[]
|
|
478
|
-
|
|
479
|
-
const parsePullRequestFiles = (response: Schema.Schema.Type<typeof PullRequestFilesResponseSchema>): readonly RawPullRequestFile[] =>
|
|
480
|
-
flattenSlurpedPages(response)
|
|
481
|
-
|
|
482
|
-
const diffPath = (path: string) => /\s|"/.test(path) ? JSON.stringify(path) : path
|
|
483
|
-
|
|
484
|
-
const prefixedDiffPath = (prefix: "a" | "b", path: string) => diffPath(`${prefix}/${path}`)
|
|
485
|
-
|
|
486
|
-
const fileHeaderPatch = (file: RawPullRequestFile) => {
|
|
487
|
-
const oldPath = file.previous_filename ?? file.filename
|
|
488
|
-
const newPath = file.filename
|
|
489
|
-
const oldRef = file.status === "added" ? "/dev/null" : prefixedDiffPath("a", oldPath)
|
|
490
|
-
const newRef = file.status === "removed" ? "/dev/null" : prefixedDiffPath("b", newPath)
|
|
491
|
-
const lines = [
|
|
492
|
-
`diff --git ${prefixedDiffPath("a", oldPath)} ${prefixedDiffPath("b", newPath)}`,
|
|
493
|
-
...(file.status === "renamed" && file.previous_filename ? [`rename from ${oldPath}`, `rename to ${newPath}`] : []),
|
|
494
|
-
`--- ${oldRef}`,
|
|
495
|
-
`+++ ${newRef}`,
|
|
496
|
-
]
|
|
497
|
-
if (file.patch) lines.push(file.patch.trimEnd())
|
|
498
|
-
return lines.join("\n")
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
export const pullRequestFilesToPatch = (files: readonly RawPullRequestFile[]) =>
|
|
502
|
-
files.map(fileHeaderPatch).join("\n")
|
|
503
|
-
|
|
504
|
-
const fallbackCreatedComment = (input: CreatePullRequestCommentInput): PullRequestReviewComment => ({
|
|
505
|
-
id: `created:${input.repository}:${input.number}:${input.path}:${input.side}:${input.line}:${Date.now()}`,
|
|
506
|
-
path: input.path,
|
|
507
|
-
line: input.line,
|
|
508
|
-
side: input.side,
|
|
509
|
-
author: config.author.replace(/^@/, "") || "you",
|
|
510
|
-
body: input.body,
|
|
511
|
-
createdAt: new Date(),
|
|
512
|
-
url: null,
|
|
513
|
-
})
|
|
514
|
-
|
|
515
|
-
export type GitHubError = CommandError | JsonParseError | Schema.SchemaError
|
|
516
|
-
|
|
517
|
-
const MERGEABLE_BY_RAW: Record<string, Mergeable> = {
|
|
518
|
-
MERGEABLE: "mergeable",
|
|
519
|
-
CONFLICTING: "conflicting",
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
const normalizeMergeable = (value: string): Mergeable =>
|
|
523
|
-
MERGEABLE_BY_RAW[value] ?? "unknown"
|
|
524
|
-
|
|
525
|
-
export class GitHubService extends Context.Service<GitHubService, {
|
|
526
|
-
readonly listOpenPullRequests: (mode: PullRequestQueueMode, repository: string | null) => Effect.Effect<readonly PullRequestItem[], GitHubError>
|
|
527
|
-
readonly listOpenPullRequestPage: (input: ListPullRequestPageInput) => Effect.Effect<PullRequestPage, GitHubError>
|
|
528
|
-
readonly listOpenPullRequestDetails: (mode: PullRequestQueueMode, repository: string | null) => Effect.Effect<readonly PullRequestItem[], GitHubError>
|
|
529
|
-
readonly getPullRequestDetails: (repository: string, number: number) => Effect.Effect<PullRequestItem, GitHubError>
|
|
530
|
-
readonly getAuthenticatedUser: () => Effect.Effect<string, GitHubError>
|
|
531
|
-
readonly getPullRequestDiff: (repository: string, number: number) => Effect.Effect<string, GitHubError>
|
|
532
|
-
readonly listPullRequestComments: (repository: string, number: number) => Effect.Effect<readonly PullRequestReviewComment[], GitHubError>
|
|
533
|
-
readonly listPullRequestConversation: (repository: string, number: number) => Effect.Effect<readonly PullRequestConversationItem[], GitHubError>
|
|
534
|
-
readonly getPullRequestMergeInfo: (repository: string, number: number) => Effect.Effect<PullRequestMergeInfo, GitHubError>
|
|
535
|
-
readonly mergePullRequest: (repository: string, number: number, action: PullRequestMergeAction) => Effect.Effect<void, CommandError>
|
|
536
|
-
readonly closePullRequest: (repository: string, number: number) => Effect.Effect<void, CommandError>
|
|
537
|
-
readonly createPullRequestComment: (input: CreatePullRequestCommentInput) => Effect.Effect<PullRequestReviewComment, GitHubError>
|
|
538
|
-
readonly toggleDraftStatus: (repository: string, number: number, isDraft: boolean) => Effect.Effect<void, CommandError>
|
|
539
|
-
readonly listRepoLabels: (repository: string) => Effect.Effect<readonly { readonly name: string; readonly color: string | null }[], GitHubError>
|
|
540
|
-
readonly addPullRequestLabel: (repository: string, number: number, label: string) => Effect.Effect<void, CommandError>
|
|
541
|
-
readonly removePullRequestLabel: (repository: string, number: number, label: string) => Effect.Effect<void, CommandError>
|
|
542
|
-
}>()("ghui/GitHubService") {
|
|
543
|
-
static readonly layerNoDeps = Layer.effect(
|
|
544
|
-
GitHubService,
|
|
545
|
-
Effect.gen(function*() {
|
|
546
|
-
const command = yield* CommandRunner
|
|
547
|
-
|
|
548
|
-
const ghJson = <S extends Schema.Top>(label: string, schema: S, args: readonly string[]) =>
|
|
549
|
-
command.runSchema(schema, "gh", args).pipe(Effect.withSpan(`GitHubService.${label}`))
|
|
550
|
-
|
|
551
|
-
const ghVoid = (label: string, args: readonly string[]) =>
|
|
552
|
-
command.run("gh", args).pipe(Effect.withSpan(`GitHubService.${label}`), Effect.asVoid)
|
|
553
|
-
|
|
554
|
-
const searchPage = <Item extends Schema.Top>(label: string, query: string, schema: Item, parse: (node: Item["Type"]) => PullRequestItem) => {
|
|
555
|
-
const responseSchema = SearchResponseSchema(schema)
|
|
556
|
-
return Effect.fn(`GitHubService.${label}`)(function*(input: ListPullRequestPageInput) {
|
|
557
|
-
const response: SearchResponse<Item["Type"]> = yield* command.runSchema(responseSchema, "gh", [
|
|
558
|
-
"api", "graphql",
|
|
559
|
-
"-f", `query=${query}`,
|
|
560
|
-
"-F", `searchQuery=${searchQuery(input.mode, config.author, input.repository)}`,
|
|
561
|
-
"-F", `first=${input.pageSize}`,
|
|
562
|
-
...(input.cursor ? ["-F", `after=${input.cursor}`] : []),
|
|
563
|
-
])
|
|
564
|
-
return pullRequestPage(response.data.search, parse)
|
|
565
|
-
})
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
const listOpenPullRequestSearchPage = searchPage("listOpenPullRequestSearchPage", pullRequestSummarySearchQuery, RawPullRequestSummaryNodeSchema, parsePullRequestSummary)
|
|
569
|
-
const listOpenPullRequestDetailsPage = searchPage("listOpenPullRequestDetailsPage", pullRequestSearchQuery, RawPullRequestNodeSchema, parsePullRequest)
|
|
570
|
-
|
|
571
|
-
const listRepositoryPullRequestPage = Effect.fn("GitHubService.listRepositoryPullRequestPage")(function*(input: ListPullRequestPageInput) {
|
|
572
|
-
if (!input.repository) return { items: [], endCursor: null, hasNextPage: false } satisfies PullRequestPage
|
|
573
|
-
const repo = repositoryParts(input.repository)
|
|
574
|
-
if (!repo) {
|
|
575
|
-
return yield* new CommandError({ command: "gh", args: [], detail: `Invalid repository: ${input.repository}`, cause: input.repository })
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
const response = yield* command.runSchema(RepositoryPullRequestsResponseSchema, "gh", [
|
|
579
|
-
"api", "graphql",
|
|
580
|
-
"-f", `query=${repositoryPullRequestsQuery}`,
|
|
581
|
-
"-F", `owner=${repo.owner}`,
|
|
582
|
-
"-F", `name=${repo.name}`,
|
|
583
|
-
"-F", `first=${input.pageSize}`,
|
|
584
|
-
...(input.cursor ? ["-F", `after=${input.cursor}`] : []),
|
|
585
|
-
])
|
|
586
|
-
const connection = response.data.repository?.pullRequests
|
|
587
|
-
if (!connection) {
|
|
588
|
-
return yield* new CommandError({ command: "gh", args: [], detail: `Repository not found: ${input.repository}`, cause: input.repository })
|
|
589
|
-
}
|
|
590
|
-
return pullRequestPage(connection, parsePullRequestSummary)
|
|
591
|
-
})
|
|
592
|
-
|
|
593
|
-
const listOpenPullRequestPage = Effect.fn("GitHubService.listOpenPullRequestPage")(function*(input: ListPullRequestPageInput) {
|
|
594
|
-
const pageSize = Math.max(1, Math.min(100, input.pageSize))
|
|
595
|
-
const pageInput = { ...input, pageSize }
|
|
596
|
-
if (pageInput.mode === "repository" && pageInput.repository) return yield* listRepositoryPullRequestPage(pageInput)
|
|
597
|
-
return yield* listOpenPullRequestSearchPage(pageInput)
|
|
598
|
-
})
|
|
599
|
-
|
|
600
|
-
const paginatePages = Effect.fn("GitHubService.paginatePages")(function*(mode: PullRequestQueueMode, repository: string | null, loadPage: (input: ListPullRequestPageInput) => Effect.Effect<PullRequestPage, GitHubError>) {
|
|
601
|
-
const pullRequests: PullRequestItem[] = []
|
|
602
|
-
let cursor: string | null = null
|
|
603
|
-
|
|
604
|
-
while (pullRequests.length < config.prFetchLimit) {
|
|
605
|
-
const page: PullRequestPage = yield* loadPage({ mode, repository, cursor, pageSize: Math.min(100, config.prFetchLimit - pullRequests.length) })
|
|
606
|
-
pullRequests.push(...page.items)
|
|
607
|
-
if (!page.hasNextPage || !page.endCursor) break
|
|
608
|
-
cursor = page.endCursor
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
return pullRequests
|
|
612
|
-
})
|
|
613
|
-
|
|
614
|
-
const listOpenPullRequests = Effect.fn("GitHubService.listOpenPullRequests")(function*(mode: PullRequestQueueMode, repository: string | null) {
|
|
615
|
-
return yield* paginatePages(mode, repository, listOpenPullRequestPage)
|
|
616
|
-
})
|
|
617
|
-
const listOpenPullRequestDetails = Effect.fn("GitHubService.listOpenPullRequestDetails")(function*(mode: PullRequestQueueMode, repository: string | null) {
|
|
618
|
-
return yield* paginatePages(mode, repository, listOpenPullRequestDetailsPage)
|
|
619
|
-
})
|
|
620
|
-
|
|
621
|
-
const getPullRequestDetails = Effect.fn("GitHubService.getPullRequestDetails")(function*(repository: string, number: number) {
|
|
622
|
-
const repo = repositoryParts(repository)
|
|
623
|
-
if (!repo) {
|
|
624
|
-
return yield* new CommandError({ command: "gh", args: [], detail: `Invalid repository: ${repository}`, cause: repository })
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
const response = yield* command.runSchema(PullRequestDetailResponseSchema, "gh", [
|
|
628
|
-
"api", "graphql",
|
|
629
|
-
"-f", `query=${pullRequestDetailQuery}`,
|
|
630
|
-
"-F", `owner=${repo.owner}`,
|
|
631
|
-
"-F", `name=${repo.name}`,
|
|
632
|
-
"-F", `number=${number}`,
|
|
633
|
-
])
|
|
634
|
-
const pullRequest = response.data.repository?.pullRequest
|
|
635
|
-
if (!pullRequest) {
|
|
636
|
-
return yield* new CommandError({ command: "gh", args: [], detail: `Pull request not found: ${repository}#${number}`, cause: `${repository}#${number}` })
|
|
637
|
-
}
|
|
638
|
-
return parsePullRequest(pullRequest)
|
|
639
|
-
})
|
|
640
|
-
|
|
641
|
-
const getAuthenticatedUser = () =>
|
|
642
|
-
ghJson("getAuthenticatedUser", ViewerSchema, ["api", "user"]).pipe(Effect.map((viewer) => viewer.login))
|
|
643
|
-
|
|
644
|
-
const getPullRequestDiff = (repository: string, number: number) =>
|
|
645
|
-
ghJson("getPullRequestDiff", PullRequestFilesResponseSchema, [
|
|
646
|
-
"api", "--paginate", "--slurp", `repos/${repository}/pulls/${number}/files`,
|
|
647
|
-
]).pipe(Effect.map((response) => pullRequestFilesToPatch(parsePullRequestFiles(response))))
|
|
648
|
-
|
|
649
|
-
const listPullRequestComments = (repository: string, number: number) =>
|
|
650
|
-
ghJson("listPullRequestComments", CommentsResponseSchema, [
|
|
651
|
-
"api", "--paginate", "--slurp", `repos/${repository}/pulls/${number}/comments`,
|
|
652
|
-
]).pipe(Effect.map(parsePullRequestComments))
|
|
653
|
-
|
|
654
|
-
const listPullRequestConversation = Effect.fn("GitHubService.listPullRequestConversation")(function*(repository: string, number: number) {
|
|
655
|
-
const [issueComments, reviewComments] = yield* Effect.all([
|
|
656
|
-
ghJson("listPullRequestIssueComments", CommentsResponseSchema, [
|
|
657
|
-
"api", "--paginate", "--slurp", `repos/${repository}/issues/${number}/comments`,
|
|
658
|
-
]).pipe(Effect.map(parseIssueComments)),
|
|
659
|
-
listPullRequestComments(repository, number).pipe(Effect.map((comments) => comments.map(reviewCommentConversationItem))),
|
|
660
|
-
], { concurrency: "unbounded" })
|
|
661
|
-
|
|
662
|
-
return sortConversationItems([...issueComments, ...reviewComments])
|
|
663
|
-
})
|
|
664
|
-
|
|
665
|
-
const getPullRequestMergeInfo = Effect.fn("GitHubService.getPullRequestMergeInfo")(function*(repository: string, number: number) {
|
|
666
|
-
const info = yield* command.runSchema(MergeInfoResponseSchema, "gh", [
|
|
667
|
-
"pr", "view", String(number), "--repo", repository,
|
|
668
|
-
"--json", "number,title,state,isDraft,mergeable,reviewDecision,autoMergeRequest,statusCheckRollup",
|
|
669
|
-
])
|
|
670
|
-
const checkInfo = getCheckInfoFromContexts(info.statusCheckRollup)
|
|
671
|
-
|
|
672
|
-
return {
|
|
673
|
-
repository,
|
|
674
|
-
number: info.number,
|
|
675
|
-
title: info.title,
|
|
676
|
-
state: info.state.toLowerCase() === "open" ? "open" : "closed",
|
|
677
|
-
isDraft: info.isDraft,
|
|
678
|
-
mergeable: normalizeMergeable(info.mergeable),
|
|
679
|
-
reviewStatus: getReviewStatus(info),
|
|
680
|
-
checkStatus: checkInfo.checkStatus,
|
|
681
|
-
checkSummary: checkInfo.checkSummary,
|
|
682
|
-
autoMergeEnabled: info.autoMergeRequest !== null,
|
|
683
|
-
} satisfies PullRequestMergeInfo
|
|
684
|
-
})
|
|
685
|
-
|
|
686
|
-
const mergePullRequest = (repository: string, number: number, action: PullRequestMergeAction) =>
|
|
687
|
-
ghVoid("mergePullRequest", ["pr", "merge", String(number), "--repo", repository, ...getMergeActionDefinition(action).cliArgs])
|
|
688
|
-
|
|
689
|
-
const closePullRequest = (repository: string, number: number) =>
|
|
690
|
-
ghVoid("closePullRequest", ["pr", "close", String(number), "--repo", repository])
|
|
691
|
-
|
|
692
|
-
const createPullRequestComment = Effect.fn("GitHubService.createPullRequestComment")(function*(input: CreatePullRequestCommentInput) {
|
|
693
|
-
const response = yield* command.runSchema(PullRequestCommentSchema, "gh", [
|
|
694
|
-
"api", "--method", "POST", `repos/${input.repository}/pulls/${input.number}/comments`,
|
|
695
|
-
"-f", `body=${input.body}`,
|
|
696
|
-
"-f", `commit_id=${input.commitId}`,
|
|
697
|
-
"-f", `path=${input.path}`,
|
|
698
|
-
"-F", `line=${input.line}`,
|
|
699
|
-
"-f", `side=${input.side}`,
|
|
700
|
-
...(input.startLine === undefined ? [] : ["-F", `start_line=${input.startLine}`, "-f", `start_side=${input.startSide ?? input.side}`]),
|
|
701
|
-
])
|
|
702
|
-
return parsePullRequestComment(response) ?? fallbackCreatedComment(input)
|
|
703
|
-
})
|
|
704
|
-
|
|
705
|
-
const toggleDraftStatus = (repository: string, number: number, isDraft: boolean) =>
|
|
706
|
-
ghVoid("toggleDraftStatus", ["pr", "ready", String(number), "--repo", repository, ...(isDraft ? [] : ["--undo"])])
|
|
707
|
-
|
|
708
|
-
const listRepoLabels = (repository: string) =>
|
|
709
|
-
ghJson("listRepoLabels", RepoLabelsResponseSchema, [
|
|
710
|
-
"label", "list", "--repo", repository, "--json", "name,color", "--limit", "100",
|
|
711
|
-
]).pipe(Effect.map((labels) => labels.map((label) => ({ name: label.name, color: `#${label.color}` }))))
|
|
712
|
-
|
|
713
|
-
const addPullRequestLabel = (repository: string, number: number, label: string) =>
|
|
714
|
-
ghVoid("addPullRequestLabel", ["pr", "edit", String(number), "--repo", repository, "--add-label", label])
|
|
715
|
-
|
|
716
|
-
const removePullRequestLabel = (repository: string, number: number, label: string) =>
|
|
717
|
-
ghVoid("removePullRequestLabel", ["pr", "edit", String(number), "--repo", repository, "--remove-label", label])
|
|
718
|
-
|
|
719
|
-
return GitHubService.of({
|
|
720
|
-
listOpenPullRequests,
|
|
721
|
-
listOpenPullRequestPage,
|
|
722
|
-
listOpenPullRequestDetails,
|
|
723
|
-
getPullRequestDetails,
|
|
724
|
-
getAuthenticatedUser,
|
|
725
|
-
getPullRequestDiff,
|
|
726
|
-
listPullRequestComments,
|
|
727
|
-
listPullRequestConversation,
|
|
728
|
-
getPullRequestMergeInfo,
|
|
729
|
-
mergePullRequest,
|
|
730
|
-
closePullRequest,
|
|
731
|
-
createPullRequestComment,
|
|
732
|
-
toggleDraftStatus,
|
|
733
|
-
listRepoLabels,
|
|
734
|
-
addPullRequestLabel,
|
|
735
|
-
removePullRequestLabel,
|
|
736
|
-
})
|
|
737
|
-
}),
|
|
738
|
-
)
|
|
739
|
-
|
|
740
|
-
static readonly layer = GitHubService.layerNoDeps.pipe(Layer.provide(CommandRunner.layer))
|
|
741
|
-
}
|