@gitkraken/core-gitlens 0.12.0 → 0.14.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 (198) hide show
  1. package/CHANGELOG.md +78 -1
  2. package/dist/git/cache.d.ts +84 -29
  3. package/dist/git/cache.js +188 -54
  4. package/dist/git/cache.js.map +1 -1
  5. package/dist/git/features.d.ts +1 -1
  6. package/dist/git/features.js +5 -0
  7. package/dist/git/features.js.map +1 -1
  8. package/dist/git/models/branch.d.ts +20 -2
  9. package/dist/git/models/branch.js +28 -6
  10. package/dist/git/models/branch.js.map +1 -1
  11. package/dist/git/models/reference.d.ts +3 -0
  12. package/dist/git/models/status.js +2 -2
  13. package/dist/git/models/worktree.d.ts +4 -1
  14. package/dist/git/models/worktree.js +4 -2
  15. package/dist/git/models/worktree.js.map +1 -1
  16. package/dist/git/parsers/diffParser.js +1 -1
  17. package/dist/git/providers/branches.d.ts +73 -8
  18. package/dist/git/providers/operations.d.ts +3 -2
  19. package/dist/git/providers/pausedOperations.d.ts +6 -0
  20. package/dist/git/providers/provider.d.ts +45 -2
  21. package/dist/git/providers/revision.d.ts +17 -3
  22. package/dist/git/providers/status.d.ts +8 -2
  23. package/dist/git/remotes/azure-devops.js +61 -53
  24. package/dist/git/remotes/azure-devops.js.map +1 -1
  25. package/dist/git/run.types.d.ts +12 -0
  26. package/dist/git/service.d.ts +8 -5
  27. package/dist/git/service.js +16 -8
  28. package/dist/git/service.js.map +1 -1
  29. package/dist/git/utils/branch.utils.d.ts +12 -2
  30. package/dist/git/utils/branch.utils.js +49 -5
  31. package/dist/git/utils/branch.utils.js.map +1 -1
  32. package/dist/git/utils/reference.utils.d.ts +1 -0
  33. package/dist/git/utils/reference.utils.js.map +1 -1
  34. package/dist/git/watching/changeEvent.d.ts +13 -2
  35. package/dist/git/watching/changeEvent.js +17 -3
  36. package/dist/git/watching/changeEvent.js.map +1 -1
  37. package/dist/git/watching/gitDirPoller.d.ts +111 -0
  38. package/dist/git/watching/gitDirPoller.js +266 -0
  39. package/dist/git/watching/gitDirPoller.js.map +1 -0
  40. package/dist/git/watching/provider.d.ts +16 -0
  41. package/dist/git/watching/watchGroup.d.ts +40 -4
  42. package/dist/git/watching/watchGroup.js +143 -17
  43. package/dist/git/watching/watchGroup.js.map +1 -1
  44. package/dist/git/watching/watchService.d.ts +70 -1
  45. package/dist/git/watching/watchService.js +158 -13
  46. package/dist/git/watching/watchService.js.map +1 -1
  47. package/dist/git/watching/watchSession.d.ts +44 -0
  48. package/dist/git/watching/watchSession.js +91 -6
  49. package/dist/git/watching/watchSession.js.map +1 -1
  50. package/dist/git/watching/watcherPatterns.d.ts +21 -0
  51. package/dist/git/watching/watcherPatterns.js +67 -1
  52. package/dist/git/watching/watcherPatterns.js.map +1 -1
  53. package/dist/git/watching/worktreeSummary.d.ts +34 -7
  54. package/dist/git/watching/worktreeSummary.js +39 -12
  55. package/dist/git/watching/worktreeSummary.js.map +1 -1
  56. package/dist/git-cli/cliGitProvider.d.ts +40 -2
  57. package/dist/git-cli/cliGitProvider.js +182 -18
  58. package/dist/git-cli/cliGitProvider.js.map +1 -1
  59. package/dist/git-cli/exec/commandClassifier.d.ts +15 -0
  60. package/dist/git-cli/exec/commandClassifier.js +48 -2
  61. package/dist/git-cli/exec/commandClassifier.js.map +1 -1
  62. package/dist/git-cli/exec/git.d.ts +20 -3
  63. package/dist/git-cli/exec/git.js +63 -19
  64. package/dist/git-cli/exec/git.js.map +1 -1
  65. package/dist/git-cli/exec/gitQueue.d.ts +2 -0
  66. package/dist/git-cli/exec/gitQueue.js +2 -1
  67. package/dist/git-cli/exec/gitQueue.js.map +1 -1
  68. package/dist/git-cli/exec/indexLock.d.ts +39 -0
  69. package/dist/git-cli/exec/indexLock.js +160 -0
  70. package/dist/git-cli/exec/indexLock.js.map +1 -0
  71. package/dist/git-cli/parsers/blameParser.js +2 -2
  72. package/dist/git-cli/parsers/refParser.js +4 -2
  73. package/dist/git-cli/parsers/refParser.js.map +1 -1
  74. package/dist/git-cli/parsers/statusParser.d.ts +6 -0
  75. package/dist/git-cli/parsers/statusParser.js +33 -3
  76. package/dist/git-cli/parsers/statusParser.js.map +1 -1
  77. package/dist/git-cli/parsers/worktreeParser.js +42 -2
  78. package/dist/git-cli/parsers/worktreeParser.js.map +1 -1
  79. package/dist/git-cli/providers/branchRemote.d.ts +14 -0
  80. package/dist/git-cli/providers/branchRemote.js +36 -0
  81. package/dist/git-cli/providers/branchRemote.js.map +1 -0
  82. package/dist/git-cli/providers/branches.d.ts +109 -10
  83. package/dist/git-cli/providers/branches.js +630 -134
  84. package/dist/git-cli/providers/branches.js.map +1 -1
  85. package/dist/git-cli/providers/commits.js +2 -2
  86. package/dist/git-cli/providers/config.d.ts +9 -5
  87. package/dist/git-cli/providers/config.js +58 -25
  88. package/dist/git-cli/providers/config.js.map +1 -1
  89. package/dist/git-cli/providers/contributors.js +1 -1
  90. package/dist/git-cli/providers/graph.js +9 -6
  91. package/dist/git-cli/providers/graph.js.map +1 -1
  92. package/dist/git-cli/providers/operations.d.ts +17 -0
  93. package/dist/git-cli/providers/operations.js +172 -111
  94. package/dist/git-cli/providers/operations.js.map +1 -1
  95. package/dist/git-cli/providers/pausedOperations.d.ts +8 -0
  96. package/dist/git-cli/providers/pausedOperations.js +59 -39
  97. package/dist/git-cli/providers/pausedOperations.js.map +1 -1
  98. package/dist/git-cli/providers/refs.js +11 -8
  99. package/dist/git-cli/providers/refs.js.map +1 -1
  100. package/dist/git-cli/providers/remotes.js +19 -10
  101. package/dist/git-cli/providers/remotes.js.map +1 -1
  102. package/dist/git-cli/providers/revision.d.ts +6 -2
  103. package/dist/git-cli/providers/revision.js +38 -12
  104. package/dist/git-cli/providers/revision.js.map +1 -1
  105. package/dist/git-cli/providers/staging.js +39 -14
  106. package/dist/git-cli/providers/staging.js.map +1 -1
  107. package/dist/git-cli/providers/stash.d.ts +2 -0
  108. package/dist/git-cli/providers/stash.js +33 -18
  109. package/dist/git-cli/providers/stash.js.map +1 -1
  110. package/dist/git-cli/providers/status.d.ts +3 -1
  111. package/dist/git-cli/providers/status.js +40 -22
  112. package/dist/git-cli/providers/status.js.map +1 -1
  113. package/dist/git-cli/providers/tags.js +6 -4
  114. package/dist/git-cli/providers/tags.js.map +1 -1
  115. package/dist/git-cli/providers/worktrees.d.ts +1 -0
  116. package/dist/git-cli/providers/worktrees.js +60 -31
  117. package/dist/git-cli/providers/worktrees.js.map +1 -1
  118. package/dist/git-cli/service.d.ts +6 -0
  119. package/dist/git-cli/service.js +5 -2
  120. package/dist/git-cli/service.js.map +1 -1
  121. package/dist/plus/git-github/providers/github/revision.d.ts +6 -2
  122. package/dist/plus/git-github/providers/github/revision.js +15 -10
  123. package/dist/plus/git-github/providers/github/revision.js.map +1 -1
  124. package/dist/plus/git-github/providers/github/status.d.ts +3 -1
  125. package/dist/plus/git-github/providers/github/status.js +2 -2
  126. package/dist/plus/git-github/providers/github/status.js.map +1 -1
  127. package/dist/plus/integrations/collectionMetadata.d.ts +13 -2
  128. package/dist/plus/integrations/collectionMetadata.js +14 -2
  129. package/dist/plus/integrations/collectionMetadata.js.map +1 -1
  130. package/dist/plus/integrations/index.d.ts +2 -2
  131. package/dist/plus/integrations/index.js.map +1 -1
  132. package/dist/plus/integrations/integrationService.d.ts +1 -0
  133. package/dist/plus/integrations/integrationService.js +1 -1
  134. package/dist/plus/integrations/integrationService.js.map +1 -1
  135. package/dist/plus/integrations/manager.d.ts +1 -0
  136. package/dist/plus/integrations/models/gitHostIntegration.d.ts +41 -5
  137. package/dist/plus/integrations/models/gitHostIntegration.js +106 -29
  138. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
  139. package/dist/plus/integrations/models/issueReads.d.ts +17 -4
  140. package/dist/plus/integrations/providers/apiClients.d.ts +2 -0
  141. package/dist/plus/integrations/providers/apiClients.js +7 -1
  142. package/dist/plus/integrations/providers/apiClients.js.map +1 -1
  143. package/dist/plus/integrations/providers/azure/azure.d.ts +5 -4
  144. package/dist/plus/integrations/providers/azure/azure.js +36 -76
  145. package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
  146. package/dist/plus/integrations/providers/azure/azure.utils.js +20 -17
  147. package/dist/plus/integrations/providers/azure/azure.utils.js.map +1 -1
  148. package/dist/plus/integrations/providers/azure/models.d.ts +2 -2
  149. package/dist/plus/integrations/providers/azure/models.js +4 -4
  150. package/dist/plus/integrations/providers/azure/models.js.map +1 -1
  151. package/dist/plus/integrations/providers/azure/workItemStateCategories.d.ts +21 -12
  152. package/dist/plus/integrations/providers/azure/workItemStateCategories.js +74 -17
  153. package/dist/plus/integrations/providers/azure/workItemStateCategories.js.map +1 -1
  154. package/dist/plus/integrations/providers/azureDevOps.d.ts +44 -0
  155. package/dist/plus/integrations/providers/azureDevOps.js +108 -17
  156. package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
  157. package/dist/plus/integrations/providers/azureDevOpsServer.d.ts +45 -1
  158. package/dist/plus/integrations/providers/azureDevOpsServer.js +121 -6
  159. package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
  160. package/dist/plus/integrations/providers/jira-server.js +18 -14
  161. package/dist/plus/integrations/providers/jira-server.js.map +1 -1
  162. package/dist/plus/integrations/providers/jira.js +18 -14
  163. package/dist/plus/integrations/providers/jira.js.map +1 -1
  164. package/dist/plus/integrations/providers/models.d.ts +1 -2
  165. package/dist/plus/integrations/providers/models.js +9 -2
  166. package/dist/plus/integrations/providers/models.js.map +1 -1
  167. package/dist/plus/integrations/providers/providersApi.d.ts +7 -17
  168. package/dist/plus/integrations/providers/providersApi.js +85 -78
  169. package/dist/plus/integrations/providers/providersApi.js.map +1 -1
  170. package/dist/plus/integrations/providers/utils/jiraIssueDrains.d.ts +22 -0
  171. package/dist/plus/integrations/providers/utils/jiraIssueDrains.js +69 -9
  172. package/dist/plus/integrations/providers/utils/jiraIssueDrains.js.map +1 -1
  173. package/dist/plus/integrations/providers/utils/providerPaging.js +8 -2
  174. package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
  175. package/dist/plus/integrations/reads/filters.d.ts +3 -3
  176. package/dist/plus/integrations/reads/filters.js.map +1 -1
  177. package/dist/plus/integrations/reads/issueBatch.d.ts +2 -1
  178. package/dist/plus/integrations/reads/issueBatch.js +1 -1
  179. package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
  180. package/dist/plus/integrations/reads/issueTracker.js +23 -14
  181. package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
  182. package/dist/plus/integrations/reads/issues.d.ts +2 -1
  183. package/dist/plus/integrations/reads/issues.js +18 -3
  184. package/dist/plus/integrations/reads/issues.js.map +1 -1
  185. package/dist/plus/integrations/reads/pullRequestBatch.js +1 -1
  186. package/dist/plus/integrations/reads/resolveRepository.js +12 -3
  187. package/dist/plus/integrations/reads/resolveRepository.js.map +1 -1
  188. package/dist/plus/integrations/reads/warnings.d.ts +11 -4
  189. package/dist/plus/integrations/reads/warnings.js +15 -5
  190. package/dist/plus/integrations/reads/warnings.js.map +1 -1
  191. package/dist/plus/integrations/results.d.ts +36 -2
  192. package/dist/plus/integrations/results.js +33 -13
  193. package/dist/plus/integrations/results.js.map +1 -1
  194. package/dist/utils/promiseCache.d.ts +10 -0
  195. package/dist/utils/promiseCache.js +17 -2
  196. package/dist/utils/promiseCache.js.map +1 -1
  197. package/docs/integrations.md +52 -20
  198. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"resolveRepository.js","names":[],"sources":["../../../../../plus/integrations/src/reads/resolveRepository.ts"],"sourcesContent":["import type { RemoteProvider } from '@gitlens/git/models/remoteProvider.js';\nimport type { ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport type { RemoteProviderConfig } from '@gitlens/git/remotes/matcher.js';\nimport { createRemoteProviderMatcher } from '@gitlens/git/remotes/matcher.js';\nimport { parseGitRemoteUrl } from '@gitlens/git/utils/remote.utils.js';\nimport type { ConfiguredIntegrationDescriptor } from '../authentication/models.js';\nimport { mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { GitCloudHostIntegrationId, GitSelfManagedHostIntegrationId } from '../constants.js';\nimport { AuthenticationError, RequestNotFoundError } from '../errors.js';\nimport { isIssuesIntegration } from '../models/issuesIntegration.js';\nimport { isAzureCloudDomain, isBitbucketCloudDomain, isGitHubDotCom, isGitLabDotCom } from '../providers/models.js';\nimport type { ProviderWarning, RepositoryIdentity, RepositoryResolution, ResolveRepositoryResult } from '../results.js';\nimport { toProviderWarning } from '../results.js';\nimport { decodePathSegment, getRemoteHostMatcher, hostFromDomain, isWebRemoteScheme } from '../utils/domain.utils.js';\nimport {\n\tgetIntegrationIdForRemote,\n\tgetSelfManagedBaseUrl,\n\tisGitCloudHostIntegrationId,\n\tisGitSelfManagedHostIntegrationId,\n\tremoteProviderTypeForConfig,\n\tremoteProviderTypeForIntegration,\n} from '../utils/integration.utils.js';\nimport type { RepositoryResolutionContext } from './context.js';\nimport { noConnectionWarning } from './warnings.js';\n\ntype ResolveRepositoryOptions = {\n\tproviderId?: IntegrationIds;\n\tremoteUrl: string;\n\thost?: string;\n\tconnectionId?: string;\n\t/**\n\t * Explicit self-managed host domain used to select the integration instance. Used only when the requested\n\t * connection has no configured domain; it must come from the trusted authentication configuration, not\n\t * repository or remote data.\n\t */\n\tdomain?: string;\n};\n\n/**\n * Resolution of a remote URL to a provider repository identity — the facade's equivalent of `gk repo resolve`.\n *\n * Unlike every other read here, this one's input is REPOSITORY data, so most of the code below is about not\n * letting that data choose the credentials: an explicit cloud `providerId` must agree with the remote's canonical\n * host, and a self-managed host resolves only against a domain the user already authenticated. The\n * `host-mismatch` returns are those guards, not edge cases — resolving `owner/repo` against the wrong account\n * would hand back a confidently wrong identity and seed the domain-keyed instance cache from remote data.\n */\nexport async function resolveRepository(\n\tctx: RepositoryResolutionContext,\n\toptions: ResolveRepositoryOptions,\n): Promise<ResolveRepositoryResult> {\n\tconst result = (status: RepositoryResolution['status']): ResolveRepositoryResult => ({\n\t\tresolution: { status: status },\n\t});\n\n\tconst [scheme, parsedDomain, path] = parseGitRemoteUrl(options.remoteUrl);\n\t// An SSH remote's port belongs to its SSH daemon, not to the web authority a connection is keyed by.\n\tconst hostsMatch = getRemoteHostMatcher(scheme);\n\tconst parsedHost = hostFromDomain(parsedDomain);\n\tconst explicitHost = hostFromDomain(options.host);\n\tif (parsedHost != null && explicitHost != null && !hostsMatch(parsedHost, explicitHost)) {\n\t\treturn result('host-mismatch');\n\t}\n\n\tconst matcherDomain = parsedDomain || options.host || '';\n\n\t// An explicit cloud provider must agree with the remote's canonical cloud host. Without this guard the\n\t// synthetic matcher below can reinterpret, for example, a gitlab.com URL as GitHub and then resolve a\n\t// homonymous owner/repo through the wrong account. Match once without caller configs/synthetic entries;\n\t// Azure's matcher also normalizes ssh.dev.azure.com and vs-ssh.visualstudio.com to their web host.\n\tif (options.providerId != null && isGitCloudHostIntegrationId(options.providerId)) {\n\t\tconst nativeProvider = createRemoteProviderMatcher([])(options.remoteUrl, matcherDomain, path, scheme);\n\t\tconst nativeId = nativeProvider != null ? getIntegrationIdForRemote(nativeProvider) : undefined;\n\t\tconst nativeDomain = nativeProvider?.domain ?? matcherDomain;\n\t\tconst canonicalHost =\n\t\t\toptions.providerId === GitCloudHostIntegrationId.GitHub\n\t\t\t\t? isGitHubDotCom(nativeDomain)\n\t\t\t\t: options.providerId === GitCloudHostIntegrationId.GitLab\n\t\t\t\t\t? isGitLabDotCom(nativeDomain)\n\t\t\t\t\t: options.providerId === GitCloudHostIntegrationId.Bitbucket\n\t\t\t\t\t\t? isBitbucketCloudDomain(nativeDomain)\n\t\t\t\t\t\t: isAzureCloudDomain(nativeDomain);\n\t\tif (nativeId !== options.providerId || !canonicalHost) return result('host-mismatch');\n\t}\n\n\tlet provider = createRemoteProviderMatcher(buildMatcherConfigs(ctx, options, parsedDomain))(\n\t\toptions.remoteUrl,\n\t\tmatcherDomain,\n\t\tpath,\n\t\tscheme,\n\t);\n\tif (provider == null) return result('invalid-remote-url');\n\n\tlet id = options.providerId ?? getIntegrationIdForRemote(provider);\n\t// Custom Azure DevOps Server domains matched via getRemoteConfigs return undefined from\n\t// getIntegrationIdForRemote because the provider is marked custom; map them to the server id so the\n\t// project-scoped lookup uses that host's configured connection and API base URL.\n\tif (id == null && provider.id === 'azure-devops' && provider.custom) {\n\t\tid = GitSelfManagedHostIntegrationId.AzureDevOpsServer;\n\t}\n\tif (id == null) return result('unsupported-provider');\n\n\t// On a self-managed host, resolve only against a TRUSTED host: the pinned connection's configured\n\t// domain, the explicit `domain`, or — when neither was supplied — a configured host matching the\n\t// remote's. That last case keeps `remoteUrl` (repository-supplied) out of the trusted path: it selects\n\t// among hosts the user already authenticated and can never introduce a new one. Resolving `owner/repo`\n\t// against some other host's account would, if that host has the same owner/repo, return a confidently\n\t// wrong identity — and would seed the domain-keyed integration cache (never evicted before dispose) with\n\t// an entry derived from repository data.\n\tconst urlHost = hostFromDomain(provider.domain);\n\t// Normalize BOTH sides before comparing: a stored connection domain is usually a full URL\n\t// (`https://git.example.com`) while `urlHost` is already a bare host, so a raw compare would fail on\n\t// scheme/trailing-slash alone and wrongly reject a correctly-configured connection.\n\tlet trustedHost =\n\t\toptions.connectionId != null || options.domain != null\n\t\t\t? hostFromDomain(ctx.resolveDomainForRead(id, options.connectionId, options.domain))\n\t\t\t: undefined;\n\tlet connectionId = options.connectionId;\n\tlet providerRepo: ResourceDescriptor | undefined;\n\tif (isGitSelfManagedHostIntegrationId(id)) {\n\t\tif (path.split('/').some(segment => /^(?:\\.|%2e){1,2}$/i.test(segment) || /%2f|%5c|\\\\/i.test(segment))) {\n\t\t\treturn result('invalid-remote-url');\n\t\t}\n\n\t\tconst configured = ctx.getConfigured(id);\n\t\tconst configuredHosts = configured.map(c => hostFromDomain(c.domain));\n\t\tif (trustedHost == null && urlHost != null) {\n\t\t\tconst matchingHosts = new Set(configuredHosts.filter(host => hostsMatch(host, urlHost)));\n\t\t\tif (matchingHosts.size > 1) return result('host-mismatch');\n\n\t\t\ttrustedHost = matchingHosts.values().next().value;\n\t\t}\n\t\t// A configured host that doesn't match the remote's is a genuine mismatch. With nothing configured\n\t\t// there is no host to mismatch against — it's simply not connected, so fall through to the\n\t\t// `unauthorized` path below (still WITHOUT resolving by the URL domain, so repo data can't seed the\n\t\t// cache).\n\t\tif (trustedHost == null && configuredHosts.length !== 0) return result('host-mismatch');\n\t\tif (trustedHost != null && urlHost != null && !hostsMatch(trustedHost, urlHost)) {\n\t\t\treturn result('host-mismatch');\n\t\t}\n\n\t\tconst installation = matchInstallation(\n\t\t\tctx,\n\t\t\toptions,\n\t\t\tid,\n\t\t\tconfigured.filter(c => hostFromDomain(c.domain) === trustedHost),\n\t\t\t{ scheme: scheme, domain: parsedDomain, path: path },\n\t\t);\n\t\tif (typeof installation === 'string') return result(installation);\n\n\t\tif (installation != null) {\n\t\t\tprovider = installation.provider;\n\t\t\tconnectionId = installation.connectionId;\n\t\t\tproviderRepo = installation.repoDesc;\n\t\t}\n\t}\n\n\tconst owner = provider.owner;\n\tconst name = provider.repoName;\n\tif (owner == null || name == null) return result('invalid-remote-url');\n\n\tproviderRepo ??= provider.repoDesc;\n\n\tlet integration;\n\ttry {\n\t\t// Resolve the instance through the trusted target (as `getIntegrationForRead` does):\n\t\t// `resolveReadSession` looks the session up against the instance's domain-scoped descriptor, so\n\t\t// selecting the instance by the URL domain could miss the session and degrade to `no-connection`.\n\t\tintegration = isGitSelfManagedHostIntegrationId(id)\n\t\t\t? trustedHost != null\n\t\t\t\t? await ctx.getIntegrationForRead(id, connectionId, trustedHost)\n\t\t\t\t: undefined\n\t\t\t: await ctx.getIntegrationForDomain(id, provider.domain);\n\t} catch {\n\t\tintegration = undefined;\n\t}\n\tif (integration == null) {\n\t\t// Attach a warning, as the thrown-AuthenticationError branch below does: a consumer driving auth\n\t\t// recovery off `warning.kind` would otherwise get nothing for an unresolvable target.\n\t\treturn {\n\t\t\tresolution: {\n\t\t\t\tstatus: 'unauthorized',\n\t\t\t\twarning: noConnectionWarning(id, provider.domain, connectionId),\n\t\t\t},\n\t\t};\n\t}\n\t// Issue trackers have no `getRepo` client; a git host without `getRepoFn` leaves `getRepoInfo`\n\t// undefined. Either way this provider can't resolve repositories.\n\tif (isIssuesIntegration(integration) || integration.getRepoInfo == null) {\n\t\treturn result('unsupported-provider');\n\t}\n\n\t// Azure repos are org + project scoped; the remote provider exposes project as `providerDesc.repoDomain`.\n\tconst project = provider.id === 'azure-devops' ? provider.providerDesc?.repoDomain : undefined;\n\tconst domain = trustedHost ?? provider.domain;\n\n\t// Azure's lookup is project-scoped and returns `undefined` without one, which the connection-gap branch\n\t// below would misreport as `unauthorized` (driving a pointless reconnect). A remote URL that carries no\n\t// project simply can't address an Azure repo.\n\tif (provider.id === 'azure-devops' && project == null) return result('invalid-remote-url');\n\n\ttry {\n\t\tconst repo = await integration.getRepoInfo({\n\t\t\t...providerRepo,\n\t\t\towner: owner,\n\t\t\tname: name,\n\t\t\tproject: project,\n\t\t\tconnectionId: connectionId,\n\t\t});\n\t\tif (repo == null) {\n\t\t\t// `getRepoInfo` returns undefined only when no session could be resolved (not connected, or the\n\t\t\t// requested connection is gone) — a real 404 throws below. So this is a connection gap. Attach a\n\t\t\t// warning so a consumer driving auth recovery off `warning.kind` sees the same signal it gets from\n\t\t\t// the thrown-AuthenticationError branch below; without it, the most common unauthorized case is\n\t\t\t// silent.\n\t\t\treturn {\n\t\t\t\tresolution: {\n\t\t\t\t\tstatus: 'unauthorized',\n\t\t\t\t\twarning: noConnectionWarning(id, domain, connectionId),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Prefer the provider's canonical namespace/name (GitHub's REST/GraphQL lookup follows the 301\n\t\t// rename redirect, so a stale old name resolves to the new canonical identity), falling back to the\n\t\t// parsed remote when the response omits them. `renamed` is a case-insensitive compare of input vs\n\t\t// canonical, mirroring gkcli's `EqualFold`, so hosts that merely echo the input casing (e.g.\n\t\t// Bitbucket Server/Azure) don't get spuriously flagged.\n\t\tconst canonicalOwner = repo.namespace || owner;\n\t\tconst canonicalName = repo.name || name;\n\t\tconst renamed =\n\t\t\tcanonicalOwner.toLowerCase() !== owner.toLowerCase() || canonicalName.toLowerCase() !== name.toLowerCase();\n\n\t\tconst identity: RepositoryIdentity = {\n\t\t\tproviderId: id,\n\t\t\tdomain: domain,\n\t\t\towner: canonicalOwner,\n\t\t\tname: canonicalName,\n\t\t\tproject: project,\n\t\t\tremoteUrl: options.remoteUrl,\n\t\t\trenamed: renamed,\n\t\t};\n\t\treturn { resolution: { status: 'resolved', identity: identity } };\n\t} catch (ex) {\n\t\t// Order matters: 404 throws RequestNotFoundError (not `undefined`), so check not-found before auth\n\t\t// and before the generic 5xx/unknown bucket — never classify a 401/403 as not-found.\n\t\tlet resolution: RepositoryResolution;\n\t\tif (ex instanceof RequestNotFoundError) {\n\t\t\tresolution = { status: 'not-found' };\n\t\t} else if (ex instanceof AuthenticationError) {\n\t\t\t// One repository refusing a credential that works (e.g. GitHub's OAuth App access restrictions) is that\n\t\t\t// repository's warning, scoped and with its cause, not a dead connection's.\n\t\t\tlet warning = toProviderWarning(id, domain, connectionId, ex);\n\t\t\ttry {\n\t\t\t\tconst failure = await integration.settleRepositoryRefusal(\n\t\t\t\t\t{ owner: owner, name: name, project: project, connectionId: connectionId },\n\t\t\t\t\tex,\n\t\t\t\t);\n\t\t\t\tif (failure != null) {\n\t\t\t\t\tconst warnings: ProviderWarning[] = [];\n\t\t\t\t\tmergeAssessmentInto(warnings, id, domain, connectionId, {\n\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\tfailures: [failure],\n\t\t\t\t\t});\n\t\t\t\t\twarning = warnings[0] ?? warning;\n\t\t\t\t}\n\t\t\t} catch (credentialError) {\n\t\t\t\tif (credentialError instanceof AuthenticationError) {\n\t\t\t\t\twarning = toProviderWarning(id, domain, connectionId, credentialError);\n\t\t\t\t}\n\t\t\t}\n\t\t\tresolution = {\n\t\t\t\tstatus: 'unauthorized',\n\t\t\t\twarning: warning,\n\t\t\t};\n\t\t} else {\n\t\t\tresolution = { status: 'undetermined', warning: toProviderWarning(id, domain, connectionId, ex) };\n\t\t}\n\t\treturn { resolution: resolution };\n\t}\n}\n\n/**\n * Narrows a web remote on a self-managed host to the configured installation it lives under: the pinned\n * connection's or, unpinned, the one whose installation path is the longest prefix of the remote's, so two\n * installations behind one host each resolve with their own credentials. The provider is re-matched against the\n * path relative to that installation, because the remote parsers expect the repository path to start at the\n * host root. An SSH remote carries no installation path, so it is left to the host-level checks.\n */\nfunction matchInstallation(\n\tctx: RepositoryResolutionContext,\n\toptions: ResolveRepositoryOptions,\n\tid: GitSelfManagedHostIntegrationId,\n\tconnections: ConfiguredIntegrationDescriptor[],\n\tremote: { scheme: string; domain: string; path: string },\n):\n\t| { provider: RemoteProvider; connectionId: string | undefined; repoDesc: ResourceDescriptor }\n\t| 'host-mismatch'\n\t| 'invalid-remote-url'\n\t| undefined {\n\tif (!isWebRemoteScheme(remote.scheme)) return undefined;\n\n\tconst installationPath = (connection: ConfiguredIntegrationDescriptor | undefined): string | undefined => {\n\t\tconst baseUrl = getSelfManagedBaseUrl(\n\t\t\tid,\n\t\t\tconnection != null ? (connection.baseUrl ?? connection.domain) : options.domain,\n\t\t);\n\t\treturn baseUrl != null ? new URL(baseUrl).pathname.replace(/\\/+$/, '') : undefined;\n\t};\n\n\t// The connection an unpinned read already targets comes first, so it wins a tie between installations.\n\tconst defaultConnection = connections.find(c => c.primary) ?? connections[0];\n\t// A cloud sync serves the host's cloud-scoped default instead, even over a local primary (see\n\t// `getStoredSession`), so an unpinned read reaches the default's installation only when both agree.\n\tconst cloudConnections = connections.filter(c => c.cloud);\n\tconst cloudDefault = cloudConnections.find(c => c.primary) ?? cloudConnections[0];\n\tconst isDefaultReadable =\n\t\tdefaultConnection?.cloud !== false ||\n\t\tcloudDefault == null ||\n\t\tinstallationPath(cloudDefault) === installationPath(defaultConnection);\n\t// Whether an unpinned read can reach this connection's session: the default one is read without an id, any\n\t// other only by a real cloud token id. Session lookup treats every connection id as one, and a local (PAT)\n\t// descriptor's or domain-derived legacy id is not (see `getConfiguredConnectionId`).\n\tconst isReadable = (connection: ConfiguredIntegrationDescriptor | undefined): boolean =>\n\t\toptions.connectionId != null ||\n\t\t(connection === defaultConnection\n\t\t\t? isDefaultReadable\n\t\t\t: connection?.cloud === true && hostFromDomain(connection.id) !== connection.domain);\n\n\tconst candidates: (ConfiguredIntegrationDescriptor | undefined)[] =\n\t\toptions.connectionId != null\n\t\t\t? connections.filter(c => c.id === options.connectionId)\n\t\t\t: [defaultConnection, ...connections.filter(c => c !== defaultConnection)];\n\t// With no configured candidate, the explicit (trusted) `domain` is the only installation address there is.\n\tif (!candidates.length) {\n\t\tcandidates.push(undefined);\n\t}\n\n\tconst remotePath = `/${remote.path}`;\n\tconst remoteSegments = remote.path.split('/');\n\t// The remote's path below an installation, or `undefined` when it isn't under it. Azure DevOps Server runs on\n\t// IIS, which serves its paths decoded and case-insensitively, so it compares segment by segment.\n\tconst relativeTo = (basePath: string): string | undefined => {\n\t\tif (id !== GitSelfManagedHostIntegrationId.AzureDevOpsServer) {\n\t\t\treturn remotePath.startsWith(`${basePath}/`) ? remotePath.slice(basePath.length + 1) : undefined;\n\t\t}\n\n\t\tconst baseSegments = basePath.split('/').filter(Boolean);\n\t\tconst isUnder =\n\t\t\tremoteSegments.length > baseSegments.length &&\n\t\t\tbaseSegments.every(\n\t\t\t\t(segment, i) =>\n\t\t\t\t\tdecodePathSegment(segment).toLowerCase() === decodePathSegment(remoteSegments[i]).toLowerCase(),\n\t\t\t);\n\t\treturn isUnder ? remoteSegments.slice(baseSegments.length).join('/') : undefined;\n\t};\n\tlet match: { connection: ConfiguredIntegrationDescriptor | undefined; basePath: string } | undefined;\n\tlet checked = false;\n\tfor (const connection of candidates) {\n\t\tconst basePath = installationPath(connection);\n\t\tif (basePath == null) continue;\n\n\t\tchecked = true;\n\t\tif (relativeTo(basePath) == null) continue;\n\n\t\t// The longest installation wins; on a tie, a readable connection replaces an unreadable one.\n\t\tconst isBetter =\n\t\t\tmatch == null ||\n\t\t\tbasePath.length > match.basePath.length ||\n\t\t\t(basePath.length === match.basePath.length && !isReadable(match.connection) && isReadable(connection));\n\t\tif (isBetter) {\n\t\t\tmatch = { connection: connection, basePath: basePath };\n\t\t}\n\t}\n\tif (match == null) return checked ? 'host-mismatch' : undefined;\n\t// Reading through the default connection instead would address its installation with a path relative to\n\t// another one, so an unreachable installation fails closed.\n\tif (!isReadable(match.connection)) return 'host-mismatch';\n\n\tconst connectionId =\n\t\toptions.connectionId ?? (match.connection !== defaultConnection ? match.connection?.id : undefined);\n\n\tconst matcher = createRemoteProviderMatcher(\n\t\tbuildMatcherConfigs(ctx, { ...options, providerId: id }, remote.domain),\n\t);\n\tconst rematch = (path: string) => matcher(options.remoteUrl, remote.domain, relativeTo(path) ?? '', remote.scheme);\n\tlet basePath = match.basePath;\n\tlet provider = rematch(basePath);\n\tif (id !== GitSelfManagedHostIntegrationId.AzureDevOpsServer) {\n\t\treturn provider != null\n\t\t\t? { provider: provider, connectionId: connectionId, repoDesc: provider.repoDesc }\n\t\t\t: 'invalid-remote-url';\n\t}\n\n\t// An Azure DevOps Server address may name the collection as well as the virtual directory; the parser expects\n\t// the collection in the path, so re-base on the address's parent when no project was found.\n\tif (basePath && provider?.providerDesc?.repoDomain == null) {\n\t\tbasePath = basePath.slice(0, basePath.lastIndexOf('/'));\n\t\tprovider = rematch(basePath);\n\t}\n\tif (provider == null) return 'invalid-remote-url';\n\n\t// Requests address the repository's virtual directory from the host root, not from the installation the\n\t// re-match made it relative to; the absolute form also tells a directory apart from a collection of that name.\n\tconst repoDesc = provider.repoDesc;\n\tconst relative = typeof repoDesc.virtualDirectory === 'string' ? repoDesc.virtualDirectory : undefined;\n\tconst virtualDirectory =\n\t\t[...basePath.split('/').filter(Boolean).map(decodePathSegment), ...(relative?.split('/') ?? [])].join('/') ||\n\t\tundefined;\n\treturn {\n\t\tprovider: provider,\n\t\tconnectionId: connectionId,\n\t\trepoDesc: { ...repoDesc, virtualDirectory: virtualDirectory },\n\t};\n}\n\n/**\n * The remote-matcher configs for this resolution: the user's own host remote configs (self-managed/custom\n * domains), plus a synthetic exact-domain entry for an explicit `providerId` + host so a custom domain still maps\n * to the right provider for path parsing.\n */\nfunction buildMatcherConfigs(\n\tctx: RepositoryResolutionContext,\n\toptions: { providerId?: IntegrationIds; host?: string },\n\tparsedDomain: string,\n): RemoteProviderConfig[] {\n\tconst configs: RemoteProviderConfig[] = [];\n\tfor (const cfg of ctx.getRemoteConfigs()) {\n\t\tconst type = remoteProviderTypeForConfig(cfg.type);\n\t\tif (type == null) continue;\n\n\t\t// Forward both domain- and regex-based custom remotes (carrying any protocol override), so a\n\t\t// regex-configured host resolves instead of falling through to `unsupported`.\n\t\tif (cfg.domain) {\n\t\t\tconfigs.push({ type: type, domain: cfg.domain, protocol: cfg.protocol });\n\t\t} else if (cfg.regex) {\n\t\t\tconfigs.push({ type: type, regex: cfg.regex, protocol: cfg.protocol });\n\t\t}\n\t}\n\tif (options.providerId == null) return configs;\n\n\tconst type = remoteProviderTypeForIntegration(options.providerId);\n\tconst domain = parsedDomain || options.host;\n\tif (type == null || !domain) return configs;\n\n\t// The synthetic exact-domain entry is unshifted to the front, so it wins the match over the user's own\n\t// config for the same host. Carry that config's protocol override across (matched by domain or regex,\n\t// mirroring `ignoreSSLErrors`) so a self-managed host configured for a custom protocol — e.g. plain\n\t// `http` — isn't silently downgraded to the provider default here.\n\tconst lowerDomain = domain.toLowerCase();\n\tconst protocol = configs.find(c => {\n\t\tif (c.type !== type) return false;\n\t\tif (c.domain != null) return c.domain.toLowerCase() === lowerDomain;\n\n\t\t// Truthy (not just non-null): an empty regex would compile to a match-everything pattern.\n\t\tif (c.regex) {\n\t\t\ttry {\n\t\t\t\treturn new RegExp(c.regex, 'i').test(lowerDomain);\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t})?.protocol;\n\tconfigs.unshift({ type: type, domain: domain, protocol: protocol });\n\treturn configs;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAgDA,eAAsB,kBACrB,KACA,SACmC;CACnC,MAAM,UAAU,YAAqE,EACpF,YAAY,EAAU,OAAO,EAC9B;CAEA,MAAM,CAAC,QAAQ,cAAc,QAAQ,kBAAkB,QAAQ,SAAS;CAExE,MAAM,aAAa,qBAAqB,MAAM;CAC9C,MAAM,aAAa,eAAe,YAAY;CAC9C,MAAM,eAAe,eAAe,QAAQ,IAAI;CAChD,IAAI,cAAc,QAAQ,gBAAgB,QAAQ,CAAC,WAAW,YAAY,YAAY,GACrF,OAAO,OAAO,eAAe;CAG9B,MAAM,gBAAgB,gBAAgB,QAAQ,QAAQ;CAMtD,IAAI,QAAQ,cAAc,QAAQ,4BAA4B,QAAQ,UAAU,GAAG;EAClF,MAAM,iBAAiB,4BAA4B,CAAC,CAAC,CAAC,CAAC,QAAQ,WAAW,eAAe,MAAM,MAAM;EACrG,MAAM,WAAW,kBAAkB,OAAO,0BAA0B,cAAc,IAAI,KAAA;EACtF,MAAM,eAAe,gBAAgB,UAAU;EAC/C,MAAM,gBACL,QAAQ,eAAA,WACL,eAAe,YAAY,IAC3B,QAAQ,eAAA,WACP,eAAe,YAAY,IAC3B,QAAQ,eAAA,cACP,uBAAuB,YAAY,IACnC,mBAAmB,YAAY;EACrC,IAAI,aAAa,QAAQ,cAAc,CAAC,eAAe,OAAO,OAAO,eAAe;CACrF;CAEA,IAAI,WAAW,4BAA4B,oBAAoB,KAAK,SAAS,YAAY,CAAC,CAAC,CAC1F,QAAQ,WACR,eACA,MACA,MACD;CACA,IAAI,YAAY,MAAM,OAAO,OAAO,oBAAoB;CAExD,IAAI,KAAK,QAAQ,cAAc,0BAA0B,QAAQ;CAIjE,IAAI,MAAM,QAAQ,SAAS,OAAO,kBAAkB,SAAS,QAC5D,KAAA;CAED,IAAI,MAAM,MAAM,OAAO,OAAO,sBAAsB;CASpD,MAAM,UAAU,eAAe,SAAS,MAAM;CAI9C,IAAI,cACH,QAAQ,gBAAgB,QAAQ,QAAQ,UAAU,OAC/C,eAAe,IAAI,qBAAqB,IAAI,QAAQ,cAAc,QAAQ,MAAM,CAAC,IACjF,KAAA;CACJ,IAAI,eAAe,QAAQ;CAC3B,IAAI;CACJ,IAAI,kCAAkC,EAAE,GAAG;EAC1C,IAAI,KAAK,MAAM,GAAG,CAAC,CAAC,MAAK,YAAW,qBAAqB,KAAK,OAAO,KAAK,cAAc,KAAK,OAAO,CAAC,GACpG,OAAO,OAAO,oBAAoB;EAGnC,MAAM,aAAa,IAAI,cAAc,EAAE;EACvC,MAAM,kBAAkB,WAAW,KAAI,MAAK,eAAe,EAAE,MAAM,CAAC;EACpE,IAAI,eAAe,QAAQ,WAAW,MAAM;GAC3C,MAAM,gBAAgB,IAAI,IAAI,gBAAgB,QAAO,SAAQ,WAAW,MAAM,OAAO,CAAC,CAAC;GACvF,IAAI,cAAc,OAAO,GAAG,OAAO,OAAO,eAAe;GAEzD,cAAc,cAAc,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC;EAC7C;EAKA,IAAI,eAAe,QAAQ,gBAAgB,WAAW,GAAG,OAAO,OAAO,eAAe;EACtF,IAAI,eAAe,QAAQ,WAAW,QAAQ,CAAC,WAAW,aAAa,OAAO,GAC7E,OAAO,OAAO,eAAe;EAG9B,MAAM,eAAe,kBACpB,KACA,SACA,IACA,WAAW,QAAO,MAAK,eAAe,EAAE,MAAM,MAAM,WAAW,GAC/D;GAAU;GAAQ,QAAQ;GAAoB;EAAK,CACpD;EACA,IAAI,OAAO,iBAAiB,UAAU,OAAO,OAAO,YAAY;EAEhE,IAAI,gBAAgB,MAAM;GACzB,WAAW,aAAa;GACxB,eAAe,aAAa;GAC5B,eAAe,aAAa;EAC7B;CACD;CAEA,MAAM,QAAQ,SAAS;CACvB,MAAM,OAAO,SAAS;CACtB,IAAI,SAAS,QAAQ,QAAQ,MAAM,OAAO,OAAO,oBAAoB;CAErE,iBAAiB,SAAS;CAE1B,IAAI;CACJ,IAAI;EAIH,cAAc,kCAAkC,EAAE,IAC/C,eAAe,OACd,MAAM,IAAI,sBAAsB,IAAI,cAAc,WAAW,IAC7D,KAAA,IACD,MAAM,IAAI,wBAAwB,IAAI,SAAS,MAAM;CACzD,QAAQ;EACP,cAAc,KAAA;CACf;CACA,IAAI,eAAe,MAGlB,OAAO,EACN,YAAY;EACX,QAAQ;EACR,SAAS,oBAAoB,IAAI,SAAS,QAAQ,YAAY;CAC/D,EACD;CAID,IAAI,oBAAoB,WAAW,KAAK,YAAY,eAAe,MAClE,OAAO,OAAO,sBAAsB;CAIrC,MAAM,UAAU,SAAS,OAAO,iBAAiB,SAAS,cAAc,aAAa,KAAA;CACrF,MAAM,SAAS,eAAe,SAAS;CAKvC,IAAI,SAAS,OAAO,kBAAkB,WAAW,MAAM,OAAO,OAAO,oBAAoB;CAEzF,IAAI;EACH,MAAM,OAAO,MAAM,YAAY,YAAY;GAC1C,GAAG;GACI;GACD;GACG;GACK;EACf,CAAC;EACD,IAAI,QAAQ,MAMX,OAAO,EACN,YAAY;GACX,QAAQ;GACR,SAAS,oBAAoB,IAAI,QAAQ,YAAY;EACtD,EACD;EAQD,MAAM,iBAAiB,KAAK,aAAa;EACzC,MAAM,gBAAgB,KAAK,QAAQ;EACnC,MAAM,UACL,eAAe,YAAY,MAAM,MAAM,YAAY,KAAK,cAAc,YAAY,MAAM,KAAK,YAAY;EAW1G,OAAO,EAAE,YAAY;GAAE,QAAQ;GAAY,UAAU;IARpD,YAAY;IACJ;IACR,OAAO;IACP,MAAM;IACG;IACT,WAAW,QAAQ;IACV;GAEkD;EAAE,EAAE;CACjE,SAAS,IAAI;EAGZ,IAAI;EACJ,IAAI,cAAc,sBACjB,aAAa,EAAE,QAAQ,YAAY;OAC7B,IAAI,cAAc,qBAAqB;GAG7C,IAAI,UAAU,kBAAkB,IAAI,QAAQ,cAAc,EAAE;GAC5D,IAAI;IACH,MAAM,UAAU,MAAM,YAAY,wBACjC;KAAS;KAAa;KAAe;KAAuB;IAAa,GACzE,EACD;IACA,IAAI,WAAW,MAAM;KACpB,MAAM,WAA8B,CAAC;KACrC,oBAAoB,UAAU,IAAI,QAAQ,cAAc;MACvD,cAAc;MACd,UAAU,CAAC,OAAO;KACnB,CAAC;KACD,UAAU,SAAS,MAAM;IAC1B;GACD,SAAS,iBAAiB;IACzB,IAAI,2BAA2B,qBAC9B,UAAU,kBAAkB,IAAI,QAAQ,cAAc,eAAe;GAEvE;GACA,aAAa;IACZ,QAAQ;IACC;GACV;EACD,OACC,aAAa;GAAE,QAAQ;GAAgB,SAAS,kBAAkB,IAAI,QAAQ,cAAc,EAAE;EAAE;EAEjG,OAAO,EAAc,WAAW;CACjC;AACD;;;;;;;;AASA,SAAS,kBACR,KACA,SACA,IACA,aACA,QAKY;CACZ,IAAI,CAAC,kBAAkB,OAAO,MAAM,GAAG,OAAO,KAAA;CAE9C,MAAM,oBAAoB,eAAgF;EACzG,MAAM,UAAU,sBACf,IACA,cAAc,OAAQ,WAAW,WAAW,WAAW,SAAU,QAAQ,MAC1E;EACA,OAAO,WAAW,OAAO,IAAI,IAAI,OAAO,CAAC,CAAC,SAAS,QAAQ,QAAQ,EAAE,IAAI,KAAA;CAC1E;CAGA,MAAM,oBAAoB,YAAY,MAAK,MAAK,EAAE,OAAO,KAAK,YAAY;CAG1E,MAAM,mBAAmB,YAAY,QAAO,MAAK,EAAE,KAAK;CACxD,MAAM,eAAe,iBAAiB,MAAK,MAAK,EAAE,OAAO,KAAK,iBAAiB;CAC/E,MAAM,oBACL,mBAAmB,UAAU,SAC7B,gBAAgB,QAChB,iBAAiB,YAAY,MAAM,iBAAiB,iBAAiB;CAItE,MAAM,cAAc,eACnB,QAAQ,gBAAgB,SACvB,eAAe,oBACb,oBACA,YAAY,UAAU,QAAQ,eAAe,WAAW,EAAE,MAAM,WAAW;CAE/E,MAAM,aACL,QAAQ,gBAAgB,OACrB,YAAY,QAAO,MAAK,EAAE,OAAO,QAAQ,YAAY,IACrD,CAAC,mBAAmB,GAAG,YAAY,QAAO,MAAK,MAAM,iBAAiB,CAAC;CAE3E,IAAI,CAAC,WAAW,QACf,WAAW,KAAK,KAAA,CAAS;CAG1B,MAAM,aAAa,IAAI,OAAO;CAC9B,MAAM,iBAAiB,OAAO,KAAK,MAAM,GAAG;CAG5C,MAAM,cAAc,aAAyC;EAC5D,IAAI,OAAA,uBACH,OAAO,WAAW,WAAW,GAAG,SAAS,EAAE,IAAI,WAAW,MAAM,SAAS,SAAS,CAAC,IAAI,KAAA;EAGxF,MAAM,eAAe,SAAS,MAAM,GAAG,CAAC,CAAC,OAAO,OAAO;EAOvD,OALC,eAAe,SAAS,aAAa,UACrC,aAAa,OACX,SAAS,MACT,kBAAkB,OAAO,CAAC,CAAC,YAAY,MAAM,kBAAkB,eAAe,EAAE,CAAC,CAAC,YAAY,CAChG,IACgB,eAAe,MAAM,aAAa,MAAM,CAAC,CAAC,KAAK,GAAG,IAAI,KAAA;CACxE;CACA,IAAI;CACJ,IAAI,UAAU;CACd,KAAK,MAAM,cAAc,YAAY;EACpC,MAAM,WAAW,iBAAiB,UAAU;EAC5C,IAAI,YAAY,MAAM;EAEtB,UAAU;EACV,IAAI,WAAW,QAAQ,KAAK,MAAM;EAOlC,IAHC,SAAS,QACT,SAAS,SAAS,MAAM,SAAS,UAChC,SAAS,WAAW,MAAM,SAAS,UAAU,CAAC,WAAW,MAAM,UAAU,KAAK,WAAW,UAAU,GAEpG,QAAQ;GAAc;GAAsB;EAAS;CAEvD;CACA,IAAI,SAAS,MAAM,OAAO,UAAU,kBAAkB,KAAA;CAGtD,IAAI,CAAC,WAAW,MAAM,UAAU,GAAG,OAAO;CAE1C,MAAM,eACL,QAAQ,iBAAiB,MAAM,eAAe,oBAAoB,MAAM,YAAY,KAAK,KAAA;CAE1F,MAAM,UAAU,4BACf,oBAAoB,KAAK;EAAE,GAAG;EAAS,YAAY;CAAG,GAAG,OAAO,MAAM,CACvE;CACA,MAAM,WAAW,SAAiB,QAAQ,QAAQ,WAAW,OAAO,QAAQ,WAAW,IAAI,KAAK,IAAI,OAAO,MAAM;CACjH,IAAI,WAAW,MAAM;CACrB,IAAI,WAAW,QAAQ,QAAQ;CAC/B,IAAI,OAAA,uBACH,OAAO,YAAY,OAChB;EAAY;EAAwB;EAAc,UAAU,SAAS;CAAS,IAC9E;CAKJ,IAAI,YAAY,UAAU,cAAc,cAAc,MAAM;EAC3D,WAAW,SAAS,MAAM,GAAG,SAAS,YAAY,GAAG,CAAC;EACtD,WAAW,QAAQ,QAAQ;CAC5B;CACA,IAAI,YAAY,MAAM,OAAO;CAI7B,MAAM,WAAW,SAAS;CAC1B,MAAM,WAAW,OAAO,SAAS,qBAAqB,WAAW,SAAS,mBAAmB,KAAA;CAC7F,MAAM,mBACL,CAAC,GAAG,SAAS,MAAM,GAAG,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,IAAI,iBAAiB,GAAG,GAAI,UAAU,MAAM,GAAG,KAAK,CAAC,CAAE,CAAC,CAAC,KAAK,GAAG,KACzG,KAAA;CACD,OAAO;EACI;EACI;EACd,UAAU;GAAE,GAAG;GAA4B;EAAiB;CAC7D;AACD;;;;;;AAOA,SAAS,oBACR,KACA,SACA,cACyB;CACzB,MAAM,UAAkC,CAAC;CACzC,KAAK,MAAM,OAAO,IAAI,iBAAiB,GAAG;EACzC,MAAM,OAAO,4BAA4B,IAAI,IAAI;EACjD,IAAI,QAAQ,MAAM;EAIlB,IAAI,IAAI,QACP,QAAQ,KAAK;GAAQ;GAAM,QAAQ,IAAI;GAAQ,UAAU,IAAI;EAAS,CAAC;OACjE,IAAI,IAAI,OACd,QAAQ,KAAK;GAAQ;GAAM,OAAO,IAAI;GAAO,UAAU,IAAI;EAAS,CAAC;CAEvE;CACA,IAAI,QAAQ,cAAc,MAAM,OAAO;CAEvC,MAAM,OAAO,iCAAiC,QAAQ,UAAU;CAChE,MAAM,SAAS,gBAAgB,QAAQ;CACvC,IAAI,QAAQ,QAAQ,CAAC,QAAQ,OAAO;CAMpC,MAAM,cAAc,OAAO,YAAY;CACvC,MAAM,WAAW,QAAQ,MAAK,MAAK;EAClC,IAAI,EAAE,SAAS,MAAM,OAAO;EAC5B,IAAI,EAAE,UAAU,MAAM,OAAO,EAAE,OAAO,YAAY,MAAM;EAGxD,IAAI,EAAE,OACL,IAAI;GACH,OAAO,IAAI,OAAO,EAAE,OAAO,GAAG,CAAC,CAAC,KAAK,WAAW;EACjD,QAAQ;GACP,OAAO;EACR;EAGD,OAAO;CACR,CAAC,CAAC,EAAE;CACJ,QAAQ,QAAQ;EAAQ;EAAc;EAAkB;CAAS,CAAC;CAClE,OAAO;AACR"}
