@kitlangton/ghui 0.3.0 → 0.3.3

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.
Files changed (55) hide show
  1. package/README.md +13 -3
  2. package/bin/ghui.js +6 -1
  3. package/dist/index.js +9456 -0
  4. package/package.json +7 -4
  5. package/src/App.tsx +0 -2779
  6. package/src/appCommands.ts +0 -409
  7. package/src/commands.ts +0 -73
  8. package/src/config.ts +0 -20
  9. package/src/date.ts +0 -20
  10. package/src/domain.ts +0 -149
  11. package/src/errors.ts +0 -10
  12. package/src/index.tsx +0 -50
  13. package/src/keyboard/opentuiAdapter.ts +0 -43
  14. package/src/keymap/all.ts +0 -116
  15. package/src/keymap/changedFilesModal.ts +0 -23
  16. package/src/keymap/closeModal.ts +0 -13
  17. package/src/keymap/commandPalette.ts +0 -16
  18. package/src/keymap/commentModal.ts +0 -73
  19. package/src/keymap/commentThreadModal.ts +0 -24
  20. package/src/keymap/detailView.ts +0 -30
  21. package/src/keymap/diffView.ts +0 -93
  22. package/src/keymap/filterMode.ts +0 -13
  23. package/src/keymap/helpers.ts +0 -24
  24. package/src/keymap/labelModal.ts +0 -16
  25. package/src/keymap/listNav.ts +0 -119
  26. package/src/keymap/mergeModal.ts +0 -23
  27. package/src/keymap/openRepositoryModal.ts +0 -13
  28. package/src/keymap/submitReviewModal.ts +0 -48
  29. package/src/keymap/themeModal.ts +0 -49
  30. package/src/mergeActions.ts +0 -88
  31. package/src/observability.ts +0 -46
  32. package/src/pullRequestCache.ts +0 -19
  33. package/src/pullRequestViews.ts +0 -43
  34. package/src/services/BrowserOpener.ts +0 -22
  35. package/src/services/Clipboard.ts +0 -46
  36. package/src/services/CommandRunner.ts +0 -91
  37. package/src/services/GitHubService.ts +0 -756
  38. package/src/services/MockGitHubService.ts +0 -182
  39. package/src/themeStore.ts +0 -60
  40. package/src/ui/CommandPalette.tsx +0 -176
  41. package/src/ui/DetailsPane.tsx +0 -656
  42. package/src/ui/FooterHints.tsx +0 -90
  43. package/src/ui/LoadingLogo.tsx +0 -75
  44. package/src/ui/PullRequestDiffPane.tsx +0 -249
  45. package/src/ui/PullRequestList.tsx +0 -194
  46. package/src/ui/colors.ts +0 -821
  47. package/src/ui/commentEditor.ts +0 -126
  48. package/src/ui/comments.tsx +0 -143
  49. package/src/ui/diff.ts +0 -650
  50. package/src/ui/diffStats.tsx +0 -25
  51. package/src/ui/modals.tsx +0 -964
  52. package/src/ui/primitives.tsx +0 -350
  53. package/src/ui/pullRequests.ts +0 -106
  54. package/src/ui/singleLineInput.ts +0 -26
  55. package/src/ui/spinner.ts +0 -1
