@gitkraken/core-gitlens 0.7.1 → 0.8.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 (188) hide show
  1. package/CHANGELOG.md +48 -1
  2. package/dist/git/cache.d.ts +18 -5
  3. package/dist/git/cache.js +26 -8
  4. package/dist/git/cache.js.map +1 -1
  5. package/dist/git/context.d.ts +5 -1
  6. package/dist/git/context.js.map +1 -1
  7. package/dist/git/errors.d.ts +28 -1
  8. package/dist/git/errors.js +45 -1
  9. package/dist/git/errors.js.map +1 -1
  10. package/dist/git/providers/branches.d.ts +5 -1
  11. package/dist/git/providers/commits.d.ts +24 -1
  12. package/dist/git/providers/config.d.ts +7 -1
  13. package/dist/git/providers/operations.d.ts +6 -0
  14. package/dist/git/providers/provider.d.ts +17 -0
  15. package/dist/git/providers/refs.d.ts +38 -3
  16. package/dist/git/providers/revision.d.ts +5 -1
  17. package/dist/git/providers/staging.d.ts +12 -0
  18. package/dist/git/providers/worktrees.d.ts +23 -3
  19. package/dist/git/run.types.d.ts +6 -0
  20. package/dist/git-cli/cliGitProvider.d.ts +17 -1
  21. package/dist/git-cli/cliGitProvider.js +37 -1
  22. package/dist/git-cli/cliGitProvider.js.map +1 -1
  23. package/dist/git-cli/exec/git.d.ts +12 -2
  24. package/dist/git-cli/exec/git.js +48 -17
  25. package/dist/git-cli/exec/git.js.map +1 -1
  26. package/dist/git-cli/exec/gitQueue.d.ts +4 -1
  27. package/dist/git-cli/exec/gitQueue.js +1 -1
  28. package/dist/git-cli/exec/gitQueue.js.map +1 -1
  29. package/dist/git-cli/providers/branches.d.ts +10 -1
  30. package/dist/git-cli/providers/branches.js +15 -8
  31. package/dist/git-cli/providers/branches.js.map +1 -1
  32. package/dist/git-cli/providers/commits.d.ts +23 -1
  33. package/dist/git-cli/providers/commits.js +57 -8
  34. package/dist/git-cli/providers/commits.js.map +1 -1
  35. package/dist/git-cli/providers/config.js +4 -3
  36. package/dist/git-cli/providers/config.js.map +1 -1
  37. package/dist/git-cli/providers/operations.d.ts +1 -0
  38. package/dist/git-cli/providers/operations.js +39 -3
  39. package/dist/git-cli/providers/operations.js.map +1 -1
  40. package/dist/git-cli/providers/patch.js +20 -41
  41. package/dist/git-cli/providers/patch.js.map +1 -1
  42. package/dist/git-cli/providers/pausedOperations.js +2 -2
  43. package/dist/git-cli/providers/pausedOperations.js.map +1 -1
  44. package/dist/git-cli/providers/refs.d.ts +15 -3
  45. package/dist/git-cli/providers/refs.js +115 -10
  46. package/dist/git-cli/providers/refs.js.map +1 -1
  47. package/dist/git-cli/providers/revision.d.ts +3 -1
  48. package/dist/git-cli/providers/revision.js +9 -5
  49. package/dist/git-cli/providers/revision.js.map +1 -1
  50. package/dist/git-cli/providers/staging.d.ts +8 -0
  51. package/dist/git-cli/providers/staging.js +59 -7
  52. package/dist/git-cli/providers/staging.js.map +1 -1
  53. package/dist/git-cli/providers/worktrees.d.ts +31 -2
  54. package/dist/git-cli/providers/worktrees.js +75 -7
  55. package/dist/git-cli/providers/worktrees.js.map +1 -1
  56. package/dist/ipc/discovery.js +1 -1
  57. package/dist/plus/agents/providers/claudeCodeTranscript.js +6 -6
  58. package/dist/plus/agents/providers/claudeCodeTranscript.js.map +1 -1
  59. package/dist/plus/ai/providers/openAICompatibleProviderBase.js +1 -1
  60. package/dist/plus/git-github/api/github.d.ts +64 -3
  61. package/dist/plus/git-github/api/github.js +237 -24
  62. package/dist/plus/git-github/api/github.js.map +1 -1
  63. package/dist/plus/git-github/providers/github/branches.d.ts +3 -1
  64. package/dist/plus/git-github/providers/github/branches.js +2 -1
  65. package/dist/plus/git-github/providers/github/branches.js.map +1 -1
  66. package/dist/plus/git-github/providers/github/refs.d.ts +10 -3
  67. package/dist/plus/git-github/providers/github/refs.js +4 -4
  68. package/dist/plus/git-github/providers/github/refs.js.map +1 -1
  69. package/dist/plus/git-github/providers/github/revision.d.ts +3 -1
  70. package/dist/plus/git-github/providers/github/revision.js +1 -1
  71. package/dist/plus/git-github/providers/github/revision.js.map +1 -1
  72. package/dist/plus/integrations/collectionMetadata.d.ts +48 -3
  73. package/dist/plus/integrations/collectionMetadata.js +138 -8
  74. package/dist/plus/integrations/collectionMetadata.js.map +1 -1
  75. package/dist/plus/integrations/errors.d.ts +3 -1
  76. package/dist/plus/integrations/errors.js +7 -3
  77. package/dist/plus/integrations/errors.js.map +1 -1
  78. package/dist/plus/integrations/index.d.ts +6 -3
  79. package/dist/plus/integrations/index.js.map +1 -1
  80. package/dist/plus/integrations/integrationService.d.ts +36 -10
  81. package/dist/plus/integrations/integrationService.js +16 -3
  82. package/dist/plus/integrations/integrationService.js.map +1 -1
  83. package/dist/plus/integrations/manager.d.ts +107 -37
  84. package/dist/plus/integrations/models/gitHostIntegration.d.ts +129 -14
  85. package/dist/plus/integrations/models/gitHostIntegration.js +142 -18
  86. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
  87. package/dist/plus/integrations/models/integration.d.ts +117 -11
  88. package/dist/plus/integrations/models/integration.js +175 -13
  89. package/dist/plus/integrations/models/integration.js.map +1 -1
  90. package/dist/plus/integrations/models/issuesIntegration.d.ts +18 -5
  91. package/dist/plus/integrations/models/issuesIntegration.js +42 -10
  92. package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
  93. package/dist/plus/integrations/providers/azure/azure.d.ts +1 -1
  94. package/dist/plus/integrations/providers/azure/azure.js +14 -18
  95. package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
  96. package/dist/plus/integrations/providers/azure/models.d.ts +29 -0
  97. package/dist/plus/integrations/providers/azure/models.js +112 -1
  98. package/dist/plus/integrations/providers/azure/models.js.map +1 -1
  99. package/dist/plus/integrations/providers/azureDevOps.d.ts +73 -3
  100. package/dist/plus/integrations/providers/azureDevOps.js +117 -3
  101. package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
  102. package/dist/plus/integrations/providers/azureDevOpsServer.js +2 -1
  103. package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
  104. package/dist/plus/integrations/providers/bitbucket/bitbucket.d.ts +61 -0
  105. package/dist/plus/integrations/providers/bitbucket/bitbucket.js +165 -23
  106. package/dist/plus/integrations/providers/bitbucket/bitbucket.js.map +1 -1
  107. package/dist/plus/integrations/providers/bitbucket/models.d.ts +6 -1
  108. package/dist/plus/integrations/providers/bitbucket/models.js +4 -3
  109. package/dist/plus/integrations/providers/bitbucket/models.js.map +1 -1
  110. package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js +2 -2
  111. package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js.map +1 -1
  112. package/dist/plus/integrations/providers/bitbucket-server.d.ts +48 -2
  113. package/dist/plus/integrations/providers/bitbucket-server.js +51 -1
  114. package/dist/plus/integrations/providers/bitbucket-server.js.map +1 -1
  115. package/dist/plus/integrations/providers/bitbucket.d.ts +45 -2
  116. package/dist/plus/integrations/providers/bitbucket.js +41 -2
  117. package/dist/plus/integrations/providers/bitbucket.js.map +1 -1
  118. package/dist/plus/integrations/providers/github.d.ts +49 -5
  119. package/dist/plus/integrations/providers/github.js +102 -7
  120. package/dist/plus/integrations/providers/github.js.map +1 -1
  121. package/dist/plus/integrations/providers/gitlab/gitlab.d.ts +35 -0
  122. package/dist/plus/integrations/providers/gitlab/gitlab.js +132 -15
  123. package/dist/plus/integrations/providers/gitlab/gitlab.js.map +1 -1
  124. package/dist/plus/integrations/providers/gitlab/models.d.ts +1 -0
  125. package/dist/plus/integrations/providers/gitlab/models.js +1 -1
  126. package/dist/plus/integrations/providers/gitlab/models.js.map +1 -1
  127. package/dist/plus/integrations/providers/gitlab.d.ts +49 -2
  128. package/dist/plus/integrations/providers/gitlab.js +80 -4
  129. package/dist/plus/integrations/providers/gitlab.js.map +1 -1
  130. package/dist/plus/integrations/providers/jira-server.d.ts +3 -3
  131. package/dist/plus/integrations/providers/jira-server.js +5 -8
  132. package/dist/plus/integrations/providers/jira-server.js.map +1 -1
  133. package/dist/plus/integrations/providers/jira.d.ts +9 -0
  134. package/dist/plus/integrations/providers/jira.js +13 -1
  135. package/dist/plus/integrations/providers/jira.js.map +1 -1
  136. package/dist/plus/integrations/providers/linear.js +1 -1
  137. package/dist/plus/integrations/providers/models.d.ts +13 -2
  138. package/dist/plus/integrations/providers/models.js +11 -1
  139. package/dist/plus/integrations/providers/models.js.map +1 -1
  140. package/dist/plus/integrations/providers/providerErrors.d.ts +9 -1
  141. package/dist/plus/integrations/providers/providerErrors.js +10 -1
  142. package/dist/plus/integrations/providers/providerErrors.js.map +1 -1
  143. package/dist/plus/integrations/providers/providersApi.d.ts +60 -1
  144. package/dist/plus/integrations/providers/providersApi.js +137 -2
  145. package/dist/plus/integrations/providers/providersApi.js.map +1 -1
  146. package/dist/plus/integrations/providers/utils/providerPaging.d.ts +42 -0
  147. package/dist/plus/integrations/providers/utils/providerPaging.js +58 -1
  148. package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
  149. package/dist/plus/integrations/providers/utils.d.ts +1 -1
  150. package/dist/plus/integrations/reads/broaden.js +1 -1
  151. package/dist/plus/integrations/reads/context.d.ts +1 -1
  152. package/dist/plus/integrations/reads/counts.js +1 -1
  153. package/dist/plus/integrations/reads/currentAccount.d.ts +35 -0
  154. package/dist/plus/integrations/reads/currentAccount.js +46 -0
  155. package/dist/plus/integrations/reads/currentAccount.js.map +1 -0
  156. package/dist/plus/integrations/reads/drains.d.ts +5 -2
  157. package/dist/plus/integrations/reads/drains.js +8 -4
  158. package/dist/plus/integrations/reads/drains.js.map +1 -1
  159. package/dist/plus/integrations/reads/hierarchy.js +1 -1
  160. package/dist/plus/integrations/reads/hierarchy.utils.js +1 -1
  161. package/dist/plus/integrations/reads/issueBatch.d.ts +72 -12
  162. package/dist/plus/integrations/reads/issueBatch.js +193 -48
  163. package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
  164. package/dist/plus/integrations/reads/issueTracker.js +1 -1
  165. package/dist/plus/integrations/reads/issues.js +1 -1
  166. package/dist/plus/integrations/reads/pullRequestBatch.d.ts +57 -0
  167. package/dist/plus/integrations/reads/pullRequestBatch.js +80 -0
  168. package/dist/plus/integrations/reads/pullRequestBatch.js.map +1 -0
  169. package/dist/plus/integrations/reads/pullRequestBranches.d.ts +58 -0
  170. package/dist/plus/integrations/reads/pullRequestBranches.js +111 -0
  171. package/dist/plus/integrations/reads/pullRequestBranches.js.map +1 -0
  172. package/dist/plus/integrations/reads/pullRequests.js +3 -3
  173. package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
  174. package/dist/plus/integrations/reads/resolveRepository.js +1 -1
  175. package/dist/plus/integrations/reads/sweeps.js +4 -4
  176. package/dist/plus/integrations/reads/sweeps.js.map +1 -1
  177. package/dist/plus/integrations/results.d.ts +87 -6
  178. package/dist/plus/integrations/results.js +9 -2
  179. package/dist/plus/integrations/results.js.map +1 -1
  180. package/dist/utils/env/node/exec.js +3 -3
  181. package/dist/utils/env/node/exec.js.map +1 -1
  182. package/docs/integrations.md +175 -35
  183. package/docs/kepler-git-write-api-parity.md +239 -0
  184. package/docs/kepler-read-api-parity.md +75 -28
  185. package/package.json +1 -1
  186. package/dist/plus/integrations/reads/trackerIssue.d.ts +0 -33
  187. package/dist/plus/integrations/reads/trackerIssue.js +0 -60
  188. package/dist/plus/integrations/reads/trackerIssue.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"gitlab.js","names":[],"sources":["../../../../../plus/integrations/src/providers/gitlab.ts"],"sourcesContent":["import type { CollectionMetadata, CollectionScopeFailure } from '@gitkraken/provider-apis';\nimport * as l10n from '@vscode/l10n';\nimport type { Account } from '@gitlens/git/models/author.js';\nimport type { DefaultBranch } from '@gitlens/git/models/defaultBranch.js';\nimport type { Issue, IssueShape } from '@gitlens/git/models/issue.js';\nimport type { IssueOrPullRequest } from '@gitlens/git/models/issueOrPullRequest.js';\nimport type {\n\tPullRequest,\n\tPullRequestMergeMethod,\n\tPullRequestState,\n\tPullRequestStateFilter,\n} from '@gitlens/git/models/pullRequest.js';\nimport type { RepositoryMetadata } from '@gitlens/git/models/repositoryMetadata.js';\nimport type { RepositoryDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport type { PullRequestUrlIdentity } from '@gitlens/git/utils/pullRequest.utils.js';\nimport { CancellationError } from '@gitlens/utils/cancellation.js';\nimport type { Emitter } from '@gitlens/utils/event.js';\nimport { uniqueBy } from '@gitlens/utils/iterable.js';\nimport { batch } from '@gitlens/utils/promise.js';\nimport type { IntegrationAuthenticationProviderDescriptor } from '../authentication/integrationAuthenticationProvider.js';\nimport type { IntegrationAuthenticationService } from '../authentication/integrationAuthenticationService.js';\nimport type { ProviderAuthenticationSession } from '../authentication/models.js';\nimport { toTokenWithInfo } from '../authentication/models.js';\nimport { toCollectionScopeFailure } from '../collectionMetadata.js';\nimport { GitCloudHostIntegrationId, GitSelfManagedHostIntegrationId } from '../constants.js';\nimport type { IntegrationServiceContext } from '../context.js';\nimport { IntegrationReadUnavailableError } from '../errors.js';\nimport type { IntegrationConnectionChangeEvent } from '../integrationService.js';\nimport type { SearchMyPullRequestsOptions, SearchPullRequestsOptions } from '../models/gitHostIntegration.js';\nimport { GitHostIntegration } from '../models/gitHostIntegration.js';\nimport type { AccountWideIssuesResult, SearchMyIssuesOptions } from '../models/integration.js';\nimport type { GitLabIntegrationIds } from './gitlab/gitlab.utils.js';\nimport { getGitLabPullRequestIdentityFromMaybeUrl, matchesGitLabOrgNamespace } from './gitlab/gitlab.utils.js';\nimport { fromGitLabMergeRequestProvidersApi } from './gitlab/models.js';\nimport type {\n\tProviderApiPagedResult,\n\tProviderHierarchyResult,\n\tProviderOrganization,\n\tProviderPullRequest,\n\tProviderRepository,\n} from './models.js';\nimport {\n\tgetProviderPullRequestIdentity,\n\tIssueFilter,\n\tProviderPullRequestReviewState,\n\tprovidersMetadata,\n\tPullRequestFilter,\n\ttoIssueShape,\n\ttoProviderPullRequestStates,\n} from './models.js';\nimport type { ProvidersApi } from './providersApi.js';\nimport { collectProviderPagedResult, mergeCollectionMetadata } from './utils/providerPaging.js';\n\nconst metadata = providersMetadata[GitCloudHostIntegrationId.GitLab];\nconst authProvider: IntegrationAuthenticationProviderDescriptor = Object.freeze({\n\tid: metadata.id,\n\tscopes: metadata.scopes,\n});\n\nconst cloudEnterpriseMetadata = providersMetadata[GitSelfManagedHostIntegrationId.CloudGitLabSelfHosted];\nconst cloudEnterpriseAuthProvider: IntegrationAuthenticationProviderDescriptor = Object.freeze({\n\tid: cloudEnterpriseMetadata.id,\n\tscopes: cloudEnterpriseMetadata.scopes,\n});\n\ntype GitLabPullRequestAssociation = 'assigned' | 'authored' | 'reviewRequested';\ntype GitLabPullRequestFacetCursor = Partial<Record<GitLabPullRequestAssociation, string>>;\n\n/**\n * The account-wide relationships GitLab can express, and the `scope` each maps to. `undefined` means GitLab has\n * no equivalent axis (it has neither `Mention` nor `reviewed-by`), and this map is the single declaration of\n * that. Exhaustive over `PullRequestFilter` on purpose: a new member must be decided for GitLab here rather\n * than compiling through as silently unsupported. Exported for the advertised-vs-expressible capability test,\n * which is what keeps it agreeing with `supportedAccountWidePullRequestFilters`; no runtime consumer imports it.\n */\nexport const gitLabAssociationForFilter: Record<PullRequestFilter, GitLabPullRequestAssociation | undefined> = {\n\t[PullRequestFilter.Assignee]: 'assigned',\n\t[PullRequestFilter.Author]: 'authored',\n\t[PullRequestFilter.ReviewRequested]: 'reviewRequested',\n\t[PullRequestFilter.Reviewed]: undefined,\n\t[PullRequestFilter.Mention]: undefined,\n};\n\nfunction parseGitLabPullRequestFacetCursor(cursor: string | undefined): GitLabPullRequestFacetCursor {\n\tif (!cursor) return {};\n\n\ttry {\n\t\tconst parsed = JSON.parse(cursor) as { type?: unknown; cursors?: unknown };\n\t\tif (parsed.type !== 'gitlab-associations' || parsed.cursors == null || typeof parsed.cursors !== 'object') {\n\t\t\treturn {};\n\t\t}\n\n\t\tconst cursors = parsed.cursors as Record<string, unknown>;\n\t\treturn Object.fromEntries(\n\t\t\t(['assigned', 'authored', 'reviewRequested'] as const)\n\t\t\t\t.filter(association => typeof cursors[association] === 'string' && cursors[association] !== '')\n\t\t\t\t.map(association => [association, cursors[association] as string]),\n\t\t);\n\t} catch {\n\t\treturn {};\n\t}\n}\n\nexport type GitLabRepositoryDescriptor = RepositoryDescriptor;\n\n/** How many SSH signing-key lookups (account resolve + keys fetch) to run concurrently, to avoid a request burst. */\nconst sshSigningKeyResolveBatchSize = 10;\n\nabstract class GitLabIntegrationBase<ID extends GitLabIntegrationIds> extends GitHostIntegration<\n\tID,\n\tGitLabRepositoryDescriptor\n> {\n\tprotected abstract apiBaseUrlFor(session: ProviderAuthenticationSession): string;\n\n\t/** The installation base the PAT-based reads take on self-hosted GitLab; gitlab.com passes none. */\n\tprotected enterpriseBaseUrlFor(session: ProviderAuthenticationSession): string | undefined {\n\t\treturn this.isEnterprise ? this.getSelfManagedApiBaseUrl(session) : undefined;\n\t}\n\n\t/** Self-hosted GitLab uses PAT semantics and a domain-based API base; gitlab.com does not. */\n\tprotected get isEnterprise(): boolean {\n\t\treturn this.id === GitSelfManagedHostIntegrationId.CloudGitLabSelfHosted;\n\t}\n\n\tprotected override async getProviderAccountForCommit(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitLabRepositoryDescriptor,\n\t\trev: string,\n\t\toptions?: {\n\t\t\tavatarSize?: number;\n\t\t},\n\t): Promise<Account | undefined> {\n\t\treturn (await this.authenticationService.apis.gitlab)?.getAccountForCommit(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\trev,\n\t\t\t{\n\t\t\t\t...options,\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderAccountForEmail(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitLabRepositoryDescriptor,\n\t\temail: string,\n\t\toptions?: {\n\t\t\tavatarSize?: number;\n\t\t},\n\t): Promise<Account | undefined> {\n\t\treturn (await this.authenticationService.apis.gitlab)?.getAccountForEmail(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\temail,\n\t\t\t{\n\t\t\t\t...options,\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderSshSigningKeysForEmails(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitLabRepositoryDescriptor,\n\t\temails: string[],\n\t): Promise<Map<string, string[]>> {\n\t\tconst result = new Map<string, string[]>();\n\n\t\tconst api = await this.authenticationService.apis.gitlab;\n\t\tif (api == null) return result;\n\n\t\tconst token = toTokenWithInfo(this.id, session);\n\n\t\t// GitLab resolves users one email at a time (no batch search). Run them in bounded batches rather than firing all\n\t\t// (up to providerVerifyLimit) account + key lookups at once, to avoid a request burst that trips rate limiting.\n\t\tawait batch(emails, sshSigningKeyResolveBatchSize, async email => {\n\t\t\tconst account = await this.getProviderAccountForEmail(session, repo, email);\n\t\t\tif (account?.id == null) return;\n\n\t\t\tconst keys = await api.getUserSigningKeys(this, token, account.id, {\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t});\n\t\t\tresult.set(\n\t\t\t\temail.toLowerCase(),\n\t\t\t\tkeys.map(k => k.key),\n\t\t\t);\n\t\t});\n\n\t\treturn result;\n\t}\n\n\tprotected override async getProviderDefaultBranch(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitLabRepositoryDescriptor,\n\t): Promise<DefaultBranch | undefined> {\n\t\treturn (await this.authenticationService.apis.gitlab)?.getDefaultBranch(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\t{\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderLinkedIssueOrPullRequest(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitLabRepositoryDescriptor,\n\t\t{ id }: { id: string; key: string },\n\t): Promise<IssueOrPullRequest | undefined> {\n\t\treturn (await this.authenticationService.apis.gitlab)?.getIssueOrPullRequest(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\tNumber(id),\n\t\t\t{\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderIssue(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitLabRepositoryDescriptor,\n\t\tid: string,\n\t): Promise<Issue | undefined> {\n\t\tconst api = await this.authenticationService.apis.gitlab;\n\t\tconst providerApi = await this.getProvidersApi();\n\t\tif (!api || !repo || !id) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst repoId = await api.getProjectId(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\tthis.apiBaseUrlFor(session),\n\t\t\tundefined,\n\t\t);\n\t\tif (!repoId) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst apiResult = await providerApi.getIssue(\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t{ namespace: repo.owner, name: repo.name, number: id },\n\t\t\t{\n\t\t\t\tisPAT: this.isEnterprise,\n\t\t\t\tbaseUrl: this.enterpriseBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t\tconst issue = apiResult != null ? toIssueShape(apiResult, this) : undefined;\n\t\treturn issue != null ? { ...issue, type: 'issue' } : undefined;\n\t}\n\n\tprotected override async getProviderPullRequestForBranch(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitLabRepositoryDescriptor,\n\t\tbranch: string,\n\t\toptions?: {\n\t\t\tavatarSize?: number;\n\t\t\tinclude?: PullRequestState[];\n\t\t},\n\t): Promise<PullRequest | undefined> {\n\t\tconst { include, ...opts } = options ?? {};\n\n\t\tconst toGitLabMergeRequestState = (await import(/* webpackChunkName: \"integrations\" */ './gitlab/models.js'))\n\t\t\t.toGitLabMergeRequestState;\n\t\treturn (await this.authenticationService.apis.gitlab)?.getPullRequestForBranch(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\tbranch,\n\t\t\t{\n\t\t\t\t...opts,\n\t\t\t\tinclude: include?.map(s => toGitLabMergeRequestState(s)),\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderPullRequestForCommit(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitLabRepositoryDescriptor,\n\t\trev: string,\n\t): Promise<PullRequest | undefined> {\n\t\treturn (await this.authenticationService.apis.gitlab)?.getPullRequestForCommit(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\trev,\n\t\t\t{\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderPullRequest(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: GitLabRepositoryDescriptor,\n\t\tid: string,\n\t): Promise<PullRequest | undefined> {\n\t\treturn (await this.authenticationService.apis.gitlab)?.getPullRequest(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\tresource.owner,\n\t\t\tresource.name,\n\t\t\tparseInt(id, 10),\n\t\t\t{\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tpublic override async getRepoInfo(repo: {\n\t\towner: string;\n\t\tname: string;\n\t\tproject?: string;\n\t\tconnectionId?: string;\n\t}): Promise<ProviderRepository | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\t// `connectionId` targets a specific account (multi-account); omitted reads the primary.\n\t\tconst session = await this.resolveReadSession(repo.connectionId, undefined);\n\t\tif (session == null) return undefined;\n\n\t\treturn api.getRepo(toTokenWithInfo(this.id, session), repo.owner, repo.name, repo.project, {\n\t\t\tisPAT: this.isEnterprise,\n\t\t\tbaseUrl: this.enterpriseBaseUrlFor(session),\n\t\t});\n\t}\n\n\tprotected override async getProviderRepositoryMetadata(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitLabRepositoryDescriptor,\n\t\tcancellation?: AbortSignal,\n\t): Promise<RepositoryMetadata | undefined> {\n\t\treturn (await this.authenticationService.apis.gitlab)?.getRepositoryMetadata(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\t{\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\tprotected override async getProviderOrganizationsForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<ProviderHierarchyResult<ProviderOrganization> | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst result = await api.getGitlabGroupsForCurrentUser(toTokenWithInfo(this.id, session), {\n\t\t\tisPAT: this.isEnterprise,\n\t\t\tbaseUrl: this.enterpriseBaseUrlFor(session),\n\t\t});\n\t\treturn {\n\t\t\tvalues: result.values.map(g => ({ id: g.id, providerId: this.id, name: g.fullPath, url: g.webUrl })),\n\t\t\t...(result.truncated ? { truncated: true } : {}),\n\t\t\t...(result.metadata != null ? { metadata: result.metadata } : {}),\n\t\t};\n\t}\n\n\t/**\n\t * GitLab has no group-scoped repos endpoint, so each page is a page of the current user's member repos\n\t * across all namespaces, filtered to `org` and its subgroups (mirroring gkcli's `gitlabOrgMatches`).\n\t * Follow `paging.cursor` to page the full list; repos the user isn't a member of aren't returned.\n\t * Because the filter is applied per page, a page can come back with `values: []` while\n\t * `paging.more` is still `true` (e.g. a page full of repos outside `org`) — don't treat an empty\n\t * page as the end of pagination, only `paging.more`/`collectPagedResults` reaching exhaustion.\n\t */\n\tprotected override async getProviderRepositoriesForOrg(\n\t\tsession: ProviderAuthenticationSession,\n\t\torg: string,\n\t\toptions?: { cursor?: string },\n\t): Promise<ProviderHierarchyResult<ProviderRepository> | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst result = await api.getReposForCurrentUser(toTokenWithInfo(this.id, session), {\n\t\t\tisPAT: this.isEnterprise,\n\t\t\tbaseUrl: this.enterpriseBaseUrlFor(session),\n\t\t\tcursor: options?.cursor,\n\t\t});\n\t\treturn {\n\t\t\tvalues: result.values.filter(r => matchesGitLabOrgNamespace(r.namespace, org)),\n\t\t\t...(result.paging != null ? { paging: result.paging } : {}),\n\t\t};\n\t}\n\n\tprotected override async getProviderRepositoriesForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t\toptions?: { cursor?: string },\n\t): Promise<ProviderHierarchyResult<ProviderRepository> | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\t// GitLab's membership read is already user-affiliated, so the account-wide walk is the unfiltered\n\t\t// version of the per-org read above (which pages the same source and filters by namespace).\n\t\treturn api.getReposForCurrentUser(toTokenWithInfo(this.id, session), {\n\t\t\tisPAT: this.isEnterprise,\n\t\t\tbaseUrl: this.enterpriseBaseUrlFor(session),\n\t\t\tcursor: options?.cursor,\n\t\t});\n\t}\n\n\tprotected override async searchProviderMyPullRequests(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepos?: GitLabRepositoryDescriptor[],\n\t\t_cancellation?: AbortSignal,\n\t\toptions?: SearchMyPullRequestsOptions,\n\t): Promise<PullRequest[] | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\t// Resolve the username from THIS session's token (multi-account: `session` may be a non-primary\n\t\t// connection); `getCurrentAccount()` would use the primary `_session` and filter by the wrong user.\n\t\tconst username = (await this.getProviderCurrentAccount(session))?.username;\n\t\tif (!username) {\n\t\t\treturn Promise.resolve([]);\n\t\t}\n\n\t\tconst apiResult = await api.getPullRequestsForUser(toTokenWithInfo(this.id, session), username, {\n\t\t\tisPAT: this.isEnterprise,\n\t\t\tbaseUrl: this.enterpriseBaseUrlFor(session),\n\t\t\tstates: toProviderPullRequestStates(options?.state),\n\t\t});\n\n\t\tif (apiResult == null) {\n\t\t\treturn Promise.resolve([]);\n\t\t}\n\n\t\t// now I'm going to filter prs from the result according to the repos parameter\n\t\tlet prs;\n\t\tif (repos != null) {\n\t\t\tconst repoMap = new Map<string, GitLabRepositoryDescriptor>();\n\t\t\tfor (const repo of repos) {\n\t\t\t\trepoMap.set(`${repo.owner}/${repo.name}`, repo);\n\t\t\t}\n\t\t\tprs = apiResult.values.filter(pr => {\n\t\t\t\tconst repo = repoMap.get(`${pr.repository.owner.login}/${pr.repository.name}`);\n\t\t\t\treturn repo != null;\n\t\t\t});\n\t\t} else {\n\t\t\tprs = apiResult.values;\n\t\t}\n\n\t\tconst results: IterableIterator<PullRequest> = uniqueBy(\n\t\t\t[\n\t\t\t\t...prs\n\t\t\t\t\t.filter(pr => {\n\t\t\t\t\t\tconst isAssignee = pr.assignees?.some(a => a.username === username);\n\t\t\t\t\t\tconst isRequestedReviewer = pr.reviews?.some(\n\t\t\t\t\t\t\t// Match only reviews assigned to the current user; a bare `state === ReviewRequested`\n\t\t\t\t\t\t\t// check would also match reviews requested from OTHER people, leaking their MRs in.\n\t\t\t\t\t\t\treview =>\n\t\t\t\t\t\t\t\treview.reviewer?.username === username &&\n\t\t\t\t\t\t\t\treview.state === ProviderPullRequestReviewState.ReviewRequested,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst isAuthor = pr.author?.username === username;\n\t\t\t\t\t\t// It seems like GitLab doesn't give us mentioned PRs.\n\t\t\t\t\t\t// const isMentioned = ???;\n\n\t\t\t\t\t\treturn isAssignee || isRequestedReviewer || isAuthor;\n\t\t\t\t\t})\n\t\t\t\t\t.map(pr => fromGitLabMergeRequestProvidersApi(pr, this)),\n\t\t\t],\n\t\t\tr => r.url,\n\t\t\t(original, _current) => original,\n\t\t);\n\n\t\treturn [...results];\n\t}\n\n\tprotected override async getProviderMyPullRequestsForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t\toptions?: { state?: PullRequestStateFilter[]; cursor?: string; filters?: PullRequestFilter[] },\n\t): Promise<ProviderApiPagedResult<ProviderPullRequest> | undefined> {\n\t\t// Resolve the username from THIS session's token (multi-account safe) to scope the account-wide read.\n\t\tconst username = (await this.getProviderCurrentAccount(session))?.username;\n\t\tif (username == null) return undefined;\n\n\t\tconst api = await this.getProvidersApi();\n\t\tconst requested = options?.filters?.length ? new Set(options.filters) : undefined;\n\t\t// The filter contract is all-or-nothing, so refuse as soon as ONE requested member is inexpressible rather\n\t\t// than only when every one is: dropping the rest would answer a narrower question than was asked\n\t\t// (`[Author, Mention]` becoming `authored`). Throw rather than return `undefined`, which the facade reads\n\t\t// as \"no session could be resolved\" and a drain turns into a not-connected warning, misattributing a\n\t\t// refusal; `getMyPullRequestsForUserResult` recovers the throw into `{ error }`. Defense in depth:\n\t\t// `resolveAccountWidePullRequestFilters` already refuses such a set, so only a direct caller of the public\n\t\t// method reaches this.\n\t\tconst unsupported = requested == null ? [] : [...requested].filter(f => gitLabAssociationForFilter[f] == null);\n\t\tif (unsupported.length > 0) {\n\t\t\tthrow new IntegrationReadUnavailableError(\n\t\t\t\tthis.name,\n\t\t\t\t`account-wide pull request filters not expressible by GitLab: ${unsupported.join(', ')}`,\n\t\t\t);\n\t\t}\n\n\t\tconst associations: GitLabPullRequestAssociation[] =\n\t\t\trequested == null\n\t\t\t\t? ['authored', 'assigned', 'reviewRequested']\n\t\t\t\t: Array.from(requested, f => gitLabAssociationForFilter[f]).filter(a => a != null);\n\n\t\tconst cursors = parseGitLabPullRequestFacetCursor(options?.cursor);\n\t\tconst resumable = associations.filter(association => cursors[association] != null);\n\t\tconst associationsToQuery = options?.cursor != null && resumable.length > 0 ? resumable : associations;\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\tconst settled = await Promise.allSettled(\n\t\t\tassociationsToQuery.map(async association => ({\n\t\t\t\tassociation: association,\n\t\t\t\tresult: await api.getGitLabPullRequestsForUserAssociation(tokenWithInfo, username, association, {\n\t\t\t\t\tisPAT: this.isEnterprise,\n\t\t\t\t\tbaseUrl: this.enterpriseBaseUrlFor(session),\n\t\t\t\t\tstates: toProviderPullRequestStates(options?.state),\n\t\t\t\t\tcursor: cursors[association],\n\t\t\t\t}),\n\t\t\t})),\n\t\t);\n\t\tif (settled.every(outcome => outcome.status === 'rejected')) {\n\t\t\tconst first = settled[0];\n\t\t\tif (first?.status === 'rejected') throw first.reason;\n\t\t}\n\n\t\tconst values = new Map<string, ProviderPullRequest>();\n\t\tconst nextCursors: GitLabPullRequestFacetCursor = {};\n\t\tconst failures: CollectionScopeFailure[] = [];\n\t\tlet aggregateMetadata: CollectionMetadata | undefined;\n\t\tlet truncated = false;\n\t\tlet unkeyedPullRequest = 0;\n\t\tfor (let index = 0; index < settled.length; index++) {\n\t\t\tconst outcome = settled[index];\n\t\t\tconst association = associationsToQuery[index];\n\t\t\tif (outcome == null || association == null) continue;\n\t\t\tif (outcome.status === 'rejected') {\n\t\t\t\tconst failure = toCollectionScopeFailure({ providerId: this.id }, outcome.reason);\n\t\t\t\tfailures.push({\n\t\t\t\t\t...failure,\n\t\t\t\t\tmessage: `GitLab ${association} pull requests could not be read${\n\t\t\t\t\t\tfailure.message != null ? `: ${failure.message}` : ''\n\t\t\t\t\t}`,\n\t\t\t\t});\n\t\t\t\ttruncated = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst result = outcome.value.result;\n\t\t\taggregateMetadata = mergeCollectionMetadata(aggregateMetadata, result.metadata);\n\t\t\ttruncated ||=\n\t\t\t\tresult.paging?.truncated === true ||\n\t\t\t\t(result.metadata != null && result.metadata.completeness !== 'complete');\n\t\t\tfor (const pr of result.values) {\n\t\t\t\tconst identity = getProviderPullRequestIdentity(pr) ?? `unkeyed:${unkeyedPullRequest++}`;\n\t\t\t\tif (!values.has(identity)) {\n\t\t\t\t\tvalues.set(identity, pr);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (result.paging?.more && result.paging.cursor != null && result.paging.cursor !== '{}') {\n\t\t\t\tnextCursors[association] = result.paging.cursor;\n\t\t\t}\n\t\t}\n\n\t\taggregateMetadata = mergeCollectionMetadata(\n\t\t\taggregateMetadata,\n\t\t\tfailures.length > 0 ? { completeness: 'partial', failures: failures } : undefined,\n\t\t);\n\t\tconst hasMore = Object.keys(nextCursors).length > 0;\n\t\treturn {\n\t\t\tvalues: [...values.values()],\n\t\t\tpaging: {\n\t\t\t\tmore: hasMore,\n\t\t\t\tcursor: hasMore ? JSON.stringify({ type: 'gitlab-associations', cursors: nextCursors }) : '{}',\n\t\t\t\ttruncated: truncated || undefined,\n\t\t\t},\n\t\t\t...(aggregateMetadata != null ? { metadata: aggregateMetadata } : {}),\n\t\t};\n\t}\n\n\tprotected override async searchProviderMyIssues(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepos?: GitLabRepositoryDescriptor[],\n\t): Promise<IssueShape[] | undefined> {\n\t\tconst api = await this.authenticationService.apis.gitlab;\n\t\tconst providerApi = await this.getProvidersApi();\n\t\tif (!api || !repos) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst repoIdsResult = await Promise.allSettled(\n\t\t\trepos.map((r: GitLabRepositoryDescriptor): Promise<string | undefined> =>\n\t\t\t\tapi.getProjectId(\n\t\t\t\t\tthis,\n\t\t\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t\t\tr.owner,\n\t\t\t\t\tr.name,\n\t\t\t\t\tthis.apiBaseUrlFor(session),\n\t\t\t\t\tundefined,\n\t\t\t\t),\n\t\t\t) ?? [],\n\t\t);\n\t\tconst repoInput = repoIdsResult\n\t\t\t.map(result => (result.status === 'fulfilled' ? result.value : undefined))\n\t\t\t.filter((r): r is string => r != null);\n\t\tconst apiResult = await providerApi.getIssuesForRepos(toTokenWithInfo(this.id, session), repoInput, {\n\t\t\tisPAT: this.isEnterprise,\n\t\t\tbaseUrl: this.enterpriseBaseUrlFor(session),\n\t\t});\n\n\t\treturn apiResult.values\n\t\t\t.map(issue => toIssueShape(issue, this))\n\t\t\t.filter((result): result is IssueShape => result != null);\n\t}\n\n\t/**\n\t * Account-wide \"my issues\" for GitLab. GitLab's repo-scoped issue read bails without repos, and GitLab has\n\t * no GraphQL cross-project issue field, so this goes through the SDK's REST `GET /issues` read\n\t * ({@link ProvidersApi.getIssuesForCurrentUser}). Open issues only (matching GitHub's baked `is:open`).\n\t *\n\t * Default scope is the current user's assigned issues; `includeAllAssignees` broadens to `scope=all` (every\n\t * visible issue, any assignee). The read is numbered-paged, so each page is drained to exhaustion, bounded by\n\t * a defensive backstop — a hit backstop is reported as `truncated` so the facade surfaces an incomplete read\n\t * rather than publishing a partial list as complete.\n\t *\n\t * `options.filters` selects which relationships to read: `Assignee` (the default when omitted) and `Author`,\n\t * each its own drain, unioned by url — see the comment on `passes` for why they can't be one request. Mention\n\t * is absent because GitLab's REST read exposes no first-class mention filter, and approximating it via `search`\n\t * would return a different set than asked for; the facade refuses it\n\t * (see `ProviderMetadata.supportedAccountWideIssueFilters`).\n\t */\n\tprotected override async searchProviderMyIssuesWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resources?: GitLabRepositoryDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\toptions?: SearchMyIssuesOptions,\n\t): Promise<AccountWideIssuesResult | undefined> {\n\t\tif (cancellation?.aborted) throw new CancellationError();\n\n\t\tconst api = await this.getProvidersApi();\n\n\t\t// Resolve the username from THIS session's token (multi-account safe), matching the PR account-wide read.\n\t\t// Only needed to scope the default (assigned-to-me) read; the all-assignees broaden drops it.\n\t\tconst username = options?.includeAllAssignees\n\t\t\t? undefined\n\t\t\t: (await this.getProviderCurrentAccount(session))?.username;\n\t\tif (!options?.includeAllAssignees && username == null) return undefined;\n\t\tif (cancellation?.aborted) throw new CancellationError();\n\n\t\tconst baseUrl = this.enterpriseBaseUrlFor(session);\n\t\tconst maxPages = 20;\n\t\t// Dedupe by `url`, not `IssueShape.id`: for GitLab `id` is the per-project `iid`, which collides across\n\t\t// projects in an account-wide read (two repos both have issue `#1`), so an id-keyed map would silently\n\t\t// drop distinct issues. `url` is globally unique. Matches the GitHub/GitLab account-wide PR reads.\n\t\tconst issuesByUrl = new Map<string, IssueShape>();\n\t\tlet truncated = false;\n\t\tlet collectionMetadata: CollectionMetadata | undefined;\n\n\t\t// One drain PER requested relationship, unioned by url. The account-wide filter contract is a union\n\t\t// (`authored ∪ assigned`, matching GitHub's three searches and Azure's two drains), and GitLab can express\n\t\t// only one relationship per REST call: `assignee_username` and `author_username` on the same request\n\t\t// compose with AND, so a single combined call would return the intersection — issues the user both opened\n\t\t// and is assigned to — instead of either set. Overlap between the passes is collapsed by the url map.\n\t\tconst passes: { scope: 'assigned_to_me' | 'all'; assigneeUsername?: string; authorUsername?: string }[] = [];\n\t\tif (options?.includeAllAssignees) {\n\t\t\t// Broadens past \"mine\" entirely: every visible issue, any assignee. Contradicts `filters`, which the\n\t\t\t// facade refuses before reaching here.\n\t\t\tpasses.push({ scope: 'all' });\n\t\t} else {\n\t\t\tconst filters = options?.filters;\n\t\t\t// Assignee is this read's default relationship, so it runs unless the caller narrowed to author alone.\n\t\t\tif (filters == null || filters.length === 0 || filters.includes(IssueFilter.Assignee)) {\n\t\t\t\tpasses.push({ scope: 'assigned_to_me', assigneeUsername: username });\n\t\t\t}\n\t\t\t// `scope: 'all'` is required for the author axis: paired with `assigned_to_me` GitLab would intersect\n\t\t\t// the two rather than read authored issues.\n\t\t\tif (filters?.includes(IssueFilter.Author)) {\n\t\t\t\tpasses.push({ scope: 'all', authorUsername: username });\n\t\t\t}\n\t\t}\n\n\t\tfor (const pass of passes) {\n\t\t\tconst result = await collectProviderPagedResult(\n\t\t\t\tasync cursor => {\n\t\t\t\t\tif (cancellation?.aborted) throw new CancellationError();\n\n\t\t\t\t\tconst page = await api.getIssuesForCurrentUser(toTokenWithInfo(this.id, session), {\n\t\t\t\t\t\t...pass,\n\t\t\t\t\t\tisPAT: this.isEnterprise,\n\t\t\t\t\t\tbaseUrl: baseUrl,\n\t\t\t\t\t\tcursor: cursor,\n\t\t\t\t\t\tsort: options?.sort,\n\t\t\t\t\t});\n\t\t\t\t\tif (cancellation?.aborted) throw new CancellationError();\n\t\t\t\t\treturn page;\n\t\t\t\t},\n\t\t\t\tmaxPages,\n\t\t\t\t{ providerId: this.id },\n\t\t\t);\n\n\t\t\tfor (const issue of result.values) {\n\t\t\t\tconst shape = toIssueShape(issue, this);\n\t\t\t\tif (shape != null && !issuesByUrl.has(shape.url)) {\n\t\t\t\t\tissuesByUrl.set(shape.url, shape);\n\t\t\t\t}\n\t\t\t}\n\t\t\ttruncated ||= result.truncated === true;\n\t\t\tcollectionMetadata = mergeCollectionMetadata(collectionMetadata, result.metadata);\n\t\t}\n\n\t\treturn {\n\t\t\tvalues: [...issuesByUrl.values()],\n\t\t\ttruncated: truncated,\n\t\t\t...(collectionMetadata != null ? { metadata: collectionMetadata } : {}),\n\t\t};\n\t}\n\n\tprotected override async searchProviderPullRequests(\n\t\tsession: ProviderAuthenticationSession,\n\t\tsearchQuery: string,\n\t\trepos?: GitLabRepositoryDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\toptions?: SearchPullRequestsOptions,\n\t): Promise<PullRequest[] | undefined> {\n\t\tconst api = await this.authenticationService.apis.gitlab;\n\t\tif (!api) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn api.searchPullRequests(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t{\n\t\t\t\tsearch: searchQuery,\n\t\t\t\trepos: repos?.map(r => `${r.owner}/${r.name}`),\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t\tinclude: options?.include,\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\tprotected override async mergeProviderPullRequest(\n\t\tsession: ProviderAuthenticationSession,\n\t\tpr: PullRequest,\n\t\toptions?: {\n\t\t\tmergeMethod?: PullRequestMergeMethod;\n\t\t},\n\t): Promise<boolean> {\n\t\tif (!this.isPullRequest(pr)) return false;\n\n\t\tconst api = await this.getProvidersApi();\n\t\ttry {\n\t\t\tconst res = await api.mergePullRequest(toTokenWithInfo(this.id, session), pr, {\n\t\t\t\t...options,\n\t\t\t\tisPAT: this.isEnterprise,\n\t\t\t\tbaseUrl: this.enterpriseBaseUrlFor(session),\n\t\t\t});\n\t\t\treturn res;\n\t\t} catch (ex) {\n\t\t\tvoid this.showMergeErrorMessage(ex);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tprivate async showMergeErrorMessage(ex: Error) {\n\t\t// Unfortunately, providers-api does not let us know the exact reason for the error,\n\t\t// so we show the same message to everything.\n\t\t// When we update the library, we can improve the error handling here.\n\t\tconst reauthenticate = await this.ctx.hooks?.onReauthenticationRequired?.(\n\t\t\tl10n.t(\n\t\t\t\t\"{0}. Would you like to try reauthenticating to provide additional access? Your token needs to have the 'api' scope to perform merge.\",\n\t\t\t\tex.message,\n\t\t\t),\n\t\t);\n\n\t\tif (reauthenticate) {\n\t\t\tawait this.reauthenticate();\n\t\t}\n\t}\n\n\tprivate isPullRequest(pr: PullRequest | { id: string; headRefSha: string }): pr is PullRequest {\n\t\treturn (pr as PullRequest).refs != null;\n\t}\n\n\tprotected override async getProviderCurrentAccount(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<Account | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst currentUser = await api.getCurrentUser(toTokenWithInfo(this.id, session), {\n\t\t\tisPAT: this.isEnterprise,\n\t\t\tbaseUrl: this.enterpriseBaseUrlFor(session),\n\t\t});\n\t\tif (currentUser == null) return undefined;\n\n\t\treturn {\n\t\t\tprovider: {\n\t\t\t\tid: this.id,\n\t\t\t\tname: this.name,\n\t\t\t\tdomain: this.domain,\n\t\t\t\ticon: this.icon,\n\t\t\t},\n\t\t\tid: currentUser.id,\n\t\t\tname: currentUser.name || undefined,\n\t\t\temail: currentUser.email || undefined,\n\t\t\tavatarUrl: currentUser.avatarUrl || undefined,\n\t\t\tusername: currentUser.username || undefined,\n\t\t};\n\t}\n\n\tprotected override getProviderPullRequestIdentityFromMaybeUrl(search: string): PullRequestUrlIdentity | undefined {\n\t\treturn getGitLabPullRequestIdentityFromMaybeUrl(search, this.id);\n\t}\n}\n\nexport class GitLabIntegration extends GitLabIntegrationBase<GitCloudHostIntegrationId.GitLab> {\n\treadonly authProvider = authProvider;\n\treadonly id = GitCloudHostIntegrationId.GitLab;\n\tprotected readonly key = this.id;\n\treadonly name: string = 'GitLab';\n\tget domain(): string {\n\t\treturn metadata.domain;\n\t}\n\n\tprotected apiBaseUrlFor(_session: ProviderAuthenticationSession): string {\n\t\treturn 'https://gitlab.com/api';\n\t}\n\n\toverride access(): Promise<boolean> {\n\t\t// Always allow GitHub cloud integration access\n\t\treturn Promise.resolve(true);\n\t}\n}\n\nexport class GitLabSelfHostedIntegration extends GitLabIntegrationBase<GitSelfManagedHostIntegrationId.CloudGitLabSelfHosted> {\n\treadonly authProvider = cloudEnterpriseAuthProvider;\n\treadonly id = GitSelfManagedHostIntegrationId.CloudGitLabSelfHosted;\n\tprotected readonly key;\n\treadonly name = 'GitLab Self-Hosted';\n\tget domain(): string {\n\t\treturn this._domain;\n\t}\n\tprotected override apiBaseUrlFor(session: ProviderAuthenticationSession): string {\n\t\treturn `${this.getSelfManagedApiBaseUrl(session)}/api`;\n\t}\n\n\tconstructor(\n\t\tctx: IntegrationServiceContext,\n\t\tauthenticationService: IntegrationAuthenticationService,\n\t\tgetProvidersApi: () => Promise<ProvidersApi>,\n\t\tdidChangeConnection: Emitter<IntegrationConnectionChangeEvent>,\n\t\tprivate readonly _domain: string,\n\t) {\n\t\tsuper(ctx, authenticationService, getProvidersApi, didChangeConnection);\n\t\tthis.key = `${this.id}:${this.domain}` as const;\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"gitlab.js","names":[],"sources":["../../../../../plus/integrations/src/providers/gitlab.ts"],"sourcesContent":["import type { CollectionMetadata, CollectionScopeFailure } from '@gitkraken/provider-apis';\nimport * as l10n from '@vscode/l10n';\nimport type { Account } from '@gitlens/git/models/author.js';\nimport type { DefaultBranch } from '@gitlens/git/models/defaultBranch.js';\nimport type { Issue, IssueShape } from '@gitlens/git/models/issue.js';\nimport type { IssueOrPullRequest } from '@gitlens/git/models/issueOrPullRequest.js';\nimport type {\n\tPullRequest,\n\tPullRequestMergeMethod,\n\tPullRequestShape,\n\tPullRequestState,\n\tPullRequestStateFilter,\n} from '@gitlens/git/models/pullRequest.js';\nimport type { RepositoryMetadata } from '@gitlens/git/models/repositoryMetadata.js';\nimport type { RepositoryDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport type { PullRequestUrlIdentity } from '@gitlens/git/utils/pullRequest.utils.js';\nimport { CancellationError } from '@gitlens/utils/cancellation.js';\nimport type { Emitter } from '@gitlens/utils/event.js';\nimport { uniqueBy } from '@gitlens/utils/iterable.js';\nimport { batch, mapSettledBounded } from '@gitlens/utils/promise.js';\nimport type { IntegrationAuthenticationProviderDescriptor } from '../authentication/integrationAuthenticationProvider.js';\nimport type { IntegrationAuthenticationService } from '../authentication/integrationAuthenticationService.js';\nimport type { ProviderAuthenticationSession } from '../authentication/models.js';\nimport { toTokenWithInfo } from '../authentication/models.js';\nimport { toCollectionScopeFailure } from '../collectionMetadata.js';\nimport { GitCloudHostIntegrationId, GitSelfManagedHostIntegrationId, providerFanOutConcurrency } from '../constants.js';\nimport type { IntegrationServiceContext } from '../context.js';\nimport { IntegrationReadUnavailableError } from '../errors.js';\nimport type { IntegrationConnectionChangeEvent } from '../integrationService.js';\nimport type { SearchMyPullRequestsOptions, SearchPullRequestsOptions } from '../models/gitHostIntegration.js';\nimport { GitHostIntegration } from '../models/gitHostIntegration.js';\nimport type { AccountWideIssuesResult, SearchMyIssuesOptions } from '../models/integration.js';\nimport type { GitLabIntegrationIds } from './gitlab/gitlab.utils.js';\nimport { getGitLabPullRequestIdentityFromMaybeUrl, matchesGitLabOrgNamespace } from './gitlab/gitlab.utils.js';\nimport { fromGitLabMergeRequestProvidersApi } from './gitlab/models.js';\nimport type {\n\tProviderApiPagedResult,\n\tProviderHierarchyResult,\n\tProviderOrganization,\n\tProviderPullRequest,\n\tProviderRepository,\n} from './models.js';\nimport {\n\tfromProviderPullRequest,\n\tgetProviderPullRequestIdentity,\n\tIssueFilter,\n\tProviderPullRequestReviewState,\n\tprovidersMetadata,\n\tPullRequestFilter,\n\ttoIssueShape,\n\ttoProviderPullRequestStates,\n} from './models.js';\nimport type { ProvidersApi } from './providersApi.js';\nimport {\n\tcollectProviderPagedResult,\n\tmergeCollectionMetadata,\n\tresolveBranchPullRequests,\n} from './utils/providerPaging.js';\n\nconst metadata = providersMetadata[GitCloudHostIntegrationId.GitLab];\nconst authProvider: IntegrationAuthenticationProviderDescriptor = Object.freeze({\n\tid: metadata.id,\n\tscopes: metadata.scopes,\n});\n\nconst cloudEnterpriseMetadata = providersMetadata[GitSelfManagedHostIntegrationId.CloudGitLabSelfHosted];\nconst cloudEnterpriseAuthProvider: IntegrationAuthenticationProviderDescriptor = Object.freeze({\n\tid: cloudEnterpriseMetadata.id,\n\tscopes: cloudEnterpriseMetadata.scopes,\n});\n\ntype GitLabPullRequestAssociation = 'assigned' | 'authored' | 'reviewRequested';\ntype GitLabPullRequestFacetCursor = Partial<Record<GitLabPullRequestAssociation, string>>;\n\n/**\n * The account-wide relationships GitLab can express, and the `scope` each maps to. `undefined` means GitLab has\n * no equivalent axis (it has neither `Mention` nor `reviewed-by`), and this map is the single declaration of\n * that. Exhaustive over `PullRequestFilter` on purpose: a new member must be decided for GitLab here rather\n * than compiling through as silently unsupported. Exported for the advertised-vs-expressible capability test,\n * which is what keeps it agreeing with `supportedAccountWidePullRequestFilters`; no runtime consumer imports it.\n */\nexport const gitLabAssociationForFilter: Record<PullRequestFilter, GitLabPullRequestAssociation | undefined> = {\n\t[PullRequestFilter.Assignee]: 'assigned',\n\t[PullRequestFilter.Author]: 'authored',\n\t[PullRequestFilter.ReviewRequested]: 'reviewRequested',\n\t[PullRequestFilter.Reviewed]: undefined,\n\t[PullRequestFilter.Mention]: undefined,\n};\n\nfunction parseGitLabPullRequestFacetCursor(cursor: string | undefined): GitLabPullRequestFacetCursor {\n\tif (!cursor) return {};\n\n\ttry {\n\t\tconst parsed = JSON.parse(cursor) as { type?: unknown; cursors?: unknown };\n\t\tif (parsed.type !== 'gitlab-associations' || parsed.cursors == null || typeof parsed.cursors !== 'object') {\n\t\t\treturn {};\n\t\t}\n\n\t\tconst cursors = parsed.cursors as Record<string, unknown>;\n\t\treturn Object.fromEntries(\n\t\t\t(['assigned', 'authored', 'reviewRequested'] as const)\n\t\t\t\t.filter(association => typeof cursors[association] === 'string' && cursors[association] !== '')\n\t\t\t\t.map(association => [association, cursors[association] as string]),\n\t\t);\n\t} catch {\n\t\treturn {};\n\t}\n}\n\nexport type GitLabRepositoryDescriptor = RepositoryDescriptor;\n\n/** How many SSH signing-key lookups (account resolve + keys fetch) to run concurrently, to avoid a request burst. */\nconst sshSigningKeyResolveBatchSize = 10;\n\nabstract class GitLabIntegrationBase<ID extends GitLabIntegrationIds> extends GitHostIntegration<\n\tID,\n\tGitLabRepositoryDescriptor\n> {\n\tprotected abstract apiBaseUrlFor(session: ProviderAuthenticationSession): string;\n\n\t/** The installation base the PAT-based reads take on self-hosted GitLab; gitlab.com passes none. */\n\tprotected enterpriseBaseUrlFor(session: ProviderAuthenticationSession): string | undefined {\n\t\treturn this.isEnterprise ? this.getSelfManagedApiBaseUrl(session) : undefined;\n\t}\n\n\t/** Self-hosted GitLab uses PAT semantics and a domain-based API base; gitlab.com does not. */\n\tprotected get isEnterprise(): boolean {\n\t\treturn this.id === GitSelfManagedHostIntegrationId.CloudGitLabSelfHosted;\n\t}\n\n\tprotected override async getProviderAccountForCommit(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitLabRepositoryDescriptor,\n\t\trev: string,\n\t\toptions?: {\n\t\t\tavatarSize?: number;\n\t\t},\n\t): Promise<Account | undefined> {\n\t\treturn (await this.authenticationService.apis.gitlab)?.getAccountForCommit(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\trev,\n\t\t\t{\n\t\t\t\t...options,\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderAccountForEmail(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitLabRepositoryDescriptor,\n\t\temail: string,\n\t\toptions?: {\n\t\t\tavatarSize?: number;\n\t\t},\n\t): Promise<Account | undefined> {\n\t\treturn (await this.authenticationService.apis.gitlab)?.getAccountForEmail(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\temail,\n\t\t\t{\n\t\t\t\t...options,\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderSshSigningKeysForEmails(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitLabRepositoryDescriptor,\n\t\temails: string[],\n\t): Promise<Map<string, string[]>> {\n\t\tconst result = new Map<string, string[]>();\n\n\t\tconst api = await this.authenticationService.apis.gitlab;\n\t\tif (api == null) return result;\n\n\t\tconst token = toTokenWithInfo(this.id, session);\n\n\t\t// GitLab resolves users one email at a time (no batch search). Run them in bounded batches rather than firing all\n\t\t// (up to providerVerifyLimit) account + key lookups at once, to avoid a request burst that trips rate limiting.\n\t\tawait batch(emails, sshSigningKeyResolveBatchSize, async email => {\n\t\t\tconst account = await this.getProviderAccountForEmail(session, repo, email);\n\t\t\tif (account?.id == null) return;\n\n\t\t\tconst keys = await api.getUserSigningKeys(this, token, account.id, {\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t});\n\t\t\tresult.set(\n\t\t\t\temail.toLowerCase(),\n\t\t\t\tkeys.map(k => k.key),\n\t\t\t);\n\t\t});\n\n\t\treturn result;\n\t}\n\n\tprotected override async getProviderDefaultBranch(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitLabRepositoryDescriptor,\n\t): Promise<DefaultBranch | undefined> {\n\t\treturn (await this.authenticationService.apis.gitlab)?.getDefaultBranch(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\t{\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderLinkedIssueOrPullRequest(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitLabRepositoryDescriptor,\n\t\t{ id }: { id: string; key: string },\n\t): Promise<IssueOrPullRequest | undefined> {\n\t\treturn (await this.authenticationService.apis.gitlab)?.getIssueOrPullRequest(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\tNumber(id),\n\t\t\t{\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderIssue(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitLabRepositoryDescriptor,\n\t\tid: string,\n\t): Promise<Issue | undefined> {\n\t\tconst api = await this.authenticationService.apis.gitlab;\n\t\tconst providerApi = await this.getProvidersApi();\n\t\tif (!api || !repo || !id) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst repoId = await api.getProjectId(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\tthis.apiBaseUrlFor(session),\n\t\t\tundefined,\n\t\t);\n\t\tif (!repoId) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst apiResult = await providerApi.getIssue(\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t{ namespace: repo.owner, name: repo.name, number: id },\n\t\t\t{\n\t\t\t\tisPAT: this.isEnterprise,\n\t\t\t\tbaseUrl: this.enterpriseBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t\tconst issue = apiResult != null ? toIssueShape(apiResult, this) : undefined;\n\t\treturn issue != null ? { ...issue, type: 'issue' } : undefined;\n\t}\n\n\tprotected override async getProviderPullRequestForBranch(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitLabRepositoryDescriptor,\n\t\tbranch: string,\n\t\toptions?: {\n\t\t\tavatarSize?: number;\n\t\t\tinclude?: PullRequestState[];\n\t\t},\n\t): Promise<PullRequest | undefined> {\n\t\tconst { include, ...opts } = options ?? {};\n\n\t\tconst toGitLabMergeRequestState = (await import(/* webpackChunkName: \"integrations\" */ './gitlab/models.js'))\n\t\t\t.toGitLabMergeRequestState;\n\t\treturn (await this.authenticationService.apis.gitlab)?.getPullRequestForBranch(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\tbranch,\n\t\t\t{\n\t\t\t\t...opts,\n\t\t\t\tinclude: include?.map(s => toGitLabMergeRequestState(s)),\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderPullRequestForCommit(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitLabRepositoryDescriptor,\n\t\trev: string,\n\t): Promise<PullRequest | undefined> {\n\t\treturn (await this.authenticationService.apis.gitlab)?.getPullRequestForCommit(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\trev,\n\t\t\t{\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderPullRequest(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: GitLabRepositoryDescriptor,\n\t\tid: string,\n\t): Promise<PullRequest | undefined> {\n\t\treturn (await this.authenticationService.apis.gitlab)?.getPullRequest(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\tresource.owner,\n\t\t\tresource.name,\n\t\t\tparseInt(id, 10),\n\t\t\t{\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\t/**\n\t * One request per target, settled independently so one target's failure rejects only its own slot; through the\n\t * SDK's issue read and the conversion the repo-scoped list rows take. GitLens' own GitLab client can't alias\n\t * the read instead: it fails a whole document on any GraphQL error, where a batch needs per-target errors.\n\t */\n\tprotected override async getProviderIssuesBatch(\n\t\tsession: ProviderAuthenticationSession,\n\t\tcoordinates: readonly { owner: string; repo: string; number: number; project?: string }[],\n\t\tcancellation?: AbortSignal,\n\t): Promise<PromiseSettledResult<IssueShape | undefined>[] | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\tconst baseUrl = this.enterpriseBaseUrlFor(session);\n\t\t// The confirming read must ask the same host, over the same protocol, as the read it confirms.\n\t\tconst confirmBaseUrl = this.apiBaseUrlFor(session);\n\n\t\treturn mapSettledBounded(coordinates, providerFanOutConcurrency, async c => {\n\t\t\tconst issue = await api.getIssueForRepo(tokenWithInfo, { namespace: c.owner, name: c.repo }, c.number, {\n\t\t\t\tisPAT: this.isEnterprise,\n\t\t\t\tbaseUrl: baseUrl,\n\t\t\t});\n\t\t\tif (issue != null) {\n\t\t\t\tconst shape = toIssueShape(issue, this);\n\t\t\t\tif (shape == null) throw new Error(`GitLab returned issue ${c.number} without a URL or update time`);\n\n\t\t\t\treturn shape;\n\t\t\t}\n\n\t\t\t// provider-apis' \"not found\" can come from a reply carrying only GraphQL errors (see `getIssueForRepo`),\n\t\t\t// so only our own strict read can prove the miss.\n\t\t\tconst gitlab = await this.authenticationService.apis.gitlab;\n\t\t\tif (gitlab == null) {\n\t\t\t\tthrow new IntegrationReadUnavailableError(this.name, 'cannot confirm a missing issue');\n\t\t\t}\n\n\t\t\tconst exists = await gitlab.hasIssue(\n\t\t\t\tthis,\n\t\t\t\ttokenWithInfo,\n\t\t\t\tc.owner,\n\t\t\t\tc.repo,\n\t\t\t\tc.number,\n\t\t\t\t{ baseUrl: confirmBaseUrl, deferFailure: true },\n\t\t\t\tcancellation,\n\t\t\t);\n\t\t\t// Our own query can't produce the list rows' shape, so a disagreement fails rather than answering thinner.\n\t\t\tif (exists) throw new Error(`GitLab reported issue ${c.number} missing, then found it`);\n\n\t\t\treturn undefined;\n\t\t});\n\t}\n\n\t/**\n\t * One request per target, settled independently so one target's failure rejects only its own slot; through\n\t * the same SDK read and conversion the manager's list rows take.\n\t */\n\tprotected override async getProviderPullRequestsBatch(\n\t\tsession: ProviderAuthenticationSession,\n\t\tcoordinates: readonly { owner: string; repo: string; number: number; project?: string }[],\n\t\toptions: { currentAccount?: { id: string; username?: string } } | undefined,\n\t\tcancellation?: AbortSignal,\n\t): Promise<PromiseSettledResult<PullRequestShape | undefined>[] | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\tconst baseUrl = this.enterpriseBaseUrlFor(session);\n\t\t// The confirming read must ask the same host, over the same protocol, as the read it confirms.\n\t\tconst confirmBaseUrl = this.apiBaseUrlFor(session);\n\n\t\treturn mapSettledBounded(coordinates, providerFanOutConcurrency, async c => {\n\t\t\tconst pr = await api.getPullRequestForRepo(tokenWithInfo, { namespace: c.owner, name: c.repo }, c.number, {\n\t\t\t\tisPAT: this.isEnterprise,\n\t\t\t\tbaseUrl: baseUrl,\n\t\t\t});\n\t\t\tif (pr != null) return fromProviderPullRequest(pr, this, { currentAccount: options?.currentAccount });\n\n\t\t\t// provider-apis' GitLab GraphQL helper ignores the response's `errors`, so a reply carrying only errors\n\t\t\t// (e.g. a server-side timeout) comes back as `null`, the same as not-found. Our own client throws on\n\t\t\t// `errors`, and `strict` makes it answer `undefined` only for a real miss.\n\t\t\tconst gitlab = await this.authenticationService.apis.gitlab;\n\t\t\tif (gitlab == null) {\n\t\t\t\tthrow new IntegrationReadUnavailableError(this.name, 'cannot confirm a missing merge request');\n\t\t\t}\n\n\t\t\tconst confirmed = await gitlab.getPullRequest(\n\t\t\t\tthis,\n\t\t\t\ttokenWithInfo,\n\t\t\t\tc.owner,\n\t\t\t\tc.repo,\n\t\t\t\tc.number,\n\t\t\t\t{ baseUrl: confirmBaseUrl, strict: true, deferFailure: true },\n\t\t\t\tcancellation,\n\t\t\t);\n\t\t\t// Our own client's row can't produce the list rows' shape (`id` is the iid, not the global id; no\n\t\t\t// `authoredByMe`), so a disagreement fails rather than answering with a different identity.\n\t\t\tif (confirmed != null) throw new Error(`GitLab reported pull request ${c.number} missing, then found it`);\n\n\t\t\treturn undefined;\n\t\t});\n\t}\n\n\t/**\n\t * Two steps, because provider-apis has no source-branch filter: GitLens' own GitLab client finds each branch's\n\t * matching iids, one request per target, then {@link getProviderPullRequestsBatch} resolves them — so a row is\n\t * the one `getPullRequestsBatch` returns for that merge request, `url` and `authoredByMe` included.\n\t */\n\tprotected override async getProviderPullRequestsForBranches(\n\t\tsession: ProviderAuthenticationSession,\n\t\ttargets: readonly { owner: string; repo: string; project?: string; branch: string; headOwner?: string }[],\n\t\toptions: { currentAccount?: { id: string; username?: string }; limit: number },\n\t\tcancellation?: AbortSignal,\n\t): Promise<PromiseSettledResult<{ pullRequests: PullRequestShape[]; truncated: boolean }>[] | undefined> {\n\t\tconst gitlab = await this.authenticationService.apis.gitlab;\n\t\tif (gitlab == null) return undefined;\n\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\t// The session's own host and protocol, as the batch read's confirming read uses.\n\t\tconst apiBaseUrl = this.apiBaseUrlFor(session);\n\n\t\tconst found = await mapSettledBounded(targets, providerFanOutConcurrency, t =>\n\t\t\tgitlab.getPullRequestNumbersForBranch(\n\t\t\t\tthis,\n\t\t\t\ttokenWithInfo,\n\t\t\t\tt.owner,\n\t\t\t\tt.repo,\n\t\t\t\tt.branch,\n\t\t\t\t{ baseUrl: apiBaseUrl, headOwner: t.headOwner, limit: options.limit, deferFailure: true },\n\t\t\t\tcancellation,\n\t\t\t),\n\t\t);\n\t\treturn resolveBranchPullRequests(targets, found, coordinates =>\n\t\t\tthis.getProviderPullRequestsBatch(\n\t\t\t\tsession,\n\t\t\t\tcoordinates,\n\t\t\t\t{ currentAccount: options.currentAccount },\n\t\t\t\tcancellation,\n\t\t\t),\n\t\t);\n\t}\n\n\tpublic override async getRepoInfo(repo: {\n\t\towner: string;\n\t\tname: string;\n\t\tproject?: string;\n\t\tconnectionId?: string;\n\t}): Promise<ProviderRepository | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\t// `connectionId` targets a specific account (multi-account); omitted reads the primary.\n\t\tconst session = await this.resolveReadSession(repo.connectionId, undefined);\n\t\tif (session == null) return undefined;\n\n\t\treturn api.getRepo(toTokenWithInfo(this.id, session), repo.owner, repo.name, repo.project, {\n\t\t\tisPAT: this.isEnterprise,\n\t\t\tbaseUrl: this.enterpriseBaseUrlFor(session),\n\t\t});\n\t}\n\n\tprotected override async getProviderRepositoryMetadata(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitLabRepositoryDescriptor,\n\t\tcancellation?: AbortSignal,\n\t): Promise<RepositoryMetadata | undefined> {\n\t\treturn (await this.authenticationService.apis.gitlab)?.getRepositoryMetadata(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\t{\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\tprotected override async getProviderOrganizationsForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<ProviderHierarchyResult<ProviderOrganization> | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst result = await api.getGitlabGroupsForCurrentUser(toTokenWithInfo(this.id, session), {\n\t\t\tisPAT: this.isEnterprise,\n\t\t\tbaseUrl: this.enterpriseBaseUrlFor(session),\n\t\t});\n\t\treturn {\n\t\t\tvalues: result.values.map(g => ({ id: g.id, providerId: this.id, name: g.fullPath, url: g.webUrl })),\n\t\t\t...(result.truncated ? { truncated: true } : {}),\n\t\t\t...(result.metadata != null ? { metadata: result.metadata } : {}),\n\t\t};\n\t}\n\n\t/**\n\t * GitLab has no group-scoped repos endpoint, so each page is a page of the current user's member repos\n\t * across all namespaces, filtered to `org` and its subgroups (mirroring gkcli's `gitlabOrgMatches`).\n\t * Follow `paging.cursor` to page the full list; repos the user isn't a member of aren't returned.\n\t * Because the filter is applied per page, a page can come back with `values: []` while\n\t * `paging.more` is still `true` (e.g. a page full of repos outside `org`) — don't treat an empty\n\t * page as the end of pagination, only `paging.more`/`collectPagedResults` reaching exhaustion.\n\t */\n\tprotected override async getProviderRepositoriesForOrg(\n\t\tsession: ProviderAuthenticationSession,\n\t\torg: string,\n\t\toptions?: { cursor?: string },\n\t): Promise<ProviderHierarchyResult<ProviderRepository> | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst result = await api.getReposForCurrentUser(toTokenWithInfo(this.id, session), {\n\t\t\tisPAT: this.isEnterprise,\n\t\t\tbaseUrl: this.enterpriseBaseUrlFor(session),\n\t\t\tcursor: options?.cursor,\n\t\t});\n\t\treturn {\n\t\t\tvalues: result.values.filter(r => matchesGitLabOrgNamespace(r.namespace, org)),\n\t\t\t...(result.paging != null ? { paging: result.paging } : {}),\n\t\t};\n\t}\n\n\tprotected override async getProviderRepositoriesForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t\toptions?: { cursor?: string },\n\t): Promise<ProviderHierarchyResult<ProviderRepository> | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\t// GitLab's membership read is already user-affiliated, so the account-wide walk is the unfiltered\n\t\t// version of the per-org read above (which pages the same source and filters by namespace).\n\t\treturn api.getReposForCurrentUser(toTokenWithInfo(this.id, session), {\n\t\t\tisPAT: this.isEnterprise,\n\t\t\tbaseUrl: this.enterpriseBaseUrlFor(session),\n\t\t\tcursor: options?.cursor,\n\t\t});\n\t}\n\n\tprotected override async searchProviderMyPullRequests(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepos?: GitLabRepositoryDescriptor[],\n\t\t_cancellation?: AbortSignal,\n\t\toptions?: SearchMyPullRequestsOptions,\n\t): Promise<PullRequest[] | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\t// Resolve the username from THIS session's token (multi-account: `session` may be a non-primary\n\t\t// connection); `getCurrentAccount()` would use the primary `_session` and filter by the wrong user.\n\t\tconst username = (await this.getProviderCurrentAccount(session))?.username;\n\t\tif (!username) {\n\t\t\treturn Promise.resolve([]);\n\t\t}\n\n\t\tconst apiResult = await api.getPullRequestsForUser(toTokenWithInfo(this.id, session), username, {\n\t\t\tisPAT: this.isEnterprise,\n\t\t\tbaseUrl: this.enterpriseBaseUrlFor(session),\n\t\t\tstates: toProviderPullRequestStates(options?.state),\n\t\t});\n\n\t\tif (apiResult == null) {\n\t\t\treturn Promise.resolve([]);\n\t\t}\n\n\t\t// now I'm going to filter prs from the result according to the repos parameter\n\t\tlet prs;\n\t\tif (repos != null) {\n\t\t\tconst repoMap = new Map<string, GitLabRepositoryDescriptor>();\n\t\t\tfor (const repo of repos) {\n\t\t\t\trepoMap.set(`${repo.owner}/${repo.name}`, repo);\n\t\t\t}\n\t\t\tprs = apiResult.values.filter(pr => {\n\t\t\t\tconst repo = repoMap.get(`${pr.repository.owner.login}/${pr.repository.name}`);\n\t\t\t\treturn repo != null;\n\t\t\t});\n\t\t} else {\n\t\t\tprs = apiResult.values;\n\t\t}\n\n\t\tconst results: IterableIterator<PullRequest> = uniqueBy(\n\t\t\t[\n\t\t\t\t...prs\n\t\t\t\t\t.filter(pr => {\n\t\t\t\t\t\tconst isAssignee = pr.assignees?.some(a => a.username === username);\n\t\t\t\t\t\tconst isRequestedReviewer = pr.reviews?.some(\n\t\t\t\t\t\t\t// Match only reviews assigned to the current user; a bare `state === ReviewRequested`\n\t\t\t\t\t\t\t// check would also match reviews requested from OTHER people, leaking their MRs in.\n\t\t\t\t\t\t\treview =>\n\t\t\t\t\t\t\t\treview.reviewer?.username === username &&\n\t\t\t\t\t\t\t\treview.state === ProviderPullRequestReviewState.ReviewRequested,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst isAuthor = pr.author?.username === username;\n\t\t\t\t\t\t// It seems like GitLab doesn't give us mentioned PRs.\n\t\t\t\t\t\t// const isMentioned = ???;\n\n\t\t\t\t\t\treturn isAssignee || isRequestedReviewer || isAuthor;\n\t\t\t\t\t})\n\t\t\t\t\t.map(pr => fromGitLabMergeRequestProvidersApi(pr, this)),\n\t\t\t],\n\t\t\tr => r.url,\n\t\t\t(original, _current) => original,\n\t\t);\n\n\t\treturn [...results];\n\t}\n\n\tprotected override async getProviderMyPullRequestsForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t\toptions?: { state?: PullRequestStateFilter[]; cursor?: string; filters?: PullRequestFilter[] },\n\t): Promise<ProviderApiPagedResult<ProviderPullRequest> | undefined> {\n\t\t// Resolve the username from THIS session's token (multi-account safe) to scope the account-wide read.\n\t\tconst username = (await this.getProviderCurrentAccount(session))?.username;\n\t\tif (username == null) return undefined;\n\n\t\tconst api = await this.getProvidersApi();\n\t\tconst requested = options?.filters?.length ? new Set(options.filters) : undefined;\n\t\t// The filter contract is all-or-nothing, so refuse as soon as ONE requested member is inexpressible rather\n\t\t// than only when every one is: dropping the rest would answer a narrower question than was asked\n\t\t// (`[Author, Mention]` becoming `authored`). Throw rather than return `undefined`, which the facade reads\n\t\t// as \"no session could be resolved\" and a drain turns into a not-connected warning, misattributing a\n\t\t// refusal; `getMyPullRequestsForUserResult` recovers the throw into `{ error }`. Defense in depth:\n\t\t// `resolveAccountWidePullRequestFilters` already refuses such a set, so only a direct caller of the public\n\t\t// method reaches this.\n\t\tconst unsupported = requested == null ? [] : [...requested].filter(f => gitLabAssociationForFilter[f] == null);\n\t\tif (unsupported.length > 0) {\n\t\t\tthrow new IntegrationReadUnavailableError(\n\t\t\t\tthis.name,\n\t\t\t\t`account-wide pull request filters not expressible by GitLab: ${unsupported.join(', ')}`,\n\t\t\t);\n\t\t}\n\n\t\tconst associations: GitLabPullRequestAssociation[] =\n\t\t\trequested == null\n\t\t\t\t? ['authored', 'assigned', 'reviewRequested']\n\t\t\t\t: Array.from(requested, f => gitLabAssociationForFilter[f]).filter(a => a != null);\n\n\t\tconst cursors = parseGitLabPullRequestFacetCursor(options?.cursor);\n\t\tconst resumable = associations.filter(association => cursors[association] != null);\n\t\tconst associationsToQuery = options?.cursor != null && resumable.length > 0 ? resumable : associations;\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\tconst settled = await Promise.allSettled(\n\t\t\tassociationsToQuery.map(async association => ({\n\t\t\t\tassociation: association,\n\t\t\t\tresult: await api.getGitLabPullRequestsForUserAssociation(tokenWithInfo, username, association, {\n\t\t\t\t\tisPAT: this.isEnterprise,\n\t\t\t\t\tbaseUrl: this.enterpriseBaseUrlFor(session),\n\t\t\t\t\tstates: toProviderPullRequestStates(options?.state),\n\t\t\t\t\tcursor: cursors[association],\n\t\t\t\t}),\n\t\t\t})),\n\t\t);\n\t\tif (settled.every(outcome => outcome.status === 'rejected')) {\n\t\t\tconst first = settled[0];\n\t\t\tif (first?.status === 'rejected') throw first.reason;\n\t\t}\n\n\t\tconst values = new Map<string, ProviderPullRequest>();\n\t\tconst nextCursors: GitLabPullRequestFacetCursor = {};\n\t\tconst failures: CollectionScopeFailure[] = [];\n\t\tlet aggregateMetadata: CollectionMetadata | undefined;\n\t\tlet truncated = false;\n\t\tlet unkeyedPullRequest = 0;\n\t\tfor (let index = 0; index < settled.length; index++) {\n\t\t\tconst outcome = settled[index];\n\t\t\tconst association = associationsToQuery[index];\n\t\t\tif (outcome == null || association == null) continue;\n\t\t\tif (outcome.status === 'rejected') {\n\t\t\t\tconst failure = toCollectionScopeFailure({ providerId: this.id }, outcome.reason);\n\t\t\t\tfailures.push({\n\t\t\t\t\t...failure,\n\t\t\t\t\tmessage: `GitLab ${association} pull requests could not be read${\n\t\t\t\t\t\tfailure.message != null ? `: ${failure.message}` : ''\n\t\t\t\t\t}`,\n\t\t\t\t});\n\t\t\t\ttruncated = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst result = outcome.value.result;\n\t\t\taggregateMetadata = mergeCollectionMetadata(aggregateMetadata, result.metadata);\n\t\t\ttruncated ||=\n\t\t\t\tresult.paging?.truncated === true ||\n\t\t\t\t(result.metadata != null && result.metadata.completeness !== 'complete');\n\t\t\tfor (const pr of result.values) {\n\t\t\t\tconst identity = getProviderPullRequestIdentity(pr) ?? `unkeyed:${unkeyedPullRequest++}`;\n\t\t\t\tif (!values.has(identity)) {\n\t\t\t\t\tvalues.set(identity, pr);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (result.paging?.more && result.paging.cursor != null && result.paging.cursor !== '{}') {\n\t\t\t\tnextCursors[association] = result.paging.cursor;\n\t\t\t}\n\t\t}\n\n\t\taggregateMetadata = mergeCollectionMetadata(\n\t\t\taggregateMetadata,\n\t\t\tfailures.length > 0 ? { completeness: 'partial', failures: failures } : undefined,\n\t\t);\n\t\tconst hasMore = Object.keys(nextCursors).length > 0;\n\t\treturn {\n\t\t\tvalues: [...values.values()],\n\t\t\tpaging: {\n\t\t\t\tmore: hasMore,\n\t\t\t\tcursor: hasMore ? JSON.stringify({ type: 'gitlab-associations', cursors: nextCursors }) : '{}',\n\t\t\t\ttruncated: truncated || undefined,\n\t\t\t},\n\t\t\t...(aggregateMetadata != null ? { metadata: aggregateMetadata } : {}),\n\t\t};\n\t}\n\n\tprotected override async searchProviderMyIssues(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepos?: GitLabRepositoryDescriptor[],\n\t): Promise<IssueShape[] | undefined> {\n\t\tconst api = await this.authenticationService.apis.gitlab;\n\t\tconst providerApi = await this.getProvidersApi();\n\t\tif (!api || !repos) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst repoIdsResult = await Promise.allSettled(\n\t\t\trepos.map((r: GitLabRepositoryDescriptor): Promise<string | undefined> =>\n\t\t\t\tapi.getProjectId(\n\t\t\t\t\tthis,\n\t\t\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t\t\tr.owner,\n\t\t\t\t\tr.name,\n\t\t\t\t\tthis.apiBaseUrlFor(session),\n\t\t\t\t\tundefined,\n\t\t\t\t),\n\t\t\t) ?? [],\n\t\t);\n\t\tconst repoInput = repoIdsResult\n\t\t\t.map(result => (result.status === 'fulfilled' ? result.value : undefined))\n\t\t\t.filter((r): r is string => r != null);\n\t\tconst apiResult = await providerApi.getIssuesForRepos(toTokenWithInfo(this.id, session), repoInput, {\n\t\t\tisPAT: this.isEnterprise,\n\t\t\tbaseUrl: this.enterpriseBaseUrlFor(session),\n\t\t});\n\n\t\treturn apiResult.values\n\t\t\t.map(issue => toIssueShape(issue, this))\n\t\t\t.filter((result): result is IssueShape => result != null);\n\t}\n\n\t/**\n\t * Account-wide \"my issues\" for GitLab. GitLab's repo-scoped issue read bails without repos, and GitLab has\n\t * no GraphQL cross-project issue field, so this goes through the SDK's REST `GET /issues` read\n\t * ({@link ProvidersApi.getIssuesForCurrentUser}). Open issues only (matching GitHub's baked `is:open`).\n\t *\n\t * Default scope is the current user's assigned issues; `includeAllAssignees` broadens to `scope=all` (every\n\t * visible issue, any assignee). The read is numbered-paged, so each page is drained to exhaustion, bounded by\n\t * a defensive backstop — a hit backstop is reported as `truncated` so the facade surfaces an incomplete read\n\t * rather than publishing a partial list as complete.\n\t *\n\t * `options.filters` selects which relationships to read: `Assignee` (the default when omitted) and `Author`,\n\t * each its own drain, unioned by url — see the comment on `passes` for why they can't be one request. Mention\n\t * is absent because GitLab's REST read exposes no first-class mention filter, and approximating it via `search`\n\t * would return a different set than asked for; the facade refuses it\n\t * (see `ProviderMetadata.supportedAccountWideIssueFilters`).\n\t */\n\tprotected override async searchProviderMyIssuesWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resources?: GitLabRepositoryDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\toptions?: SearchMyIssuesOptions,\n\t): Promise<AccountWideIssuesResult | undefined> {\n\t\tif (cancellation?.aborted) throw new CancellationError();\n\n\t\tconst api = await this.getProvidersApi();\n\n\t\t// Resolve the username from THIS session's token (multi-account safe), matching the PR account-wide read.\n\t\t// Only needed to scope the default (assigned-to-me) read; the all-assignees broaden drops it.\n\t\tconst username = options?.includeAllAssignees\n\t\t\t? undefined\n\t\t\t: (await this.getProviderCurrentAccount(session))?.username;\n\t\tif (!options?.includeAllAssignees && username == null) return undefined;\n\t\tif (cancellation?.aborted) throw new CancellationError();\n\n\t\tconst baseUrl = this.enterpriseBaseUrlFor(session);\n\t\tconst maxPages = 20;\n\t\t// Dedupe by `url`, not `IssueShape.id`: for GitLab `id` is the per-project `iid`, which collides across\n\t\t// projects in an account-wide read (two repos both have issue `#1`), so an id-keyed map would silently\n\t\t// drop distinct issues. `url` is globally unique. Matches the GitHub/GitLab account-wide PR reads.\n\t\tconst issuesByUrl = new Map<string, IssueShape>();\n\t\tlet truncated = false;\n\t\tlet collectionMetadata: CollectionMetadata | undefined;\n\n\t\t// One drain PER requested relationship, unioned by url. The account-wide filter contract is a union\n\t\t// (`authored ∪ assigned`, matching GitHub's three searches and Azure's two drains), and GitLab can express\n\t\t// only one relationship per REST call: `assignee_username` and `author_username` on the same request\n\t\t// compose with AND, so a single combined call would return the intersection — issues the user both opened\n\t\t// and is assigned to — instead of either set. Overlap between the passes is collapsed by the url map.\n\t\tconst passes: { scope: 'assigned_to_me' | 'all'; assigneeUsername?: string; authorUsername?: string }[] = [];\n\t\tif (options?.includeAllAssignees) {\n\t\t\t// Broadens past \"mine\" entirely: every visible issue, any assignee. Contradicts `filters`, which the\n\t\t\t// facade refuses before reaching here.\n\t\t\tpasses.push({ scope: 'all' });\n\t\t} else {\n\t\t\tconst filters = options?.filters;\n\t\t\t// Assignee is this read's default relationship, so it runs unless the caller narrowed to author alone.\n\t\t\tif (filters == null || filters.length === 0 || filters.includes(IssueFilter.Assignee)) {\n\t\t\t\tpasses.push({ scope: 'assigned_to_me', assigneeUsername: username });\n\t\t\t}\n\t\t\t// `scope: 'all'` is required for the author axis: paired with `assigned_to_me` GitLab would intersect\n\t\t\t// the two rather than read authored issues.\n\t\t\tif (filters?.includes(IssueFilter.Author)) {\n\t\t\t\tpasses.push({ scope: 'all', authorUsername: username });\n\t\t\t}\n\t\t}\n\n\t\tfor (const pass of passes) {\n\t\t\tconst result = await collectProviderPagedResult(\n\t\t\t\tasync cursor => {\n\t\t\t\t\tif (cancellation?.aborted) throw new CancellationError();\n\n\t\t\t\t\tconst page = await api.getIssuesForCurrentUser(toTokenWithInfo(this.id, session), {\n\t\t\t\t\t\t...pass,\n\t\t\t\t\t\tisPAT: this.isEnterprise,\n\t\t\t\t\t\tbaseUrl: baseUrl,\n\t\t\t\t\t\tcursor: cursor,\n\t\t\t\t\t\tsort: options?.sort,\n\t\t\t\t\t});\n\t\t\t\t\tif (cancellation?.aborted) throw new CancellationError();\n\t\t\t\t\treturn page;\n\t\t\t\t},\n\t\t\t\tmaxPages,\n\t\t\t\t{ providerId: this.id },\n\t\t\t);\n\n\t\t\tfor (const issue of result.values) {\n\t\t\t\tconst shape = toIssueShape(issue, this);\n\t\t\t\tif (shape != null && !issuesByUrl.has(shape.url)) {\n\t\t\t\t\tissuesByUrl.set(shape.url, shape);\n\t\t\t\t}\n\t\t\t}\n\t\t\ttruncated ||= result.truncated === true;\n\t\t\tcollectionMetadata = mergeCollectionMetadata(collectionMetadata, result.metadata);\n\t\t}\n\n\t\treturn {\n\t\t\tvalues: [...issuesByUrl.values()],\n\t\t\ttruncated: truncated,\n\t\t\t...(collectionMetadata != null ? { metadata: collectionMetadata } : {}),\n\t\t};\n\t}\n\n\tprotected override async searchProviderPullRequests(\n\t\tsession: ProviderAuthenticationSession,\n\t\tsearchQuery: string,\n\t\trepos?: GitLabRepositoryDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\toptions?: SearchPullRequestsOptions,\n\t): Promise<PullRequest[] | undefined> {\n\t\tconst api = await this.authenticationService.apis.gitlab;\n\t\tif (!api) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn api.searchPullRequests(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t{\n\t\t\t\tsearch: searchQuery,\n\t\t\t\trepos: repos?.map(r => `${r.owner}/${r.name}`),\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t\tinclude: options?.include,\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\tprotected override async mergeProviderPullRequest(\n\t\tsession: ProviderAuthenticationSession,\n\t\tpr: PullRequest,\n\t\toptions?: {\n\t\t\tmergeMethod?: PullRequestMergeMethod;\n\t\t},\n\t): Promise<boolean> {\n\t\tif (!this.isPullRequest(pr)) return false;\n\n\t\tconst api = await this.getProvidersApi();\n\t\ttry {\n\t\t\tconst res = await api.mergePullRequest(toTokenWithInfo(this.id, session), pr, {\n\t\t\t\t...options,\n\t\t\t\tisPAT: this.isEnterprise,\n\t\t\t\tbaseUrl: this.enterpriseBaseUrlFor(session),\n\t\t\t});\n\t\t\treturn res;\n\t\t} catch (ex) {\n\t\t\tvoid this.showMergeErrorMessage(ex);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tprivate async showMergeErrorMessage(ex: Error) {\n\t\t// Unfortunately, providers-api does not let us know the exact reason for the error,\n\t\t// so we show the same message to everything.\n\t\t// When we update the library, we can improve the error handling here.\n\t\tconst reauthenticate = await this.ctx.hooks?.onReauthenticationRequired?.(\n\t\t\tl10n.t(\n\t\t\t\t\"{0}. Would you like to try reauthenticating to provide additional access? Your token needs to have the 'api' scope to perform merge.\",\n\t\t\t\tex.message,\n\t\t\t),\n\t\t);\n\n\t\tif (reauthenticate) {\n\t\t\tawait this.reauthenticate();\n\t\t}\n\t}\n\n\tprivate isPullRequest(pr: PullRequest | { id: string; headRefSha: string }): pr is PullRequest {\n\t\treturn (pr as PullRequest).refs != null;\n\t}\n\n\tprotected override async getProviderCurrentAccount(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<Account | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst currentUser = await api.getCurrentUser(toTokenWithInfo(this.id, session), {\n\t\t\tisPAT: this.isEnterprise,\n\t\t\tbaseUrl: this.enterpriseBaseUrlFor(session),\n\t\t});\n\t\tif (currentUser == null) return undefined;\n\n\t\treturn {\n\t\t\tprovider: {\n\t\t\t\tid: this.id,\n\t\t\t\tname: this.name,\n\t\t\t\tdomain: this.domain,\n\t\t\t\ticon: this.icon,\n\t\t\t},\n\t\t\tid: currentUser.id,\n\t\t\tname: currentUser.name || undefined,\n\t\t\temail: currentUser.email || undefined,\n\t\t\tavatarUrl: currentUser.avatarUrl || undefined,\n\t\t\tusername: currentUser.username || undefined,\n\t\t};\n\t}\n\n\tprotected override getProviderPullRequestIdentityFromMaybeUrl(search: string): PullRequestUrlIdentity | undefined {\n\t\treturn getGitLabPullRequestIdentityFromMaybeUrl(search, this.id);\n\t}\n}\n\nexport class GitLabIntegration extends GitLabIntegrationBase<GitCloudHostIntegrationId.GitLab> {\n\treadonly authProvider = authProvider;\n\treadonly id = GitCloudHostIntegrationId.GitLab;\n\tprotected readonly key = this.id;\n\treadonly name: string = 'GitLab';\n\tget domain(): string {\n\t\treturn metadata.domain;\n\t}\n\n\tprotected apiBaseUrlFor(_session: ProviderAuthenticationSession): string {\n\t\treturn 'https://gitlab.com/api';\n\t}\n\n\toverride access(): Promise<boolean> {\n\t\t// Always allow GitHub cloud integration access\n\t\treturn Promise.resolve(true);\n\t}\n}\n\nexport class GitLabSelfHostedIntegration extends GitLabIntegrationBase<GitSelfManagedHostIntegrationId.CloudGitLabSelfHosted> {\n\treadonly authProvider = cloudEnterpriseAuthProvider;\n\treadonly id = GitSelfManagedHostIntegrationId.CloudGitLabSelfHosted;\n\tprotected readonly key;\n\treadonly name = 'GitLab Self-Hosted';\n\tget domain(): string {\n\t\treturn this._domain;\n\t}\n\tprotected override apiBaseUrlFor(session: ProviderAuthenticationSession): string {\n\t\treturn `${this.getSelfManagedApiBaseUrl(session)}/api`;\n\t}\n\n\tconstructor(\n\t\tctx: IntegrationServiceContext,\n\t\tauthenticationService: IntegrationAuthenticationService,\n\t\tgetProvidersApi: () => Promise<ProvidersApi>,\n\t\tdidChangeConnection: Emitter<IntegrationConnectionChangeEvent>,\n\t\tprivate readonly _domain: string,\n\t) {\n\t\tsuper(ctx, authenticationService, getProvidersApi, didChangeConnection);\n\t\tthis.key = `${this.id}:${this.domain}` as const;\n\t}\n}\n"],"mappings":"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"}
@@ -77,9 +77,9 @@ export declare class JiraServerIntegration extends IssuesIntegration<IssuesSelfM
77
77
  }): Promise<IssueOrPullRequest | undefined>;
78
78
  protected getProviderIssue(session: ProviderAuthenticationSession, _resource: JiraServerResourceDescriptor, id: string): Promise<Issue | undefined>;
79
79
  /**
80
- * The point read behind `getTrackerIssue` (#5872). `resourceId` is this connection's single synthetic
81
- * resource — the host itself, see {@link JiraServerResourceDescriptor} — so it selects nothing further here:
82
- * the host was chosen upstream by `domain` (or the connection's configured domain) and the request is
80
+ * The point read behind `getIssuesBatch`'s tracker targets (#5872). `resourceId` is this connection's single
81
+ * synthetic resource — the host itself, see {@link JiraServerResourceDescriptor} — so it selects nothing further
82
+ * here: the host was chosen upstream by `domain` (or the connection's configured domain) and the request is
83
83
  * addressed to the SESSION's base URL like every other read. No `resourceUrl` is needed either, unlike Jira
84
84
  * Cloud: its REST `self` link is an API endpoint, whereas `getJiraServerIssue` builds the browser link from
85
85
  * the base URL it was addressed to. A key that names no issue resolves to `undefined`, a proven absence.
@@ -1,6 +1,6 @@
1
1
  import { Logger as _logger } from "../../../utils/logger.js";
2
- import { throwIfCallerContractError, toCollectionScopeFailure } from "../collectionMetadata.js";
3
2
  import "../constants.js";
3
+ import { throwIfCallerContractError, toCollectionScopeFailure } from "../collectionMetadata.js";
4
4
  import { toTokenWithInfo } from "../authentication/models.js";
5
5
  import "../providerFilters.js";
6
6
  import { mergeCollectionMetadata } from "./utils/providerPaging.js";
@@ -253,10 +253,7 @@ var JiraServerIntegration = class extends IssuesIntegration {
253
253
  const api = await this.getProvidersApi();
254
254
  const tokenWithInfo = toTokenWithInfo(this.id, session);
255
255
  const baseUrl = this.apiBaseUrlFor(session);
256
- const accountScope = {
257
- providerId: this.id,
258
- resourceId: this.domain
259
- };
256
+ const accountScope = { providerId: this.id };
260
257
  const results = [];
261
258
  let cursor;
262
259
  let truncated = false;
@@ -303,9 +300,9 @@ var JiraServerIntegration = class extends IssuesIntegration {
303
300
  } : void 0;
304
301
  }
305
302
  /**
306
- * The point read behind `getTrackerIssue` (#5872). `resourceId` is this connection's single synthetic
307
- * resource — the host itself, see {@link JiraServerResourceDescriptor} — so it selects nothing further here:
308
- * the host was chosen upstream by `domain` (or the connection's configured domain) and the request is
303
+ * The point read behind `getIssuesBatch`'s tracker targets (#5872). `resourceId` is this connection's single
304
+ * synthetic resource — the host itself, see {@link JiraServerResourceDescriptor} — so it selects nothing further
305
+ * here: the host was chosen upstream by `domain` (or the connection's configured domain) and the request is
309
306
  * addressed to the SESSION's base URL like every other read. No `resourceUrl` is needed either, unlike Jira
310
307
  * Cloud: its REST `self` link is an API endpoint, whereas `getJiraServerIssue` builds the browser link from
311
308
  * the base URL it was addressed to. A key that names no issue resolves to `undefined`, a proven absence.
@@ -1 +1 @@
1
- {"version":3,"file":"jira-server.js","names":[],"sources":["../../../../../plus/integrations/src/providers/jira-server.ts"],"sourcesContent":["import type { CollectionMetadata } from '@gitkraken/provider-apis';\nimport type { Account } from '@gitlens/git/models/author.js';\nimport type { Issue, IssueShape } from '@gitlens/git/models/issue.js';\nimport type { IssueOrPullRequest } from '@gitlens/git/models/issueOrPullRequest.js';\nimport type { IssueResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport type { Emitter } from '@gitlens/utils/event.js';\nimport { Logger } from '@gitlens/utils/logger.js';\nimport type { IntegrationAuthenticationProviderDescriptor } from '../authentication/integrationAuthenticationProvider.js';\nimport type { IntegrationAuthenticationService } from '../authentication/integrationAuthenticationService.js';\nimport type { ProviderAuthenticationSession } from '../authentication/models.js';\nimport { toTokenWithInfo } from '../authentication/models.js';\nimport { throwIfCallerContractError, toCollectionScopeFailure } from '../collectionMetadata.js';\nimport { IssuesSelfManagedHostIntegrationId } from '../constants.js';\nimport type { IntegrationServiceContext } from '../context.js';\nimport type { IntegrationConnectionChangeEvent } from '../integrationService.js';\nimport type { IntegrationKey } from '../models/integration.js';\nimport type { AccountWideIssuesResult, IssuesForProjectOptions, ProjectIssuesDrain } from '../models/issueReads.js';\nimport { IssuesIntegration } from '../models/issuesIntegration.js';\nimport { areDomainsOnSameHost, baseUrlFromDomain } from '../utils/domain.utils.js';\nimport type { ProviderIssue } from './models.js';\nimport { IssueFilter, providersMetadata, toAccount, toIssueShape } from './models.js';\nimport type { ProvidersApi } from './providersApi.js';\nimport { mergeCollectionMetadata } from './utils/providerPaging.js';\n\n/**\n * Why three states rather than a boolean, matching `jira.ts`: the backstop is recoverable by narrowing the\n * scope, a page error or a stalled cursor is not, and {@link ProjectIssuesDrain} makes the caller say which.\n */\ntype JiraServerProjectIssuesDrain<T> = {\n\tissues: T[];\n\tstatus: 'complete' | 'backstop' | 'incomplete';\n\tmetadata?: CollectionMetadata;\n};\n\nconst metadata = providersMetadata[IssuesSelfManagedHostIntegrationId.JiraServer];\nconst authProvider = Object.freeze({ id: metadata.id, scopes: metadata.scopes });\nconst maxPagesPerRequest = 10;\n\n/**\n * The single resource a Jira Server connection has: the instance itself.\n *\n * Jira Cloud's account spans many sites, so its resources come from an API call that lists them. A\n * self-hosted instance IS the one site the connection addresses, so there is nothing to enumerate — this\n * descriptor is synthesized from the configured host. Modelling it as a resource anyway keeps the\n * resource → project contract of {@link IssuesIntegration} intact, so every tracker read (`listOrgs`,\n * `listProjects`, `listIssueTrackerIssuesPage`) works unchanged.\n */\nexport interface JiraServerResourceDescriptor extends IssueResourceDescriptor {\n\turl: string;\n}\n\nexport interface JiraServerProjectDescriptor extends IssueResourceDescriptor {\n\tresourceId: string;\n}\n\nexport class JiraServerIntegration extends IssuesIntegration<IssuesSelfManagedHostIntegrationId.JiraServer> {\n\treadonly authProvider: IntegrationAuthenticationProviderDescriptor = authProvider;\n\treadonly id = IssuesSelfManagedHostIntegrationId.JiraServer;\n\tprotected readonly key: IntegrationKey<IssuesSelfManagedHostIntegrationId.JiraServer>;\n\treadonly name: string = 'Jira Data Center';\n\n\tconstructor(\n\t\tctx: IntegrationServiceContext,\n\t\tauthenticationService: IntegrationAuthenticationService,\n\t\tgetProvidersApi: () => Promise<ProvidersApi>,\n\t\tdidChangeConnection: Emitter<IntegrationConnectionChangeEvent>,\n\t\tprivate readonly _domain: string,\n\t) {\n\t\tsuper(ctx, authenticationService, getProvidersApi, didChangeConnection);\n\t\tthis.key = `${this.id}:${this._domain}`;\n\t}\n\n\tget domain(): string {\n\t\treturn this._domain;\n\t}\n\n\t/**\n\t * The URL every request for this connection is addressed to, built from the SESSION's domain rather than\n\t * the integration's.\n\t *\n\t * `this.domain` is the host-keyed identity — `IntegrationService.get()` runs it through `hostFromDomain`\n\t * so one host can't build two instances — which means it has had any context path stripped, and so has\n\t * `session.domain`, which is stored as that same normalized host. Jira Data Center officially supports\n\t * being mounted below a context path (`https://jira.example.com/jira`), so the address comes from\n\t * {@link ProviderAuthenticationSession.baseUrl}, which carries the configured value verbatim; it falls\n\t * back to the host when the backend reported nothing more, which is the common case.\n\t *\n\t * Takes the session rather than reading `_session` so a per-connection (multi-account) read is addressed\n\t * to the host of the connection it resolved, not to whichever one happens to be current.\n\t */\n\tprotected apiBaseUrlFor(session: ProviderAuthenticationSession | undefined): string {\n\t\tconst configured = baseUrlFromDomain(session?.baseUrl, session?.protocol);\n\t\t// A `baseUrl` from another host is not this connection's address — refuse it rather than routing this\n\t\t// integration's reads somewhere its key never named.\n\t\tif (configured != null && areDomainsOnSameHost(session?.baseUrl, this.domain)) return configured;\n\n\t\treturn (\n\t\t\tbaseUrlFromDomain(session?.domain, session?.protocol) ??\n\t\t\tbaseUrlFromDomain(this.domain, session?.protocol) ??\n\t\t\t`${session?.protocol ?? 'https:'}//${this.domain}`\n\t\t);\n\t}\n\n\t/** The one resource this connection has — see {@link JiraServerResourceDescriptor}. */\n\tprivate resourceFor(session: ProviderAuthenticationSession | undefined): JiraServerResourceDescriptor {\n\t\treturn { id: this.domain, key: this.domain, name: this.domain, url: this.apiBaseUrlFor(session) };\n\t}\n\n\t// No `autolinks()` override, unlike Jira Cloud: an autolink prefix has to be the Jira project KEY, which\n\t// is what a commit message or branch name carries (`PROJ-123`). `/rest/api/2/project` reports no key — see\n\t// `getProviderProjectsForResources`, which falls back to the name because JQL accepts it — so the only\n\t// prefix this class could build is the display name, which matches nothing and yields a `/browse/` URL that\n\t// is not an issue. Registering that would be worse than registering none. Restore the override once the SDK\n\t// surfaces the project key (GKDEV-3629's sibling gap).\n\n\tprotected override async getProviderAccountForResource(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resource: JiraServerResourceDescriptor,\n\t): Promise<Account | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst user = await api.getJiraServerCurrentUser(toTokenWithInfo(this.id, session), this.apiBaseUrlFor(session));\n\n\t\tif (user == null) return undefined;\n\t\treturn toAccount(user, this);\n\t}\n\n\tprotected override getProviderResourcesForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_force?: boolean,\n\t): Promise<JiraServerResourceDescriptor[] | undefined> {\n\t\t// Synthesized from the configured host rather than fetched, so there is nothing to cache or force.\n\t\treturn Promise.resolve([this.resourceFor(session)]);\n\t}\n\n\tprivate _projects: Map<string, JiraServerProjectDescriptor[] | undefined> | undefined;\n\tprotected override async getProviderProjectsForResources(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources: JiraServerResourceDescriptor[],\n\t\tforce: boolean = false,\n\t): Promise<JiraServerProjectDescriptor[] | undefined> {\n\t\t// Every resource here is this instance (see `getProviderResourcesForUser`), so one read serves them\n\t\t// all; a caller that passes none is asking for nothing.\n\t\tif (resources.length === 0) return [];\n\n\t\tconst cacheKey = this.projectsCacheKey(session);\n\t\tconst cached = this._projects?.get(cacheKey);\n\t\tif (cached != null && !force) return cached;\n\n\t\tconst api = await this.getProvidersApi();\n\t\tconst projects = await api.getJiraServerProjects(\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\tthis.apiBaseUrlFor(session),\n\t\t);\n\t\tif (projects == null) return undefined;\n\n\t\tconst resourceId = this.resourceFor(session).id;\n\t\tconst descriptors = projects.map<JiraServerProjectDescriptor>(p => ({\n\t\t\tid: p.id,\n\t\t\t// The display name, NOT what the reads address the project by. `/rest/api/2/project` reports no\n\t\t\t// project key (GKDEV-3629's sibling gap) and the SDK's `JiraServerProject` carries only `id` and\n\t\t\t// `name`, so there is no key to put here; the reads below use `id` instead, because JQL resolves a\n\t\t\t// `project = \"<value>\"` clause against key before name, and a display name that collides with\n\t\t\t// ANOTHER project's key would silently return that project's issues.\n\t\t\tkey: p.name,\n\t\t\tname: p.name,\n\t\t\tresourceId: resourceId,\n\t\t}));\n\n\t\tthis._projects ??= new Map<string, JiraServerProjectDescriptor[] | undefined>();\n\t\tthis._projects.set(cacheKey, descriptors);\n\n\t\treturn descriptors;\n\t}\n\n\tprotected override async getProviderIssuesForProject(\n\t\tsession: ProviderAuthenticationSession,\n\t\tproject: JiraServerProjectDescriptor,\n\t\toptions?: IssuesForProjectOptions,\n\t): Promise<IssueShape[] | undefined> {\n\t\treturn (await this.getProviderIssuesForProjectWithTruncation(session, project, options))?.values;\n\t}\n\n\tprotected override async getProviderIssuesForProjectWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\tproject: JiraServerProjectDescriptor,\n\t\toptions?: IssuesForProjectOptions,\n\t): Promise<ProjectIssuesDrain | undefined> {\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\tconst api = await this.getProvidersApi();\n\t\tconst baseUrl = this.apiBaseUrlFor(session);\n\n\t\t// Mirrors Jira Cloud's drain (see `jira.ts`): page to a defensive backstop, and report `truncated` plus\n\t\t// a structured failure rather than discarding an already-fetched prefix, so the facade can warn and set\n\t\t// fetchFailed while still publishing what was read.\n\t\t// The numeric project id, not the name: JQL resolves `project = \"<value>\"` against key first, so a\n\t\t// display name that matches another project's key reads that project's issues instead. A Jira project\n\t\t// key must begin with a letter, so a purely numeric value can only be an id.\n\t\tconst projectJqlKey = project.id;\n\t\tconst projectScope = {\n\t\t\tproviderId: this.id,\n\t\t\tresourceId: project.resourceId,\n\t\t\tprojectId: project.id,\n\t\t};\n\t\tconst drainIssues = async (scope: {\n\t\t\tauthorLogin?: string;\n\t\t\tassigneeLogins?: string[];\n\t\t\tmentionLogin?: string;\n\t\t}): Promise<JiraServerProjectIssuesDrain<ProviderIssue>> => {\n\t\t\tconst collected: ProviderIssue[] = [];\n\t\t\tlet cursor: string | undefined;\n\t\t\tlet status: JiraServerProjectIssuesDrain<ProviderIssue>['status'] = 'complete';\n\t\t\tlet metadata: CollectionMetadata | undefined;\n\t\t\tfor (let i = 0; i < maxPagesPerRequest; i++) {\n\t\t\t\tlet result: Awaited<ReturnType<typeof api.getJiraServerIssuesForProjectPaged>> | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tresult = await api.getJiraServerIssuesForProjectPaged(tokenWithInfo, baseUrl, projectJqlKey, {\n\t\t\t\t\t\t...scope,\n\t\t\t\t\t\tcursor: cursor,\n\t\t\t\t\t\tsort: options?.sort,\n\t\t\t\t\t});\n\t\t\t\t} catch (ex) {\n\t\t\t\t\t// A page failure after the first leaves the drained prefix intact; record it at the project\n\t\t\t\t\t// scope instead of re-throwing. With nothing fetched yet the original throw is preserved, so\n\t\t\t\t\t// the caller sees a hard error rather than an empty partial success.\n\t\t\t\t\tif (collected.length === 0) throw ex;\n\n\t\t\t\t\tstatus = 'incomplete';\n\t\t\t\t\tmetadata = mergeCollectionMetadata(metadata, {\n\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\tfailures: [toCollectionScopeFailure(projectScope, ex)],\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (result == null) {\n\t\t\t\t\tif (cursor == null) break;\n\n\t\t\t\t\tstatus = 'incomplete';\n\t\t\t\t\tmetadata = mergeCollectionMetadata(metadata, {\n\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\tfailures: [\n\t\t\t\t\t\t\ttoCollectionScopeFailure(\n\t\t\t\t\t\t\t\tprojectScope,\n\t\t\t\t\t\t\t\tnew Error('Jira Server returned no page after advertising a continuation'),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t],\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcollected.push(...result.data);\n\t\t\t\tif (!result.hasMore) break;\n\n\t\t\t\t// More pages are claimed but no advancing cursor came back: the drain can't continue, so flag it\n\t\t\t\t// rather than stopping silently.\n\t\t\t\tif (result.nextCursor == null || result.nextCursor === cursor) {\n\t\t\t\t\tstatus = 'incomplete';\n\t\t\t\t\tmetadata = mergeCollectionMetadata(metadata, {\n\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\tfailures: [\n\t\t\t\t\t\t\ttoCollectionScopeFailure(\n\t\t\t\t\t\t\t\tprojectScope,\n\t\t\t\t\t\t\t\tnew Error('Jira Server returned no advancing issue continuation'),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t],\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcursor = result.nextCursor;\n\t\t\t\t// Pages remain but this was the last allowed iteration: the backstop stopped the drain, which a\n\t\t\t\t// narrower scope would avoid.\n\t\t\t\tif (i === maxPagesPerRequest - 1) {\n\t\t\t\t\tstatus = 'backstop';\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn { issues: collected, status: status, metadata: metadata };\n\t\t};\n\n\t\t/**\n\t\t * Every clause is scoped by the HANDLE, and `options.userId` is deliberately ignored here — the one\n\t\t * place this provider diverges from Jira Cloud's, which applies it to `assignee`/`creator` (#5857).\n\t\t *\n\t\t * The two are not interchangeable because `Account.id` means something different per deployment. On\n\t\t * Cloud it is the `accountId`, which JQL resolves, so scoping by it fixes the deactivated-account case\n\t\t * where the display name no longer resolves. On Server it is the user KEY (`JIRAUSER10224`), which JQL\n\t\t * does not resolve at all: measured against a live Jira Server 8.20.13 site, `assignee in\n\t\t * (\"JIRAUSER10224\")` returns 0 with `\"The value 'JIRAUSER10224' does not exist for the field\n\t\t * 'assignee'\"` — the identical warning a genuinely nonexistent user produces — while the username and\n\t\t * the display name both return the real 4.\n\t\t *\n\t\t * So applying `userId` here would silently empty the read rather than harden it. Do not \"fix\" this\n\t\t * asymmetry to match Cloud without re-measuring against a Server instance.\n\t\t *\n\t\t * The handle itself is already the right one: `provider-apis` 0.60.0 maps `Account.username` from the\n\t\t * Server payload's `name` (falling back to `displayName` when absent), so these clauses are scoped by the\n\t\t * real username rather than a display name (GKDEV-3629). That is what makes the `mention` clause work at\n\t\t * all — it is a free-text `comment ~ \"...\"` over comment bodies, and a Jira mention is stored as\n\t\t * `[~username]`, which a display name never matches.\n\t\t */\n\t\tconst getSearchedUserIssuesForFilter = async (\n\t\t\tuser: string,\n\t\t\tfilter: IssueFilter,\n\t\t): Promise<JiraServerProjectIssuesDrain<IssueShape>> => {\n\t\t\tconst result = await drainIssues({\n\t\t\t\tauthorLogin: filter === IssueFilter.Author ? user : undefined,\n\t\t\t\tassigneeLogins: filter === IssueFilter.Assignee ? [user] : undefined,\n\t\t\t\tmentionLogin: filter === IssueFilter.Mention ? user : undefined,\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\tissues: result.issues\n\t\t\t\t\t.map(issue => toIssueShape(issue, this))\n\t\t\t\t\t.filter((r): r is IssueShape => r !== undefined),\n\t\t\t\tstatus: result.status,\n\t\t\t\tmetadata: result.metadata,\n\t\t\t};\n\t\t};\n\n\t\tif (options?.user != null) {\n\t\t\tconst user = options.user;\n\t\t\t// A resolved user always scopes the read, defaulting to \"my issues\" when no explicit filters are\n\t\t\t// given — otherwise it would fall through to the unscoped fetch and return the whole project (#5438).\n\t\t\tconst filters = options.filters?.length ? options.filters : [IssueFilter.Assignee];\n\t\t\tconst settled = await Promise.allSettled(\n\t\t\t\tfilters.map(filter => getSearchedUserIssuesForFilter(user, filter)),\n\t\t\t);\n\n\t\t\t// Every branch rejected: the read failed outright, so propagate the first reason as-is (not wrapped)\n\t\t\t// to keep its auth/rate-limit classification, and log the rest, which would otherwise be discarded.\n\t\t\tif (settled.every(r => r.status === 'rejected')) {\n\t\t\t\tfor (let i = 1; i < settled.length; i++) {\n\t\t\t\t\tconst outcome = settled[i];\n\t\t\t\t\tif (outcome.status === 'rejected') {\n\t\t\t\t\t\tLogger.error(\n\t\t\t\t\t\t\toutcome.reason,\n\t\t\t\t\t\t\t`getProviderIssuesForProjectWithTruncation: filter '${filters[i]}' failed`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthrow settled[0].status === 'rejected' ? settled[0].reason : new Error('Jira Server issue read failed');\n\t\t\t}\n\n\t\t\tlet truncated = false;\n\t\t\tlet metadata: CollectionMetadata | undefined;\n\t\t\tconst resultsById = new Map<string, IssueShape>();\n\t\t\tfor (let i = 0; i < settled.length; i++) {\n\t\t\t\tconst outcome = settled[i];\n\t\t\t\tconst filter = filters[i];\n\t\t\t\t// One rejected branch with a succeeding sibling means this project's issues are incomplete: keep\n\t\t\t\t// the sibling results and record a per-filter failure so the facade can warn on it specifically.\n\t\t\t\tif (outcome.status !== 'fulfilled') {\n\t\t\t\t\t// Identical for every branch, so degrading it would report one failure per filter for a\n\t\t\t\t\t// single invalid call — see `throwIfCallerContractError`.\n\t\t\t\t\tthrowIfCallerContractError(outcome.reason);\n\n\t\t\t\t\ttruncated = true;\n\t\t\t\t\tconst failure = toCollectionScopeFailure(projectScope, outcome.reason);\n\t\t\t\t\tmetadata = mergeCollectionMetadata(metadata, {\n\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\tfailures: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t...failure,\n\t\t\t\t\t\t\t\tmessage: `Issue filter '${filter}' could not be read${\n\t\t\t\t\t\t\t\t\tfailure.message != null ? `: ${failure.message}` : ''\n\t\t\t\t\t\t\t\t}`,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (outcome.value.status !== 'complete') {\n\t\t\t\t\ttruncated = true;\n\t\t\t\t}\n\t\t\t\tif (outcome.value.metadata != null) {\n\t\t\t\t\tmetadata = mergeCollectionMetadata(metadata, outcome.value.metadata);\n\t\t\t\t}\n\t\t\t\tfor (const resultIssue of outcome.value.issues) {\n\t\t\t\t\tif (!resultsById.has(resultIssue.id)) {\n\t\t\t\t\t\tresultsById.set(resultIssue.id, resultIssue);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// `recovery: 'none'` for the whole set: the filter branches merge here, so even a pure backstop in one\n\t\t\t// branch is not fixable by narrowing this read's scope.\n\t\t\treturn truncated\n\t\t\t\t? { values: [...resultsById.values()], truncated: true, recovery: 'none', metadata: metadata }\n\t\t\t\t: { values: [...resultsById.values()], truncated: false, metadata: metadata };\n\t\t}\n\n\t\tconst unscoped = await drainIssues({});\n\t\tconst values = unscoped.issues\n\t\t\t.map(issue => toIssueShape(issue, this))\n\t\t\t.filter((result): result is IssueShape => result !== undefined);\n\t\treturn unscoped.status !== 'complete'\n\t\t\t? {\n\t\t\t\t\tvalues: values,\n\t\t\t\t\ttruncated: true,\n\t\t\t\t\trecovery: unscoped.status === 'backstop' ? 'narrow-scope' : 'none',\n\t\t\t\t\tmetadata: unscoped.metadata,\n\t\t\t\t}\n\t\t\t: { values: values, truncated: false, metadata: unscoped.metadata };\n\t}\n\n\tprotected override async searchProviderMyIssues(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources?: JiraServerResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t): Promise<IssueShape[] | undefined> {\n\t\treturn (await this.searchProviderMyIssuesWithTruncation(session, resources, cancellation))?.values;\n\t}\n\n\t/**\n\t * Account-wide \"my issues\" for this instance, drained to the backstop and reporting whether it got there.\n\t *\n\t * The override exists for the same reason the project drain reports `status`: the default\n\t * implementation wraps whatever the normalized read returned as `truncated: false`, so a run that stopped\n\t * at `maxPagesPerRequest` with pages left, or on a continuation the server never advanced, would be\n\t * published as a complete account. Cancellation is NOT truncation — the caller asked for the stop and\n\t * already knows the prefix is partial — so it breaks without setting the flag.\n\t */\n\tprotected override async searchProviderMyIssuesWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resources?: JiraServerResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t): Promise<AccountWideIssuesResult | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\tconst baseUrl = this.apiBaseUrlFor(session);\n\n\t\t// Errors propagate, unlike Jira Cloud's sibling. Cloud swallows per resource because it loops over the\n\t\t// account's many sites and one bad site must not discard the others; this connection addresses exactly\n\t\t// one instance, so there is no sibling to protect and swallowing would turn every failure — an expired\n\t\t// token, an unreachable host — into an empty success. `searchMyIssuesWithTruncationResult` recovers the\n\t\t// throw into `{ error }`, which is what lets the caller warn instead of reporting \"no issues\".\n\t\tconst accountScope = { providerId: this.id, resourceId: this.domain };\n\t\tconst results: IssueShape[] = [];\n\t\tlet cursor: string | undefined;\n\t\tlet truncated = false;\n\t\tlet metadata: CollectionMetadata | undefined;\n\n\t\tconst stopIncomplete = (reason: string) => {\n\t\t\ttruncated = true;\n\t\t\tmetadata = mergeCollectionMetadata(metadata, {\n\t\t\t\tcompleteness: 'partial',\n\t\t\t\tfailures: [toCollectionScopeFailure(accountScope, new Error(reason))],\n\t\t\t});\n\t\t};\n\n\t\tfor (let i = 0; i < maxPagesPerRequest; i++) {\n\t\t\tif (cancellation?.aborted) break;\n\n\t\t\tconst page = await api.getJiraServerIssuesForCurrentUser(tokenWithInfo, baseUrl, { cursor: cursor });\n\t\t\tif (page == null) {\n\t\t\t\t// Nothing fetched yet and no page is an empty account, not a failure; after a continuation it\n\t\t\t\t// means the server dropped the drain mid-way.\n\t\t\t\tif (cursor != null) {\n\t\t\t\t\tstopIncomplete('Jira Server returned no page after advertising a continuation');\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tresults.push(\n\t\t\t\t...page.data.map(issue => toIssueShape(issue, this)).filter((r): r is IssueShape => r != null),\n\t\t\t);\n\n\t\t\tif (!page.hasMore) break;\n\n\t\t\t// More pages are claimed but no advancing cursor came back: the drain can't continue, so flag it\n\t\t\t// rather than stopping silently.\n\t\t\tif (page.nextCursor == null || page.nextCursor === cursor) {\n\t\t\t\tstopIncomplete('Jira Server returned no advancing issue continuation');\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcursor = page.nextCursor;\n\t\t\t// Pages remain but this was the last allowed iteration: the backstop stopped the drain.\n\t\t\tif (i === maxPagesPerRequest - 1) {\n\t\t\t\tstopIncomplete(\n\t\t\t\t\t`Jira Server account-wide issue drain stopped at the ${maxPagesPerRequest}-page backstop`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn { values: results, truncated: truncated, metadata: metadata };\n\t}\n\n\tprotected override async getProviderLinkedIssueOrPullRequest(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resource: JiraServerResourceDescriptor,\n\t\t{ key }: { id: string; key: string },\n\t): Promise<IssueOrPullRequest | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst issue = await api.getJiraServerIssue(toTokenWithInfo(this.id, session), this.apiBaseUrlFor(session), key);\n\t\treturn issue != null ? toIssueShape(issue, this) : undefined;\n\t}\n\n\tprotected override async getProviderIssue(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resource: JiraServerResourceDescriptor,\n\t\tid: string,\n\t): Promise<Issue | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst apiResult = await api.getJiraServerIssue(\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\tthis.apiBaseUrlFor(session),\n\t\t\tid,\n\t\t);\n\t\tconst issue = apiResult != null ? toIssueShape(apiResult, this) : undefined;\n\t\treturn issue != null ? { ...issue, type: 'issue' } : undefined;\n\t}\n\n\t/**\n\t * The point read behind `getTrackerIssue` (#5872). `resourceId` is this connection's single synthetic\n\t * resource — the host itself, see {@link JiraServerResourceDescriptor} — so it selects nothing further here:\n\t * the host was chosen upstream by `domain` (or the connection's configured domain) and the request is\n\t * addressed to the SESSION's base URL like every other read. No `resourceUrl` is needed either, unlike Jira\n\t * Cloud: its REST `self` link is an API endpoint, whereas `getJiraServerIssue` builds the browser link from\n\t * the base URL it was addressed to. A key that names no issue resolves to `undefined`, a proven absence.\n\t */\n\tprotected override getProviderIssueByResourceId(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resourceId: string,\n\t\tid: string,\n\t\t_resourceUrl: string | undefined,\n\t): Promise<Issue | undefined> {\n\t\treturn this.getProviderIssue(session, this.resourceFor(session), id);\n\t}\n\n\t/**\n\t * Caches are keyed by the ADDRESS the read was made against as well as the token: one integration instance\n\t * exists per configured host and two hosts can hold the same token, so a token-only key would serve one\n\t * host's projects for the other — and the host alone is not enough either, because a connection re-pointed\n\t * to another context path on the same host keeps its token and would otherwise be served the old path's\n\t * projects.\n\t */\n\tprivate projectsCacheKey(session: ProviderAuthenticationSession): string {\n\t\treturn `${this.apiBaseUrlFor(session)}:${session.accessToken}`;\n\t}\n\n\tprotected override providerOnDisconnect(): void {\n\t\tthis._projects = undefined;\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"jira-server.js","names":[],"sources":["../../../../../plus/integrations/src/providers/jira-server.ts"],"sourcesContent":["import type { CollectionMetadata } from '@gitkraken/provider-apis';\nimport type { Account } from '@gitlens/git/models/author.js';\nimport type { Issue, IssueShape } from '@gitlens/git/models/issue.js';\nimport type { IssueOrPullRequest } from '@gitlens/git/models/issueOrPullRequest.js';\nimport type { IssueResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport type { Emitter } from '@gitlens/utils/event.js';\nimport { Logger } from '@gitlens/utils/logger.js';\nimport type { IntegrationAuthenticationProviderDescriptor } from '../authentication/integrationAuthenticationProvider.js';\nimport type { IntegrationAuthenticationService } from '../authentication/integrationAuthenticationService.js';\nimport type { ProviderAuthenticationSession } from '../authentication/models.js';\nimport { toTokenWithInfo } from '../authentication/models.js';\nimport { throwIfCallerContractError, toCollectionScopeFailure } from '../collectionMetadata.js';\nimport { IssuesSelfManagedHostIntegrationId } from '../constants.js';\nimport type { IntegrationServiceContext } from '../context.js';\nimport type { IntegrationConnectionChangeEvent } from '../integrationService.js';\nimport type { IntegrationKey } from '../models/integration.js';\nimport type { AccountWideIssuesResult, IssuesForProjectOptions, ProjectIssuesDrain } from '../models/issueReads.js';\nimport { IssuesIntegration } from '../models/issuesIntegration.js';\nimport { areDomainsOnSameHost, baseUrlFromDomain } from '../utils/domain.utils.js';\nimport type { ProviderIssue } from './models.js';\nimport { IssueFilter, providersMetadata, toAccount, toIssueShape } from './models.js';\nimport type { ProvidersApi } from './providersApi.js';\nimport { mergeCollectionMetadata } from './utils/providerPaging.js';\n\n/**\n * Why three states rather than a boolean, matching `jira.ts`: the backstop is recoverable by narrowing the\n * scope, a page error or a stalled cursor is not, and {@link ProjectIssuesDrain} makes the caller say which.\n */\ntype JiraServerProjectIssuesDrain<T> = {\n\tissues: T[];\n\tstatus: 'complete' | 'backstop' | 'incomplete';\n\tmetadata?: CollectionMetadata;\n};\n\nconst metadata = providersMetadata[IssuesSelfManagedHostIntegrationId.JiraServer];\nconst authProvider = Object.freeze({ id: metadata.id, scopes: metadata.scopes });\nconst maxPagesPerRequest = 10;\n\n/**\n * The single resource a Jira Server connection has: the instance itself.\n *\n * Jira Cloud's account spans many sites, so its resources come from an API call that lists them. A\n * self-hosted instance IS the one site the connection addresses, so there is nothing to enumerate — this\n * descriptor is synthesized from the configured host. Modelling it as a resource anyway keeps the\n * resource → project contract of {@link IssuesIntegration} intact, so every tracker read (`listOrgs`,\n * `listProjects`, `listIssueTrackerIssuesPage`) works unchanged.\n */\nexport interface JiraServerResourceDescriptor extends IssueResourceDescriptor {\n\turl: string;\n}\n\nexport interface JiraServerProjectDescriptor extends IssueResourceDescriptor {\n\tresourceId: string;\n}\n\nexport class JiraServerIntegration extends IssuesIntegration<IssuesSelfManagedHostIntegrationId.JiraServer> {\n\treadonly authProvider: IntegrationAuthenticationProviderDescriptor = authProvider;\n\treadonly id = IssuesSelfManagedHostIntegrationId.JiraServer;\n\tprotected readonly key: IntegrationKey<IssuesSelfManagedHostIntegrationId.JiraServer>;\n\treadonly name: string = 'Jira Data Center';\n\n\tconstructor(\n\t\tctx: IntegrationServiceContext,\n\t\tauthenticationService: IntegrationAuthenticationService,\n\t\tgetProvidersApi: () => Promise<ProvidersApi>,\n\t\tdidChangeConnection: Emitter<IntegrationConnectionChangeEvent>,\n\t\tprivate readonly _domain: string,\n\t) {\n\t\tsuper(ctx, authenticationService, getProvidersApi, didChangeConnection);\n\t\tthis.key = `${this.id}:${this._domain}`;\n\t}\n\n\tget domain(): string {\n\t\treturn this._domain;\n\t}\n\n\t/**\n\t * The URL every request for this connection is addressed to, built from the SESSION's domain rather than\n\t * the integration's.\n\t *\n\t * `this.domain` is the host-keyed identity — `IntegrationService.get()` runs it through `hostFromDomain`\n\t * so one host can't build two instances — which means it has had any context path stripped, and so has\n\t * `session.domain`, which is stored as that same normalized host. Jira Data Center officially supports\n\t * being mounted below a context path (`https://jira.example.com/jira`), so the address comes from\n\t * {@link ProviderAuthenticationSession.baseUrl}, which carries the configured value verbatim; it falls\n\t * back to the host when the backend reported nothing more, which is the common case.\n\t *\n\t * Takes the session rather than reading `_session` so a per-connection (multi-account) read is addressed\n\t * to the host of the connection it resolved, not to whichever one happens to be current.\n\t */\n\tprotected apiBaseUrlFor(session: ProviderAuthenticationSession | undefined): string {\n\t\tconst configured = baseUrlFromDomain(session?.baseUrl, session?.protocol);\n\t\t// A `baseUrl` from another host is not this connection's address — refuse it rather than routing this\n\t\t// integration's reads somewhere its key never named.\n\t\tif (configured != null && areDomainsOnSameHost(session?.baseUrl, this.domain)) return configured;\n\n\t\treturn (\n\t\t\tbaseUrlFromDomain(session?.domain, session?.protocol) ??\n\t\t\tbaseUrlFromDomain(this.domain, session?.protocol) ??\n\t\t\t`${session?.protocol ?? 'https:'}//${this.domain}`\n\t\t);\n\t}\n\n\t/** The one resource this connection has — see {@link JiraServerResourceDescriptor}. */\n\tprivate resourceFor(session: ProviderAuthenticationSession | undefined): JiraServerResourceDescriptor {\n\t\treturn { id: this.domain, key: this.domain, name: this.domain, url: this.apiBaseUrlFor(session) };\n\t}\n\n\t// No `autolinks()` override, unlike Jira Cloud: an autolink prefix has to be the Jira project KEY, which\n\t// is what a commit message or branch name carries (`PROJ-123`). `/rest/api/2/project` reports no key — see\n\t// `getProviderProjectsForResources`, which falls back to the name because JQL accepts it — so the only\n\t// prefix this class could build is the display name, which matches nothing and yields a `/browse/` URL that\n\t// is not an issue. Registering that would be worse than registering none. Restore the override once the SDK\n\t// surfaces the project key (GKDEV-3629's sibling gap).\n\n\tprotected override async getProviderAccountForResource(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resource: JiraServerResourceDescriptor,\n\t): Promise<Account | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst user = await api.getJiraServerCurrentUser(toTokenWithInfo(this.id, session), this.apiBaseUrlFor(session));\n\n\t\tif (user == null) return undefined;\n\t\treturn toAccount(user, this);\n\t}\n\n\tprotected override getProviderResourcesForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_force?: boolean,\n\t): Promise<JiraServerResourceDescriptor[] | undefined> {\n\t\t// Synthesized from the configured host rather than fetched, so there is nothing to cache or force.\n\t\treturn Promise.resolve([this.resourceFor(session)]);\n\t}\n\n\tprivate _projects: Map<string, JiraServerProjectDescriptor[] | undefined> | undefined;\n\tprotected override async getProviderProjectsForResources(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources: JiraServerResourceDescriptor[],\n\t\tforce: boolean = false,\n\t): Promise<JiraServerProjectDescriptor[] | undefined> {\n\t\t// Every resource here is this instance (see `getProviderResourcesForUser`), so one read serves them\n\t\t// all; a caller that passes none is asking for nothing.\n\t\tif (resources.length === 0) return [];\n\n\t\tconst cacheKey = this.projectsCacheKey(session);\n\t\tconst cached = this._projects?.get(cacheKey);\n\t\tif (cached != null && !force) return cached;\n\n\t\tconst api = await this.getProvidersApi();\n\t\tconst projects = await api.getJiraServerProjects(\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\tthis.apiBaseUrlFor(session),\n\t\t);\n\t\tif (projects == null) return undefined;\n\n\t\tconst resourceId = this.resourceFor(session).id;\n\t\tconst descriptors = projects.map<JiraServerProjectDescriptor>(p => ({\n\t\t\tid: p.id,\n\t\t\t// The display name, NOT what the reads address the project by. `/rest/api/2/project` reports no\n\t\t\t// project key (GKDEV-3629's sibling gap) and the SDK's `JiraServerProject` carries only `id` and\n\t\t\t// `name`, so there is no key to put here; the reads below use `id` instead, because JQL resolves a\n\t\t\t// `project = \"<value>\"` clause against key before name, and a display name that collides with\n\t\t\t// ANOTHER project's key would silently return that project's issues.\n\t\t\tkey: p.name,\n\t\t\tname: p.name,\n\t\t\tresourceId: resourceId,\n\t\t}));\n\n\t\tthis._projects ??= new Map<string, JiraServerProjectDescriptor[] | undefined>();\n\t\tthis._projects.set(cacheKey, descriptors);\n\n\t\treturn descriptors;\n\t}\n\n\tprotected override async getProviderIssuesForProject(\n\t\tsession: ProviderAuthenticationSession,\n\t\tproject: JiraServerProjectDescriptor,\n\t\toptions?: IssuesForProjectOptions,\n\t): Promise<IssueShape[] | undefined> {\n\t\treturn (await this.getProviderIssuesForProjectWithTruncation(session, project, options))?.values;\n\t}\n\n\tprotected override async getProviderIssuesForProjectWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\tproject: JiraServerProjectDescriptor,\n\t\toptions?: IssuesForProjectOptions,\n\t): Promise<ProjectIssuesDrain | undefined> {\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\tconst api = await this.getProvidersApi();\n\t\tconst baseUrl = this.apiBaseUrlFor(session);\n\n\t\t// Mirrors Jira Cloud's drain (see `jira.ts`): page to a defensive backstop, and report `truncated` plus\n\t\t// a structured failure rather than discarding an already-fetched prefix, so the facade can warn and set\n\t\t// fetchFailed while still publishing what was read.\n\t\t// The numeric project id, not the name: JQL resolves `project = \"<value>\"` against key first, so a\n\t\t// display name that matches another project's key reads that project's issues instead. A Jira project\n\t\t// key must begin with a letter, so a purely numeric value can only be an id.\n\t\tconst projectJqlKey = project.id;\n\t\tconst projectScope = {\n\t\t\tproviderId: this.id,\n\t\t\tresourceId: project.resourceId,\n\t\t\tprojectId: project.id,\n\t\t};\n\t\tconst drainIssues = async (scope: {\n\t\t\tauthorLogin?: string;\n\t\t\tassigneeLogins?: string[];\n\t\t\tmentionLogin?: string;\n\t\t}): Promise<JiraServerProjectIssuesDrain<ProviderIssue>> => {\n\t\t\tconst collected: ProviderIssue[] = [];\n\t\t\tlet cursor: string | undefined;\n\t\t\tlet status: JiraServerProjectIssuesDrain<ProviderIssue>['status'] = 'complete';\n\t\t\tlet metadata: CollectionMetadata | undefined;\n\t\t\tfor (let i = 0; i < maxPagesPerRequest; i++) {\n\t\t\t\tlet result: Awaited<ReturnType<typeof api.getJiraServerIssuesForProjectPaged>> | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tresult = await api.getJiraServerIssuesForProjectPaged(tokenWithInfo, baseUrl, projectJqlKey, {\n\t\t\t\t\t\t...scope,\n\t\t\t\t\t\tcursor: cursor,\n\t\t\t\t\t\tsort: options?.sort,\n\t\t\t\t\t});\n\t\t\t\t} catch (ex) {\n\t\t\t\t\t// A page failure after the first leaves the drained prefix intact; record it at the project\n\t\t\t\t\t// scope instead of re-throwing. With nothing fetched yet the original throw is preserved, so\n\t\t\t\t\t// the caller sees a hard error rather than an empty partial success.\n\t\t\t\t\tif (collected.length === 0) throw ex;\n\n\t\t\t\t\tstatus = 'incomplete';\n\t\t\t\t\tmetadata = mergeCollectionMetadata(metadata, {\n\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\tfailures: [toCollectionScopeFailure(projectScope, ex)],\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (result == null) {\n\t\t\t\t\tif (cursor == null) break;\n\n\t\t\t\t\tstatus = 'incomplete';\n\t\t\t\t\tmetadata = mergeCollectionMetadata(metadata, {\n\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\tfailures: [\n\t\t\t\t\t\t\ttoCollectionScopeFailure(\n\t\t\t\t\t\t\t\tprojectScope,\n\t\t\t\t\t\t\t\tnew Error('Jira Server returned no page after advertising a continuation'),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t],\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcollected.push(...result.data);\n\t\t\t\tif (!result.hasMore) break;\n\n\t\t\t\t// More pages are claimed but no advancing cursor came back: the drain can't continue, so flag it\n\t\t\t\t// rather than stopping silently.\n\t\t\t\tif (result.nextCursor == null || result.nextCursor === cursor) {\n\t\t\t\t\tstatus = 'incomplete';\n\t\t\t\t\tmetadata = mergeCollectionMetadata(metadata, {\n\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\tfailures: [\n\t\t\t\t\t\t\ttoCollectionScopeFailure(\n\t\t\t\t\t\t\t\tprojectScope,\n\t\t\t\t\t\t\t\tnew Error('Jira Server returned no advancing issue continuation'),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t],\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcursor = result.nextCursor;\n\t\t\t\t// Pages remain but this was the last allowed iteration: the backstop stopped the drain, which a\n\t\t\t\t// narrower scope would avoid.\n\t\t\t\tif (i === maxPagesPerRequest - 1) {\n\t\t\t\t\tstatus = 'backstop';\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn { issues: collected, status: status, metadata: metadata };\n\t\t};\n\n\t\t/**\n\t\t * Every clause is scoped by the HANDLE, and `options.userId` is deliberately ignored here — the one\n\t\t * place this provider diverges from Jira Cloud's, which applies it to `assignee`/`creator` (#5857).\n\t\t *\n\t\t * The two are not interchangeable because `Account.id` means something different per deployment. On\n\t\t * Cloud it is the `accountId`, which JQL resolves, so scoping by it fixes the deactivated-account case\n\t\t * where the display name no longer resolves. On Server it is the user KEY (`JIRAUSER10224`), which JQL\n\t\t * does not resolve at all: measured against a live Jira Server 8.20.13 site, `assignee in\n\t\t * (\"JIRAUSER10224\")` returns 0 with `\"The value 'JIRAUSER10224' does not exist for the field\n\t\t * 'assignee'\"` — the identical warning a genuinely nonexistent user produces — while the username and\n\t\t * the display name both return the real 4.\n\t\t *\n\t\t * So applying `userId` here would silently empty the read rather than harden it. Do not \"fix\" this\n\t\t * asymmetry to match Cloud without re-measuring against a Server instance.\n\t\t *\n\t\t * The handle itself is already the right one: `provider-apis` 0.60.0 maps `Account.username` from the\n\t\t * Server payload's `name` (falling back to `displayName` when absent), so these clauses are scoped by the\n\t\t * real username rather than a display name (GKDEV-3629). That is what makes the `mention` clause work at\n\t\t * all — it is a free-text `comment ~ \"...\"` over comment bodies, and a Jira mention is stored as\n\t\t * `[~username]`, which a display name never matches.\n\t\t */\n\t\tconst getSearchedUserIssuesForFilter = async (\n\t\t\tuser: string,\n\t\t\tfilter: IssueFilter,\n\t\t): Promise<JiraServerProjectIssuesDrain<IssueShape>> => {\n\t\t\tconst result = await drainIssues({\n\t\t\t\tauthorLogin: filter === IssueFilter.Author ? user : undefined,\n\t\t\t\tassigneeLogins: filter === IssueFilter.Assignee ? [user] : undefined,\n\t\t\t\tmentionLogin: filter === IssueFilter.Mention ? user : undefined,\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\tissues: result.issues\n\t\t\t\t\t.map(issue => toIssueShape(issue, this))\n\t\t\t\t\t.filter((r): r is IssueShape => r !== undefined),\n\t\t\t\tstatus: result.status,\n\t\t\t\tmetadata: result.metadata,\n\t\t\t};\n\t\t};\n\n\t\tif (options?.user != null) {\n\t\t\tconst user = options.user;\n\t\t\t// A resolved user always scopes the read, defaulting to \"my issues\" when no explicit filters are\n\t\t\t// given — otherwise it would fall through to the unscoped fetch and return the whole project (#5438).\n\t\t\tconst filters = options.filters?.length ? options.filters : [IssueFilter.Assignee];\n\t\t\tconst settled = await Promise.allSettled(\n\t\t\t\tfilters.map(filter => getSearchedUserIssuesForFilter(user, filter)),\n\t\t\t);\n\n\t\t\t// Every branch rejected: the read failed outright, so propagate the first reason as-is (not wrapped)\n\t\t\t// to keep its auth/rate-limit classification, and log the rest, which would otherwise be discarded.\n\t\t\tif (settled.every(r => r.status === 'rejected')) {\n\t\t\t\tfor (let i = 1; i < settled.length; i++) {\n\t\t\t\t\tconst outcome = settled[i];\n\t\t\t\t\tif (outcome.status === 'rejected') {\n\t\t\t\t\t\tLogger.error(\n\t\t\t\t\t\t\toutcome.reason,\n\t\t\t\t\t\t\t`getProviderIssuesForProjectWithTruncation: filter '${filters[i]}' failed`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthrow settled[0].status === 'rejected' ? settled[0].reason : new Error('Jira Server issue read failed');\n\t\t\t}\n\n\t\t\tlet truncated = false;\n\t\t\tlet metadata: CollectionMetadata | undefined;\n\t\t\tconst resultsById = new Map<string, IssueShape>();\n\t\t\tfor (let i = 0; i < settled.length; i++) {\n\t\t\t\tconst outcome = settled[i];\n\t\t\t\tconst filter = filters[i];\n\t\t\t\t// One rejected branch with a succeeding sibling means this project's issues are incomplete: keep\n\t\t\t\t// the sibling results and record a per-filter failure so the facade can warn on it specifically.\n\t\t\t\tif (outcome.status !== 'fulfilled') {\n\t\t\t\t\t// Identical for every branch, so degrading it would report one failure per filter for a\n\t\t\t\t\t// single invalid call — see `throwIfCallerContractError`.\n\t\t\t\t\tthrowIfCallerContractError(outcome.reason);\n\n\t\t\t\t\ttruncated = true;\n\t\t\t\t\tconst failure = toCollectionScopeFailure(projectScope, outcome.reason);\n\t\t\t\t\tmetadata = mergeCollectionMetadata(metadata, {\n\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\tfailures: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t...failure,\n\t\t\t\t\t\t\t\tmessage: `Issue filter '${filter}' could not be read${\n\t\t\t\t\t\t\t\t\tfailure.message != null ? `: ${failure.message}` : ''\n\t\t\t\t\t\t\t\t}`,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (outcome.value.status !== 'complete') {\n\t\t\t\t\ttruncated = true;\n\t\t\t\t}\n\t\t\t\tif (outcome.value.metadata != null) {\n\t\t\t\t\tmetadata = mergeCollectionMetadata(metadata, outcome.value.metadata);\n\t\t\t\t}\n\t\t\t\tfor (const resultIssue of outcome.value.issues) {\n\t\t\t\t\tif (!resultsById.has(resultIssue.id)) {\n\t\t\t\t\t\tresultsById.set(resultIssue.id, resultIssue);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// `recovery: 'none'` for the whole set: the filter branches merge here, so even a pure backstop in one\n\t\t\t// branch is not fixable by narrowing this read's scope.\n\t\t\treturn truncated\n\t\t\t\t? { values: [...resultsById.values()], truncated: true, recovery: 'none', metadata: metadata }\n\t\t\t\t: { values: [...resultsById.values()], truncated: false, metadata: metadata };\n\t\t}\n\n\t\tconst unscoped = await drainIssues({});\n\t\tconst values = unscoped.issues\n\t\t\t.map(issue => toIssueShape(issue, this))\n\t\t\t.filter((result): result is IssueShape => result !== undefined);\n\t\treturn unscoped.status !== 'complete'\n\t\t\t? {\n\t\t\t\t\tvalues: values,\n\t\t\t\t\ttruncated: true,\n\t\t\t\t\trecovery: unscoped.status === 'backstop' ? 'narrow-scope' : 'none',\n\t\t\t\t\tmetadata: unscoped.metadata,\n\t\t\t\t}\n\t\t\t: { values: values, truncated: false, metadata: unscoped.metadata };\n\t}\n\n\tprotected override async searchProviderMyIssues(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources?: JiraServerResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t): Promise<IssueShape[] | undefined> {\n\t\treturn (await this.searchProviderMyIssuesWithTruncation(session, resources, cancellation))?.values;\n\t}\n\n\t/**\n\t * Account-wide \"my issues\" for this instance, drained to the backstop and reporting whether it got there.\n\t *\n\t * The override exists for the same reason the project drain reports `status`: the default\n\t * implementation wraps whatever the normalized read returned as `truncated: false`, so a run that stopped\n\t * at `maxPagesPerRequest` with pages left, or on a continuation the server never advanced, would be\n\t * published as a complete account. Cancellation is NOT truncation — the caller asked for the stop and\n\t * already knows the prefix is partial — so it breaks without setting the flag.\n\t */\n\tprotected override async searchProviderMyIssuesWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resources?: JiraServerResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t): Promise<AccountWideIssuesResult | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\tconst baseUrl = this.apiBaseUrlFor(session);\n\n\t\t// Errors propagate, unlike Jira Cloud's sibling. Cloud swallows per resource because it loops over the\n\t\t// account's many sites and one bad site must not discard the others; this connection addresses exactly\n\t\t// one instance, so there is no sibling to protect and swallowing would turn every failure — an expired\n\t\t// token, an unreachable host — into an empty success. `searchMyIssuesWithTruncationResult` recovers the\n\t\t// throw into `{ error }`, which is what lets the caller warn instead of reporting \"no issues\".\n\t\t// Only the provider: the instance is this connection's single resource, so naming it would publish a\n\t\t// failure of the whole connection as one confined to a sub-scope (see `ProviderWarning.scope`).\n\t\tconst accountScope = { providerId: this.id };\n\t\tconst results: IssueShape[] = [];\n\t\tlet cursor: string | undefined;\n\t\tlet truncated = false;\n\t\tlet metadata: CollectionMetadata | undefined;\n\n\t\tconst stopIncomplete = (reason: string) => {\n\t\t\ttruncated = true;\n\t\t\tmetadata = mergeCollectionMetadata(metadata, {\n\t\t\t\tcompleteness: 'partial',\n\t\t\t\tfailures: [toCollectionScopeFailure(accountScope, new Error(reason))],\n\t\t\t});\n\t\t};\n\n\t\tfor (let i = 0; i < maxPagesPerRequest; i++) {\n\t\t\tif (cancellation?.aborted) break;\n\n\t\t\tconst page = await api.getJiraServerIssuesForCurrentUser(tokenWithInfo, baseUrl, { cursor: cursor });\n\t\t\tif (page == null) {\n\t\t\t\t// Nothing fetched yet and no page is an empty account, not a failure; after a continuation it\n\t\t\t\t// means the server dropped the drain mid-way.\n\t\t\t\tif (cursor != null) {\n\t\t\t\t\tstopIncomplete('Jira Server returned no page after advertising a continuation');\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tresults.push(\n\t\t\t\t...page.data.map(issue => toIssueShape(issue, this)).filter((r): r is IssueShape => r != null),\n\t\t\t);\n\n\t\t\tif (!page.hasMore) break;\n\n\t\t\t// More pages are claimed but no advancing cursor came back: the drain can't continue, so flag it\n\t\t\t// rather than stopping silently.\n\t\t\tif (page.nextCursor == null || page.nextCursor === cursor) {\n\t\t\t\tstopIncomplete('Jira Server returned no advancing issue continuation');\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcursor = page.nextCursor;\n\t\t\t// Pages remain but this was the last allowed iteration: the backstop stopped the drain.\n\t\t\tif (i === maxPagesPerRequest - 1) {\n\t\t\t\tstopIncomplete(\n\t\t\t\t\t`Jira Server account-wide issue drain stopped at the ${maxPagesPerRequest}-page backstop`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn { values: results, truncated: truncated, metadata: metadata };\n\t}\n\n\tprotected override async getProviderLinkedIssueOrPullRequest(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resource: JiraServerResourceDescriptor,\n\t\t{ key }: { id: string; key: string },\n\t): Promise<IssueOrPullRequest | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst issue = await api.getJiraServerIssue(toTokenWithInfo(this.id, session), this.apiBaseUrlFor(session), key);\n\t\treturn issue != null ? toIssueShape(issue, this) : undefined;\n\t}\n\n\tprotected override async getProviderIssue(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resource: JiraServerResourceDescriptor,\n\t\tid: string,\n\t): Promise<Issue | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst apiResult = await api.getJiraServerIssue(\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\tthis.apiBaseUrlFor(session),\n\t\t\tid,\n\t\t);\n\t\tconst issue = apiResult != null ? toIssueShape(apiResult, this) : undefined;\n\t\treturn issue != null ? { ...issue, type: 'issue' } : undefined;\n\t}\n\n\t/**\n\t * The point read behind `getIssuesBatch`'s tracker targets (#5872). `resourceId` is this connection's single\n\t * synthetic resource — the host itself, see {@link JiraServerResourceDescriptor} — so it selects nothing further\n\t * here: the host was chosen upstream by `domain` (or the connection's configured domain) and the request is\n\t * addressed to the SESSION's base URL like every other read. No `resourceUrl` is needed either, unlike Jira\n\t * Cloud: its REST `self` link is an API endpoint, whereas `getJiraServerIssue` builds the browser link from\n\t * the base URL it was addressed to. A key that names no issue resolves to `undefined`, a proven absence.\n\t */\n\tprotected override getProviderIssueByResourceId(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resourceId: string,\n\t\tid: string,\n\t\t_resourceUrl: string | undefined,\n\t): Promise<Issue | undefined> {\n\t\treturn this.getProviderIssue(session, this.resourceFor(session), id);\n\t}\n\n\t/**\n\t * Caches are keyed by the ADDRESS the read was made against as well as the token: one integration instance\n\t * exists per configured host and two hosts can hold the same token, so a token-only key would serve one\n\t * host's projects for the other — and the host alone is not enough either, because a connection re-pointed\n\t * to another context path on the same host keeps its token and would otherwise be served the old path's\n\t * projects.\n\t */\n\tprivate projectsCacheKey(session: ProviderAuthenticationSession): string {\n\t\treturn `${this.apiBaseUrlFor(session)}:${session.accessToken}`;\n\t}\n\n\tprotected override providerOnDisconnect(): void {\n\t\tthis._projects = undefined;\n\t}\n}\n"],"mappings":"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"}
@@ -5,6 +5,7 @@ import { Issue, IssueShape } from "../../../git/models/issue.js";
5
5
  import { Account } from "../../../git/models/author.js";
6
6
  import { ProviderAuthenticationSession } from "../authentication/models.js";
7
7
  import { IssuesCloudHostIntegrationId } from "../constants.js";
8
+ import { ProviderRefusal } from "../collectionMetadata.js";
8
9
  import { IssuesForProjectOptions, ProjectIssuesDrain } from "../models/issueReads.js";
9
10
  import { IssuesIntegration } from "../models/issuesIntegration.js";
10
11
  import { ProviderApiCollectionResult } from "./models.js";
@@ -28,6 +29,14 @@ export declare class JiraIntegration extends IssuesIntegration<IssuesCloudHostIn
28
29
  private _autolinks;
29
30
  autolinks(): Promise<(AutolinkReference | DynamicAutolinkReference)[]>;
30
31
  protected getProviderAccountForResource(session: ProviderAuthenticationSession, resource: JiraOrganizationDescriptor): Promise<Account | undefined>;
32
+ /** The site-list request the resource cache would otherwise answer; see `IntegrationBase.validateCredential`. */
33
+ protected validateCredential(session: ProviderAuthenticationSession): Promise<void>;
34
+ /**
35
+ * Atlassian's `401` for a token missing a scope the request needs, which a reconnect fixes by consenting to it
36
+ * again; see `IntegrationBase.isCredentialRefusal`. Atlassian gives the same answer to a malformed request URL,
37
+ * so a request the SDK built wrong would read as the credential's too.
38
+ */
39
+ protected isCredentialRefusal(refusal: ProviderRefusal): boolean;
31
40
  private _organizations;
32
41
  protected getProviderResourcesForUser(session: ProviderAuthenticationSession, force?: boolean): Promise<JiraOrganizationDescriptor[] | undefined>;
33
42
  private _projects;
@@ -1,6 +1,6 @@
1
1
  import { Logger as _logger } from "../../../utils/logger.js";
2
- import { throwIfCallerContractError, toCollectionScopeFailure } from "../collectionMetadata.js";
3
2
  import "../constants.js";
3
+ import { throwIfCallerContractError, toCollectionScopeFailure } from "../collectionMetadata.js";
4
4
  import { toTokenWithInfo } from "../authentication/models.js";
5
5
  import "../providerFilters.js";
6
6
  import { collectProviderPagedResult, mergeCollectionMetadata } from "./utils/providerPaging.js";
@@ -69,6 +69,18 @@ var JiraIntegration = class extends IssuesIntegration {
69
69
  if (user == null) return void 0;
70
70
  return toAccount(user, this);
71
71
  }
72
+ /** The site-list request the resource cache would otherwise answer; see `IntegrationBase.validateCredential`. */
73
+ async validateCredential(session) {
74
+ if (await (await this.getProvidersApi()).getJiraResourcesForCurrentUser(toTokenWithInfo(this.id, session)) == null) throw new Error("Jira did not confirm the credential");
75
+ }
76
+ /**
77
+ * Atlassian's `401` for a token missing a scope the request needs, which a reconnect fixes by consenting to it
78
+ * again; see `IntegrationBase.isCredentialRefusal`. Atlassian gives the same answer to a malformed request URL,
79
+ * so a request the SDK built wrong would read as the credential's too.
80
+ */
81
+ isCredentialRefusal(refusal) {
82
+ return refusal.status === 401 && /scope does not match/i.test(refusal.detail ?? "");
83
+ }
72
84
  _organizations;
73
85
  async getProviderResourcesForUser(session, force = false) {
74
86
  const { accessToken } = session;