1
+ {"version":3,"file":"resolveRepository.js","names":[],"sources":["../../../../../plus/integrations/src/reads/resolveRepository.ts"],"sourcesContent":["import type { RemoteProvider } from '@gitlens/git/models/remoteProvider.js';\nimport type { ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport type { RemoteProviderConfig } from '@gitlens/git/remotes/matcher.js';\nimport { createRemoteProviderMatcher } from '@gitlens/git/remotes/matcher.js';\nimport { parseGitRemoteUrl } from '@gitlens/git/utils/remote.utils.js';\nimport type { ConfiguredIntegrationDescriptor } from '../authentication/models.js';\nimport { mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { GitCloudHostIntegrationId, GitSelfManagedHostIntegrationId } from '../constants.js';\nimport { AuthenticationError, RequestNotFoundError } from '../errors.js';\nimport { isIssuesIntegration } from '../models/issuesIntegration.js';\nimport { isAzureCloudDomain, isBitbucketCloudDomain, isGitHubDotCom, isGitLabDotCom } from '../providers/models.js';\nimport type { ProviderWarning, RepositoryIdentity, RepositoryResolution, ResolveRepositoryResult } from '../results.js';\nimport { toProviderWarning } from '../results.js';\nimport { decodePathSegment, getRemoteHostMatcher, hostFromDomain, isWebRemoteScheme } from '../utils/domain.utils.js';\nimport {\n\tgetIntegrationIdForRemote,\n\tgetSelfManagedBaseUrl,\n\tisGitCloudHostIntegrationId,\n\tisGitSelfManagedHostIntegrationId,\n\tremoteProviderTypeForConfig,\n\tremoteProviderTypeForIntegration,\n} from '../utils/integration.utils.js';\nimport type { RepositoryResolutionContext } from './context.js';\nimport { noConnectionWarning } from './warnings.js';\n\ntype ResolveRepositoryOptions = {\n\tproviderId?: IntegrationIds;\n\tremoteUrl: string;\n\thost?: string;\n\tconnectionId?: string;\n\t/**\n\t * Explicit self-managed host domain used to select the integration instance. Used only when the requested\n\t * connection has no configured domain; it must come from the trusted authentication configuration, not\n\t * repository or remote data.\n\t */\n\tdomain?: string;\n};\n\n/**\n * Resolution of a remote URL to a provider repository identity — the facade's equivalent of `gk repo resolve`.\n *\n * Unlike every other read here, this one's input is REPOSITORY data, so most of the code below is about not\n * letting that data choose the credentials: an explicit cloud `providerId` must agree with the remote's canonical\n * host, and a self-managed host resolves only against a domain the user already authenticated. The\n * `host-mismatch` returns are those guards, not edge cases — resolving `owner/repo` against the wrong account\n * would hand back a confidently wrong identity and seed the domain-keyed instance cache from remote data.\n */\nexport async function resolveRepository(\n\tctx: RepositoryResolutionContext,\n\toptions: ResolveRepositoryOptions,\n): Promise<ResolveRepositoryResult> {\n\tconst result = (status: RepositoryResolution['status']): ResolveRepositoryResult => ({\n\t\tresolution: { status: status },\n\t});\n\n\tconst [scheme, parsedDomain, path] = parseGitRemoteUrl(options.remoteUrl);\n\t// An SSH remote's port belongs to its SSH daemon, not to the web authority a connection is keyed by.\n\tconst hostsMatch = getRemoteHostMatcher(scheme);\n\tconst parsedHost = hostFromDomain(parsedDomain);\n\tconst explicitHost = hostFromDomain(options.host);\n\tif (parsedHost != null && explicitHost != null && !hostsMatch(parsedHost, explicitHost)) {\n\t\treturn result('host-mismatch');\n\t}\n\n\tconst matcherDomain = parsedDomain || options.host || '';\n\n\t// An explicit cloud provider must agree with the remote's canonical cloud host. Without this guard the\n\t// synthetic matcher below can reinterpret, for example, a gitlab.com URL as GitHub and then resolve a\n\t// homonymous owner/repo through the wrong account. Match once without caller configs/synthetic entries;\n\t// Azure's matcher also normalizes ssh.dev.azure.com and vs-ssh.visualstudio.com to their web host.\n\tif (options.providerId != null && isGitCloudHostIntegrationId(options.providerId)) {\n\t\tconst nativeProvider = createRemoteProviderMatcher([])(options.remoteUrl, matcherDomain, path, scheme);\n\t\tconst nativeId = nativeProvider != null ? getIntegrationIdForRemote(nativeProvider) : undefined;\n\t\tconst nativeDomain = nativeProvider?.domain ?? matcherDomain;\n\t\tconst canonicalHost =\n\t\t\toptions.providerId === GitCloudHostIntegrationId.GitHub\n\t\t\t\t? isGitHubDotCom(nativeDomain)\n\t\t\t\t: options.providerId === GitCloudHostIntegrationId.GitLab\n\t\t\t\t\t? isGitLabDotCom(nativeDomain)\n\t\t\t\t\t: options.providerId === GitCloudHostIntegrationId.Bitbucket\n\t\t\t\t\t\t? isBitbucketCloudDomain(nativeDomain)\n\t\t\t\t\t\t: isAzureCloudDomain(nativeDomain);\n\t\tif (nativeId !== options.providerId || !canonicalHost) return result('host-mismatch');\n\t}\n\n\tlet provider = createRemoteProviderMatcher(buildMatcherConfigs(ctx, options, parsedDomain))(\n\t\toptions.remoteUrl,\n\t\tmatcherDomain,\n\t\tpath,\n\t\tscheme,\n\t);\n\tif (provider == null) return result('invalid-remote-url');\n\n\tlet id = options.providerId ?? getIntegrationIdForRemote(provider);\n\t// Custom Azure DevOps Server domains matched via getRemoteConfigs return undefined from\n\t// getIntegrationIdForRemote because the provider is marked custom; map them to the server id so the\n\t// project-scoped lookup uses that host's configured connection and API base URL.\n\tif (id == null && provider.id === 'azure-devops' && provider.custom) {\n\t\tid = GitSelfManagedHostIntegrationId.AzureDevOpsServer;\n\t}\n\tif (id == null) return result('unsupported-provider');\n\n\t// On a self-managed host, resolve only against a TRUSTED host: the pinned connection's configured\n\t// domain, the explicit `domain`, or — when neither was supplied — a configured host matching the\n\t// remote's. That last case keeps `remoteUrl` (repository-supplied) out of the trusted path: it selects\n\t// among hosts the user already authenticated and can never introduce a new one. Resolving `owner/repo`\n\t// against some other host's account would, if that host has the same owner/repo, return a confidently\n\t// wrong identity — and would seed the domain-keyed integration cache (never evicted before dispose) with\n\t// an entry derived from repository data.\n\tconst urlHost = hostFromDomain(provider.domain);\n\t// Normalize BOTH sides before comparing: a stored connection domain is usually a full URL\n\t// (`https://git.example.com`) while `urlHost` is already a bare host, so a raw compare would fail on\n\t// scheme/trailing-slash alone and wrongly reject a correctly-configured connection.\n\tlet trustedHost =\n\t\toptions.connectionId != null || options.domain != null\n\t\t\t? hostFromDomain(ctx.resolveDomainForRead(id, options.connectionId, options.domain))\n\t\t\t: undefined;\n\tlet connectionId = options.connectionId;\n\tlet providerRepo: ResourceDescriptor | undefined;\n\tif (isGitSelfManagedHostIntegrationId(id)) {\n\t\tif (path.split('/').some(segment => /^(?:\\.|%2e){1,2}$/i.test(segment) || /%2f|%5c|\\\\/i.test(segment))) {\n\t\t\treturn result('invalid-remote-url');\n\t\t}\n\n\t\tconst configured = ctx.getConfigured(id);\n\t\tconst configuredHosts = configured.map(c => hostFromDomain(c.domain));\n\t\tif (trustedHost == null && urlHost != null) {\n\t\t\tconst matchingHosts = new Set(configuredHosts.filter(host => hostsMatch(host, urlHost)));\n\t\t\tif (matchingHosts.size > 1) return result('host-mismatch');\n\n\t\t\ttrustedHost = matchingHosts.values().next().value;\n\t\t}\n\t\t// A configured host that doesn't match the remote's is a genuine mismatch. With nothing configured\n\t\t// there is no host to mismatch against — it's simply not connected, so fall through to the\n\t\t// `unauthorized` path below (still WITHOUT resolving by the URL domain, so repo data can't seed the\n\t\t// cache).\n\t\tif (trustedHost == null && configuredHosts.length !== 0) return result('host-mismatch');\n\t\tif (trustedHost != null && urlHost != null && !hostsMatch(trustedHost, urlHost)) {\n\t\t\treturn result('host-mismatch');\n\t\t}\n\n\t\tconst installation = matchInstallation(\n\t\t\tctx,\n\t\t\toptions,\n\t\t\tid,\n\t\t\tconfigured.filter(c => hostFromDomain(c.domain) === trustedHost),\n\t\t\t{ scheme: scheme, domain: parsedDomain, path: path },\n\t\t);\n\t\tif (typeof installation === 'string') return result(installation);\n\n\t\tif (installation != null) {\n\t\t\tprovider = installation.provider;\n\t\t\tconnectionId = installation.connectionId;\n\t\t\tproviderRepo = installation.repoDesc;\n\t\t}\n\t}\n\n\tconst owner = provider.owner;\n\tconst name = provider.repoName;\n\tif (owner == null || name == null) return result('invalid-remote-url');\n\n\tproviderRepo ??= provider.repoDesc;\n\n\tlet integration;\n\ttry {\n\t\t// Resolve the instance through the trusted target (as `getIntegrationForRead` does):\n\t\t// `resolveReadSession` looks the session up against the instance's domain-scoped descriptor, so\n\t\t// selecting the instance by the URL domain could miss the session and degrade to `no-connection`.\n\t\tintegration = isGitSelfManagedHostIntegrationId(id)\n\t\t\t? trustedHost != null\n\t\t\t\t? await ctx.getIntegrationForRead(id, connectionId, trustedHost)\n\t\t\t\t: undefined\n\t\t\t: await ctx.getIntegrationForDomain(id, provider.domain);\n\t} catch {\n\t\tintegration = undefined;\n\t}\n\tif (integration == null) {\n\t\t// Attach a warning, as the thrown-AuthenticationError branch below does: a consumer driving auth\n\t\t// recovery off `warning.kind` would otherwise get nothing for an unresolvable target.\n\t\treturn {\n\t\t\tresolution: {\n\t\t\t\tstatus: 'unauthorized',\n\t\t\t\twarning: noConnectionWarning(id, provider.domain, connectionId),\n\t\t\t},\n\t\t};\n\t}\n\t// Issue trackers have no `getRepo` client; a git host without `getRepoFn` leaves `getRepoInfo`\n\t// undefined. Either way this provider can't resolve repositories.\n\tif (isIssuesIntegration(integration) || integration.getRepoInfo == null) {\n\t\treturn result('unsupported-provider');\n\t}\n\n\t// Azure repos are org + project scoped; the remote provider exposes project as `providerDesc.repoDomain`.\n\tconst project = provider.id === 'azure-devops' ? provider.providerDesc?.repoDomain : undefined;\n\tconst domain = trustedHost ?? provider.domain;\n\n\t// Azure's lookup is project-scoped and returns `undefined` without one, which the connection-gap branch\n\t// below would misreport as `unauthorized` (driving a pointless reconnect). A remote URL that carries no\n\t// project simply can't address an Azure repo.\n\tif (provider.id === 'azure-devops' && project == null) return result('invalid-remote-url');\n\n\ttry {\n\t\tconst repo = await integration.getRepoInfo({\n\t\t\t...providerRepo,\n\t\t\towner: owner,\n\t\t\tname: name,\n\t\t\tproject: project,\n\t\t\tconnectionId: connectionId,\n\t\t});\n\t\tif (repo == null) {\n\t\t\t// `getRepoInfo` returns undefined only when no session could be resolved (not connected, or the\n\t\t\t// requested connection is gone) — a real 404 throws below. So this is a connection gap. Attach a\n\t\t\t// warning so a consumer driving auth recovery off `warning.kind` sees the same signal it gets from\n\t\t\t// the thrown-AuthenticationError branch below; without it, the most common unauthorized case is\n\t\t\t// silent.\n\t\t\treturn {\n\t\t\t\tresolution: {\n\t\t\t\t\tstatus: 'unauthorized',\n\t\t\t\t\twarning: noConnectionWarning(id, domain, connectionId),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Prefer the provider's canonical namespace/name (GitHub's REST/GraphQL lookup follows the 301\n\t\t// rename redirect, so a stale old name resolves to the new canonical identity), falling back to the\n\t\t// parsed remote when the response omits them. `renamed` is a case-insensitive compare of input vs\n\t\t// canonical, mirroring gkcli's `EqualFold`, so hosts that merely echo the input casing (e.g.\n\t\t// Bitbucket Server/Azure) don't get spuriously flagged.\n\t\tconst canonicalOwner = repo.namespace || owner;\n\t\tconst canonicalName = repo.name || name;\n\t\tconst renamed =\n\t\t\tcanonicalOwner.toLowerCase() !== owner.toLowerCase() || canonicalName.toLowerCase() !== name.toLowerCase();\n\n\t\tconst identity: RepositoryIdentity = {\n\t\t\tproviderId: id,\n\t\t\tdomain: domain,\n\t\t\towner: canonicalOwner,\n\t\t\tname: canonicalName,\n\t\t\t// Azure's own project for the repository: an Azure DevOps Server remote's path may name the collection where\n\t\t\t// the parser read the project (see `AzureDevOpsServerIntegration.getRepositoryTarget`).\n\t\t\tproject: project != null ? repo.project || project : undefined,\n\t\t\tremoteUrl: options.remoteUrl,\n\t\t\trenamed: renamed,\n\t\t};\n\t\treturn { resolution: { status: 'resolved', identity: identity } };\n\t} catch (ex) {\n\t\t// Order matters: 404 throws RequestNotFoundError (not `undefined`), so check not-found before auth\n\t\t// and before the generic 5xx/unknown bucket — never classify a 401/403 as not-found.\n\t\tlet resolution: RepositoryResolution;\n\t\tif (ex instanceof RequestNotFoundError) {\n\t\t\tresolution = { status: 'not-found' };\n\t\t} else if (ex instanceof AuthenticationError) {\n\t\t\t// One repository refusing a credential that works (e.g. GitHub's OAuth App access restrictions) is that\n\t\t\t// repository's warning, scoped and with its cause, not a dead connection's.\n\t\t\tlet warning = toProviderWarning(id, domain, connectionId, ex);\n\t\t\ttry {\n\t\t\t\tconst failure = await integration.settleRepositoryRefusal(\n\t\t\t\t\t{\n\t\t\t\t\t\towner: owner,\n\t\t\t\t\t\tname: project != null ? getAzureRepositoryName(name) : name,\n\t\t\t\t\t\tproject: project,\n\t\t\t\t\t\tconnectionId: connectionId,\n\t\t\t\t\t},\n\t\t\t\t\tex,\n\t\t\t\t);\n\t\t\t\tif (failure != null) {\n\t\t\t\t\tconst warnings: ProviderWarning[] = [];\n\t\t\t\t\tmergeAssessmentInto(warnings, id, domain, connectionId, {\n\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\tfailures: [failure],\n\t\t\t\t\t});\n\t\t\t\t\twarning = warnings[0] ?? warning;\n\t\t\t\t}\n\t\t\t} catch (credentialError) {\n\t\t\t\tif (credentialError instanceof AuthenticationError) {\n\t\t\t\t\twarning = toProviderWarning(id, domain, connectionId, credentialError);\n\t\t\t\t}\n\t\t\t}\n\t\t\tresolution = {\n\t\t\t\tstatus: 'unauthorized',\n\t\t\t\twarning: warning,\n\t\t\t};\n\t\t} else {\n\t\t\tresolution = { status: 'undetermined', warning: toProviderWarning(id, domain, connectionId, ex) };\n\t\t}\n\t\treturn { resolution: resolution };\n\t}\n}\n\n/** The repository an Azure remote's `{project}/_git/{repo}` name addresses, as a batch target names it. */\nfunction getAzureRepositoryName(name: string): string {\n\treturn /\\/_git\\/([^/]+)$/i.exec(name)?.[1] ?? name;\n}\n\n/** Whether `path` is `{x}/_git/{repo}`, the Azure repository path the remote parser reads without a project segment. */\nfunction isProjectlessAzurePath(path: string | undefined): boolean {\n\tconst segments = path?.split('/');\n\treturn segments?.length === 3 && decodePathSegment(segments[1]).toLowerCase() === '_git';\n}\n\n/**\n * Narrows a web remote on a self-managed host to the configured installation it lives under: the pinned\n * connection's or, unpinned, the one whose installation path is the longest prefix of the remote's, so two\n * installations behind one host each resolve with their own credentials. The provider is re-matched against the\n * path relative to that installation, because the remote parsers expect the repository path to start at the\n * host root. An SSH remote carries no installation path, so it is left to the host-level checks.\n */\nfunction matchInstallation(\n\tctx: RepositoryResolutionContext,\n\toptions: ResolveRepositoryOptions,\n\tid: GitSelfManagedHostIntegrationId,\n\tconnections: ConfiguredIntegrationDescriptor[],\n\tremote: { scheme: string; domain: string; path: string },\n):\n\t| { provider: RemoteProvider; connectionId: string | undefined; repoDesc: ResourceDescriptor }\n\t| 'host-mismatch'\n\t| 'invalid-remote-url'\n\t| undefined {\n\tif (!isWebRemoteScheme(remote.scheme)) return undefined;\n\n\tconst installationPath = (connection: ConfiguredIntegrationDescriptor | undefined): string | undefined => {\n\t\tconst baseUrl = getSelfManagedBaseUrl(\n\t\t\tid,\n\t\t\tconnection != null ? (connection.baseUrl ?? connection.domain) : options.domain,\n\t\t);\n\t\treturn baseUrl != null ? new URL(baseUrl).pathname.replace(/\\/+$/, '') : undefined;\n\t};\n\n\t// The connection an unpinned read already targets comes first, so it wins a tie between installations.\n\tconst defaultConnection = connections.find(c => c.primary) ?? connections[0];\n\t// A cloud sync serves the host's cloud-scoped default instead, even over a local primary (see\n\t// `getStoredSession`), so an unpinned read reaches the default's installation only when both agree.\n\tconst cloudConnections = connections.filter(c => c.cloud);\n\tconst cloudDefault = cloudConnections.find(c => c.primary) ?? cloudConnections[0];\n\tconst isDefaultReadable =\n\t\tdefaultConnection?.cloud !== false ||\n\t\tcloudDefault == null ||\n\t\tinstallationPath(cloudDefault) === installationPath(defaultConnection);\n\t// Whether an unpinned read can reach this connection's session: the default one is read without an id, any\n\t// other only by a real cloud token id. Session lookup treats every connection id as one, and a local (PAT)\n\t// descriptor's or domain-derived legacy id is not (see `getConfiguredConnectionId`).\n\tconst isReadable = (connection: ConfiguredIntegrationDescriptor | undefined): boolean =>\n\t\toptions.connectionId != null ||\n\t\t(connection === defaultConnection\n\t\t\t? isDefaultReadable\n\t\t\t: connection?.cloud === true && hostFromDomain(connection.id) !== connection.domain);\n\n\tconst candidates: (ConfiguredIntegrationDescriptor | undefined)[] =\n\t\toptions.connectionId != null\n\t\t\t? connections.filter(c => c.id === options.connectionId)\n\t\t\t: [defaultConnection, ...connections.filter(c => c !== defaultConnection)];\n\t// With no configured candidate, the explicit (trusted) `domain` is the only installation address there is.\n\tif (!candidates.length) {\n\t\tcandidates.push(undefined);\n\t}\n\n\tconst remotePath = `/${remote.path}`;\n\tconst remoteSegments = remote.path.split('/');\n\t// The remote's path below an installation, or `undefined` when it isn't under it. Azure DevOps Server runs on\n\t// IIS, which serves its paths decoded and case-insensitively, so it compares segment by segment.\n\tconst relativeTo = (basePath: string): string | undefined => {\n\t\tif (id !== GitSelfManagedHostIntegrationId.AzureDevOpsServer) {\n\t\t\treturn remotePath.startsWith(`${basePath}/`) ? remotePath.slice(basePath.length + 1) : undefined;\n\t\t}\n\n\t\tconst baseSegments = basePath.split('/').filter(Boolean);\n\t\tconst isUnder =\n\t\t\tremoteSegments.length > baseSegments.length &&\n\t\t\tbaseSegments.every(\n\t\t\t\t(segment, i) =>\n\t\t\t\t\tdecodePathSegment(segment).toLowerCase() === decodePathSegment(remoteSegments[i]).toLowerCase(),\n\t\t\t);\n\t\treturn isUnder ? remoteSegments.slice(baseSegments.length).join('/') : undefined;\n\t};\n\tlet match: { connection: ConfiguredIntegrationDescriptor | undefined; basePath: string } | undefined;\n\tlet checked = false;\n\tfor (const connection of candidates) {\n\t\tconst basePath = installationPath(connection);\n\t\tif (basePath == null) continue;\n\n\t\tchecked = true;\n\t\tif (relativeTo(basePath) == null) continue;\n\n\t\t// The longest installation wins; on a tie, a readable connection replaces an unreadable one.\n\t\tconst isBetter =\n\t\t\tmatch == null ||\n\t\t\tbasePath.length > match.basePath.length ||\n\t\t\t(basePath.length === match.basePath.length && !isReadable(match.connection) && isReadable(connection));\n\t\tif (isBetter) {\n\t\t\tmatch = { connection: connection, basePath: basePath };\n\t\t}\n\t}\n\tif (match == null) return checked ? 'host-mismatch' : undefined;\n\t// Reading through the default connection instead would address its installation with a path relative to\n\t// another one, so an unreachable installation fails closed.\n\tif (!isReadable(match.connection)) return 'host-mismatch';\n\n\tconst connectionId =\n\t\toptions.connectionId ?? (match.connection !== defaultConnection ? match.connection?.id : undefined);\n\n\tconst matcher = createRemoteProviderMatcher(\n\t\tbuildMatcherConfigs(ctx, { ...options, providerId: id }, remote.domain),\n\t);\n\tconst rematch = (path: string) => matcher(options.remoteUrl, remote.domain, relativeTo(path) ?? '', remote.scheme);\n\tlet basePath = match.basePath;\n\tlet provider = rematch(basePath);\n\tif (id !== GitSelfManagedHostIntegrationId.AzureDevOpsServer) {\n\t\treturn provider != null\n\t\t\t? { provider: provider, connectionId: connectionId, repoDesc: provider.repoDesc }\n\t\t\t: 'invalid-remote-url';\n\t}\n\n\t// An Azure DevOps Server address may name the collection as well as the virtual directory; the parser expects\n\t// the collection in the path, so re-base on the address's parent when no project was found. A relative\n\t// `{x}/_git/{repo}` re-bases too: `x` is a collection only if the address is the installation rather than a\n\t// collection, which the path can't tell, so the integration settles it against the collections it discovers.\n\tif (basePath && (provider?.providerDesc?.repoDomain == null || isProjectlessAzurePath(relativeTo(basePath)))) {\n\t\tbasePath = basePath.slice(0, basePath.lastIndexOf('/'));\n\t\tprovider = rematch(basePath);\n\t}\n\tif (provider == null) return 'invalid-remote-url';\n\n\t// Requests address the repository's virtual directory from the host root, not from the installation the\n\t// re-match made it relative to; the absolute form also tells a directory apart from a collection of that name.\n\tconst repoDesc = provider.repoDesc;\n\tconst relative = typeof repoDesc.virtualDirectory === 'string' ? repoDesc.virtualDirectory : undefined;\n\tconst virtualDirectory =\n\t\t[...basePath.split('/').filter(Boolean).map(decodePathSegment), ...(relative?.split('/') ?? [])].join('/') ||\n\t\tundefined;\n\treturn {\n\t\tprovider: provider,\n\t\tconnectionId: connectionId,\n\t\trepoDesc: { ...repoDesc, virtualDirectory: virtualDirectory },\n\t};\n}\n\n/**\n * The remote-matcher configs for this resolution: the user's own host remote configs (self-managed/custom\n * domains), plus a synthetic exact-domain entry for an explicit `providerId` + host so a custom domain still maps\n * to the right provider for path parsing.\n */\nfunction buildMatcherConfigs(\n\tctx: RepositoryResolutionContext,\n\toptions: { providerId?: IntegrationIds; host?: string },\n\tparsedDomain: string,\n): RemoteProviderConfig[] {\n\tconst configs: RemoteProviderConfig[] = [];\n\tfor (const cfg of ctx.getRemoteConfigs()) {\n\t\tconst type = remoteProviderTypeForConfig(cfg.type);\n\t\tif (type == null) continue;\n\n\t\t// Forward both domain- and regex-based custom remotes (carrying any protocol override), so a\n\t\t// regex-configured host resolves instead of falling through to `unsupported`.\n\t\tif (cfg.domain) {\n\t\t\tconfigs.push({ type: type, domain: cfg.domain, protocol: cfg.protocol });\n\t\t} else if (cfg.regex) {\n\t\t\tconfigs.push({ type: type, regex: cfg.regex, protocol: cfg.protocol });\n\t\t}\n\t}\n\tif (options.providerId == null) return configs;\n\n\tconst type = remoteProviderTypeForIntegration(options.providerId);\n\tconst domain = parsedDomain || options.host;\n\tif (type == null || !domain) return configs;\n\n\t// The synthetic exact-domain entry is unshifted to the front, so it wins the match over the user's own\n\t// config for the same host. Carry that config's protocol override across (matched by domain or regex,\n\t// mirroring `ignoreSSLErrors`) so a self-managed host configured for a custom protocol — e.g. plain\n\t// `http` — isn't silently downgraded to the provider default here.\n\tconst lowerDomain = domain.toLowerCase();\n\tconst protocol = configs.find(c => {\n\t\tif (c.type !== type) return false;\n\t\tif (c.domain != null) return c.domain.toLowerCase() === lowerDomain;\n\n\t\t// Truthy (not just non-null): an empty regex would compile to a match-everything pattern.\n\t\tif (c.regex) {\n\t\t\ttry {\n\t\t\t\treturn new RegExp(c.regex, 'i').test(lowerDomain);\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t})?.protocol;\n\tconfigs.unshift({ type: type, domain: domain, protocol: protocol });\n\treturn configs;\n}\n"],"mappings":"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"}
@@ -1,8 +1,9 @@
1
1
  import { IssueSorting } from "../../../git/models/issue.js";
