github-issue-tower-defence-management 2.19.0 → 2.19.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 -1
- package/bin/adapter/entry-points/cli/index.js +0 -0
- package/bin/domain/usecases/OauthTokenSelectUseCase.js +1 -1
- package/package.json +5 -1
- package/types/domain/usecases/OauthTokenSelectUseCase.d.ts +1 -1
- package/.env.example +0 -7
- package/.gitattributes +0 -2
- package/.github/CODEOWNERS +0 -6
- package/.github/copilot-instructions.md +0 -6
- package/.github/dependabot.yml +0 -19
- package/.github/instructions/code-review.instructions.md +0 -50
- package/.github/workflows/api-created_issue_pr.yml +0 -97
- package/.github/workflows/ci-failure-issue-creator.yml +0 -48
- package/.github/workflows/close-manual-prs.yml +0 -80
- package/.github/workflows/codeql.yml +0 -107
- package/.github/workflows/commit-lint.yml +0 -85
- package/.github/workflows/configs/commitlint.config.cjs +0 -32
- package/.github/workflows/configs/commitlint.config.js +0 -32
- package/.github/workflows/console-ui.yml +0 -68
- package/.github/workflows/create-pr.yml +0 -116
- package/.github/workflows/empty-format-test-job.yml +0 -34
- package/.github/workflows/format.yml +0 -28
- package/.github/workflows/publish.yml +0 -111
- package/.github/workflows/secret-scan.yml +0 -51
- package/.github/workflows/test.yml +0 -58
- package/.github/workflows/umino-project.yml +0 -207
- package/.gitleaks.toml +0 -22
- package/.prettierignore +0 -31
- package/.prettierrc +0 -5
- package/CHANGELOG.md +0 -4155
- package/CONTRIBUTING.md +0 -60
- package/bin/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.js +0 -12
- package/bin/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.js.map +0 -1
- package/bin/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.js +0 -125
- package/bin/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.js.map +0 -1
- package/bin/adapter/repositories/issue/issueTimelineUtils.js +0 -38
- package/bin/adapter/repositories/issue/issueTimelineUtils.js.map +0 -1
- package/bin/domain/entities/UnansweredOwnerCall.js +0 -3
- package/bin/domain/entities/UnansweredOwnerCall.js.map +0 -1
- package/bin/domain/entities/WorkingTime.js +0 -3
- package/bin/domain/entities/WorkingTime.js.map +0 -1
- package/bin/domain/usecases/ChangeStatusLongInReviewIssueUseCase.js +0 -37
- package/bin/domain/usecases/ChangeStatusLongInReviewIssueUseCase.js.map +0 -1
- package/bin/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.js +0 -118
- package/bin/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.js.map +0 -1
- package/bin/domain/usecases/GenerateWorkingTimeReportUseCase.js +0 -180
- package/bin/domain/usecases/GenerateWorkingTimeReportUseCase.js.map +0 -1
- package/bin/domain/usecases/TriagerApprovalDispatchUseCase.js +0 -160
- package/bin/domain/usecases/TriagerApprovalDispatchUseCase.js.map +0 -1
- package/bin/domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository.js +0 -3
- package/bin/domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository.js.map +0 -1
- package/bin/domain/usecases/awaitingOwnerApprovalMessage.js +0 -7
- package/bin/domain/usecases/awaitingOwnerApprovalMessage.js.map +0 -1
- package/bin/domain/usecases/findLastAgentDeclaringReport.js +0 -12
- package/bin/domain/usecases/findLastAgentDeclaringReport.js.map +0 -1
- package/bin/domain/usecases/isPullRequestDeclaredUnnecessary.js +0 -25
- package/bin/domain/usecases/isPullRequestDeclaredUnnecessary.js.map +0 -1
- package/bin/domain/usecases/isWaitingForOwnerApproval.js +0 -16
- package/bin/domain/usecases/isWaitingForOwnerApproval.js.map +0 -1
- package/bin/domain/usecases/returnedToAwaitingWorkspaceMessage.js +0 -7
- package/bin/domain/usecases/returnedToAwaitingWorkspaceMessage.js.map +0 -1
- package/commitlint.config.js +0 -6
- package/eslint.config.mjs +0 -76
- package/jest.config.js +0 -84
- package/jest.setup.js +0 -1
- package/lib/buffer-equal-constant-time-compat/index.js +0 -17
- package/lib/buffer-equal-constant-time-compat/index.test.js +0 -54
- package/lib/buffer-equal-constant-time-compat/package.json +0 -5
- package/lib/typescript-compat/bin/tsc +0 -2
- package/lib/typescript-compat/bin/tsserver +0 -2
- package/lib/typescript-compat/lib/typescript.js +0 -1
- package/lib/typescript-compat/package.json +0 -13
- package/renovate.json +0 -46
- package/scripts/copyConsoleUiDist.mjs +0 -35
- package/scripts/defaultBranchTipVerify.sh +0 -23
- package/scripts/testWorkflowRunVerify.sh +0 -47
- package/scripts/verifyConsoleUiDist.mjs +0 -96
- package/src/adapter/ci/consoleUiWorkflowTimeout.test.ts +0 -61
- package/src/adapter/ci/publishTestWorkflowGate.test.ts +0 -653
- package/src/adapter/ci/workflowRunCancellation.test.ts +0 -72
- package/src/adapter/entry-points/cli/fleetConfig.test.ts +0 -553
- package/src/adapter/entry-points/cli/fleetConfig.ts +0 -266
- package/src/adapter/entry-points/cli/index.test.ts +0 -2837
- package/src/adapter/entry-points/cli/index.ts +0 -1488
- package/src/adapter/entry-points/cli/projectConfig.test.ts +0 -342
- package/src/adapter/entry-points/cli/projectConfig.ts +0 -500
- package/src/adapter/entry-points/console/consoleAirplaneSnapshotApi.test.ts +0 -658
- package/src/adapter/entry-points/console/consoleAirplaneSnapshotApi.ts +0 -482
- package/src/adapter/entry-points/console/consoleDataDelivery.test.ts +0 -222
- package/src/adapter/entry-points/console/consoleDataDelivery.ts +0 -134
- package/src/adapter/entry-points/console/consoleDoneStore.test.ts +0 -169
- package/src/adapter/entry-points/console/consoleDoneStore.ts +0 -139
- package/src/adapter/entry-points/console/consoleGithubTokenResolver.test.ts +0 -275
- package/src/adapter/entry-points/console/consoleGithubTokenResolver.ts +0 -113
- package/src/adapter/entry-points/console/consoleImageProxy.test.ts +0 -140
- package/src/adapter/entry-points/console/consoleImageProxy.ts +0 -111
- package/src/adapter/entry-points/console/consoleItemUrlLookup.test.ts +0 -63
- package/src/adapter/entry-points/console/consoleItemUrlLookup.ts +0 -66
- package/src/adapter/entry-points/console/consoleOperationApi.test.ts +0 -2523
- package/src/adapter/entry-points/console/consoleOperationApi.ts +0 -936
- package/src/adapter/entry-points/console/consoleProjectResolver.test.ts +0 -246
- package/src/adapter/entry-points/console/consoleProjectResolver.ts +0 -111
- package/src/adapter/entry-points/console/consoleReadApi.test.ts +0 -714
- package/src/adapter/entry-points/console/consoleReadApi.ts +0 -361
- package/src/adapter/entry-points/console/consoleTabNames.test.ts +0 -52
- package/src/adapter/entry-points/console/consoleTabNames.ts +0 -37
- package/src/adapter/entry-points/console/consoleUiDistSync.test.ts +0 -66
- package/src/adapter/entry-points/console/dashboardComposeService.test.ts +0 -595
- package/src/adapter/entry-points/console/dashboardComposeService.ts +0 -256
- package/src/adapter/entry-points/console/dashboardEmitterComposerKey.test.ts +0 -180
- package/src/adapter/entry-points/console/ensureConsoleRunning.test.ts +0 -115
- package/src/adapter/entry-points/console/ensureConsoleRunning.ts +0 -64
- package/src/adapter/entry-points/console/ui/.storybook/main.ts +0 -12
- package/src/adapter/entry-points/console/ui/.storybook/preview.ts +0 -15
- package/src/adapter/entry-points/console/ui/biome.json +0 -53
- package/src/adapter/entry-points/console/ui/components.json +0 -20
- package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +0 -725
- package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +0 -831
- package/src/adapter/entry-points/console/ui/e2e/playwright.config.ts +0 -30
- package/src/adapter/entry-points/console/ui/e2e/tsconfig.json +0 -18
- package/src/adapter/entry-points/console/ui/index.html +0 -12
- package/src/adapter/entry-points/console/ui/jest-globals.cjs +0 -26
- package/src/adapter/entry-points/console/ui/jest.setup.ts +0 -1
- package/src/adapter/entry-points/console/ui/jest.styleMock.js +0 -1
- package/src/adapter/entry-points/console/ui/public/sw.js +0 -60
- package/src/adapter/entry-points/console/ui/src/components/ui/badge.stories.tsx +0 -35
- package/src/adapter/entry-points/console/ui/src/components/ui/badge.tsx +0 -28
- package/src/adapter/entry-points/console/ui/src/components/ui/button.stories.tsx +0 -34
- package/src/adapter/entry-points/console/ui/src/components/ui/button.tsx +0 -50
- package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleCopyCodeButton.stories.tsx +0 -28
- package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleCopyCodeButton.test.tsx +0 -65
- package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleCopyCodeButton.tsx +0 -19
- package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.stories.tsx +0 -61
- package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.test.tsx +0 -199
- package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.tsx +0 -166
- package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagram.stories.tsx +0 -22
- package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagram.test.tsx +0 -74
- package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagram.tsx +0 -84
- package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagramModalScreen.stories.tsx +0 -27
- package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagramModalScreen.test.tsx +0 -85
- package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagramModalScreen.tsx +0 -97
- package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleReferenceLink.stories.tsx +0 -76
- package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleReferenceLink.test.tsx +0 -56
- package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleReferenceLink.tsx +0 -39
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleChangedFileList.stories.tsx +0 -32
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleChangedFileList.test.tsx +0 -105
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleChangedFileList.tsx +0 -157
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentComposer.stories.tsx +0 -68
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentComposer.test.tsx +0 -612
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentComposer.tsx +0 -276
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentList.stories.tsx +0 -33
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentList.test.tsx +0 -57
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentList.tsx +0 -76
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommitList.stories.tsx +0 -25
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommitList.test.tsx +0 -55
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommitList.tsx +0 -49
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCopyUrlButton.stories.tsx +0 -24
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCopyUrlButton.test.tsx +0 -63
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCopyUrlButton.tsx +0 -19
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFetchFailureAlert.stories.tsx +0 -33
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFetchFailureAlert.test.tsx +0 -62
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFetchFailureAlert.tsx +0 -59
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.stories.tsx +0 -38
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.test.tsx +0 -151
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.tsx +0 -245
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.stories.tsx +0 -514
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.test.tsx +0 -727
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.tsx +0 -387
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemIcon.stories.tsx +0 -82
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemIcon.test.tsx +0 -52
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemIcon.tsx +0 -32
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.stories.tsx +0 -48
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.test.tsx +0 -235
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.tsx +0 -145
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestMergeableChip.stories.tsx +0 -29
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestMergeableChip.test.tsx +0 -30
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestMergeableChip.tsx +0 -50
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestStatusBadges.stories.tsx +0 -38
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestStatusBadges.test.tsx +0 -54
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestStatusBadges.tsx +0 -60
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleAirplaneModeButton.stories.tsx +0 -56
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleAirplaneModeButton.test.tsx +0 -111
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleAirplaneModeButton.tsx +0 -92
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsolePanel.stories.tsx +0 -51
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsolePanel.test.tsx +0 -59
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsolePanel.tsx +0 -39
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.stories.tsx +0 -207
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.test.tsx +0 -332
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.tsx +0 -211
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTimerSettingsModalDialog.stories.tsx +0 -49
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTimerSettingsModalDialog.test.tsx +0 -157
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTimerSettingsModalDialog.tsx +0 -118
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleCopyStoryNameButton.stories.tsx +0 -23
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleCopyStoryNameButton.test.tsx +0 -40
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleCopyStoryNameButton.tsx +0 -17
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.stories.tsx +0 -101
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.test.tsx +0 -102
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.tsx +0 -75
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.stories.tsx +0 -40
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.test.tsx +0 -213
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.tsx +0 -115
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedItemRow.stories.tsx +0 -50
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedItemRow.test.tsx +0 -98
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedItemRow.tsx +0 -66
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedList.stories.tsx +0 -69
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedList.test.tsx +0 -101
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleQueuedList.tsx +0 -75
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.stories.tsx +0 -137
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.test.tsx +0 -554
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.tsx +0 -400
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.stories.tsx +0 -30
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.test.tsx +0 -111
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.tsx +0 -91
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStorySummary.stories.tsx +0 -27
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStorySummary.test.tsx +0 -24
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStorySummary.tsx +0 -28
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleCloseActions.stories.tsx +0 -14
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleCloseActions.test.tsx +0 -21
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleCloseActions.tsx +0 -26
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleDangerousActions.stories.tsx +0 -14
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleDangerousActions.test.tsx +0 -40
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleDangerousActions.tsx +0 -37
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleErrorToast.stories.tsx +0 -33
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleNextActionDateActions.stories.tsx +0 -20
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleNextActionDateActions.test.tsx +0 -48
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleNextActionDateActions.tsx +0 -42
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOfflinePendingActionsPanel.stories.tsx +0 -115
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOfflinePendingActionsPanel.test.tsx +0 -262
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOfflinePendingActionsPanel.tsx +0 -123
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOperationMenu.stories.tsx +0 -65
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOperationMenu.test.tsx +0 -159
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOperationMenu.tsx +0 -55
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsolePullRequestReviewActions.stories.tsx +0 -20
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsolePullRequestReviewActions.test.tsx +0 -93
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsolePullRequestReviewActions.tsx +0 -45
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStatusActions.stories.tsx +0 -17
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStatusActions.test.tsx +0 -49
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStatusActions.tsx +0 -66
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleUndoToast.stories.tsx +0 -47
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleUndoToast.test.tsx +0 -95
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleUndoToast.tsx +0 -62
- package/src/adapter/entry-points/console/ui/src/features/console/components/shared/ConsoleClipboardCopyButton.stories.tsx +0 -32
- package/src/adapter/entry-points/console/ui/src/features/console/components/shared/ConsoleClipboardCopyButton.test.tsx +0 -101
- package/src/adapter/entry-points/console/ui/src/features/console/components/shared/ConsoleClipboardCopyButton.tsx +0 -80
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useAirplaneMode.test.ts +0 -304
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useAirplaneMode.ts +0 -167
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleActionQueue.test.ts +0 -498
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleActionQueue.ts +0 -277
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.test.ts +0 -99
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.ts +0 -90
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleDetailPrefetch.test.ts +0 -110
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleDetailPrefetch.ts +0 -83
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleFeaturesConfig.test.ts +0 -51
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleFeaturesConfig.ts +0 -39
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.test.ts +0 -229
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.ts +0 -194
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleNavigation.test.ts +0 -151
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleNavigation.ts +0 -128
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.test.ts +0 -629
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.ts +0 -417
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOverlay.test.ts +0 -40
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOverlay.ts +0 -71
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsolePjcode.test.ts +0 -24
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsolePjcode.ts +0 -17
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleProjectList.test.ts +0 -97
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleProjectList.ts +0 -44
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleProjectTimer.test.ts +0 -59
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleProjectTimer.ts +0 -26
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleReferenceLink.test.ts +0 -48
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleReferenceLink.ts +0 -39
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleResource.test.ts +0 -41
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleResource.ts +0 -57
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleSwipeNavigation.test.ts +0 -115
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleSwipeNavigation.ts +0 -115
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.test.ts +0 -546
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.ts +0 -278
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTimerSettings.test.ts +0 -110
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTimerSettings.ts +0 -70
- package/src/adapter/entry-points/console/ui/src/features/console/lib/airplaneSnapshot.test.ts +0 -387
- package/src/adapter/entry-points/console/ui/src/features/console/lib/airplaneSnapshot.ts +0 -407
- package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.test.ts +0 -446
- package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +0 -533
- package/src/adapter/entry-points/console/ui/src/features/console/lib/imageProxy.test.ts +0 -78
- package/src/adapter/entry-points/console/ui/src/features/console/lib/imageProxy.ts +0 -34
- package/src/adapter/entry-points/console/ui/src/features/console/lib/markdown.test.ts +0 -252
- package/src/adapter/entry-points/console/ui/src/features/console/lib/markdown.ts +0 -207
- package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidLoader.test.ts +0 -27
- package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidLoader.ts +0 -71
- package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidZoom.test.ts +0 -88
- package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidZoom.ts +0 -34
- package/src/adapter/entry-points/console/ui/src/features/console/lib/navigation.ts +0 -7
- package/src/adapter/entry-points/console/ui/src/features/console/lib/resourceCache.test.ts +0 -104
- package/src/adapter/entry-points/console/ui/src/features/console/lib/resourceCache.ts +0 -79
- package/src/adapter/entry-points/console/ui/src/features/console/logic/actionToast.test.ts +0 -200
- package/src/adapter/entry-points/console/ui/src/features/console/logic/actionToast.ts +0 -122
- package/src/adapter/entry-points/console/ui/src/features/console/logic/clipboard.test.ts +0 -90
- package/src/adapter/entry-points/console/ui/src/features/console/logic/clipboard.ts +0 -37
- package/src/adapter/entry-points/console/ui/src/features/console/logic/colors.test.ts +0 -34
- package/src/adapter/entry-points/console/ui/src/features/console/logic/colors.ts +0 -85
- package/src/adapter/entry-points/console/ui/src/features/console/logic/diff.test.ts +0 -81
- package/src/adapter/entry-points/console/ui/src/features/console/logic/diff.ts +0 -61
- package/src/adapter/entry-points/console/ui/src/features/console/logic/fileStatus.test.ts +0 -35
- package/src/adapter/entry-points/console/ui/src/features/console/logic/fileStatus.ts +0 -21
- package/src/adapter/entry-points/console/ui/src/features/console/logic/fileTree.test.ts +0 -96
- package/src/adapter/entry-points/console/ui/src/features/console/logic/fileTree.ts +0 -93
- package/src/adapter/entry-points/console/ui/src/features/console/logic/grouping.test.ts +0 -229
- package/src/adapter/entry-points/console/ui/src/features/console/logic/grouping.ts +0 -106
- package/src/adapter/entry-points/console/ui/src/features/console/logic/imageFile.test.ts +0 -18
- package/src/adapter/entry-points/console/ui/src/features/console/logic/imageFile.ts +0 -16
- package/src/adapter/entry-points/console/ui/src/features/console/logic/itemIcons.test.ts +0 -97
- package/src/adapter/entry-points/console/ui/src/features/console/logic/itemIcons.ts +0 -95
- package/src/adapter/entry-points/console/ui/src/features/console/logic/navigation.test.ts +0 -53
- package/src/adapter/entry-points/console/ui/src/features/console/logic/navigation.ts +0 -33
- package/src/adapter/entry-points/console/ui/src/features/console/logic/operations.test.ts +0 -72
- package/src/adapter/entry-points/console/ui/src/features/console/logic/operations.ts +0 -61
- package/src/adapter/entry-points/console/ui/src/features/console/logic/overlay.test.ts +0 -325
- package/src/adapter/entry-points/console/ui/src/features/console/logic/overlay.ts +0 -94
- package/src/adapter/entry-points/console/ui/src/features/console/logic/postedComments.test.ts +0 -44
- package/src/adapter/entry-points/console/ui/src/features/console/logic/postedComments.ts +0 -17
- package/src/adapter/entry-points/console/ui/src/features/console/logic/references.test.ts +0 -103
- package/src/adapter/entry-points/console/ui/src/features/console/logic/references.ts +0 -64
- package/src/adapter/entry-points/console/ui/src/features/console/logic/relativeTime.test.ts +0 -74
- package/src/adapter/entry-points/console/ui/src/features/console/logic/relativeTime.ts +0 -53
- package/src/adapter/entry-points/console/ui/src/features/console/logic/swipe.test.ts +0 -34
- package/src/adapter/entry-points/console/ui/src/features/console/logic/swipe.ts +0 -28
- package/src/adapter/entry-points/console/ui/src/features/console/logic/tabAdvance.test.ts +0 -89
- package/src/adapter/entry-points/console/ui/src/features/console/logic/tabAdvance.ts +0 -30
- package/src/adapter/entry-points/console/ui/src/features/console/logic/timerSettings.test.ts +0 -61
- package/src/adapter/entry-points/console/ui/src/features/console/logic/timerSettings.ts +0 -77
- package/src/adapter/entry-points/console/ui/src/features/console/logic/types.ts +0 -173
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.test.tsx +0 -629
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.tsx +0 -351
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +0 -1469
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +0 -736
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleReferenceLinkContainer.tsx +0 -25
- package/src/adapter/entry-points/console/ui/src/features/console/testing/fixtures.ts +0 -418
- package/src/adapter/entry-points/console/ui/src/index.css +0 -1592
- package/src/adapter/entry-points/console/ui/src/lib/utils.ts +0 -4
- package/src/adapter/entry-points/console/ui/src/main.tsx +0 -21
- package/src/adapter/entry-points/console/ui/src/vite-env.d.ts +0 -1
- package/src/adapter/entry-points/console/ui/tsconfig.json +0 -26
- package/src/adapter/entry-points/console/ui/vite.config.ts +0 -24
- package/src/adapter/entry-points/console/ui-dist/assets/index-BYDbK1Fj.js +0 -88
- package/src/adapter/entry-points/console/ui-dist/assets/index-KyQISRuQ.css +0 -2
- package/src/adapter/entry-points/console/ui-dist/index.html +0 -13
- package/src/adapter/entry-points/console/ui-dist/sw.js +0 -60
- package/src/adapter/entry-points/console/webServer.test.ts +0 -2945
- package/src/adapter/entry-points/console/webServer.ts +0 -940
- package/src/adapter/entry-points/function/getStoryObjectMap.ts +0 -3
- package/src/adapter/entry-points/handlers/FileSystemConsoleTabsRepository.test.ts +0 -218
- package/src/adapter/entry-points/handlers/FileSystemConsoleTabsRepository.ts +0 -113
- package/src/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.test.ts +0 -110
- package/src/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.ts +0 -116
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.test.ts +0 -859
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +0 -984
- package/src/adapter/entry-points/handlers/InTmuxByHumanSessionTokenCountHandler.test.ts +0 -126
- package/src/adapter/entry-points/handlers/InTmuxByHumanSessionTokenCountHandler.ts +0 -80
- package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.test.ts +0 -491
- package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.ts +0 -137
- package/src/adapter/entry-points/handlers/OauthTokenSelectHandler.test.ts +0 -508
- package/src/adapter/entry-points/handlers/OauthTokenSelectHandler.ts +0 -156
- package/src/adapter/entry-points/handlers/consoleListsWriter.test.ts +0 -332
- package/src/adapter/entry-points/handlers/consoleListsWriter.ts +0 -60
- package/src/adapter/entry-points/handlers/dashboardRowWriter.test.ts +0 -112
- package/src/adapter/entry-points/handlers/dashboardRowWriter.ts +0 -51
- package/src/adapter/entry-points/handlers/inTmuxByHumanDataWriter.test.ts +0 -285
- package/src/adapter/entry-points/handlers/inTmuxByHumanDataWriter.ts +0 -109
- package/src/adapter/entry-points/handlers/inTmuxByHumanSessionReconciler.test.ts +0 -170
- package/src/adapter/entry-points/handlers/inTmuxByHumanSessionReconciler.ts +0 -40
- package/src/adapter/entry-points/handlers/machineStatusWriter.test.ts +0 -191
- package/src/adapter/entry-points/handlers/machineStatusWriter.ts +0 -110
- package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.test.ts +0 -548
- package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.ts +0 -192
- package/src/adapter/entry-points/handlers/ownerCallFileCleaner.test.ts +0 -111
- package/src/adapter/entry-points/handlers/ownerCallFileCleaner.ts +0 -25
- package/src/adapter/entry-points/handlers/ownerCallFileStore.test.ts +0 -286
- package/src/adapter/entry-points/handlers/ownerCallFileStore.ts +0 -145
- package/src/adapter/entry-points/handlers/ownerReplyMarkerDirectoryResolve.test.ts +0 -55
- package/src/adapter/entry-points/handlers/ownerReplyMarkerDirectoryResolve.ts +0 -36
- package/src/adapter/entry-points/handlers/resetDegeneratedTmuxSessions.test.ts +0 -72
- package/src/adapter/entry-points/handlers/resetDegeneratedTmuxSessions.ts +0 -74
- package/src/adapter/entry-points/handlers/rotationOrderFileWriter.test.ts +0 -181
- package/src/adapter/entry-points/handlers/rotationOrderFileWriter.ts +0 -18
- package/src/adapter/entry-points/handlers/situationFileWriter.test.ts +0 -489
- package/src/adapter/entry-points/handlers/situationFileWriter.ts +0 -176
- package/src/adapter/entry-points/handlers/staleTmuxSessionCleaner.test.ts +0 -169
- package/src/adapter/entry-points/handlers/staleTmuxSessionCleaner.ts +0 -32
- package/src/adapter/entry-points/handlers/subscriptionDisabledTokenHandler.ts +0 -41
- package/src/adapter/entry-points/handlers/tokenExhaustionHandover.ts +0 -81
- package/src/adapter/entry-points/handlers/tokenStatusWriter.test.ts +0 -266
- package/src/adapter/entry-points/handlers/tokenStatusWriter.ts +0 -262
- package/src/adapter/proxy/ClaudeMessageResponseParser.test.ts +0 -211
- package/src/adapter/proxy/ClaudeMessageResponseParser.ts +0 -181
- package/src/adapter/proxy/RateLimitCache.test.ts +0 -1077
- package/src/adapter/proxy/RateLimitCache.ts +0 -385
- package/src/adapter/proxy/TokenListLoader.test.ts +0 -183
- package/src/adapter/proxy/TokenListLoader.ts +0 -75
- package/src/adapter/proxy/ensureProxyRunning.test.ts +0 -85
- package/src/adapter/proxy/ensureProxyRunning.ts +0 -41
- package/src/adapter/proxy/proxyEntry.test.ts +0 -552
- package/src/adapter/proxy/proxyEntry.ts +0 -214
- package/src/adapter/repositories/BaseGitHubRepository.test.ts +0 -26
- package/src/adapter/repositories/BaseGitHubRepository.ts +0 -26
- package/src/adapter/repositories/BrowserGitHubProjectRepository.test.ts +0 -108
- package/src/adapter/repositories/BrowserGitHubProjectRepository.ts +0 -118
- package/src/adapter/repositories/CliGitHubGraphqlRateLimitRepository.test.ts +0 -71
- package/src/adapter/repositories/CliGitHubGraphqlRateLimitRepository.ts +0 -21
- package/src/adapter/repositories/ConfigurableSilentSessionMessageComposer.test.ts +0 -324
- package/src/adapter/repositories/ConfigurableSilentSessionMessageComposer.ts +0 -148
- package/src/adapter/repositories/FetchWebhookRepository.ts +0 -8
- package/src/adapter/repositories/FileHandoverStateRepository.test.ts +0 -114
- package/src/adapter/repositories/FileHandoverStateRepository.ts +0 -73
- package/src/adapter/repositories/FileSystemInteractiveLiveSessionTranscriptResolver.test.ts +0 -514
- package/src/adapter/repositories/FileSystemInteractiveLiveSessionTranscriptResolver.ts +0 -109
- package/src/adapter/repositories/FileSystemKevReportWatermarkRepository.test.ts +0 -284
- package/src/adapter/repositories/FileSystemKevReportWatermarkRepository.ts +0 -128
- package/src/adapter/repositories/FileSystemSessionAssistantTurnsRepository.test.ts +0 -138
- package/src/adapter/repositories/FileSystemSessionAssistantTurnsRepository.ts +0 -113
- package/src/adapter/repositories/FileSystemSessionDegenerationCooldownStateRepository.test.ts +0 -121
- package/src/adapter/repositories/FileSystemSessionDegenerationCooldownStateRepository.ts +0 -103
- package/src/adapter/repositories/FileSystemSessionOutputActivityRepository.test.ts +0 -360
- package/src/adapter/repositories/FileSystemSessionOutputActivityRepository.ts +0 -77
- package/src/adapter/repositories/FileSystemSessionRecordReader.test.ts +0 -59
- package/src/adapter/repositories/FileSystemSessionRecordReader.ts +0 -31
- package/src/adapter/repositories/FileSystemSilentSessionCandidateStateRepository.test.ts +0 -287
- package/src/adapter/repositories/FileSystemSilentSessionCandidateStateRepository.ts +0 -125
- package/src/adapter/repositories/FileSystemSilentSessionHubTaskStatusCacheRepository.test.ts +0 -285
- package/src/adapter/repositories/FileSystemSilentSessionHubTaskStatusCacheRepository.ts +0 -119
- package/src/adapter/repositories/FileSystemSubAgentLivenessResolver.test.ts +0 -237
- package/src/adapter/repositories/FileSystemSubAgentLivenessResolver.ts +0 -57
- package/src/adapter/repositories/FileSystemSubAgentSilentSecondsResolver.test.ts +0 -56
- package/src/adapter/repositories/FileSystemSubAgentSilentSecondsResolver.ts +0 -32
- package/src/adapter/repositories/FileSystemSubAgentTranscriptDirectoryResolver.test.ts +0 -68
- package/src/adapter/repositories/FileSystemSubAgentTranscriptDirectoryResolver.ts +0 -23
- package/src/adapter/repositories/GitHubIssueCheckpointRepository.test.ts +0 -51
- package/src/adapter/repositories/GitHubIssueCheckpointRepository.ts +0 -47
- package/src/adapter/repositories/GitHubIssueCommentRepository.test.ts +0 -278
- package/src/adapter/repositories/GitHubIssueCommentRepository.ts +0 -109
- package/src/adapter/repositories/GoogleSpreadsheetRepository.integration.test.ts +0 -265
- package/src/adapter/repositories/GoogleSpreadsheetRepository.test.ts +0 -431
- package/src/adapter/repositories/GoogleSpreadsheetRepository.ts +0 -268
- package/src/adapter/repositories/GraphqlProjectRepository.diskCache.test.ts +0 -272
- package/src/adapter/repositories/GraphqlProjectRepository.errorHandling.test.ts +0 -111
- package/src/adapter/repositories/GraphqlProjectRepository.fetchProjectId.test.ts +0 -254
- package/src/adapter/repositories/GraphqlProjectRepository.test.ts +0 -209
- package/src/adapter/repositories/GraphqlProjectRepository.ts +0 -707
- package/src/adapter/repositories/GraphqlProjectRepositoryProjectLocation.test.ts +0 -297
- package/src/adapter/repositories/KyHttpRepository.ts +0 -8
- package/src/adapter/repositories/KySlackRepository.test.ts +0 -172
- package/src/adapter/repositories/KySlackRepository.ts +0 -298
- package/src/adapter/repositories/LocalCommandIssueAttachmentRepository.test.ts +0 -193
- package/src/adapter/repositories/LocalCommandIssueAttachmentRepository.ts +0 -106
- package/src/adapter/repositories/LocalProcessLiveSessionProcessSnapshotProvider.test.ts +0 -190
- package/src/adapter/repositories/LocalProcessLiveSessionProcessSnapshotProvider.ts +0 -102
- package/src/adapter/repositories/LocalStorageCacheRepository.test.ts +0 -265
- package/src/adapter/repositories/LocalStorageCacheRepository.ts +0 -79
- package/src/adapter/repositories/LocalStorageRepository.integration.test.ts +0 -142
- package/src/adapter/repositories/LocalStorageRepository.test.ts +0 -173
- package/src/adapter/repositories/LocalStorageRepository.ts +0 -30
- package/src/adapter/repositories/NoUnansweredOwnerCallStatusProvider.test.ts +0 -15
- package/src/adapter/repositories/NoUnansweredOwnerCallStatusProvider.ts +0 -9
- package/src/adapter/repositories/NodeLocalCommandRunner.largeOutput.test.ts +0 -16
- package/src/adapter/repositories/NodeLocalCommandRunner.test.ts +0 -118
- package/src/adapter/repositories/NodeLocalCommandRunner.ts +0 -66
- package/src/adapter/repositories/NodeProcessSignalRepository.test.ts +0 -82
- package/src/adapter/repositories/NodeProcessSignalRepository.ts +0 -47
- package/src/adapter/repositories/NodeSubAgentProcessLister.test.ts +0 -44
- package/src/adapter/repositories/NodeSubAgentProcessLister.ts +0 -40
- package/src/adapter/repositories/NodeTmuxSessionRepository.test.ts +0 -546
- package/src/adapter/repositories/NodeTmuxSessionRepository.ts +0 -245
- package/src/adapter/repositories/OauthAPIClaudeRepository.ts +0 -279
- package/src/adapter/repositories/OauthAPIProxyClaudeRepository.test.ts +0 -166
- package/src/adapter/repositories/OauthAPIProxyClaudeRepository.ts +0 -32
- package/src/adapter/repositories/OauthProxyClaudeRepository.test.ts +0 -428
- package/src/adapter/repositories/OauthProxyClaudeRepository.ts +0 -124
- package/src/adapter/repositories/ProcClaudeHandoverSessionRepository.test.ts +0 -242
- package/src/adapter/repositories/ProcClaudeHandoverSessionRepository.ts +0 -261
- package/src/adapter/repositories/ProcClaudeInteractiveSessionRepository.test.ts +0 -184
- package/src/adapter/repositories/ProcClaudeInteractiveSessionRepository.ts +0 -138
- package/src/adapter/repositories/ProcClaudeLiveSessionRepository.test.ts +0 -208
- package/src/adapter/repositories/ProcClaudeLiveSessionRepository.ts +0 -130
- package/src/adapter/repositories/ProcFsProcessEnvironReader.test.ts +0 -60
- package/src/adapter/repositories/ProcFsProcessEnvironReader.ts +0 -38
- package/src/adapter/repositories/ProcHostMetricsRepository.test.ts +0 -207
- package/src/adapter/repositories/ProcHostMetricsRepository.ts +0 -181
- package/src/adapter/repositories/ProcTakeOwnershipSpawnRepository.test.ts +0 -231
- package/src/adapter/repositories/ProcTakeOwnershipSpawnRepository.ts +0 -134
- package/src/adapter/repositories/ProcessListSessionSubAgentActivityRepository.test.ts +0 -121
- package/src/adapter/repositories/ProcessListSessionSubAgentActivityRepository.ts +0 -58
- package/src/adapter/repositories/ProjectIssuesCacheRepository.test.ts +0 -218
- package/src/adapter/repositories/ProjectIssuesCacheRepository.ts +0 -141
- package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.test.ts +0 -1195
- package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.ts +0 -185
- package/src/adapter/repositories/ProxyRateLimitCacheRepository.test.ts +0 -116
- package/src/adapter/repositories/ProxyRateLimitCacheRepository.ts +0 -67
- package/src/adapter/repositories/RateLimitSnapshotRepository.ts +0 -50
- package/src/adapter/repositories/RealSleeper.test.ts +0 -19
- package/src/adapter/repositories/RealSleeper.ts +0 -12
- package/src/adapter/repositories/RestProjectRepository.test.ts +0 -277
- package/src/adapter/repositories/RestProjectRepository.ts +0 -129
- package/src/adapter/repositories/SqliteClaudeMessageResponseRepository.test.ts +0 -313
- package/src/adapter/repositories/SqliteClaudeMessageResponseRepository.ts +0 -164
- package/src/adapter/repositories/SystemDateRepository.test.ts +0 -60
- package/src/adapter/repositories/SystemDateRepository.ts +0 -34
- package/src/adapter/repositories/TmuxSilentSessionNotificationRepository.test.ts +0 -253
- package/src/adapter/repositories/TmuxSilentSessionNotificationRepository.ts +0 -158
- package/src/adapter/repositories/TranscriptOwnerCallStatusProvider.queuedReply.test.ts +0 -171
- package/src/adapter/repositories/TranscriptOwnerCallStatusProvider.singleScan.test.ts +0 -86
- package/src/adapter/repositories/TranscriptOwnerCallStatusProvider.test.ts +0 -996
- package/src/adapter/repositories/TranscriptOwnerCallStatusProvider.ts +0 -396
- package/src/adapter/repositories/TranscriptRefusalTailStatusProvider.test.ts +0 -208
- package/src/adapter/repositories/TranscriptRefusalTailStatusProvider.ts +0 -80
- package/src/adapter/repositories/TranscriptSessionSubAgentActivityRepository.test.ts +0 -1674
- package/src/adapter/repositories/TranscriptSessionSubAgentActivityRepository.ts +0 -459
- package/src/adapter/repositories/clSessionScopeUnitName.test.ts +0 -76
- package/src/adapter/repositories/clSessionScopeUnitName.ts +0 -5
- package/src/adapter/repositories/clSessionScopeUnitNameFromCgroupContent.test.ts +0 -34
- package/src/adapter/repositories/clSessionScopeUnitNameFromCgroupContent.ts +0 -8
- package/src/adapter/repositories/githubGraphqlClient.test.ts +0 -479
- package/src/adapter/repositories/githubGraphqlClient.ts +0 -243
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +0 -5182
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +0 -3303
- package/src/adapter/repositories/issue/ApiV3IssueRepository.test.ts +0 -46
- package/src/adapter/repositories/issue/ApiV3IssueRepository.ts +0 -88
- package/src/adapter/repositories/issue/GraphqlProjectItemRepository.test.ts +0 -1776
- package/src/adapter/repositories/issue/GraphqlProjectItemRepository.ts +0 -1552
- package/src/adapter/repositories/issue/RestIssueRepository.test.ts +0 -393
- package/src/adapter/repositories/issue/RestIssueRepository.ts +0 -252
- package/src/adapter/repositories/issue/gitHubRawUrl.test.ts +0 -29
- package/src/adapter/repositories/issue/gitHubRawUrl.ts +0 -30
- package/src/adapter/repositories/issue/githubRateLimitRetry.test.ts +0 -167
- package/src/adapter/repositories/issue/githubRateLimitRetry.ts +0 -109
- package/src/adapter/repositories/localStorageCacheDirectory.test.ts +0 -81
- package/src/adapter/repositories/localStorageCacheDirectory.ts +0 -13
- package/src/adapter/repositories/projectFieldDefinition.test.ts +0 -191
- package/src/adapter/repositories/projectFieldDefinition.ts +0 -144
- package/src/adapter/repositories/utils.test.ts +0 -55
- package/src/adapter/repositories/utils.ts +0 -45
- package/src/domain/entities/ClaudeHandoverSession.ts +0 -12
- package/src/domain/entities/ClaudeMessageResponse.ts +0 -31
- package/src/domain/entities/ClaudeTokenUsage.ts +0 -17
- package/src/domain/entities/ClaudeWindowUsage.ts +0 -5
- package/src/domain/entities/Comment.ts +0 -5
- package/src/domain/entities/InteractiveLiveSession.ts +0 -6
- package/src/domain/entities/Issue.ts +0 -31
- package/src/domain/entities/KevReportWatermark.ts +0 -4
- package/src/domain/entities/LiveSessionActivitySnapshot.ts +0 -14
- package/src/domain/entities/LiveSessionOutputActivity.ts +0 -4
- package/src/domain/entities/LiveSessionProcessSnapshot.ts +0 -18
- package/src/domain/entities/LiveTmuxSession.ts +0 -4
- package/src/domain/entities/Member.ts +0 -3
- package/src/domain/entities/OutputDegeneration.ts +0 -10
- package/src/domain/entities/Project.ts +0 -88
- package/src/domain/entities/ProjectField.ts +0 -3
- package/src/domain/entities/ProjectFieldName.ts +0 -2
- package/src/domain/entities/ProjectFieldSingleSelect.ts +0 -8
- package/src/domain/entities/ProjectFieldSingleSelectOption.ts +0 -8
- package/src/domain/entities/RequiredProjectField.ts +0 -68
- package/src/domain/entities/SearchedIssue.ts +0 -11
- package/src/domain/entities/StoryObjectMap.ts +0 -12
- package/src/domain/entities/TokenExhaustionHandoverState.ts +0 -8
- package/src/domain/entities/WorkflowStatus.ts +0 -66
- package/src/domain/usecases/ActionAnnouncementUseCase.test.ts +0 -91
- package/src/domain/usecases/ActionAnnouncementUseCase.ts +0 -78
- package/src/domain/usecases/AgentDesignationLabelAdoptUseCase.test.ts +0 -222
- package/src/domain/usecases/AgentDesignationLabelAdoptUseCase.ts +0 -76
- package/src/domain/usecases/AnalyzeProblemByIssueUseCase.test.ts +0 -118
- package/src/domain/usecases/AnalyzeProblemByIssueUseCase.ts +0 -112
- package/src/domain/usecases/AnalyzeStoriesUseCase.test.ts +0 -82
- package/src/domain/usecases/AnalyzeStoriesUseCase.ts +0 -296
- package/src/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.test.ts +0 -578
- package/src/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.ts +0 -137
- package/src/domain/usecases/CONTRIBUTING_FOR_TEST.md +0 -259
- package/src/domain/usecases/ChangeStatusByStoryColorUseCase.test.ts +0 -437
- package/src/domain/usecases/ChangeStatusByStoryColorUseCase.ts +0 -80
- package/src/domain/usecases/ChangeTargetPullRequestApprover.test.ts +0 -248
- package/src/domain/usecases/ChangeTargetPullRequestApprover.ts +0 -69
- package/src/domain/usecases/CheckIssueReviewReadinessUseCase.test.ts +0 -411
- package/src/domain/usecases/CheckIssueReviewReadinessUseCase.ts +0 -142
- package/src/domain/usecases/ClearDependedIssueURLUseCase.test.ts +0 -1120
- package/src/domain/usecases/ClearDependedIssueURLUseCase.ts +0 -161
- package/src/domain/usecases/ClearPastNextActionDateHourUseCase.test.ts +0 -345
- package/src/domain/usecases/ClearPastNextActionDateHourUseCase.ts +0 -81
- package/src/domain/usecases/CliErrorReportUseCase.test.ts +0 -162
- package/src/domain/usecases/CliErrorReportUseCase.ts +0 -72
- package/src/domain/usecases/ConflictedIssueRevertUseCase.test.ts +0 -886
- package/src/domain/usecases/ConflictedIssueRevertUseCase.ts +0 -146
- package/src/domain/usecases/CreateEstimationIssueUseCase.test.ts +0 -227
- package/src/domain/usecases/CreateEstimationIssueUseCase.ts +0 -205
- package/src/domain/usecases/CreateNewStoryByLabelUseCase.test.ts +0 -1084
- package/src/domain/usecases/CreateNewStoryByLabelUseCase.ts +0 -146
- package/src/domain/usecases/DailySecurityScanUseCase.test.ts +0 -1709
- package/src/domain/usecases/DailySecurityScanUseCase.ts +0 -476
- package/src/domain/usecases/DefaultSilentSessionMessageComposer.test.ts +0 -533
- package/src/domain/usecases/DefaultSilentSessionMessageComposer.ts +0 -122
- package/src/domain/usecases/GetStoryObjectMapUseCase.test.ts +0 -330
- package/src/domain/usecases/GetStoryObjectMapUseCase.ts +0 -79
- package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +0 -1913
- package/src/domain/usecases/HandleScheduledEventUseCase.ts +0 -846
- package/src/domain/usecases/InTmuxByHumanSessionTokenCountUseCase.test.ts +0 -175
- package/src/domain/usecases/InTmuxByHumanSessionTokenCountUseCase.ts +0 -63
- package/src/domain/usecases/IssueNoStatusUpdateUseCase.test.ts +0 -165
- package/src/domain/usecases/IssueNoStatusUpdateUseCase.ts +0 -44
- package/src/domain/usecases/IssueRejectionEvaluator.test.ts +0 -1137
- package/src/domain/usecases/IssueRejectionEvaluator.ts +0 -340
- package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.test.ts +0 -679
- package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.ts +0 -178
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +0 -5441
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +0 -891
- package/src/domain/usecases/NotifySilentLiveSessionsUseCase.test.ts +0 -2069
- package/src/domain/usecases/NotifySilentLiveSessionsUseCase.ts +0 -489
- package/src/domain/usecases/OauthTokenSelectUseCase.test.ts +0 -651
- package/src/domain/usecases/OauthTokenSelectUseCase.ts +0 -274
- package/src/domain/usecases/OutputDegenerationDetector.test.ts +0 -167
- package/src/domain/usecases/OutputDegenerationDetector.ts +0 -169
- package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.test.ts +0 -711
- package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.ts +0 -146
- package/src/domain/usecases/QualityCheckAdvanceUseCase.test.ts +0 -421
- package/src/domain/usecases/QualityCheckAdvanceUseCase.ts +0 -76
- package/src/domain/usecases/ReopenedDoneIssueRevertUseCase.test.ts +0 -188
- package/src/domain/usecases/ReopenedDoneIssueRevertUseCase.ts +0 -57
- package/src/domain/usecases/ResolveInteractiveLiveSessionsUseCase.test.ts +0 -303
- package/src/domain/usecases/ResolveInteractiveLiveSessionsUseCase.ts +0 -155
- package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.test.ts +0 -1838
- package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts +0 -221
- package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +0 -2392
- package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +0 -499
- package/src/domain/usecases/SessionOutputDegenerationRecoveryUseCase.test.ts +0 -224
- package/src/domain/usecases/SessionOutputDegenerationRecoveryUseCase.ts +0 -142
- package/src/domain/usecases/SetDependedIssueUrlForOpenTaskPRsUseCase.test.ts +0 -360
- package/src/domain/usecases/SetDependedIssueUrlForOpenTaskPRsUseCase.ts +0 -70
- package/src/domain/usecases/SetNoStoryIssueToStoryUseCase.test.ts +0 -400
- package/src/domain/usecases/SetNoStoryIssueToStoryUseCase.ts +0 -48
- package/src/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.test.ts +0 -696
- package/src/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.ts +0 -201
- package/src/domain/usecases/SetupTowerDefenceProjectUseCase.test.ts +0 -1363
- package/src/domain/usecases/SetupTowerDefenceProjectUseCase.ts +0 -215
- package/src/domain/usecases/StaleTaskPullRequestCloseUseCase.test.ts +0 -170
- package/src/domain/usecases/StaleTaskPullRequestCloseUseCase.ts +0 -38
- package/src/domain/usecases/StaleTmuxSessionKillUseCase.test.ts +0 -283
- package/src/domain/usecases/StaleTmuxSessionKillUseCase.ts +0 -98
- package/src/domain/usecases/StartPreparationUseCase.test.ts +0 -7822
- package/src/domain/usecases/StartPreparationUseCase.ts +0 -876
- package/src/domain/usecases/SubscriptionDisabledIssueUseCase.test.ts +0 -200
- package/src/domain/usecases/SubscriptionDisabledIssueUseCase.ts +0 -74
- package/src/domain/usecases/TokenExhaustionHandoverUseCase.test.ts +0 -838
- package/src/domain/usecases/TokenExhaustionHandoverUseCase.ts +0 -439
- package/src/domain/usecases/UpdateIssueStatusByLabelUseCase.test.ts +0 -306
- package/src/domain/usecases/UpdateIssueStatusByLabelUseCase.ts +0 -64
- package/src/domain/usecases/UpdateRateLimitCacheUseCase.test.ts +0 -178
- package/src/domain/usecases/UpdateRateLimitCacheUseCase.ts +0 -22
- package/src/domain/usecases/adapter-interfaces/ClaudeHandoverSessionRepository.ts +0 -5
- package/src/domain/usecases/adapter-interfaces/ClaudeInteractiveSessionRepository.ts +0 -9
- package/src/domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository.ts +0 -8
- package/src/domain/usecases/adapter-interfaces/ClaudeMessageResponseRepository.ts +0 -5
- package/src/domain/usecases/adapter-interfaces/ClaudeRepository.ts +0 -6
- package/src/domain/usecases/adapter-interfaces/ClaudeTokenUsageRepository.ts +0 -8
- package/src/domain/usecases/adapter-interfaces/ConsoleTabsRepository.ts +0 -12
- package/src/domain/usecases/adapter-interfaces/CopilotRepository.ts +0 -3
- package/src/domain/usecases/adapter-interfaces/DateRepository.ts +0 -7
- package/src/domain/usecases/adapter-interfaces/GitHubGraphqlRateLimitRepository.ts +0 -3
- package/src/domain/usecases/adapter-interfaces/HttpRepository.ts +0 -3
- package/src/domain/usecases/adapter-interfaces/InteractiveLiveSessionTranscriptResolver.ts +0 -7
- package/src/domain/usecases/adapter-interfaces/IssueAttachmentRepository.ts +0 -9
- package/src/domain/usecases/adapter-interfaces/IssueCheckpointRepository.ts +0 -3
- package/src/domain/usecases/adapter-interfaces/IssueCommentRepository.ts +0 -7
- package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +0 -229
- package/src/domain/usecases/adapter-interfaces/KevReportWatermarkRepository.ts +0 -11
- package/src/domain/usecases/adapter-interfaces/LiveSessionProcessSnapshotProvider.ts +0 -5
- package/src/domain/usecases/adapter-interfaces/LocalCommandRunner.ts +0 -16
- package/src/domain/usecases/adapter-interfaces/OwnerCallStatusProvider.ts +0 -5
- package/src/domain/usecases/adapter-interfaces/ProcessEnvironReader.ts +0 -3
- package/src/domain/usecases/adapter-interfaces/ProcessSignalRepository.ts +0 -5
- package/src/domain/usecases/adapter-interfaces/ProjectRepository.ts +0 -29
- package/src/domain/usecases/adapter-interfaces/RateLimitCacheRepository.ts +0 -10
- package/src/domain/usecases/adapter-interfaces/RefusalTailStatusProvider.ts +0 -5
- package/src/domain/usecases/adapter-interfaces/SessionAssistantTurnsRepository.ts +0 -6
- package/src/domain/usecases/adapter-interfaces/SessionDegenerationCooldownStateRepository.ts +0 -4
- package/src/domain/usecases/adapter-interfaces/SessionOutputActivityRepository.ts +0 -7
- package/src/domain/usecases/adapter-interfaces/SessionRecordReader.ts +0 -3
- package/src/domain/usecases/adapter-interfaces/SessionSubAgentActivityRepository.ts +0 -8
- package/src/domain/usecases/adapter-interfaces/SilentSessionCandidateStateRepository.ts +0 -10
- package/src/domain/usecases/adapter-interfaces/SilentSessionHubTaskStatusCacheRepository.ts +0 -20
- package/src/domain/usecases/adapter-interfaces/SilentSessionMessageComposer.ts +0 -18
- package/src/domain/usecases/adapter-interfaces/SilentSessionNotificationRepository.ts +0 -6
- package/src/domain/usecases/adapter-interfaces/SlackRepository.ts +0 -20
- package/src/domain/usecases/adapter-interfaces/Sleeper.ts +0 -3
- package/src/domain/usecases/adapter-interfaces/SpreadsheetRepository.ts +0 -18
- package/src/domain/usecases/adapter-interfaces/StatusDefaultRepository.ts +0 -5
- package/src/domain/usecases/adapter-interfaces/SubAgentLivenessResolver.ts +0 -6
- package/src/domain/usecases/adapter-interfaces/SubAgentProcessLister.ts +0 -8
- package/src/domain/usecases/adapter-interfaces/SubAgentSilentSecondsResolver.ts +0 -3
- package/src/domain/usecases/adapter-interfaces/SubAgentTranscriptDirectoryResolver.ts +0 -6
- package/src/domain/usecases/adapter-interfaces/TakeOwnershipSpawnRepository.ts +0 -9
- package/src/domain/usecases/adapter-interfaces/TmuxSessionRepository.ts +0 -20
- package/src/domain/usecases/adapter-interfaces/TokenExhaustionHandoverStateRepository.ts +0 -6
- package/src/domain/usecases/adapter-interfaces/TokenRateLimitSnapshotRepository.ts +0 -22
- package/src/domain/usecases/adapter-interfaces/WebhookRepository.ts +0 -3
- package/src/domain/usecases/agentReportPrefix.ts +0 -1
- package/src/domain/usecases/autoStatusCheckComments.ts +0 -1
- package/src/domain/usecases/console/.gitkeep +0 -0
- package/src/domain/usecases/console/GenerateConsoleListsUseCase.test.ts +0 -887
- package/src/domain/usecases/console/GenerateConsoleListsUseCase.ts +0 -368
- package/src/domain/usecases/dashboard/ComposeDashboardUseCase.test.ts +0 -654
- package/src/domain/usecases/dashboard/ComposeDashboardUseCase.ts +0 -318
- package/src/domain/usecases/dashboard/DashboardProjectCode.test.ts +0 -34
- package/src/domain/usecases/dashboard/DashboardProjectCode.ts +0 -27
- package/src/domain/usecases/dashboard/GenerateDashboardRowUseCase.test.ts +0 -160
- package/src/domain/usecases/dashboard/GenerateDashboardRowUseCase.ts +0 -69
- package/src/domain/usecases/dashboard/GenerateTokenStatusUseCase.test.ts +0 -362
- package/src/domain/usecases/dashboard/GenerateTokenStatusUseCase.ts +0 -234
- package/src/domain/usecases/ensureAgentOptionAndGetId.ts +0 -47
- package/src/domain/usecases/extractFencedJsonBlocks.test.ts +0 -40
- package/src/domain/usecases/extractFencedJsonBlocks.ts +0 -21
- package/src/domain/usecases/extractNextStepAgent.test.ts +0 -58
- package/src/domain/usecases/extractNextStepAgent.ts +0 -18
- package/src/domain/usecases/extractNextStepAgentFromComments.ts +0 -13
- package/src/domain/usecases/findLastAgentReport.test.ts +0 -121
- package/src/domain/usecases/findLastAgentReport.ts +0 -14
- package/src/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.test.ts +0 -339
- package/src/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.ts +0 -188
- package/src/domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase.test.ts +0 -389
- package/src/domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase.ts +0 -103
- package/src/domain/usecases/intmux/OwnerCallFile.test.ts +0 -184
- package/src/domain/usecases/intmux/OwnerCallFile.ts +0 -80
- package/src/domain/usecases/isAgentReportBody.test.ts +0 -108
- package/src/domain/usecases/isAgentReportBody.ts +0 -52
- package/src/domain/usecases/isAuthorAuthorizedForAutoStatusCheck.ts +0 -12
- package/src/domain/usecases/isHumanComment.test.ts +0 -77
- package/src/domain/usecases/isHumanComment.ts +0 -23
- package/src/domain/usecases/isRecord.ts +0 -2
- package/src/domain/usecases/issueReactivationTriggerIsPending.test.ts +0 -541
- package/src/domain/usecases/issueReactivationTriggerIsPending.ts +0 -25
- package/src/domain/usecases/nextStepAgentDispatchRepeatedMessage.ts +0 -2
- package/src/domain/usecases/normalizeReportBody.test.ts +0 -24
- package/src/domain/usecases/normalizeReportBody.ts +0 -2
- package/src/domain/usecases/resolveAllowedIssueAuthors.test.ts +0 -51
- package/src/domain/usecases/resolveAllowedIssueAuthors.ts +0 -6
- package/src/domain/usecases/resolveLabelsAsLlmAgentName.test.ts +0 -42
- package/src/domain/usecases/resolveLabelsAsLlmAgentName.ts +0 -6
- package/src/domain/usecases/resolveLabelsNotRequiringPullRequest.test.ts +0 -33
- package/src/domain/usecases/resolveLabelsNotRequiringPullRequest.ts +0 -7
- package/src/domain/usecases/resolveNextStepAgentDispatchRepetition.test.ts +0 -337
- package/src/domain/usecases/resolveNextStepAgentDispatchRepetition.ts +0 -133
- package/src/domain/usecases/silentSessionReminderSentinel.ts +0 -9
- package/src/domain/usecases/triagerAgentName.ts +0 -4
- package/src/domain/usecases/utils.test.ts +0 -35
- package/src/domain/usecases/utils.ts +0 -6
- package/src/index.test.ts +0 -8
- package/src/index.ts +0 -10
- package/src/releaseConfig.test.ts +0 -40
- package/tsconfig.build.json +0 -18
- package/tsconfig.json +0 -25
- package/types/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.d.ts +0 -13
- package/types/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.d.ts.map +0 -1
- package/types/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.d.ts +0 -16
- package/types/adapter/repositories/FileSystemSilentSessionNotifiedStateRepository.d.ts.map +0 -1
- package/types/adapter/repositories/issue/issueTimelineUtils.d.ts +0 -12
- package/types/adapter/repositories/issue/issueTimelineUtils.d.ts.map +0 -1
- package/types/domain/entities/UnansweredOwnerCall.d.ts +0 -5
- package/types/domain/entities/UnansweredOwnerCall.d.ts.map +0 -1
- package/types/domain/entities/WorkingTime.d.ts +0 -8
- package/types/domain/entities/WorkingTime.d.ts.map +0 -1
- package/types/domain/usecases/ChangeStatusLongInReviewIssueUseCase.d.ts +0 -17
- package/types/domain/usecases/ChangeStatusLongInReviewIssueUseCase.d.ts.map +0 -1
- package/types/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.d.ts +0 -23
- package/types/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.d.ts.map +0 -1
- package/types/domain/usecases/GenerateWorkingTimeReportUseCase.d.ts +0 -50
- package/types/domain/usecases/GenerateWorkingTimeReportUseCase.d.ts.map +0 -1
- package/types/domain/usecases/TriagerApprovalDispatchUseCase.d.ts +0 -15
- package/types/domain/usecases/TriagerApprovalDispatchUseCase.d.ts.map +0 -1
- package/types/domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository.d.ts +0 -11
- package/types/domain/usecases/adapter-interfaces/SilentSessionNotifiedStateRepository.d.ts.map +0 -1
- package/types/domain/usecases/awaitingOwnerApprovalMessage.d.ts +0 -3
- package/types/domain/usecases/awaitingOwnerApprovalMessage.d.ts.map +0 -1
- package/types/domain/usecases/findLastAgentDeclaringReport.d.ts +0 -5
- package/types/domain/usecases/findLastAgentDeclaringReport.d.ts.map +0 -1
- package/types/domain/usecases/isPullRequestDeclaredUnnecessary.d.ts +0 -5
- package/types/domain/usecases/isPullRequestDeclaredUnnecessary.d.ts.map +0 -1
- package/types/domain/usecases/isWaitingForOwnerApproval.d.ts +0 -2
- package/types/domain/usecases/isWaitingForOwnerApproval.d.ts.map +0 -1
- package/types/domain/usecases/returnedToAwaitingWorkspaceMessage.d.ts +0 -3
- package/types/domain/usecases/returnedToAwaitingWorkspaceMessage.d.ts.map +0 -1
|
@@ -1,3303 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
IssueRepository,
|
|
3
|
-
RelatedPullRequest,
|
|
4
|
-
OpenPullRequestCiStatus,
|
|
5
|
-
IssueComment,
|
|
6
|
-
PullRequestDetail,
|
|
7
|
-
PullRequestFile,
|
|
8
|
-
PullRequestCommit,
|
|
9
|
-
PullRequestReviewCommentSide,
|
|
10
|
-
PullRequestReviewInlineLocation,
|
|
11
|
-
} from '../../../domain/usecases/adapter-interfaces/IssueRepository';
|
|
12
|
-
import { FieldOption, Project } from '../../../domain/entities/Project';
|
|
13
|
-
import { Issue } from '../../../domain/entities/Issue';
|
|
14
|
-
import { SearchedIssue } from '../../../domain/entities/SearchedIssue';
|
|
15
|
-
import { StoryObjectMap } from '../../../domain/entities/StoryObjectMap';
|
|
16
|
-
import { ApiV3IssueRepository } from './ApiV3IssueRepository';
|
|
17
|
-
import { normalizeGitHubRawUrl } from './gitHubRawUrl';
|
|
18
|
-
import { RestIssueRepository } from './RestIssueRepository';
|
|
19
|
-
import {
|
|
20
|
-
GraphqlProjectItemRepository,
|
|
21
|
-
ProjectItem,
|
|
22
|
-
} from './GraphqlProjectItemRepository';
|
|
23
|
-
import { LocalStorageCacheRepository } from '../LocalStorageCacheRepository';
|
|
24
|
-
import {
|
|
25
|
-
CachedProjectIssues,
|
|
26
|
-
isIssueArray,
|
|
27
|
-
isProject,
|
|
28
|
-
ProjectIssuesCacheRepository,
|
|
29
|
-
} from '../ProjectIssuesCacheRepository';
|
|
30
|
-
import { BaseGitHubRepository } from '../BaseGitHubRepository';
|
|
31
|
-
import {
|
|
32
|
-
fetchGithubGraphql,
|
|
33
|
-
postGithubGraphqlJson,
|
|
34
|
-
} from '../githubGraphqlClient';
|
|
35
|
-
import { normalizeFieldName } from '../utils';
|
|
36
|
-
import { LocalStorageRepository } from '../LocalStorageRepository';
|
|
37
|
-
import { Member } from '../../../domain/entities/Member';
|
|
38
|
-
import { ProjectRepository } from '../../../domain/usecases/adapter-interfaces/ProjectRepository';
|
|
39
|
-
import { DateRepository } from '../../../domain/usecases/adapter-interfaces/DateRepository';
|
|
40
|
-
import {
|
|
41
|
-
Sleep,
|
|
42
|
-
realSleep,
|
|
43
|
-
fetchWithGitHubRateLimitRetry,
|
|
44
|
-
computeRateLimitResetIso,
|
|
45
|
-
hasRateLimitSignals,
|
|
46
|
-
GitHubRateLimitError,
|
|
47
|
-
} from './githubRateLimitRetry';
|
|
48
|
-
|
|
49
|
-
export const FULL_ISSUE_FETCH_INTERVAL_MS = 60 * 60 * 1000;
|
|
50
|
-
export const INCREMENTAL_FETCH_SKEW_BUFFER_MS = 5 * 60 * 1000;
|
|
51
|
-
export const REQUIRED_CHECKS_CACHE_TTL_MS = 10 * 60 * 1000;
|
|
52
|
-
|
|
53
|
-
const SELF_AUTHORED_REVIEW_REFUSAL =
|
|
54
|
-
'Can not request changes on your own pull request';
|
|
55
|
-
|
|
56
|
-
type TimelineItem = {
|
|
57
|
-
__typename: string;
|
|
58
|
-
willCloseTarget?: boolean;
|
|
59
|
-
source?: {
|
|
60
|
-
__typename: string;
|
|
61
|
-
url?: string;
|
|
62
|
-
number?: number;
|
|
63
|
-
body?: string | null;
|
|
64
|
-
state?: string;
|
|
65
|
-
createdAt?: string;
|
|
66
|
-
isDraft?: boolean;
|
|
67
|
-
mergeable?: string;
|
|
68
|
-
headRefName?: string;
|
|
69
|
-
baseRefName?: string;
|
|
70
|
-
baseRef?: {
|
|
71
|
-
name: string;
|
|
72
|
-
} | null;
|
|
73
|
-
};
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
type IssueTimelineResponse = {
|
|
77
|
-
data?: {
|
|
78
|
-
repository?: {
|
|
79
|
-
issue?: {
|
|
80
|
-
timelineItems: {
|
|
81
|
-
pageInfo: {
|
|
82
|
-
endCursor: string;
|
|
83
|
-
hasNextPage: boolean;
|
|
84
|
-
};
|
|
85
|
-
nodes: TimelineItem[];
|
|
86
|
-
};
|
|
87
|
-
};
|
|
88
|
-
};
|
|
89
|
-
};
|
|
90
|
-
errors?: Array<{ message: string }>;
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
type CiContextNode =
|
|
94
|
-
| {
|
|
95
|
-
__typename: 'CheckRun';
|
|
96
|
-
name: string;
|
|
97
|
-
conclusion: string | null;
|
|
98
|
-
databaseId: number;
|
|
99
|
-
}
|
|
100
|
-
| {
|
|
101
|
-
__typename: 'StatusContext';
|
|
102
|
-
context: string;
|
|
103
|
-
state: string;
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
type PrStatusComputationData = {
|
|
107
|
-
isDraft?: boolean;
|
|
108
|
-
mergeable?: string;
|
|
109
|
-
requiredCheckNames: string[];
|
|
110
|
-
ciContexts: CiContextNode[];
|
|
111
|
-
reviewThreads: Array<{
|
|
112
|
-
isResolved: boolean;
|
|
113
|
-
}>;
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
// One GraphQL query carrying this many aliased pull requests costs the same
|
|
117
|
-
// single rate-limit point as a query carrying one, measured against the live
|
|
118
|
-
// API by reading the rateLimit cost field it returns.
|
|
119
|
-
const SLIM_PULL_REQUEST_BATCH_SIZE = 100;
|
|
120
|
-
const SLIM_PULL_REQUEST_REVIEW_THREADS_PAGE_SIZE = 100;
|
|
121
|
-
|
|
122
|
-
type SlimPullRequestNodeResponse = {
|
|
123
|
-
url: string;
|
|
124
|
-
state: string;
|
|
125
|
-
isDraft?: boolean;
|
|
126
|
-
headRefName?: string;
|
|
127
|
-
baseRefName?: string;
|
|
128
|
-
mergeable?: string;
|
|
129
|
-
headRefOid?: string;
|
|
130
|
-
reviewThreads?: {
|
|
131
|
-
pageInfo: {
|
|
132
|
-
endCursor: string | null;
|
|
133
|
-
hasNextPage: boolean;
|
|
134
|
-
};
|
|
135
|
-
nodes: Array<{
|
|
136
|
-
isResolved: boolean;
|
|
137
|
-
}>;
|
|
138
|
-
};
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
type SlimPullRequestBatchResponse = {
|
|
142
|
-
data?: Record<
|
|
143
|
-
string,
|
|
144
|
-
{ pullRequest?: SlimPullRequestNodeResponse | null } | null
|
|
145
|
-
> | null;
|
|
146
|
-
errors?: Array<{
|
|
147
|
-
message: string;
|
|
148
|
-
type?: string;
|
|
149
|
-
path?: Array<string | number>;
|
|
150
|
-
}>;
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
type SlimPullRequestResponse = {
|
|
154
|
-
data?: {
|
|
155
|
-
repository?: {
|
|
156
|
-
pullRequest?: {
|
|
157
|
-
url: string;
|
|
158
|
-
state: string;
|
|
159
|
-
isDraft?: boolean;
|
|
160
|
-
headRefName?: string;
|
|
161
|
-
baseRefName?: string;
|
|
162
|
-
mergeable?: string;
|
|
163
|
-
headRefOid?: string;
|
|
164
|
-
reviewThreads?: {
|
|
165
|
-
pageInfo: {
|
|
166
|
-
endCursor: string | null;
|
|
167
|
-
hasNextPage: boolean;
|
|
168
|
-
};
|
|
169
|
-
nodes: Array<{
|
|
170
|
-
isResolved: boolean;
|
|
171
|
-
}>;
|
|
172
|
-
};
|
|
173
|
-
} | null;
|
|
174
|
-
} | null;
|
|
175
|
-
};
|
|
176
|
-
errors?: Array<{ message: string }>;
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
type SlimPullRequest = {
|
|
180
|
-
url: string;
|
|
181
|
-
state: string;
|
|
182
|
-
isDraft?: boolean;
|
|
183
|
-
headRefName?: string;
|
|
184
|
-
baseRefName?: string;
|
|
185
|
-
mergeable?: string;
|
|
186
|
-
headRefOid?: string;
|
|
187
|
-
reviewThreads: Array<{
|
|
188
|
-
isResolved: boolean;
|
|
189
|
-
}>;
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
type BranchRulesResponseItem = {
|
|
193
|
-
type: string;
|
|
194
|
-
parameters?: {
|
|
195
|
-
required_status_checks?: Array<{
|
|
196
|
-
context: string;
|
|
197
|
-
}>;
|
|
198
|
-
};
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
type BranchDetailResponse = {
|
|
202
|
-
protection?: {
|
|
203
|
-
required_status_checks?: {
|
|
204
|
-
contexts?: string[];
|
|
205
|
-
} | null;
|
|
206
|
-
} | null;
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
type CheckRunsResponse = {
|
|
210
|
-
total_count: number;
|
|
211
|
-
check_runs: Array<{
|
|
212
|
-
id: number;
|
|
213
|
-
name: string;
|
|
214
|
-
conclusion: string | null;
|
|
215
|
-
}>;
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
type CheckSuitesResponse = {
|
|
219
|
-
total_count: number;
|
|
220
|
-
check_suites: Array<{
|
|
221
|
-
id: number;
|
|
222
|
-
}>;
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
type CombinedStatusResponse = {
|
|
226
|
-
statuses: Array<{
|
|
227
|
-
context: string;
|
|
228
|
-
state: string;
|
|
229
|
-
}>;
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
type PullRequestMergeabilityResponse = {
|
|
233
|
-
data?: {
|
|
234
|
-
repository?: {
|
|
235
|
-
pullRequest?: {
|
|
236
|
-
mergeable?: string | null;
|
|
237
|
-
mergeStateStatus?: string | null;
|
|
238
|
-
} | null;
|
|
239
|
-
} | null;
|
|
240
|
-
};
|
|
241
|
-
errors?: Array<{ message: string }>;
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
function isIssueTimelineResponse(
|
|
245
|
-
value: unknown,
|
|
246
|
-
): value is IssueTimelineResponse {
|
|
247
|
-
if (typeof value !== 'object' || value === null) return false;
|
|
248
|
-
return true;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function isSlimPullRequestResponse(
|
|
252
|
-
value: unknown,
|
|
253
|
-
): value is SlimPullRequestResponse {
|
|
254
|
-
if (typeof value !== 'object' || value === null) return false;
|
|
255
|
-
return true;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
function isSlimPullRequestBatchResponse(
|
|
259
|
-
value: unknown,
|
|
260
|
-
): value is SlimPullRequestBatchResponse {
|
|
261
|
-
if (typeof value !== 'object' || value === null) return false;
|
|
262
|
-
return true;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function isBranchRulesResponse(
|
|
266
|
-
value: unknown,
|
|
267
|
-
): value is BranchRulesResponseItem[] {
|
|
268
|
-
return Array.isArray(value);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function isBranchDetailResponse(value: unknown): value is BranchDetailResponse {
|
|
272
|
-
if (typeof value !== 'object' || value === null) return false;
|
|
273
|
-
return true;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function isCheckRunsResponse(value: unknown): value is CheckRunsResponse {
|
|
277
|
-
if (typeof value !== 'object' || value === null) return false;
|
|
278
|
-
return 'check_runs' in value && Array.isArray(value.check_runs);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function isCheckSuitesResponse(value: unknown): value is CheckSuitesResponse {
|
|
282
|
-
if (typeof value !== 'object' || value === null) return false;
|
|
283
|
-
return 'check_suites' in value && Array.isArray(value.check_suites);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function isCombinedStatusResponse(
|
|
287
|
-
value: unknown,
|
|
288
|
-
): value is CombinedStatusResponse {
|
|
289
|
-
if (typeof value !== 'object' || value === null) return false;
|
|
290
|
-
return 'statuses' in value && Array.isArray(value.statuses);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
type AuthenticatedUserResponse = {
|
|
294
|
-
login: string;
|
|
295
|
-
};
|
|
296
|
-
|
|
297
|
-
function isAuthenticatedUserResponse(
|
|
298
|
-
value: unknown,
|
|
299
|
-
): value is AuthenticatedUserResponse {
|
|
300
|
-
if (typeof value !== 'object' || value === null) return false;
|
|
301
|
-
if (!('login' in value)) return false;
|
|
302
|
-
return typeof value.login === 'string';
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
function isPullRequestMergeabilityResponse(
|
|
306
|
-
value: unknown,
|
|
307
|
-
): value is PullRequestMergeabilityResponse {
|
|
308
|
-
if (typeof value !== 'object' || value === null) return false;
|
|
309
|
-
return true;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
type PullRequestFilesResponseItem = {
|
|
313
|
-
filename: string;
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
function isPullRequestFilesResponse(
|
|
317
|
-
value: unknown,
|
|
318
|
-
): value is PullRequestFilesResponseItem[] {
|
|
319
|
-
if (!Array.isArray(value)) return false;
|
|
320
|
-
return value.every(
|
|
321
|
-
(item) => typeof item === 'object' && item !== null && 'filename' in item,
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
326
|
-
return typeof value === 'object' && value !== null;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
type RestPullRequestCiStatusResponse = {
|
|
330
|
-
html_url: string;
|
|
331
|
-
state: string;
|
|
332
|
-
draft: boolean;
|
|
333
|
-
mergeable: boolean | null;
|
|
334
|
-
head: { ref: string; sha: string };
|
|
335
|
-
base: { ref: string };
|
|
336
|
-
};
|
|
337
|
-
|
|
338
|
-
function isRestPullRequestCiStatusResponse(
|
|
339
|
-
value: unknown,
|
|
340
|
-
): value is RestPullRequestCiStatusResponse {
|
|
341
|
-
if (!isRecord(value)) return false;
|
|
342
|
-
const head: unknown = value.head;
|
|
343
|
-
const base: unknown = value.base;
|
|
344
|
-
return (
|
|
345
|
-
typeof value.html_url === 'string' &&
|
|
346
|
-
typeof value.state === 'string' &&
|
|
347
|
-
typeof value.draft === 'boolean' &&
|
|
348
|
-
(value.mergeable === null || typeof value.mergeable === 'boolean') &&
|
|
349
|
-
isRecord(head) &&
|
|
350
|
-
typeof head.ref === 'string' &&
|
|
351
|
-
typeof head.sha === 'string' &&
|
|
352
|
-
isRecord(base) &&
|
|
353
|
-
typeof base.ref === 'string'
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
export const graphqlMergeableFromRestMergeable = (
|
|
358
|
-
mergeable: boolean | null,
|
|
359
|
-
): string => {
|
|
360
|
-
if (mergeable === true) {
|
|
361
|
-
return 'MERGEABLE';
|
|
362
|
-
}
|
|
363
|
-
if (mergeable === false) {
|
|
364
|
-
return 'CONFLICTING';
|
|
365
|
-
}
|
|
366
|
-
return 'UNKNOWN';
|
|
367
|
-
};
|
|
368
|
-
|
|
369
|
-
function isNullableString(value: unknown): value is string | null {
|
|
370
|
-
return value === null || typeof value === 'string';
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
function isLoginContainer(value: unknown): value is { login: string } {
|
|
374
|
-
return isRecord(value) && typeof value.login === 'string';
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
function isRefContainer(value: unknown): value is { ref: string } {
|
|
378
|
-
return isRecord(value) && typeof value.ref === 'string';
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function isRepoMergeSettings(
|
|
382
|
-
value: unknown,
|
|
383
|
-
): value is { allow_squash_merge?: boolean; allow_rebase_merge?: boolean } {
|
|
384
|
-
return isRecord(value);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
type IssueOrPullRequestBodyResponse = {
|
|
388
|
-
body: string | null;
|
|
389
|
-
};
|
|
390
|
-
|
|
391
|
-
function isIssueOrPullRequestBodyResponse(
|
|
392
|
-
value: unknown,
|
|
393
|
-
): value is IssueOrPullRequestBodyResponse {
|
|
394
|
-
return isRecord(value) && isNullableString(value.body);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
type IssueOrPullRequestStateResponse = {
|
|
398
|
-
state: string;
|
|
399
|
-
title: string;
|
|
400
|
-
};
|
|
401
|
-
|
|
402
|
-
function isIssueOrPullRequestStateResponse(
|
|
403
|
-
value: unknown,
|
|
404
|
-
): value is IssueOrPullRequestStateResponse {
|
|
405
|
-
return (
|
|
406
|
-
isRecord(value) &&
|
|
407
|
-
typeof value.state === 'string' &&
|
|
408
|
-
typeof value.title === 'string'
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
type IssueCommentsResponseItem = {
|
|
413
|
-
user: { login: string } | null;
|
|
414
|
-
body: string | null;
|
|
415
|
-
created_at: string;
|
|
416
|
-
};
|
|
417
|
-
|
|
418
|
-
function isIssueCommentsResponseItem(
|
|
419
|
-
value: unknown,
|
|
420
|
-
): value is IssueCommentsResponseItem {
|
|
421
|
-
if (!isRecord(value)) return false;
|
|
422
|
-
const userValid = value.user === null || isLoginContainer(value.user);
|
|
423
|
-
return (
|
|
424
|
-
userValid &&
|
|
425
|
-
isNullableString(value.body) &&
|
|
426
|
-
typeof value.created_at === 'string'
|
|
427
|
-
);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
function isIssueCommentsResponse(
|
|
431
|
-
value: unknown,
|
|
432
|
-
): value is IssueCommentsResponseItem[] {
|
|
433
|
-
return Array.isArray(value) && value.every(isIssueCommentsResponseItem);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
type IssueCommentIdResponseItem = {
|
|
437
|
-
id: number;
|
|
438
|
-
};
|
|
439
|
-
|
|
440
|
-
function isIssueCommentIdResponseItem(
|
|
441
|
-
value: unknown,
|
|
442
|
-
): value is IssueCommentIdResponseItem {
|
|
443
|
-
return isRecord(value) && typeof value.id === 'number';
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
function isIssueCommentIdResponse(
|
|
447
|
-
value: unknown,
|
|
448
|
-
): value is IssueCommentIdResponseItem[] {
|
|
449
|
-
return Array.isArray(value) && value.every(isIssueCommentIdResponseItem);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
type PullRequestDetailResponse = {
|
|
453
|
-
title: string;
|
|
454
|
-
state: string;
|
|
455
|
-
merged: boolean;
|
|
456
|
-
draft: boolean;
|
|
457
|
-
additions: number;
|
|
458
|
-
deletions: number;
|
|
459
|
-
changed_files: number;
|
|
460
|
-
head: { ref: string };
|
|
461
|
-
base: { ref: string };
|
|
462
|
-
user: { login: string } | null;
|
|
463
|
-
body: string | null;
|
|
464
|
-
};
|
|
465
|
-
|
|
466
|
-
function isPullRequestDetailResponse(
|
|
467
|
-
value: unknown,
|
|
468
|
-
): value is PullRequestDetailResponse {
|
|
469
|
-
if (!isRecord(value)) return false;
|
|
470
|
-
const userValid = value.user === null || isLoginContainer(value.user);
|
|
471
|
-
return (
|
|
472
|
-
typeof value.title === 'string' &&
|
|
473
|
-
typeof value.state === 'string' &&
|
|
474
|
-
typeof value.merged === 'boolean' &&
|
|
475
|
-
typeof value.draft === 'boolean' &&
|
|
476
|
-
typeof value.additions === 'number' &&
|
|
477
|
-
typeof value.deletions === 'number' &&
|
|
478
|
-
typeof value.changed_files === 'number' &&
|
|
479
|
-
isRefContainer(value.head) &&
|
|
480
|
-
isRefContainer(value.base) &&
|
|
481
|
-
userValid &&
|
|
482
|
-
isNullableString(value.body)
|
|
483
|
-
);
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
type PullRequestDetailFilesResponseItem = {
|
|
487
|
-
filename: string;
|
|
488
|
-
status: string;
|
|
489
|
-
additions: number;
|
|
490
|
-
deletions: number;
|
|
491
|
-
patch?: string;
|
|
492
|
-
raw_url?: string;
|
|
493
|
-
};
|
|
494
|
-
|
|
495
|
-
function isPullRequestDetailFilesResponseItem(
|
|
496
|
-
value: unknown,
|
|
497
|
-
): value is PullRequestDetailFilesResponseItem {
|
|
498
|
-
if (!isRecord(value)) return false;
|
|
499
|
-
return (
|
|
500
|
-
typeof value.filename === 'string' &&
|
|
501
|
-
typeof value.status === 'string' &&
|
|
502
|
-
typeof value.additions === 'number' &&
|
|
503
|
-
typeof value.deletions === 'number' &&
|
|
504
|
-
(value.patch === undefined || typeof value.patch === 'string') &&
|
|
505
|
-
(value.raw_url === undefined || typeof value.raw_url === 'string')
|
|
506
|
-
);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
function isPullRequestDetailFilesResponse(
|
|
510
|
-
value: unknown,
|
|
511
|
-
): value is PullRequestDetailFilesResponseItem[] {
|
|
512
|
-
return (
|
|
513
|
-
Array.isArray(value) && value.every(isPullRequestDetailFilesResponseItem)
|
|
514
|
-
);
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
type PullRequestCommitsResponseItem = {
|
|
518
|
-
sha: string;
|
|
519
|
-
commit: {
|
|
520
|
-
message: string;
|
|
521
|
-
author: { name: string; date: string } | null;
|
|
522
|
-
};
|
|
523
|
-
};
|
|
524
|
-
|
|
525
|
-
function isCommitAuthor(
|
|
526
|
-
value: unknown,
|
|
527
|
-
): value is { name: string; date: string } {
|
|
528
|
-
return (
|
|
529
|
-
isRecord(value) &&
|
|
530
|
-
typeof value.name === 'string' &&
|
|
531
|
-
typeof value.date === 'string'
|
|
532
|
-
);
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
function isPullRequestCommitsResponseItem(
|
|
536
|
-
value: unknown,
|
|
537
|
-
): value is PullRequestCommitsResponseItem {
|
|
538
|
-
if (!isRecord(value)) return false;
|
|
539
|
-
if (typeof value.sha !== 'string') return false;
|
|
540
|
-
if (!isRecord(value.commit)) return false;
|
|
541
|
-
if (typeof value.commit.message !== 'string') return false;
|
|
542
|
-
return value.commit.author === null || isCommitAuthor(value.commit.author);
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
function isPullRequestCommitsResponse(
|
|
546
|
-
value: unknown,
|
|
547
|
-
): value is PullRequestCommitsResponseItem[] {
|
|
548
|
-
return Array.isArray(value) && value.every(isPullRequestCommitsResponseItem);
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
export class ApiV3CheerioRestIssueRepository
|
|
552
|
-
extends BaseGitHubRepository
|
|
553
|
-
implements IssueRepository
|
|
554
|
-
{
|
|
555
|
-
constructor(
|
|
556
|
-
readonly apiV3IssueRepository: Pick<ApiV3IssueRepository, 'searchIssue'>,
|
|
557
|
-
readonly restIssueRepository: Pick<
|
|
558
|
-
RestIssueRepository,
|
|
559
|
-
| 'createNewIssue'
|
|
560
|
-
| 'updateIssue'
|
|
561
|
-
| 'updateIssueBody'
|
|
562
|
-
| 'createComment'
|
|
563
|
-
| 'getIssue'
|
|
564
|
-
| 'updateLabels'
|
|
565
|
-
| 'removeLabel'
|
|
566
|
-
| 'updateAssigneeList'
|
|
567
|
-
| 'searchIssues'
|
|
568
|
-
| 'getOrCreateLabel'
|
|
569
|
-
>,
|
|
570
|
-
readonly graphqlProjectItemRepository: Pick<
|
|
571
|
-
GraphqlProjectItemRepository,
|
|
572
|
-
| 'fetchProjectItems'
|
|
573
|
-
| 'fetchProjectItemsLight'
|
|
574
|
-
| 'fetchProjectItemsByIds'
|
|
575
|
-
| 'fetchProjectItemByUrl'
|
|
576
|
-
| 'updateProjectField'
|
|
577
|
-
| 'clearProjectField'
|
|
578
|
-
| 'updateProjectTextField'
|
|
579
|
-
| 'addIssueToProject'
|
|
580
|
-
>,
|
|
581
|
-
readonly localStorageCacheRepository: Pick<
|
|
582
|
-
LocalStorageCacheRepository,
|
|
583
|
-
'getSingle' | 'setSingle'
|
|
584
|
-
>,
|
|
585
|
-
readonly projectRepository: Pick<ProjectRepository, 'getProject'>,
|
|
586
|
-
readonly dateRepository: DateRepository,
|
|
587
|
-
readonly localStorageRepository: LocalStorageRepository,
|
|
588
|
-
readonly ghToken: string = process.env.GH_TOKEN || 'dummy',
|
|
589
|
-
readonly sleep: Sleep = realSleep,
|
|
590
|
-
) {
|
|
591
|
-
super(localStorageRepository, ghToken);
|
|
592
|
-
this.projectIssuesCacheRepository = new ProjectIssuesCacheRepository(
|
|
593
|
-
localStorageCacheRepository,
|
|
594
|
-
);
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
private readonly projectIssuesCacheRepository: ProjectIssuesCacheRepository;
|
|
598
|
-
|
|
599
|
-
private readonly getAllIssuesRefreshMemo = new Map<
|
|
600
|
-
Project['id'],
|
|
601
|
-
{ issues: Issue[]; project: Project; cacheUsed: boolean }
|
|
602
|
-
>();
|
|
603
|
-
|
|
604
|
-
private readonly lastIssuesFetchedAtByProjectId = new Map<
|
|
605
|
-
Project['id'],
|
|
606
|
-
string
|
|
607
|
-
>();
|
|
608
|
-
|
|
609
|
-
getLastIssuesFetchedAt = (projectId: Project['id']): string | null =>
|
|
610
|
-
this.lastIssuesFetchedAtByProjectId.get(projectId) ?? null;
|
|
611
|
-
|
|
612
|
-
private fetchWithRateLimitRetry = (
|
|
613
|
-
request: () => Promise<Response>,
|
|
614
|
-
): Promise<Response> => fetchWithGitHubRateLimitRetry(request, this.sleep);
|
|
615
|
-
|
|
616
|
-
private throwGitHubError = async (
|
|
617
|
-
prefix: string,
|
|
618
|
-
response: Response,
|
|
619
|
-
): Promise<never> => {
|
|
620
|
-
const bodyText = await response.clone().text();
|
|
621
|
-
const isRateLimit = hasRateLimitSignals(
|
|
622
|
-
response.status,
|
|
623
|
-
response.headers,
|
|
624
|
-
bodyText,
|
|
625
|
-
);
|
|
626
|
-
const formatted = await this.formatGitHubErrorWithStatus(response);
|
|
627
|
-
const message = `${prefix}: ${formatted}`;
|
|
628
|
-
if (isRateLimit) {
|
|
629
|
-
throw new GitHubRateLimitError(message);
|
|
630
|
-
}
|
|
631
|
-
throw new Error(message);
|
|
632
|
-
};
|
|
633
|
-
|
|
634
|
-
updateStatus: (
|
|
635
|
-
project: Project,
|
|
636
|
-
issue: Issue,
|
|
637
|
-
statusId: string,
|
|
638
|
-
) => Promise<void> = async (project, issue, statusId) => {
|
|
639
|
-
await this.graphqlProjectItemRepository.updateProjectField(
|
|
640
|
-
project.id,
|
|
641
|
-
project.status.fieldId,
|
|
642
|
-
issue.itemId,
|
|
643
|
-
{ singleSelectOptionId: statusId },
|
|
644
|
-
);
|
|
645
|
-
};
|
|
646
|
-
|
|
647
|
-
convertProjectItemToIssue = (item: ProjectItem): Issue => {
|
|
648
|
-
const nextActionDate = item.customFields.find(
|
|
649
|
-
(field) => normalizeFieldName(field.name) === 'nextactiondate',
|
|
650
|
-
)?.value;
|
|
651
|
-
const nextActionHour = item.customFields.find(
|
|
652
|
-
(field) => normalizeFieldName(field.name) === 'nextactionhour',
|
|
653
|
-
)?.value;
|
|
654
|
-
const estimationMinutes = item.customFields.find(
|
|
655
|
-
(field) => normalizeFieldName(field.name) === 'estimationminutes',
|
|
656
|
-
)?.value;
|
|
657
|
-
const dependedIssueUrls =
|
|
658
|
-
item.customFields
|
|
659
|
-
.find((field) =>
|
|
660
|
-
normalizeFieldName(field.name).startsWith('dependedissueurls'),
|
|
661
|
-
)
|
|
662
|
-
?.value?.split(',')
|
|
663
|
-
.map((url) => url.trim())
|
|
664
|
-
.filter((url) => url.length > 0) || [];
|
|
665
|
-
const completionDate50PercentConfidence = item.customFields.find((field) =>
|
|
666
|
-
normalizeFieldName(field.name).startsWith('completiondate50'),
|
|
667
|
-
)?.value;
|
|
668
|
-
const story = item.customFields.find(
|
|
669
|
-
(field) => normalizeFieldName(field.name) === 'story',
|
|
670
|
-
)?.value;
|
|
671
|
-
const status = item.customFields.find(
|
|
672
|
-
(field) => normalizeFieldName(field.name) === 'status',
|
|
673
|
-
)?.value;
|
|
674
|
-
const agent =
|
|
675
|
-
item.customFields.find(
|
|
676
|
-
(field) => normalizeFieldName(field.name) === 'agent',
|
|
677
|
-
)?.value ?? null;
|
|
678
|
-
const { owner, repo } = this.extractIssueFromUrl(item.url);
|
|
679
|
-
|
|
680
|
-
return {
|
|
681
|
-
nameWithOwner: item.nameWithOwner,
|
|
682
|
-
url: item.url,
|
|
683
|
-
title: item.title,
|
|
684
|
-
number: item.number,
|
|
685
|
-
state: item.state,
|
|
686
|
-
labels: item.labels,
|
|
687
|
-
assignees: item.assignees,
|
|
688
|
-
nextActionDate: nextActionDate ? new Date(nextActionDate) : null,
|
|
689
|
-
nextActionHour: nextActionHour ? parseInt(nextActionHour) : null,
|
|
690
|
-
estimationMinutes: estimationMinutes ? parseInt(estimationMinutes) : null,
|
|
691
|
-
dependedIssueUrls: dependedIssueUrls,
|
|
692
|
-
completionDate50PercentConfidence: completionDate50PercentConfidence
|
|
693
|
-
? new Date(completionDate50PercentConfidence)
|
|
694
|
-
: null,
|
|
695
|
-
status: status || null,
|
|
696
|
-
story: story || null,
|
|
697
|
-
org: owner,
|
|
698
|
-
repo: repo,
|
|
699
|
-
body: item.body ?? '',
|
|
700
|
-
itemId: item.id,
|
|
701
|
-
isPr: item.url.includes('/pull/'),
|
|
702
|
-
isInProgress: normalizeFieldName(status || '').includes('progress'),
|
|
703
|
-
isClosed: item.state !== 'OPEN',
|
|
704
|
-
createdAt: new Date(item.createdAt || '2000-01-01'),
|
|
705
|
-
author: item.author,
|
|
706
|
-
closingIssueReferenceUrls: item.closingIssueReferenceUrls,
|
|
707
|
-
agent,
|
|
708
|
-
isRepoArchived: item.isRepoArchived,
|
|
709
|
-
stateReason: item.stateReason,
|
|
710
|
-
};
|
|
711
|
-
};
|
|
712
|
-
private restoreIssuesFromCache = (rawIssues: unknown): Issue[] | null => {
|
|
713
|
-
if (!Array.isArray(rawIssues)) {
|
|
714
|
-
return null;
|
|
715
|
-
}
|
|
716
|
-
const issues = rawIssues
|
|
717
|
-
.filter(
|
|
718
|
-
(issue: unknown): issue is object =>
|
|
719
|
-
typeof issue === 'object' && issue !== null,
|
|
720
|
-
)
|
|
721
|
-
.map((issue: object): object => {
|
|
722
|
-
const nextActionDate =
|
|
723
|
-
!('nextActionDate' in issue) ||
|
|
724
|
-
typeof issue.nextActionDate !== 'string' ||
|
|
725
|
-
issue.nextActionDate === null
|
|
726
|
-
? null
|
|
727
|
-
: new Date(issue.nextActionDate);
|
|
728
|
-
const completionDate50PercentConfidence =
|
|
729
|
-
!('completionDate50PercentConfidence' in issue) ||
|
|
730
|
-
typeof issue.completionDate50PercentConfidence !== 'string'
|
|
731
|
-
? null
|
|
732
|
-
: new Date(issue.completionDate50PercentConfidence);
|
|
733
|
-
const createdAt =
|
|
734
|
-
!('createdAt' in issue) || typeof issue.createdAt !== 'string'
|
|
735
|
-
? new Date()
|
|
736
|
-
: new Date(issue.createdAt);
|
|
737
|
-
const closingIssueReferenceUrls =
|
|
738
|
-
'closingIssueReferenceUrls' in issue &&
|
|
739
|
-
Array.isArray(issue.closingIssueReferenceUrls) &&
|
|
740
|
-
issue.closingIssueReferenceUrls.every(
|
|
741
|
-
(url): url is string => typeof url === 'string',
|
|
742
|
-
)
|
|
743
|
-
? issue.closingIssueReferenceUrls
|
|
744
|
-
: [];
|
|
745
|
-
|
|
746
|
-
const stateReason =
|
|
747
|
-
'stateReason' in issue &&
|
|
748
|
-
(issue.stateReason === 'COMPLETED' ||
|
|
749
|
-
issue.stateReason === 'NOT_PLANNED' ||
|
|
750
|
-
issue.stateReason === 'REOPENED')
|
|
751
|
-
? issue.stateReason
|
|
752
|
-
: null;
|
|
753
|
-
return {
|
|
754
|
-
...issue,
|
|
755
|
-
nextActionDate: nextActionDate,
|
|
756
|
-
completionDate50PercentConfidence: completionDate50PercentConfidence,
|
|
757
|
-
createdAt: createdAt,
|
|
758
|
-
closingIssueReferenceUrls: closingIssueReferenceUrls,
|
|
759
|
-
stateReason: stateReason,
|
|
760
|
-
};
|
|
761
|
-
});
|
|
762
|
-
|
|
763
|
-
if (isIssueArray(issues)) {
|
|
764
|
-
return issues;
|
|
765
|
-
}
|
|
766
|
-
return null;
|
|
767
|
-
};
|
|
768
|
-
|
|
769
|
-
private readCachedProjectIssues = async (
|
|
770
|
-
projectId: Project['id'],
|
|
771
|
-
): Promise<CachedProjectIssues | null> => {
|
|
772
|
-
const raw = await this.projectIssuesCacheRepository.readRaw(projectId);
|
|
773
|
-
if (typeof raw !== 'object' || raw === null) {
|
|
774
|
-
return null;
|
|
775
|
-
}
|
|
776
|
-
if (
|
|
777
|
-
!('lastFetchedAt' in raw) ||
|
|
778
|
-
typeof raw.lastFetchedAt !== 'string' ||
|
|
779
|
-
!('lastFullFetchAt' in raw) ||
|
|
780
|
-
typeof raw.lastFullFetchAt !== 'string' ||
|
|
781
|
-
!('project' in raw) ||
|
|
782
|
-
!('issues' in raw)
|
|
783
|
-
) {
|
|
784
|
-
return null;
|
|
785
|
-
}
|
|
786
|
-
if (!isProject(raw.project)) {
|
|
787
|
-
return null;
|
|
788
|
-
}
|
|
789
|
-
const issues = this.restoreIssuesFromCache(raw.issues);
|
|
790
|
-
if (!issues) {
|
|
791
|
-
return null;
|
|
792
|
-
}
|
|
793
|
-
return {
|
|
794
|
-
lastFetchedAt: raw.lastFetchedAt,
|
|
795
|
-
lastFullFetchAt: raw.lastFullFetchAt,
|
|
796
|
-
project: raw.project,
|
|
797
|
-
issues,
|
|
798
|
-
};
|
|
799
|
-
};
|
|
800
|
-
|
|
801
|
-
// Reads the Project (status/story option ids and field ids) that the TDPM
|
|
802
|
-
// daemon persisted into the `allIssues-${projectId}` cache, without any
|
|
803
|
-
// GraphQL call. Returns null on cache miss so callers can fall back to a
|
|
804
|
-
// GraphQL project load only when the daemon has not populated the cache yet.
|
|
805
|
-
getCachedProject = async (
|
|
806
|
-
projectId: Project['id'],
|
|
807
|
-
): Promise<Project | null> =>
|
|
808
|
-
this.projectIssuesCacheRepository.readProject(projectId);
|
|
809
|
-
|
|
810
|
-
private toDateString = (date: Date): string =>
|
|
811
|
-
`${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}-${String(date.getUTCDate()).padStart(2, '0')}`;
|
|
812
|
-
|
|
813
|
-
getAllIssues = async (
|
|
814
|
-
projectId: Project['id'],
|
|
815
|
-
): Promise<{
|
|
816
|
-
issues: Issue[];
|
|
817
|
-
project: Project;
|
|
818
|
-
cacheUsed: boolean;
|
|
819
|
-
}> => {
|
|
820
|
-
const memoized = this.getAllIssuesRefreshMemo.get(projectId);
|
|
821
|
-
if (memoized) {
|
|
822
|
-
return memoized;
|
|
823
|
-
}
|
|
824
|
-
const result = await this.refreshAllIssues(projectId);
|
|
825
|
-
this.getAllIssuesRefreshMemo.set(projectId, result);
|
|
826
|
-
return result;
|
|
827
|
-
};
|
|
828
|
-
|
|
829
|
-
private refreshAllIssues = async (
|
|
830
|
-
projectId: Project['id'],
|
|
831
|
-
): Promise<{ issues: Issue[]; project: Project; cacheUsed: boolean }> => {
|
|
832
|
-
const now = await this.dateRepository.now();
|
|
833
|
-
const cache = await this.readCachedProjectIssues(projectId);
|
|
834
|
-
const isFullFetch =
|
|
835
|
-
cache === null ||
|
|
836
|
-
now.getTime() - new Date(cache.lastFullFetchAt).getTime() >=
|
|
837
|
-
FULL_ISSUE_FETCH_INTERVAL_MS;
|
|
838
|
-
|
|
839
|
-
let project: Project;
|
|
840
|
-
try {
|
|
841
|
-
const freshProject = await this.projectRepository.getProject(projectId);
|
|
842
|
-
if (!freshProject) {
|
|
843
|
-
throw new Error(`Project not found. projectId: ${projectId}`);
|
|
844
|
-
}
|
|
845
|
-
project = freshProject;
|
|
846
|
-
} catch (error) {
|
|
847
|
-
if (!isFullFetch && cache !== null) {
|
|
848
|
-
console.warn(
|
|
849
|
-
`Failed to refresh project metadata, using cached. projectId: ${projectId}, error: ${String(error)}`,
|
|
850
|
-
);
|
|
851
|
-
project = cache.project;
|
|
852
|
-
} else {
|
|
853
|
-
throw error;
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
const cacheStoriesByOptionId = new Map(
|
|
858
|
-
cache?.project.story?.stories.map((s) => [s.id, s.name]) ?? [],
|
|
859
|
-
);
|
|
860
|
-
const freshStoryOptionIds = new Set(
|
|
861
|
-
project.story?.stories.map((s) => s.id) ?? [],
|
|
862
|
-
);
|
|
863
|
-
const storyOptionsChanged =
|
|
864
|
-
cache !== null &&
|
|
865
|
-
([...cacheStoriesByOptionId.entries()].some(
|
|
866
|
-
([id, name]) =>
|
|
867
|
-
!freshStoryOptionIds.has(id) ||
|
|
868
|
-
project.story?.stories.find((s) => s.id === id)?.name !== name,
|
|
869
|
-
) ??
|
|
870
|
-
false);
|
|
871
|
-
const effectiveIsFullFetch = isFullFetch || storyOptionsChanged;
|
|
872
|
-
|
|
873
|
-
if (effectiveIsFullFetch) {
|
|
874
|
-
const items =
|
|
875
|
-
await this.graphqlProjectItemRepository.fetchProjectItems(projectId);
|
|
876
|
-
const issues = items.map((item) => this.convertProjectItemToIssue(item));
|
|
877
|
-
const nowIso = now.toISOString();
|
|
878
|
-
await this.projectIssuesCacheRepository.write(projectId, {
|
|
879
|
-
lastFetchedAt: nowIso,
|
|
880
|
-
lastFullFetchAt: nowIso,
|
|
881
|
-
project,
|
|
882
|
-
issues,
|
|
883
|
-
});
|
|
884
|
-
this.lastIssuesFetchedAtByProjectId.set(projectId, nowIso);
|
|
885
|
-
return { issues, project, cacheUsed: false };
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
const lastFetchedAt = new Date(cache.lastFetchedAt);
|
|
889
|
-
const cutoff = new Date(
|
|
890
|
-
lastFetchedAt.getTime() - INCREMENTAL_FETCH_SKEW_BUFFER_MS,
|
|
891
|
-
);
|
|
892
|
-
const lightItems =
|
|
893
|
-
await this.graphqlProjectItemRepository.fetchProjectItemsLight(
|
|
894
|
-
projectId,
|
|
895
|
-
`updated:>=${this.toDateString(cutoff)}`,
|
|
896
|
-
);
|
|
897
|
-
const changedItemIds = lightItems
|
|
898
|
-
.filter((item) => new Date(item.updatedAt).getTime() >= cutoff.getTime())
|
|
899
|
-
.map((item) => item.id);
|
|
900
|
-
const issuesByUrl = new Map<string, Issue>(
|
|
901
|
-
cache.issues.map((issue) => [issue.url, issue]),
|
|
902
|
-
);
|
|
903
|
-
if (changedItemIds.length > 0) {
|
|
904
|
-
const changedItems =
|
|
905
|
-
await this.graphqlProjectItemRepository.fetchProjectItemsByIds(
|
|
906
|
-
changedItemIds,
|
|
907
|
-
);
|
|
908
|
-
for (const item of changedItems) {
|
|
909
|
-
const issue = this.convertProjectItemToIssue(item);
|
|
910
|
-
issuesByUrl.set(issue.url, issue);
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
const issues = Array.from(issuesByUrl.values());
|
|
914
|
-
const nowIso = now.toISOString();
|
|
915
|
-
await this.projectIssuesCacheRepository.write(projectId, {
|
|
916
|
-
lastFetchedAt: nowIso,
|
|
917
|
-
lastFullFetchAt: cache.lastFullFetchAt,
|
|
918
|
-
project,
|
|
919
|
-
issues,
|
|
920
|
-
});
|
|
921
|
-
this.lastIssuesFetchedAtByProjectId.set(projectId, nowIso);
|
|
922
|
-
return { issues, project, cacheUsed: true };
|
|
923
|
-
};
|
|
924
|
-
createNewIssue = async (
|
|
925
|
-
org: string,
|
|
926
|
-
repo: string,
|
|
927
|
-
title: string,
|
|
928
|
-
body: string,
|
|
929
|
-
assignees: string[],
|
|
930
|
-
labels: string[],
|
|
931
|
-
): Promise<number> => {
|
|
932
|
-
return await this.restIssueRepository.createNewIssue(
|
|
933
|
-
org,
|
|
934
|
-
repo,
|
|
935
|
-
title,
|
|
936
|
-
body,
|
|
937
|
-
assignees,
|
|
938
|
-
labels,
|
|
939
|
-
);
|
|
940
|
-
};
|
|
941
|
-
searchIssue = async (query: {
|
|
942
|
-
owner: string;
|
|
943
|
-
repositoryName: string;
|
|
944
|
-
type?: 'issue' | 'pr';
|
|
945
|
-
state?: 'open' | 'closed' | 'all';
|
|
946
|
-
title?: string;
|
|
947
|
-
createdFrom?: string;
|
|
948
|
-
assignee?: string;
|
|
949
|
-
}): Promise<
|
|
950
|
-
{
|
|
951
|
-
url: string;
|
|
952
|
-
title: string;
|
|
953
|
-
number: string;
|
|
954
|
-
}[]
|
|
955
|
-
> => {
|
|
956
|
-
return await this.apiV3IssueRepository.searchIssue(query);
|
|
957
|
-
};
|
|
958
|
-
updateIssue = async (issue: Issue): Promise<void> => {
|
|
959
|
-
await this.restIssueRepository.updateIssue(issue);
|
|
960
|
-
};
|
|
961
|
-
updateIssueBody = async (
|
|
962
|
-
issue: Pick<Issue, 'org' | 'repo' | 'number'>,
|
|
963
|
-
body: string,
|
|
964
|
-
): Promise<void> => {
|
|
965
|
-
await this.restIssueRepository.updateIssueBody(issue, body);
|
|
966
|
-
};
|
|
967
|
-
getIssueByUrl = async (url: string): Promise<Issue | null> => {
|
|
968
|
-
const projectItem =
|
|
969
|
-
await this.graphqlProjectItemRepository.fetchProjectItemByUrl(url);
|
|
970
|
-
if (!projectItem) {
|
|
971
|
-
return null;
|
|
972
|
-
}
|
|
973
|
-
return this.convertProjectItemToIssue(projectItem);
|
|
974
|
-
};
|
|
975
|
-
addIssueToProject = async (
|
|
976
|
-
project: Project,
|
|
977
|
-
issueUrl: string,
|
|
978
|
-
): Promise<void> => {
|
|
979
|
-
await this.graphqlProjectItemRepository.addIssueToProject(
|
|
980
|
-
project.id,
|
|
981
|
-
issueUrl,
|
|
982
|
-
);
|
|
983
|
-
};
|
|
984
|
-
setDependedIssueUrl = async (
|
|
985
|
-
prUrl: string,
|
|
986
|
-
project: Project,
|
|
987
|
-
issueUrl: string,
|
|
988
|
-
): Promise<void> => {
|
|
989
|
-
const dependedIssueUrlField = project.dependedIssueUrlSeparatedByComma;
|
|
990
|
-
if (!dependedIssueUrlField) {
|
|
991
|
-
return;
|
|
992
|
-
}
|
|
993
|
-
const existingProjectItem =
|
|
994
|
-
await this.graphqlProjectItemRepository.fetchProjectItemByUrl(
|
|
995
|
-
prUrl,
|
|
996
|
-
project.id,
|
|
997
|
-
);
|
|
998
|
-
const existingValue = existingProjectItem?.customFields.find(
|
|
999
|
-
(field) => field.name === dependedIssueUrlField.name,
|
|
1000
|
-
)?.value;
|
|
1001
|
-
if (existingValue) {
|
|
1002
|
-
return;
|
|
1003
|
-
}
|
|
1004
|
-
const projectItemId =
|
|
1005
|
-
existingProjectItem?.id ??
|
|
1006
|
-
(await this.graphqlProjectItemRepository.addIssueToProject(
|
|
1007
|
-
project.id,
|
|
1008
|
-
prUrl,
|
|
1009
|
-
));
|
|
1010
|
-
await this.graphqlProjectItemRepository.updateProjectTextField(
|
|
1011
|
-
project.id,
|
|
1012
|
-
dependedIssueUrlField.fieldId,
|
|
1013
|
-
projectItemId,
|
|
1014
|
-
issueUrl,
|
|
1015
|
-
);
|
|
1016
|
-
};
|
|
1017
|
-
|
|
1018
|
-
setIssueAgentField = async (
|
|
1019
|
-
issueUrl: string,
|
|
1020
|
-
project: Project,
|
|
1021
|
-
agentOptionId: string,
|
|
1022
|
-
): Promise<void> => {
|
|
1023
|
-
if (!project.agent) {
|
|
1024
|
-
return;
|
|
1025
|
-
}
|
|
1026
|
-
const existingProjectItem =
|
|
1027
|
-
await this.graphqlProjectItemRepository.fetchProjectItemByUrl(
|
|
1028
|
-
issueUrl,
|
|
1029
|
-
project.id,
|
|
1030
|
-
);
|
|
1031
|
-
const projectItemId =
|
|
1032
|
-
existingProjectItem?.id ??
|
|
1033
|
-
(await this.graphqlProjectItemRepository.addIssueToProject(
|
|
1034
|
-
project.id,
|
|
1035
|
-
issueUrl,
|
|
1036
|
-
));
|
|
1037
|
-
await this.graphqlProjectItemRepository.updateProjectField(
|
|
1038
|
-
project.id,
|
|
1039
|
-
project.agent.fieldId,
|
|
1040
|
-
projectItemId,
|
|
1041
|
-
{ singleSelectOptionId: agentOptionId },
|
|
1042
|
-
);
|
|
1043
|
-
};
|
|
1044
|
-
|
|
1045
|
-
updateNextActionDate = async (
|
|
1046
|
-
issueUrl: string,
|
|
1047
|
-
project: Project,
|
|
1048
|
-
date: Date,
|
|
1049
|
-
projectItemId?: string,
|
|
1050
|
-
): Promise<void> => {
|
|
1051
|
-
if (!project.nextActionDate) {
|
|
1052
|
-
return;
|
|
1053
|
-
}
|
|
1054
|
-
// When the caller already knows the project item id (e.g. the console,
|
|
1055
|
-
// which receives it in the request body), use it directly and skip the
|
|
1056
|
-
// GraphQL fetchProjectItemByUrl lookup. Fall back to the lookup only when
|
|
1057
|
-
// no id was supplied, preserving the original behavior for other callers.
|
|
1058
|
-
const itemId =
|
|
1059
|
-
projectItemId ??
|
|
1060
|
-
(
|
|
1061
|
-
await this.graphqlProjectItemRepository.fetchProjectItemByUrl(
|
|
1062
|
-
issueUrl,
|
|
1063
|
-
project.id,
|
|
1064
|
-
)
|
|
1065
|
-
)?.id;
|
|
1066
|
-
if (!itemId) {
|
|
1067
|
-
return;
|
|
1068
|
-
}
|
|
1069
|
-
return this.graphqlProjectItemRepository.updateProjectField(
|
|
1070
|
-
project.id,
|
|
1071
|
-
project.nextActionDate.fieldId,
|
|
1072
|
-
itemId,
|
|
1073
|
-
{ date: date.toISOString().split('T')[0] },
|
|
1074
|
-
);
|
|
1075
|
-
};
|
|
1076
|
-
updateNextActionHour = async (
|
|
1077
|
-
project: Project & {
|
|
1078
|
-
nextActionHour: NonNullable<Project['nextActionHour']>;
|
|
1079
|
-
},
|
|
1080
|
-
issue: Issue,
|
|
1081
|
-
hour: number,
|
|
1082
|
-
): Promise<void> => {
|
|
1083
|
-
const option = project.nextActionHour.options.find(
|
|
1084
|
-
(o) => o.name === String(hour),
|
|
1085
|
-
);
|
|
1086
|
-
const value = option
|
|
1087
|
-
? { singleSelectOptionId: option.id }
|
|
1088
|
-
: { number: hour };
|
|
1089
|
-
return this.graphqlProjectItemRepository.updateProjectField(
|
|
1090
|
-
project.id,
|
|
1091
|
-
project.nextActionHour.fieldId,
|
|
1092
|
-
issue.itemId,
|
|
1093
|
-
value,
|
|
1094
|
-
);
|
|
1095
|
-
};
|
|
1096
|
-
updateStory = async (
|
|
1097
|
-
project: Project & { story: NonNullable<Project['story']> },
|
|
1098
|
-
issue: Issue,
|
|
1099
|
-
storyOptionId: string,
|
|
1100
|
-
): Promise<void> => {
|
|
1101
|
-
await this.graphqlProjectItemRepository.updateProjectField(
|
|
1102
|
-
project.id,
|
|
1103
|
-
project.story.fieldId,
|
|
1104
|
-
issue.itemId,
|
|
1105
|
-
{ singleSelectOptionId: storyOptionId },
|
|
1106
|
-
);
|
|
1107
|
-
};
|
|
1108
|
-
updateStoryOptionColor = async (
|
|
1109
|
-
project: Project & { story: NonNullable<Project['story']> },
|
|
1110
|
-
storyOptionId: string,
|
|
1111
|
-
newColor: FieldOption['color'],
|
|
1112
|
-
): Promise<void> => {
|
|
1113
|
-
const mutation = `mutation UpdateStoryOptionColor($fieldId: ID!, $options: [ProjectV2SingleSelectFieldOptionInput!]!) {
|
|
1114
|
-
updateProjectV2Field(input: {
|
|
1115
|
-
fieldId: $fieldId
|
|
1116
|
-
singleSelectOptions: $options
|
|
1117
|
-
}) {
|
|
1118
|
-
projectV2Field {
|
|
1119
|
-
... on ProjectV2SingleSelectField {
|
|
1120
|
-
options { id name color description }
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
}`;
|
|
1125
|
-
const options = project.story.stories.map((opt) => ({
|
|
1126
|
-
id: opt.id,
|
|
1127
|
-
name: opt.name,
|
|
1128
|
-
color: opt.id === storyOptionId ? newColor : opt.color,
|
|
1129
|
-
description: opt.description,
|
|
1130
|
-
}));
|
|
1131
|
-
const response = await postGithubGraphqlJson<{
|
|
1132
|
-
errors?: { message: string }[];
|
|
1133
|
-
}>({
|
|
1134
|
-
ghToken: this.ghToken,
|
|
1135
|
-
query: mutation,
|
|
1136
|
-
variables: { fieldId: project.story.fieldId, options },
|
|
1137
|
-
});
|
|
1138
|
-
if (response.errors && response.errors.length > 0) {
|
|
1139
|
-
throw new Error(response.errors.map((e) => e.message).join('; '));
|
|
1140
|
-
}
|
|
1141
|
-
};
|
|
1142
|
-
clearProjectField = async (
|
|
1143
|
-
project: Project,
|
|
1144
|
-
fieldId: string,
|
|
1145
|
-
issue: Issue,
|
|
1146
|
-
): Promise<void> => {
|
|
1147
|
-
await this.graphqlProjectItemRepository.clearProjectField(
|
|
1148
|
-
project.id,
|
|
1149
|
-
fieldId,
|
|
1150
|
-
issue.itemId,
|
|
1151
|
-
);
|
|
1152
|
-
return;
|
|
1153
|
-
};
|
|
1154
|
-
createComment = async (issue: Issue, comment: string): Promise<void> => {
|
|
1155
|
-
await this.restIssueRepository.createComment(issue.url, comment);
|
|
1156
|
-
};
|
|
1157
|
-
updateProjectTextField = async (
|
|
1158
|
-
project: Project,
|
|
1159
|
-
fieldId: string,
|
|
1160
|
-
issue: Issue,
|
|
1161
|
-
text: string,
|
|
1162
|
-
): Promise<void> => {
|
|
1163
|
-
await this.graphqlProjectItemRepository.updateProjectTextField(
|
|
1164
|
-
project.id,
|
|
1165
|
-
fieldId,
|
|
1166
|
-
issue.itemId,
|
|
1167
|
-
text,
|
|
1168
|
-
);
|
|
1169
|
-
};
|
|
1170
|
-
|
|
1171
|
-
updateLabels = (issue: Issue, labels: Issue['labels']): Promise<void> => {
|
|
1172
|
-
return this.restIssueRepository.updateLabels(issue, labels);
|
|
1173
|
-
};
|
|
1174
|
-
removeLabel = (issue: Issue, label: string): Promise<void> => {
|
|
1175
|
-
return this.restIssueRepository.removeLabel(issue, label);
|
|
1176
|
-
};
|
|
1177
|
-
getOrCreateLabel = (
|
|
1178
|
-
org: string,
|
|
1179
|
-
repo: string,
|
|
1180
|
-
labelName: string,
|
|
1181
|
-
): Promise<void> => {
|
|
1182
|
-
return this.restIssueRepository.getOrCreateLabel(org, repo, labelName);
|
|
1183
|
-
};
|
|
1184
|
-
updateAssigneeList = (
|
|
1185
|
-
issue: Pick<Issue, 'org' | 'repo' | 'number'>,
|
|
1186
|
-
assigneeList: Member['name'][],
|
|
1187
|
-
): Promise<void> => {
|
|
1188
|
-
return this.restIssueRepository.updateAssigneeList(issue, assigneeList);
|
|
1189
|
-
};
|
|
1190
|
-
searchIssues = (query: string): Promise<SearchedIssue[]> => {
|
|
1191
|
-
return this.restIssueRepository.searchIssues(query);
|
|
1192
|
-
};
|
|
1193
|
-
get = async (issueUrl: string, project: Project): Promise<Issue | null> => {
|
|
1194
|
-
const projectItem =
|
|
1195
|
-
await this.graphqlProjectItemRepository.fetchProjectItemByUrl(
|
|
1196
|
-
issueUrl,
|
|
1197
|
-
project.id,
|
|
1198
|
-
);
|
|
1199
|
-
if (!projectItem) {
|
|
1200
|
-
return null;
|
|
1201
|
-
}
|
|
1202
|
-
return this.convertProjectItemToIssue(projectItem);
|
|
1203
|
-
};
|
|
1204
|
-
update = async (issue: Issue, _project: Project): Promise<void> => {
|
|
1205
|
-
await this.updateIssue(issue);
|
|
1206
|
-
};
|
|
1207
|
-
private parseIssueUrl = (
|
|
1208
|
-
issueUrl: string,
|
|
1209
|
-
): {
|
|
1210
|
-
owner: string;
|
|
1211
|
-
repo: string;
|
|
1212
|
-
issueNumber: number;
|
|
1213
|
-
isPr: boolean;
|
|
1214
|
-
} => {
|
|
1215
|
-
const urlMatch = issueUrl.match(
|
|
1216
|
-
/github\.com\/([^/]+)\/([^/]+)\/(issues|pull)\/(\d+)/,
|
|
1217
|
-
);
|
|
1218
|
-
if (!urlMatch) {
|
|
1219
|
-
throw new Error(`Invalid GitHub issue URL: ${issueUrl}`);
|
|
1220
|
-
}
|
|
1221
|
-
return {
|
|
1222
|
-
owner: urlMatch[1],
|
|
1223
|
-
repo: urlMatch[2],
|
|
1224
|
-
issueNumber: parseInt(urlMatch[4], 10),
|
|
1225
|
-
isPr: urlMatch[3] === 'pull',
|
|
1226
|
-
};
|
|
1227
|
-
};
|
|
1228
|
-
|
|
1229
|
-
private computePrStatus = (
|
|
1230
|
-
prUrl: string,
|
|
1231
|
-
headRefName: string | undefined,
|
|
1232
|
-
data: PrStatusComputationData,
|
|
1233
|
-
): RelatedPullRequest => {
|
|
1234
|
-
const isConflicted = data.mergeable === 'CONFLICTING';
|
|
1235
|
-
const hasStatusCheckRollup = data.ciContexts.length > 0;
|
|
1236
|
-
const contexts = data.ciContexts;
|
|
1237
|
-
|
|
1238
|
-
const requiredCheckNames = data.requiredCheckNames;
|
|
1239
|
-
const seenContextNames = new Set<string>();
|
|
1240
|
-
for (const ctx of contexts) {
|
|
1241
|
-
if ('name' in ctx) {
|
|
1242
|
-
seenContextNames.add(ctx.name);
|
|
1243
|
-
}
|
|
1244
|
-
if ('context' in ctx) {
|
|
1245
|
-
seenContextNames.add(ctx.context);
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
const missingRequiredCheckNames = requiredCheckNames.filter(
|
|
1250
|
-
(name) => !seenContextNames.has(name),
|
|
1251
|
-
);
|
|
1252
|
-
const allRequiredChecksPassed = missingRequiredCheckNames.length === 0;
|
|
1253
|
-
|
|
1254
|
-
const latestCheckRunByName = new Map<
|
|
1255
|
-
string,
|
|
1256
|
-
{ conclusion: string | null; databaseId: number }
|
|
1257
|
-
>();
|
|
1258
|
-
for (const ctx of contexts) {
|
|
1259
|
-
if (ctx.__typename === 'CheckRun') {
|
|
1260
|
-
const existing = latestCheckRunByName.get(ctx.name);
|
|
1261
|
-
if (!existing || ctx.databaseId > existing.databaseId) {
|
|
1262
|
-
latestCheckRunByName.set(ctx.name, {
|
|
1263
|
-
conclusion: ctx.conclusion,
|
|
1264
|
-
databaseId: ctx.databaseId,
|
|
1265
|
-
});
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
|
-
const failureConclusions = new Set([
|
|
1270
|
-
'FAILURE',
|
|
1271
|
-
'CANCELLED',
|
|
1272
|
-
'TIMED_OUT',
|
|
1273
|
-
'ACTION_REQUIRED',
|
|
1274
|
-
'STARTUP_FAILURE',
|
|
1275
|
-
'STALE',
|
|
1276
|
-
]);
|
|
1277
|
-
const isCiStateSuccess = (() => {
|
|
1278
|
-
if (!hasStatusCheckRollup) return false;
|
|
1279
|
-
const latestRuns = [...latestCheckRunByName.values()];
|
|
1280
|
-
const statusContexts = contexts.filter(
|
|
1281
|
-
(
|
|
1282
|
-
ctx,
|
|
1283
|
-
): ctx is {
|
|
1284
|
-
__typename: 'StatusContext';
|
|
1285
|
-
context: string;
|
|
1286
|
-
state: string;
|
|
1287
|
-
} => ctx.__typename === 'StatusContext',
|
|
1288
|
-
);
|
|
1289
|
-
const hasFailure =
|
|
1290
|
-
latestRuns.some(
|
|
1291
|
-
(r) => r.conclusion !== null && failureConclusions.has(r.conclusion),
|
|
1292
|
-
) ||
|
|
1293
|
-
statusContexts.some(
|
|
1294
|
-
(ctx) => ctx.state === 'FAILURE' || ctx.state === 'ERROR',
|
|
1295
|
-
);
|
|
1296
|
-
if (hasFailure) return false;
|
|
1297
|
-
const hasPending =
|
|
1298
|
-
latestRuns.some((r) => r.conclusion === null) ||
|
|
1299
|
-
statusContexts.some((ctx) => ctx.state === 'PENDING');
|
|
1300
|
-
return !hasPending;
|
|
1301
|
-
})();
|
|
1302
|
-
const isPassedAllCiJob = isCiStateSuccess && allRequiredChecksPassed;
|
|
1303
|
-
|
|
1304
|
-
const reviewThreads = data.reviewThreads;
|
|
1305
|
-
const isResolvedAllReviewComments =
|
|
1306
|
-
reviewThreads.length === 0 ||
|
|
1307
|
-
reviewThreads.every((thread) => thread.isResolved);
|
|
1308
|
-
|
|
1309
|
-
return {
|
|
1310
|
-
url: prUrl,
|
|
1311
|
-
branchName: headRefName ?? null,
|
|
1312
|
-
createdAt: new Date(0),
|
|
1313
|
-
isDraft: data.isDraft === true,
|
|
1314
|
-
isConflicted,
|
|
1315
|
-
mergeable: data.mergeable ?? null,
|
|
1316
|
-
isPassedAllCiJob,
|
|
1317
|
-
isCiStateSuccess,
|
|
1318
|
-
isResolvedAllReviewComments,
|
|
1319
|
-
isBranchOutOfDate: false,
|
|
1320
|
-
missingRequiredCheckNames,
|
|
1321
|
-
};
|
|
1322
|
-
};
|
|
1323
|
-
|
|
1324
|
-
private prBodyContainsCrossRepoClosingKeyword = (
|
|
1325
|
-
prBody: string | null,
|
|
1326
|
-
issueUrl: string,
|
|
1327
|
-
): boolean => {
|
|
1328
|
-
if (!prBody) return false;
|
|
1329
|
-
const closingKeywords =
|
|
1330
|
-
/(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s+/gi;
|
|
1331
|
-
const normalizedIssueUrl = issueUrl.replace(/\/+$/, '');
|
|
1332
|
-
let match: RegExpExecArray | null;
|
|
1333
|
-
while ((match = closingKeywords.exec(prBody)) !== null) {
|
|
1334
|
-
const afterKeyword = prBody.slice(match.index + match[0].length);
|
|
1335
|
-
const urlMatch = afterKeyword.match(/^https?:\/\/[^\s]+/);
|
|
1336
|
-
if (!urlMatch) continue;
|
|
1337
|
-
const candidateUrl = urlMatch[0].replace(/\/+$/, '');
|
|
1338
|
-
if (candidateUrl.toLowerCase() === normalizedIssueUrl.toLowerCase())
|
|
1339
|
-
return true;
|
|
1340
|
-
}
|
|
1341
|
-
return false;
|
|
1342
|
-
};
|
|
1343
|
-
|
|
1344
|
-
private readonly requiredCheckNamesCache = new Map<
|
|
1345
|
-
string,
|
|
1346
|
-
{ fetchedAtMs: number; names: string[] }
|
|
1347
|
-
>();
|
|
1348
|
-
|
|
1349
|
-
private getRequiredCheckNames = async (
|
|
1350
|
-
owner: string,
|
|
1351
|
-
repo: string,
|
|
1352
|
-
branch: string,
|
|
1353
|
-
): Promise<string[]> => {
|
|
1354
|
-
const cacheKey = `${owner}/${repo}/${branch}`;
|
|
1355
|
-
const nowMs = (await this.dateRepository.now()).getTime();
|
|
1356
|
-
const cached = this.requiredCheckNamesCache.get(cacheKey);
|
|
1357
|
-
if (cached && nowMs - cached.fetchedAtMs < REQUIRED_CHECKS_CACHE_TTL_MS) {
|
|
1358
|
-
return cached.names;
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
const ownerSegment = encodeURIComponent(owner);
|
|
1362
|
-
const repoSegment = encodeURIComponent(repo);
|
|
1363
|
-
const branchSegment = encodeURIComponent(branch);
|
|
1364
|
-
const requiredCheckNamesSet = new Set<string>();
|
|
1365
|
-
|
|
1366
|
-
const rulesResponse = await this.fetchWithRateLimitRetry(() =>
|
|
1367
|
-
fetch(
|
|
1368
|
-
`https://api.github.com/repos/${ownerSegment}/${repoSegment}/rules/branches/${branchSegment}?per_page=100`,
|
|
1369
|
-
{
|
|
1370
|
-
method: 'GET',
|
|
1371
|
-
headers: {
|
|
1372
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
1373
|
-
Accept: 'application/vnd.github+json',
|
|
1374
|
-
},
|
|
1375
|
-
},
|
|
1376
|
-
),
|
|
1377
|
-
);
|
|
1378
|
-
if (rulesResponse.ok) {
|
|
1379
|
-
const rulesBody: unknown = await rulesResponse.json();
|
|
1380
|
-
if (!isBranchRulesResponse(rulesBody)) {
|
|
1381
|
-
throw new Error(
|
|
1382
|
-
`Unexpected response shape when fetching branch rules: ${owner}/${repo}/${branch}`,
|
|
1383
|
-
);
|
|
1384
|
-
}
|
|
1385
|
-
for (const rule of rulesBody) {
|
|
1386
|
-
if (rule.type !== 'required_status_checks') continue;
|
|
1387
|
-
for (const check of rule.parameters?.required_status_checks || []) {
|
|
1388
|
-
requiredCheckNamesSet.add(check.context);
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
|
-
} else if (rulesResponse.status === 403) {
|
|
1392
|
-
const reason = await this.formatGitHubErrorWithStatus(rulesResponse);
|
|
1393
|
-
console.warn(
|
|
1394
|
-
`ApiV3CheerioRestIssueRepository: branch rules are not accessible for ${owner}/${repo}/${branch}, treating as no required checks. reason: ${reason}`,
|
|
1395
|
-
);
|
|
1396
|
-
} else if (rulesResponse.status !== 404) {
|
|
1397
|
-
const reason = await this.formatGitHubErrorWithStatus(rulesResponse);
|
|
1398
|
-
throw new Error(
|
|
1399
|
-
`Failed to fetch branch rules for ${owner}/${repo}/${branch}: ${reason}`,
|
|
1400
|
-
);
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1403
|
-
const branchResponse = await this.fetchWithRateLimitRetry(() =>
|
|
1404
|
-
fetch(
|
|
1405
|
-
`https://api.github.com/repos/${ownerSegment}/${repoSegment}/branches/${branchSegment}`,
|
|
1406
|
-
{
|
|
1407
|
-
method: 'GET',
|
|
1408
|
-
headers: {
|
|
1409
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
1410
|
-
Accept: 'application/vnd.github+json',
|
|
1411
|
-
},
|
|
1412
|
-
},
|
|
1413
|
-
),
|
|
1414
|
-
);
|
|
1415
|
-
if (branchResponse.ok) {
|
|
1416
|
-
const branchBody: unknown = await branchResponse.json();
|
|
1417
|
-
if (!isBranchDetailResponse(branchBody)) {
|
|
1418
|
-
throw new Error(
|
|
1419
|
-
`Unexpected response shape when fetching branch detail: ${owner}/${repo}/${branch}`,
|
|
1420
|
-
);
|
|
1421
|
-
}
|
|
1422
|
-
for (const context of branchBody.protection?.required_status_checks
|
|
1423
|
-
?.contexts || []) {
|
|
1424
|
-
requiredCheckNamesSet.add(context);
|
|
1425
|
-
}
|
|
1426
|
-
} else if (branchResponse.status === 403) {
|
|
1427
|
-
const reason = await this.formatGitHubErrorWithStatus(branchResponse);
|
|
1428
|
-
console.warn(
|
|
1429
|
-
`ApiV3CheerioRestIssueRepository: branch detail (classic protection) is not accessible for ${owner}/${repo}/${branch}, treating as no required checks. reason: ${reason}`,
|
|
1430
|
-
);
|
|
1431
|
-
} else if (branchResponse.status !== 404) {
|
|
1432
|
-
const reason = await this.formatGitHubErrorWithStatus(branchResponse);
|
|
1433
|
-
throw new Error(
|
|
1434
|
-
`Failed to fetch branch detail for ${owner}/${repo}/${branch}: ${reason}`,
|
|
1435
|
-
);
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
const names = Array.from(requiredCheckNamesSet);
|
|
1439
|
-
this.requiredCheckNamesCache.set(cacheKey, {
|
|
1440
|
-
fetchedAtMs: nowMs,
|
|
1441
|
-
names,
|
|
1442
|
-
});
|
|
1443
|
-
return names;
|
|
1444
|
-
};
|
|
1445
|
-
|
|
1446
|
-
private getCheckRunsViaCheckSuitesFallback = async (
|
|
1447
|
-
owner: string,
|
|
1448
|
-
repo: string,
|
|
1449
|
-
commitSha: string,
|
|
1450
|
-
): Promise<CiContextNode[]> => {
|
|
1451
|
-
const ownerSegment = encodeURIComponent(owner);
|
|
1452
|
-
const repoSegment = encodeURIComponent(repo);
|
|
1453
|
-
const shaSegment = encodeURIComponent(commitSha);
|
|
1454
|
-
|
|
1455
|
-
const checkSuitesResponse = await this.fetchWithRateLimitRetry(() =>
|
|
1456
|
-
fetch(
|
|
1457
|
-
`https://api.github.com/repos/${ownerSegment}/${repoSegment}/commits/${shaSegment}/check-suites`,
|
|
1458
|
-
{
|
|
1459
|
-
method: 'GET',
|
|
1460
|
-
headers: {
|
|
1461
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
1462
|
-
Accept: 'application/vnd.github+json',
|
|
1463
|
-
},
|
|
1464
|
-
},
|
|
1465
|
-
),
|
|
1466
|
-
);
|
|
1467
|
-
|
|
1468
|
-
if (!checkSuitesResponse.ok) {
|
|
1469
|
-
if (checkSuitesResponse.status === 404) {
|
|
1470
|
-
console.warn(
|
|
1471
|
-
`ApiV3CheerioRestIssueRepository: commits/${commitSha}/check-suites returned 404 for ${owner}/${repo}, treating as no check runs.`,
|
|
1472
|
-
);
|
|
1473
|
-
return [];
|
|
1474
|
-
}
|
|
1475
|
-
const reason =
|
|
1476
|
-
await this.formatGitHubErrorWithStatus(checkSuitesResponse);
|
|
1477
|
-
throw new Error(
|
|
1478
|
-
`Failed to fetch check suites for ${owner}/${repo}@${commitSha}: ${reason}`,
|
|
1479
|
-
);
|
|
1480
|
-
}
|
|
1481
|
-
|
|
1482
|
-
const checkSuitesBody: unknown = await checkSuitesResponse.json();
|
|
1483
|
-
if (!isCheckSuitesResponse(checkSuitesBody)) {
|
|
1484
|
-
throw new Error(
|
|
1485
|
-
`Unexpected response shape when fetching check suites: ${owner}/${repo}@${commitSha}`,
|
|
1486
|
-
);
|
|
1487
|
-
}
|
|
1488
|
-
|
|
1489
|
-
const contexts: CiContextNode[] = [];
|
|
1490
|
-
const perPage = 100;
|
|
1491
|
-
for (const suite of checkSuitesBody.check_suites) {
|
|
1492
|
-
let page = 1;
|
|
1493
|
-
let hasMore = true;
|
|
1494
|
-
while (hasMore) {
|
|
1495
|
-
const runsResponse = await this.fetchWithRateLimitRetry(() =>
|
|
1496
|
-
fetch(
|
|
1497
|
-
`https://api.github.com/repos/${ownerSegment}/${repoSegment}/check-suites/${suite.id}/check-runs?per_page=${perPage}&page=${page}`,
|
|
1498
|
-
{
|
|
1499
|
-
method: 'GET',
|
|
1500
|
-
headers: {
|
|
1501
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
1502
|
-
Accept: 'application/vnd.github+json',
|
|
1503
|
-
},
|
|
1504
|
-
},
|
|
1505
|
-
),
|
|
1506
|
-
);
|
|
1507
|
-
if (!runsResponse.ok) {
|
|
1508
|
-
const reason = await this.formatGitHubErrorWithStatus(runsResponse);
|
|
1509
|
-
throw new Error(
|
|
1510
|
-
`Failed to fetch check runs for suite ${suite.id} in ${owner}/${repo}: ${reason}`,
|
|
1511
|
-
);
|
|
1512
|
-
}
|
|
1513
|
-
const runsBody: unknown = await runsResponse.json();
|
|
1514
|
-
if (!isCheckRunsResponse(runsBody)) {
|
|
1515
|
-
throw new Error(
|
|
1516
|
-
`Unexpected response shape when fetching check runs for suite ${suite.id}: ${owner}/${repo}@${commitSha}`,
|
|
1517
|
-
);
|
|
1518
|
-
}
|
|
1519
|
-
for (const checkRun of runsBody.check_runs) {
|
|
1520
|
-
contexts.push({
|
|
1521
|
-
__typename: 'CheckRun',
|
|
1522
|
-
name: checkRun.name,
|
|
1523
|
-
conclusion: checkRun.conclusion
|
|
1524
|
-
? checkRun.conclusion.toUpperCase()
|
|
1525
|
-
: null,
|
|
1526
|
-
databaseId: checkRun.id,
|
|
1527
|
-
});
|
|
1528
|
-
}
|
|
1529
|
-
if (
|
|
1530
|
-
runsBody.check_runs.length < perPage ||
|
|
1531
|
-
page * perPage >= runsBody.total_count
|
|
1532
|
-
) {
|
|
1533
|
-
hasMore = false;
|
|
1534
|
-
} else {
|
|
1535
|
-
page += 1;
|
|
1536
|
-
}
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
return contexts;
|
|
1540
|
-
};
|
|
1541
|
-
|
|
1542
|
-
private getCommitCiContexts = async (
|
|
1543
|
-
owner: string,
|
|
1544
|
-
repo: string,
|
|
1545
|
-
commitSha: string,
|
|
1546
|
-
): Promise<CiContextNode[]> => {
|
|
1547
|
-
const ownerSegment = encodeURIComponent(owner);
|
|
1548
|
-
const repoSegment = encodeURIComponent(repo);
|
|
1549
|
-
const shaSegment = encodeURIComponent(commitSha);
|
|
1550
|
-
const contexts: CiContextNode[] = [];
|
|
1551
|
-
|
|
1552
|
-
const perPage = 100;
|
|
1553
|
-
let page = 1;
|
|
1554
|
-
let hasMore = true;
|
|
1555
|
-
let usedCheckSuitesFallback = false;
|
|
1556
|
-
while (hasMore) {
|
|
1557
|
-
const checkRunsResponse = await this.fetchWithRateLimitRetry(() =>
|
|
1558
|
-
fetch(
|
|
1559
|
-
`https://api.github.com/repos/${ownerSegment}/${repoSegment}/commits/${shaSegment}/check-runs?per_page=${perPage}&page=${page}`,
|
|
1560
|
-
{
|
|
1561
|
-
method: 'GET',
|
|
1562
|
-
headers: {
|
|
1563
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
1564
|
-
Accept: 'application/vnd.github+json',
|
|
1565
|
-
},
|
|
1566
|
-
},
|
|
1567
|
-
),
|
|
1568
|
-
);
|
|
1569
|
-
if (!checkRunsResponse.ok) {
|
|
1570
|
-
if (checkRunsResponse.status === 404 && page === 1) {
|
|
1571
|
-
const fallbackContexts =
|
|
1572
|
-
await this.getCheckRunsViaCheckSuitesFallback(
|
|
1573
|
-
owner,
|
|
1574
|
-
repo,
|
|
1575
|
-
commitSha,
|
|
1576
|
-
);
|
|
1577
|
-
contexts.push(...fallbackContexts);
|
|
1578
|
-
usedCheckSuitesFallback = true;
|
|
1579
|
-
break;
|
|
1580
|
-
}
|
|
1581
|
-
const reason =
|
|
1582
|
-
await this.formatGitHubErrorWithStatus(checkRunsResponse);
|
|
1583
|
-
throw new Error(
|
|
1584
|
-
`Failed to fetch check runs for ${owner}/${repo}@${commitSha}: ${reason}`,
|
|
1585
|
-
);
|
|
1586
|
-
}
|
|
1587
|
-
const checkRunsBody: unknown = await checkRunsResponse.json();
|
|
1588
|
-
if (!isCheckRunsResponse(checkRunsBody)) {
|
|
1589
|
-
throw new Error(
|
|
1590
|
-
`Unexpected response shape when fetching check runs: ${owner}/${repo}@${commitSha}`,
|
|
1591
|
-
);
|
|
1592
|
-
}
|
|
1593
|
-
for (const checkRun of checkRunsBody.check_runs) {
|
|
1594
|
-
contexts.push({
|
|
1595
|
-
__typename: 'CheckRun',
|
|
1596
|
-
name: checkRun.name,
|
|
1597
|
-
conclusion: checkRun.conclusion
|
|
1598
|
-
? checkRun.conclusion.toUpperCase()
|
|
1599
|
-
: null,
|
|
1600
|
-
databaseId: checkRun.id,
|
|
1601
|
-
});
|
|
1602
|
-
}
|
|
1603
|
-
if (
|
|
1604
|
-
checkRunsBody.check_runs.length < perPage ||
|
|
1605
|
-
page * perPage >= checkRunsBody.total_count
|
|
1606
|
-
) {
|
|
1607
|
-
hasMore = false;
|
|
1608
|
-
} else {
|
|
1609
|
-
page += 1;
|
|
1610
|
-
}
|
|
1611
|
-
}
|
|
1612
|
-
|
|
1613
|
-
if (!usedCheckSuitesFallback) {
|
|
1614
|
-
const combinedStatusResponse = await this.fetchWithRateLimitRetry(() =>
|
|
1615
|
-
fetch(
|
|
1616
|
-
`https://api.github.com/repos/${ownerSegment}/${repoSegment}/commits/${shaSegment}/status?per_page=100`,
|
|
1617
|
-
{
|
|
1618
|
-
method: 'GET',
|
|
1619
|
-
headers: {
|
|
1620
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
1621
|
-
Accept: 'application/vnd.github+json',
|
|
1622
|
-
},
|
|
1623
|
-
},
|
|
1624
|
-
),
|
|
1625
|
-
);
|
|
1626
|
-
if (!combinedStatusResponse.ok) {
|
|
1627
|
-
const reason = await this.formatGitHubErrorWithStatus(
|
|
1628
|
-
combinedStatusResponse,
|
|
1629
|
-
);
|
|
1630
|
-
throw new Error(
|
|
1631
|
-
`Failed to fetch combined status for ${owner}/${repo}@${commitSha}: ${reason}`,
|
|
1632
|
-
);
|
|
1633
|
-
}
|
|
1634
|
-
const combinedStatusBody: unknown = await combinedStatusResponse.json();
|
|
1635
|
-
if (!isCombinedStatusResponse(combinedStatusBody)) {
|
|
1636
|
-
throw new Error(
|
|
1637
|
-
`Unexpected response shape when fetching combined status: ${owner}/${repo}@${commitSha}`,
|
|
1638
|
-
);
|
|
1639
|
-
}
|
|
1640
|
-
for (const status of combinedStatusBody.statuses) {
|
|
1641
|
-
contexts.push({
|
|
1642
|
-
__typename: 'StatusContext',
|
|
1643
|
-
context: status.context,
|
|
1644
|
-
state: status.state.toUpperCase(),
|
|
1645
|
-
});
|
|
1646
|
-
}
|
|
1647
|
-
}
|
|
1648
|
-
|
|
1649
|
-
return contexts;
|
|
1650
|
-
};
|
|
1651
|
-
|
|
1652
|
-
private fetchSlimPullRequest = async (
|
|
1653
|
-
owner: string,
|
|
1654
|
-
repo: string,
|
|
1655
|
-
prNumber: number,
|
|
1656
|
-
): Promise<SlimPullRequest | null> => {
|
|
1657
|
-
const query = `
|
|
1658
|
-
query PullRequestSlimStatus($owner: String!, $repo: String!, $prNumber: Int!, $reviewThreadsAfter: String) {
|
|
1659
|
-
repository(owner: $owner, name: $repo) {
|
|
1660
|
-
pullRequest(number: $prNumber) {
|
|
1661
|
-
url
|
|
1662
|
-
state
|
|
1663
|
-
isDraft
|
|
1664
|
-
headRefName
|
|
1665
|
-
baseRefName
|
|
1666
|
-
mergeable
|
|
1667
|
-
headRefOid
|
|
1668
|
-
reviewThreads(first: 100, after: $reviewThreadsAfter) {
|
|
1669
|
-
pageInfo {
|
|
1670
|
-
endCursor
|
|
1671
|
-
hasNextPage
|
|
1672
|
-
}
|
|
1673
|
-
nodes {
|
|
1674
|
-
isResolved
|
|
1675
|
-
}
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1678
|
-
}
|
|
1679
|
-
}
|
|
1680
|
-
`;
|
|
1681
|
-
|
|
1682
|
-
let slimPullRequest: SlimPullRequest | null = null;
|
|
1683
|
-
let reviewThreadsAfter: string | null = null;
|
|
1684
|
-
let hasNextPage = true;
|
|
1685
|
-
|
|
1686
|
-
while (hasNextPage) {
|
|
1687
|
-
const response = await this.fetchWithRateLimitRetry(() =>
|
|
1688
|
-
fetchGithubGraphql({
|
|
1689
|
-
ghToken: this.ghToken,
|
|
1690
|
-
query,
|
|
1691
|
-
variables: { owner, repo, prNumber, reviewThreadsAfter },
|
|
1692
|
-
}),
|
|
1693
|
-
);
|
|
1694
|
-
|
|
1695
|
-
if (!response.ok) {
|
|
1696
|
-
throw new Error(
|
|
1697
|
-
`Failed to fetch pull request from GitHub GraphQL API: HTTP ${response.status}`,
|
|
1698
|
-
);
|
|
1699
|
-
}
|
|
1700
|
-
|
|
1701
|
-
const responseData: unknown = await response.json();
|
|
1702
|
-
if (!isSlimPullRequestResponse(responseData)) {
|
|
1703
|
-
throw new Error('Unexpected response shape when fetching pull request');
|
|
1704
|
-
}
|
|
1705
|
-
|
|
1706
|
-
if (responseData.errors && responseData.errors.length > 0) {
|
|
1707
|
-
throw new Error(
|
|
1708
|
-
`GraphQL errors: ${JSON.stringify(responseData.errors)}`,
|
|
1709
|
-
);
|
|
1710
|
-
}
|
|
1711
|
-
|
|
1712
|
-
const pr = responseData.data?.repository?.pullRequest;
|
|
1713
|
-
if (!pr) {
|
|
1714
|
-
return null;
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
|
-
if (!slimPullRequest) {
|
|
1718
|
-
slimPullRequest = {
|
|
1719
|
-
url: pr.url,
|
|
1720
|
-
state: pr.state,
|
|
1721
|
-
isDraft: pr.isDraft,
|
|
1722
|
-
headRefName: pr.headRefName,
|
|
1723
|
-
baseRefName: pr.baseRefName,
|
|
1724
|
-
mergeable: pr.mergeable,
|
|
1725
|
-
headRefOid: pr.headRefOid,
|
|
1726
|
-
reviewThreads: [],
|
|
1727
|
-
};
|
|
1728
|
-
}
|
|
1729
|
-
for (const thread of pr.reviewThreads?.nodes || []) {
|
|
1730
|
-
slimPullRequest.reviewThreads.push({ isResolved: thread.isResolved });
|
|
1731
|
-
}
|
|
1732
|
-
|
|
1733
|
-
hasNextPage = pr.reviewThreads?.pageInfo.hasNextPage === true;
|
|
1734
|
-
reviewThreadsAfter = pr.reviewThreads?.pageInfo.endCursor ?? null;
|
|
1735
|
-
}
|
|
1736
|
-
|
|
1737
|
-
return slimPullRequest;
|
|
1738
|
-
};
|
|
1739
|
-
|
|
1740
|
-
private buildRelatedPullRequestFromSlim = async (
|
|
1741
|
-
owner: string,
|
|
1742
|
-
repo: string,
|
|
1743
|
-
slimPullRequest: SlimPullRequest,
|
|
1744
|
-
): Promise<RelatedPullRequest> => {
|
|
1745
|
-
const requiredCheckNames = slimPullRequest.baseRefName
|
|
1746
|
-
? await this.getRequiredCheckNames(
|
|
1747
|
-
owner,
|
|
1748
|
-
repo,
|
|
1749
|
-
slimPullRequest.baseRefName,
|
|
1750
|
-
)
|
|
1751
|
-
: [];
|
|
1752
|
-
const ciContexts = slimPullRequest.headRefOid
|
|
1753
|
-
? await this.getCommitCiContexts(owner, repo, slimPullRequest.headRefOid)
|
|
1754
|
-
: [];
|
|
1755
|
-
return this.computePrStatus(
|
|
1756
|
-
slimPullRequest.url,
|
|
1757
|
-
slimPullRequest.headRefName,
|
|
1758
|
-
{
|
|
1759
|
-
isDraft: slimPullRequest.isDraft,
|
|
1760
|
-
mergeable: slimPullRequest.mergeable,
|
|
1761
|
-
requiredCheckNames,
|
|
1762
|
-
ciContexts,
|
|
1763
|
-
reviewThreads: slimPullRequest.reviewThreads,
|
|
1764
|
-
},
|
|
1765
|
-
);
|
|
1766
|
-
};
|
|
1767
|
-
|
|
1768
|
-
private resolveMergeabilityWithRetry = async (
|
|
1769
|
-
owner: string,
|
|
1770
|
-
repo: string,
|
|
1771
|
-
prNumber: number,
|
|
1772
|
-
): Promise<{
|
|
1773
|
-
mergeable: string | null;
|
|
1774
|
-
mergeStateStatus: string | null;
|
|
1775
|
-
} | null> => {
|
|
1776
|
-
const query = `
|
|
1777
|
-
query PullRequestMergeability($owner: String!, $repo: String!, $prNumber: Int!) {
|
|
1778
|
-
repository(owner: $owner, name: $repo) {
|
|
1779
|
-
pullRequest(number: $prNumber) {
|
|
1780
|
-
mergeable
|
|
1781
|
-
mergeStateStatus
|
|
1782
|
-
}
|
|
1783
|
-
}
|
|
1784
|
-
}
|
|
1785
|
-
`;
|
|
1786
|
-
|
|
1787
|
-
const maxAttempts = 3;
|
|
1788
|
-
const retryDelayMilliseconds = 1000;
|
|
1789
|
-
let lastResult: {
|
|
1790
|
-
mergeable: string | null;
|
|
1791
|
-
mergeStateStatus: string | null;
|
|
1792
|
-
} | null = null;
|
|
1793
|
-
|
|
1794
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
1795
|
-
if (attempt > 0) {
|
|
1796
|
-
await this.sleep(retryDelayMilliseconds);
|
|
1797
|
-
}
|
|
1798
|
-
|
|
1799
|
-
const response = await this.fetchWithRateLimitRetry(() =>
|
|
1800
|
-
fetchGithubGraphql({
|
|
1801
|
-
ghToken: this.ghToken,
|
|
1802
|
-
query,
|
|
1803
|
-
variables: { owner, repo, prNumber },
|
|
1804
|
-
}),
|
|
1805
|
-
);
|
|
1806
|
-
|
|
1807
|
-
if (!response.ok) {
|
|
1808
|
-
throw new Error(
|
|
1809
|
-
`Failed to fetch pull request mergeability from GitHub GraphQL API: HTTP ${response.status}`,
|
|
1810
|
-
);
|
|
1811
|
-
}
|
|
1812
|
-
|
|
1813
|
-
const responseData: unknown = await response.json();
|
|
1814
|
-
if (!isPullRequestMergeabilityResponse(responseData)) {
|
|
1815
|
-
throw new Error(
|
|
1816
|
-
`Unexpected response shape when fetching pull request mergeability: ${owner}/${repo}#${prNumber}`,
|
|
1817
|
-
);
|
|
1818
|
-
}
|
|
1819
|
-
|
|
1820
|
-
if (responseData.errors && responseData.errors.length > 0) {
|
|
1821
|
-
throw new Error(
|
|
1822
|
-
`GraphQL errors: ${JSON.stringify(responseData.errors)}`,
|
|
1823
|
-
);
|
|
1824
|
-
}
|
|
1825
|
-
|
|
1826
|
-
const pr = responseData.data?.repository?.pullRequest;
|
|
1827
|
-
if (!pr) {
|
|
1828
|
-
return null;
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
|
-
lastResult = {
|
|
1832
|
-
mergeable: pr.mergeable ?? null,
|
|
1833
|
-
mergeStateStatus: pr.mergeStateStatus ?? null,
|
|
1834
|
-
};
|
|
1835
|
-
|
|
1836
|
-
if (lastResult.mergeable !== null && lastResult.mergeable !== 'UNKNOWN') {
|
|
1837
|
-
return lastResult;
|
|
1838
|
-
}
|
|
1839
|
-
}
|
|
1840
|
-
|
|
1841
|
-
return lastResult;
|
|
1842
|
-
};
|
|
1843
|
-
|
|
1844
|
-
findRelatedOpenPRs = async (
|
|
1845
|
-
issueUrl: string,
|
|
1846
|
-
): Promise<RelatedPullRequest[]> => {
|
|
1847
|
-
const { owner, repo, issueNumber, isPr } = this.parseIssueUrl(issueUrl);
|
|
1848
|
-
if (isPr) {
|
|
1849
|
-
throw new Error(
|
|
1850
|
-
'findRelatedOpenPRs only supports issue URLs, not pull request URLs',
|
|
1851
|
-
);
|
|
1852
|
-
}
|
|
1853
|
-
|
|
1854
|
-
const query = `
|
|
1855
|
-
query IssueRelatedOpenPullRequests($owner: String!, $repo: String!, $issueNumber: Int!, $after: String) {
|
|
1856
|
-
repository(owner: $owner, name: $repo) {
|
|
1857
|
-
issue(number: $issueNumber) {
|
|
1858
|
-
timelineItems(first: 100, after: $after, itemTypes: [CROSS_REFERENCED_EVENT]) {
|
|
1859
|
-
pageInfo {
|
|
1860
|
-
endCursor
|
|
1861
|
-
hasNextPage
|
|
1862
|
-
}
|
|
1863
|
-
nodes {
|
|
1864
|
-
__typename
|
|
1865
|
-
... on CrossReferencedEvent {
|
|
1866
|
-
willCloseTarget
|
|
1867
|
-
source {
|
|
1868
|
-
__typename
|
|
1869
|
-
... on PullRequest {
|
|
1870
|
-
url
|
|
1871
|
-
number
|
|
1872
|
-
body
|
|
1873
|
-
state
|
|
1874
|
-
createdAt
|
|
1875
|
-
isDraft
|
|
1876
|
-
mergeable
|
|
1877
|
-
headRefName
|
|
1878
|
-
baseRefName
|
|
1879
|
-
baseRef {
|
|
1880
|
-
name
|
|
1881
|
-
}
|
|
1882
|
-
}
|
|
1883
|
-
}
|
|
1884
|
-
}
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
}
|
|
1888
|
-
}
|
|
1889
|
-
}
|
|
1890
|
-
`;
|
|
1891
|
-
|
|
1892
|
-
const relatedPRsMap: Map<string, RelatedPullRequest> = new Map();
|
|
1893
|
-
let after: string | null = null;
|
|
1894
|
-
let hasNextPage = true;
|
|
1895
|
-
|
|
1896
|
-
while (hasNextPage) {
|
|
1897
|
-
const response = await this.fetchWithRateLimitRetry(() =>
|
|
1898
|
-
fetchGithubGraphql({
|
|
1899
|
-
ghToken: this.ghToken,
|
|
1900
|
-
query,
|
|
1901
|
-
variables: { owner, repo, issueNumber, after },
|
|
1902
|
-
}),
|
|
1903
|
-
);
|
|
1904
|
-
|
|
1905
|
-
if (!response.ok) {
|
|
1906
|
-
await this.throwGitHubError(
|
|
1907
|
-
'Failed to fetch issue timeline from GitHub GraphQL API',
|
|
1908
|
-
response,
|
|
1909
|
-
);
|
|
1910
|
-
}
|
|
1911
|
-
|
|
1912
|
-
const responseData: unknown = await response.json();
|
|
1913
|
-
if (!isIssueTimelineResponse(responseData)) {
|
|
1914
|
-
throw new Error(
|
|
1915
|
-
`Unexpected response shape when fetching issue timeline: ${issueUrl}`,
|
|
1916
|
-
);
|
|
1917
|
-
}
|
|
1918
|
-
|
|
1919
|
-
const issueData = responseData.data?.repository?.issue;
|
|
1920
|
-
if (!issueData) {
|
|
1921
|
-
console.info(
|
|
1922
|
-
`ApiV3CheerioRestIssueRepository: issue not found when fetching timeline, returning empty related PRs. issueUrl: ${issueUrl}`,
|
|
1923
|
-
);
|
|
1924
|
-
return [];
|
|
1925
|
-
}
|
|
1926
|
-
|
|
1927
|
-
for (const item of issueData.timelineItems.nodes) {
|
|
1928
|
-
if (item.__typename !== 'CrossReferencedEvent') continue;
|
|
1929
|
-
if (!item.source || item.source.__typename !== 'PullRequest') continue;
|
|
1930
|
-
if (item.source.state !== 'OPEN') continue;
|
|
1931
|
-
if (
|
|
1932
|
-
!item.willCloseTarget &&
|
|
1933
|
-
!this.prBodyContainsCrossRepoClosingKeyword(
|
|
1934
|
-
item.source.body ?? null,
|
|
1935
|
-
issueUrl,
|
|
1936
|
-
)
|
|
1937
|
-
)
|
|
1938
|
-
continue;
|
|
1939
|
-
|
|
1940
|
-
const pr = item.source;
|
|
1941
|
-
const prUrl = pr.url || '';
|
|
1942
|
-
|
|
1943
|
-
if (!prUrl) continue;
|
|
1944
|
-
|
|
1945
|
-
const { owner: prOwner, repo: prRepo } = this.parseIssueUrl(prUrl);
|
|
1946
|
-
|
|
1947
|
-
let isConflicted = pr.mergeable === 'CONFLICTING';
|
|
1948
|
-
let mergeable: string | null = pr.mergeable ?? null;
|
|
1949
|
-
if (
|
|
1950
|
-
pr.number !== undefined &&
|
|
1951
|
-
(pr.mergeable === undefined ||
|
|
1952
|
-
pr.mergeable === null ||
|
|
1953
|
-
pr.mergeable === 'UNKNOWN')
|
|
1954
|
-
) {
|
|
1955
|
-
let resolved: {
|
|
1956
|
-
mergeable: string | null;
|
|
1957
|
-
mergeStateStatus: string | null;
|
|
1958
|
-
} | null;
|
|
1959
|
-
try {
|
|
1960
|
-
resolved = await this.resolveMergeabilityWithRetry(
|
|
1961
|
-
prOwner,
|
|
1962
|
-
prRepo,
|
|
1963
|
-
pr.number,
|
|
1964
|
-
);
|
|
1965
|
-
} catch (error) {
|
|
1966
|
-
const errorMessage =
|
|
1967
|
-
error instanceof Error ? error.message : String(error);
|
|
1968
|
-
if (errorMessage.includes('NOT_FOUND')) {
|
|
1969
|
-
console.info(
|
|
1970
|
-
`ApiV3CheerioRestIssueRepository: pull request no longer exists, excluding it from related open PRs. prUrl: ${prUrl}`,
|
|
1971
|
-
);
|
|
1972
|
-
} else {
|
|
1973
|
-
console.warn(
|
|
1974
|
-
`ApiV3CheerioRestIssueRepository: resolveMergeabilityWithRetry failed, skipping PR for this cycle. prUrl: ${prUrl} error: ${errorMessage}`,
|
|
1975
|
-
);
|
|
1976
|
-
}
|
|
1977
|
-
continue;
|
|
1978
|
-
}
|
|
1979
|
-
if (resolved !== null) {
|
|
1980
|
-
mergeable = resolved.mergeable;
|
|
1981
|
-
isConflicted =
|
|
1982
|
-
resolved.mergeable === 'CONFLICTING' ||
|
|
1983
|
-
resolved.mergeStateStatus === 'DIRTY';
|
|
1984
|
-
}
|
|
1985
|
-
}
|
|
1986
|
-
|
|
1987
|
-
if (pr.number === undefined) continue;
|
|
1988
|
-
|
|
1989
|
-
let prStatus: RelatedPullRequest;
|
|
1990
|
-
try {
|
|
1991
|
-
const slimPullRequest = await this.fetchSlimPullRequest(
|
|
1992
|
-
prOwner,
|
|
1993
|
-
prRepo,
|
|
1994
|
-
pr.number,
|
|
1995
|
-
);
|
|
1996
|
-
if (!slimPullRequest || slimPullRequest.state !== 'OPEN') {
|
|
1997
|
-
console.info(
|
|
1998
|
-
`ApiV3CheerioRestIssueRepository: pull request is no longer open, excluding it from related open PRs. prUrl: ${prUrl}`,
|
|
1999
|
-
);
|
|
2000
|
-
continue;
|
|
2001
|
-
}
|
|
2002
|
-
const baseRefName =
|
|
2003
|
-
slimPullRequest.baseRefName ?? pr.baseRefName ?? pr.baseRef?.name;
|
|
2004
|
-
prStatus = await this.buildRelatedPullRequestFromSlim(
|
|
2005
|
-
prOwner,
|
|
2006
|
-
prRepo,
|
|
2007
|
-
{
|
|
2008
|
-
...slimPullRequest,
|
|
2009
|
-
url: slimPullRequest.url || prUrl,
|
|
2010
|
-
headRefName: slimPullRequest.headRefName ?? pr.headRefName,
|
|
2011
|
-
baseRefName,
|
|
2012
|
-
},
|
|
2013
|
-
);
|
|
2014
|
-
} catch (error) {
|
|
2015
|
-
const errorMessage =
|
|
2016
|
-
error instanceof Error ? error.message : String(error);
|
|
2017
|
-
console.warn(
|
|
2018
|
-
`ApiV3CheerioRestIssueRepository: fetching pull request status failed, skipping PR for this cycle. prUrl: ${prUrl} error: ${errorMessage}`,
|
|
2019
|
-
);
|
|
2020
|
-
continue;
|
|
2021
|
-
}
|
|
2022
|
-
|
|
2023
|
-
relatedPRsMap.set(prUrl, {
|
|
2024
|
-
...prStatus,
|
|
2025
|
-
isConflicted,
|
|
2026
|
-
mergeable,
|
|
2027
|
-
createdAt: pr.createdAt ? new Date(pr.createdAt) : new Date(0),
|
|
2028
|
-
});
|
|
2029
|
-
}
|
|
2030
|
-
|
|
2031
|
-
hasNextPage = issueData.timelineItems.pageInfo.hasNextPage;
|
|
2032
|
-
after = issueData.timelineItems.pageInfo.endCursor;
|
|
2033
|
-
}
|
|
2034
|
-
|
|
2035
|
-
return Array.from(relatedPRsMap.values());
|
|
2036
|
-
};
|
|
2037
|
-
|
|
2038
|
-
getAllOpened = async (project: Project): Promise<Issue[]> => {
|
|
2039
|
-
const { issues } = await this.getAllIssues(project.id);
|
|
2040
|
-
return issues.filter((issue) => !issue.isClosed);
|
|
2041
|
-
};
|
|
2042
|
-
|
|
2043
|
-
getStoryObjectMap = async (project: Project): Promise<StoryObjectMap> => {
|
|
2044
|
-
const { issues } = await this.getAllIssues(project.id);
|
|
2045
|
-
const storyObjectMap: StoryObjectMap = new Map();
|
|
2046
|
-
const targetStories = project.story?.stories || [];
|
|
2047
|
-
for (const story of targetStories) {
|
|
2048
|
-
const storyIssue = issues.find((issue) =>
|
|
2049
|
-
story.name.startsWith(issue.title),
|
|
2050
|
-
);
|
|
2051
|
-
storyObjectMap.set(story.name, {
|
|
2052
|
-
story,
|
|
2053
|
-
storyIssue: storyIssue || null,
|
|
2054
|
-
issues: [],
|
|
2055
|
-
});
|
|
2056
|
-
for (const issue of issues) {
|
|
2057
|
-
if (issue.story !== story.name) continue;
|
|
2058
|
-
storyObjectMap.get(story.name)?.issues.push(issue);
|
|
2059
|
-
}
|
|
2060
|
-
}
|
|
2061
|
-
return storyObjectMap;
|
|
2062
|
-
};
|
|
2063
|
-
|
|
2064
|
-
getOpenPullRequest = async (
|
|
2065
|
-
prUrl: string,
|
|
2066
|
-
): Promise<RelatedPullRequest | null> => {
|
|
2067
|
-
const parsedUrl = this.parseIssueUrl(prUrl);
|
|
2068
|
-
if (!parsedUrl.isPr) {
|
|
2069
|
-
return null;
|
|
2070
|
-
}
|
|
2071
|
-
const { owner, repo, issueNumber: prNumber } = parsedUrl;
|
|
2072
|
-
|
|
2073
|
-
const slimPullRequest = await this.fetchSlimPullRequest(
|
|
2074
|
-
owner,
|
|
2075
|
-
repo,
|
|
2076
|
-
prNumber,
|
|
2077
|
-
);
|
|
2078
|
-
if (!slimPullRequest || slimPullRequest.state !== 'OPEN') {
|
|
2079
|
-
return null;
|
|
2080
|
-
}
|
|
2081
|
-
|
|
2082
|
-
return this.buildRelatedPullRequestFromSlim(owner, repo, slimPullRequest);
|
|
2083
|
-
};
|
|
2084
|
-
|
|
2085
|
-
private fetchRestPullRequestCiStatus = async (
|
|
2086
|
-
owner: string,
|
|
2087
|
-
repo: string,
|
|
2088
|
-
prNumber: number,
|
|
2089
|
-
prUrl: string,
|
|
2090
|
-
): Promise<RestPullRequestCiStatusResponse | null> => {
|
|
2091
|
-
const maxAttempts = 3;
|
|
2092
|
-
const retryDelayMilliseconds = 1000;
|
|
2093
|
-
let lastPullRequest: RestPullRequestCiStatusResponse | null = null;
|
|
2094
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
2095
|
-
if (attempt > 0) {
|
|
2096
|
-
await this.sleep(retryDelayMilliseconds);
|
|
2097
|
-
}
|
|
2098
|
-
const response = await this.fetchWithRateLimitRetry(() =>
|
|
2099
|
-
fetch(
|
|
2100
|
-
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}`,
|
|
2101
|
-
{
|
|
2102
|
-
method: 'GET',
|
|
2103
|
-
headers: {
|
|
2104
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
2105
|
-
Accept: 'application/vnd.github+json',
|
|
2106
|
-
},
|
|
2107
|
-
},
|
|
2108
|
-
),
|
|
2109
|
-
);
|
|
2110
|
-
if (response.status === 404) {
|
|
2111
|
-
return null;
|
|
2112
|
-
}
|
|
2113
|
-
if (!response.ok) {
|
|
2114
|
-
await this.throwGitHubError(
|
|
2115
|
-
`Failed to fetch pull request status for ${prUrl}`,
|
|
2116
|
-
response,
|
|
2117
|
-
);
|
|
2118
|
-
}
|
|
2119
|
-
const body: unknown = await response.json();
|
|
2120
|
-
if (!isRestPullRequestCiStatusResponse(body)) {
|
|
2121
|
-
throw new Error(
|
|
2122
|
-
`Unexpected response shape when fetching pull request status for ${prUrl}`,
|
|
2123
|
-
);
|
|
2124
|
-
}
|
|
2125
|
-
lastPullRequest = body;
|
|
2126
|
-
if (body.state !== 'open' || body.mergeable !== null) {
|
|
2127
|
-
return body;
|
|
2128
|
-
}
|
|
2129
|
-
}
|
|
2130
|
-
return lastPullRequest;
|
|
2131
|
-
};
|
|
2132
|
-
|
|
2133
|
-
getOpenPullRequestCiStatus = async (
|
|
2134
|
-
prUrl: string,
|
|
2135
|
-
): Promise<OpenPullRequestCiStatus | null> => {
|
|
2136
|
-
const parsedUrl = this.parseIssueUrl(prUrl);
|
|
2137
|
-
if (!parsedUrl.isPr) {
|
|
2138
|
-
return null;
|
|
2139
|
-
}
|
|
2140
|
-
const { owner, repo, issueNumber: prNumber } = parsedUrl;
|
|
2141
|
-
|
|
2142
|
-
const pullRequest = await this.fetchRestPullRequestCiStatus(
|
|
2143
|
-
owner,
|
|
2144
|
-
repo,
|
|
2145
|
-
prNumber,
|
|
2146
|
-
prUrl,
|
|
2147
|
-
);
|
|
2148
|
-
if (!pullRequest || pullRequest.state !== 'open') {
|
|
2149
|
-
return null;
|
|
2150
|
-
}
|
|
2151
|
-
|
|
2152
|
-
const requiredCheckNames = await this.getRequiredCheckNames(
|
|
2153
|
-
owner,
|
|
2154
|
-
repo,
|
|
2155
|
-
pullRequest.base.ref,
|
|
2156
|
-
);
|
|
2157
|
-
const ciContexts = await this.getCommitCiContexts(
|
|
2158
|
-
owner,
|
|
2159
|
-
repo,
|
|
2160
|
-
pullRequest.head.sha,
|
|
2161
|
-
);
|
|
2162
|
-
const status = this.computePrStatus(
|
|
2163
|
-
pullRequest.html_url,
|
|
2164
|
-
pullRequest.head.ref,
|
|
2165
|
-
{
|
|
2166
|
-
isDraft: pullRequest.draft,
|
|
2167
|
-
mergeable: graphqlMergeableFromRestMergeable(pullRequest.mergeable),
|
|
2168
|
-
requiredCheckNames,
|
|
2169
|
-
ciContexts,
|
|
2170
|
-
reviewThreads: [],
|
|
2171
|
-
},
|
|
2172
|
-
);
|
|
2173
|
-
return {
|
|
2174
|
-
url: status.url,
|
|
2175
|
-
isConflicted: status.isConflicted,
|
|
2176
|
-
mergeable: status.mergeable,
|
|
2177
|
-
isPassedAllCiJob: status.isPassedAllCiJob,
|
|
2178
|
-
isCiStateSuccess: status.isCiStateSuccess,
|
|
2179
|
-
isBranchOutOfDate: status.isBranchOutOfDate,
|
|
2180
|
-
missingRequiredCheckNames: status.missingRequiredCheckNames,
|
|
2181
|
-
};
|
|
2182
|
-
};
|
|
2183
|
-
|
|
2184
|
-
// Resolves many pull requests with one GraphQL query per hundred instead of
|
|
2185
|
-
// one query per pull request. A URL this cannot settle is left out of the
|
|
2186
|
-
// returned map rather than mapped to null, so the caller falls back to
|
|
2187
|
-
// getOpenPullRequest for it and an unknown state is never mistaken for an
|
|
2188
|
-
// absent pull request.
|
|
2189
|
-
getOpenPullRequests = async (
|
|
2190
|
-
prUrls: string[],
|
|
2191
|
-
): Promise<Map<string, RelatedPullRequest | null>> => {
|
|
2192
|
-
const resolved = new Map<string, RelatedPullRequest | null>();
|
|
2193
|
-
const parsedByUrl = new Map<
|
|
2194
|
-
string,
|
|
2195
|
-
{ owner: string; repo: string; prNumber: number }
|
|
2196
|
-
>();
|
|
2197
|
-
for (const prUrl of Array.from(new Set(prUrls))) {
|
|
2198
|
-
const parsedUrl = this.parseIssueUrl(prUrl);
|
|
2199
|
-
if (!parsedUrl.isPr) {
|
|
2200
|
-
resolved.set(prUrl, null);
|
|
2201
|
-
continue;
|
|
2202
|
-
}
|
|
2203
|
-
parsedByUrl.set(prUrl, {
|
|
2204
|
-
owner: parsedUrl.owner,
|
|
2205
|
-
repo: parsedUrl.repo,
|
|
2206
|
-
prNumber: parsedUrl.issueNumber,
|
|
2207
|
-
});
|
|
2208
|
-
}
|
|
2209
|
-
|
|
2210
|
-
const urlsToFetch = Array.from(parsedByUrl.keys());
|
|
2211
|
-
for (
|
|
2212
|
-
let start = 0;
|
|
2213
|
-
start < urlsToFetch.length;
|
|
2214
|
-
start += SLIM_PULL_REQUEST_BATCH_SIZE
|
|
2215
|
-
) {
|
|
2216
|
-
const batchUrls = urlsToFetch.slice(
|
|
2217
|
-
start,
|
|
2218
|
-
start + SLIM_PULL_REQUEST_BATCH_SIZE,
|
|
2219
|
-
);
|
|
2220
|
-
let slimByUrl: Map<string, SlimPullRequest | null>;
|
|
2221
|
-
try {
|
|
2222
|
-
slimByUrl = await this.fetchSlimPullRequestsInOneQuery(
|
|
2223
|
-
batchUrls.map((prUrl) => ({
|
|
2224
|
-
prUrl,
|
|
2225
|
-
...this.requireParsedPullRequest(parsedByUrl, prUrl),
|
|
2226
|
-
})),
|
|
2227
|
-
);
|
|
2228
|
-
} catch (error) {
|
|
2229
|
-
console.warn(
|
|
2230
|
-
`ApiV3CheerioRestIssueRepository: batched pull request status query failed, leaving ${batchUrls.length} pull request(s) to per-request resolution. error: ${error instanceof Error ? error.message : String(error)}`,
|
|
2231
|
-
);
|
|
2232
|
-
continue;
|
|
2233
|
-
}
|
|
2234
|
-
for (const prUrl of batchUrls) {
|
|
2235
|
-
if (!slimByUrl.has(prUrl)) {
|
|
2236
|
-
continue;
|
|
2237
|
-
}
|
|
2238
|
-
const slimPullRequest = slimByUrl.get(prUrl) ?? null;
|
|
2239
|
-
if (!slimPullRequest || slimPullRequest.state !== 'OPEN') {
|
|
2240
|
-
resolved.set(prUrl, null);
|
|
2241
|
-
continue;
|
|
2242
|
-
}
|
|
2243
|
-
const parsed = this.requireParsedPullRequest(parsedByUrl, prUrl);
|
|
2244
|
-
try {
|
|
2245
|
-
resolved.set(
|
|
2246
|
-
prUrl,
|
|
2247
|
-
await this.buildRelatedPullRequestFromSlim(
|
|
2248
|
-
parsed.owner,
|
|
2249
|
-
parsed.repo,
|
|
2250
|
-
slimPullRequest,
|
|
2251
|
-
),
|
|
2252
|
-
);
|
|
2253
|
-
} catch (error) {
|
|
2254
|
-
console.warn(
|
|
2255
|
-
`ApiV3CheerioRestIssueRepository: building the pull request status failed, leaving it to per-request resolution. prUrl: ${prUrl} error: ${error instanceof Error ? error.message : String(error)}`,
|
|
2256
|
-
);
|
|
2257
|
-
}
|
|
2258
|
-
}
|
|
2259
|
-
}
|
|
2260
|
-
return resolved;
|
|
2261
|
-
};
|
|
2262
|
-
|
|
2263
|
-
private requireParsedPullRequest = (
|
|
2264
|
-
parsedByUrl: Map<string, { owner: string; repo: string; prNumber: number }>,
|
|
2265
|
-
prUrl: string,
|
|
2266
|
-
): { owner: string; repo: string; prNumber: number } => {
|
|
2267
|
-
const parsed = parsedByUrl.get(prUrl);
|
|
2268
|
-
if (!parsed) {
|
|
2269
|
-
throw new Error(`Pull request URL was not parsed: ${prUrl}`);
|
|
2270
|
-
}
|
|
2271
|
-
return parsed;
|
|
2272
|
-
};
|
|
2273
|
-
|
|
2274
|
-
// A pull request whose review threads do not fit the first page is omitted
|
|
2275
|
-
// from the returned map, so the caller resolves it through the paginating
|
|
2276
|
-
// single-pull-request path and the resolved-thread state stays complete.
|
|
2277
|
-
private fetchSlimPullRequestsInOneQuery = async (
|
|
2278
|
-
references: {
|
|
2279
|
-
prUrl: string;
|
|
2280
|
-
owner: string;
|
|
2281
|
-
repo: string;
|
|
2282
|
-
prNumber: number;
|
|
2283
|
-
}[],
|
|
2284
|
-
): Promise<Map<string, SlimPullRequest | null>> => {
|
|
2285
|
-
const aliasOf = (index: number): string => `pullRequest${index}`;
|
|
2286
|
-
const variableDeclarations = references
|
|
2287
|
-
.map(
|
|
2288
|
-
(_, index) =>
|
|
2289
|
-
`$owner${index}: String!, $repo${index}: String!, $prNumber${index}: Int!`,
|
|
2290
|
-
)
|
|
2291
|
-
.join(', ');
|
|
2292
|
-
const selections = references
|
|
2293
|
-
.map(
|
|
2294
|
-
(
|
|
2295
|
-
_,
|
|
2296
|
-
index,
|
|
2297
|
-
) => ` ${aliasOf(index)}: repository(owner: $owner${index}, name: $repo${index}) {
|
|
2298
|
-
pullRequest(number: $prNumber${index}) {
|
|
2299
|
-
url
|
|
2300
|
-
state
|
|
2301
|
-
isDraft
|
|
2302
|
-
headRefName
|
|
2303
|
-
baseRefName
|
|
2304
|
-
mergeable
|
|
2305
|
-
headRefOid
|
|
2306
|
-
reviewThreads(first: ${SLIM_PULL_REQUEST_REVIEW_THREADS_PAGE_SIZE}) {
|
|
2307
|
-
pageInfo {
|
|
2308
|
-
endCursor
|
|
2309
|
-
hasNextPage
|
|
2310
|
-
}
|
|
2311
|
-
nodes {
|
|
2312
|
-
isResolved
|
|
2313
|
-
}
|
|
2314
|
-
}
|
|
2315
|
-
}
|
|
2316
|
-
}`,
|
|
2317
|
-
)
|
|
2318
|
-
.join('\n');
|
|
2319
|
-
const query = `query PullRequestSlimStatusBatch(${variableDeclarations}) {\n${selections}\n}`;
|
|
2320
|
-
const variables: Record<string, string | number> = {};
|
|
2321
|
-
references.forEach((reference, index) => {
|
|
2322
|
-
variables[`owner${index}`] = reference.owner;
|
|
2323
|
-
variables[`repo${index}`] = reference.repo;
|
|
2324
|
-
variables[`prNumber${index}`] = reference.prNumber;
|
|
2325
|
-
});
|
|
2326
|
-
|
|
2327
|
-
const response = await this.fetchWithRateLimitRetry(() =>
|
|
2328
|
-
fetchGithubGraphql({ ghToken: this.ghToken, query, variables }),
|
|
2329
|
-
);
|
|
2330
|
-
if (!response.ok) {
|
|
2331
|
-
throw new Error(
|
|
2332
|
-
`Failed to fetch pull requests from GitHub GraphQL API: HTTP ${response.status}`,
|
|
2333
|
-
);
|
|
2334
|
-
}
|
|
2335
|
-
const responseData: unknown = await response.json();
|
|
2336
|
-
if (!isSlimPullRequestBatchResponse(responseData)) {
|
|
2337
|
-
throw new Error('Unexpected response shape when fetching pull requests');
|
|
2338
|
-
}
|
|
2339
|
-
const errors = responseData.errors || [];
|
|
2340
|
-
const notFoundAliases = new Set(
|
|
2341
|
-
errors
|
|
2342
|
-
.filter((error) => error.type === 'NOT_FOUND')
|
|
2343
|
-
.map((error) => String(error.path?.[0] ?? '')),
|
|
2344
|
-
);
|
|
2345
|
-
const otherErrorAliases = new Set(
|
|
2346
|
-
errors
|
|
2347
|
-
.filter((error) => error.type !== 'NOT_FOUND')
|
|
2348
|
-
.map((error) => String(error.path?.[0] ?? '')),
|
|
2349
|
-
);
|
|
2350
|
-
if (!responseData.data) {
|
|
2351
|
-
throw new Error(`GraphQL errors: ${JSON.stringify(errors)}`);
|
|
2352
|
-
}
|
|
2353
|
-
const unattributedError = errors.some(
|
|
2354
|
-
(error) => error.type !== 'NOT_FOUND' && !error.path,
|
|
2355
|
-
);
|
|
2356
|
-
if (unattributedError) {
|
|
2357
|
-
throw new Error(`GraphQL errors: ${JSON.stringify(errors)}`);
|
|
2358
|
-
}
|
|
2359
|
-
|
|
2360
|
-
const slimByUrl = new Map<string, SlimPullRequest | null>();
|
|
2361
|
-
references.forEach((reference, index) => {
|
|
2362
|
-
const alias = aliasOf(index);
|
|
2363
|
-
if (otherErrorAliases.has(alias)) {
|
|
2364
|
-
return;
|
|
2365
|
-
}
|
|
2366
|
-
const node = responseData.data?.[alias]?.pullRequest ?? null;
|
|
2367
|
-
if (!node) {
|
|
2368
|
-
if (notFoundAliases.has(alias) || !errors.length) {
|
|
2369
|
-
slimByUrl.set(reference.prUrl, null);
|
|
2370
|
-
}
|
|
2371
|
-
return;
|
|
2372
|
-
}
|
|
2373
|
-
if (node.reviewThreads?.pageInfo.hasNextPage === true) {
|
|
2374
|
-
return;
|
|
2375
|
-
}
|
|
2376
|
-
slimByUrl.set(reference.prUrl, {
|
|
2377
|
-
url: node.url,
|
|
2378
|
-
state: node.state,
|
|
2379
|
-
isDraft: node.isDraft,
|
|
2380
|
-
headRefName: node.headRefName,
|
|
2381
|
-
baseRefName: node.baseRefName,
|
|
2382
|
-
mergeable: node.mergeable,
|
|
2383
|
-
headRefOid: node.headRefOid,
|
|
2384
|
-
reviewThreads: (node.reviewThreads?.nodes || []).map((thread) => ({
|
|
2385
|
-
isResolved: thread.isResolved,
|
|
2386
|
-
})),
|
|
2387
|
-
});
|
|
2388
|
-
});
|
|
2389
|
-
return slimByUrl;
|
|
2390
|
-
};
|
|
2391
|
-
|
|
2392
|
-
closePullRequest = async (prUrl: string): Promise<void> => {
|
|
2393
|
-
const { owner, repo, issueNumber: prNumber } = this.parseIssueUrl(prUrl);
|
|
2394
|
-
const response = await this.fetchWithRateLimitRetry(() =>
|
|
2395
|
-
fetch(
|
|
2396
|
-
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}`,
|
|
2397
|
-
{
|
|
2398
|
-
method: 'PATCH',
|
|
2399
|
-
headers: {
|
|
2400
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
2401
|
-
'Content-Type': 'application/json',
|
|
2402
|
-
},
|
|
2403
|
-
body: JSON.stringify({ state: 'closed' }),
|
|
2404
|
-
},
|
|
2405
|
-
),
|
|
2406
|
-
);
|
|
2407
|
-
if (!response.ok) {
|
|
2408
|
-
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
2409
|
-
throw new Error(`Failed to close PR ${prUrl}: ${reason}`);
|
|
2410
|
-
}
|
|
2411
|
-
};
|
|
2412
|
-
|
|
2413
|
-
closeIssueByUrl = async (
|
|
2414
|
-
issueUrl: string,
|
|
2415
|
-
stateReason: 'completed' | 'not_planned',
|
|
2416
|
-
): Promise<void> => {
|
|
2417
|
-
const { owner, repo, issueNumber } = this.parseIssueUrl(issueUrl);
|
|
2418
|
-
const ownerSegment = encodeURIComponent(owner);
|
|
2419
|
-
const repoSegment = encodeURIComponent(repo);
|
|
2420
|
-
const response = await this.fetchWithRateLimitRetry(() =>
|
|
2421
|
-
fetch(
|
|
2422
|
-
`https://api.github.com/repos/${ownerSegment}/${repoSegment}/issues/${issueNumber}`,
|
|
2423
|
-
{
|
|
2424
|
-
method: 'PATCH',
|
|
2425
|
-
headers: {
|
|
2426
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
2427
|
-
'Content-Type': 'application/json',
|
|
2428
|
-
},
|
|
2429
|
-
body: JSON.stringify({ state: 'closed', state_reason: stateReason }),
|
|
2430
|
-
},
|
|
2431
|
-
),
|
|
2432
|
-
);
|
|
2433
|
-
if (!response.ok) {
|
|
2434
|
-
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
2435
|
-
throw new Error(`Failed to close issue ${issueUrl}: ${reason}`);
|
|
2436
|
-
}
|
|
2437
|
-
};
|
|
2438
|
-
|
|
2439
|
-
getPullRequestChangedFilePaths = async (prUrl: string): Promise<string[]> => {
|
|
2440
|
-
const { owner, repo, issueNumber: prNumber } = this.parseIssueUrl(prUrl);
|
|
2441
|
-
const perPage = 100;
|
|
2442
|
-
const collectedPaths: string[] = [];
|
|
2443
|
-
let page = 1;
|
|
2444
|
-
let hasMore = true;
|
|
2445
|
-
while (hasMore) {
|
|
2446
|
-
const response = await this.fetchWithRateLimitRetry(() =>
|
|
2447
|
-
fetch(
|
|
2448
|
-
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/files?per_page=${perPage}&page=${page}`,
|
|
2449
|
-
{
|
|
2450
|
-
method: 'GET',
|
|
2451
|
-
headers: {
|
|
2452
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
2453
|
-
Accept: 'application/vnd.github+json',
|
|
2454
|
-
},
|
|
2455
|
-
},
|
|
2456
|
-
),
|
|
2457
|
-
);
|
|
2458
|
-
if (!response.ok) {
|
|
2459
|
-
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
2460
|
-
throw new Error(
|
|
2461
|
-
`Failed to fetch changed files for PR ${prUrl}: ${reason}`,
|
|
2462
|
-
);
|
|
2463
|
-
}
|
|
2464
|
-
const body: unknown = await response.json();
|
|
2465
|
-
if (!isPullRequestFilesResponse(body)) {
|
|
2466
|
-
throw new Error(
|
|
2467
|
-
`Unexpected response shape when fetching changed files for PR ${prUrl}`,
|
|
2468
|
-
);
|
|
2469
|
-
}
|
|
2470
|
-
for (const file of body) {
|
|
2471
|
-
collectedPaths.push(file.filename);
|
|
2472
|
-
}
|
|
2473
|
-
if (body.length < perPage) {
|
|
2474
|
-
hasMore = false;
|
|
2475
|
-
} else {
|
|
2476
|
-
page += 1;
|
|
2477
|
-
}
|
|
2478
|
-
}
|
|
2479
|
-
return collectedPaths;
|
|
2480
|
-
};
|
|
2481
|
-
|
|
2482
|
-
getAuthenticatedUserLogin = async (): Promise<string> => {
|
|
2483
|
-
const response = await this.fetchWithRateLimitRetry(() =>
|
|
2484
|
-
fetch('https://api.github.com/user', {
|
|
2485
|
-
method: 'GET',
|
|
2486
|
-
headers: {
|
|
2487
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
2488
|
-
Accept: 'application/vnd.github+json',
|
|
2489
|
-
},
|
|
2490
|
-
}),
|
|
2491
|
-
);
|
|
2492
|
-
if (!response.ok) {
|
|
2493
|
-
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
2494
|
-
throw new Error(`Failed to fetch authenticated user: ${reason}`);
|
|
2495
|
-
}
|
|
2496
|
-
const body: unknown = await response.json();
|
|
2497
|
-
if (!isAuthenticatedUserResponse(body)) {
|
|
2498
|
-
throw new Error(
|
|
2499
|
-
'Unexpected response shape when fetching authenticated user',
|
|
2500
|
-
);
|
|
2501
|
-
}
|
|
2502
|
-
return body.login;
|
|
2503
|
-
};
|
|
2504
|
-
|
|
2505
|
-
approvePullRequest = async (prUrl: string): Promise<void> => {
|
|
2506
|
-
const { owner, repo, issueNumber: prNumber } = this.parseIssueUrl(prUrl);
|
|
2507
|
-
const response = await this.fetchWithRateLimitRetry(() =>
|
|
2508
|
-
fetch(
|
|
2509
|
-
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/reviews`,
|
|
2510
|
-
{
|
|
2511
|
-
method: 'POST',
|
|
2512
|
-
headers: {
|
|
2513
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
2514
|
-
'Content-Type': 'application/json',
|
|
2515
|
-
Accept: 'application/vnd.github+json',
|
|
2516
|
-
},
|
|
2517
|
-
body: JSON.stringify({ event: 'APPROVE' }),
|
|
2518
|
-
},
|
|
2519
|
-
),
|
|
2520
|
-
);
|
|
2521
|
-
if (!response.ok) {
|
|
2522
|
-
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
2523
|
-
throw new Error(`Failed to approve PR ${prUrl}: ${reason}`);
|
|
2524
|
-
}
|
|
2525
|
-
};
|
|
2526
|
-
|
|
2527
|
-
mergePullRequest = async (prUrl: string): Promise<void> => {
|
|
2528
|
-
const { owner, repo, issueNumber: prNumber } = this.parseIssueUrl(prUrl);
|
|
2529
|
-
const mergeUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/merge`;
|
|
2530
|
-
const headers = {
|
|
2531
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
2532
|
-
'Content-Type': 'application/json',
|
|
2533
|
-
Accept: 'application/vnd.github+json',
|
|
2534
|
-
};
|
|
2535
|
-
const mergeWith = (mergeMethod?: 'squash' | 'rebase') =>
|
|
2536
|
-
this.fetchWithRateLimitRetry(() =>
|
|
2537
|
-
fetch(mergeUrl, {
|
|
2538
|
-
method: 'PUT',
|
|
2539
|
-
headers,
|
|
2540
|
-
body: JSON.stringify(
|
|
2541
|
-
mergeMethod ? { merge_method: mergeMethod } : {},
|
|
2542
|
-
),
|
|
2543
|
-
}),
|
|
2544
|
-
);
|
|
2545
|
-
const response = await mergeWith();
|
|
2546
|
-
if (response.ok) {
|
|
2547
|
-
return;
|
|
2548
|
-
}
|
|
2549
|
-
if (response.status === 405) {
|
|
2550
|
-
const fallbackMethod = await this.resolveAllowedMergeMethod(owner, repo);
|
|
2551
|
-
if (fallbackMethod !== null) {
|
|
2552
|
-
const retryResponse = await mergeWith(fallbackMethod);
|
|
2553
|
-
if (retryResponse.ok) {
|
|
2554
|
-
return;
|
|
2555
|
-
}
|
|
2556
|
-
const retryReason =
|
|
2557
|
-
await this.formatGitHubErrorWithStatus(retryResponse);
|
|
2558
|
-
throw new Error(`Failed to merge PR ${prUrl}: ${retryReason}`);
|
|
2559
|
-
}
|
|
2560
|
-
}
|
|
2561
|
-
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
2562
|
-
throw new Error(`Failed to merge PR ${prUrl}: ${reason}`);
|
|
2563
|
-
};
|
|
2564
|
-
|
|
2565
|
-
private resolveAllowedMergeMethod = async (
|
|
2566
|
-
owner: string,
|
|
2567
|
-
repo: string,
|
|
2568
|
-
): Promise<'squash' | 'rebase' | null> => {
|
|
2569
|
-
const response = await this.fetchWithRateLimitRetry(() =>
|
|
2570
|
-
fetch(
|
|
2571
|
-
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`,
|
|
2572
|
-
{
|
|
2573
|
-
headers: {
|
|
2574
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
2575
|
-
Accept: 'application/vnd.github+json',
|
|
2576
|
-
},
|
|
2577
|
-
},
|
|
2578
|
-
),
|
|
2579
|
-
);
|
|
2580
|
-
if (!response.ok) {
|
|
2581
|
-
return null;
|
|
2582
|
-
}
|
|
2583
|
-
const data: unknown = await response.json();
|
|
2584
|
-
if (!isRepoMergeSettings(data)) {
|
|
2585
|
-
return null;
|
|
2586
|
-
}
|
|
2587
|
-
if (data.allow_squash_merge === true) {
|
|
2588
|
-
return 'squash';
|
|
2589
|
-
}
|
|
2590
|
-
if (data.allow_rebase_merge === true) {
|
|
2591
|
-
return 'rebase';
|
|
2592
|
-
}
|
|
2593
|
-
return null;
|
|
2594
|
-
};
|
|
2595
|
-
|
|
2596
|
-
requestChangesWithInlineComment = async (
|
|
2597
|
-
prUrl: string,
|
|
2598
|
-
changedFilePath: string | null,
|
|
2599
|
-
commentBody: string,
|
|
2600
|
-
inlineCommentLocation: PullRequestReviewInlineLocation | null = null,
|
|
2601
|
-
): Promise<void> => {
|
|
2602
|
-
const { owner, repo, issueNumber: prNumber } = this.parseIssueUrl(prUrl);
|
|
2603
|
-
if (changedFilePath === null) {
|
|
2604
|
-
await this.createCommentByUrl(prUrl, commentBody);
|
|
2605
|
-
return;
|
|
2606
|
-
}
|
|
2607
|
-
const inlineComment =
|
|
2608
|
-
inlineCommentLocation === null
|
|
2609
|
-
? { path: changedFilePath, position: 1, body: commentBody }
|
|
2610
|
-
: {
|
|
2611
|
-
path: changedFilePath,
|
|
2612
|
-
line: inlineCommentLocation.line,
|
|
2613
|
-
side: inlineCommentLocation.side,
|
|
2614
|
-
body: commentBody,
|
|
2615
|
-
};
|
|
2616
|
-
const reviewBody: Record<string, unknown> = {
|
|
2617
|
-
event: 'REQUEST_CHANGES',
|
|
2618
|
-
body: commentBody,
|
|
2619
|
-
comments: [inlineComment],
|
|
2620
|
-
};
|
|
2621
|
-
const response = await this.fetchWithRateLimitRetry(() =>
|
|
2622
|
-
fetch(
|
|
2623
|
-
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/reviews`,
|
|
2624
|
-
{
|
|
2625
|
-
method: 'POST',
|
|
2626
|
-
headers: {
|
|
2627
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
2628
|
-
'Content-Type': 'application/json',
|
|
2629
|
-
Accept: 'application/vnd.github+json',
|
|
2630
|
-
},
|
|
2631
|
-
body: JSON.stringify(reviewBody),
|
|
2632
|
-
},
|
|
2633
|
-
),
|
|
2634
|
-
);
|
|
2635
|
-
if (!response.ok) {
|
|
2636
|
-
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
2637
|
-
if (
|
|
2638
|
-
response.status === 422 &&
|
|
2639
|
-
reason.includes(SELF_AUTHORED_REVIEW_REFUSAL)
|
|
2640
|
-
) {
|
|
2641
|
-
await this.recordRequestedChangesWithoutReview(
|
|
2642
|
-
prUrl,
|
|
2643
|
-
changedFilePath,
|
|
2644
|
-
commentBody,
|
|
2645
|
-
inlineCommentLocation,
|
|
2646
|
-
);
|
|
2647
|
-
return;
|
|
2648
|
-
}
|
|
2649
|
-
throw new Error(`Failed to request changes on PR ${prUrl}: ${reason}`);
|
|
2650
|
-
}
|
|
2651
|
-
};
|
|
2652
|
-
|
|
2653
|
-
private recordRequestedChangesWithoutReview = async (
|
|
2654
|
-
prUrl: string,
|
|
2655
|
-
changedFilePath: string,
|
|
2656
|
-
commentBody: string,
|
|
2657
|
-
inlineCommentLocation: PullRequestReviewInlineLocation | null,
|
|
2658
|
-
): Promise<void> => {
|
|
2659
|
-
if (inlineCommentLocation === null) {
|
|
2660
|
-
await this.createCommentByUrl(prUrl, commentBody);
|
|
2661
|
-
return;
|
|
2662
|
-
}
|
|
2663
|
-
await this.createPullRequestReviewComment(
|
|
2664
|
-
prUrl,
|
|
2665
|
-
changedFilePath,
|
|
2666
|
-
inlineCommentLocation.line,
|
|
2667
|
-
inlineCommentLocation.side,
|
|
2668
|
-
commentBody,
|
|
2669
|
-
);
|
|
2670
|
-
};
|
|
2671
|
-
|
|
2672
|
-
private fetchPullRequestHeadSha = async (
|
|
2673
|
-
owner: string,
|
|
2674
|
-
repo: string,
|
|
2675
|
-
prNumber: number,
|
|
2676
|
-
prUrl: string,
|
|
2677
|
-
): Promise<string> => {
|
|
2678
|
-
const ownerSegment = encodeURIComponent(owner);
|
|
2679
|
-
const repoSegment = encodeURIComponent(repo);
|
|
2680
|
-
const response = await this.fetchWithRateLimitRetry(() =>
|
|
2681
|
-
fetch(
|
|
2682
|
-
`https://api.github.com/repos/${ownerSegment}/${repoSegment}/pulls/${prNumber}`,
|
|
2683
|
-
{
|
|
2684
|
-
method: 'GET',
|
|
2685
|
-
headers: {
|
|
2686
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
2687
|
-
Accept: 'application/vnd.github+json',
|
|
2688
|
-
},
|
|
2689
|
-
},
|
|
2690
|
-
),
|
|
2691
|
-
);
|
|
2692
|
-
if (!response.ok) {
|
|
2693
|
-
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
2694
|
-
throw new Error(`Failed to fetch head commit for PR ${prUrl}: ${reason}`);
|
|
2695
|
-
}
|
|
2696
|
-
const body: unknown = await response.json();
|
|
2697
|
-
if (
|
|
2698
|
-
!isRecord(body) ||
|
|
2699
|
-
!isRecord(body.head) ||
|
|
2700
|
-
typeof body.head.sha !== 'string'
|
|
2701
|
-
) {
|
|
2702
|
-
throw new Error(
|
|
2703
|
-
`Unexpected response shape when fetching head commit for PR ${prUrl}`,
|
|
2704
|
-
);
|
|
2705
|
-
}
|
|
2706
|
-
return body.head.sha;
|
|
2707
|
-
};
|
|
2708
|
-
|
|
2709
|
-
createPullRequestReviewComment = async (
|
|
2710
|
-
prUrl: string,
|
|
2711
|
-
path: string,
|
|
2712
|
-
line: number,
|
|
2713
|
-
side: PullRequestReviewCommentSide,
|
|
2714
|
-
commentBody: string,
|
|
2715
|
-
): Promise<void> => {
|
|
2716
|
-
const { owner, repo, issueNumber: prNumber } = this.parseIssueUrl(prUrl);
|
|
2717
|
-
const commitId = await this.fetchPullRequestHeadSha(
|
|
2718
|
-
owner,
|
|
2719
|
-
repo,
|
|
2720
|
-
prNumber,
|
|
2721
|
-
prUrl,
|
|
2722
|
-
);
|
|
2723
|
-
const ownerSegment = encodeURIComponent(owner);
|
|
2724
|
-
const repoSegment = encodeURIComponent(repo);
|
|
2725
|
-
const response = await this.fetchWithRateLimitRetry(() =>
|
|
2726
|
-
fetch(
|
|
2727
|
-
`https://api.github.com/repos/${ownerSegment}/${repoSegment}/pulls/${prNumber}/comments`,
|
|
2728
|
-
{
|
|
2729
|
-
method: 'POST',
|
|
2730
|
-
headers: {
|
|
2731
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
2732
|
-
'Content-Type': 'application/json',
|
|
2733
|
-
Accept: 'application/vnd.github+json',
|
|
2734
|
-
},
|
|
2735
|
-
body: JSON.stringify({
|
|
2736
|
-
body: commentBody,
|
|
2737
|
-
commit_id: commitId,
|
|
2738
|
-
path,
|
|
2739
|
-
line,
|
|
2740
|
-
side,
|
|
2741
|
-
}),
|
|
2742
|
-
},
|
|
2743
|
-
),
|
|
2744
|
-
);
|
|
2745
|
-
if (!response.ok) {
|
|
2746
|
-
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
2747
|
-
throw new Error(
|
|
2748
|
-
`Failed to create review comment on PR ${prUrl}: ${reason}`,
|
|
2749
|
-
);
|
|
2750
|
-
}
|
|
2751
|
-
};
|
|
2752
|
-
|
|
2753
|
-
private readGitHubErrorReason = async (
|
|
2754
|
-
response: Response,
|
|
2755
|
-
): Promise<string | null> => {
|
|
2756
|
-
let parsed: unknown;
|
|
2757
|
-
try {
|
|
2758
|
-
parsed = await response.json();
|
|
2759
|
-
} catch {
|
|
2760
|
-
return null;
|
|
2761
|
-
}
|
|
2762
|
-
if (!isRecord(parsed) || typeof parsed.message !== 'string') {
|
|
2763
|
-
return null;
|
|
2764
|
-
}
|
|
2765
|
-
if (Array.isArray(parsed.errors) && parsed.errors.length > 0) {
|
|
2766
|
-
const details = parsed.errors
|
|
2767
|
-
.map((error) => {
|
|
2768
|
-
if (typeof error === 'string') {
|
|
2769
|
-
return error;
|
|
2770
|
-
}
|
|
2771
|
-
if (isRecord(error) && typeof error.message === 'string') {
|
|
2772
|
-
return error.message;
|
|
2773
|
-
}
|
|
2774
|
-
return '';
|
|
2775
|
-
})
|
|
2776
|
-
.filter((detail) => detail.length > 0)
|
|
2777
|
-
.join('; ');
|
|
2778
|
-
if (details.length > 0) {
|
|
2779
|
-
return `${parsed.message}: ${details}`;
|
|
2780
|
-
}
|
|
2781
|
-
}
|
|
2782
|
-
return parsed.message;
|
|
2783
|
-
};
|
|
2784
|
-
|
|
2785
|
-
private formatGitHubErrorWithStatus = async (
|
|
2786
|
-
response: Response,
|
|
2787
|
-
): Promise<string> => {
|
|
2788
|
-
const status = `HTTP ${response.status}`;
|
|
2789
|
-
const bodyText = await response.clone().text();
|
|
2790
|
-
const reason = await this.readGitHubErrorReason(response);
|
|
2791
|
-
if (hasRateLimitSignals(response.status, response.headers, bodyText)) {
|
|
2792
|
-
const resetIso = computeRateLimitResetIso(response.headers);
|
|
2793
|
-
const resetSuffix = resetIso === null ? '' : ` (resets at ${resetIso})`;
|
|
2794
|
-
return `${status} GitHub rate limit exceeded, please retry shortly${resetSuffix}`;
|
|
2795
|
-
}
|
|
2796
|
-
if (response.status === 403) {
|
|
2797
|
-
const permissionSuffix = reason === null ? '' : ` ${reason}`;
|
|
2798
|
-
return `${status} permission denied, the token cannot perform this operation${permissionSuffix}`;
|
|
2799
|
-
}
|
|
2800
|
-
if (reason === null) {
|
|
2801
|
-
return status;
|
|
2802
|
-
}
|
|
2803
|
-
return `${status} ${reason}`;
|
|
2804
|
-
};
|
|
2805
|
-
|
|
2806
|
-
updateBranch = async (prUrl: string): Promise<boolean> => {
|
|
2807
|
-
const { owner, repo, issueNumber: prNumber } = this.parseIssueUrl(prUrl);
|
|
2808
|
-
const response = await this.fetchWithRateLimitRetry(() =>
|
|
2809
|
-
fetch(
|
|
2810
|
-
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/update-branch`,
|
|
2811
|
-
{
|
|
2812
|
-
method: 'PUT',
|
|
2813
|
-
headers: {
|
|
2814
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
2815
|
-
Accept: 'application/vnd.github+json',
|
|
2816
|
-
},
|
|
2817
|
-
},
|
|
2818
|
-
),
|
|
2819
|
-
);
|
|
2820
|
-
if (response.ok) {
|
|
2821
|
-
return true;
|
|
2822
|
-
}
|
|
2823
|
-
if (response.status === 422) {
|
|
2824
|
-
return false;
|
|
2825
|
-
}
|
|
2826
|
-
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
2827
|
-
throw new Error(`Failed to update branch for PR ${prUrl}: ${reason}`);
|
|
2828
|
-
};
|
|
2829
|
-
|
|
2830
|
-
deletePullRequestBranch = async (
|
|
2831
|
-
prUrl: string,
|
|
2832
|
-
branchName: string,
|
|
2833
|
-
): Promise<void> => {
|
|
2834
|
-
const { owner, repo } = this.parseIssueUrl(prUrl);
|
|
2835
|
-
const response = await this.fetchWithRateLimitRetry(() =>
|
|
2836
|
-
fetch(
|
|
2837
|
-
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/git/refs/heads/${encodeURIComponent(branchName)}`,
|
|
2838
|
-
{
|
|
2839
|
-
method: 'DELETE',
|
|
2840
|
-
headers: {
|
|
2841
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
2842
|
-
},
|
|
2843
|
-
},
|
|
2844
|
-
),
|
|
2845
|
-
);
|
|
2846
|
-
if (!response.ok && response.status !== 422) {
|
|
2847
|
-
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
2848
|
-
throw new Error(
|
|
2849
|
-
`Failed to delete branch ${branchName} for PR ${prUrl}: ${reason}`,
|
|
2850
|
-
);
|
|
2851
|
-
}
|
|
2852
|
-
};
|
|
2853
|
-
|
|
2854
|
-
createCommentByUrl = async (
|
|
2855
|
-
issueOrPrUrl: string,
|
|
2856
|
-
commentBody: string,
|
|
2857
|
-
): Promise<void> => {
|
|
2858
|
-
await this.restIssueRepository.createComment(issueOrPrUrl, commentBody);
|
|
2859
|
-
};
|
|
2860
|
-
|
|
2861
|
-
private fetchIssueBodyResponse = (url: string): Promise<Response> => {
|
|
2862
|
-
const { owner, repo, issueNumber } = this.parseIssueUrl(url);
|
|
2863
|
-
return this.fetchWithRateLimitRetry(() =>
|
|
2864
|
-
fetch(
|
|
2865
|
-
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}`,
|
|
2866
|
-
{
|
|
2867
|
-
method: 'GET',
|
|
2868
|
-
headers: {
|
|
2869
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
2870
|
-
Accept: 'application/vnd.github+json',
|
|
2871
|
-
},
|
|
2872
|
-
},
|
|
2873
|
-
),
|
|
2874
|
-
);
|
|
2875
|
-
};
|
|
2876
|
-
|
|
2877
|
-
private parseIssueBodyResponse = async (
|
|
2878
|
-
url: string,
|
|
2879
|
-
response: Response,
|
|
2880
|
-
): Promise<string> => {
|
|
2881
|
-
const body: unknown = await response.json();
|
|
2882
|
-
if (!isIssueOrPullRequestBodyResponse(body)) {
|
|
2883
|
-
throw new Error(
|
|
2884
|
-
`Unexpected response shape when fetching body for ${url}`,
|
|
2885
|
-
);
|
|
2886
|
-
}
|
|
2887
|
-
return body.body ?? '';
|
|
2888
|
-
};
|
|
2889
|
-
|
|
2890
|
-
getIssueOrPullRequestBody = async (url: string): Promise<string> => {
|
|
2891
|
-
const response = await this.fetchIssueBodyResponse(url);
|
|
2892
|
-
if (!response.ok) {
|
|
2893
|
-
await this.throwGitHubError(`Failed to fetch body for ${url}`, response);
|
|
2894
|
-
}
|
|
2895
|
-
return this.parseIssueBodyResponse(url, response);
|
|
2896
|
-
};
|
|
2897
|
-
|
|
2898
|
-
getIssueBodyByUrl = async (url: string): Promise<string | null> => {
|
|
2899
|
-
const response = await this.fetchIssueBodyResponse(url);
|
|
2900
|
-
if (response.status === 404) {
|
|
2901
|
-
return null;
|
|
2902
|
-
}
|
|
2903
|
-
if (!response.ok) {
|
|
2904
|
-
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
2905
|
-
throw new Error(`Failed to fetch body for ${url}: ${reason}`);
|
|
2906
|
-
}
|
|
2907
|
-
return this.parseIssueBodyResponse(url, response);
|
|
2908
|
-
};
|
|
2909
|
-
|
|
2910
|
-
getIssueOrPullRequestComments = async (
|
|
2911
|
-
url: string,
|
|
2912
|
-
): Promise<IssueComment[]> => {
|
|
2913
|
-
const { owner, repo, issueNumber } = this.parseIssueUrl(url);
|
|
2914
|
-
const perPage = 100;
|
|
2915
|
-
const collectedComments: IssueComment[] = [];
|
|
2916
|
-
let page = 1;
|
|
2917
|
-
let hasMore = true;
|
|
2918
|
-
while (hasMore) {
|
|
2919
|
-
const response = await this.fetchWithRateLimitRetry(() =>
|
|
2920
|
-
fetch(
|
|
2921
|
-
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}/comments?per_page=${perPage}&page=${page}`,
|
|
2922
|
-
{
|
|
2923
|
-
method: 'GET',
|
|
2924
|
-
headers: {
|
|
2925
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
2926
|
-
Accept: 'application/vnd.github+json',
|
|
2927
|
-
},
|
|
2928
|
-
},
|
|
2929
|
-
),
|
|
2930
|
-
);
|
|
2931
|
-
if (!response.ok) {
|
|
2932
|
-
await this.throwGitHubError(
|
|
2933
|
-
`Failed to fetch comments for ${url}`,
|
|
2934
|
-
response,
|
|
2935
|
-
);
|
|
2936
|
-
}
|
|
2937
|
-
const body: unknown = await response.json();
|
|
2938
|
-
if (!isIssueCommentsResponse(body)) {
|
|
2939
|
-
throw new Error(
|
|
2940
|
-
`Unexpected response shape when fetching comments for ${url}`,
|
|
2941
|
-
);
|
|
2942
|
-
}
|
|
2943
|
-
for (const comment of body) {
|
|
2944
|
-
collectedComments.push({
|
|
2945
|
-
author: comment.user?.login ?? '',
|
|
2946
|
-
body: comment.body ?? '',
|
|
2947
|
-
createdAt: new Date(comment.created_at),
|
|
2948
|
-
});
|
|
2949
|
-
}
|
|
2950
|
-
if (body.length < perPage) {
|
|
2951
|
-
hasMore = false;
|
|
2952
|
-
} else {
|
|
2953
|
-
page += 1;
|
|
2954
|
-
}
|
|
2955
|
-
}
|
|
2956
|
-
return collectedComments;
|
|
2957
|
-
};
|
|
2958
|
-
|
|
2959
|
-
getPullRequestDetail = async (
|
|
2960
|
-
prUrl: string,
|
|
2961
|
-
): Promise<PullRequestDetail | null> => {
|
|
2962
|
-
const {
|
|
2963
|
-
owner,
|
|
2964
|
-
repo,
|
|
2965
|
-
issueNumber: prNumber,
|
|
2966
|
-
isPr,
|
|
2967
|
-
} = this.parseIssueUrl(prUrl);
|
|
2968
|
-
if (!isPr) {
|
|
2969
|
-
return null;
|
|
2970
|
-
}
|
|
2971
|
-
const detailResponse = await this.fetchWithRateLimitRetry(() =>
|
|
2972
|
-
fetch(
|
|
2973
|
-
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}`,
|
|
2974
|
-
{
|
|
2975
|
-
method: 'GET',
|
|
2976
|
-
headers: {
|
|
2977
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
2978
|
-
Accept: 'application/vnd.github+json',
|
|
2979
|
-
},
|
|
2980
|
-
},
|
|
2981
|
-
),
|
|
2982
|
-
);
|
|
2983
|
-
if (!detailResponse.ok) {
|
|
2984
|
-
await this.throwGitHubError(
|
|
2985
|
-
`Failed to fetch detail for PR ${prUrl}`,
|
|
2986
|
-
detailResponse,
|
|
2987
|
-
);
|
|
2988
|
-
}
|
|
2989
|
-
const detailBody: unknown = await detailResponse.json();
|
|
2990
|
-
if (!isPullRequestDetailResponse(detailBody)) {
|
|
2991
|
-
throw new Error(
|
|
2992
|
-
`Unexpected response shape when fetching detail for PR ${prUrl}`,
|
|
2993
|
-
);
|
|
2994
|
-
}
|
|
2995
|
-
const files = await this.fetchPullRequestFiles(
|
|
2996
|
-
owner,
|
|
2997
|
-
repo,
|
|
2998
|
-
prNumber,
|
|
2999
|
-
prUrl,
|
|
3000
|
-
);
|
|
3001
|
-
return {
|
|
3002
|
-
title: detailBody.title,
|
|
3003
|
-
state: detailBody.state,
|
|
3004
|
-
merged: detailBody.merged,
|
|
3005
|
-
isDraft: detailBody.draft,
|
|
3006
|
-
additions: detailBody.additions,
|
|
3007
|
-
deletions: detailBody.deletions,
|
|
3008
|
-
changedFiles: detailBody.changed_files,
|
|
3009
|
-
headRefName: detailBody.head.ref,
|
|
3010
|
-
baseRefName: detailBody.base.ref,
|
|
3011
|
-
author: detailBody.user?.login ?? '',
|
|
3012
|
-
files,
|
|
3013
|
-
};
|
|
3014
|
-
};
|
|
3015
|
-
|
|
3016
|
-
private fetchPullRequestFiles = async (
|
|
3017
|
-
owner: string,
|
|
3018
|
-
repo: string,
|
|
3019
|
-
prNumber: number,
|
|
3020
|
-
prUrl: string,
|
|
3021
|
-
): Promise<PullRequestFile[]> => {
|
|
3022
|
-
const perPage = 100;
|
|
3023
|
-
const collectedFiles: PullRequestFile[] = [];
|
|
3024
|
-
let page = 1;
|
|
3025
|
-
let hasMore = true;
|
|
3026
|
-
while (hasMore) {
|
|
3027
|
-
const response = await this.fetchWithRateLimitRetry(() =>
|
|
3028
|
-
fetch(
|
|
3029
|
-
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/files?per_page=${perPage}&page=${page}`,
|
|
3030
|
-
{
|
|
3031
|
-
method: 'GET',
|
|
3032
|
-
headers: {
|
|
3033
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
3034
|
-
Accept: 'application/vnd.github+json',
|
|
3035
|
-
},
|
|
3036
|
-
},
|
|
3037
|
-
),
|
|
3038
|
-
);
|
|
3039
|
-
if (!response.ok) {
|
|
3040
|
-
await this.throwGitHubError(
|
|
3041
|
-
`Failed to fetch files for PR ${prUrl}`,
|
|
3042
|
-
response,
|
|
3043
|
-
);
|
|
3044
|
-
}
|
|
3045
|
-
const body: unknown = await response.json();
|
|
3046
|
-
if (!isPullRequestDetailFilesResponse(body)) {
|
|
3047
|
-
throw new Error(
|
|
3048
|
-
`Unexpected response shape when fetching files for PR ${prUrl}`,
|
|
3049
|
-
);
|
|
3050
|
-
}
|
|
3051
|
-
for (const file of body) {
|
|
3052
|
-
collectedFiles.push({
|
|
3053
|
-
filename: file.filename,
|
|
3054
|
-
status: file.status,
|
|
3055
|
-
additions: file.additions,
|
|
3056
|
-
deletions: file.deletions,
|
|
3057
|
-
patch: file.patch ?? null,
|
|
3058
|
-
rawUrl:
|
|
3059
|
-
file.raw_url === undefined
|
|
3060
|
-
? null
|
|
3061
|
-
: normalizeGitHubRawUrl(file.raw_url),
|
|
3062
|
-
});
|
|
3063
|
-
}
|
|
3064
|
-
if (body.length < perPage) {
|
|
3065
|
-
hasMore = false;
|
|
3066
|
-
} else {
|
|
3067
|
-
page += 1;
|
|
3068
|
-
}
|
|
3069
|
-
}
|
|
3070
|
-
return collectedFiles;
|
|
3071
|
-
};
|
|
3072
|
-
|
|
3073
|
-
getPullRequestCommits = async (
|
|
3074
|
-
prUrl: string,
|
|
3075
|
-
): Promise<PullRequestCommit[]> => {
|
|
3076
|
-
const {
|
|
3077
|
-
owner,
|
|
3078
|
-
repo,
|
|
3079
|
-
issueNumber: prNumber,
|
|
3080
|
-
isPr,
|
|
3081
|
-
} = this.parseIssueUrl(prUrl);
|
|
3082
|
-
if (!isPr) {
|
|
3083
|
-
return [];
|
|
3084
|
-
}
|
|
3085
|
-
const perPage = 100;
|
|
3086
|
-
const collectedCommits: PullRequestCommit[] = [];
|
|
3087
|
-
let page = 1;
|
|
3088
|
-
let hasMore = true;
|
|
3089
|
-
while (hasMore) {
|
|
3090
|
-
const response = await this.fetchWithRateLimitRetry(() =>
|
|
3091
|
-
fetch(
|
|
3092
|
-
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/commits?per_page=${perPage}&page=${page}`,
|
|
3093
|
-
{
|
|
3094
|
-
method: 'GET',
|
|
3095
|
-
headers: {
|
|
3096
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
3097
|
-
Accept: 'application/vnd.github+json',
|
|
3098
|
-
},
|
|
3099
|
-
},
|
|
3100
|
-
),
|
|
3101
|
-
);
|
|
3102
|
-
if (!response.ok) {
|
|
3103
|
-
await this.throwGitHubError(
|
|
3104
|
-
`Failed to fetch commits for PR ${prUrl}`,
|
|
3105
|
-
response,
|
|
3106
|
-
);
|
|
3107
|
-
}
|
|
3108
|
-
const body: unknown = await response.json();
|
|
3109
|
-
if (!isPullRequestCommitsResponse(body)) {
|
|
3110
|
-
throw new Error(
|
|
3111
|
-
`Unexpected response shape when fetching commits for PR ${prUrl}`,
|
|
3112
|
-
);
|
|
3113
|
-
}
|
|
3114
|
-
for (const commit of body) {
|
|
3115
|
-
collectedCommits.push({
|
|
3116
|
-
sha: commit.sha,
|
|
3117
|
-
message: commit.commit.message,
|
|
3118
|
-
author: commit.commit.author?.name ?? '',
|
|
3119
|
-
authoredAt: new Date(commit.commit.author?.date ?? 0),
|
|
3120
|
-
});
|
|
3121
|
-
}
|
|
3122
|
-
if (body.length < perPage) {
|
|
3123
|
-
hasMore = false;
|
|
3124
|
-
} else {
|
|
3125
|
-
page += 1;
|
|
3126
|
-
}
|
|
3127
|
-
}
|
|
3128
|
-
return collectedCommits;
|
|
3129
|
-
};
|
|
3130
|
-
|
|
3131
|
-
getIssueOrPullRequestState = async (
|
|
3132
|
-
url: string,
|
|
3133
|
-
): Promise<{
|
|
3134
|
-
state: string;
|
|
3135
|
-
merged: boolean;
|
|
3136
|
-
isPullRequest: boolean;
|
|
3137
|
-
title: string;
|
|
3138
|
-
}> => {
|
|
3139
|
-
const { owner, repo, issueNumber, isPr } = this.parseIssueUrl(url);
|
|
3140
|
-
if (isPr) {
|
|
3141
|
-
const response = await this.fetchWithRateLimitRetry(() =>
|
|
3142
|
-
fetch(
|
|
3143
|
-
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${issueNumber}`,
|
|
3144
|
-
{
|
|
3145
|
-
method: 'GET',
|
|
3146
|
-
headers: {
|
|
3147
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
3148
|
-
Accept: 'application/vnd.github+json',
|
|
3149
|
-
},
|
|
3150
|
-
},
|
|
3151
|
-
),
|
|
3152
|
-
);
|
|
3153
|
-
if (!response.ok) {
|
|
3154
|
-
await this.throwGitHubError(
|
|
3155
|
-
`Failed to fetch state for ${url}`,
|
|
3156
|
-
response,
|
|
3157
|
-
);
|
|
3158
|
-
}
|
|
3159
|
-
const body: unknown = await response.json();
|
|
3160
|
-
if (!isPullRequestDetailResponse(body)) {
|
|
3161
|
-
throw new Error(
|
|
3162
|
-
`Unexpected response shape when fetching state for ${url}`,
|
|
3163
|
-
);
|
|
3164
|
-
}
|
|
3165
|
-
return {
|
|
3166
|
-
state: body.state,
|
|
3167
|
-
merged: body.merged,
|
|
3168
|
-
isPullRequest: true,
|
|
3169
|
-
title: body.title,
|
|
3170
|
-
};
|
|
3171
|
-
}
|
|
3172
|
-
const response = await this.fetchWithRateLimitRetry(() =>
|
|
3173
|
-
fetch(
|
|
3174
|
-
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}`,
|
|
3175
|
-
{
|
|
3176
|
-
method: 'GET',
|
|
3177
|
-
headers: {
|
|
3178
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
3179
|
-
Accept: 'application/vnd.github+json',
|
|
3180
|
-
},
|
|
3181
|
-
},
|
|
3182
|
-
),
|
|
3183
|
-
);
|
|
3184
|
-
if (!response.ok) {
|
|
3185
|
-
await this.throwGitHubError(`Failed to fetch state for ${url}`, response);
|
|
3186
|
-
}
|
|
3187
|
-
const body: unknown = await response.json();
|
|
3188
|
-
if (!isIssueOrPullRequestStateResponse(body)) {
|
|
3189
|
-
throw new Error(
|
|
3190
|
-
`Unexpected response shape when fetching state for ${url}`,
|
|
3191
|
-
);
|
|
3192
|
-
}
|
|
3193
|
-
return {
|
|
3194
|
-
state: body.state,
|
|
3195
|
-
merged: false,
|
|
3196
|
-
isPullRequest: false,
|
|
3197
|
-
title: body.title,
|
|
3198
|
-
};
|
|
3199
|
-
};
|
|
3200
|
-
|
|
3201
|
-
getPullRequestSummary = async (
|
|
3202
|
-
prUrl: string,
|
|
3203
|
-
): Promise<{
|
|
3204
|
-
title: string;
|
|
3205
|
-
body: string;
|
|
3206
|
-
additions: number;
|
|
3207
|
-
deletions: number;
|
|
3208
|
-
changedFiles: number;
|
|
3209
|
-
} | null> => {
|
|
3210
|
-
const {
|
|
3211
|
-
owner,
|
|
3212
|
-
repo,
|
|
3213
|
-
issueNumber: prNumber,
|
|
3214
|
-
isPr,
|
|
3215
|
-
} = this.parseIssueUrl(prUrl);
|
|
3216
|
-
if (!isPr) {
|
|
3217
|
-
return null;
|
|
3218
|
-
}
|
|
3219
|
-
const response = await this.fetchWithRateLimitRetry(() =>
|
|
3220
|
-
fetch(
|
|
3221
|
-
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}`,
|
|
3222
|
-
{
|
|
3223
|
-
method: 'GET',
|
|
3224
|
-
headers: {
|
|
3225
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
3226
|
-
Accept: 'application/vnd.github+json',
|
|
3227
|
-
},
|
|
3228
|
-
},
|
|
3229
|
-
),
|
|
3230
|
-
);
|
|
3231
|
-
if (!response.ok) {
|
|
3232
|
-
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
3233
|
-
throw new Error(`Failed to fetch summary for PR ${prUrl}: ${reason}`);
|
|
3234
|
-
}
|
|
3235
|
-
const body: unknown = await response.json();
|
|
3236
|
-
if (!isPullRequestDetailResponse(body)) {
|
|
3237
|
-
throw new Error(
|
|
3238
|
-
`Unexpected response shape when fetching summary for PR ${prUrl}`,
|
|
3239
|
-
);
|
|
3240
|
-
}
|
|
3241
|
-
return {
|
|
3242
|
-
title: body.title,
|
|
3243
|
-
body: body.body ?? '',
|
|
3244
|
-
additions: body.additions,
|
|
3245
|
-
deletions: body.deletions,
|
|
3246
|
-
changedFiles: body.changed_files,
|
|
3247
|
-
};
|
|
3248
|
-
};
|
|
3249
|
-
|
|
3250
|
-
deleteAllCommentsByUrl = async (issueOrPrUrl: string): Promise<void> => {
|
|
3251
|
-
const { owner, repo, issueNumber } = this.parseIssueUrl(issueOrPrUrl);
|
|
3252
|
-
const perPage = 100;
|
|
3253
|
-
while (true) {
|
|
3254
|
-
const response = await this.fetchWithRateLimitRetry(() =>
|
|
3255
|
-
fetch(
|
|
3256
|
-
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}/comments?per_page=${perPage}&page=1`,
|
|
3257
|
-
{
|
|
3258
|
-
method: 'GET',
|
|
3259
|
-
headers: {
|
|
3260
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
3261
|
-
Accept: 'application/vnd.github+json',
|
|
3262
|
-
},
|
|
3263
|
-
},
|
|
3264
|
-
),
|
|
3265
|
-
);
|
|
3266
|
-
if (!response.ok) {
|
|
3267
|
-
await this.throwGitHubError(
|
|
3268
|
-
`Failed to fetch comments for ${issueOrPrUrl}`,
|
|
3269
|
-
response,
|
|
3270
|
-
);
|
|
3271
|
-
}
|
|
3272
|
-
const body: unknown = await response.json();
|
|
3273
|
-
if (!isIssueCommentIdResponse(body)) {
|
|
3274
|
-
throw new Error(
|
|
3275
|
-
`Unexpected response shape when fetching comments for ${issueOrPrUrl}`,
|
|
3276
|
-
);
|
|
3277
|
-
}
|
|
3278
|
-
for (const comment of body) {
|
|
3279
|
-
const deleteResponse = await this.fetchWithRateLimitRetry(() =>
|
|
3280
|
-
fetch(
|
|
3281
|
-
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/comments/${comment.id}`,
|
|
3282
|
-
{
|
|
3283
|
-
method: 'DELETE',
|
|
3284
|
-
headers: {
|
|
3285
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
3286
|
-
Accept: 'application/vnd.github+json',
|
|
3287
|
-
},
|
|
3288
|
-
},
|
|
3289
|
-
),
|
|
3290
|
-
);
|
|
3291
|
-
if (!deleteResponse.ok) {
|
|
3292
|
-
await this.throwGitHubError(
|
|
3293
|
-
`Failed to delete comment ${comment.id} on ${issueOrPrUrl}`,
|
|
3294
|
-
deleteResponse,
|
|
3295
|
-
);
|
|
3296
|
-
}
|
|
3297
|
-
}
|
|
3298
|
-
if (body.length < perPage) {
|
|
3299
|
-
break;
|
|
3300
|
-
}
|
|
3301
|
-
}
|
|
3302
|
-
};
|
|
3303
|
-
}
|