@gitkraken/core-gitlens 0.11.1 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/CHANGELOG.md +84 -1
  2. package/dist/git/cache.d.ts +84 -29
  3. package/dist/git/cache.js +188 -54
  4. package/dist/git/cache.js.map +1 -1
  5. package/dist/git/features.d.ts +1 -1
  6. package/dist/git/features.js +4 -0
  7. package/dist/git/features.js.map +1 -1
  8. package/dist/git/models/branch.d.ts +15 -2
  9. package/dist/git/models/branch.js +27 -5
  10. package/dist/git/models/branch.js.map +1 -1
  11. package/dist/git/models/issue.d.ts +3 -1
  12. package/dist/git/models/issue.js.map +1 -1
  13. package/dist/git/models/status.js +1 -1
  14. package/dist/git/models/worktree.d.ts +4 -1
  15. package/dist/git/models/worktree.js +4 -2
  16. package/dist/git/models/worktree.js.map +1 -1
  17. package/dist/git/providers/branches.d.ts +73 -8
  18. package/dist/git/providers/pausedOperations.d.ts +6 -0
  19. package/dist/git/providers/provider.d.ts +34 -2
  20. package/dist/git/providers/status.d.ts +8 -2
  21. package/dist/git/remotes/azure-devops.js +61 -53
  22. package/dist/git/remotes/azure-devops.js.map +1 -1
  23. package/dist/git/service.d.ts +8 -5
  24. package/dist/git/service.js +16 -8
  25. package/dist/git/service.js.map +1 -1
  26. package/dist/git/utils/branch.utils.d.ts +8 -1
  27. package/dist/git/utils/branch.utils.js +30 -1
  28. package/dist/git/utils/branch.utils.js.map +1 -1
  29. package/dist/git/utils/issue.utils.d.ts +3 -1
  30. package/dist/git/utils/issue.utils.js +5 -1
  31. package/dist/git/utils/issue.utils.js.map +1 -1
  32. package/dist/git/watching/changeEvent.d.ts +13 -2
  33. package/dist/git/watching/changeEvent.js +17 -3
  34. package/dist/git/watching/changeEvent.js.map +1 -1
  35. package/dist/git/watching/gitDirPoller.d.ts +111 -0
  36. package/dist/git/watching/gitDirPoller.js +266 -0
  37. package/dist/git/watching/gitDirPoller.js.map +1 -0
  38. package/dist/git/watching/provider.d.ts +16 -0
  39. package/dist/git/watching/watchGroup.d.ts +40 -4
  40. package/dist/git/watching/watchGroup.js +143 -17
  41. package/dist/git/watching/watchGroup.js.map +1 -1
  42. package/dist/git/watching/watchService.d.ts +70 -1
  43. package/dist/git/watching/watchService.js +158 -13
  44. package/dist/git/watching/watchService.js.map +1 -1
  45. package/dist/git/watching/watchSession.d.ts +44 -0
  46. package/dist/git/watching/watchSession.js +91 -6
  47. package/dist/git/watching/watchSession.js.map +1 -1
  48. package/dist/git/watching/watcherPatterns.d.ts +21 -0
  49. package/dist/git/watching/watcherPatterns.js +67 -1
  50. package/dist/git/watching/watcherPatterns.js.map +1 -1
  51. package/dist/git/watching/worktreeSummary.d.ts +34 -7
  52. package/dist/git/watching/worktreeSummary.js +39 -12
  53. package/dist/git/watching/worktreeSummary.js.map +1 -1
  54. package/dist/git-cli/cliGitProvider.d.ts +34 -2
  55. package/dist/git-cli/cliGitProvider.js +119 -17
  56. package/dist/git-cli/cliGitProvider.js.map +1 -1
  57. package/dist/git-cli/exec/commandClassifier.d.ts +15 -0
  58. package/dist/git-cli/exec/commandClassifier.js +48 -2
  59. package/dist/git-cli/exec/commandClassifier.js.map +1 -1
  60. package/dist/git-cli/exec/git.d.ts +15 -3
  61. package/dist/git-cli/exec/git.js +38 -17
  62. package/dist/git-cli/exec/git.js.map +1 -1
  63. package/dist/git-cli/exec/gitQueue.d.ts +2 -0
  64. package/dist/git-cli/exec/gitQueue.js +2 -1
  65. package/dist/git-cli/exec/gitQueue.js.map +1 -1
  66. package/dist/git-cli/exec/indexLock.d.ts +38 -0
  67. package/dist/git-cli/exec/indexLock.js +150 -0
  68. package/dist/git-cli/exec/indexLock.js.map +1 -0
  69. package/dist/git-cli/parsers/worktreeParser.js +42 -2
  70. package/dist/git-cli/parsers/worktreeParser.js.map +1 -1
  71. package/dist/git-cli/providers/branches.d.ts +109 -10
  72. package/dist/git-cli/providers/branches.js +624 -131
  73. package/dist/git-cli/providers/branches.js.map +1 -1
  74. package/dist/git-cli/providers/config.d.ts +9 -5
  75. package/dist/git-cli/providers/config.js +60 -27
  76. package/dist/git-cli/providers/config.js.map +1 -1
  77. package/dist/git-cli/providers/operations.d.ts +17 -0
  78. package/dist/git-cli/providers/operations.js +153 -96
  79. package/dist/git-cli/providers/operations.js.map +1 -1
  80. package/dist/git-cli/providers/pausedOperations.d.ts +8 -0
  81. package/dist/git-cli/providers/pausedOperations.js +60 -40
  82. package/dist/git-cli/providers/pausedOperations.js.map +1 -1
  83. package/dist/git-cli/providers/refs.js +11 -8
  84. package/dist/git-cli/providers/refs.js.map +1 -1
  85. package/dist/git-cli/providers/remotes.js +19 -10
  86. package/dist/git-cli/providers/remotes.js.map +1 -1
  87. package/dist/git-cli/providers/staging.js +40 -15
  88. package/dist/git-cli/providers/staging.js.map +1 -1
  89. package/dist/git-cli/providers/stash.d.ts +2 -0
  90. package/dist/git-cli/providers/stash.js +33 -18
  91. package/dist/git-cli/providers/stash.js.map +1 -1
  92. package/dist/git-cli/providers/status.d.ts +3 -1
  93. package/dist/git-cli/providers/status.js +38 -20
  94. package/dist/git-cli/providers/status.js.map +1 -1
  95. package/dist/git-cli/providers/tags.js +6 -4
  96. package/dist/git-cli/providers/tags.js.map +1 -1
  97. package/dist/git-cli/providers/worktrees.d.ts +1 -0
  98. package/dist/git-cli/providers/worktrees.js +60 -31
  99. package/dist/git-cli/providers/worktrees.js.map +1 -1
  100. package/dist/git-cli/service.d.ts +6 -0
  101. package/dist/git-cli/service.js +5 -2
  102. package/dist/git-cli/service.js.map +1 -1
  103. package/dist/ipc/discovery.js +1 -1
  104. package/dist/plus/agents/providers/claudeCodeTranscript.js +6 -6
  105. package/dist/plus/agents/providers/claudeCodeTranscript.js.map +1 -1
  106. package/dist/plus/ai/providers/openAICompatibleProviderBase.js +1 -1
  107. package/dist/plus/git-github/providers/github/status.d.ts +3 -1
  108. package/dist/plus/git-github/providers/github/status.js +1 -1
  109. package/dist/plus/git-github/providers/github/status.js.map +1 -1
  110. package/dist/plus/integrations/collectionMetadata.d.ts +13 -2
  111. package/dist/plus/integrations/collectionMetadata.js +14 -2
  112. package/dist/plus/integrations/collectionMetadata.js.map +1 -1
  113. package/dist/plus/integrations/index.d.ts +2 -2
  114. package/dist/plus/integrations/index.js.map +1 -1
  115. package/dist/plus/integrations/integrationService.d.ts +1 -0
  116. package/dist/plus/integrations/integrationService.js +1 -1
  117. package/dist/plus/integrations/integrationService.js.map +1 -1
  118. package/dist/plus/integrations/manager.d.ts +1 -0
  119. package/dist/plus/integrations/models/gitHostIntegration.d.ts +41 -5
  120. package/dist/plus/integrations/models/gitHostIntegration.js +109 -30
  121. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
  122. package/dist/plus/integrations/models/integration.js +1 -1
  123. package/dist/plus/integrations/models/issueReads.d.ts +17 -4
  124. package/dist/plus/integrations/providers/apiClients.d.ts +2 -0
  125. package/dist/plus/integrations/providers/apiClients.js +7 -1
  126. package/dist/plus/integrations/providers/apiClients.js.map +1 -1
  127. package/dist/plus/integrations/providers/azure/azure.d.ts +5 -4
  128. package/dist/plus/integrations/providers/azure/azure.js +38 -77
  129. package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
  130. package/dist/plus/integrations/providers/azure/azure.utils.js +20 -17
  131. package/dist/plus/integrations/providers/azure/azure.utils.js.map +1 -1
  132. package/dist/plus/integrations/providers/azure/models.d.ts +27 -10
  133. package/dist/plus/integrations/providers/azure/models.js +40 -20
  134. package/dist/plus/integrations/providers/azure/models.js.map +1 -1
  135. package/dist/plus/integrations/providers/azure/workItemStateCategories.d.ts +23 -0
  136. package/dist/plus/integrations/providers/azure/workItemStateCategories.js +84 -0
  137. package/dist/plus/integrations/providers/azure/workItemStateCategories.js.map +1 -0
  138. package/dist/plus/integrations/providers/azureDevOps.d.ts +44 -0
  139. package/dist/plus/integrations/providers/azureDevOps.js +113 -22
  140. package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
  141. package/dist/plus/integrations/providers/azureDevOpsServer.d.ts +45 -1
  142. package/dist/plus/integrations/providers/azureDevOpsServer.js +121 -6
  143. package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
  144. package/dist/plus/integrations/providers/issueSorts.d.ts +0 -1
  145. package/dist/plus/integrations/providers/jira-server.js +29 -19
  146. package/dist/plus/integrations/providers/jira-server.js.map +1 -1
  147. package/dist/plus/integrations/providers/jira.d.ts +5 -2
  148. package/dist/plus/integrations/providers/jira.js +63 -44
  149. package/dist/plus/integrations/providers/jira.js.map +1 -1
  150. package/dist/plus/integrations/providers/jiraIssueByKey.d.ts +3 -4
  151. package/dist/plus/integrations/providers/jiraIssueByKey.js +3 -4
  152. package/dist/plus/integrations/providers/jiraIssueByKey.js.map +1 -1
  153. package/dist/plus/integrations/providers/linearIssuesEtag.d.ts +5 -5
  154. package/dist/plus/integrations/providers/linearIssuesEtag.js +6 -6
  155. package/dist/plus/integrations/providers/linearIssuesEtag.js.map +1 -1
  156. package/dist/plus/integrations/providers/models.d.ts +12 -3
  157. package/dist/plus/integrations/providers/models.js +16 -9
  158. package/dist/plus/integrations/providers/models.js.map +1 -1
  159. package/dist/plus/integrations/providers/providersApi.d.ts +21 -33
  160. package/dist/plus/integrations/providers/providersApi.js +159 -49
  161. package/dist/plus/integrations/providers/providersApi.js.map +1 -1
  162. package/dist/plus/integrations/providers/utils/jiraIssueDrains.d.ts +49 -15
  163. package/dist/plus/integrations/providers/utils/jiraIssueDrains.js +132 -10
  164. package/dist/plus/integrations/providers/utils/jiraIssueDrains.js.map +1 -1
  165. package/dist/plus/integrations/providers/utils/providerPaging.js +8 -2
  166. package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
  167. package/dist/plus/integrations/reads/cursors.js +1 -1
  168. package/dist/plus/integrations/reads/drains.d.ts +0 -1
  169. package/dist/plus/integrations/reads/filters.d.ts +3 -3
  170. package/dist/plus/integrations/reads/filters.js.map +1 -1
  171. package/dist/plus/integrations/reads/hierarchy.js +2 -2
  172. package/dist/plus/integrations/reads/hierarchy.js.map +1 -1
  173. package/dist/plus/integrations/reads/hierarchy.utils.d.ts +13 -0
  174. package/dist/plus/integrations/reads/hierarchy.utils.js +26 -1
  175. package/dist/plus/integrations/reads/hierarchy.utils.js.map +1 -1
  176. package/dist/plus/integrations/reads/issueBatch.d.ts +2 -1
  177. package/dist/plus/integrations/reads/issueBatch.js +1 -1
  178. package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
  179. package/dist/plus/integrations/reads/issueTracker.js +27 -18
  180. package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
  181. package/dist/plus/integrations/reads/issues.d.ts +2 -1
  182. package/dist/plus/integrations/reads/issues.js +19 -4
  183. package/dist/plus/integrations/reads/issues.js.map +1 -1
  184. package/dist/plus/integrations/reads/pullRequestBatch.js +1 -1
  185. package/dist/plus/integrations/reads/resolveRepository.js +12 -3
  186. package/dist/plus/integrations/reads/resolveRepository.js.map +1 -1
  187. package/dist/plus/integrations/reads/warnings.d.ts +11 -4
  188. package/dist/plus/integrations/reads/warnings.js +15 -5
  189. package/dist/plus/integrations/reads/warnings.js.map +1 -1
  190. package/dist/plus/integrations/results.d.ts +48 -4
  191. package/dist/plus/integrations/results.js +33 -13
  192. package/dist/plus/integrations/results.js.map +1 -1
  193. package/dist/plus/integrations/utils/integration.utils.d.ts +6 -1
  194. package/dist/plus/integrations/utils/integration.utils.js +8 -1
  195. package/dist/plus/integrations/utils/integration.utils.js.map +1 -1
  196. package/dist/utils/env/node/exec.js +2 -2
  197. package/dist/utils/promiseCache.d.ts +10 -0
  198. package/dist/utils/promiseCache.js +17 -2
  199. package/dist/utils/promiseCache.js.map +1 -1
  200. package/docs/integrations.md +93 -31
  201. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"issues.js","names":[],"sources":["../../../../../plus/integrations/src/reads/issues.ts"],"sourcesContent":["import type { IssueShape, IssueSorting } from '@gitlens/git/models/issue.js';\nimport { mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { GitCloudHostIntegrationId, GitSelfManagedHostIntegrationId } from '../constants.js';\nimport type { GitHostIntegration } from '../models/gitHostIntegration.js';\nimport type { IssueFilter, ProviderReposInput } from '../providers/models.js';\nimport { isRepoIdsInput, PagingMode, providersMetadata } from '../providers/models.js';\nimport { mergeCollectionMetadata } from '../providers/utils/providerPaging.js';\nimport type { ProviderPagedResult, ProviderWarning } from '../results.js';\nimport { reconcileOmissionsWithFailure } from '../results.js';\nimport {\n\tisGitHostIntegration,\n\tisIssuesHostIntegrationId,\n\twarnOnMissingSessionForDomain,\n} from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { runCaptured } from './drains.js';\nimport { resolveAccountWideIssueFilters } from './filters.js';\nimport { resolveIssueSort, toIssueOrdering } from './ordering.js';\nimport {\n\tdrainFlatPagesToRequestedPage,\n\tdrainToRequestedPage,\n\tisPageNumberAdvanceable,\n\tpageToCursor,\n\trefusedPage,\n\tresolveContinuation,\n\tresolveCurrentPage,\n\ttoProviderPageInfo,\n\tusableCursor,\n} from './paging.js';\nimport {\n\tgitHostOnlySurfaceWarning,\n\tissuesUnsupportedWarning,\n\totherWarning,\n\ttruncationWarning,\n\tunmergeableIssueSortWarning,\n\tunsupportedAccountWideIssueFiltersWarning,\n\tunsupportedIssueSortWarning,\n\tunsupportedWarning,\n} from './warnings.js';\n\n/**\n * Whether a repo-scoped page is assembled from SEVERAL provider queries and merged here — which is what decides\n * whether the requested order can be honored at all.\n *\n * `PagingMode.Repos` (GitHub) sends one search however many repositories are named, so its page arrives ordered by\n * the provider. `Repo` (GitLab) issues one query per repository, and `Project` (Azure) one per PROJECT — several\n * repositories of the same project are still one query, which is why that case counts distinct projects rather than\n * repositories.\n *\n * The `Repo` case has a second way to merge that has nothing to do with the count: given repository IDS rather than\n * descriptors, `getMyIssuesForReposResult` skips its per-repository fan-out entirely (that branch is guarded on\n * `!isRepoIdsInput`) and calls the SDK's `getIssuesForRepos`, which for GitLab is the multi-project aggregate that\n * merges in the SDK and refuses `priority`/`dueDate` however few scopes it was given. Treating that as merged is\n * what makes this facade refuse it up front, with the message that names the reason, instead of letting the SDK\n * reject a read the capability table had just promised.\n */\nfunction mergesProviderQueries(pagingMode: PagingMode | undefined, repos: ProviderReposInput | undefined): boolean {\n\tconst scopes = repos ?? [];\n\tswitch (pagingMode) {\n\t\tcase PagingMode.Repo:\n\t\t\t// `isRepoIdsInput` is the same predicate `ProvidersApi` selects the SDK aggregate with, so this cannot\n\t\t\t// disagree with the branch it is predicting.\n\t\t\treturn isRepoIdsInput(scopes) ? scopes.length > 0 : scopes.length > 1;\n\t\tcase PagingMode.Project:\n\t\t\t// Only the descriptor form carries a project; the id form is refused by the Azure read before it runs.\n\t\t\treturn new Set(scopes.map(r => (typeof r === 'object' ? r.project : undefined))).size > 1;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\n/**\n * What {@link listIssuesPage} accepts, shared by name with the two functions it dispatches to.\n *\n * Named rather than left inline: three functions now take this exact shape, and reaching it from the other two as\n * `Parameters<typeof listIssuesPage>[1]` would define the contract in terms of the function that consumes it —\n * inside out, and unreadable at the point where the fields are actually used.\n */\nexport interface ListIssuesPageOptions {\n\tproviderId: IntegrationIds;\n\trepos?: ProviderReposInput;\n\t/** Narrows the account-wide read to one org/account. Requires a host with a project layer (Azure). */\n\torg?: string;\n\t/** Narrows the account-wide read to one project. Requires a host with a project layer (Azure). */\n\tproject?: string;\n\t/**\n\t * Narrows to the requested relationship(s). On the account-wide path this replaces the provider's own\n\t * definition of \"my issues\" (GitHub/GHE: authored ∪ assigned ∪ mentioned; Azure: assigned ∪ authored;\n\t * GitLab: assigned-to-me), so `[Assignee]` yields `assignee:@me` everywhere it's expressible. A set the\n\t * provider can't express server-side is refused whole (warning + `fetchFailed`), never widened — check\n\t * getSupportedFilters first to avoid that path.\n\t */\n\tfilters?: IssueFilter[];\n\t/** Broadens the read to every assignee. Contradicts `filters`; passing both is refused. */\n\tincludeAllAssignees?: boolean;\n\t/**\n\t * How to order the page, as `field:direction`. Omitted orders most-recently-updated-first wherever the\n\t * provider can express that, which is this facade's default rather than the provider's own.\n\t *\n\t * Validated against `getSupportedFilters().issueSorts` on the repo-scoped path and `.issueSortsAccountWide`\n\t * on the account-wide one — two genuinely different vocabularies for GitLab, whose two reads are different\n\t * APIs. A key the provider can't express refuses the read (warning + `fetchFailed`) rather than serving a\n\t * differently-ordered list, for the same reason an inexpressible filter does: the reachable window is\n\t * bounded, so another order is another subset.\n\t *\n\t * On the repo-scoped path with SEVERAL scopes the page is a merge of one query per repository/project, so\n\t * only a key a normalized issue carries can be honored: `priority`, `dueDate` and `resolved` are refused\n\t * there even where the provider supports them on a single scope. Order is per page, and per scope across\n\t * pages — the merge orders what a page contains, not the sequence of pages.\n\t */\n\tsort?: IssueSorting;\n\tpage?: number;\n\tcursor?: string;\n\titemsPerPage?: number;\n\tforceSync?: boolean;\n\tconnectionId?: string;\n\t/**\n\t * Explicit self-managed host domain. Used only when the requested connection has no configured domain;\n\t * it must come from the trusted authentication configuration, not repository or remote data.\n\t */\n\tdomain?: string;\n}\n\n/**\n * What both halves of {@link listIssuesPage} need from its shared prologue.\n *\n * A bundle rather than six parameters because the two reads take exactly the same inputs and differ only in what\n * they do with them; threading them positionally would make the two call sites read as two different reads of two\n * different things, which is the opposite of what is true.\n *\n * Note `ctx` is absent: every use of it — resolving the integration, the domain, the forced refresh, the\n * connection warnings — is in the prologue, which is what makes this the right cut.\n */\ninterface IssueReadContext {\n\toptions: ListIssuesPageOptions;\n\tintegration: GitHostIntegration;\n\tdomain: string | undefined;\n\twarnOnMissingSession: boolean;\n\tpage: number;\n}\n\nexport async function listIssuesPage(\n\tctx: ProviderReadContext,\n\toptions: ListIssuesPageOptions,\n): Promise<ProviderPagedResult<IssueShape>> {\n\t// Truncated as well as floored, like every other paged read here: `page` is reported back as\n\t// `page.currentPage` and compared against the position a drain reached, and neither can be fractional.\n\tconst page = Math.max(1, Math.trunc(options.page ?? 1));\n\tif (isIssuesHostIntegrationId(options.providerId)) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, 'repository issue reads')],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\t// A supplied connection or domain that no longer resolves is a broken target, not an empty account —\n\t\t// surface a no-connection warning + fetchFailed rather than a silent empty page.\n\t\tconst early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);\n\t\treturn refusedPage(page, early.warnings, early.fetchFailed);\n\t}\n\tif (!isGitHostIntegration(integration)) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, 'repository issue reads')],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tawait ctx.forceRefreshIfRequested(integration, options.forceSync, options.connectionId);\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\tconst warnOnMissingSession = warnOnMissingSessionForDomain(options.providerId, options.domain);\n\n\t// A git host whose issue tracker is deprecated (Bitbucket, superseded by dedicated issue integrations)\n\t// reports issues as explicitly unsupported rather than serving a partial/legacy source or a silent empty.\n\tif (!integration.supportsIssues) {\n\t\treturn refusedPage(page, [issuesUnsupportedWarning(options.providerId, domain, options.connectionId)], true);\n\t}\n\n\t// `repos` is what selects between the two: with none there is no scope to query per repository, so the\n\t// provider's own account-wide search answers instead. They are different provider queries with different\n\t// capability tables, cursors and completeness signals — see each function.\n\tconst context: IssueReadContext = {\n\t\toptions: options,\n\t\tintegration: integration,\n\t\tdomain: domain,\n\t\twarnOnMissingSession: warnOnMissingSession,\n\t\tpage: page,\n\t};\n\treturn (options.repos?.length ?? 0) === 0 ? readAccountWideIssuesPage(context) : readRepoScopedIssuesPage(context);\n}\n\n/**\n * The account-wide read: the provider's own definition of \"my issues\", behind a composite cursor that spans\n * several provider searches.\n *\n * Its own function rather than a branch of the paged read, because almost nothing is shared past the guards: it\n * validates a different filter table and a different sort table, it is cursor-only (a page number cannot address\n * a composite cursor), it accumulates per-scope failures across the pages it walks, and it always merges several\n * queries. The two used to share one 460-line function behind a mode flag, and the comments explaining how far\n * apart they are were the clue that they wanted separating.\n */\nasync function readAccountWideIssuesPage({\n\toptions,\n\tintegration,\n\tdomain,\n\twarnOnMissingSession,\n\tpage,\n}: IssueReadContext): Promise<ProviderPagedResult<IssueShape>> {\n\t// The account-wide read is cursor-only, so a refusal can't claim the requested position — it reports\n\t// page 1, per ProviderPageInfo.currentPage.\n\tconst refused = (warning: ProviderWarning) => refusedPage<IssueShape>(1, [warning], true);\n\n\t// Checked before the provider-specific guards below: a caller passing both has a contradictory request\n\t// whatever the provider, and saying so is more useful than reporting one half of it as unsupported.\n\t// `filters` narrows this read to a relationship (`[Assignee]` ⇒ just assigned-to-me); `includeAllAssignees`\n\t// broadens it to every assignee. Honoring either silently would answer a question the caller didn't ask.\n\tif (options.filters?.length && options.includeAllAssignees === true) {\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'`filters` and `includeAllAssignees` are contradictory for an account-wide issue read; pass only one.',\n\t\t\t),\n\t\t);\n\t}\n\n\t// GitHub expresses \"any assignee\" as `assignee:*`, which is meaningless without a scope: unscoped it\n\t// matches millions of issues across all of GitHub instead of the user's own world. ANY scope makes it\n\t// meaningful (one repository, several, or an org) — it is specifically THIS branch, the account-wide read,\n\t// that has none to offer. Scope the read to repositories (or use `searchIssuesPage`, whose criteria model\n\t// takes `any-assignee` alongside a `repos`/`org` scope) rather than reading unscoped.\n\tif (\n\t\toptions.includeAllAssignees === true &&\n\t\t(options.providerId === GitCloudHostIntegrationId.GitHub ||\n\t\t\toptions.providerId === GitSelfManagedHostIntegrationId.CloudGitHubEnterprise)\n\t) {\n\t\treturn refused(\n\t\t\tunsupportedWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'`includeAllAssignees` is not supported for account-wide GitHub issue reads; scope the read to repositories instead.',\n\t\t\t),\n\t\t);\n\t}\n\n\t// Only a host with a project layer can narrow server-side. Reject the request rather than serving an\n\t// unscoped list as if it had been narrowed: the caller would otherwise have to filter client-side,\n\t// which desynchronizes the filtered `items` from this read's `hasMore`/`currentPage` and shows\n\t// \"no issues\" for a page that simply held none of the requested project's.\n\tif ((options.org != null || options.project != null) && !integration.supportsProjectDiscovery) {\n\t\treturn refused(\n\t\t\tunsupportedWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`Project-scoped issue reads are not supported by '${options.providerId}'; scope the read to repositories instead.`,\n\t\t\t),\n\t\t);\n\t}\n\n\t// Narrowing the account-wide read is only honest when the provider can express it server-side: its\n\t// per-relationship queries produced the page and the cursor together, so dropping items afterward would\n\t// leave `items` describing a different result set than `hasMore`/`currentPage`.\n\tconst resolvedIssueFilters = resolveAccountWideIssueFilters(options.providerId, options.filters);\n\tif (resolvedIssueFilters.unsupported) {\n\t\treturn refused(\n\t\t\tunsupportedAccountWideIssueFiltersWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\toptions.filters!,\n\t\t\t),\n\t\t);\n\t}\n\n\t// Every provider's account-wide read is a UNION of several queries — GitHub's three `@me` searches,\n\t// GitLab's one REST call per relationship, Azure's (project x relationship) drains — so it always merges,\n\t// and `supportedAccountWideIssueSorts` already lists only keys a merge can honor. No separate\n\t// mergeability refusal is needed here, unlike on the repo-scoped path below.\n\tconst resolvedSort = resolveIssueSort(\n\t\tprovidersMetadata[options.providerId]?.supportedAccountWideIssueSorts,\n\t\toptions.sort,\n\t);\n\tif (resolvedSort.rejection != null) {\n\t\treturn refused(\n\t\t\tunsupportedIssueSortWarning(options.providerId, domain, options.connectionId, resolvedSort.rejection),\n\t\t);\n\t}\n\n\t// `merged: true` unconditionally: this read has no scope count for a caller to reduce. It is also why no\n\t// `unmergeable` check follows — `supportedAccountWideIssueSorts` declares only keys a merge can honor.\n\tconst accountWideOrdering = toIssueOrdering(resolvedSort.sort, true);\n\n\t// The repo-scoped core rejects empty repos (GitHub/Bitbucket/Azure); read the account-wide,\n\t// already-user-scoped core instead. GitHub exposes a composite cursor across its authored,\n\t// assigned, and mentioned searches. Walk it internally when the caller supplies only page N.\n\tconst readAccountWidePage = (cursor: string | undefined) =>\n\t\trunCaptured(\n\t\t\toptions.providerId,\n\t\t\tdomain,\n\t\t\toptions.connectionId,\n\t\t\t() =>\n\t\t\t\tintegration.searchMyIssuesWithTruncationResult(undefined, undefined, options.connectionId, {\n\t\t\t\t\tincludeAllAssignees: options.includeAllAssignees,\n\t\t\t\t\tfilters: resolvedIssueFilters.filters,\n\t\t\t\t\tcursor: cursor,\n\t\t\t\t\tpageSize: options.itemsPerPage,\n\t\t\t\t\torg: options.org,\n\t\t\t\t\tproject: options.project,\n\t\t\t\t\tsort: accountWideOrdering.sort,\n\t\t\t\t}),\n\t\t\t{ warnOnMissingSession: warnOnMissingSession },\n\t\t);\n\tconst first = await readAccountWidePage(options.cursor);\n\tconst warnings = first.warning != null ? [first.warning] : [];\n\t// Merged across the walked pages, so a per-scope failure reported on an earlier page isn't lost by\n\t// paging past it — the one thing this read accumulates that the filtered search doesn't.\n\tlet allMetadata = first.value?.metadata;\n\tconst drained = await drainFlatPagesToRequestedPage(first, {\n\t\trequestedPage: page,\n\t\tsuppliedCursor: options.cursor,\n\t\twarnings: warnings,\n\t\treadPage: readAccountWidePage,\n\t\tfold: p => {\n\t\t\tallMetadata = mergeCollectionMetadata(allMetadata, p.metadata);\n\t\t},\n\t});\n\tconst { value, currentPage, requestedPageMissing } = drained;\n\tconst currentTruncated = drained.truncated;\n\tconst pageFetchFailed = drained.fetchFailed;\n\n\t// GitHub, GitLab, and Azure implement an account-wide issue search; a provider that doesn't (Bitbucket\n\t// exposes no issues at all, and `supportsIssues` already short-circuits it above) returns `undefined`\n\t// with no error. Surface that as an explicit unsupported warning + fetchFailed rather than a silent\n\t// empty success — the caller must fall back (e.g. broadenIssues over repos).\n\tif (value == null && warnings.length === 0) {\n\t\treturn refused(\n\t\t\tunsupportedWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`Account-wide issue search is not supported by '${options.providerId}'; scope the read to repositories instead.`,\n\t\t\t),\n\t\t);\n\t}\n\n\tconst items = requestedPageMissing ? [] : (value?.values ?? []);\n\t// Each query arrived ordered by the provider; their union is not, and the union is what this read publishes.\n\t// Idempotent for GitHub, whose aliased-search engine already ordered the same merge — one call here rather\n\t// than the same logic repeated in each of the three provider implementations.\n\tconst orderedItems = accountWideOrdering.order(items);\n\t// Fold in structured per-scope failures from the account-wide fan-out (e.g. Azure across projects):\n\t// scope-aware warnings + `fetchFailed` when a scope failed, without discarding the successful items.\n\tconst assessment = mergeAssessmentInto(warnings, options.providerId, domain, options.connectionId, allMetadata);\n\t// An account-wide search that couldn't confirm completeness (a provider cap with no cursor, or a\n\t// per-scope backstop/failure) is incomplete and can't be paged; report it as truncated (+ a\n\t// provider-neutral warning, unless a structured failure already explains it) rather than a complete\n\t// list. Don't hard-code GitHub's \"100 per category\" cap here — Azure reaches this via a per-project\n\t// backstop, and other providers may cap differently.\n\t// The account-wide read is cursor-only (its composite cursor spans several provider searches, so a\n\t// page number can't address it): `hasMore` without a real cursor is a dead end, so report it as\n\t// terminal-but-incomplete rather than inviting the caller to page forever.\n\tconst continuation = resolveContinuation(\n\t\t{\n\t\t\thasMore: requestedPageMissing ? false : (value?.hasMore ?? false),\n\t\t\tcursor: requestedPageMissing ? undefined : usableCursor(value?.cursor),\n\t\t\ttruncated: currentTruncated,\n\t\t},\n\t\tundefined,\n\t);\n\tconst truncated = continuation.truncated || assessment.truncated;\n\tif (truncated && warnings.length === 0) {\n\t\twarnings.push(\n\t\t\ttruncationWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'Account-wide issue search',\n\t\t\t\t// `exhausted`: this composite read exposes no budget the caller can raise, so nothing it\n\t\t\t\t// could call would return the withheld items.\n\t\t\t\tassessment.fetchFailed || pageFetchFailed ? 'interrupted' : 'exhausted',\n\t\t\t),\n\t\t);\n\t}\n\t// A metadata omission from an earlier page asserts the read succeeded; a later page may since have failed.\n\treconcileOmissionsWithFailure(warnings, assessment.fetchFailed || pageFetchFailed);\n\treturn {\n\t\titems: orderedItems,\n\t\twarnings: warnings,\n\t\tpage: {\n\t\t\t// Positional, per ProviderPageInfo.currentPage. `currentPage` already carries what the provider\n\t\t\t// reported or what the internal drain counted; a requested page past the terminal cursor is\n\t\t\t// reported as that empty page N. The account-wide read is cursor-only, so a `page` the caller\n\t\t\t// didn't pair with a cursor is never echoed.\n\t\t\tcurrentPage: requestedPageMissing\n\t\t\t\t? page\n\t\t\t\t: resolveCurrentPage({\n\t\t\t\t\t\tproviderPage: currentPage,\n\t\t\t\t\t\trequestedPage: page,\n\t\t\t\t\t\tsuppliedCursor: options.cursor,\n\t\t\t\t\t\tpageAdvanceable: false,\n\t\t\t\t\t}),\n\t\t\titemsPerPage: orderedItems.length,\n\t\t\ttruncated: truncated || undefined,\n\t\t},\n\t\thasMore: continuation.hasMore,\n\t\tcursor: continuation.cursor,\n\t\tfetchFailed: assessment.fetchFailed || pageFetchFailed || undefined,\n\t};\n}\n\n/**\n * The repository-scoped read: one provider query per repository or project, paged by the provider's own\n * continuation and drained internally when the caller supplied only a page number.\n *\n * See {@link readAccountWideIssuesPage} for why the two are separate functions.\n */\nasync function readRepoScopedIssuesPage({\n\toptions,\n\tintegration,\n\tdomain,\n\twarnOnMissingSession,\n\tpage,\n}: IssueReadContext): Promise<ProviderPagedResult<IssueShape>> {\n\tconst metadata = providersMetadata[options.providerId];\n\n\t// How many separate provider queries this page is assembled from, which decides whether the requested order can\n\t// be honored at all. `PagingMode.Repos` (GitHub) sends ONE search however many repositories are named, so its\n\t// page arrives ordered by the provider; `Repo` (GitLab) and `Project` (Azure) issue one query per scope and are\n\t// concatenated here, so a page spanning several of them is only as orderable as a normalized issue is.\n\tconst resolvedRepoScopedSort = resolveIssueSort(metadata?.supportedIssueSorts, options.sort);\n\tif (resolvedRepoScopedSort.rejection != null) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[\n\t\t\t\tunsupportedIssueSortWarning(\n\t\t\t\t\toptions.providerId,\n\t\t\t\t\tdomain,\n\t\t\t\t\toptions.connectionId,\n\t\t\t\t\tresolvedRepoScopedSort.rejection,\n\t\t\t\t),\n\t\t\t],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tconst issuesPagingMode = metadata?.issuesPagingMode;\n\tconst ordering = toIssueOrdering(\n\t\tresolvedRepoScopedSort.sort,\n\t\tmergesProviderQueries(issuesPagingMode, options.repos),\n\t);\n\t// Refused rather than served as concatenated per-scope runs, which would look ordered without being so — and\n\t// only where it actually merges, since the same key against the same provider is perfectly answerable for a\n\t// single repository or project.\n\tif (ordering.unmergeable != null) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[\n\t\t\t\tunmergeableIssueSortWarning(\n\t\t\t\t\toptions.providerId,\n\t\t\t\t\tdomain,\n\t\t\t\t\toptions.connectionId,\n\t\t\t\t\tordering.unmergeable,\n\t\t\t\t\tissuesPagingMode === PagingMode.Project ? 'projects' : 'repositories',\n\t\t\t\t),\n\t\t\t],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tconst cursor = options.cursor ?? pageToCursor(page);\n\tconst { value, warning } = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\t() =>\n\t\t\t// The shapes seam returns normalized IssueShape, and is overridable by a provider whose only issue\n\t\t\t// client already yields shapes (serving this path without a raw ProviderIssue round-trip).\n\t\t\tintegration.getMyIssuesForReposAsShapesResult(\n\t\t\t\toptions.repos ?? [],\n\t\t\t\t// Forward `page`/`pageSize` alongside the cursor so PagingMode.Repo/Project hosts honor the\n\t\t\t\t// requested page and page size rather than ignoring a synthesized page-number cursor.\n\t\t\t\t{\n\t\t\t\t\tfilters: options.filters,\n\t\t\t\t\tincludeAllAssignees: options.includeAllAssignees,\n\t\t\t\t\tcursor: cursor,\n\t\t\t\t\t// The normalized `page`, so the number forwarded to the provider can't disagree with the\n\t\t\t\t\t// page-number cursor beside it — both are built from the same value. Only when the caller asked\n\t\t\t\t\t// for one: an omitted page is the provider's own first page.\n\t\t\t\t\tpage: options.page != null ? page : undefined,\n\t\t\t\t\tpageSize: options.itemsPerPage,\n\t\t\t\t\tsort: ordering.sort,\n\t\t\t\t},\n\t\t\t\toptions.connectionId,\n\t\t\t),\n\t\t{ warnOnMissingSession: warnOnMissingSession },\n\t);\n\n\tlet items = value?.values ?? [];\n\tconst warnings = warning != null ? [warning] : [];\n\tlet pageFetchFailed = warning != null && value == null;\n\tlet paged = toProviderPageInfo(options.itemsPerPage ?? items.length, value?.paging);\n\tlet allMetadata = value?.metadata;\n\n\t// Cursor-only repo-scoped hosts (e.g. GitHub) ignore a synthesized page-number cursor, so a page-only\n\t// request is advanced through the provider's own continuations (see drainToRequestedPage).\n\tif (\n\t\tissuesPagingMode === PagingMode.Repos &&\n\t\toptions.page != null &&\n\t\tpage > 1 &&\n\t\toptions.cursor == null &&\n\t\tpaged.page.currentPage === 1\n\t) {\n\t\tconst drained = await drainToRequestedPage(\n\t\t\t{ items: items, paged: paged, metadata: allMetadata, fetchFailed: pageFetchFailed },\n\t\t\t{\n\t\t\t\trequestedPage: page,\n\t\t\t\titemsPerPage: options.itemsPerPage,\n\t\t\t\twarnings: warnings,\n\t\t\t\treadPage: (cursor: string) =>\n\t\t\t\t\trunCaptured(\n\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tintegration.getMyIssuesForReposAsShapesResult(\n\t\t\t\t\t\t\t\toptions.repos ?? [],\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tfilters: options.filters,\n\t\t\t\t\t\t\t\t\tincludeAllAssignees: options.includeAllAssignees,\n\t\t\t\t\t\t\t\t\tcursor: cursor,\n\t\t\t\t\t\t\t\t\tpageSize: options.itemsPerPage,\n\t\t\t\t\t\t\t\t\tsort: ordering.sort,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t{ warnOnMissingSession: warnOnMissingSession },\n\t\t\t\t\t),\n\t\t\t},\n\t\t);\n\t\titems = drained.items;\n\t\tpaged = drained.paged;\n\t\tallMetadata = drained.metadata;\n\t\tpageFetchFailed = drained.fetchFailed;\n\t}\n\n\t// Ordered once the drain (above) has assembled whichever pages this position spans.\n\titems = ordering.order(items);\n\n\t// Convert the SDK collection metadata into scope-aware warnings + failure/truncation flags, appending\n\t// them to any captured thrown-error warning without discarding the partial result's items.\n\tconst assessment = mergeAssessmentInto(warnings, options.providerId, domain, options.connectionId, allMetadata);\n\t// Never advertise `hasMore` without a continuation the caller can act on, and only synthesize a page\n\t// number for a host that reads it as one (see `isPageNumberAdvanceable`).\n\tconst issuesPageAdvanceable = isPageNumberAdvanceable(issuesPagingMode);\n\tconst continuation = resolveContinuation(paged, issuesPageAdvanceable ? paged.page.currentPage + 1 : undefined);\n\t// A provider read that couldn't confirm completeness (e.g. Bitbucket's single-page repo issue read\n\t// that dropped a repo) sets `paging.truncated`; surface it as a terminal `page.truncated` so a partial\n\t// page isn't published as complete. Metadata incompleteness is an independent source of the same signal.\n\tconst truncated = continuation.truncated || assessment.truncated;\n\tif (truncated && warnings.length === 0) {\n\t\twarnings.push(\n\t\t\ttruncationWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'Issue',\n\t\t\t\t// `exhausted`, never `page-budget`: a paged read has no budget the caller can raise, and\n\t\t\t\t// ordinary continuation is already expressed by `hasMore`/`cursor`.\n\t\t\t\tassessment.fetchFailed || pageFetchFailed ? 'interrupted' : 'exhausted',\n\t\t\t),\n\t\t);\n\t}\n\t// A metadata omission from an earlier page asserts the read succeeded; a later page may since have failed.\n\treconcileOmissionsWithFailure(warnings, assessment.fetchFailed || pageFetchFailed);\n\treturn {\n\t\titems: items,\n\t\twarnings: warnings,\n\t\tpage: {\n\t\t\t...paged.page,\n\t\t\t// Positional, per ProviderPageInfo.currentPage: the drain (above) already advanced `paged.page` for\n\t\t\t// a cursor-only host, and a caller-threaded cursor advanced the provider without one.\n\t\t\tcurrentPage: resolveCurrentPage({\n\t\t\t\tproviderPage: paged.page.currentPage,\n\t\t\t\trequestedPage: page,\n\t\t\t\tsuppliedCursor: options.cursor,\n\t\t\t\tpageAdvanceable: issuesPageAdvanceable,\n\t\t\t}),\n\t\t\ttruncated: truncated || undefined,\n\t\t},\n\t\thasMore: continuation.hasMore,\n\t\tcursor: continuation.cursor,\n\t\tfetchFailed: assessment.fetchFailed || pageFetchFailed || undefined,\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyDA,SAAS,sBAAsB,YAAoC,OAAgD;CAClH,MAAM,SAAS,SAAS,CAAC;CACzB,QAAQ,YAAR;EACC,KAAA,QAGC,OAAO,eAAe,MAAM,IAAI,OAAO,SAAS,IAAI,OAAO,SAAS;EACrE,KAAA,WAEC,OAAO,IAAI,IAAI,OAAO,KAAI,MAAM,OAAO,MAAM,WAAW,EAAE,UAAU,KAAA,CAAU,CAAC,CAAC,CAAC,OAAO;EACzF,SACC,OAAO;CACT;AACD;AAwEA,eAAsB,eACrB,KACA,SAC2C;CAG3C,MAAM,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,QAAQ,CAAC,CAAC;CACtD,IAAI,0BAA0B,QAAQ,UAAU,GAC/C,OAAO,YACN,MACA,CAAC,0BAA0B,QAAQ,YAAY,KAAA,GAAW,QAAQ,cAAc,wBAAwB,CAAC,GACzG,IACD;CAGD,MAAM,cAAc,MAAM,IAAI,sBAAsB,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;CAC5G,IAAI,eAAe,MAAM;EAGxB,MAAM,QAAQ,IAAI,8BAA8B,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;EACxG,OAAO,YAAY,MAAM,MAAM,UAAU,MAAM,WAAW;CAC3D;CACA,IAAI,CAAC,qBAAqB,WAAW,GACpC,OAAO,YACN,MACA,CAAC,0BAA0B,QAAQ,YAAY,KAAA,GAAW,QAAQ,cAAc,wBAAwB,CAAC,GACzG,IACD;CAGD,MAAM,IAAI,wBAAwB,aAAa,QAAQ,WAAW,QAAQ,YAAY;CAEtF,MAAM,SAAS,IAAI,cAAc,aAAa,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;CACtG,MAAM,uBAAuB,8BAA8B,QAAQ,YAAY,QAAQ,MAAM;CAI7F,IAAI,CAAC,YAAY,gBAChB,OAAO,YAAY,MAAM,CAAC,yBAAyB,QAAQ,YAAY,QAAQ,QAAQ,YAAY,CAAC,GAAG,IAAI;CAM5G,MAAM,UAA4B;EACxB;EACI;EACL;EACc;EAChB;CACP;CACA,QAAQ,QAAQ,OAAO,UAAU,OAAO,IAAI,0BAA0B,OAAO,IAAI,yBAAyB,OAAO;AAClH;;;;;;;;;;;AAYA,eAAe,0BAA0B,EACxC,SACA,aACA,QACA,sBACA,QAC8D;CAG9D,MAAM,WAAW,YAA6B,YAAwB,GAAG,CAAC,OAAO,GAAG,IAAI;CAMxF,IAAI,QAAQ,SAAS,UAAU,QAAQ,wBAAwB,MAC9D,OAAO,QACN,aACC,QAAQ,YACR,QACA,QAAQ,cACR,sGACD,CACD;CAQD,IACC,QAAQ,wBAAwB,SAC/B,QAAQ,eAAA,YACR,QAAQ,eAAA,4BAET,OAAO,QACN,mBACC,QAAQ,YACR,QACA,QAAQ,cACR,qHACD,CACD;CAOD,KAAK,QAAQ,OAAO,QAAQ,QAAQ,WAAW,SAAS,CAAC,YAAY,0BACpE,OAAO,QACN,mBACC,QAAQ,YACR,QACA,QAAQ,cACR,oDAAoD,QAAQ,WAAW,2CACxE,CACD;CAMD,MAAM,uBAAuB,+BAA+B,QAAQ,YAAY,QAAQ,OAAO;CAC/F,IAAI,qBAAqB,aACxB,OAAO,QACN,0CACC,QAAQ,YACR,QACA,QAAQ,cACR,QAAQ,OACT,CACD;CAOD,MAAM,eAAe,iBACpB,kBAAkB,QAAQ,WAAW,EAAE,gCACvC,QAAQ,IACT;CACA,IAAI,aAAa,aAAa,MAC7B,OAAO,QACN,4BAA4B,QAAQ,YAAY,QAAQ,QAAQ,cAAc,aAAa,SAAS,CACrG;CAKD,MAAM,sBAAsB,gBAAgB,aAAa,MAAM,IAAI;CAKnE,MAAM,uBAAuB,WAC5B,YACC,QAAQ,YACR,QACA,QAAQ,oBAEP,YAAY,mCAAmC,KAAA,GAAW,KAAA,GAAW,QAAQ,cAAc;EAC1F,qBAAqB,QAAQ;EAC7B,SAAS,qBAAqB;EACtB;EACR,UAAU,QAAQ;EAClB,KAAK,QAAQ;EACb,SAAS,QAAQ;EACjB,MAAM,oBAAoB;CAC3B,CAAC,GACF,EAAwB,qBAAqB,CAC9C;CACD,MAAM,QAAQ,MAAM,oBAAoB,QAAQ,MAAM;CACtD,MAAM,WAAW,MAAM,WAAW,OAAO,CAAC,MAAM,OAAO,IAAI,CAAC;CAG5D,IAAI,cAAc,MAAM,OAAO;CAC/B,MAAM,UAAU,MAAM,8BAA8B,OAAO;EAC1D,eAAe;EACf,gBAAgB,QAAQ;EACd;EACV,UAAU;EACV,OAAM,MAAK;GACV,cAAc,wBAAwB,aAAa,EAAE,QAAQ;EAC9D;CACD,CAAC;CACD,MAAM,EAAE,OAAO,aAAa,yBAAyB;CACrD,MAAM,mBAAmB,QAAQ;CACjC,MAAM,kBAAkB,QAAQ;CAMhC,IAAI,SAAS,QAAQ,SAAS,WAAW,GACxC,OAAO,QACN,mBACC,QAAQ,YACR,QACA,QAAQ,cACR,kDAAkD,QAAQ,WAAW,2CACtE,CACD;CAGD,MAAM,QAAQ,uBAAuB,CAAC,IAAK,OAAO,UAAU,CAAC;CAI7D,MAAM,eAAe,oBAAoB,MAAM,KAAK;CAGpD,MAAM,aAAa,oBAAoB,UAAU,QAAQ,YAAY,QAAQ,QAAQ,cAAc,WAAW;CAS9G,MAAM,eAAe,oBACpB;EACC,SAAS,uBAAuB,QAAS,OAAO,WAAW;EAC3D,QAAQ,uBAAuB,KAAA,IAAY,aAAa,OAAO,MAAM;EACrE,WAAW;CACZ,GACA,KAAA,CACD;CACA,MAAM,YAAY,aAAa,aAAa,WAAW;CACvD,IAAI,aAAa,SAAS,WAAW,GACpC,SAAS,KACR,kBACC,QAAQ,YACR,QACA,QAAQ,cACR,6BAGA,WAAW,eAAe,kBAAkB,gBAAgB,WAC7D,CACD;CAGD,8BAA8B,UAAU,WAAW,eAAe,eAAe;CACjF,OAAO;EACN,OAAO;EACG;EACV,MAAM;GAKL,aAAa,uBACV,OACA,mBAAmB;IACnB,cAAc;IACd,eAAe;IACf,gBAAgB,QAAQ;IACxB,iBAAiB;GAClB,CAAC;GACH,cAAc,aAAa;GAC3B,WAAW,aAAa,KAAA;EACzB;EACA,SAAS,aAAa;EACtB,QAAQ,aAAa;EACrB,aAAa,WAAW,eAAe,mBAAmB,KAAA;CAC3D;AACD;;;;;;;AAQA,eAAe,yBAAyB,EACvC,SACA,aACA,QACA,sBACA,QAC8D;CAC9D,MAAM,WAAW,kBAAkB,QAAQ;CAM3C,MAAM,yBAAyB,iBAAiB,UAAU,qBAAqB,QAAQ,IAAI;CAC3F,IAAI,uBAAuB,aAAa,MACvC,OAAO,YACN,MACA,CACC,4BACC,QAAQ,YACR,QACA,QAAQ,cACR,uBAAuB,SACxB,CACD,GACA,IACD;CAGD,MAAM,mBAAmB,UAAU;CACnC,MAAM,WAAW,gBAChB,uBAAuB,MACvB,sBAAsB,kBAAkB,QAAQ,KAAK,CACtD;CAIA,IAAI,SAAS,eAAe,MAC3B,OAAO,YACN,MACA,CACC,4BACC,QAAQ,YACR,QACA,QAAQ,cACR,SAAS,aACT,qBAAA,YAA0C,aAAa,cACxD,CACD,GACA,IACD;CAGD,MAAM,SAAS,QAAQ,UAAU,aAAa,IAAI;CAClD,MAAM,EAAE,OAAO,YAAY,MAAM,YAChC,QAAQ,YACR,QACA,QAAQ,oBAIP,YAAY,kCACX,QAAQ,SAAS,CAAC,GAGlB;EACC,SAAS,QAAQ;EACjB,qBAAqB,QAAQ;EACrB;EAIR,MAAM,QAAQ,QAAQ,OAAO,OAAO,KAAA;EACpC,UAAU,QAAQ;EAClB,MAAM,SAAS;CAChB,GACA,QAAQ,YACT,GACD,EAAwB,qBAAqB,CAC9C;CAEA,IAAI,QAAQ,OAAO,UAAU,CAAC;CAC9B,MAAM,WAAW,WAAW,OAAO,CAAC,OAAO,IAAI,CAAC;CAChD,IAAI,kBAAkB,WAAW,QAAQ,SAAS;CAClD,IAAI,QAAQ,mBAAmB,QAAQ,gBAAgB,MAAM,QAAQ,OAAO,MAAM;CAClF,IAAI,cAAc,OAAO;CAIzB,IACC,qBAAA,WACA,QAAQ,QAAQ,QAChB,OAAO,KACP,QAAQ,UAAU,QAClB,MAAM,KAAK,gBAAgB,GAC1B;EACD,MAAM,UAAU,MAAM,qBACrB;GAAS;GAAc;GAAO,UAAU;GAAa,aAAa;EAAgB,GAClF;GACC,eAAe;GACf,cAAc,QAAQ;GACZ;GACV,WAAW,WACV,YACC,QAAQ,YACR,QACA,QAAQ,oBAEP,YAAY,kCACX,QAAQ,SAAS,CAAC,GAClB;IACC,SAAS,QAAQ;IACjB,qBAAqB,QAAQ;IACrB;IACR,UAAU,QAAQ;IAClB,MAAM,SAAS;GAChB,GACA,QAAQ,YACT,GACD,EAAwB,qBAAqB,CAC9C;EACF,CACD;EACA,QAAQ,QAAQ;EAChB,QAAQ,QAAQ;EAChB,cAAc,QAAQ;EACtB,kBAAkB,QAAQ;CAC3B;CAGA,QAAQ,SAAS,MAAM,KAAK;CAI5B,MAAM,aAAa,oBAAoB,UAAU,QAAQ,YAAY,QAAQ,QAAQ,cAAc,WAAW;CAG9G,MAAM,wBAAwB,wBAAwB,gBAAgB;CACtE,MAAM,eAAe,oBAAoB,OAAO,wBAAwB,MAAM,KAAK,cAAc,IAAI,KAAA,CAAS;CAI9G,MAAM,YAAY,aAAa,aAAa,WAAW;CACvD,IAAI,aAAa,SAAS,WAAW,GACpC,SAAS,KACR,kBACC,QAAQ,YACR,QACA,QAAQ,cACR,SAGA,WAAW,eAAe,kBAAkB,gBAAgB,WAC7D,CACD;CAGD,8BAA8B,UAAU,WAAW,eAAe,eAAe;CACjF,OAAO;EACC;EACG;EACV,MAAM;GACL,GAAG,MAAM;GAGT,aAAa,mBAAmB;IAC/B,cAAc,MAAM,KAAK;IACzB,eAAe;IACf,gBAAgB,QAAQ;IACxB,iBAAiB;GAClB,CAAC;GACD,WAAW,aAAa,KAAA;EACzB;EACA,SAAS,aAAa;EACtB,QAAQ,aAAa;EACrB,aAAa,WAAW,eAAe,mBAAmB,KAAA;CAC3D;AACD"}