2
2
  import { PullRequestFilter, PullRequestSorting } from "../../../git/models/pullRequest.js";
3
3
  import { IntegrationIds } from "../constants.js";
4
- import { ProviderWarning } from "../results.js";
4
+ import { ProviderWarning, ProviderWarningOmissionScope } from "../results.js";
5
5
  import { IssueFilter } from "../providerFilters.js";
6
+ import { ProjectIssuesRecovery } from "../models/issueReads.js";
6
7
  import { IssueSearchCriteriaRejection, PullRequestSearchCriteriaRejection } from "./filters.js";
7
8
  import { UnmergeableIssueSort, UnsupportedIssueSortRejection } from "./ordering.js";
8
9
  //#region ../plus/integrations/src/reads/warnings.d.ts
@@ -61,10 +62,16 @@ export type IncompleteReadCause = 'interrupted' | 'page-budget' | 'exhausted' |
61
62
  * is the same shape `assessCollectionMetadata` derives from SDK metadata, so a consumer never has to care
62
63
  * which layer noticed the gap.
63
64
  *
64
- * No `scope`: this applies to the whole read, not to one repository or project the way an SDK-attributed
65
- * omission can, so a consumer acting on `recovery` re-runs the read itself.
65
+ * `scope` names the one project (or repository) the gap is confined to, as an SDK-attributed omission does; without it
66
+ * the warning applies to the whole read, so a consumer acting on `recovery` re-runs the read itself.
66
67
  */