@@ -1,409 +0,0 @@
1
- import type { AppCommand } from "./commands.js"
2
- import { defineCommand } from "./commands.js"
3
- import type { LoadStatus, PullRequestItem } from "./domain.js"
4
- import type { DiffView, DiffWhitespaceMode, DiffWrapMode } from "./ui/diff.js"
5
- import { type PullRequestView, viewEquals, viewLabel, viewMode } from "./pullRequestViews.js"
6
-
7
- interface AppCommandActions {
8
- readonly openCommandPalette: () => void
9
- readonly refreshPullRequests: (message?: string, options?: { readonly resetTransientState?: boolean }) => void
10
- readonly openFilter: () => void
11
- readonly clearFilter: () => void
12
- readonly openThemeModal: () => void
13
- readonly openRepositoryPicker: () => void
14
- readonly loadMorePullRequests: () => void
15
- readonly switchViewTo: (view: PullRequestView) => void
16
- readonly openDetails: () => void
17
- readonly closeDetails: () => void
18
- readonly openDiffView: () => void
19
- readonly closeDiffView: () => void
20
- readonly reloadDiff: () => void
21
- readonly toggleDiffRenderView: () => void
22
- readonly toggleDiffWrapMode: () => void
23
- readonly toggleDiffWhitespaceMode: () => void
24
- readonly openChangedFilesModal: () => void
25
- readonly jumpDiffFile: (delta: 1 | -1) => void
26
- readonly openSelectedDiffComment: () => void
27
- readonly toggleDiffCommentRange: () => void
28
- readonly moveDiffCommentThread: (delta: 1 | -1) => void
29
- readonly openDiffCommentModal: () => void
30
- readonly openSubmitReviewModal: () => void
31
- readonly togglePullRequestDraftStatus: () => void
32
- readonly openLabelModal: () => void
33
- readonly openMergeModal: () => void
34
- readonly openCloseModal: () => void
35
- readonly openPullRequestInBrowser: () => void
36
- readonly copyPullRequestMetadata: () => void
37
- readonly quit: () => void
38
- }
39
-
40
- interface BuildAppCommandsInput {
41
- readonly pullRequestStatus: LoadStatus
42
- readonly filterQuery: string
43
- readonly filterMode: boolean
44
- readonly selectedRepository: string | null
45
- readonly activeViews: readonly PullRequestView[]
46
- readonly activeView: PullRequestView
47
- readonly loadedPullRequestCount: number
48
- readonly hasMorePullRequests: boolean
49
- readonly isLoadingMorePullRequests: boolean
50
- readonly selectedPullRequest: PullRequestItem | null
51
- readonly detailFullView: boolean
52
- readonly diffFullView: boolean
53
- readonly diffReady: boolean
54
- readonly effectiveDiffRenderView: DiffView
55
- readonly diffWrapMode: DiffWrapMode
56
- readonly diffWhitespaceMode: DiffWhitespaceMode
57
- readonly readyDiffFileCount: number
58
- readonly diffFileIndex: number
59
- readonly diffRangeActive: boolean
60
- readonly selectedDiffCommentAnchorLabel: string | null
61
- readonly selectedDiffCommentThreadCount: number
62
- readonly hasDiffCommentThreads: boolean
63
- readonly actions: AppCommandActions
64
- }
65
-
66
- export const buildAppCommands = ({
67
- pullRequestStatus,
68
- filterQuery,
69
- filterMode,
70
- selectedRepository,
71
- activeViews,
72
- activeView,
73
- loadedPullRequestCount,
74
- hasMorePullRequests,
75
- isLoadingMorePullRequests,
76
- selectedPullRequest,
77
- detailFullView,
78
- diffFullView,
79
- diffReady,
80
- effectiveDiffRenderView,
81
- diffWrapMode,
82
- diffWhitespaceMode,
83
- readyDiffFileCount,
84
- diffFileIndex,
85
- diffRangeActive,
86
- selectedDiffCommentAnchorLabel,
87
- selectedDiffCommentThreadCount,
88
- hasDiffCommentThreads,
89
- actions,
90
- }: BuildAppCommandsInput): readonly AppCommand[] => {
91
- const selectedPullRequestLabel = selectedPullRequest ? `#${selectedPullRequest.number} ${selectedPullRequest.repository}` : "No pull request selected"
92
- const noPullRequestReason = selectedPullRequest ? null : "Select a pull request first."
93
- const noOpenPullRequestReason = selectedPullRequest?.state === "open" ? null : selectedPullRequest ? "Pull request is not open." : noPullRequestReason
94
- const diffReadyReason = selectedPullRequest
95
- ? diffReady ? null : "Load the diff before running this command."
96
- : noPullRequestReason
97
- const diffOpenReadyReason = diffFullView ? diffReadyReason : "Open a diff first."
98
- const selectedDiffLineReason = diffFullView && diffReady
99
- ? selectedDiffCommentAnchorLabel ? null : "No diff line selected."
100
- : diffOpenReadyReason
101
- const diffThreadReason = diffFullView && diffReady
102
- ? hasDiffCommentThreads ? null : "No diff comments loaded."
103
- : diffOpenReadyReason
104
- const changedFilesReason = diffFullView && diffReady
105
- ? readyDiffFileCount > 0 ? null : "No changed files loaded."
106
- : diffOpenReadyReason
107
- const submitReviewReason = diffFullView && diffReady ? noOpenPullRequestReason : diffOpenReadyReason
108
- const loadMoreDisabledReason = isLoadingMorePullRequests
109
- ? "Already loading more pull requests."
110
- : hasMorePullRequests ? null : "No more pull requests loaded by this view."
111
-
112
- const forSelected = (
113
- command: Omit<AppCommand, "subtitle" | "disabledReason"> & { readonly requireOpen?: boolean },
114
- ): AppCommand => {
115
- const { requireOpen, ...rest } = command
116
- return defineCommand({
117
- ...rest,
118
- subtitle: selectedPullRequestLabel,
119
- disabledReason: requireOpen ? noOpenPullRequestReason : noPullRequestReason,
120
- })
121
- }
122
-
123
- return [
124
- defineCommand({
125
- id: "command.open",
126
- title: "Open command palette",
127
- scope: "Global",
128
- subtitle: "Search every available route through ghui",
129
- shortcut: "ctrl-p/cmd-k",
130
- keywords: ["palette", "commands", "deck"],
131
- run: actions.openCommandPalette,
132
- }),
133
- defineCommand({
134
- id: "pull.refresh",
135
- title: pullRequestStatus === "error" ? "Retry loading pull requests" : "Refresh pull requests",
136
- scope: "Global",
137
- subtitle: "Fetch the latest queue from GitHub",
138
- shortcut: "r",
139
- keywords: ["reload", "sync"],
140
- run: () => actions.refreshPullRequests("Refreshed", { resetTransientState: true }),
141
- }),
142
- defineCommand({
143
- id: "filter.open",
144
- title: "Filter pull requests",
145
- scope: "Global",
146
- subtitle: "Search the visible queue",
147
- shortcut: "/",
148
- keywords: ["search"],
149
- run: actions.openFilter,
150
- }),
151
- defineCommand({
152
- id: "filter.clear",
153
- title: "Clear pull request filter",
154
- scope: "Global",
155
- subtitle: "Show every pull request in the current queue",
156
- shortcut: "esc",
157
- disabledReason: filterQuery.length > 0 || filterMode ? null : "No filter is active.",
158
- run: actions.clearFilter,
159
- }),
160
- defineCommand({
161
- id: "theme.open",
162
- title: "Choose theme",
163
- scope: "Global",
164
- subtitle: "Preview and persist a terminal color theme",
165
- shortcut: "t",
166
- keywords: ["colors", "appearance"],
167
- run: actions.openThemeModal,
168
- }),
169
- defineCommand({
170
- id: "repository.open",
171
- title: "Open repository...",
172
- scope: "View",
173
- subtitle: selectedRepository ? `Current repository: ${selectedRepository}` : "Enter owner/name or a GitHub URL",
174
- keywords: ["repo", "repository", "owner", "github"],
175
- run: actions.openRepositoryPicker,
176
- }),
177
- ...activeViews.map((view) => defineCommand({
178
- id: view._tag === "Repository" ? "view.repository" : `view.${view.mode}`,
179
- title: `Show ${viewLabel(view)} view`,
180
- scope: "View" as const,
181
- subtitle: viewEquals(view, activeView) ? "Already showing this view" : "Switch pull request view",
182
- keywords: [viewMode(view), viewLabel(view), "queue", "view"],
183
- disabledReason: viewEquals(view, activeView) ? "Already showing this view." : null,
184
- run: () => actions.switchViewTo(view),
185
- })),
186
- defineCommand({
187
- id: "pull.load-more",
188
- title: "Load more pull requests",
189
- scope: "Navigation",
190
- subtitle: `${loadedPullRequestCount} loaded`,
191
- disabledReason: loadMoreDisabledReason,
192
- keywords: ["next page", "pagination", "more"],
193
- run: actions.loadMorePullRequests,
194
- }),
195
- forSelected({
196
- id: "detail.open",
197
- title: "Open pull request details",
198
- scope: "Pull request",
199
- shortcut: "enter",
200
- run: actions.openDetails,
201
- }),
202
- defineCommand({
203
- id: "detail.close",
204
- title: "Close details view",
205
- scope: "Pull request",
206
- subtitle: "Return to the queue",
207
- shortcut: "esc",
208
- disabledReason: detailFullView ? null : "Details view is not open.",
209
- run: actions.closeDetails,
210
- }),
211
- forSelected({
212
- id: "diff.open",
213
- title: "Open diff",
214
- scope: "Diff",
215
- shortcut: "d",
216
- keywords: ["files", "patch"],
217
- run: actions.openDiffView,
218
- }),
219
- defineCommand({
220
- id: "diff.close",
221
- title: "Close diff view",
222
- scope: "Diff",
223
- subtitle: "Return to the queue or detail view",
224
- shortcut: "esc",
225
- disabledReason: diffFullView ? null : "Diff view is not open.",
226
- run: actions.closeDiffView,
227
- }),
228
- defineCommand({
229
- id: "diff.reload",
230
- title: "Reload diff",
231
- scope: "Diff",
232
- subtitle: selectedPullRequestLabel,
233
- shortcut: "r",
234
- disabledReason: diffFullView && selectedPullRequest ? null : "Open a pull request diff first.",
235
- keywords: ["refresh", "comments"],
236
- run: actions.reloadDiff,
237
- }),
238
- defineCommand({
239
- id: "diff.toggle-view",
240
- title: "Toggle diff split/unified view",
241
- scope: "Diff",
242
- subtitle: effectiveDiffRenderView === "split" ? "Switch to unified view" : "Switch to split view",
243
- shortcut: "shift-v",
244
- disabledReason: diffFullView ? null : "Open a diff first.",
245
- run: actions.toggleDiffRenderView,
246
- }),
247
- defineCommand({
248
- id: "diff.toggle-wrap",
249
- title: "Toggle diff word wrap",
250
- scope: "Diff",
251
- subtitle: diffWrapMode === "none" ? "Wrap long diff lines" : "Keep diff lines unwrapped",
252
- shortcut: "w",
253
- disabledReason: diffFullView ? null : "Open a diff first.",
254
- run: actions.toggleDiffWrapMode,
255
- }),
256
- defineCommand({
257
- id: "diff.toggle-whitespace",
258
- title: diffWhitespaceMode === "ignore" ? "Show whitespace changes" : "Ignore whitespace changes",
259
- scope: "Diff",
260
- subtitle: diffWhitespaceMode === "ignore" ? "Display the original GitHub patch" : "Hide whitespace-only line changes",
261
- disabledReason: diffFullView ? null : "Open a diff first.",
262
- keywords: ["whitespace", "spacing", "ignore", "show"],
263
- run: actions.toggleDiffWhitespaceMode,
264
- }),
265
- defineCommand({
266
- id: "diff.changed-files",
267
- title: "Open changed files navigator",
268
- scope: "Diff",
269
- subtitle: readyDiffFileCount > 0 ? `${readyDiffFileCount} changed files` : "No diff files loaded",
270
- shortcut: "f",
271
- disabledReason: changedFilesReason,
272
- keywords: ["files", "navigator", "search"],
273
- run: actions.openChangedFilesModal,
274
- }),
275
- defineCommand({
276
- id: "diff.next-file",
277
- title: "Next diff file",
278
- scope: "Diff",
279
- subtitle: readyDiffFileCount > 0 ? `${diffFileIndex + 1}/${readyDiffFileCount}` : "No diff files loaded",
280
- shortcut: "]",
281
- disabledReason: changedFilesReason,
282
- run: () => actions.jumpDiffFile(1),
283
- }),
284
- defineCommand({
285
- id: "diff.previous-file",
286
- title: "Previous diff file",
287
- scope: "Diff",
288
- subtitle: readyDiffFileCount > 0 ? `${diffFileIndex + 1}/${readyDiffFileCount}` : "No diff files loaded",
289
- shortcut: "[",
290
- disabledReason: changedFilesReason,
291
- run: () => actions.jumpDiffFile(-1),
292
- }),
293
- defineCommand({
294
- id: "diff.open-comment-target",
295
- title: selectedDiffCommentThreadCount > 0 ? "Open selected diff thread" : "Comment on selected diff line",
296
- scope: "Diff",
297
- subtitle: selectedDiffCommentAnchorLabel ?? "No diff line selected",
298
- shortcut: "enter",
299
- disabledReason: selectedDiffLineReason,
300
- keywords: ["review", "comment", "thread", "line"],
301
- run: actions.openSelectedDiffComment,
302
- }),
303
- defineCommand({
304
- id: "diff.toggle-range",
305
- title: diffRangeActive ? "Clear diff comment range" : "Start diff comment range",
306
- scope: "Diff",
307
- subtitle: selectedDiffCommentAnchorLabel ?? "No diff line selected",
308
- shortcut: "v",
309
- disabledReason: selectedDiffLineReason,
310
- keywords: ["review", "comment", "range", "visual"],
311
- run: actions.toggleDiffCommentRange,
312
- }),
313
- defineCommand({
314
- id: "diff.next-thread",
315
- title: "Next diff thread",
316
- scope: "Diff",
317
- subtitle: hasDiffCommentThreads ? "Jump to the next commented line" : "No diff comments loaded",
318
- shortcut: "n",
319
- disabledReason: diffThreadReason,
320
- keywords: ["review", "comment", "thread"],
321
- run: () => actions.moveDiffCommentThread(1),
322
- }),
323
- defineCommand({
324
- id: "diff.previous-thread",
325
- title: "Previous diff thread",
326
- scope: "Diff",
327
- subtitle: hasDiffCommentThreads ? "Jump to the previous commented line" : "No diff comments loaded",
328
- shortcut: "p",
329
- disabledReason: diffThreadReason,
330
- keywords: ["review", "comment", "thread"],
331
- run: () => actions.moveDiffCommentThread(-1),
332
- }),
333
- defineCommand({
334
- id: "diff.add-comment",
335
- title: "Add comment on selected diff line",
336
- scope: "Diff",
337
- subtitle: selectedDiffCommentAnchorLabel ?? "No diff line selected",
338
- disabledReason: selectedDiffLineReason,
339
- keywords: ["review", "reply"],
340
- run: actions.openDiffCommentModal,
341
- }),
342
- defineCommand({
343
- id: "diff.submit-review",
344
- title: "Submit pull request review",
345
- scope: "Diff",
346
- subtitle: selectedPullRequestLabel,
347
- shortcut: "R",
348
- disabledReason: submitReviewReason,
349
- keywords: ["review", "approve", "request changes"],
350
- run: actions.openSubmitReviewModal,
351
- }),
352
- forSelected({
353
- id: "pull.toggle-draft",
354
- title: selectedPullRequest?.reviewStatus === "draft" ? "Mark ready for review" : "Mark as draft",
355
- scope: "Pull request",
356
- shortcut: "s",
357
- keywords: ["state", "ready"],
358
- run: actions.togglePullRequestDraftStatus,
359
- }),
360
- forSelected({
361
- id: "pull.labels",
362
- title: "Manage labels",
363
- scope: "Pull request",
364
- shortcut: "l",
365
- run: actions.openLabelModal,
366
- }),
367
- forSelected({
368
- id: "pull.merge",
369
- title: "Merge pull request",
370
- scope: "Pull request",
371
- shortcut: "m",
372
- keywords: ["auto merge", "squash"],
373
- run: actions.openMergeModal,
374
- }),
375
- forSelected({
376
- id: "pull.close",
377
- title: "Close pull request",
378
- scope: "Pull request",
379
- shortcut: "x",
380
- requireOpen: true,
381
- run: actions.openCloseModal,
382
- }),
383
- forSelected({
384
- id: "pull.open-browser",
385
- title: "Open pull request in browser",
386
- scope: "Pull request",
387
- shortcut: "o",
388
- keywords: ["github", "web"],
389
- run: actions.openPullRequestInBrowser,
390
- }),
391
- forSelected({
392
- id: "pull.copy-metadata",
393
- title: "Copy pull request metadata",
394
- scope: "Pull request",
395
- shortcut: "y",
396
- keywords: ["clipboard", "url", "title"],
397
- run: actions.copyPullRequestMetadata,
398
- }),
399
- defineCommand({
400
- id: "app.quit",
401
- title: "Quit ghui",
402
- scope: "System",
403
- subtitle: "Leave the terminal UI",
404
- shortcut: "q",
405
- keywords: ["exit"],
406
- run: actions.quit,
407
- }),
408
- ]
409
- }
package/src/commands.ts DELETED
@@ -1,73 +0,0 @@
1
- export type CommandScope = "Global" | "View" | "Pull request" | "Diff" | "Navigation" | "System"
2
-
3
- const SCOPE_ORDER: readonly CommandScope[] = ["Global", "View", "Pull request", "Diff", "Navigation", "System"]
4
-
5
- export const sortCommandsByScope = (commands: readonly AppCommand[]) =>
6
- [...commands].sort((left, right) => SCOPE_ORDER.indexOf(left.scope) - SCOPE_ORDER.indexOf(right.scope))
7
-
8
- export interface AppCommand {
9
- readonly id: string
10
- readonly title: string
11
- readonly scope: CommandScope
12
- readonly run: () => void
13
- readonly subtitle?: string
14
- readonly shortcut?: string
15
- readonly keywords?: readonly string[]
16
- readonly disabledReason?: string | null
17
- }
18
-
19
- export const defineCommand = (command: AppCommand): AppCommand => command
20
-
21
- export const commandEnabled = (command: AppCommand) => !command.disabledReason
22
-
23
- const normalize = (text: string) => text.toLowerCase().replace(/[^a-z0-9#]+/g, " ").trim()
24
-
25
- const acronym = (text: string) => normalize(text).split(" ").filter(Boolean).map((word) => word[0]).join("")
26
-
27
- const fuzzyIncludes = (text: string, query: string) => {
28
- let index = 0
29
- for (const char of text) {
30
- if (char === query[index]) index++
31
- if (index >= query.length) return true
32
- }
33
- return query.length === 0
34
- }
35
-
36
- const commandSearchText = (command: AppCommand) => normalize([
37
- command.title,
38
- command.subtitle,
39
- command.scope,
40
- command.shortcut,
41
- ...(command.keywords ?? []),
42
- ].filter(Boolean).join(" "))
43
-
44
- const commandScore = (command: AppCommand, query: string) => {
45
- const normalizedQuery = normalize(query)
46
- if (normalizedQuery.length === 0) return 0
47
-
48
- const title = normalize(command.title)
49
- const searchText = commandSearchText(command)
50
- const titleAcronym = acronym(command.title)
51
- if (title.startsWith(normalizedQuery)) return 0
52
- if (searchText.startsWith(normalizedQuery)) return 1
53
- if (title.includes(normalizedQuery)) return 2
54
- if (searchText.includes(normalizedQuery)) return 3
55
- if (titleAcronym.startsWith(normalizedQuery)) return 4
56
- if (fuzzyIncludes(searchText, normalizedQuery.replaceAll(" ", ""))) return 5
57
- return null
58
- }
59
-
60
- export const filterCommands = (commands: readonly AppCommand[], query: string) => {
61
- return commands.flatMap((command, index) => {
62
- const score = commandScore(command, query)
63
- return score === null ? [] : [{ command, index, score }]
64
- }).sort((left, right) => {
65
- const enabled = Number(commandEnabled(right.command)) - Number(commandEnabled(left.command))
66
- return enabled || left.score - right.score || left.index - right.index
67
- }).map(({ command }) => command)
68
- }
69
-
70
- export const clampCommandIndex = (index: number, commands: readonly AppCommand[]) => {
71
- if (commands.length === 0) return 0
72
- return Math.max(0, Math.min(commands.length - 1, index))
73
- }
package/src/config.ts DELETED
@@ -1,20 +0,0 @@
1
- import { Config, Effect } from "effect"
2
-
3
- const positiveIntOr = (fallback: number) => (value: number) => Number.isFinite(value) && value > 0 ? value : fallback
4
-
5
- const pageSizeOr = (fallback: number) => (value: number) => Math.min(100, positiveIntOr(fallback)(value))
6
-
7
- const appConfig = Config.all({
8
- prFetchLimit: Config.int("GHUI_PR_FETCH_LIMIT").pipe(
9
- Config.withDefault(200),
10
- Config.map(positiveIntOr(200)),
11
- ),
12
- prPageSize: Config.int("GHUI_PR_PAGE_SIZE").pipe(
13
- Config.withDefault(50),
14
- Config.map(pageSizeOr(50)),
15
- ),
16
- })
17
-
18
- export const config = Effect.runSync(Effect.gen(function*() {
19
- return yield* appConfig
20
- }))
package/src/date.ts DELETED
@@ -1,20 +0,0 @@
1
- const DAY_MS = 24 * 60 * 60 * 1000
2
-
3
- export const formatShortDate = (date: Date) =>
4
- date.toLocaleDateString("en-US", { weekday: "short", month: "numeric", day: "numeric" })
5
-
6
- export const formatTimestamp = (date: Date) =>
7
- date.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" }).toLowerCase()
8
-
9
- export const daysOpen = (date: Date) => Math.max(0, Math.floor((Date.now() - date.getTime()) / DAY_MS))
10
-
11
- export const formatRelativeDate = (date: Date): string => {
12
- const days = daysOpen(date)
13
- if (days === 0) return "today"
14
- if (days === 1) return "yesterday"
15
- if (days < 7) return `${days} days ago`
16
- if (days < 14) return "last week"
17
- if (days < 30) return `${Math.floor(days / 7)} weeks ago`
18
- if (days < 60) return "last month"
19
- return `${Math.floor(days / 30)} months ago`
20
- }
package/src/domain.ts DELETED
@@ -1,149 +0,0 @@
1
- import { Schema } from "effect"
2
-
3
- export type LoadStatus = "loading" | "ready" | "error"
4
-
5
- export type PullRequestState = "open" | "closed" | "merged"
6
-
7
- export const pullRequestQueueModes = ["authored", "review", "assigned", "mentioned"] as const
8
- export type PullRequestUserQueueMode = (typeof pullRequestQueueModes)[number]
9
- export type PullRequestQueueMode = "repository" | PullRequestUserQueueMode
10
-
11
- export const pullRequestQueueLabels = {
12
- repository: "repository",
13
- authored: "authored",
14
- review: "review requested",
15
- assigned: "assigned",
16
- mentioned: "mentioned",
17
- } as const satisfies Record<PullRequestQueueMode, string>
18
-
19
- export const pullRequestQueueSearchQualifier = (mode: PullRequestQueueMode, repository: string | null) => {
20
- const qualifiers = {
21
- repository: repository ? `repo:${repository}` : "author:@me",
22
- authored: "author:@me",
23
- review: "review-requested:@me",
24
- assigned: "assignee:@me",
25
- mentioned: "mentions:@me",
26
- } as const satisfies Record<PullRequestQueueMode, string>
27
- return qualifiers[mode]
28
- }
29
-
30
- export type CheckConclusion = "success" | "failure" | "neutral" | "skipped" | "cancelled" | "timed_out"
31
-
32
- export type CheckRunStatus = "completed" | "in_progress" | "queued" | "pending"
33
-
34
- export type CheckRollupStatus = "passing" | "pending" | "failing" | "none"
35
-
36
- export type ReviewStatus = "draft" | "approved" | "changes" | "review" | "none"
37
-
38
- export type Mergeable = "mergeable" | "conflicting" | "unknown"
39
-
40
- // DiffCommentSide is the only literal type still consumed at runtime — GitHubService
41
- // uses it as a Schema inside PullRequestCommentSchema.
42
- export const DiffCommentSide = Schema.Literals(["LEFT", "RIGHT"])
43
- export type DiffCommentSide = Schema.Schema.Type<typeof DiffCommentSide>
44
-
45
- export type PullRequestMergeAction = "squash" | "auto" | "admin" | "disable-auto"
46
-
47
- export const pullRequestReviewEvents = ["COMMENT", "APPROVE", "REQUEST_CHANGES"] as const
48
- export type PullRequestReviewEvent = (typeof pullRequestReviewEvents)[number]
49
-
50
- export interface CheckItem {
51
- readonly name: string
52
- readonly status: CheckRunStatus
53
- readonly conclusion: CheckConclusion | null
54
- }
55
-
56
- export interface PullRequestLabel {
57
- readonly name: string
58
- readonly color: string | null
59
- }
60
-
61
- export interface CreatePullRequestCommentInput {
62
- readonly repository: string
63
- readonly number: number
64
- readonly commitId: string
65
- readonly path: string
66
- readonly line: number
67
- readonly side: DiffCommentSide
68
- readonly startLine?: number
69
- readonly startSide?: DiffCommentSide
70
- readonly body: string
71
- }
72
-
73
- export interface SubmitPullRequestReviewInput {
74
- readonly repository: string
75
- readonly number: number
76
- readonly event: PullRequestReviewEvent
77
- readonly body: string
78
- }
79
-
80
- export interface PullRequestReviewComment {
81
- readonly id: string
82
- readonly path: string
83
- readonly line: number
84
- readonly side: DiffCommentSide
85
- readonly author: string
86
- readonly body: string
87
- readonly createdAt: Date | null
88
- readonly url: string | null
89
- }
90
-
91
- export type PullRequestConversationItem =
92
- | {
93
- readonly _tag: "comment"
94
- readonly id: string
95
- readonly author: string
96
- readonly body: string
97
- readonly createdAt: Date | null
98
- readonly url: string | null
99
- }
100
- | ({ readonly _tag: "review-comment" } & PullRequestReviewComment)
101
-
102
- export interface PullRequestItem {
103
- readonly repository: string
104
- readonly author: string
105
- readonly headRefOid: string
106
- readonly number: number
107
- readonly title: string
108
- readonly body: string
109
- readonly labels: readonly PullRequestLabel[]
110
- readonly additions: number
111
- readonly deletions: number
112
- readonly changedFiles: number
113
- readonly state: PullRequestState
114
- readonly reviewStatus: ReviewStatus
115
- readonly checkStatus: CheckRollupStatus
116
- readonly checkSummary: string | null
117
- readonly checks: readonly CheckItem[]
118
- readonly autoMergeEnabled: boolean
119
- readonly detailLoaded: boolean
120
- readonly createdAt: Date
121
- readonly closedAt: Date | null
122
- readonly url: string
123
- }
124
-
125
- export interface PullRequestPage {
126
- readonly items: readonly PullRequestItem[]
127
- readonly endCursor: string | null
128
- readonly hasNextPage: boolean
129
- }
130
-
131
- export interface ListPullRequestPageInput {
132
- readonly mode: PullRequestQueueMode
133
- readonly repository: string | null
134
- readonly cursor: string | null
135
- readonly pageSize: number
136
- }
137
-
138
- export interface PullRequestMergeInfo {
139
- readonly repository: string
140
- readonly number: number
141
- readonly title: string
142
- readonly state: PullRequestState
143
- readonly isDraft: boolean
144
- readonly mergeable: Mergeable
145
- readonly reviewStatus: ReviewStatus
146
- readonly checkStatus: CheckRollupStatus
147
- readonly checkSummary: string | null
148
- readonly autoMergeEnabled: boolean
149
- }
package/src/errors.ts DELETED
@@ -1,10 +0,0 @@
1
- const hasStringField = <K extends string>(value: object, key: K): value is { [P in K]: string } & object =>
2
- key in value && typeof (value as Record<string, unknown>)[key] === "string"
3
-
4
- export const errorMessage = (error: unknown): string => {
5
- if (typeof error === "object" && error !== null) {
6
- if (hasStringField(error, "detail") && error.detail.length > 0) return error.detail
7
- if (hasStringField(error, "message") && error.message.length > 0) return error.message
8
- }
9
- return error instanceof Error ? error.message : String(error)
10
- }