1
+ {"version":3,"file":"issues.js","names":[],"sources":["../../../../../plus/integrations/src/reads/issues.ts"],"sourcesContent":["import type { IssueShape, IssueSorting, IssueStateFilter } from '@gitlens/git/models/issue.js';\nimport { mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { GitCloudHostIntegrationId, GitSelfManagedHostIntegrationId } from '../constants.js';\nimport type { GitHostIntegration } from '../models/gitHostIntegration.js';\nimport type { IssueFilter, ProviderReposInput } from '../providers/models.js';\nimport { isRepoIdsInput, PagingMode, providersMetadata } from '../providers/models.js';\nimport { mergeCollectionMetadata } from '../providers/utils/providerPaging.js';\nimport type { ProviderPagedResult, ProviderWarning } from '../results.js';\nimport { reconcileOmissionsWithFailure } from '../results.js';\nimport {\n\tisGitHostIntegration,\n\tisIssuesHostIntegrationId,\n\twarnOnMissingSessionForDomain,\n} from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { runCaptured } from './drains.js';\nimport { resolveAccountWideIssueFilters } from './filters.js';\nimport { resolveIssueSort, toIssueOrdering } from './ordering.js';\nimport {\n\tdrainFlatPagesToRequestedPage,\n\tdrainToRequestedPage,\n\tisPageNumberAdvanceable,\n\tpageToCursor,\n\trefusedPage,\n\tresolveContinuation,\n\tresolveCurrentPage,\n\ttoProviderPageInfo,\n\tusableCursor,\n} from './paging.js';\nimport {\n\tgitHostOnlySurfaceWarning,\n\tissuesUnsupportedWarning,\n\totherWarning,\n\ttruncationWarning,\n\tunmergeableIssueSortWarning,\n\tunsupportedAccountWideIssueFiltersWarning,\n\tunsupportedIssueSortWarning,\n\tunsupportedWarning,\n} from './warnings.js';\n\nconst issueStateFilters: readonly IssueStateFilter[] = ['open', 'closed', 'all'];\n\n/**\n * Whether a repo-scoped page is assembled from SEVERAL provider queries and merged here — which is what decides\n * whether the requested order can be honored at all.\n *\n * `PagingMode.Repos` (GitHub) sends one search however many repositories are named, so its page arrives ordered by\n * the provider. `Repo` (GitLab) issues one query per repository, and `Project` (Azure) one per PROJECT — several\n * repositories of the same project are still one query, which is why that case counts distinct projects rather than\n * repositories.\n *\n * The `Repo` case has a second way to merge that has nothing to do with the count: given repository IDS rather than\n * descriptors, `getMyIssuesForReposResult` skips its per-repository fan-out entirely (that branch is guarded on\n * `!isRepoIdsInput`) and calls the SDK's `getIssuesForRepos`, which for GitLab is the multi-project aggregate that\n * merges in the SDK and refuses `priority`/`dueDate` however few scopes it was given. Treating that as merged is\n * what makes this facade refuse it up front, with the message that names the reason, instead of letting the SDK\n * reject a read the capability table had just promised.\n */\nfunction mergesProviderQueries(pagingMode: PagingMode | undefined, repos: ProviderReposInput | undefined): boolean {\n\tconst scopes = repos ?? [];\n\tswitch (pagingMode) {\n\t\tcase PagingMode.Repo:\n\t\t\t// `isRepoIdsInput` is the same predicate `ProvidersApi` selects the SDK aggregate with, so this cannot\n\t\t\t// disagree with the branch it is predicting.\n\t\t\treturn isRepoIdsInput(scopes) ? scopes.length > 0 : scopes.length > 1;\n\t\tcase PagingMode.Project:\n\t\t\t// Only the descriptor form carries a project; the id form is refused by the Azure read before it runs.\n\t\t\treturn new Set(scopes.map(r => (typeof r === 'object' ? r.project : undefined))).size > 1;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\n/**\n * What {@link listIssuesPage} accepts, shared by name with the two functions it dispatches to.\n *\n * Named rather than left inline: three functions now take this exact shape, and reaching it from the other two as\n * `Parameters<typeof listIssuesPage>[1]` would define the contract in terms of the function that consumes it —\n * inside out, and unreadable at the point where the fields are actually used.\n */\nexport interface ListIssuesPageOptions {\n\tstate?: IssueStateFilter;\n\tproviderId: IntegrationIds;\n\trepos?: ProviderReposInput;\n\t/** Narrows the account-wide read to one org/account. Requires a host with a project layer (Azure). */\n\torg?: string;\n\t/** Narrows the account-wide read to one project. Requires a host with a project layer (Azure). */\n\tproject?: string;\n\t/**\n\t * Narrows to the requested relationship(s). On the account-wide path this replaces the provider's own\n\t * definition of \"my issues\" (GitHub/GHE: authored ∪ assigned ∪ mentioned; Azure: assigned ∪ authored;\n\t * GitLab: assigned-to-me), so `[Assignee]` yields `assignee:@me` everywhere it's expressible. A set the\n\t * provider can't express server-side is refused whole (warning + `fetchFailed`), never widened — check\n\t * getSupportedFilters first to avoid that path.\n\t */\n\tfilters?: IssueFilter[];\n\t/** Broadens the read to every assignee. Contradicts `filters`; passing both is refused. */\n\tincludeAllAssignees?: boolean;\n\t/**\n\t * How to order the page, as `field:direction`. Omitted orders most-recently-updated-first wherever the\n\t * provider can express that, which is this facade's default rather than the provider's own.\n\t *\n\t * Validated against `getSupportedFilters().issueSorts` on the repo-scoped path and `.issueSortsAccountWide`\n\t * on the account-wide one — two genuinely different vocabularies for GitLab, whose two reads are different\n\t * APIs. A key the provider can't express refuses the read (warning + `fetchFailed`) rather than serving a\n\t * differently-ordered list, for the same reason an inexpressible filter does: the reachable window is\n\t * bounded, so another order is another subset.\n\t *\n\t * On the repo-scoped path with SEVERAL scopes the page is a merge of one query per repository/project, so\n\t * only a key a normalized issue carries can be honored: `priority`, `dueDate` and `resolved` are refused\n\t * there even where the provider supports them on a single scope. Order is per page, and per scope across\n\t * pages — the merge orders what a page contains, not the sequence of pages.\n\t */\n\tsort?: IssueSorting;\n\tpage?: number;\n\tcursor?: string;\n\titemsPerPage?: number;\n\tforceSync?: boolean;\n\tconnectionId?: string;\n\t/**\n\t * Explicit self-managed host domain. Used only when the requested connection has no configured domain;\n\t * it must come from the trusted authentication configuration, not repository or remote data.\n\t */\n\tdomain?: string;\n}\n\n/**\n * What both halves of {@link listIssuesPage} need from its shared prologue.\n *\n * A bundle rather than six parameters because the two reads take exactly the same inputs and differ only in what\n * they do with them; threading them positionally would make the two call sites read as two different reads of two\n * different things, which is the opposite of what is true.\n *\n * Note `ctx` is absent: every use of it — resolving the integration, the domain, the forced refresh, the\n * connection warnings — is in the prologue, which is what makes this the right cut.\n */\ninterface IssueReadContext {\n\toptions: ListIssuesPageOptions;\n\tintegration: GitHostIntegration;\n\tdomain: string | undefined;\n\twarnOnMissingSession: boolean;\n\tpage: number;\n}\n\nexport async function listIssuesPage(\n\tctx: ProviderReadContext,\n\toptions: ListIssuesPageOptions,\n): Promise<ProviderPagedResult<IssueShape>> {\n\t// Truncated as well as floored, like every other paged read here: `page` is reported back as\n\t// `page.currentPage` and compared against the position a drain reached, and neither can be fractional.\n\tconst page = Math.max(1, Math.trunc(options.page ?? 1));\n\tif (isIssuesHostIntegrationId(options.providerId)) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, 'repository issue reads')],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\t// A supplied connection or domain that no longer resolves is a broken target, not an empty account —\n\t\t// surface a no-connection warning + fetchFailed rather than a silent empty page.\n\t\tconst early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);\n\t\treturn refusedPage(page, early.warnings, early.fetchFailed);\n\t}\n\tif (!isGitHostIntegration(integration)) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, 'repository issue reads')],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tawait ctx.forceRefreshIfRequested(integration, options.forceSync, options.connectionId);\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\tconst warnOnMissingSession = warnOnMissingSessionForDomain(options.providerId, options.domain);\n\n\t// A git host whose issue tracker is deprecated (Bitbucket, superseded by dedicated issue integrations)\n\t// reports issues as explicitly unsupported rather than serving a partial/legacy source or a silent empty.\n\tif (!integration.supportsIssues) {\n\t\treturn refusedPage(page, [issuesUnsupportedWarning(options.providerId, domain, options.connectionId)], true);\n\t}\n\n\tif (\n\t\toptions.state != null &&\n\t\t(!issueStateFilters.includes(options.state) ||\n\t\t\t(options.state !== 'open' && !providersMetadata[options.providerId]?.supportsIssueStates))\n\t) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[\n\t\t\t\tissueStateFilters.includes(options.state)\n\t\t\t\t\t? unsupportedWarning(\n\t\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\t\tdomain,\n\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t\t`Issue state '${options.state}' is not supported by '${options.providerId}'.`,\n\t\t\t\t\t\t)\n\t\t\t\t\t: otherWarning(\n\t\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\t\tdomain,\n\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t\t`Unknown issue state; expected one of ${issueStateFilters.join(', ')}.`,\n\t\t\t\t\t\t),\n\t\t\t],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\t// `repos` is what selects between the two: with none there is no scope to query per repository, so the\n\t// provider's own account-wide search answers instead. They are different provider queries with different\n\t// capability tables, cursors and completeness signals — see each function.\n\tconst context: IssueReadContext = {\n\t\toptions: options,\n\t\tintegration: integration,\n\t\tdomain: domain,\n\t\twarnOnMissingSession: warnOnMissingSession,\n\t\tpage: page,\n\t};\n\treturn (options.repos?.length ?? 0) === 0 ? readAccountWideIssuesPage(context) : readRepoScopedIssuesPage(context);\n}\n\n/**\n * The account-wide read: the provider's own definition of \"my issues\", behind a composite cursor that spans\n * several provider searches.\n *\n * Its own function rather than a branch of the paged read, because almost nothing is shared past the guards: it\n * validates a different filter table and a different sort table, it is cursor-only (a page number cannot address\n * a composite cursor), it accumulates per-scope failures across the pages it walks, and it always merges several\n * queries. The two used to share one 460-line function behind a mode flag, and the comments explaining how far\n * apart they are were the clue that they wanted separating.\n */\nasync function readAccountWideIssuesPage({\n\toptions,\n\tintegration,\n\tdomain,\n\twarnOnMissingSession,\n\tpage,\n}: IssueReadContext): Promise<ProviderPagedResult<IssueShape>> {\n\t// The account-wide read is cursor-only, so a refusal can't claim the requested position — it reports\n\t// page 1, per ProviderPageInfo.currentPage.\n\tconst refused = (warning: ProviderWarning) => refusedPage<IssueShape>(1, [warning], true);\n\n\t// Checked before the provider-specific guards below: a caller passing both has a contradictory request\n\t// whatever the provider, and saying so is more useful than reporting one half of it as unsupported.\n\t// `filters` narrows this read to a relationship (`[Assignee]` ⇒ just assigned-to-me); `includeAllAssignees`\n\t// broadens it to every assignee. Honoring either silently would answer a question the caller didn't ask.\n\tif (options.filters?.length && options.includeAllAssignees === true) {\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'`filters` and `includeAllAssignees` are contradictory for an account-wide issue read; pass only one.',\n\t\t\t),\n\t\t);\n\t}\n\n\t// GitHub expresses \"any assignee\" as `assignee:*`, which is meaningless without a scope: unscoped it\n\t// matches millions of issues across all of GitHub instead of the user's own world. ANY scope makes it\n\t// meaningful (one repository, several, or an org) — it is specifically THIS branch, the account-wide read,\n\t// that has none to offer. Scope the read to repositories (or use `searchIssuesPage`, whose criteria model\n\t// takes `any-assignee` alongside a `repos`/`org` scope) rather than reading unscoped.\n\tif (\n\t\toptions.includeAllAssignees === true &&\n\t\t(options.providerId === GitCloudHostIntegrationId.GitHub ||\n\t\t\toptions.providerId === GitSelfManagedHostIntegrationId.CloudGitHubEnterprise)\n\t) {\n\t\treturn refused(\n\t\t\tunsupportedWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'`includeAllAssignees` is not supported for account-wide GitHub issue reads; scope the read to repositories instead.',\n\t\t\t),\n\t\t);\n\t}\n\n\t// Only a host with a project layer can narrow server-side. Reject the request rather than serving an\n\t// unscoped list as if it had been narrowed: the caller would otherwise have to filter client-side,\n\t// which desynchronizes the filtered `items` from this read's `hasMore`/`currentPage` and shows\n\t// \"no issues\" for a page that simply held none of the requested project's.\n\tif ((options.org != null || options.project != null) && !integration.supportsProjectDiscovery) {\n\t\treturn refused(\n\t\t\tunsupportedWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`Project-scoped issue reads are not supported by '${options.providerId}'; scope the read to repositories instead.`,\n\t\t\t),\n\t\t);\n\t}\n\n\t// Narrowing the account-wide read is only honest when the provider can express it server-side: its\n\t// per-relationship queries produced the page and the cursor together, so dropping items afterward would\n\t// leave `items` describing a different result set than `hasMore`/`currentPage`.\n\tconst resolvedIssueFilters = resolveAccountWideIssueFilters(options.providerId, options.filters);\n\tif (resolvedIssueFilters.unsupported) {\n\t\treturn refused(\n\t\t\tunsupportedAccountWideIssueFiltersWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\toptions.filters!,\n\t\t\t),\n\t\t);\n\t}\n\n\t// Every provider's account-wide read is a UNION of several queries — GitHub's three `@me` searches,\n\t// GitLab's one REST call per relationship, Azure's (project x relationship) drains — so it always merges,\n\t// and `supportedAccountWideIssueSorts` already lists only keys a merge can honor. No separate\n\t// mergeability refusal is needed here, unlike on the repo-scoped path below.\n\tconst resolvedSort = resolveIssueSort(\n\t\tprovidersMetadata[options.providerId]?.supportedAccountWideIssueSorts,\n\t\toptions.sort,\n\t);\n\tif (resolvedSort.rejection != null) {\n\t\treturn refused(\n\t\t\tunsupportedIssueSortWarning(options.providerId, domain, options.connectionId, resolvedSort.rejection),\n\t\t);\n\t}\n\n\t// `merged: true` unconditionally: this read has no scope count for a caller to reduce. It is also why no\n\t// `unmergeable` check follows — `supportedAccountWideIssueSorts` declares only keys a merge can honor.\n\tconst accountWideOrdering = toIssueOrdering(resolvedSort.sort, true);\n\n\t// The repo-scoped core rejects empty repos (GitHub/Bitbucket/Azure); read the account-wide,\n\t// already-user-scoped core instead. GitHub exposes a composite cursor across its authored,\n\t// assigned, and mentioned searches. Walk it internally when the caller supplies only page N.\n\tconst readAccountWidePage = (cursor: string | undefined) =>\n\t\trunCaptured(\n\t\t\toptions.providerId,\n\t\t\tdomain,\n\t\t\toptions.connectionId,\n\t\t\t() =>\n\t\t\t\tintegration.searchMyIssuesWithTruncationResult(undefined, undefined, options.connectionId, {\n\t\t\t\t\tincludeAllAssignees: options.includeAllAssignees,\n\t\t\t\t\tstate: options.state,\n\t\t\t\t\tfilters: resolvedIssueFilters.filters,\n\t\t\t\t\tcursor: cursor,\n\t\t\t\t\tpageSize: options.itemsPerPage,\n\t\t\t\t\torg: options.org,\n\t\t\t\t\tproject: options.project,\n\t\t\t\t\tsort: accountWideOrdering.sort,\n\t\t\t\t}),\n\t\t\t{ warnOnMissingSession: warnOnMissingSession },\n\t\t);\n\tconst first = await readAccountWidePage(options.cursor);\n\tconst warnings = first.warning != null ? [first.warning] : [];\n\t// Merged across the walked pages, so a per-scope failure reported on an earlier page isn't lost by\n\t// paging past it — the one thing this read accumulates that the filtered search doesn't.\n\tlet allMetadata = first.value?.metadata;\n\tconst drained = await drainFlatPagesToRequestedPage(first, {\n\t\trequestedPage: page,\n\t\tsuppliedCursor: options.cursor,\n\t\twarnings: warnings,\n\t\treadPage: readAccountWidePage,\n\t\tfold: p => {\n\t\t\tallMetadata = mergeCollectionMetadata(allMetadata, p.metadata);\n\t\t},\n\t});\n\tconst { value, currentPage, requestedPageMissing } = drained;\n\tconst currentTruncated = drained.truncated;\n\tconst pageFetchFailed = drained.fetchFailed;\n\n\t// GitHub, GitLab, and Azure implement an account-wide issue search; a provider that doesn't (Bitbucket\n\t// exposes no issues at all, and `supportsIssues` already short-circuits it above) returns `undefined`\n\t// with no error. Surface that as an explicit unsupported warning + fetchFailed rather than a silent\n\t// empty success — the caller must fall back (e.g. broadenIssues over repos).\n\tif (value == null && warnings.length === 0) {\n\t\treturn refused(\n\t\t\tunsupportedWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`Account-wide issue search is not supported by '${options.providerId}'; scope the read to repositories instead.`,\n\t\t\t),\n\t\t);\n\t}\n\n\tconst items = requestedPageMissing ? [] : (value?.values ?? []);\n\t// Each query arrived ordered by the provider; their union is not, and the union is what this read publishes.\n\t// Idempotent for GitHub, whose aliased-search engine already ordered the same merge — one call here rather\n\t// than the same logic repeated in each of the three provider implementations.\n\tconst orderedItems = accountWideOrdering.order(items);\n\t// Fold in structured per-scope failures from the account-wide fan-out (e.g. Azure across projects):\n\t// scope-aware warnings + `fetchFailed` when a scope failed, without discarding the successful items.\n\tconst assessment = mergeAssessmentInto(warnings, options.providerId, domain, options.connectionId, allMetadata);\n\t// An account-wide search that couldn't confirm completeness (a provider cap with no cursor, or a\n\t// per-scope backstop/failure) is incomplete and can't be paged; report it as truncated (+ a\n\t// provider-neutral warning, unless a structured failure already explains it) rather than a complete\n\t// list. Don't hard-code GitHub's \"100 per category\" cap here — Azure reaches this via a per-project\n\t// backstop, and other providers may cap differently.\n\t// The account-wide read is cursor-only (its composite cursor spans several provider searches, so a\n\t// page number can't address it): `hasMore` without a real cursor is a dead end, so report it as\n\t// terminal-but-incomplete rather than inviting the caller to page forever.\n\tconst continuation = resolveContinuation(\n\t\t{\n\t\t\thasMore: requestedPageMissing ? false : (value?.hasMore ?? false),\n\t\t\tcursor: requestedPageMissing ? undefined : usableCursor(value?.cursor),\n\t\t\ttruncated: currentTruncated,\n\t\t},\n\t\tundefined,\n\t);\n\tconst truncated = continuation.truncated || assessment.truncated;\n\tif (truncated && warnings.length === 0) {\n\t\twarnings.push(\n\t\t\ttruncationWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'Account-wide issue search',\n\t\t\t\t// `exhausted`: this composite read exposes no budget the caller can raise, so nothing it\n\t\t\t\t// could call would return the withheld items.\n\t\t\t\tassessment.fetchFailed || pageFetchFailed ? 'interrupted' : 'exhausted',\n\t\t\t),\n\t\t);\n\t}\n\t// A metadata omission from an earlier page asserts the read succeeded; a later page may since have failed.\n\treconcileOmissionsWithFailure(warnings, assessment.fetchFailed || pageFetchFailed);\n\treturn {\n\t\titems: orderedItems,\n\t\twarnings: warnings,\n\t\tpage: {\n\t\t\t// Positional, per ProviderPageInfo.currentPage. `currentPage` already carries what the provider\n\t\t\t// reported or what the internal drain counted; a requested page past the terminal cursor is\n\t\t\t// reported as that empty page N. The account-wide read is cursor-only, so a `page` the caller\n\t\t\t// didn't pair with a cursor is never echoed.\n\t\t\tcurrentPage: requestedPageMissing\n\t\t\t\t? page\n\t\t\t\t: resolveCurrentPage({\n\t\t\t\t\t\tproviderPage: currentPage,\n\t\t\t\t\t\trequestedPage: page,\n\t\t\t\t\t\tsuppliedCursor: options.cursor,\n\t\t\t\t\t\tpageAdvanceable: false,\n\t\t\t\t\t}),\n\t\t\titemsPerPage: orderedItems.length,\n\t\t\ttruncated: truncated || undefined,\n\t\t},\n\t\thasMore: continuation.hasMore,\n\t\tcursor: continuation.cursor,\n\t\tfetchFailed: assessment.fetchFailed || pageFetchFailed || undefined,\n\t};\n}\n\n/**\n * The repository-scoped read: one provider query per repository or project, paged by the provider's own\n * continuation and drained internally when the caller supplied only a page number.\n *\n * See {@link readAccountWideIssuesPage} for why the two are separate functions.\n */\nasync function readRepoScopedIssuesPage({\n\toptions,\n\tintegration,\n\tdomain,\n\twarnOnMissingSession,\n\tpage,\n}: IssueReadContext): Promise<ProviderPagedResult<IssueShape>> {\n\tconst metadata = providersMetadata[options.providerId];\n\n\t// How many separate provider queries this page is assembled from, which decides whether the requested order can\n\t// be honored at all. `PagingMode.Repos` (GitHub) sends ONE search however many repositories are named, so its\n\t// page arrives ordered by the provider; `Repo` (GitLab) and `Project` (Azure) issue one query per scope and are\n\t// concatenated here, so a page spanning several of them is only as orderable as a normalized issue is.\n\tconst resolvedRepoScopedSort = resolveIssueSort(metadata?.supportedIssueSorts, options.sort);\n\tif (resolvedRepoScopedSort.rejection != null) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[\n\t\t\t\tunsupportedIssueSortWarning(\n\t\t\t\t\toptions.providerId,\n\t\t\t\t\tdomain,\n\t\t\t\t\toptions.connectionId,\n\t\t\t\t\tresolvedRepoScopedSort.rejection,\n\t\t\t\t),\n\t\t\t],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tconst issuesPagingMode = metadata?.issuesPagingMode;\n\t// Counted, and read, as the host settles them: a scope can name another project than the one it was parsed with\n\t// (see `GitHostIntegration.resolveRepositoryScopes`), which changes how many queries a per-project read makes.\n\tlet repos = options.repos ?? [];\n\tif (issuesPagingMode === PagingMode.Project && repos.length) {\n\t\tconst settled = await runCaptured(\n\t\t\toptions.providerId,\n\t\t\tdomain,\n\t\t\toptions.connectionId,\n\t\t\t() => integration.resolveRepositoryScopes(repos, options.connectionId),\n\t\t\t{ warnOnMissingSession: warnOnMissingSession },\n\t\t);\n\t\t// No session to read with, or the read's own failure resolving it: answered as that read would, without it.\n\t\tif (settled.value == null) {\n\t\t\treturn refusedPage(page, settled.warning != null ? [settled.warning] : [], settled.warning != null);\n\t\t}\n\n\t\trepos = settled.value;\n\t}\n\tconst ordering = toIssueOrdering(resolvedRepoScopedSort.sort, mergesProviderQueries(issuesPagingMode, repos));\n\t// Refused rather than served as concatenated per-scope runs, which would look ordered without being so — and\n\t// only where it actually merges, since the same key against the same provider is perfectly answerable for a\n\t// single repository or project.\n\tif (ordering.unmergeable != null) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[\n\t\t\t\tunmergeableIssueSortWarning(\n\t\t\t\t\toptions.providerId,\n\t\t\t\t\tdomain,\n\t\t\t\t\toptions.connectionId,\n\t\t\t\t\tordering.unmergeable,\n\t\t\t\t\tissuesPagingMode === PagingMode.Project ? 'projects' : 'repositories',\n\t\t\t\t),\n\t\t\t],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tconst cursor = options.cursor ?? pageToCursor(page);\n\tconst { value, warning } = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\t() =>\n\t\t\t// The shapes seam returns normalized IssueShape, and is overridable by a provider whose only issue\n\t\t\t// client already yields shapes (serving this path without a raw ProviderIssue round-trip).\n\t\t\tintegration.getMyIssuesForReposAsShapesResult(\n\t\t\t\trepos,\n\t\t\t\t// Forward `page`/`pageSize` alongside the cursor so PagingMode.Repo/Project hosts honor the\n\t\t\t\t// requested page and page size rather than ignoring a synthesized page-number cursor.\n\t\t\t\t{\n\t\t\t\t\tfilters: options.filters,\n\t\t\t\t\tincludeAllAssignees: options.includeAllAssignees,\n\t\t\t\t\tstate: options.state,\n\t\t\t\t\tcursor: cursor,\n\t\t\t\t\t// The normalized `page`, so the number forwarded to the provider can't disagree with the\n\t\t\t\t\t// page-number cursor beside it — both are built from the same value. Only when the caller asked\n\t\t\t\t\t// for one: an omitted page is the provider's own first page.\n\t\t\t\t\tpage: options.page != null ? page : undefined,\n\t\t\t\t\tpageSize: options.itemsPerPage,\n\t\t\t\t\tsort: ordering.sort,\n\t\t\t\t},\n\t\t\t\toptions.connectionId,\n\t\t\t),\n\t\t{ warnOnMissingSession: warnOnMissingSession },\n\t);\n\n\tlet items = value?.values ?? [];\n\tconst warnings = warning != null ? [warning] : [];\n\tlet pageFetchFailed = warning != null && value == null;\n\tlet paged = toProviderPageInfo(options.itemsPerPage ?? items.length, value?.paging);\n\tlet allMetadata = value?.metadata;\n\n\t// Cursor-only repo-scoped hosts (e.g. GitHub) ignore a synthesized page-number cursor, so a page-only\n\t// request is advanced through the provider's own continuations (see drainToRequestedPage).\n\tif (\n\t\tissuesPagingMode === PagingMode.Repos &&\n\t\toptions.page != null &&\n\t\tpage > 1 &&\n\t\toptions.cursor == null &&\n\t\tpaged.page.currentPage === 1\n\t) {\n\t\tconst drained = await drainToRequestedPage(\n\t\t\t{ items: items, paged: paged, metadata: allMetadata, fetchFailed: pageFetchFailed },\n\t\t\t{\n\t\t\t\trequestedPage: page,\n\t\t\t\titemsPerPage: options.itemsPerPage,\n\t\t\t\twarnings: warnings,\n\t\t\t\treadPage: (cursor: string) =>\n\t\t\t\t\trunCaptured(\n\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tintegration.getMyIssuesForReposAsShapesResult(\n\t\t\t\t\t\t\t\trepos,\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tfilters: options.filters,\n\t\t\t\t\t\t\t\t\tincludeAllAssignees: options.includeAllAssignees,\n\t\t\t\t\t\t\t\t\tstate: options.state,\n\t\t\t\t\t\t\t\t\tcursor: cursor,\n\t\t\t\t\t\t\t\t\tpageSize: options.itemsPerPage,\n\t\t\t\t\t\t\t\t\tsort: ordering.sort,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t{ warnOnMissingSession: warnOnMissingSession },\n\t\t\t\t\t),\n\t\t\t},\n\t\t);\n\t\titems = drained.items;\n\t\tpaged = drained.paged;\n\t\tallMetadata = drained.metadata;\n\t\tpageFetchFailed = drained.fetchFailed;\n\t}\n\n\t// Ordered once the drain (above) has assembled whichever pages this position spans.\n\titems = ordering.order(items);\n\n\t// Convert the SDK collection metadata into scope-aware warnings + failure/truncation flags, appending\n\t// them to any captured thrown-error warning without discarding the partial result's items.\n\tconst assessment = mergeAssessmentInto(warnings, options.providerId, domain, options.connectionId, allMetadata);\n\t// Never advertise `hasMore` without a continuation the caller can act on, and only synthesize a page\n\t// number for a host that reads it as one (see `isPageNumberAdvanceable`).\n\tconst issuesPageAdvanceable = isPageNumberAdvanceable(issuesPagingMode);\n\tconst continuation = resolveContinuation(paged, issuesPageAdvanceable ? paged.page.currentPage + 1 : undefined);\n\t// A provider read that couldn't confirm completeness (e.g. Bitbucket's single-page repo issue read\n\t// that dropped a repo) sets `paging.truncated`; surface it as a terminal `page.truncated` so a partial\n\t// page isn't published as complete. Metadata incompleteness is an independent source of the same signal.\n\tconst truncated = continuation.truncated || assessment.truncated;\n\tif (truncated && warnings.length === 0) {\n\t\twarnings.push(\n\t\t\ttruncationWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'Issue',\n\t\t\t\t// `exhausted`, never `page-budget`: a paged read has no budget the caller can raise, and\n\t\t\t\t// ordinary continuation is already expressed by `hasMore`/`cursor`.\n\t\t\t\tassessment.fetchFailed || pageFetchFailed ? 'interrupted' : 'exhausted',\n\t\t\t),\n\t\t);\n\t}\n\t// A metadata omission from an earlier page asserts the read succeeded; a later page may since have failed.\n\treconcileOmissionsWithFailure(warnings, assessment.fetchFailed || pageFetchFailed);\n\treturn {\n\t\titems: items,\n\t\twarnings: warnings,\n\t\tpage: {\n\t\t\t...paged.page,\n\t\t\t// Positional, per ProviderPageInfo.currentPage: the drain (above) already advanced `paged.page` for\n\t\t\t// a cursor-only host, and a caller-threaded cursor advanced the provider without one.\n\t\t\tcurrentPage: resolveCurrentPage({\n\t\t\t\tproviderPage: paged.page.currentPage,\n\t\t\t\trequestedPage: page,\n\t\t\t\tsuppliedCursor: options.cursor,\n\t\t\t\tpageAdvanceable: issuesPageAdvanceable,\n\t\t\t}),\n\t\t\ttruncated: truncated || undefined,\n\t\t},\n\t\thasMore: continuation.hasMore,\n\t\tcursor: continuation.cursor,\n\t\tfetchFailed: assessment.fetchFailed || pageFetchFailed || undefined,\n\t};\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { githubGraphQLInt32Max, isAzureProviderId, isGitHubProviderId } from "../providers/providerErrors.js";
2
2
  import { isGitHostIntegration, isIssuesHostIntegrationId } from "../utils/integration.utils.js";
3
+ import { pullRequestEtagIncludes } from "../models/integration.js";
3
4
  import { gitHostOnlySurfaceWarning, otherWarning, unsupportedWarning } from "./warnings.js";
4
5
  import { getCurrentAccountIdentity, runCaptured } from "./drains.js";
5
- import { pullRequestEtagIncludes } from "../models/integration.js";
6
6
  import { readEtaggedBatch } from "./batchEtags.js";
7
7
  import { findInvalidPullRequestEtagInclude, normalizePullRequestEtagIncludes, pullRequestEtag, pullRequestEtagFieldsFromShape } from "./etag.js";
8
8
  import { findDuplicateKey, trimCoordinateFields, unresolvedIntegration } from "./issueBatch.js";
@@ -106,7 +106,7 @@ async function resolveRepository(ctx, options) {
106
106
  domain,
107
107
  owner: canonicalOwner,
108
108
  name: canonicalName,
109
- project,
109
+ project: project != null ? repo.project || project : void 0,
110
110
  remoteUrl: options.remoteUrl,
111
111
  renamed
112
112
  }
@@ -119,7 +119,7 @@ async function resolveRepository(ctx, options) {
119
119
  try {
120
120
  const failure = await integration.settleRepositoryRefusal({
121
121
  owner,
122
- name,
122
+ name: project != null ? getAzureRepositoryName(name) : name,
123
123
  project,
124
124
  connectionId
125
125
  }, ex);
@@ -145,6 +145,15 @@ async function resolveRepository(ctx, options) {
145
145
  return { resolution };
146
146
  }
147
147
  }
148
+ /** The repository an Azure remote's `{project}/_git/{repo}` name addresses, as a batch target names it. */
149
+ function getAzureRepositoryName(name) {
150
+ return /\/_git\/([^/]+)$/i.exec(name)?.[1] ?? name;
151
+ }
152
+ /** Whether `path` is `{x}/_git/{repo}`, the Azure repository path the remote parser reads without a project segment. */
153
+ function isProjectlessAzurePath(path) {
154
+ const segments = path?.split("/");
155
+ return segments?.length === 3 && decodePathSegment(segments[1]).toLowerCase() === "_git";
156
+ }
148
157
  /**
149
158
  * Narrows a web remote on a self-managed host to the configured installation it lives under: the pinned
150
159
  * connection's or, unpinned, the one whose installation path is the longest prefix of the remote's, so two
@@ -199,7 +208,7 @@ function matchInstallation(ctx, options, id, connections, remote) {
199
208
  connectionId,
200
209
  repoDesc: provider.repoDesc
201
210
  } : "invalid-remote-url";
202
- if (basePath && provider?.providerDesc?.repoDomain == null) {
211
+ if (basePath && (provider?.providerDesc?.repoDomain == null || isProjectlessAzurePath(relativeTo(basePath)))) {
203
212
  basePath = basePath.slice(0, basePath.lastIndexOf("/"));
204
213
  provider = rematch(basePath);
205
214
  }
@@ -1 +1 @@
1
- {"version":3,"file":"resolveRepository.js","names":[],"sources":["../../../../../plus/integrations/src/reads/resolveRepository.ts"],"sourcesContent":["import type { RemoteProvider } from '@gitlens/git/models/remoteProvider.js';\nimport type { ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport type { RemoteProviderConfig } from '@gitlens/git/remotes/matcher.js';\nimport { createRemoteProviderMatcher } from '@gitlens/git/remotes/matcher.js';\nimport { parseGitRemoteUrl } from '@gitlens/git/utils/remote.utils.js';\nimport type { ConfiguredIntegrationDescriptor } from '../authentication/models.js';\nimport { mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { GitCloudHostIntegrationId, GitSelfManagedHostIntegrationId } from '../constants.js';\nimport { AuthenticationError, RequestNotFoundError } from '../errors.js';\nimport { isIssuesIntegration } from '../models/issuesIntegration.js';\nimport { isAzureCloudDomain, isBitbucketCloudDomain, isGitHubDotCom, isGitLabDotCom } from '../providers/models.js';\nimport type { ProviderWarning, RepositoryIdentity, RepositoryResolution, ResolveRepositoryResult } from '../results.js';\nimport { toProviderWarning } from '../results.js';\nimport { decodePathSegment, getRemoteHostMatcher, hostFromDomain, isWebRemoteScheme } from '../utils/domain.utils.js';\nimport {\n\tgetIntegrationIdForRemote,\n\tgetSelfManagedBaseUrl,\n\tisGitCloudHostIntegrationId,\n\tisGitSelfManagedHostIntegrationId,\n\tremoteProviderTypeForConfig,\n\tremoteProviderTypeForIntegration,\n} from '../utils/integration.utils.js';\nimport type { RepositoryResolutionContext } from './context.js';\nimport { noConnectionWarning } from './warnings.js';\n\ntype ResolveRepositoryOptions = {\n\tproviderId?: IntegrationIds;\n\tremoteUrl: string;\n\thost?: string;\n\tconnectionId?: string;\n\t/**\n\t * Explicit self-managed host domain used to select the integration instance. Used only when the requested\n\t * connection has no configured domain; it must come from the trusted authentication configuration, not\n\t * repository or remote data.\n\t */\n\tdomain?: string;\n};\n\n/**\n * Resolution of a remote URL to a provider repository identity — the facade's equivalent of `gk repo resolve`.\n *\n * Unlike every other read here, this one's input is REPOSITORY data, so most of the code below is about not\n * letting that data choose the credentials: an explicit cloud `providerId` must agree with the remote's canonical\n * host, and a self-managed host resolves only against a domain the user already authenticated. The\n * `host-mismatch` returns are those guards, not edge cases — resolving `owner/repo` against the wrong account\n * would hand back a confidently wrong identity and seed the domain-keyed instance cache from remote data.\n */\nexport async function resolveRepository(\n\tctx: RepositoryResolutionContext,\n\toptions: ResolveRepositoryOptions,\n): Promise<ResolveRepositoryResult> {\n\tconst result = (status: RepositoryResolution['status']): ResolveRepositoryResult => ({\n\t\tresolution: { status: status },\n\t});\n\n\tconst [scheme, parsedDomain, path] = parseGitRemoteUrl(options.remoteUrl);\n\t// An SSH remote's port belongs to its SSH daemon, not to the web authority a connection is keyed by.\n\tconst hostsMatch = getRemoteHostMatcher(scheme);\n\tconst parsedHost = hostFromDomain(parsedDomain);\n\tconst explicitHost = hostFromDomain(options.host);\n\tif (parsedHost != null && explicitHost != null && !hostsMatch(parsedHost, explicitHost)) {\n\t\treturn result('host-mismatch');\n\t}\n\n\tconst matcherDomain = parsedDomain || options.host || '';\n\n\t// An explicit cloud provider must agree with the remote's canonical cloud host. Without this guard the\n\t// synthetic matcher below can reinterpret, for example, a gitlab.com URL as GitHub and then resolve a\n\t// homonymous owner/repo through the wrong account. Match once without caller configs/synthetic entries;\n\t// Azure's matcher also normalizes ssh.dev.azure.com and vs-ssh.visualstudio.com to their web host.\n\tif (options.providerId != null && isGitCloudHostIntegrationId(options.providerId)) {\n\t\tconst nativeProvider = createRemoteProviderMatcher([])(options.remoteUrl, matcherDomain, path, scheme);\n\t\tconst nativeId = nativeProvider != null ? getIntegrationIdForRemote(nativeProvider) : undefined;\n\t\tconst nativeDomain = nativeProvider?.domain ?? matcherDomain;\n\t\tconst canonicalHost =\n\t\t\toptions.providerId === GitCloudHostIntegrationId.GitHub\n\t\t\t\t? isGitHubDotCom(nativeDomain)\n\t\t\t\t: options.providerId === GitCloudHostIntegrationId.GitLab\n\t\t\t\t\t? isGitLabDotCom(nativeDomain)\n\t\t\t\t\t: options.providerId === GitCloudHostIntegrationId.Bitbucket\n\t\t\t\t\t\t? isBitbucketCloudDomain(nativeDomain)\n\t\t\t\t\t\t: isAzureCloudDomain(nativeDomain);\n\t\tif (nativeId !== options.providerId || !canonicalHost) return result('host-mismatch');\n\t}\n\n\tlet provider = createRemoteProviderMatcher(buildMatcherConfigs(ctx, options, parsedDomain))(\n\t\toptions.remoteUrl,\n\t\tmatcherDomain,\n\t\tpath,\n\t\tscheme,\n\t);\n\tif (provider == null) return result('invalid-remote-url');\n\n\tlet id = options.providerId ?? getIntegrationIdForRemote(provider);\n\t// Custom Azure DevOps Server domains matched via getRemoteConfigs return undefined from\n\t// getIntegrationIdForRemote because the provider is marked custom; map them to the server id so the\n\t// project-scoped lookup uses that host's configured connection and API base URL.\n\tif (id == null && provider.id === 'azure-devops' && provider.custom) {\n\t\tid = GitSelfManagedHostIntegrationId.AzureDevOpsServer;\n\t}\n\tif (id == null) return result('unsupported-provider');\n\n\t// On a self-managed host, resolve only against a TRUSTED host: the pinned connection's configured\n\t// domain, the explicit `domain`, or — when neither was supplied — a configured host matching the\n\t// remote's. That last case keeps `remoteUrl` (repository-supplied) out of the trusted path: it selects\n\t// among hosts the user already authenticated and can never introduce a new one. Resolving `owner/repo`\n\t// against some other host's account would, if that host has the same owner/repo, return a confidently\n\t// wrong identity — and would seed the domain-keyed integration cache (never evicted before dispose) with\n\t// an entry derived from repository data.\n\tconst urlHost = hostFromDomain(provider.domain);\n\t// Normalize BOTH sides before comparing: a stored connection domain is usually a full URL\n\t// (`https://git.example.com`) while `urlHost` is already a bare host, so a raw compare would fail on\n\t// scheme/trailing-slash alone and wrongly reject a correctly-configured connection.\n\tlet trustedHost =\n\t\toptions.connectionId != null || options.domain != null\n\t\t\t? hostFromDomain(ctx.resolveDomainForRead(id, options.connectionId, options.domain))\n\t\t\t: undefined;\n\tlet connectionId = options.connectionId;\n\tlet providerRepo: ResourceDescriptor | undefined;\n\tif (isGitSelfManagedHostIntegrationId(id)) {\n\t\tif (path.split('/').some(segment => /^(?:\\.|%2e){1,2}$/i.test(segment) || /%2f|%5c|\\\\/i.test(segment))) {\n\t\t\treturn result('invalid-remote-url');\n\t\t}\n\n\t\tconst configured = ctx.getConfigured(id);\n\t\tconst configuredHosts = configured.map(c => hostFromDomain(c.domain));\n\t\tif (trustedHost == null && urlHost != null) {\n\t\t\tconst matchingHosts = new Set(configuredHosts.filter(host => hostsMatch(host, urlHost)));\n\t\t\tif (matchingHosts.size > 1) return result('host-mismatch');\n\n\t\t\ttrustedHost = matchingHosts.values().next().value;\n\t\t}\n\t\t// A configured host that doesn't match the remote's is a genuine mismatch. With nothing configured\n\t\t// there is no host to mismatch against — it's simply not connected, so fall through to the\n\t\t// `unauthorized` path below (still WITHOUT resolving by the URL domain, so repo data can't seed the\n\t\t// cache).\n\t\tif (trustedHost == null && configuredHosts.length !== 0) return result('host-mismatch');\n\t\tif (trustedHost != null && urlHost != null && !hostsMatch(trustedHost, urlHost)) {\n\t\t\treturn result('host-mismatch');\n\t\t}\n\n\t\tconst installation = matchInstallation(\n\t\t\tctx,\n\t\t\toptions,\n\t\t\tid,\n\t\t\tconfigured.filter(c => hostFromDomain(c.domain) === trustedHost),\n\t\t\t{ scheme: scheme, domain: parsedDomain, path: path },\n\t\t);\n\t\tif (typeof installation === 'string') return result(installation);\n\n\t\tif (installation != null) {\n\t\t\tprovider = installation.provider;\n\t\t\tconnectionId = installation.connectionId;\n\t\t\tproviderRepo = installation.repoDesc;\n\t\t}\n\t}\n\n\tconst owner = provider.owner;\n\tconst name = provider.repoName;\n\tif (owner == null || name == null) return result('invalid-remote-url');\n\n\tproviderRepo ??= provider.repoDesc;\n\n\tlet integration;\n\ttry {\n\t\t// Resolve the instance through the trusted target (as `getIntegrationForRead` does):\n\t\t// `resolveReadSession` looks the session up against the instance's domain-scoped descriptor, so\n\t\t// selecting the instance by the URL domain could miss the session and degrade to `no-connection`.\n\t\tintegration = isGitSelfManagedHostIntegrationId(id)\n\t\t\t? trustedHost != null\n\t\t\t\t? await ctx.getIntegrationForRead(id, connectionId, trustedHost)\n\t\t\t\t: undefined\n\t\t\t: await ctx.getIntegrationForDomain(id, provider.domain);\n\t} catch {\n\t\tintegration = undefined;\n\t}\n\tif (integration == null) {\n\t\t// Attach a warning, as the thrown-AuthenticationError branch below does: a consumer driving auth\n\t\t// recovery off `warning.kind` would otherwise get nothing for an unresolvable target.\n\t\treturn {\n\t\t\tresolution: {\n\t\t\t\tstatus: 'unauthorized',\n\t\t\t\twarning: noConnectionWarning(id, provider.domain, connectionId),\n\t\t\t},\n\t\t};\n\t}\n\t// Issue trackers have no `getRepo` client; a git host without `getRepoFn` leaves `getRepoInfo`\n\t// undefined. Either way this provider can't resolve repositories.\n\tif (isIssuesIntegration(integration) || integration.getRepoInfo == null) {\n\t\treturn result('unsupported-provider');\n\t}\n\n\t// Azure repos are org + project scoped; the remote provider exposes project as `providerDesc.repoDomain`.\n\tconst project = provider.id === 'azure-devops' ? provider.providerDesc?.repoDomain : undefined;\n\tconst domain = trustedHost ?? provider.domain;\n\n\t// Azure's lookup is project-scoped and returns `undefined` without one, which the connection-gap branch\n\t// below would misreport as `unauthorized` (driving a pointless reconnect). A remote URL that carries no\n\t// project simply can't address an Azure repo.\n\tif (provider.id === 'azure-devops' && project == null) return result('invalid-remote-url');\n\n\ttry {\n\t\tconst repo = await integration.getRepoInfo({\n\t\t\t...providerRepo,\n\t\t\towner: owner,\n\t\t\tname: name,\n\t\t\tproject: project,\n\t\t\tconnectionId: connectionId,\n\t\t});\n\t\tif (repo == null) {\n\t\t\t// `getRepoInfo` returns undefined only when no session could be resolved (not connected, or the\n\t\t\t// requested connection is gone) — a real 404 throws below. So this is a connection gap. Attach a\n\t\t\t// warning so a consumer driving auth recovery off `warning.kind` sees the same signal it gets from\n\t\t\t// the thrown-AuthenticationError branch below; without it, the most common unauthorized case is\n\t\t\t// silent.\n\t\t\treturn {\n\t\t\t\tresolution: {\n\t\t\t\t\tstatus: 'unauthorized',\n\t\t\t\t\twarning: noConnectionWarning(id, domain, connectionId),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Prefer the provider's canonical namespace/name (GitHub's REST/GraphQL lookup follows the 301\n\t\t// rename redirect, so a stale old name resolves to the new canonical identity), falling back to the\n\t\t// parsed remote when the response omits them. `renamed` is a case-insensitive compare of input vs\n\t\t// canonical, mirroring gkcli's `EqualFold`, so hosts that merely echo the input casing (e.g.\n\t\t// Bitbucket Server/Azure) don't get spuriously flagged.\n\t\tconst canonicalOwner = repo.namespace || owner;\n\t\tconst canonicalName = repo.name || name;\n\t\tconst renamed =\n\t\t\tcanonicalOwner.toLowerCase() !== owner.toLowerCase() || canonicalName.toLowerCase() !== name.toLowerCase();\n\n\t\tconst identity: RepositoryIdentity = {\n\t\t\tproviderId: id,\n\t\t\tdomain: domain,\n\t\t\towner: canonicalOwner,\n\t\t\tname: canonicalName,\n\t\t\tproject: project,\n\t\t\tremoteUrl: options.remoteUrl,\n\t\t\trenamed: renamed,\n\t\t};\n\t\treturn { resolution: { status: 'resolved', identity: identity } };\n\t} catch (ex) {\n\t\t// Order matters: 404 throws RequestNotFoundError (not `undefined`), so check not-found before auth\n\t\t// and before the generic 5xx/unknown bucket — never classify a 401/403 as not-found.\n\t\tlet resolution: RepositoryResolution;\n\t\tif (ex instanceof RequestNotFoundError) {\n\t\t\tresolution = { status: 'not-found' };\n\t\t} else if (ex instanceof AuthenticationError) {\n\t\t\t// One repository refusing a credential that works (e.g. GitHub's OAuth App access restrictions) is that\n\t\t\t// repository's warning, scoped and with its cause, not a dead connection's.\n\t\t\tlet warning = toProviderWarning(id, domain, connectionId, ex);\n\t\t\ttry {\n\t\t\t\tconst failure = await integration.settleRepositoryRefusal(\n\t\t\t\t\t{ owner: owner, name: name, project: project, connectionId: connectionId },\n\t\t\t\t\tex,\n\t\t\t\t);\n\t\t\t\tif (failure != null) {\n\t\t\t\t\tconst warnings: ProviderWarning[] = [];\n\t\t\t\t\tmergeAssessmentInto(warnings, id, domain, connectionId, {\n\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\tfailures: [failure],\n\t\t\t\t\t});\n\t\t\t\t\twarning = warnings[0] ?? warning;\n\t\t\t\t}\n\t\t\t} catch (credentialError) {\n\t\t\t\tif (credentialError instanceof AuthenticationError) {\n\t\t\t\t\twarning = toProviderWarning(id, domain, connectionId, credentialError);\n\t\t\t\t}\n\t\t\t}\n\t\t\tresolution = {\n\t\t\t\tstatus: 'unauthorized',\n\t\t\t\twarning: warning,\n\t\t\t};\n\t\t} else {\n\t\t\tresolution = { status: 'undetermined', warning: toProviderWarning(id, domain, connectionId, ex) };\n\t\t}\n\t\treturn { resolution: resolution };\n\t}\n}\n\n/**\n * Narrows a web remote on a self-managed host to the configured installation it lives under: the pinned\n * connection's or, unpinned, the one whose installation path is the longest prefix of the remote's, so two\n * installations behind one host each resolve with their own credentials. The provider is re-matched against the\n * path relative to that installation, because the remote parsers expect the repository path to start at the\n * host root. An SSH remote carries no installation path, so it is left to the host-level checks.\n */\nfunction matchInstallation(\n\tctx: RepositoryResolutionContext,\n\toptions: ResolveRepositoryOptions,\n\tid: GitSelfManagedHostIntegrationId,\n\tconnections: ConfiguredIntegrationDescriptor[],\n\tremote: { scheme: string; domain: string; path: string },\n):\n\t| { provider: RemoteProvider; connectionId: string | undefined; repoDesc: ResourceDescriptor }\n\t| 'host-mismatch'\n\t| 'invalid-remote-url'\n\t| undefined {\n\tif (!isWebRemoteScheme(remote.scheme)) return undefined;\n\n\tconst installationPath = (connection: ConfiguredIntegrationDescriptor | undefined): string | undefined => {\n\t\tconst baseUrl = getSelfManagedBaseUrl(\n\t\t\tid,\n\t\t\tconnection != null ? (connection.baseUrl ?? connection.domain) : options.domain,\n\t\t);\n\t\treturn baseUrl != null ? new URL(baseUrl).pathname.replace(/\\/+$/, '') : undefined;\n\t};\n\n\t// The connection an unpinned read already targets comes first, so it wins a tie between installations.\n\tconst defaultConnection = connections.find(c => c.primary) ?? connections[0];\n\t// A cloud sync serves the host's cloud-scoped default instead, even over a local primary (see\n\t// `getStoredSession`), so an unpinned read reaches the default's installation only when both agree.\n\tconst cloudConnections = connections.filter(c => c.cloud);\n\tconst cloudDefault = cloudConnections.find(c => c.primary) ?? cloudConnections[0];\n\tconst isDefaultReadable =\n\t\tdefaultConnection?.cloud !== false ||\n\t\tcloudDefault == null ||\n\t\tinstallationPath(cloudDefault) === installationPath(defaultConnection);\n\t// Whether an unpinned read can reach this connection's session: the default one is read without an id, any\n\t// other only by a real cloud token id. Session lookup treats every connection id as one, and a local (PAT)\n\t// descriptor's or domain-derived legacy id is not (see `getConfiguredConnectionId`).\n\tconst isReadable = (connection: ConfiguredIntegrationDescriptor | undefined): boolean =>\n\t\toptions.connectionId != null ||\n\t\t(connection === defaultConnection\n\t\t\t? isDefaultReadable\n\t\t\t: connection?.cloud === true && hostFromDomain(connection.id) !== connection.domain);\n\n\tconst candidates: (ConfiguredIntegrationDescriptor | undefined)[] =\n\t\toptions.connectionId != null\n\t\t\t? connections.filter(c => c.id === options.connectionId)\n\t\t\t: [defaultConnection, ...connections.filter(c => c !== defaultConnection)];\n\t// With no configured candidate, the explicit (trusted) `domain` is the only installation address there is.\n\tif (!candidates.length) {\n\t\tcandidates.push(undefined);\n\t}\n\n\tconst remotePath = `/${remote.path}`;\n\tconst remoteSegments = remote.path.split('/');\n\t// The remote's path below an installation, or `undefined` when it isn't under it. Azure DevOps Server runs on\n\t// IIS, which serves its paths decoded and case-insensitively, so it compares segment by segment.\n\tconst relativeTo = (basePath: string): string | undefined => {\n\t\tif (id !== GitSelfManagedHostIntegrationId.AzureDevOpsServer) {\n\t\t\treturn remotePath.startsWith(`${basePath}/`) ? remotePath.slice(basePath.length + 1) : undefined;\n\t\t}\n\n\t\tconst baseSegments = basePath.split('/').filter(Boolean);\n\t\tconst isUnder =\n\t\t\tremoteSegments.length > baseSegments.length &&\n\t\t\tbaseSegments.every(\n\t\t\t\t(segment, i) =>\n\t\t\t\t\tdecodePathSegment(segment).toLowerCase() === decodePathSegment(remoteSegments[i]).toLowerCase(),\n\t\t\t);\n\t\treturn isUnder ? remoteSegments.slice(baseSegments.length).join('/') : undefined;\n\t};\n\tlet match: { connection: ConfiguredIntegrationDescriptor | undefined; basePath: string } | undefined;\n\tlet checked = false;\n\tfor (const connection of candidates) {\n\t\tconst basePath = installationPath(connection);\n\t\tif (basePath == null) continue;\n\n\t\tchecked = true;\n\t\tif (relativeTo(basePath) == null) continue;\n\n\t\t// The longest installation wins; on a tie, a readable connection replaces an unreadable one.\n\t\tconst isBetter =\n\t\t\tmatch == null ||\n\t\t\tbasePath.length > match.basePath.length ||\n\t\t\t(basePath.length === match.basePath.length && !isReadable(match.connection) && isReadable(connection));\n\t\tif (isBetter) {\n\t\t\tmatch = { connection: connection, basePath: basePath };\n\t\t}\n\t}\n\tif (match == null) return checked ? 'host-mismatch' : undefined;\n\t// Reading through the default connection instead would address its installation with a path relative to\n\t// another one, so an unreachable installation fails closed.\n\tif (!isReadable(match.connection)) return 'host-mismatch';\n\n\tconst connectionId =\n\t\toptions.connectionId ?? (match.connection !== defaultConnection ? match.connection?.id : undefined);\n\n\tconst matcher = createRemoteProviderMatcher(\n\t\tbuildMatcherConfigs(ctx, { ...options, providerId: id }, remote.domain),\n\t);\n\tconst rematch = (path: string) => matcher(options.remoteUrl, remote.domain, relativeTo(path) ?? '', remote.scheme);\n\tlet basePath = match.basePath;\n\tlet provider = rematch(basePath);\n\tif (id !== GitSelfManagedHostIntegrationId.AzureDevOpsServer) {\n\t\treturn provider != null\n\t\t\t? { provider: provider, connectionId: connectionId, repoDesc: provider.repoDesc }\n\t\t\t: 'invalid-remote-url';\n\t}\n\n\t// An Azure DevOps Server address may name the collection as well as the virtual directory; the parser expects\n\t// the collection in the path, so re-base on the address's parent when no project was found.\n\tif (basePath && provider?.providerDesc?.repoDomain == null) {\n\t\tbasePath = basePath.slice(0, basePath.lastIndexOf('/'));\n\t\tprovider = rematch(basePath);\n\t}\n\tif (provider == null) return 'invalid-remote-url';\n\n\t// Requests address the repository's virtual directory from the host root, not from the installation the\n\t// re-match made it relative to; the absolute form also tells a directory apart from a collection of that name.\n\tconst repoDesc = provider.repoDesc;\n\tconst relative = typeof repoDesc.virtualDirectory === 'string' ? repoDesc.virtualDirectory : undefined;\n\tconst virtualDirectory =\n\t\t[...basePath.split('/').filter(Boolean).map(decodePathSegment), ...(relative?.split('/') ?? [])].join('/') ||\n\t\tundefined;\n\treturn {\n\t\tprovider: provider,\n\t\tconnectionId: connectionId,\n\t\trepoDesc: { ...repoDesc, virtualDirectory: virtualDirectory },\n\t};\n}\n\n/**\n * The remote-matcher configs for this resolution: the user's own host remote configs (self-managed/custom\n * domains), plus a synthetic exact-domain entry for an explicit `providerId` + host so a custom domain still maps\n * to the right provider for path parsing.\n */\nfunction buildMatcherConfigs(\n\tctx: RepositoryResolutionContext,\n\toptions: { providerId?: IntegrationIds; host?: string },\n\tparsedDomain: string,\n): RemoteProviderConfig[] {\n\tconst configs: RemoteProviderConfig[] = [];\n\tfor (const cfg of ctx.getRemoteConfigs()) {\n\t\tconst type = remoteProviderTypeForConfig(cfg.type);\n\t\tif (type == null) continue;\n\n\t\t// Forward both domain- and regex-based custom remotes (carrying any protocol override), so a\n\t\t// regex-configured host resolves instead of falling through to `unsupported`.\n\t\tif (cfg.domain) {\n\t\t\tconfigs.push({ type: type, domain: cfg.domain, protocol: cfg.protocol });\n\t\t} else if (cfg.regex) {\n\t\t\tconfigs.push({ type: type, regex: cfg.regex, protocol: cfg.protocol });\n\t\t}\n\t}\n\tif (options.providerId == null) return configs;\n\n\tconst type = remoteProviderTypeForIntegration(options.providerId);\n\tconst domain = parsedDomain || options.host;\n\tif (type == null || !domain) return configs;\n\n\t// The synthetic exact-domain entry is unshifted to the front, so it wins the match over the user's own\n\t// config for the same host. Carry that config's protocol override across (matched by domain or regex,\n\t// mirroring `ignoreSSLErrors`) so a self-managed host configured for a custom protocol — e.g. plain\n\t// `http` — isn't silently downgraded to the provider default here.\n\tconst lowerDomain = domain.toLowerCase();\n\tconst protocol = configs.find(c => {\n\t\tif (c.type !== type) return false;\n\t\tif (c.domain != null) return c.domain.toLowerCase() === lowerDomain;\n\n\t\t// Truthy (not just non-null): an empty regex would compile to a match-everything pattern.\n\t\tif (c.regex) {\n\t\t\ttry {\n\t\t\t\treturn new RegExp(c.regex, 'i').test(lowerDomain);\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t})?.protocol;\n\tconfigs.unshift({ type: type, domain: domain, protocol: protocol });\n\treturn configs;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"resolveRepository.js","names":[],"sources":["../../../../../plus/integrations/src/reads/resolveRepository.ts"],"sourcesContent":["import type { RemoteProvider } from '@gitlens/git/models/remoteProvider.js';\nimport type { ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport type { RemoteProviderConfig } from '@gitlens/git/remotes/matcher.js';\nimport { createRemoteProviderMatcher } from '@gitlens/git/remotes/matcher.js';\nimport { parseGitRemoteUrl } from '@gitlens/git/utils/remote.utils.js';\nimport type { ConfiguredIntegrationDescriptor } from '../authentication/models.js';\nimport { mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { GitCloudHostIntegrationId, GitSelfManagedHostIntegrationId } from '../constants.js';\nimport { AuthenticationError, RequestNotFoundError } from '../errors.js';\nimport { isIssuesIntegration } from '../models/issuesIntegration.js';\nimport { isAzureCloudDomain, isBitbucketCloudDomain, isGitHubDotCom, isGitLabDotCom } from '../providers/models.js';\nimport type { ProviderWarning, RepositoryIdentity, RepositoryResolution, ResolveRepositoryResult } from '../results.js';\nimport { toProviderWarning } from '../results.js';\nimport { decodePathSegment, getRemoteHostMatcher, hostFromDomain, isWebRemoteScheme } from '../utils/domain.utils.js';\nimport {\n\tgetIntegrationIdForRemote,\n\tgetSelfManagedBaseUrl,\n\tisGitCloudHostIntegrationId,\n\tisGitSelfManagedHostIntegrationId,\n\tremoteProviderTypeForConfig,\n\tremoteProviderTypeForIntegration,\n} from '../utils/integration.utils.js';\nimport type { RepositoryResolutionContext } from './context.js';\nimport { noConnectionWarning } from './warnings.js';\n\ntype ResolveRepositoryOptions = {\n\tproviderId?: IntegrationIds;\n\tremoteUrl: string;\n\thost?: string;\n\tconnectionId?: string;\n\t/**\n\t * Explicit self-managed host domain used to select the integration instance. Used only when the requested\n\t * connection has no configured domain; it must come from the trusted authentication configuration, not\n\t * repository or remote data.\n\t */\n\tdomain?: string;\n};\n\n/**\n * Resolution of a remote URL to a provider repository identity — the facade's equivalent of `gk repo resolve`.\n *\n * Unlike every other read here, this one's input is REPOSITORY data, so most of the code below is about not\n * letting that data choose the credentials: an explicit cloud `providerId` must agree with the remote's canonical\n * host, and a self-managed host resolves only against a domain the user already authenticated. The\n * `host-mismatch` returns are those guards, not edge cases — resolving `owner/repo` against the wrong account\n * would hand back a confidently wrong identity and seed the domain-keyed instance cache from remote data.\n */\nexport async function resolveRepository(\n\tctx: RepositoryResolutionContext,\n\toptions: ResolveRepositoryOptions,\n): Promise<ResolveRepositoryResult> {\n\tconst result = (status: RepositoryResolution['status']): ResolveRepositoryResult => ({\n\t\tresolution: { status: status },\n\t});\n\n\tconst [scheme, parsedDomain, path] = parseGitRemoteUrl(options.remoteUrl);\n\t// An SSH remote's port belongs to its SSH daemon, not to the web authority a connection is keyed by.\n\tconst hostsMatch = getRemoteHostMatcher(scheme);\n\tconst parsedHost = hostFromDomain(parsedDomain);\n\tconst explicitHost = hostFromDomain(options.host);\n\tif (parsedHost != null && explicitHost != null && !hostsMatch(parsedHost, explicitHost)) {\n\t\treturn result('host-mismatch');\n\t}\n\n\tconst matcherDomain = parsedDomain || options.host || '';\n\n\t// An explicit cloud provider must agree with the remote's canonical cloud host. Without this guard the\n\t// synthetic matcher below can reinterpret, for example, a gitlab.com URL as GitHub and then resolve a\n\t// homonymous owner/repo through the wrong account. Match once without caller configs/synthetic entries;\n\t// Azure's matcher also normalizes ssh.dev.azure.com and vs-ssh.visualstudio.com to their web host.\n\tif (options.providerId != null && isGitCloudHostIntegrationId(options.providerId)) {\n\t\tconst nativeProvider = createRemoteProviderMatcher([])(options.remoteUrl, matcherDomain, path, scheme);\n\t\tconst nativeId = nativeProvider != null ? getIntegrationIdForRemote(nativeProvider) : undefined;\n\t\tconst nativeDomain = nativeProvider?.domain ?? matcherDomain;\n\t\tconst canonicalHost =\n\t\t\toptions.providerId === GitCloudHostIntegrationId.GitHub\n\t\t\t\t? isGitHubDotCom(nativeDomain)\n\t\t\t\t: options.providerId === GitCloudHostIntegrationId.GitLab\n\t\t\t\t\t? isGitLabDotCom(nativeDomain)\n\t\t\t\t\t: options.providerId === GitCloudHostIntegrationId.Bitbucket\n\t\t\t\t\t\t? isBitbucketCloudDomain(nativeDomain)\n\t\t\t\t\t\t: isAzureCloudDomain(nativeDomain);\n\t\tif (nativeId !== options.providerId || !canonicalHost) return result('host-mismatch');\n\t}\n\n\tlet provider = createRemoteProviderMatcher(buildMatcherConfigs(ctx, options, parsedDomain))(\n\t\toptions.remoteUrl,\n\t\tmatcherDomain,\n\t\tpath,\n\t\tscheme,\n\t);\n\tif (provider == null) return result('invalid-remote-url');\n\n\tlet id = options.providerId ?? getIntegrationIdForRemote(provider);\n\t// Custom Azure DevOps Server domains matched via getRemoteConfigs return undefined from\n\t// getIntegrationIdForRemote because the provider is marked custom; map them to the server id so the\n\t// project-scoped lookup uses that host's configured connection and API base URL.\n\tif (id == null && provider.id === 'azure-devops' && provider.custom) {\n\t\tid = GitSelfManagedHostIntegrationId.AzureDevOpsServer;\n\t}\n\tif (id == null) return result('unsupported-provider');\n\n\t// On a self-managed host, resolve only against a TRUSTED host: the pinned connection's configured\n\t// domain, the explicit `domain`, or — when neither was supplied — a configured host matching the\n\t// remote's. That last case keeps `remoteUrl` (repository-supplied) out of the trusted path: it selects\n\t// among hosts the user already authenticated and can never introduce a new one. Resolving `owner/repo`\n\t// against some other host's account would, if that host has the same owner/repo, return a confidently\n\t// wrong identity — and would seed the domain-keyed integration cache (never evicted before dispose) with\n\t// an entry derived from repository data.\n\tconst urlHost = hostFromDomain(provider.domain);\n\t// Normalize BOTH sides before comparing: a stored connection domain is usually a full URL\n\t// (`https://git.example.com`) while `urlHost` is already a bare host, so a raw compare would fail on\n\t// scheme/trailing-slash alone and wrongly reject a correctly-configured connection.\n\tlet trustedHost =\n\t\toptions.connectionId != null || options.domain != null\n\t\t\t? hostFromDomain(ctx.resolveDomainForRead(id, options.connectionId, options.domain))\n\t\t\t: undefined;\n\tlet connectionId = options.connectionId;\n\tlet providerRepo: ResourceDescriptor | undefined;\n\tif (isGitSelfManagedHostIntegrationId(id)) {\n\t\tif (path.split('/').some(segment => /^(?:\\.|%2e){1,2}$/i.test(segment) || /%2f|%5c|\\\\/i.test(segment))) {\n\t\t\treturn result('invalid-remote-url');\n\t\t}\n\n\t\tconst configured = ctx.getConfigured(id);\n\t\tconst configuredHosts = configured.map(c => hostFromDomain(c.domain));\n\t\tif (trustedHost == null && urlHost != null) {\n\t\t\tconst matchingHosts = new Set(configuredHosts.filter(host => hostsMatch(host, urlHost)));\n\t\t\tif (matchingHosts.size > 1) return result('host-mismatch');\n\n\t\t\ttrustedHost = matchingHosts.values().next().value;\n\t\t}\n\t\t// A configured host that doesn't match the remote's is a genuine mismatch. With nothing configured\n\t\t// there is no host to mismatch against — it's simply not connected, so fall through to the\n\t\t// `unauthorized` path below (still WITHOUT resolving by the URL domain, so repo data can't seed the\n\t\t// cache).\n\t\tif (trustedHost == null && configuredHosts.length !== 0) return result('host-mismatch');\n\t\tif (trustedHost != null && urlHost != null && !hostsMatch(trustedHost, urlHost)) {\n\t\t\treturn result('host-mismatch');\n\t\t}\n\n\t\tconst installation = matchInstallation(\n\t\t\tctx,\n\t\t\toptions,\n\t\t\tid,\n\t\t\tconfigured.filter(c => hostFromDomain(c.domain) === trustedHost),\n\t\t\t{ scheme: scheme, domain: parsedDomain, path: path },\n\t\t);\n\t\tif (typeof installation === 'string') return result(installation);\n\n\t\tif (installation != null) {\n\t\t\tprovider = installation.provider;\n\t\t\tconnectionId = installation.connectionId;\n\t\t\tproviderRepo = installation.repoDesc;\n\t\t}\n\t}\n\n\tconst owner = provider.owner;\n\tconst name = provider.repoName;\n\tif (owner == null || name == null) return result('invalid-remote-url');\n\n\tproviderRepo ??= provider.repoDesc;\n\n\tlet integration;\n\ttry {\n\t\t// Resolve the instance through the trusted target (as `getIntegrationForRead` does):\n\t\t// `resolveReadSession` looks the session up against the instance's domain-scoped descriptor, so\n\t\t// selecting the instance by the URL domain could miss the session and degrade to `no-connection`.\n\t\tintegration = isGitSelfManagedHostIntegrationId(id)\n\t\t\t? trustedHost != null\n\t\t\t\t? await ctx.getIntegrationForRead(id, connectionId, trustedHost)\n\t\t\t\t: undefined\n\t\t\t: await ctx.getIntegrationForDomain(id, provider.domain);\n\t} catch {\n\t\tintegration = undefined;\n\t}\n\tif (integration == null) {\n\t\t// Attach a warning, as the thrown-AuthenticationError branch below does: a consumer driving auth\n\t\t// recovery off `warning.kind` would otherwise get nothing for an unresolvable target.\n\t\treturn {\n\t\t\tresolution: {\n\t\t\t\tstatus: 'unauthorized',\n\t\t\t\twarning: noConnectionWarning(id, provider.domain, connectionId),\n\t\t\t},\n\t\t};\n\t}\n\t// Issue trackers have no `getRepo` client; a git host without `getRepoFn` leaves `getRepoInfo`\n\t// undefined. Either way this provider can't resolve repositories.\n\tif (isIssuesIntegration(integration) || integration.getRepoInfo == null) {\n\t\treturn result('unsupported-provider');\n\t}\n\n\t// Azure repos are org + project scoped; the remote provider exposes project as `providerDesc.repoDomain`.\n\tconst project = provider.id === 'azure-devops' ? provider.providerDesc?.repoDomain : undefined;\n\tconst domain = trustedHost ?? provider.domain;\n\n\t// Azure's lookup is project-scoped and returns `undefined` without one, which the connection-gap branch\n\t// below would misreport as `unauthorized` (driving a pointless reconnect). A remote URL that carries no\n\t// project simply can't address an Azure repo.\n\tif (provider.id === 'azure-devops' && project == null) return result('invalid-remote-url');\n\n\ttry {\n\t\tconst repo = await integration.getRepoInfo({\n\t\t\t...providerRepo,\n\t\t\towner: owner,\n\t\t\tname: name,\n\t\t\tproject: project,\n\t\t\tconnectionId: connectionId,\n\t\t});\n\t\tif (repo == null) {\n\t\t\t// `getRepoInfo` returns undefined only when no session could be resolved (not connected, or the\n\t\t\t// requested connection is gone) — a real 404 throws below. So this is a connection gap. Attach a\n\t\t\t// warning so a consumer driving auth recovery off `warning.kind` sees the same signal it gets from\n\t\t\t// the thrown-AuthenticationError branch below; without it, the most common unauthorized case is\n\t\t\t// silent.\n\t\t\treturn {\n\t\t\t\tresolution: {\n\t\t\t\t\tstatus: 'unauthorized',\n\t\t\t\t\twarning: noConnectionWarning(id, domain, connectionId),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Prefer the provider's canonical namespace/name (GitHub's REST/GraphQL lookup follows the 301\n\t\t// rename redirect, so a stale old name resolves to the new canonical identity), falling back to the\n\t\t// parsed remote when the response omits them. `renamed` is a case-insensitive compare of input vs\n\t\t// canonical, mirroring gkcli's `EqualFold`, so hosts that merely echo the input casing (e.g.\n\t\t// Bitbucket Server/Azure) don't get spuriously flagged.\n\t\tconst canonicalOwner = repo.namespace || owner;\n\t\tconst canonicalName = repo.name || name;\n\t\tconst renamed =\n\t\t\tcanonicalOwner.toLowerCase() !== owner.toLowerCase() || canonicalName.toLowerCase() !== name.toLowerCase();\n\n\t\tconst identity: RepositoryIdentity = {\n\t\t\tproviderId: id,\n\t\t\tdomain: domain,\n\t\t\towner: canonicalOwner,\n\t\t\tname: canonicalName,\n\t\t\t// Azure's own project for the repository: an Azure DevOps Server remote's path may name the collection where\n\t\t\t// the parser read the project (see `AzureDevOpsServerIntegration.getRepositoryTarget`).\n\t\t\tproject: project != null ? repo.project || project : undefined,\n\t\t\tremoteUrl: options.remoteUrl,\n\t\t\trenamed: renamed,\n\t\t};\n\t\treturn { resolution: { status: 'resolved', identity: identity } };\n\t} catch (ex) {\n\t\t// Order matters: 404 throws RequestNotFoundError (not `undefined`), so check not-found before auth\n\t\t// and before the generic 5xx/unknown bucket — never classify a 401/403 as not-found.\n\t\tlet resolution: RepositoryResolution;\n\t\tif (ex instanceof RequestNotFoundError) {\n\t\t\tresolution = { status: 'not-found' };\n\t\t} else if (ex instanceof AuthenticationError) {\n\t\t\t// One repository refusing a credential that works (e.g. GitHub's OAuth App access restrictions) is that\n\t\t\t// repository's warning, scoped and with its cause, not a dead connection's.\n\t\t\tlet warning = toProviderWarning(id, domain, connectionId, ex);\n\t\t\ttry {\n\t\t\t\tconst failure = await integration.settleRepositoryRefusal(\n\t\t\t\t\t{\n\t\t\t\t\t\towner: owner,\n\t\t\t\t\t\tname: project != null ? getAzureRepositoryName(name) : name,\n\t\t\t\t\t\tproject: project,\n\t\t\t\t\t\tconnectionId: connectionId,\n\t\t\t\t\t},\n\t\t\t\t\tex,\n\t\t\t\t);\n\t\t\t\tif (failure != null) {\n\t\t\t\t\tconst warnings: ProviderWarning[] = [];\n\t\t\t\t\tmergeAssessmentInto(warnings, id, domain, connectionId, {\n\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\tfailures: [failure],\n\t\t\t\t\t});\n\t\t\t\t\twarning = warnings[0] ?? warning;\n\t\t\t\t}\n\t\t\t} catch (credentialError) {\n\t\t\t\tif (credentialError instanceof AuthenticationError) {\n\t\t\t\t\twarning = toProviderWarning(id, domain, connectionId, credentialError);\n\t\t\t\t}\n\t\t\t}\n\t\t\tresolution = {\n\t\t\t\tstatus: 'unauthorized',\n\t\t\t\twarning: warning,\n\t\t\t};\n\t\t} else {\n\t\t\tresolution = { status: 'undetermined', warning: toProviderWarning(id, domain, connectionId, ex) };\n\t\t}\n\t\treturn { resolution: resolution };\n\t}\n}\n\n/** The repository an Azure remote's `{project}/_git/{repo}` name addresses, as a batch target names it. */\nfunction getAzureRepositoryName(name: string): string {\n\treturn /\\/_git\\/([^/]+)$/i.exec(name)?.[1] ?? name;\n}\n\n/** Whether `path` is `{x}/_git/{repo}`, the Azure repository path the remote parser reads without a project segment. */\nfunction isProjectlessAzurePath(path: string | undefined): boolean {\n\tconst segments = path?.split('/');\n\treturn segments?.length === 3 && decodePathSegment(segments[1]).toLowerCase() === '_git';\n}\n\n/**\n * Narrows a web remote on a self-managed host to the configured installation it lives under: the pinned\n * connection's or, unpinned, the one whose installation path is the longest prefix of the remote's, so two\n * installations behind one host each resolve with their own credentials. The provider is re-matched against the\n * path relative to that installation, because the remote parsers expect the repository path to start at the\n * host root. An SSH remote carries no installation path, so it is left to the host-level checks.\n */\nfunction matchInstallation(\n\tctx: RepositoryResolutionContext,\n\toptions: ResolveRepositoryOptions,\n\tid: GitSelfManagedHostIntegrationId,\n\tconnections: ConfiguredIntegrationDescriptor[],\n\tremote: { scheme: string; domain: string; path: string },\n):\n\t| { provider: RemoteProvider; connectionId: string | undefined; repoDesc: ResourceDescriptor }\n\t| 'host-mismatch'\n\t| 'invalid-remote-url'\n\t| undefined {\n\tif (!isWebRemoteScheme(remote.scheme)) return undefined;\n\n\tconst installationPath = (connection: ConfiguredIntegrationDescriptor | undefined): string | undefined => {\n\t\tconst baseUrl = getSelfManagedBaseUrl(\n\t\t\tid,\n\t\t\tconnection != null ? (connection.baseUrl ?? connection.domain) : options.domain,\n\t\t);\n\t\treturn baseUrl != null ? new URL(baseUrl).pathname.replace(/\\/+$/, '') : undefined;\n\t};\n\n\t// The connection an unpinned read already targets comes first, so it wins a tie between installations.\n\tconst defaultConnection = connections.find(c => c.primary) ?? connections[0];\n\t// A cloud sync serves the host's cloud-scoped default instead, even over a local primary (see\n\t// `getStoredSession`), so an unpinned read reaches the default's installation only when both agree.\n\tconst cloudConnections = connections.filter(c => c.cloud);\n\tconst cloudDefault = cloudConnections.find(c => c.primary) ?? cloudConnections[0];\n\tconst isDefaultReadable =\n\t\tdefaultConnection?.cloud !== false ||\n\t\tcloudDefault == null ||\n\t\tinstallationPath(cloudDefault) === installationPath(defaultConnection);\n\t// Whether an unpinned read can reach this connection's session: the default one is read without an id, any\n\t// other only by a real cloud token id. Session lookup treats every connection id as one, and a local (PAT)\n\t// descriptor's or domain-derived legacy id is not (see `getConfiguredConnectionId`).\n\tconst isReadable = (connection: ConfiguredIntegrationDescriptor | undefined): boolean =>\n\t\toptions.connectionId != null ||\n\t\t(connection === defaultConnection\n\t\t\t? isDefaultReadable\n\t\t\t: connection?.cloud === true && hostFromDomain(connection.id) !== connection.domain);\n\n\tconst candidates: (ConfiguredIntegrationDescriptor | undefined)[] =\n\t\toptions.connectionId != null\n\t\t\t? connections.filter(c => c.id === options.connectionId)\n\t\t\t: [defaultConnection, ...connections.filter(c => c !== defaultConnection)];\n\t// With no configured candidate, the explicit (trusted) `domain` is the only installation address there is.\n\tif (!candidates.length) {\n\t\tcandidates.push(undefined);\n\t}\n\n\tconst remotePath = `/${remote.path}`;\n\tconst remoteSegments = remote.path.split('/');\n\t// The remote's path below an installation, or `undefined` when it isn't under it. Azure DevOps Server runs on\n\t// IIS, which serves its paths decoded and case-insensitively, so it compares segment by segment.\n\tconst relativeTo = (basePath: string): string | undefined => {\n\t\tif (id !== GitSelfManagedHostIntegrationId.AzureDevOpsServer) {\n\t\t\treturn remotePath.startsWith(`${basePath}/`) ? remotePath.slice(basePath.length + 1) : undefined;\n\t\t}\n\n\t\tconst baseSegments = basePath.split('/').filter(Boolean);\n\t\tconst isUnder =\n\t\t\tremoteSegments.length > baseSegments.length &&\n\t\t\tbaseSegments.every(\n\t\t\t\t(segment, i) =>\n\t\t\t\t\tdecodePathSegment(segment).toLowerCase() === decodePathSegment(remoteSegments[i]).toLowerCase(),\n\t\t\t);\n\t\treturn isUnder ? remoteSegments.slice(baseSegments.length).join('/') : undefined;\n\t};\n\tlet match: { connection: ConfiguredIntegrationDescriptor | undefined; basePath: string } | undefined;\n\tlet checked = false;\n\tfor (const connection of candidates) {\n\t\tconst basePath = installationPath(connection);\n\t\tif (basePath == null) continue;\n\n\t\tchecked = true;\n\t\tif (relativeTo(basePath) == null) continue;\n\n\t\t// The longest installation wins; on a tie, a readable connection replaces an unreadable one.\n\t\tconst isBetter =\n\t\t\tmatch == null ||\n\t\t\tbasePath.length > match.basePath.length ||\n\t\t\t(basePath.length === match.basePath.length && !isReadable(match.connection) && isReadable(connection));\n\t\tif (isBetter) {\n\t\t\tmatch = { connection: connection, basePath: basePath };\n\t\t}\n\t}\n\tif (match == null) return checked ? 'host-mismatch' : undefined;\n\t// Reading through the default connection instead would address its installation with a path relative to\n\t// another one, so an unreachable installation fails closed.\n\tif (!isReadable(match.connection)) return 'host-mismatch';\n\n\tconst connectionId =\n\t\toptions.connectionId ?? (match.connection !== defaultConnection ? match.connection?.id : undefined);\n\n\tconst matcher = createRemoteProviderMatcher(\n\t\tbuildMatcherConfigs(ctx, { ...options, providerId: id }, remote.domain),\n\t);\n\tconst rematch = (path: string) => matcher(options.remoteUrl, remote.domain, relativeTo(path) ?? '', remote.scheme);\n\tlet basePath = match.basePath;\n\tlet provider = rematch(basePath);\n\tif (id !== GitSelfManagedHostIntegrationId.AzureDevOpsServer) {\n\t\treturn provider != null\n\t\t\t? { provider: provider, connectionId: connectionId, repoDesc: provider.repoDesc }\n\t\t\t: 'invalid-remote-url';\n\t}\n\n\t// An Azure DevOps Server address may name the collection as well as the virtual directory; the parser expects\n\t// the collection in the path, so re-base on the address's parent when no project was found. A relative\n\t// `{x}/_git/{repo}` re-bases too: `x` is a collection only if the address is the installation rather than a\n\t// collection, which the path can't tell, so the integration settles it against the collections it discovers.\n\tif (basePath && (provider?.providerDesc?.repoDomain == null || isProjectlessAzurePath(relativeTo(basePath)))) {\n\t\tbasePath = basePath.slice(0, basePath.lastIndexOf('/'));\n\t\tprovider = rematch(basePath);\n\t}\n\tif (provider == null) return 'invalid-remote-url';\n\n\t// Requests address the repository's virtual directory from the host root, not from the installation the\n\t// re-match made it relative to; the absolute form also tells a directory apart from a collection of that name.\n\tconst repoDesc = provider.repoDesc;\n\tconst relative = typeof repoDesc.virtualDirectory === 'string' ? repoDesc.virtualDirectory : undefined;\n\tconst virtualDirectory =\n\t\t[...basePath.split('/').filter(Boolean).map(decodePathSegment), ...(relative?.split('/') ?? [])].join('/') ||\n\t\tundefined;\n\treturn {\n\t\tprovider: provider,\n\t\tconnectionId: connectionId,\n\t\trepoDesc: { ...repoDesc, virtualDirectory: virtualDirectory },\n\t};\n}\n\n/**\n * The remote-matcher configs for this resolution: the user's own host remote configs (self-managed/custom\n * domains), plus a synthetic exact-domain entry for an explicit `providerId` + host so a custom domain still maps\n * to the right provider for path parsing.\n */\nfunction buildMatcherConfigs(\n\tctx: RepositoryResolutionContext,\n\toptions: { providerId?: IntegrationIds; host?: string },\n\tparsedDomain: string,\n): RemoteProviderConfig[] {\n\tconst configs: RemoteProviderConfig[] = [];\n\tfor (const cfg of ctx.getRemoteConfigs()) {\n\t\tconst type = remoteProviderTypeForConfig(cfg.type);\n\t\tif (type == null) continue;\n\n\t\t// Forward both domain- and regex-based custom remotes (carrying any protocol override), so a\n\t\t// regex-configured host resolves instead of falling through to `unsupported`.\n\t\tif (cfg.domain) {\n\t\t\tconfigs.push({ type: type, domain: cfg.domain, protocol: cfg.protocol });\n\t\t} else if (cfg.regex) {\n\t\t\tconfigs.push({ type: type, regex: cfg.regex, protocol: cfg.protocol });\n\t\t}\n\t}\n\tif (options.providerId == null) return configs;\n\n\tconst type = remoteProviderTypeForIntegration(options.providerId);\n\tconst domain = parsedDomain || options.host;\n\tif (type == null || !domain) return configs;\n\n\t// The synthetic exact-domain entry is unshifted to the front, so it wins the match over the user's own\n\t// config for the same host. Carry that config's protocol override across (matched by domain or regex,\n\t// mirroring `ignoreSSLErrors`) so a self-managed host configured for a custom protocol — e.g. plain\n\t// `http` — isn't silently downgraded to the provider default here.\n\tconst lowerDomain = domain.toLowerCase();\n\tconst protocol = configs.find(c => {\n\t\tif (c.type !== type) return false;\n\t\tif (c.domain != null) return c.domain.toLowerCase() === lowerDomain;\n\n\t\t// Truthy (not just non-null): an empty regex would compile to a match-everything pattern.\n\t\tif (c.regex) {\n\t\t\ttry {\n\t\t\t\treturn new RegExp(c.regex, 'i').test(lowerDomain);\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t})?.protocol;\n\tconfigs.unshift({ type: type, domain: domain, protocol: protocol });\n\treturn configs;\n}\n"],"mappings":"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"}
@@ -1,8 +1,9 @@
1
1
  import { IssueSorting } from "../../../git/models/issue.js";
2
2
  import { PullRequestFilter, PullRequestSorting } from "../../../git/models/pullRequest.js";
3
3
  import { IntegrationIds } from "../constants.js";
4
- import { ProviderWarning } from "../results.js";
4
+ import { ProviderWarning, ProviderWarningOmissionScope } from "../results.js";
5
5
  import { IssueFilter } from "../providerFilters.js";
6
+ import { ProjectIssuesRecovery } from "../models/issueReads.js";
6
7
  import { IssueSearchCriteriaRejection, PullRequestSearchCriteriaRejection } from "./filters.js";
7
8
  import { UnmergeableIssueSort, UnsupportedIssueSortRejection } from "./ordering.js";
8
9
  //#region ../plus/integrations/src/reads/warnings.d.ts
@@ -61,10 +62,16 @@ export type IncompleteReadCause = 'interrupted' | 'page-budget' | 'exhausted' |
61
62
  * is the same shape `assessCollectionMetadata` derives from SDK metadata, so a consumer never has to care
62
63
  * which layer noticed the gap.
63
64
  *
64
- * No `scope`: this applies to the whole read, not to one repository or project the way an SDK-attributed
65
- * omission can, so a consumer acting on `recovery` re-runs the read itself.
65
+ * `scope` names the one project (or repository) the gap is confined to, as an SDK-attributed omission does; without it
66
+ * the warning applies to the whole read, so a consumer acting on `recovery` re-runs the read itself.
66
67
  */
67
- export declare function incompleteReadWarning(id: IntegrationIds, domain: string | undefined, connectionId: string | undefined, message: string, cause: IncompleteReadCause): ProviderWarning;
68
+ export declare function incompleteReadWarning(id: IntegrationIds, domain: string | undefined, connectionId: string | undefined, message: string, cause: IncompleteReadCause, scope?: ProviderWarningOmissionScope): ProviderWarning;
69
+ /**
70
+ * {@link incompleteReadWarning} for one project of a tracker read that stopped short with nothing in its metadata to say
71
+ * why: a drain cut by its page backstop (`narrow-scope`) or otherwise left incomplete (`none`). Confined to `scope`,
72
+ * which names the project the way the provider's own failures do.
73
+ */
74
+ export declare function projectCutWarning(id: IntegrationIds, domain: string | undefined, connectionId: string | undefined, project: string, recovery: ProjectIssuesRecovery, scope: ProviderWarningOmissionScope): ProviderWarning;
68
75
  /**
69
76
  * The read surfaces {@link truncationWarning} phrases for. `'Account-wide issue search'` names the composite
70
77
  * read that spans several provider searches rather than one surface.
@@ -93,20 +93,30 @@ function recoveryForIncompleteReadCause(cause) {
93
93
  * is the same shape `assessCollectionMetadata` derives from SDK metadata, so a consumer never has to care
94
94
  * which layer noticed the gap.
95
95
  *
96
- * No `scope`: this applies to the whole read, not to one repository or project the way an SDK-attributed
97
- * omission can, so a consumer acting on `recovery` re-runs the read itself.
96
+ * `scope` names the one project (or repository) the gap is confined to, as an SDK-attributed omission does; without it
97
+ * the warning applies to the whole read, so a consumer acting on `recovery` re-runs the read itself.
98
98
  */
99
- function incompleteReadWarning(id, domain, connectionId, message, cause) {
99
+ function incompleteReadWarning(id, domain, connectionId, message, cause, scope) {
100
100
  const warning = otherWarning(id, domain, connectionId, message);
101
101
  if (cause === "interrupted") return warning;
102
102
  return {
103
103
  ...warning,
104
104
  omission: {
105
105
  kind: "pagination-incomplete",
106
- recovery: recoveryForIncompleteReadCause(cause)
106
+ recovery: recoveryForIncompleteReadCause(cause),
107
+ ...scope != null ? { scope: { ...scope } } : {}
107
108
  }
108
109
  };
109
110
  }
111
+ /**
112
+ * {@link incompleteReadWarning} for one project of a tracker read that stopped short with nothing in its metadata to say
113
+ * why: a drain cut by its page backstop (`narrow-scope`) or otherwise left incomplete (`none`). Confined to `scope`,
114
+ * which names the project the way the provider's own failures do.
115
+ */
116
+ function projectCutWarning(id, domain, connectionId, project, recovery, scope) {
117
+ const [message, cause] = recovery === "narrow-scope" ? [`Project '${project}' holds more issues than one read can return; narrow the scope to read the rest.`, "scope-too-large"] : [`Some issues of project '${project}' were omitted; the provider returned an incomplete result.`, "exhausted"];
118
+ return incompleteReadWarning(id, domain, connectionId, message, cause, scope);
119
+ }
110
120
  /** {@link incompleteReadWarning} for a paged or drained read, phrased per surface. */
111
121
  function truncationWarning(id, domain, connectionId, readKind, cause) {
112
122
  return incompleteReadWarning(id, domain, connectionId, truncationMessage(id, readKind, cause), cause);
@@ -340,6 +350,6 @@ function issuesUnsupportedWarning(id, domain, connectionId) {
340
350
  return unsupportedWarning(id, domain, connectionId, `Issues are not supported by '${id}'; use a dedicated issue integration (e.g. Jira) instead.`);
341
351
  }
342
352
  //#endregion
343
- export { cappedBesideBudgetWarning, gitHostOnlySurfaceWarning, incompleteReadWarning, issueSearchCapResultWarning, issueTrackerOnlySurfaceWarning, issuesUnsupportedWarning, noConnectionWarning, otherWarning, pullRequestSearchCapResultWarning, truncationWarning, unmergeableIssueSortWarning, unsupportedAccountWideIssueFiltersWarning, unsupportedAccountWidePullRequestFiltersWarning, unsupportedFiltersWarning, unsupportedIssueSearchCriteriaWarning, unsupportedIssueSortWarning, unsupportedPullRequestSearchCriteriaWarning, unsupportedWarning, unusableSearchScopeMessage };
353
+ export { cappedBesideBudgetWarning, gitHostOnlySurfaceWarning, incompleteReadWarning, issueSearchCapResultWarning, issueTrackerOnlySurfaceWarning, issuesUnsupportedWarning, noConnectionWarning, otherWarning, projectCutWarning, pullRequestSearchCapResultWarning, truncationWarning, unmergeableIssueSortWarning, unsupportedAccountWideIssueFiltersWarning, unsupportedAccountWidePullRequestFiltersWarning, unsupportedFiltersWarning, unsupportedIssueSearchCriteriaWarning, unsupportedIssueSortWarning, unsupportedPullRequestSearchCriteriaWarning, unsupportedWarning, unusableSearchScopeMessage };
344
354
 
345
355
  //# sourceMappingURL=warnings.js.map