67
- export declare function incompleteReadWarning(id: IntegrationIds, domain: string | undefined, connectionId: string | undefined, message: string, cause: IncompleteReadCause): ProviderWarning;
68
+ export declare function incompleteReadWarning(id: IntegrationIds, domain: string | undefined, connectionId: string | undefined, message: string, cause: IncompleteReadCause, scope?: ProviderWarningOmissionScope): ProviderWarning;
69
+ /**
70
+ * {@link incompleteReadWarning} for one project of a tracker read that stopped short with nothing in its metadata to say
71
+ * why: a drain cut by its page backstop (`narrow-scope`) or otherwise left incomplete (`none`). Confined to `scope`,
72
+ * which names the project the way the provider's own failures do.
73
+ */
74
+ export declare function projectCutWarning(id: IntegrationIds, domain: string | undefined, connectionId: string | undefined, project: string, recovery: ProjectIssuesRecovery, scope: ProviderWarningOmissionScope): ProviderWarning;
68
75
  /**
69
76
  * The read surfaces {@link truncationWarning} phrases for. `'Account-wide issue search'` names the composite
70
77
  * read that spans several provider searches rather than one surface.
@@ -93,20 +93,30 @@ function recoveryForIncompleteReadCause(cause) {
93
93
  * is the same shape `assessCollectionMetadata` derives from SDK metadata, so a consumer never has to care
94
94
  * which layer noticed the gap.
95
95
  *
96
- * No `scope`: this applies to the whole read, not to one repository or project the way an SDK-attributed
97
- * omission can, so a consumer acting on `recovery` re-runs the read itself.
96
+ * `scope` names the one project (or repository) the gap is confined to, as an SDK-attributed omission does; without it
97
+ * the warning applies to the whole read, so a consumer acting on `recovery` re-runs the read itself.
98
98
  */
99
- function incompleteReadWarning(id, domain, connectionId, message, cause) {
99
+ function incompleteReadWarning(id, domain, connectionId, message, cause, scope) {
100
100
  const warning = otherWarning(id, domain, connectionId, message);
101
101
  if (cause === "interrupted") return warning;
102
102
  return {
103
103
  ...warning,
104
104
  omission: {
105
105
  kind: "pagination-incomplete",
106
- recovery: recoveryForIncompleteReadCause(cause)
106
+ recovery: recoveryForIncompleteReadCause(cause),
107
+ ...scope != null ? { scope: { ...scope } } : {}
107
108
  }
108
109
  };
109
110
  }
111
+ /**
112
+ * {@link incompleteReadWarning} for one project of a tracker read that stopped short with nothing in its metadata to say
113
+ * why: a drain cut by its page backstop (`narrow-scope`) or otherwise left incomplete (`none`). Confined to `scope`,
114
+ * which names the project the way the provider's own failures do.
115
+ */
116
+ function projectCutWarning(id, domain, connectionId, project, recovery, scope) {
117
+ const [message, cause] = recovery === "narrow-scope" ? [`Project '${project}' holds more issues than one read can return; narrow the scope to read the rest.`, "scope-too-large"] : [`Some issues of project '${project}' were omitted; the provider returned an incomplete result.`, "exhausted"];
118
+ return incompleteReadWarning(id, domain, connectionId, message, cause, scope);
119
+ }
110
120
  /** {@link incompleteReadWarning} for a paged or drained read, phrased per surface. */
111
121
  function truncationWarning(id, domain, connectionId, readKind, cause) {
112
122
  return incompleteReadWarning(id, domain, connectionId, truncationMessage(id, readKind, cause), cause);
@@ -340,6 +350,6 @@ function issuesUnsupportedWarning(id, domain, connectionId) {
340
350
  return unsupportedWarning(id, domain, connectionId, `Issues are not supported by '${id}'; use a dedicated issue integration (e.g. Jira) instead.`);
341
351
  }
342
352
  //#endregion
343
- export { cappedBesideBudgetWarning, gitHostOnlySurfaceWarning, incompleteReadWarning, issueSearchCapResultWarning, issueTrackerOnlySurfaceWarning, issuesUnsupportedWarning, noConnectionWarning, otherWarning, pullRequestSearchCapResultWarning, truncationWarning, unmergeableIssueSortWarning, unsupportedAccountWideIssueFiltersWarning, unsupportedAccountWidePullRequestFiltersWarning, unsupportedFiltersWarning, unsupportedIssueSearchCriteriaWarning, unsupportedIssueSortWarning, unsupportedPullRequestSearchCriteriaWarning, unsupportedWarning, unusableSearchScopeMessage };
353
+ export { cappedBesideBudgetWarning, gitHostOnlySurfaceWarning, incompleteReadWarning, issueSearchCapResultWarning, issueTrackerOnlySurfaceWarning, issuesUnsupportedWarning, noConnectionWarning, otherWarning, projectCutWarning, pullRequestSearchCapResultWarning, truncationWarning, unmergeableIssueSortWarning, unsupportedAccountWideIssueFiltersWarning, unsupportedAccountWidePullRequestFiltersWarning, unsupportedFiltersWarning, unsupportedIssueSearchCriteriaWarning, unsupportedIssueSortWarning, unsupportedPullRequestSearchCriteriaWarning, unsupportedWarning, unusableSearchScopeMessage };
344
354
 
345
355
  //# sourceMappingURL=warnings.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"warnings.js","names":[],"sources":["../../../../../plus/integrations/src/reads/warnings.ts"],"sourcesContent":["import type { IntegrationIds } from '../constants.js';\nimport type {\n\tIssueFilter,\n\tIssueSearchCapabilities,\n\tIssueSorting,\n\tPullRequestFilter,\n\tPullRequestSearchCapabilities,\n\tPullRequestSorting,\n} from '../providerFilters.js';\nimport { providersMetadata } from '../providers/models.js';\nimport type { ProviderWarning, ProviderWarningOmissionRecovery } from '../results.js';\nimport type { IssueSearchCriteriaRejection, PullRequestSearchCriteriaRejection } from './filters.js';\nimport type { UnmergeableIssueSort, UnsupportedIssueSortRejection } from './ordering.js';\n\n/**\n * The warnings the provider facade raises on its OWN terms, as opposed to the ones derived from a caught\n * provider error ({@link toProviderWarning}) or from SDK collection metadata (`collectionMetadata.ts`).\n *\n * These are pure message builders, kept together and out of the service so a read method reads as the decision\n * it makes (\"this surface doesn't apply, refuse it\") rather than as the wording of the refusal, and so the same\n * refusal can't be phrased two ways in two reads.\n */\n\n/**\n * The single builder for a provider-neutral, non-auth `kind: 'other'` warning: a contradictory or malformed\n * request the caller fixes itself, or a read that couldn't confirm completeness. A refusal for a capability the\n * provider lacks goes through {@link unsupportedWarning} instead, so a consumer can tell the two apart without\n * reading `message`. Every `'other'` or `'unsupported'` warning the facade raises on its own terms goes through\n * one of the two (directly, or via a named builder below that pins a recurring message), so the discriminant is\n * assigned in one place. The warnings mapped from SDK collection metadata are the documented exception —\n * `collectionMetadata.ts` builds those from the structured failure/omission it is given.\n *\n * The kinds that carry a programmatic remedy — `auth`, `rate-limit`, `not-found`, `no-connection` — are derived\n * from the caught error's type instead and never come from here; see `ProviderWarningKind` and\n * {@link noConnectionWarning}.\n */\nexport function otherWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmessage: string,\n): ProviderWarning {\n\treturn {\n\t\tproviderId: id,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage: message,\n\t\tkind: 'other',\n\t\tisAuth: false,\n\t};\n}\n\n/**\n * Builds a `kind: 'unsupported'` warning: the provider — or this integration of it — lacks the capability the\n * request asked for: a surface (issues on Bitbucket, pull requests on an issue tracker, a batch read, org/repo\n * discovery, current-account lookup, project-scoped or account-wide issue reads), or a filter, sort, state,\n * search criterion or option combination it cannot express. Retrying the same request never succeeds; a\n * different provider, scope or request may.\n *\n * NOT for malformed or contradictory caller input the caller fixes in the request itself (a duplicate batch key,\n * invalid target numbers, an empty owner, an unusable scope), nor for incompleteness warnings; those are\n * {@link otherWarning}.\n */\nexport function unsupportedWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmessage: string,\n): ProviderWarning {\n\treturn {\n\t\tproviderId: id,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage: message,\n\t\tkind: 'unsupported',\n\t\tisAuth: false,\n\t};\n}\n\n/**\n * Builds a `no-connection` warning for a per-connection read that resolved neither a session nor an error: the\n * requested `connectionId` no longer resolves (deleted, or its authentication is invalid). Consumers use this\n * to tell a truly empty account apart from a broken connection.\n */\nexport function noConnectionWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId?: string,\n): ProviderWarning {\n\treturn {\n\t\tproviderId: id,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage:\n\t\t\tconnectionId != null\n\t\t\t\t? `Connection '${connectionId}' for '${id}' could not be resolved (deleted or invalid authentication).`\n\t\t\t\t: `No active connection for '${id}' could be resolved.`,\n\t\tkind: 'no-connection',\n\t\tisAuth: false,\n\t};\n}\n\n/** Warning for an issue-tracker provider asked for a surface only a git host serves (repos, PRs, sweeps). */\nexport function gitHostOnlySurfaceWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tsurface: string,\n): ProviderWarning {\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`${surface} is not supported by '${id}'; use a git-host integration instead.`,\n\t);\n}\n\n/** Warning for a git host asked for a surface only an issue tracker serves (resource → project issue reads). */\nexport function issueTrackerOnlySurfaceWarning(\n\tid: IntegrationIds,\n\tconnectionId: string | undefined,\n\tsurface: string,\n): ProviderWarning {\n\treturn unsupportedWarning(\n\t\tid,\n\t\tundefined,\n\t\tconnectionId,\n\t\t`${surface} is not supported by '${id}'; use an issue-tracker integration instead.`,\n\t);\n}\n\n/** Why a facade-driven read returned fewer results than requested. */\nexport type IncompleteReadCause = 'interrupted' | 'page-budget' | 'exhausted' | 'scope-too-large';\n\nfunction recoveryForIncompleteReadCause(cause: IncompleteReadCause): ProviderWarningOmissionRecovery {\n\tswitch (cause) {\n\t\tcase 'page-budget':\n\t\t\treturn 'page-budget';\n\t\tcase 'scope-too-large':\n\t\t\treturn 'narrow-scope';\n\t\tcase 'interrupted':\n\t\tcase 'exhausted':\n\t\t\treturn 'none';\n\t\tdefault:\n\t\t\tcause satisfies never;\n\t\t\treturn 'none';\n\t}\n}\n\n/**\n * Builds the warning for a read that returned less than everything, carrying the two facts a consumer cannot\n * recover on its own: did the request succeed, and would anything fetch the rest?\n *\n * Every incompleteness warning the facade raises on its own terms goes through here, so no read can drift\n * into claiming success on a failure or offering a \"load more\" that cannot deliver. The omission it attaches\n * is the same shape `assessCollectionMetadata` derives from SDK metadata, so a consumer never has to care\n * which layer noticed the gap.\n *\n * No `scope`: this applies to the whole read, not to one repository or project the way an SDK-attributed\n * omission can, so a consumer acting on `recovery` re-runs the read itself.\n */\nexport function incompleteReadWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmessage: string,\n\tcause: IncompleteReadCause,\n): ProviderWarning {\n\tconst warning = otherWarning(id, domain, connectionId, message);\n\tif (cause === 'interrupted') return warning;\n\n\treturn {\n\t\t...warning,\n\t\tomission: {\n\t\t\tkind: 'pagination-incomplete',\n\t\t\trecovery: recoveryForIncompleteReadCause(cause),\n\t\t},\n\t};\n}\n\n/**\n * The read surfaces {@link truncationWarning} phrases for. `'Account-wide issue search'` names the composite\n * read that spans several provider searches rather than one surface.\n *\n * Deliberately not exported: every caller passes a literal, and publishing it would invite a consumer to switch\n * on a set that exists only to word a message.\n */\ntype TruncatedReadKind =\n\t| 'Pull request'\n\t| 'Issue'\n\t| 'Repository'\n\t| 'Organization'\n\t| 'Project'\n\t| 'Account-wide issue search'\n\t| 'Issue search'\n\t| 'Pull request search';\n\n/** {@link incompleteReadWarning} for a paged or drained read, phrased per surface. */\nexport function truncationWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\treadKind: TruncatedReadKind,\n\t/**\n\t * Deliberately REQUIRED and not defaulted: a default would make one of these the silent fallback at any\n\t * call site that forgot it, and both of the claims it carries — \"the request succeeded\" and \"more can be\n\t * fetched\" — are ones this warning must never make by accident.\n\t */\n\tcause: IncompleteReadCause,\n): ProviderWarning {\n\treturn incompleteReadWarning(id, domain, connectionId, truncationMessage(id, readKind, cause), cause);\n}\n\n/**\n * The part of a drain that paging could not reach, when that drain ALSO stopped at its page budget with a usable\n * cursor — one facet of a composite read reaching the provider's ceiling while a sibling can still page. Reported\n * beside the `page-budget` {@link truncationWarning}, never in place of it: raising the budget returns more of the\n * rest, and nothing returns this part, so each warning speaks only for its own.\n *\n * The message names no mechanism. The fact behind it is `paging.truncated`, which a provider cap sets but so does\n * `hasMore` with no usable cursor (GitHub's `structuralIncompleteness`) and any per-repository truncation on the\n * repo-scoped path, so \"capped by the provider\" would name a cause we cannot confirm.\n */\nexport function cappedBesideBudgetWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\treadKind: TruncatedReadKind,\n): ProviderWarning {\n\treturn incompleteReadWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`${readKind} read for '${id}' could not reach part of its results by paging; raising the page budget will not return those.`,\n\t\t'exhausted',\n\t);\n}\n\nfunction truncationMessage(id: IntegrationIds, readKind: TruncatedReadKind, cause: IncompleteReadCause): string {\n\tswitch (cause) {\n\t\tcase 'interrupted':\n\t\t\t// Deliberately does not name a mechanism. This fires both when a page was lost mid-drain and when a\n\t\t\t// drain that latched an earlier scope failure later stopped for its own reasons; \"was interrupted\"\n\t\t\t// would be false in the second. What is true in both is that something failed and results are gone.\n\t\t\treturn `${readKind} read for '${id}' did not complete; some results are missing and the read reported a failure.`;\n\t\tcase 'page-budget':\n\t\t\treturn `${readKind} read for '${id}' stopped at its page budget; more results can be read by raising it.`;\n\t\tcase 'exhausted':\n\t\t\treturn `${readKind} read for '${id}' was truncated and cannot be continued; results may be incomplete.`;\n\t\tcase 'scope-too-large':\n\t\t\t// Says what is true — the scope is larger than a read of it — rather than blaming the provider for a\n\t\t\t// ceiling that is ours, and names the remedy that exists, since no retry or budget has one.\n\t\t\treturn `${readKind} read for '${id}' covers more than one read can return; narrow the scope to read the rest.`;\n\t}\n\t// No `default`: `IncompleteReadCause` is declared in this file, so `noImplicitReturns` already fails the\n\t// build here if a cause is added without its own wording. (`collectionOmissionMessage`'s `satisfies never`\n\t// is not the same case — that union is the SDK's, and can widen under a dependency bump.)\n}\n\n/**\n * The one sentence every \"you asked for a narrowing this provider can't express\" refusal uses.\n *\n * Shared because the three callers below differ only in a noun: the wording is consumer-visible, and three\n * hand-written copies drift three ways — including the `(supported: …)` clause, which exists to avoid emitting an\n * empty parenthetical and has to be the same guard in all of them.\n */\nfunction unsupportedNarrowingMessage(id: IntegrationIds, noun: string, requested: string, supported: string): string {\n\treturn `The requested ${noun} (${requested}) are not supported by '${id}'${supported.length > 0 ? ` (supported: ${supported})` : ''}; skipped to avoid returning a wider result than requested.`;\n}\n\n/** Warning for an account-wide issue read whose requested filters the provider can't express server-side. */\nexport function unsupportedAccountWideIssueFiltersWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tfilters: IssueFilter[],\n): ProviderWarning {\n\tconst supported = providersMetadata[id]?.supportedAccountWideIssueFilters ?? [];\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tunsupportedNarrowingMessage(id, 'account-wide issue filters', filters.join(', '), supported.join(', ')),\n\t);\n}\n\n/** Warning for an account-wide PR read whose requested relationship union cannot be expressed exactly. */\nexport function unsupportedAccountWidePullRequestFiltersWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tfilters: PullRequestFilter[],\n): ProviderWarning {\n\tconst supported = providersMetadata[id]?.supportedAccountWidePullRequestFilters ?? [];\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tunsupportedNarrowingMessage(id, 'account-wide pull request filters', filters.join(', '), supported.join(', ')),\n\t);\n}\n\n/** Warning for a repo-scoped PR read whose requested filters the provider supports none of. */\nexport function unsupportedFiltersWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n): ProviderWarning {\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`The requested pull request filters are not supported by '${id}'; skipped to avoid returning unfiltered results.`,\n\t);\n}\n\n/**\n * The one sentence every `unusable-scope` refusal uses — the five surfaces that validate a search scope (both\n * searches, both count probes, and issue broadening) differ only in the noun and in whether they name a count\n * key, so the reason a scope is unusable is written once. `prefix` is what the count probes put in front of it\n * to name their key.\n *\n * NAMES the offending values, because which failure mode applied is not deducible from the value: a quote makes\n * `git\"kraken` a request for the real and different org `gitkraken`, a space makes `my org` a search of `my`\n * filtered by the text `org`, and a value of only quotes or whitespace leaves no scope at all. The caller is the\n * only layer that knows which scope it meant.\n *\n * Says how to fix it rather than what was stripped: a consumer cannot mirror this rule (unlike free-text\n * sanitizing, whose rules are published for exactly that), so \"pass the scope as the provider names it\" is the\n * actionable half.\n */\nexport function unusableSearchScopeMessage(noun: string, scopes: readonly string[], prefix?: string): string {\n\treturn `${prefix != null ? `${prefix}: ` : ''}The requested ${noun} (${scopes.map(s => JSON.stringify(s)).join(', ')}) cannot be used as given: a scope name cannot contain quotes, or an inner space or control character, which a query would drop or read as a second qualifier — searching a different scope, or none. Pass the scope exactly as the provider names it.`;\n}\n\n/** Warning for a filtered pull-request search the provider cannot run as requested. */\nexport function unsupportedPullRequestSearchCriteriaWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\trejection: PullRequestSearchCriteriaRejection,\n): ProviderWarning {\n\tlet message: string;\n\tswitch (rejection.reason) {\n\t\tcase 'unsupported-search':\n\t\t\tmessage = `Filtered pull request search is not supported by '${id}'; use \\`listPullRequestsPage\\` for its own pull request reads.`;\n\t\t\tbreak;\n\t\tcase 'unsupported-criteria': {\n\t\t\tconst supported = providersMetadata[id]?.supportedPullRequestSearch;\n\t\t\tmessage = unsupportedNarrowingMessage(\n\t\t\t\tid,\n\t\t\t\t'pull request search criteria',\n\t\t\t\trejection.criteria.join(', '),\n\t\t\t\tsupported != null ? describePullRequestSearchCapabilities(supported) : '',\n\t\t\t);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn unsupportedWarning(id, domain, connectionId, message);\n}\n\nfunction describePullRequestSearchCapabilities(capabilities: PullRequestSearchCapabilities): string {\n\treturn [\n\t\t...(capabilities.relationships.length ? [`relationships:${capabilities.relationships.join('|')}`] : []),\n\t\t...(capabilities.states.length ? [`states:${capabilities.states.join('|')}`] : []),\n\t\t...(capabilities.text ? ['text'] : []),\n\t\t...(capabilities.updatedAfter ? ['updatedAfter'] : []),\n\t\t...(capabilities.createdAfter ? ['createdAfter'] : []),\n\t\t...(capabilities.includeArchived ? ['includeArchived'] : []),\n\t\t...(capabilities.draft ? ['draft'] : []),\n\t\t...(capabilities.repositoryScope ? ['repository scope'] : []),\n\t\t...(capabilities.organizationScope ? ['organization scope'] : []),\n\t\t...(capabilities.sorts.length ? [`sorts:${capabilities.sorts.join('|')}`] : []),\n\t].join(', ');\n}\n\n/**\n * Warning for a filtered issue search whose criteria the provider can't serve — either it has no such search at\n * all, or it can't express what was asked for, or the set contradicts itself.\n *\n * One builder for all three because they are one decision from the caller's side (\"this search can't be run as\n * asked\") and the remedy differs only in what the message says. The rejection shape carries which case it is, so\n * the wording can't drift from the check that produced it (see {@link resolveIssueSearchCriteria}). The kind\n * follows the reason: the first two are `'unsupported'` capabilities; a contradictory set is caller input and\n * stays `'other'`.\n */\nexport function unsupportedIssueSearchCriteriaWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\trejection: IssueSearchCriteriaRejection,\n): ProviderWarning {\n\tlet message: string;\n\tswitch (rejection.reason) {\n\t\tcase 'unsupported-search':\n\t\t\tmessage = `Filtered issue search is not supported by '${id}'; use \\`listIssuesPage\\` for its own issue reads.`;\n\t\t\tbreak;\n\t\tcase 'unsupported-criteria': {\n\t\t\tconst supported = providersMetadata[id]?.supportedIssueSearch;\n\t\t\tmessage = unsupportedNarrowingMessage(\n\t\t\t\tid,\n\t\t\t\t'issue search criteria',\n\t\t\t\trejection.criteria.join(', '),\n\t\t\t\tsupported != null ? describeIssueSearchCapabilities(supported) : '',\n\t\t\t);\n\t\t\tbreak;\n\t\t}\n\t\tcase 'contradictory-relationships':\n\t\t\t// Caller input the caller fixes in the request itself, so `'other'` rather than `'unsupported'`.\n\t\t\treturn otherWarning(\n\t\t\t\tid,\n\t\t\t\tdomain,\n\t\t\t\tconnectionId,\n\t\t\t\t`The relationships \\`any-assignee\\` and \\`unassigned\\` are contradictory — they partition the scope between them, so no issue satisfies both; pass only one.`,\n\t\t\t);\n\t}\n\t// No `default`: the union is declared alongside the validator, so `noImplicitReturns` fails the build here if\n\t// a rejection reason is added without its own wording.\n\n\treturn unsupportedWarning(id, domain, connectionId, message);\n}\n\n/** The criteria a provider CAN express, for the \"supported: …\" half of a refusal message. */\nfunction describeIssueSearchCapabilities(capabilities: IssueSearchCapabilities): string {\n\tconst flags: [name: string, supported: boolean][] = [\n\t\t['text', capabilities.text],\n\t\t['labels', capabilities.labels],\n\t\t['milestone', capabilities.milestone],\n\t\t['updatedAfter', capabilities.updatedAfter],\n\t\t['createdAfter', capabilities.createdAfter],\n\t\t['withoutLinkedPullRequest', capabilities.withoutLinkedPullRequest],\n\t\t['state', capabilities.states],\n\t];\n\treturn [\n\t\t...capabilities.relationships.map(r => `relationships:${r}`),\n\t\t...flags.filter(([, supported]) => supported).map(([name]) => name),\n\t\t...capabilities.sorts.map(s => `sort:${s}`),\n\t].join(', ');\n}\n\n/**\n * Warning for an issue read asked to order by a key its provider can't express server-side.\n *\n * Separate from {@link unsupportedIssueSearchCriteriaWarning} because it serves the reads that take `sort` as an\n * OPTION rather than as a criterion — repo-scoped, account-wide, and issue-tracker — where there is no criteria\n * set to fold it into. The filtered search keeps reporting it as one more `unsupported-criteria` entry, so a\n * caller there still gets a single refusal listing everything at once.\n *\n * Takes the rejection the check produced rather than a re-read supported list: which of the two tables applies is\n * the caller's own branch (`repos` present or not), so reading it back here — or threading it by hand — would be\n * free to disagree with the check that refused.\n */\nexport function unsupportedIssueSortWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\trejection: UnsupportedIssueSortRejection,\n): ProviderWarning {\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tunsupportedNarrowingMessage(id, 'issue sort', rejection.requested, (rejection.supported ?? []).join(', ')),\n\t);\n}\n\n/**\n * Warning for a merged issue read asked to order by a key that no normalized issue carries.\n *\n * The distinction this exists to make is not about the provider: the provider orders by `priority` perfectly well\n * within ONE project. It is about the read, which fans out over several and merges the results here, where the\n * only fields available are the ones {@link IssueShape} models. Serving the concatenated per-scope runs would look\n * ordered without being so, which is why this refuses instead — and why the message says WHY, since the same key\n * against the same provider succeeds on a single-scope read.\n *\n * What a read fans out OVER differs by read, hence the `scope`: repositories, projects, or — where one query is\n * issued per requested relationship and the runs are unioned within a single project (Jira) — the filters\n * themselves. Naming the wrong one would tell a caller to narrow something that isn't what merged.\n */\nexport function unmergeableIssueSortWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tunmergeable: UnmergeableIssueSort,\n\tscope: 'repositories' | 'projects' | 'filters',\n): ProviderWarning {\n\t// Both forms are spelled out rather than depluralized at runtime: the message needs each, and\n\t// `'repositories'.replace(/s$/, '')` is `'repositorie'`.\n\tconst nouns = {\n\t\trepositories: { many: 'repositories', one: 'repository' },\n\t\tprojects: { many: 'projects', one: 'project' },\n\t\tfilters: { many: 'issue relationships', one: 'relationship' },\n\t}[scope];\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`Cannot order by '${unmergeable.requested}' across several ${nouns.many}: this read merges their results, and a normalized issue carries no such field to merge on. Read one ${nouns.one} at a time, or order by a date, title, comment or reaction count.`,\n\t);\n}\n\n/**\n * The omission for a filtered issue search that hit the provider's RESULT CEILING: more issues matched than the\n * provider will ever serve for one query, however it is paged.\n *\n * Distinct from {@link truncationWarning} because this is the one incompleteness this read can quantify —\n * `totalCount` is how many matched, `limit` how many are reachable — which is what lets a consumer say \"19.240\n * matched, showing the 1.000 most recently updated\" instead of a bare \"results may be incomplete\". `recovery:\n * 'none'` because the items past the ceiling are UNREACHABLE, not merely unfetched: no budget, no retry, and no\n * cursor would return them. Narrowing the criteria is the only way through, which is a decision for the caller.\n *\n * The message names the ORDER, and has to now that ordering is an option: the reachable window is the top `limit`\n * under that key, so \"the 1.000 most recent\" is a true sentence only while the key is the default one. The same\n * value goes on the omission, so a consumer wording its own sentence doesn't have to remember what it asked for.\n *\n * Returns `undefined` when the provider declares no ceiling, so the caller falls back to the generic wording\n * rather than reporting a limit it invented.\n */\nexport function issueSearchCapResultWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\ttotalCount: number | undefined,\n\tsort: IssueSorting,\n\t/** The provider proved the ceiling was reached without reporting how many matched; see `limitReached`. */\n\tlimitReached?: boolean,\n): ProviderWarning | undefined {\n\treturn searchCapResultWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t'Issue search',\n\t\tprovidersMetadata[id]?.issueSearchResultLimit,\n\t\ttotalCount,\n\t\tsort,\n\t\tlimitReached,\n\t);\n}\n\n/**\n * The shared body of the two ceiling warnings. Only a ceiling that was actually reached warns: either a reported\n * total above it, or a provider that proved the ceiling was reached but can't count past it — in which case the\n * omission carries the limit and the order but no `totalCount`, since any figure would be invented.\n */\nfunction searchCapResultWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\treadKind: 'Issue search' | 'Pull request search',\n\tlimit: number | undefined,\n\ttotalCount: number | undefined,\n\tsort: IssueSorting | PullRequestSorting,\n\tlimitReached: boolean | undefined,\n): ProviderWarning | undefined {\n\tif (limit == null) return undefined;\n\n\t// Below the ceiling this warning would be a false claim; the caller's truncation had another cause.\n\tconst counted = totalCount != null && totalCount > limit;\n\tif (!counted && limitReached !== true) return undefined;\n\n\tconst [field, direction] = sort.split(':');\n\tconst matched = counted ? `matched ${totalCount} results` : `matched more than ${limit} results`;\n\treturn {\n\t\t...otherWarning(\n\t\t\tid,\n\t\t\tdomain,\n\t\t\tconnectionId,\n\t\t\t`${readKind} ${matched}, but '${id}' serves at most ${limit}, ordered by ${field} ${direction}ending; narrow the search to read the rest.`,\n\t\t),\n\t\tomission: {\n\t\t\tkind: 'provider-limit',\n\t\t\t// The items past the ceiling can't be fetched by anything, so never offer a \"load more\".\n\t\t\trecovery: 'none',\n\t\t\tlimit: limit,\n\t\t\t...(counted ? { totalCount: totalCount } : {}),\n\t\t\tsort: sort,\n\t\t},\n\t};\n}\n\n/** Quantified omission for a pull-request search that exceeded the provider's reachable result window. */\nexport function pullRequestSearchCapResultWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\ttotalCount: number | undefined,\n\tsort: PullRequestSorting,\n\t/** See {@link issueSearchCapResultWarning}. */\n\tlimitReached?: boolean,\n): ProviderWarning | undefined {\n\t// Named order for the same reason as the issue twin: the reachable window is the first `limit` under THIS sort.\n\treturn searchCapResultWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t'Pull request search',\n\t\tprovidersMetadata[id]?.pullRequestSearchResultLimit,\n\t\ttotalCount,\n\t\tsort,\n\t\tlimitReached,\n\t);\n}\n\n/** Warning for a git host that doesn't expose issues on this surface (e.g. Bitbucket, deprecated in favor of Jira). */\nexport function issuesUnsupportedWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n): ProviderWarning {\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`Issues are not supported by '${id}'; use a dedicated issue integration (e.g. Jira) instead.`,\n\t);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"warnings.js","names":[],"sources":["../../../../../plus/integrations/src/reads/warnings.ts"],"sourcesContent":["import type { IntegrationIds } from '../constants.js';\nimport type { ProjectIssuesRecovery } from '../models/issueReads.js';\nimport type {\n\tIssueFilter,\n\tIssueSearchCapabilities,\n\tIssueSorting,\n\tPullRequestFilter,\n\tPullRequestSearchCapabilities,\n\tPullRequestSorting,\n} from '../providerFilters.js';\nimport { providersMetadata } from '../providers/models.js';\nimport type { ProviderWarning, ProviderWarningOmissionRecovery, ProviderWarningOmissionScope } from '../results.js';\nimport type { IssueSearchCriteriaRejection, PullRequestSearchCriteriaRejection } from './filters.js';\nimport type { UnmergeableIssueSort, UnsupportedIssueSortRejection } from './ordering.js';\n\n/**\n * The warnings the provider facade raises on its OWN terms, as opposed to the ones derived from a caught\n * provider error ({@link toProviderWarning}) or from SDK collection metadata (`collectionMetadata.ts`).\n *\n * These are pure message builders, kept together and out of the service so a read method reads as the decision\n * it makes (\"this surface doesn't apply, refuse it\") rather than as the wording of the refusal, and so the same\n * refusal can't be phrased two ways in two reads.\n */\n\n/**\n * The single builder for a provider-neutral, non-auth `kind: 'other'` warning: a contradictory or malformed\n * request the caller fixes itself, or a read that couldn't confirm completeness. A refusal for a capability the\n * provider lacks goes through {@link unsupportedWarning} instead, so a consumer can tell the two apart without\n * reading `message`. Every `'other'` or `'unsupported'` warning the facade raises on its own terms goes through\n * one of the two (directly, or via a named builder below that pins a recurring message), so the discriminant is\n * assigned in one place. The warnings mapped from SDK collection metadata are the documented exception —\n * `collectionMetadata.ts` builds those from the structured failure/omission it is given.\n *\n * The kinds that carry a programmatic remedy — `auth`, `rate-limit`, `not-found`, `no-connection` — are derived\n * from the caught error's type instead and never come from here; see `ProviderWarningKind` and\n * {@link noConnectionWarning}.\n */\nexport function otherWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmessage: string,\n): ProviderWarning {\n\treturn {\n\t\tproviderId: id,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage: message,\n\t\tkind: 'other',\n\t\tisAuth: false,\n\t};\n}\n\n/**\n * Builds a `kind: 'unsupported'` warning: the provider — or this integration of it — lacks the capability the\n * request asked for: a surface (issues on Bitbucket, pull requests on an issue tracker, a batch read, org/repo\n * discovery, current-account lookup, project-scoped or account-wide issue reads), or a filter, sort, state,\n * search criterion or option combination it cannot express. Retrying the same request never succeeds; a\n * different provider, scope or request may.\n *\n * NOT for malformed or contradictory caller input the caller fixes in the request itself (a duplicate batch key,\n * invalid target numbers, an empty owner, an unusable scope), nor for incompleteness warnings; those are\n * {@link otherWarning}.\n */\nexport function unsupportedWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmessage: string,\n): ProviderWarning {\n\treturn {\n\t\tproviderId: id,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage: message,\n\t\tkind: 'unsupported',\n\t\tisAuth: false,\n\t};\n}\n\n/**\n * Builds a `no-connection` warning for a per-connection read that resolved neither a session nor an error: the\n * requested `connectionId` no longer resolves (deleted, or its authentication is invalid). Consumers use this\n * to tell a truly empty account apart from a broken connection.\n */\nexport function noConnectionWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId?: string,\n): ProviderWarning {\n\treturn {\n\t\tproviderId: id,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage:\n\t\t\tconnectionId != null\n\t\t\t\t? `Connection '${connectionId}' for '${id}' could not be resolved (deleted or invalid authentication).`\n\t\t\t\t: `No active connection for '${id}' could be resolved.`,\n\t\tkind: 'no-connection',\n\t\tisAuth: false,\n\t};\n}\n\n/** Warning for an issue-tracker provider asked for a surface only a git host serves (repos, PRs, sweeps). */\nexport function gitHostOnlySurfaceWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tsurface: string,\n): ProviderWarning {\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`${surface} is not supported by '${id}'; use a git-host integration instead.`,\n\t);\n}\n\n/** Warning for a git host asked for a surface only an issue tracker serves (resource → project issue reads). */\nexport function issueTrackerOnlySurfaceWarning(\n\tid: IntegrationIds,\n\tconnectionId: string | undefined,\n\tsurface: string,\n): ProviderWarning {\n\treturn unsupportedWarning(\n\t\tid,\n\t\tundefined,\n\t\tconnectionId,\n\t\t`${surface} is not supported by '${id}'; use an issue-tracker integration instead.`,\n\t);\n}\n\n/** Why a facade-driven read returned fewer results than requested. */\nexport type IncompleteReadCause = 'interrupted' | 'page-budget' | 'exhausted' | 'scope-too-large';\n\nfunction recoveryForIncompleteReadCause(cause: IncompleteReadCause): ProviderWarningOmissionRecovery {\n\tswitch (cause) {\n\t\tcase 'page-budget':\n\t\t\treturn 'page-budget';\n\t\tcase 'scope-too-large':\n\t\t\treturn 'narrow-scope';\n\t\tcase 'interrupted':\n\t\tcase 'exhausted':\n\t\t\treturn 'none';\n\t\tdefault:\n\t\t\tcause satisfies never;\n\t\t\treturn 'none';\n\t}\n}\n\n/**\n * Builds the warning for a read that returned less than everything, carrying the two facts a consumer cannot\n * recover on its own: did the request succeed, and would anything fetch the rest?\n *\n * Every incompleteness warning the facade raises on its own terms goes through here, so no read can drift\n * into claiming success on a failure or offering a \"load more\" that cannot deliver. The omission it attaches\n * is the same shape `assessCollectionMetadata` derives from SDK metadata, so a consumer never has to care\n * which layer noticed the gap.\n *\n * `scope` names the one project (or repository) the gap is confined to, as an SDK-attributed omission does; without it\n * the warning applies to the whole read, so a consumer acting on `recovery` re-runs the read itself.\n */\nexport function incompleteReadWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmessage: string,\n\tcause: IncompleteReadCause,\n\tscope?: ProviderWarningOmissionScope,\n): ProviderWarning {\n\tconst warning = otherWarning(id, domain, connectionId, message);\n\tif (cause === 'interrupted') return warning;\n\n\treturn {\n\t\t...warning,\n\t\tomission: {\n\t\t\tkind: 'pagination-incomplete',\n\t\t\trecovery: recoveryForIncompleteReadCause(cause),\n\t\t\t...(scope != null ? { scope: { ...scope } } : {}),\n\t\t},\n\t};\n}\n\n/**\n * {@link incompleteReadWarning} for one project of a tracker read that stopped short with nothing in its metadata to say\n * why: a drain cut by its page backstop (`narrow-scope`) or otherwise left incomplete (`none`). Confined to `scope`,\n * which names the project the way the provider's own failures do.\n */\nexport function projectCutWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tproject: string,\n\trecovery: ProjectIssuesRecovery,\n\tscope: ProviderWarningOmissionScope,\n): ProviderWarning {\n\tconst [message, cause]: [string, IncompleteReadCause] =\n\t\trecovery === 'narrow-scope'\n\t\t\t? [\n\t\t\t\t\t`Project '${project}' holds more issues than one read can return; narrow the scope to read the rest.`,\n\t\t\t\t\t'scope-too-large',\n\t\t\t\t]\n\t\t\t: [\n\t\t\t\t\t`Some issues of project '${project}' were omitted; the provider returned an incomplete result.`,\n\t\t\t\t\t'exhausted',\n\t\t\t\t];\n\treturn incompleteReadWarning(id, domain, connectionId, message, cause, scope);\n}\n\n/**\n * The read surfaces {@link truncationWarning} phrases for. `'Account-wide issue search'` names the composite\n * read that spans several provider searches rather than one surface.\n *\n * Deliberately not exported: every caller passes a literal, and publishing it would invite a consumer to switch\n * on a set that exists only to word a message.\n */\ntype TruncatedReadKind =\n\t| 'Pull request'\n\t| 'Issue'\n\t| 'Repository'\n\t| 'Organization'\n\t| 'Project'\n\t| 'Account-wide issue search'\n\t| 'Issue search'\n\t| 'Pull request search';\n\n/** {@link incompleteReadWarning} for a paged or drained read, phrased per surface. */\nexport function truncationWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\treadKind: TruncatedReadKind,\n\t/**\n\t * Deliberately REQUIRED and not defaulted: a default would make one of these the silent fallback at any\n\t * call site that forgot it, and both of the claims it carries — \"the request succeeded\" and \"more can be\n\t * fetched\" — are ones this warning must never make by accident.\n\t */\n\tcause: IncompleteReadCause,\n): ProviderWarning {\n\treturn incompleteReadWarning(id, domain, connectionId, truncationMessage(id, readKind, cause), cause);\n}\n\n/**\n * The part of a drain that paging could not reach, when that drain ALSO stopped at its page budget with a usable\n * cursor — one facet of a composite read reaching the provider's ceiling while a sibling can still page. Reported\n * beside the `page-budget` {@link truncationWarning}, never in place of it: raising the budget returns more of the\n * rest, and nothing returns this part, so each warning speaks only for its own.\n *\n * The message names no mechanism. The fact behind it is `paging.truncated`, which a provider cap sets but so does\n * `hasMore` with no usable cursor (GitHub's `structuralIncompleteness`) and any per-repository truncation on the\n * repo-scoped path, so \"capped by the provider\" would name a cause we cannot confirm.\n */\nexport function cappedBesideBudgetWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\treadKind: TruncatedReadKind,\n): ProviderWarning {\n\treturn incompleteReadWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`${readKind} read for '${id}' could not reach part of its results by paging; raising the page budget will not return those.`,\n\t\t'exhausted',\n\t);\n}\n\nfunction truncationMessage(id: IntegrationIds, readKind: TruncatedReadKind, cause: IncompleteReadCause): string {\n\tswitch (cause) {\n\t\tcase 'interrupted':\n\t\t\t// Deliberately does not name a mechanism. This fires both when a page was lost mid-drain and when a\n\t\t\t// drain that latched an earlier scope failure later stopped for its own reasons; \"was interrupted\"\n\t\t\t// would be false in the second. What is true in both is that something failed and results are gone.\n\t\t\treturn `${readKind} read for '${id}' did not complete; some results are missing and the read reported a failure.`;\n\t\tcase 'page-budget':\n\t\t\treturn `${readKind} read for '${id}' stopped at its page budget; more results can be read by raising it.`;\n\t\tcase 'exhausted':\n\t\t\treturn `${readKind} read for '${id}' was truncated and cannot be continued; results may be incomplete.`;\n\t\tcase 'scope-too-large':\n\t\t\t// Says what is true — the scope is larger than a read of it — rather than blaming the provider for a\n\t\t\t// ceiling that is ours, and names the remedy that exists, since no retry or budget has one.\n\t\t\treturn `${readKind} read for '${id}' covers more than one read can return; narrow the scope to read the rest.`;\n\t}\n\t// No `default`: `IncompleteReadCause` is declared in this file, so `noImplicitReturns` already fails the\n\t// build here if a cause is added without its own wording. (`collectionOmissionMessage`'s `satisfies never`\n\t// is not the same case — that union is the SDK's, and can widen under a dependency bump.)\n}\n\n/**\n * The one sentence every \"you asked for a narrowing this provider can't express\" refusal uses.\n *\n * Shared because the three callers below differ only in a noun: the wording is consumer-visible, and three\n * hand-written copies drift three ways — including the `(supported: …)` clause, which exists to avoid emitting an\n * empty parenthetical and has to be the same guard in all of them.\n */\nfunction unsupportedNarrowingMessage(id: IntegrationIds, noun: string, requested: string, supported: string): string {\n\treturn `The requested ${noun} (${requested}) are not supported by '${id}'${supported.length > 0 ? ` (supported: ${supported})` : ''}; skipped to avoid returning a wider result than requested.`;\n}\n\n/** Warning for an account-wide issue read whose requested filters the provider can't express server-side. */\nexport function unsupportedAccountWideIssueFiltersWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tfilters: IssueFilter[],\n): ProviderWarning {\n\tconst supported = providersMetadata[id]?.supportedAccountWideIssueFilters ?? [];\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tunsupportedNarrowingMessage(id, 'account-wide issue filters', filters.join(', '), supported.join(', ')),\n\t);\n}\n\n/** Warning for an account-wide PR read whose requested relationship union cannot be expressed exactly. */\nexport function unsupportedAccountWidePullRequestFiltersWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tfilters: PullRequestFilter[],\n): ProviderWarning {\n\tconst supported = providersMetadata[id]?.supportedAccountWidePullRequestFilters ?? [];\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tunsupportedNarrowingMessage(id, 'account-wide pull request filters', filters.join(', '), supported.join(', ')),\n\t);\n}\n\n/** Warning for a repo-scoped PR read whose requested filters the provider supports none of. */\nexport function unsupportedFiltersWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n): ProviderWarning {\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`The requested pull request filters are not supported by '${id}'; skipped to avoid returning unfiltered results.`,\n\t);\n}\n\n/**\n * The one sentence every `unusable-scope` refusal uses — the five surfaces that validate a search scope (both\n * searches, both count probes, and issue broadening) differ only in the noun and in whether they name a count\n * key, so the reason a scope is unusable is written once. `prefix` is what the count probes put in front of it\n * to name their key.\n *\n * NAMES the offending values, because which failure mode applied is not deducible from the value: a quote makes\n * `git\"kraken` a request for the real and different org `gitkraken`, a space makes `my org` a search of `my`\n * filtered by the text `org`, and a value of only quotes or whitespace leaves no scope at all. The caller is the\n * only layer that knows which scope it meant.\n *\n * Says how to fix it rather than what was stripped: a consumer cannot mirror this rule (unlike free-text\n * sanitizing, whose rules are published for exactly that), so \"pass the scope as the provider names it\" is the\n * actionable half.\n */\nexport function unusableSearchScopeMessage(noun: string, scopes: readonly string[], prefix?: string): string {\n\treturn `${prefix != null ? `${prefix}: ` : ''}The requested ${noun} (${scopes.map(s => JSON.stringify(s)).join(', ')}) cannot be used as given: a scope name cannot contain quotes, or an inner space or control character, which a query would drop or read as a second qualifier — searching a different scope, or none. Pass the scope exactly as the provider names it.`;\n}\n\n/** Warning for a filtered pull-request search the provider cannot run as requested. */\nexport function unsupportedPullRequestSearchCriteriaWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\trejection: PullRequestSearchCriteriaRejection,\n): ProviderWarning {\n\tlet message: string;\n\tswitch (rejection.reason) {\n\t\tcase 'unsupported-search':\n\t\t\tmessage = `Filtered pull request search is not supported by '${id}'; use \\`listPullRequestsPage\\` for its own pull request reads.`;\n\t\t\tbreak;\n\t\tcase 'unsupported-criteria': {\n\t\t\tconst supported = providersMetadata[id]?.supportedPullRequestSearch;\n\t\t\tmessage = unsupportedNarrowingMessage(\n\t\t\t\tid,\n\t\t\t\t'pull request search criteria',\n\t\t\t\trejection.criteria.join(', '),\n\t\t\t\tsupported != null ? describePullRequestSearchCapabilities(supported) : '',\n\t\t\t);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn unsupportedWarning(id, domain, connectionId, message);\n}\n\nfunction describePullRequestSearchCapabilities(capabilities: PullRequestSearchCapabilities): string {\n\treturn [\n\t\t...(capabilities.relationships.length ? [`relationships:${capabilities.relationships.join('|')}`] : []),\n\t\t...(capabilities.states.length ? [`states:${capabilities.states.join('|')}`] : []),\n\t\t...(capabilities.text ? ['text'] : []),\n\t\t...(capabilities.updatedAfter ? ['updatedAfter'] : []),\n\t\t...(capabilities.createdAfter ? ['createdAfter'] : []),\n\t\t...(capabilities.includeArchived ? ['includeArchived'] : []),\n\t\t...(capabilities.draft ? ['draft'] : []),\n\t\t...(capabilities.repositoryScope ? ['repository scope'] : []),\n\t\t...(capabilities.organizationScope ? ['organization scope'] : []),\n\t\t...(capabilities.sorts.length ? [`sorts:${capabilities.sorts.join('|')}`] : []),\n\t].join(', ');\n}\n\n/**\n * Warning for a filtered issue search whose criteria the provider can't serve — either it has no such search at\n * all, or it can't express what was asked for, or the set contradicts itself.\n *\n * One builder for all three because they are one decision from the caller's side (\"this search can't be run as\n * asked\") and the remedy differs only in what the message says. The rejection shape carries which case it is, so\n * the wording can't drift from the check that produced it (see {@link resolveIssueSearchCriteria}). The kind\n * follows the reason: the first two are `'unsupported'` capabilities; a contradictory set is caller input and\n * stays `'other'`.\n */\nexport function unsupportedIssueSearchCriteriaWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\trejection: IssueSearchCriteriaRejection,\n): ProviderWarning {\n\tlet message: string;\n\tswitch (rejection.reason) {\n\t\tcase 'unsupported-search':\n\t\t\tmessage = `Filtered issue search is not supported by '${id}'; use \\`listIssuesPage\\` for its own issue reads.`;\n\t\t\tbreak;\n\t\tcase 'unsupported-criteria': {\n\t\t\tconst supported = providersMetadata[id]?.supportedIssueSearch;\n\t\t\tmessage = unsupportedNarrowingMessage(\n\t\t\t\tid,\n\t\t\t\t'issue search criteria',\n\t\t\t\trejection.criteria.join(', '),\n\t\t\t\tsupported != null ? describeIssueSearchCapabilities(supported) : '',\n\t\t\t);\n\t\t\tbreak;\n\t\t}\n\t\tcase 'contradictory-relationships':\n\t\t\t// Caller input the caller fixes in the request itself, so `'other'` rather than `'unsupported'`.\n\t\t\treturn otherWarning(\n\t\t\t\tid,\n\t\t\t\tdomain,\n\t\t\t\tconnectionId,\n\t\t\t\t`The relationships \\`any-assignee\\` and \\`unassigned\\` are contradictory — they partition the scope between them, so no issue satisfies both; pass only one.`,\n\t\t\t);\n\t}\n\t// No `default`: the union is declared alongside the validator, so `noImplicitReturns` fails the build here if\n\t// a rejection reason is added without its own wording.\n\n\treturn unsupportedWarning(id, domain, connectionId, message);\n}\n\n/** The criteria a provider CAN express, for the \"supported: …\" half of a refusal message. */\nfunction describeIssueSearchCapabilities(capabilities: IssueSearchCapabilities): string {\n\tconst flags: [name: string, supported: boolean][] = [\n\t\t['text', capabilities.text],\n\t\t['labels', capabilities.labels],\n\t\t['milestone', capabilities.milestone],\n\t\t['updatedAfter', capabilities.updatedAfter],\n\t\t['createdAfter', capabilities.createdAfter],\n\t\t['withoutLinkedPullRequest', capabilities.withoutLinkedPullRequest],\n\t\t['state', capabilities.states],\n\t];\n\treturn [\n\t\t...capabilities.relationships.map(r => `relationships:${r}`),\n\t\t...flags.filter(([, supported]) => supported).map(([name]) => name),\n\t\t...capabilities.sorts.map(s => `sort:${s}`),\n\t].join(', ');\n}\n\n/**\n * Warning for an issue read asked to order by a key its provider can't express server-side.\n *\n * Separate from {@link unsupportedIssueSearchCriteriaWarning} because it serves the reads that take `sort` as an\n * OPTION rather than as a criterion — repo-scoped, account-wide, and issue-tracker — where there is no criteria\n * set to fold it into. The filtered search keeps reporting it as one more `unsupported-criteria` entry, so a\n * caller there still gets a single refusal listing everything at once.\n *\n * Takes the rejection the check produced rather than a re-read supported list: which of the two tables applies is\n * the caller's own branch (`repos` present or not), so reading it back here — or threading it by hand — would be\n * free to disagree with the check that refused.\n */\nexport function unsupportedIssueSortWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\trejection: UnsupportedIssueSortRejection,\n): ProviderWarning {\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tunsupportedNarrowingMessage(id, 'issue sort', rejection.requested, (rejection.supported ?? []).join(', ')),\n\t);\n}\n\n/**\n * Warning for a merged issue read asked to order by a key that no normalized issue carries.\n *\n * The distinction this exists to make is not about the provider: the provider orders by `priority` perfectly well\n * within ONE project. It is about the read, which fans out over several and merges the results here, where the\n * only fields available are the ones {@link IssueShape} models. Serving the concatenated per-scope runs would look\n * ordered without being so, which is why this refuses instead — and why the message says WHY, since the same key\n * against the same provider succeeds on a single-scope read.\n *\n * What a read fans out OVER differs by read, hence the `scope`: repositories, projects, or — where one query is\n * issued per requested relationship and the runs are unioned within a single project (Jira) — the filters\n * themselves. Naming the wrong one would tell a caller to narrow something that isn't what merged.\n */\nexport function unmergeableIssueSortWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tunmergeable: UnmergeableIssueSort,\n\tscope: 'repositories' | 'projects' | 'filters',\n): ProviderWarning {\n\t// Both forms are spelled out rather than depluralized at runtime: the message needs each, and\n\t// `'repositories'.replace(/s$/, '')` is `'repositorie'`.\n\tconst nouns = {\n\t\trepositories: { many: 'repositories', one: 'repository' },\n\t\tprojects: { many: 'projects', one: 'project' },\n\t\tfilters: { many: 'issue relationships', one: 'relationship' },\n\t}[scope];\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`Cannot order by '${unmergeable.requested}' across several ${nouns.many}: this read merges their results, and a normalized issue carries no such field to merge on. Read one ${nouns.one} at a time, or order by a date, title, comment or reaction count.`,\n\t);\n}\n\n/**\n * The omission for a filtered issue search that hit the provider's RESULT CEILING: more issues matched than the\n * provider will ever serve for one query, however it is paged.\n *\n * Distinct from {@link truncationWarning} because this is the one incompleteness this read can quantify —\n * `totalCount` is how many matched, `limit` how many are reachable — which is what lets a consumer say \"19.240\n * matched, showing the 1.000 most recently updated\" instead of a bare \"results may be incomplete\". `recovery:\n * 'none'` because the items past the ceiling are UNREACHABLE, not merely unfetched: no budget, no retry, and no\n * cursor would return them. Narrowing the criteria is the only way through, which is a decision for the caller.\n *\n * The message names the ORDER, and has to now that ordering is an option: the reachable window is the top `limit`\n * under that key, so \"the 1.000 most recent\" is a true sentence only while the key is the default one. The same\n * value goes on the omission, so a consumer wording its own sentence doesn't have to remember what it asked for.\n *\n * Returns `undefined` when the provider declares no ceiling, so the caller falls back to the generic wording\n * rather than reporting a limit it invented.\n */\nexport function issueSearchCapResultWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\ttotalCount: number | undefined,\n\tsort: IssueSorting,\n\t/** The provider proved the ceiling was reached without reporting how many matched; see `limitReached`. */\n\tlimitReached?: boolean,\n): ProviderWarning | undefined {\n\treturn searchCapResultWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t'Issue search',\n\t\tprovidersMetadata[id]?.issueSearchResultLimit,\n\t\ttotalCount,\n\t\tsort,\n\t\tlimitReached,\n\t);\n}\n\n/**\n * The shared body of the two ceiling warnings. Only a ceiling that was actually reached warns: either a reported\n * total above it, or a provider that proved the ceiling was reached but can't count past it — in which case the\n * omission carries the limit and the order but no `totalCount`, since any figure would be invented.\n */\nfunction searchCapResultWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\treadKind: 'Issue search' | 'Pull request search',\n\tlimit: number | undefined,\n\ttotalCount: number | undefined,\n\tsort: IssueSorting | PullRequestSorting,\n\tlimitReached: boolean | undefined,\n): ProviderWarning | undefined {\n\tif (limit == null) return undefined;\n\n\t// Below the ceiling this warning would be a false claim; the caller's truncation had another cause.\n\tconst counted = totalCount != null && totalCount > limit;\n\tif (!counted && limitReached !== true) return undefined;\n\n\tconst [field, direction] = sort.split(':');\n\tconst matched = counted ? `matched ${totalCount} results` : `matched more than ${limit} results`;\n\treturn {\n\t\t...otherWarning(\n\t\t\tid,\n\t\t\tdomain,\n\t\t\tconnectionId,\n\t\t\t`${readKind} ${matched}, but '${id}' serves at most ${limit}, ordered by ${field} ${direction}ending; narrow the search to read the rest.`,\n\t\t),\n\t\tomission: {\n\t\t\tkind: 'provider-limit',\n\t\t\t// The items past the ceiling can't be fetched by anything, so never offer a \"load more\".\n\t\t\trecovery: 'none',\n\t\t\tlimit: limit,\n\t\t\t...(counted ? { totalCount: totalCount } : {}),\n\t\t\tsort: sort,\n\t\t},\n\t};\n}\n\n/** Quantified omission for a pull-request search that exceeded the provider's reachable result window. */\nexport function pullRequestSearchCapResultWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\ttotalCount: number | undefined,\n\tsort: PullRequestSorting,\n\t/** See {@link issueSearchCapResultWarning}. */\n\tlimitReached?: boolean,\n): ProviderWarning | undefined {\n\t// Named order for the same reason as the issue twin: the reachable window is the first `limit` under THIS sort.\n\treturn searchCapResultWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t'Pull request search',\n\t\tprovidersMetadata[id]?.pullRequestSearchResultLimit,\n\t\ttotalCount,\n\t\tsort,\n\t\tlimitReached,\n\t);\n}\n\n/** Warning for a git host that doesn't expose issues on this surface (e.g. Bitbucket, deprecated in favor of Jira). */\nexport function issuesUnsupportedWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n): ProviderWarning {\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`Issues are not supported by '${id}'; use a dedicated issue integration (e.g. Jira) instead.`,\n\t);\n}\n"],"mappings":"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"}
@@ -21,8 +21,29 @@ export interface ConnectionStateChangeEvent {
21
21
  * provider, scope or request may. NOT unsupported (these stay `'other'`): malformed or contradictory caller
22
22
  * input the caller fixes in the request itself (a duplicate batch key, invalid target numbers, an empty owner,
23
23
  * an unusable scope), incompleteness or omission warnings, and unclassified errors.
24
+ *
25
+ * `'unreadable-items'`: the provider returned items this read could not read (a Jira issue whose fields the SDK cannot
26
+ * parse), so it left them out and read everything else in the warning's {@link ProviderWarning.scope}. It still fails
27
+ * the read (`fetchFailed`), since those items are missing, but retrying returns the same gap. On the reads that publish
28
+ * it (an issue tracker's project reads) it never hides a read that was cut short: a cut always comes with a warning of
29
+ * another kind. {@link ProviderWarning.unreadableItems} names the items left out.
24
30
  */
25
- export type ProviderWarningKind = 'auth' | 'rate-limit' | 'not-found' | 'no-connection' | 'unsupported' | 'other';
31
+ export type ProviderWarningKind = 'auth' | 'rate-limit' | 'not-found' | 'no-connection' | 'unsupported' | 'unreadable-items' | 'other';
32
+ /** The items an `'unreadable-items'` warning left out: see {@link ProviderWarning.unreadableItems}. */
33
+ export interface ProviderWarningUnreadableItems {
34
+ /**
35
+ * The provider's identifiers of the items left out (a Jira issue's key), without duplicates, in reported order. Read
36
+ * from the provider's prose, it may also hold an identifier that only appeared in an item's error text, never lack
37
+ * one it named: rely on an identifier's absence, not on its presence.
38
+ */
39
+ keys: string[];
40
+ /**
41
+ * Whether {@link keys} names every item left out. `false` when the provider named only some of them (Jira names at
42
+ * most ten per page), named one by position rather than by key, or reported them in a form this layer could not
43
+ * read, so an identifier absent from `keys` may still be one of them.
44
+ */
45
+ complete: boolean;
46
+ }
26
47
  /**
27
48
  * Why a read that SUCCEEDED still withheld results:
28
49
  * - `provider-limit`: the provider refuses to serve past a cap it enforces (GitHub search's 1,000-result
@@ -200,6 +221,12 @@ export interface ProviderWarning {
200
221
  * out, so treat a bare `kind: 'other'` warning as unclassified rather than as a proven failure.
201
222
  */
202
223
  omission?: ProviderWarningOmission;
224
+ /**
225
+ * Present exactly when {@link kind} is `'unreadable-items'`: which items the read left out because it could not read
226
+ * them. A consumer deciding whether an identifier is absent from the provider, rather than merely unread, must find
227
+ * it in no warning's `keys` and see `complete` on every one.
228
+ */
229
+ unreadableItems?: ProviderWarningUnreadableItems;
203
230
  /**
204
231
  * The keys of the batch targets this warning concerns: the target a per-target failure was refused or failed
205
232
  * for, or every target of an integration call that failed as a whole. So a caller that sends several callers'
@@ -414,8 +441,15 @@ export declare function reconcileOmissionsWithFailure(warnings: ProviderWarning[
414
441
  * Appends `warning` to `into` only when an equal warning (by provider/connection/domain/kind/message, plus the
415
442
  * structured scope, cause and omission when present) is absent. An equal warning already there merges its
416
443
  * {@link ProviderWarning.targetKeys} with the new one's instead: the union of both, or none when either concerns
417
- * the whole call. The existing warning is replaced, never mutated, since it may be shared.
444
+ * the whole call. Its {@link ProviderWarning.unreadableItems} merge too (see {@link mergeUnreadableItems}): two reads
445
+ * can report the same text for different items. The existing warning is replaced, never mutated, since it may be
446
+ * shared.
418
447
  */
419
448
  export declare function appendDedupedWarning(into: ProviderWarning[], warning: ProviderWarning): void;
449
+ /**
450
+ * The items two reports of unreadable items left out together: the union of their keys, complete only when both are.
451
+ * `existing` when `next` adds nothing to it.
452
+ */
453
+ export declare function mergeUnreadableItems(existing: ProviderWarningUnreadableItems | undefined, next: ProviderWarningUnreadableItems | undefined): ProviderWarningUnreadableItems | undefined;
420
454
  //#endregion
421
455
  //# sourceMappingURL=results.d.ts.map
@@ -123,7 +123,9 @@ function providerWarningKey(warning) {
123
123
  * Appends `warning` to `into` only when an equal warning (by provider/connection/domain/kind/message, plus the
124
124
  * structured scope, cause and omission when present) is absent. An equal warning already there merges its
125
125
  * {@link ProviderWarning.targetKeys} with the new one's instead: the union of both, or none when either concerns
126
- * the whole call. The existing warning is replaced, never mutated, since it may be shared.
126
+ * the whole call. Its {@link ProviderWarning.unreadableItems} merge too (see {@link mergeUnreadableItems}): two reads
127
+ * can report the same text for different items. The existing warning is replaced, never mutated, since it may be
128
+ * shared.
127
129
  */
128
130
  function appendDedupedWarning(into, warning) {
129
131
  const key = providerWarningKey(warning);
@@ -133,21 +135,39 @@ function appendDedupedWarning(into, warning) {
133
135
  return;
134
136
  }
135
137
  const existing = into[index];
136
- const existingKeys = existing.targetKeys;
137
- if (existingKeys == null) return;
138
- if (warning.targetKeys == null) {
139
- const { targetKeys: _, ...wholeCall } = existing;
140
- into[index] = wholeCall;
141
- return;
142
- }
143
- const added = warning.targetKeys.filter((k) => !existingKeys.includes(k));
144
- if (!added.length) return;
138
+ const targetKeys = mergeTargetKeys(existing.targetKeys, warning.targetKeys);
139
+ const unreadableItems = mergeUnreadableItems(existing.unreadableItems, warning.unreadableItems);
140
+ if (targetKeys === existing.targetKeys && unreadableItems === existing.unreadableItems) return;
141
+ const { targetKeys: _, ...rest } = existing;
145
142
  into[index] = {
146
- ...existing,
147
- targetKeys: [...existingKeys, ...new Set(added)]
143
+ ...rest,
144
+ ...targetKeys != null ? { targetKeys } : {},
145
+ ...unreadableItems != null ? { unreadableItems } : {}
146
+ };
147
+ }
148
+ /** The union of two warnings' target keys, or none when either concerns the whole call. `existing` when unchanged. */
149
+ function mergeTargetKeys(existing, next) {
150
+ if (existing == null || next == null) return void 0;
151
+ const added = next.filter((k) => !existing.includes(k));
152
+ if (!added.length) return existing;
153
+ return [...existing, ...new Set(added)];
154
+ }
155
+ /**
156
+ * The items two reports of unreadable items left out together: the union of their keys, complete only when both are.
157
+ * `existing` when `next` adds nothing to it.
158
+ */
159
+ function mergeUnreadableItems(existing, next) {
160
+ if (existing == null) return next;
161
+ if (next == null) return existing;
162
+ const added = next.keys.filter((k) => !existing.keys.includes(k));
163
+ const complete = existing.complete && next.complete;
164
+ if (!added.length && complete === existing.complete) return existing;
165
+ return {
166
+ keys: [...existing.keys, ...new Set(added)],
167
+ complete
148
168
  };
149
169
  }
150
170
  //#endregion
151
- export { appendDedupedWarning, collectionScopeKey, providerWarningMessage, reconcileOmissionsWithFailure, toProviderWarning };
171
+ export { appendDedupedWarning, collectionScopeKey, mergeUnreadableItems, providerWarningMessage, reconcileOmissionsWithFailure, toProviderWarning };
152
172
 
153
173
  //# sourceMappingURL=results.js.map