@gitkraken/core-gitlens 0.15.3 → 0.15.4

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 (66) hide show
  1. package/CHANGELOG.md +28 -1
  2. package/dist/git/cache.d.ts +24 -11
  3. package/dist/git/cache.js +44 -16
  4. package/dist/git/cache.js.map +1 -1
  5. package/dist/git/models/pullRequest.d.ts +7 -5
  6. package/dist/git/models/pullRequest.js.map +1 -1
  7. package/dist/git/models/reference.d.ts +8 -0
  8. package/dist/git/providers/branches.d.ts +43 -0
  9. package/dist/git/watching/changeEvent.d.ts +4 -2
  10. package/dist/git/watching/changeEvent.js +3 -1
  11. package/dist/git/watching/changeEvent.js.map +1 -1
  12. package/dist/git/watching/watchGroup.d.ts +3 -3
  13. package/dist/git/watching/watchGroup.js +1 -0
  14. package/dist/git/watching/watchGroup.js.map +1 -1
  15. package/dist/git/watching/watchService.js.map +1 -1
  16. package/dist/git/watching/watchSession.d.ts +2 -1
  17. package/dist/git/watching/watchSession.js +3 -2
  18. package/dist/git/watching/watchSession.js.map +1 -1
  19. package/dist/git/watching/watcherPatterns.js +1 -0
  20. package/dist/git/watching/watcherPatterns.js.map +1 -1
  21. package/dist/git/watching/worktreeSummary.d.ts +19 -3
  22. package/dist/git/watching/worktreeSummary.js +107 -17
  23. package/dist/git/watching/worktreeSummary.js.map +1 -1
  24. package/dist/git-cli/parsers/refParser.js +2 -1
  25. package/dist/git-cli/parsers/refParser.js.map +1 -1
  26. package/dist/git-cli/providers/branches.d.ts +8 -1
  27. package/dist/git-cli/providers/branches.js +84 -12
  28. package/dist/git-cli/providers/branches.js.map +1 -1
  29. package/dist/git-cli/providers/config.d.ts +32 -9
  30. package/dist/git-cli/providers/config.js +113 -24
  31. package/dist/git-cli/providers/config.js.map +1 -1
  32. package/dist/plus/integrations/models/gitHostIntegration.d.ts +29 -14
  33. package/dist/plus/integrations/models/gitHostIntegration.js +41 -19
  34. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
  35. package/dist/plus/integrations/providers/azure/azure.d.ts +16 -16
  36. package/dist/plus/integrations/providers/azure/azure.js +45 -25
  37. package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
  38. package/dist/plus/integrations/providers/azure/azure.utils.d.ts +12 -0
  39. package/dist/plus/integrations/providers/azure/azure.utils.js +17 -1
  40. package/dist/plus/integrations/providers/azure/azure.utils.js.map +1 -1
  41. package/dist/plus/integrations/providers/azure/search.d.ts +4 -0
  42. package/dist/plus/integrations/providers/azure/search.js +46 -2
  43. package/dist/plus/integrations/providers/azure/search.js.map +1 -1
  44. package/dist/plus/integrations/providers/azure/workItemSearch.d.ts +11 -0
  45. package/dist/plus/integrations/providers/azure/workItemSearch.js +54 -0
  46. package/dist/plus/integrations/providers/azure/workItemSearch.js.map +1 -0
  47. package/dist/plus/integrations/providers/azure/workItemStateCategories.d.ts +2 -2
  48. package/dist/plus/integrations/providers/azure/workItemStateCategories.js +19 -15
  49. package/dist/plus/integrations/providers/azure/workItemStateCategories.js.map +1 -1
  50. package/dist/plus/integrations/providers/azureDevOps.d.ts +33 -20
  51. package/dist/plus/integrations/providers/azureDevOps.js +109 -48
  52. package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
  53. package/dist/plus/integrations/providers/azureDevOpsServer.d.ts +16 -47
  54. package/dist/plus/integrations/providers/azureDevOpsServer.js +52 -91
  55. package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
  56. package/dist/plus/integrations/providers/models.d.ts +5 -0
  57. package/dist/plus/integrations/providers/models.js +14 -3
  58. package/dist/plus/integrations/providers/models.js.map +1 -1
  59. package/dist/plus/integrations/reads/drains.d.ts +2 -2
  60. package/dist/plus/integrations/reads/drains.js +4 -4
  61. package/dist/plus/integrations/reads/drains.js.map +1 -1
  62. package/dist/plus/integrations/reads/issues.js +7 -4
  63. package/dist/plus/integrations/reads/issues.js.map +1 -1
  64. package/dist/plus/integrations/reads/resolveRepository.js +5 -5
  65. package/dist/plus/integrations/reads/resolveRepository.js.map +1 -1
  66. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"drains.js","names":[],"sources":["../../../../../plus/integrations/src/reads/drains.ts"],"sourcesContent":["import type { CollectionMetadata } from '@gitkraken/provider-apis';\nimport type { PullRequest, PullRequestStateFilter } from '@gitlens/git/models/pullRequest.js';\nimport { defaultPullRequestSort } from '@gitlens/git/models/pullRequest.js';\nimport type { ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { supportedOrderedCloudIntegrationIds } from '../constants.js';\nimport type { ProviderSweepTarget, PullRequestSweepOptions } from '../manager.js';\nimport type { GitHostIntegration } from '../models/gitHostIntegration.js';\nimport type { IntegrationResult } from '../models/integration.js';\nimport type { IssuesIntegration } from '../models/issuesIntegration.js';\nimport type { PullRequestFilter } from '../providerFilters.js';\nimport type { ProviderPullRequest, ProviderReposInput, ProviderRepository } from '../providers/models.js';\nimport { getProviderPullRequestIdentity } from '../providers/models.js';\nimport type { ProviderWarning } from '../results.js';\nimport { appendDedupedWarning, reconcileOmissionsWithFailure, toProviderWarning } from '../results.js';\nimport { isIssuesHostIntegrationId } from '../utils/integration.utils.js';\nimport {\n\tcappedBesideBudgetWarning,\n\tnoConnectionWarning,\n\tpullRequestSearchCapResultWarning,\n\ttruncationWarning,\n} from './warnings.js';\n\n/**\n * The all-pages reads: a provider read run repeatedly until it runs out of pages, as opposed to the single-page\n * reads the facade's paged surface exposes. `sweepPullRequests` and `broadenIssues` are built on these.\n *\n * A drain's job is to keep going where a single page would stop, so what distinguishes it is its failure\n * bookkeeping rather than its loop: it must return the prefix it did read plus enough signal for the caller to\n * know the result is not authoritative — `truncated` when pages remain unread (a page backstop, or a provider\n * claiming another page while handing back no usable cursor), `fetchFailed` when a page or scope failed\n * outright. Discarding the prefix on a late failure, or returning it as if complete, are the two mistakes these\n * exist to prevent.\n */\n\n/**\n * Runs a result-returning read and captures failure as a neutral {@link ProviderWarning} rather than\n * letting it throw or silently vanish. Handles both a returned `{ error }` (the read cores' contract)\n * and a hard throw; a soft warning (`{ value, error }`) yields the value *and* a warning.\n */\nexport async function runCaptured<T>(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tfn: () => Promise<IntegrationResult<T>>,\n\toptions?: { warnOnMissingSession?: boolean },\n): Promise<{ value?: T; warning?: ProviderWarning }> {\n\ttry {\n\t\tconst result = await fn();\n\t\tif (result == null) {\n\t\t\t// The read core returns undefined only when it couldn't resolve a session. For a per-connection\n\t\t\t// or explicit-domain read that means the requested target is gone or its authentication is invalid,\n\t\t\t// which must not be reported as an empty account. The untargeted primary path legitimately yields\n\t\t\t// nothing when the provider isn't connected, so leave it as an empty result.\n\t\t\treturn connectionId != null || options?.warnOnMissingSession\n\t\t\t\t? { warning: noConnectionWarning(id, domain, connectionId) }\n\t\t\t\t: {};\n\t\t}\n\t\tif (result.error != null) {\n\t\t\treturn { value: result.value, warning: toProviderWarning(id, domain, connectionId, result.error) };\n\t\t}\n\t\treturn { value: result.value };\n\t} catch (ex) {\n\t\treturn { warning: toProviderWarning(id, domain, connectionId, ex) };\n\t}\n}\n\n/**\n * {@link runCaptured} over an issue tracker's resource (site, organization, board) discovery. A read that resolved a\n * session yet answered with no data and no error carries `completeness: 'unknown'` metadata, so it reads as an\n * incomplete discovery rather than as an account with no resources, which `runCaptured` alone can't tell apart from\n * an untargeted read with no session.\n */\nexport async function discoverResources(\n\tintegration: IssuesIntegration,\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\toptions?: { warnOnMissingSession?: boolean },\n): Promise<{ value?: ResourceDescriptor[]; warning?: ProviderWarning; metadata?: CollectionMetadata }> {\n\tlet noData = false;\n\tconst captured = await runCaptured(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tasync () => {\n\t\t\tconst result = await integration.getResourcesForUserResult(connectionId);\n\t\t\tnoData = result != null && result.error == null && result.value == null;\n\t\t\treturn result;\n\t\t},\n\t\toptions,\n\t);\n\treturn noData ? { ...captured, metadata: { completeness: 'unknown' } } : captured;\n}\n\n/**\n * Drains every page of the user's pull requests for one git-host integration, threading the opaque\n * next-cursor the provider returns (so it works for both page- and cursor-based hosts). Stops at\n * `maxPages` (marking `truncated`) or on a hard read failure (marking `fetchFailed`), keeping the\n * pages fetched so far. A soft warning (`{ value, error }`) is recorded but the drain continues.\n */\nexport async function drainPullRequests(\n\tintegration: GitHostIntegration,\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\trepos: ProviderReposInput,\n\tstate: PullRequestStateFilter[] | undefined,\n\tfilters: PullRequestFilter[] | undefined,\n\t/**\n\t * Breadth opt-ins that only the account-wide read can express. Grouped rather than passed positionally\n\t * because they are same-typed neighbors a transposition would silently swap, and picked from the sweep\n\t * options rather than restated so a third opt-in is declared once.\n\t */\n\taccountWideOptions: Pick<PullRequestSweepOptions, 'includeReviewRequested' | 'includeReviews'> | undefined,\n\tconnectionId: string | undefined,\n\tmaxPages: number,\n\tattributeUnavailableProvider: boolean,\n): Promise<{\n\titems: (ProviderPullRequest | PullRequest)[];\n\twarnings: ProviderWarning[];\n\tfetchFailed: boolean;\n\ttruncated: boolean;\n\tfailedProvider: boolean;\n}> {\n\tconst items: (ProviderPullRequest | PullRequest)[] = [];\n\tconst itemIndexByIdentity = new Map<string, number>();\n\tconst warnings: ProviderWarning[] = [];\n\tlet cursor: string | undefined;\n\tlet page = 0;\n\t// SDK metadata failures across pages mean the collection is incomplete even when no page threw; carry\n\t// this through the terminal returns instead of resetting it to false at the last page.\n\tlet fetchFailed = false;\n\tlet truncated = false;\n\t// A page the provider capped or couldn't vouch for, and which no other warning already explains — decides\n\t// whether this drain raises one of its own for that part.\n\tlet unexplainedTruncation = false;\n\t// The largest match count reported on a truncated page, from which the drain derives the provider's cap.\n\tlet truncatedTotalCount: number | undefined;\n\tconst capWarning = (): ProviderWarning | undefined =>\n\t\tpullRequestSearchCapResultWarning(id, domain, connectionId, truncatedTotalCount, defaultPullRequestSort);\n\tconst appendTruncationWarnings = (cause: 'interrupted' | 'exhausted'): void => {\n\t\tconst cap = capWarning();\n\t\tif (cap != null) {\n\t\t\tappendDedupedWarning(warnings, cap);\n\t\t}\n\t\t// A cap explains the missing tail of a read that succeeded; a failed read still owes its own warning.\n\t\tif (unexplainedTruncation && (cap == null || cause === 'interrupted')) {\n\t\t\tappendDedupedWarning(warnings, truncationWarning(id, domain, connectionId, 'Pull request', cause));\n\t\t}\n\t};\n\n\t// With no repos this is an account-wide \"my PRs\" sweep. The repo-scoped core rejects an empty `repos`\n\t// input, so read the provider-native account-wide core instead.\n\tconst accountWide = repos.length === 0;\n\t// The breadth opt-ins are only expressible on the account-wide core, so gate them here — the single place\n\t// that already knows which core it is about to call.\n\tconst includeReviewRequested = accountWide && (accountWideOptions?.includeReviewRequested ?? false);\n\tconst includeReviews = accountWide && (accountWideOptions?.includeReviews ?? false);\n\t/** Every cursor already followed, so a provider that cycles them can't keep the drain walking in circles. */\n\tconst seenCursors = new Set<string>();\n\n\tfor (;;) {\n\t\tpage++;\n\t\t// Snapshot the mutable loop cursor so the read closure doesn't capture a later-reassigned value.\n\t\tconst pageCursor = cursor;\n\t\tconst { value, warning } = await runCaptured(id, domain, connectionId, () =>\n\t\t\taccountWide\n\t\t\t\t? integration.getMyPullRequestsForUserResult(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstate: state,\n\t\t\t\t\t\t\tcursor: pageCursor,\n\t\t\t\t\t\t\tincludeReviewRequested: includeReviewRequested,\n\t\t\t\t\t\t\tfilters: filters,\n\t\t\t\t\t\t\t// Lite by default; the full projection (reviews) only when a caller opts in.\n\t\t\t\t\t\t\tsummary: !includeReviews,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t)\n\t\t\t\t: integration.getMyPullRequestsForReposResult(\n\t\t\t\t\t\trepos,\n\t\t\t\t\t\t{ state: state, filters: filters, cursor: pageCursor },\n\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t),\n\t\t);\n\t\tif (warning != null) {\n\t\t\tappendDedupedWarning(warnings, warning);\n\t\t}\n\t\tif (value == null) {\n\t\t\t// An implicit sweep may silently skip a provider that has no session before it yields data.\n\t\t\t// Once a page has been returned, however, losing that session leaves an unread tail and must\n\t\t\t// be attributed even when the provider wasn't explicitly requested.\n\t\t\tconst sessionLostAfterProgress = warning == null && page > 1;\n\t\t\tconst unavailable = warning == null && (attributeUnavailableProvider || sessionLostAfterProgress);\n\t\t\tif (unavailable) {\n\t\t\t\tappendDedupedWarning(warnings, noConnectionWarning(id, domain, connectionId));\n\t\t\t}\n\t\t\t// `warning` set → a hard read failure (incomplete items); otherwise not connected / no session.\n\t\t\tconst failed = fetchFailed || warning != null || unavailable;\n\t\t\t// A cap seen on an earlier page still left results out, so it is still worth saying — but as part\n\t\t\t// of a read that failed, never as an omission. (`failed` is always true here when anything was\n\t\t\t// latched: reaching this exit past page 1 means a later page was lost.)\n\t\t\tappendTruncationWarnings(failed ? 'interrupted' : 'exhausted');\n\t\t\t// An earlier page may already have emitted an omission before this one died; it asserts the read\n\t\t\t// succeeded, which is no longer true.\n\t\t\treconcileOmissionsWithFailure(warnings, failed);\n\t\t\treturn {\n\t\t\t\titems: items,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: failed,\n\t\t\t\ttruncated: truncated || sessionLostAfterProgress,\n\t\t\t\t// Only a top-level first-page rejection means the provider itself failed. A later-page or\n\t\t\t\t// per-scope failure still yielded a usable provider slice and stays represented separately.\n\t\t\t\tfailedProvider: page === 1 && (warning != null || unavailable),\n\t\t\t};\n\t\t}\n\n\t\t// Composite account-wide queries can surface the same PR on different relationship/state pages\n\t\t// (for example authored + review-requested, or closed + merged). Keep the first stable position but\n\t\t// replace its value with the latest representation so a later, richer merged state wins. Repository\n\t\t// identity is what keys a row (see `getProviderPullRequestIdentity`), falling back to the canonical\n\t\t// URL; a row with neither stays unkeyed and is retained so unrelated incomplete rows are never\n\t\t// collapsed.\n\t\tfor (const pullRequest of value.values) {\n\t\t\tconst identity = getProviderPullRequestIdentity(pullRequest);\n\t\t\tif (identity == null) {\n\t\t\t\titems.push(pullRequest);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst existingIndex = itemIndexByIdentity.get(identity);\n\t\t\tif (existingIndex == null) {\n\t\t\t\titemIndexByIdentity.set(identity, items.length);\n\t\t\t\titems.push(pullRequest);\n\t\t\t} else {\n\t\t\t\titems[existingIndex] = pullRequest;\n\t\t\t}\n\t\t}\n\n\t\t// Assess this page's SDK metadata: append scope-aware warnings (deduped across pages), and remember\n\t\t// whether a structured failure or incompleteness occurred.\n\t\tconst assessment = mergeAssessmentInto(warnings, id, domain, connectionId, value.metadata);\n\t\tfetchFailed = fetchFailed || assessment.fetchFailed;\n\t\tconst pageTruncated =\n\t\t\t(value as { truncated?: boolean }).truncated === true ||\n\t\t\tvalue.paging?.truncated === true ||\n\t\t\tassessment.truncated;\n\t\ttruncated = truncated || pageTruncated;\n\t\t// Whether this drain owes its OWN warning for a capped page — `mergeAssessmentInto` has already appended\n\t\t// one when the fact came from SDK metadata, and repeating it would be noise.\n\t\tunexplainedTruncation = unexplainedTruncation || (pageTruncated && !assessment.truncated);\n\t\t// Only the account-wide read reports a search match count here; the repo-scoped one reports the item\n\t\t// total, which no cap is derived from. Latched even when SDK metadata explains the page: a composite page\n\t\t// reports a failed or cursorless sibling facet there, which says nothing about the cap another reached.\n\t\tif (accountWide && pageTruncated && value.paging?.totalCount != null) {\n\t\t\ttruncatedTotalCount = Math.max(truncatedTotalCount ?? 0, value.paging.totalCount);\n\t\t}\n\n\t\tif (!(value.paging?.more ?? false)) {\n\t\t\t// A read that can't confirm completeness (single-page provider reads with no `hasNextPage`)\n\t\t\t// sets `paging.truncated`; propagate it (and any top-level `truncated` and SDK incompleteness)\n\t\t\t// so the sweep doesn't claim an all-pages result.\n\t\t\tappendTruncationWarnings(fetchFailed ? 'interrupted' : 'exhausted');\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn {\n\t\t\t\titems: items,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: fetchFailed,\n\t\t\t\ttruncated: truncated,\n\t\t\t\tfailedProvider: false,\n\t\t\t};\n\t\t}\n\n\t\t// Resolve the continuation BEFORE deciding why the drain stops. `page-budget` claims the missing items\n\t\t// are reachable, which is only true with a usable cursor in hand — and a provider can report another\n\t\t// page while handing back none (Bitbucket Server does, when it omits `nextPageStart`). Checking the\n\t\t// budget first would label that unreachable tail as merely unfetched, so raising `maxPages` would\n\t\t// return the identical set.\n\t\tconst nextCursor = value.paging?.cursor;\n\t\t// A cursor already used isn't a continuation either — following it refetches a page we have, and a\n\t\t// provider that cycles (A→B→A) would otherwise burn the whole budget and then be reported as merely\n\t\t// out of budget. Tracked as a SET rather than compared one-back, matching the SDK's own `followCursors`:\n\t\t// `drainToRequestedPage` and `collectProviderPagedResult` compare only the previous cursor, which a\n\t\t// cycle slips past. That is tolerable there and not here, because only this drain reports `page-budget`.\n\t\tconst continuable = nextCursor != null && nextCursor !== '{}' && !seenCursors.has(nextCursor);\n\t\tif (!continuable || page >= maxPages) {\n\t\t\t// A usable cursor in hand means `page-budget`, even when an earlier page was capped: a composite\n\t\t\t// read caps its facets independently, so one facet reaching the provider's ceiling leaves its\n\t\t\t// siblings' cursors live, and raising the budget returns more of them. What no budget returns is the\n\t\t\t// capped part, so that is reported BESIDE the budget stop rather than in place of it — unless an SDK\n\t\t\t// omission already says so. Neither warning then misstates the part the other describes.\n\t\t\tconst budgetStop = !fetchFailed && continuable;\n\t\t\tappendDedupedWarning(\n\t\t\t\twarnings,\n\t\t\t\ttruncationWarning(\n\t\t\t\t\tid,\n\t\t\t\t\tdomain,\n\t\t\t\t\tconnectionId,\n\t\t\t\t\t'Pull request',\n\t\t\t\t\tfetchFailed ? 'interrupted' : budgetStop ? 'page-budget' : 'exhausted',\n\t\t\t\t),\n\t\t\t);\n\t\t\tconst cap = capWarning();\n\t\t\tif (cap != null) {\n\t\t\t\tappendDedupedWarning(warnings, cap);\n\t\t\t} else if (budgetStop && unexplainedTruncation) {\n\t\t\t\tappendDedupedWarning(warnings, cappedBesideBudgetWarning(id, domain, connectionId, 'Pull request'));\n\t\t\t}\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn {\n\t\t\t\titems: items,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: fetchFailed,\n\t\t\t\ttruncated: true,\n\t\t\t\tfailedProvider: false,\n\t\t\t};\n\t\t}\n\n\t\tseenCursors.add(nextCursor);\n\t\tcursor = nextCursor;\n\t}\n}\n\n/**\n * Drains every page of repositories under an org for one git-host integration, threading the opaque\n * next-cursor the provider returns. Stops at `maxPages` (marking `truncated`) or on a hard read failure\n * (marking `fetchFailed`), keeping the pages fetched so far.\n */\nexport async function drainRepositories(\n\tintegration: GitHostIntegration,\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\torg: string,\n\tproject: string | undefined,\n\tconnectionId: string | undefined,\n\tmaxPages: number,\n): Promise<{\n\trepos: ProviderRepository[];\n\twarnings: ProviderWarning[];\n\tfetchFailed: boolean;\n\ttruncated: boolean;\n}> {\n\tconst repos: ProviderRepository[] = [];\n\tconst warnings: ProviderWarning[] = [];\n\tlet fetchFailed = false;\n\tlet truncated = false;\n\tlet cursor: string | undefined;\n\tlet page = 0;\n\n\tfor (;;) {\n\t\tpage++;\n\t\tconst pageCursor = cursor;\n\t\tconst { value, warning } = await runCaptured(\n\t\t\tid,\n\t\t\tdomain,\n\t\t\tconnectionId,\n\t\t\t() =>\n\t\t\t\tintegration.getRepositoriesForOrgResult(org, {\n\t\t\t\t\tproject: project,\n\t\t\t\t\tcursor: pageCursor,\n\t\t\t\t\tconnectionId: connectionId,\n\t\t\t\t}),\n\t\t\t{ warnOnMissingSession: true },\n\t\t);\n\t\tif (warning != null) {\n\t\t\t// Deduped, like every other accumulation here: a soft warning (`{ value, error }`) repeats verbatim on\n\t\t\t// each page that hits the same condition, and the array's contract is that no two entries are equal.\n\t\t\tappendDedupedWarning(warnings, warning);\n\t\t}\n\t\tif (value == null) {\n\t\t\tconst interruptedAfterProgress = page > 1;\n\t\t\tif (interruptedAfterProgress && warning == null) {\n\t\t\t\t// Not a backstop: the read was cut short mid-drain, which is why it also sets `fetchFailed`\n\t\t\t\t// below. A retry may complete it, so this must not claim the succeeded-but-capped omission.\n\t\t\t\tappendDedupedWarning(\n\t\t\t\t\twarnings,\n\t\t\t\t\ttruncationWarning(id, domain, connectionId, 'Repository', 'interrupted'),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst failed = fetchFailed || warning != null || interruptedAfterProgress;\n\t\t\t// An SDK omission from an earlier page asserts the read succeeded; this one didn't.\n\t\t\treconcileOmissionsWithFailure(warnings, failed);\n\t\t\treturn {\n\t\t\t\trepos: repos,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: failed,\n\t\t\t\ttruncated: truncated || interruptedAfterProgress,\n\t\t\t};\n\t\t}\n\n\t\trepos.push(...value.values);\n\t\tconst assessment = mergeAssessmentInto(warnings, id, domain, connectionId, value.metadata);\n\t\tfetchFailed = fetchFailed || assessment.fetchFailed;\n\t\ttruncated = truncated || value.truncated === true || value.paging?.truncated === true || assessment.truncated;\n\t\tif (!(value.paging?.more ?? false)) {\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn {\n\t\t\t\trepos: repos,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: fetchFailed,\n\t\t\t\ttruncated: truncated,\n\t\t\t};\n\t\t}\n\n\t\tif (page >= maxPages) {\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn { repos: repos, warnings: warnings, fetchFailed: fetchFailed, truncated: true };\n\t\t}\n\n\t\tconst nextCursor = value.paging?.cursor;\n\t\tif (nextCursor == null || nextCursor === '{}') {\n\t\t\t// Provider says there is more but didn't return a usable cursor; stop rather than refetch the same page.\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn { repos: repos, warnings: warnings, fetchFailed: fetchFailed, truncated: true };\n\t\t}\n\n\t\tcursor = nextCursor;\n\t}\n}\n\nexport function resolvePullRequestSweepTargets(options: PullRequestSweepOptions | undefined): {\n\ttargets: readonly ProviderSweepTarget[];\n\tattributeUnavailableProviders: boolean;\n} {\n\tif (options?.targets != null) {\n\t\tif (options.providerIds != null || options.connectionId != null) {\n\t\t\tthrow new TypeError(\"Pull request sweep 'targets' cannot be combined with 'providerIds' or 'connectionId'\");\n\t\t}\n\n\t\tconst seenProviderIds = new Set<IntegrationIds>();\n\t\tfor (const target of options.targets) {\n\t\t\tif (seenProviderIds.has(target.providerId)) {\n\t\t\t\tthrow new TypeError(\n\t\t\t\t\t`Pull request sweep targets must contain at most one target per provider; duplicate '${target.providerId}'`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tseenProviderIds.add(target.providerId);\n\t\t}\n\n\t\treturn { targets: options.targets, attributeUnavailableProviders: true };\n\t}\n\n\tconst providerIds =\n\t\toptions?.providerIds ?? supportedOrderedCloudIntegrationIds.filter(id => !isIssuesHostIntegrationId(id));\n\tconst connectionId = providerIds.length === 1 ? options?.connectionId : undefined;\n\treturn {\n\t\ttargets: providerIds.map(providerId => ({ providerId: providerId, connectionId: connectionId })),\n\t\tattributeUnavailableProviders: options?.providerIds != null,\n\t};\n}\n\nexport async function getCurrentAccountIdentity(\n\tintegration: GitHostIntegration,\n\tconnectionId: string | undefined,\n): Promise<{ id: string; username?: string } | undefined> {\n\ttry {\n\t\tconst account = await integration.getCurrentAccount({ connectionId: connectionId });\n\t\treturn account != null ? { id: account.id, username: account.username } : undefined;\n\t} catch {\n\t\t// Authorship is optional enrichment; don't turn a successful PR read into a failure if identity lookup fails.\n\t\treturn undefined;\n\t}\n}\n\n/**\n * The current account to map each raw row against, in order (see {@link GitHostIntegration.getPullRequestViewers}).\n * Rows with no author can't be matched, so a read of only those resolves nothing; a failed lookup leaves every row's\n * authorship unknown rather than failing the read.\n */\nexport async function getPullRequestViewers(\n\tintegration: GitHostIntegration,\n\tconnectionId: string | undefined,\n\tpullRequests: readonly (ProviderPullRequest | PullRequest)[],\n): Promise<({ id: string; username?: string } | undefined)[]> {\n\tif (!pullRequests.some(pr => pr.author != null)) return pullRequests.map(() => undefined);\n\n\ttry {\n\t\treturn await integration.getPullRequestViewers(pullRequests, connectionId);\n\t} catch {\n\t\treturn pullRequests.map(() => undefined);\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"drains.js","names":[],"sources":["../../../../../plus/integrations/src/reads/drains.ts"],"sourcesContent":["import type { CollectionMetadata } from '@gitkraken/provider-apis';\nimport type { PullRequest, PullRequestStateFilter } from '@gitlens/git/models/pullRequest.js';\nimport { defaultPullRequestSort } from '@gitlens/git/models/pullRequest.js';\nimport type { ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { supportedOrderedCloudIntegrationIds } from '../constants.js';\nimport type { ProviderSweepTarget, PullRequestSweepOptions } from '../manager.js';\nimport type { GitHostIntegration } from '../models/gitHostIntegration.js';\nimport type { IntegrationResult } from '../models/integration.js';\nimport type { IssuesIntegration } from '../models/issuesIntegration.js';\nimport type { PullRequestFilter } from '../providerFilters.js';\nimport type { ProviderPullRequest, ProviderReposInput, ProviderRepository } from '../providers/models.js';\nimport { getProviderPullRequestIdentity, pullRequestNamesPeople } from '../providers/models.js';\nimport type { ProviderWarning } from '../results.js';\nimport { appendDedupedWarning, reconcileOmissionsWithFailure, toProviderWarning } from '../results.js';\nimport { isIssuesHostIntegrationId } from '../utils/integration.utils.js';\nimport {\n\tcappedBesideBudgetWarning,\n\tnoConnectionWarning,\n\tpullRequestSearchCapResultWarning,\n\ttruncationWarning,\n} from './warnings.js';\n\n/**\n * The all-pages reads: a provider read run repeatedly until it runs out of pages, as opposed to the single-page\n * reads the facade's paged surface exposes. `sweepPullRequests` and `broadenIssues` are built on these.\n *\n * A drain's job is to keep going where a single page would stop, so what distinguishes it is its failure\n * bookkeeping rather than its loop: it must return the prefix it did read plus enough signal for the caller to\n * know the result is not authoritative — `truncated` when pages remain unread (a page backstop, or a provider\n * claiming another page while handing back no usable cursor), `fetchFailed` when a page or scope failed\n * outright. Discarding the prefix on a late failure, or returning it as if complete, are the two mistakes these\n * exist to prevent.\n */\n\n/**\n * Runs a result-returning read and captures failure as a neutral {@link ProviderWarning} rather than\n * letting it throw or silently vanish. Handles both a returned `{ error }` (the read cores' contract)\n * and a hard throw; a soft warning (`{ value, error }`) yields the value *and* a warning.\n */\nexport async function runCaptured<T>(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tfn: () => Promise<IntegrationResult<T>>,\n\toptions?: { warnOnMissingSession?: boolean },\n): Promise<{ value?: T; warning?: ProviderWarning }> {\n\ttry {\n\t\tconst result = await fn();\n\t\tif (result == null) {\n\t\t\t// The read core returns undefined only when it couldn't resolve a session. For a per-connection\n\t\t\t// or explicit-domain read that means the requested target is gone or its authentication is invalid,\n\t\t\t// which must not be reported as an empty account. The untargeted primary path legitimately yields\n\t\t\t// nothing when the provider isn't connected, so leave it as an empty result.\n\t\t\treturn connectionId != null || options?.warnOnMissingSession\n\t\t\t\t? { warning: noConnectionWarning(id, domain, connectionId) }\n\t\t\t\t: {};\n\t\t}\n\t\tif (result.error != null) {\n\t\t\treturn { value: result.value, warning: toProviderWarning(id, domain, connectionId, result.error) };\n\t\t}\n\t\treturn { value: result.value };\n\t} catch (ex) {\n\t\treturn { warning: toProviderWarning(id, domain, connectionId, ex) };\n\t}\n}\n\n/**\n * {@link runCaptured} over an issue tracker's resource (site, organization, board) discovery. A read that resolved a\n * session yet answered with no data and no error carries `completeness: 'unknown'` metadata, so it reads as an\n * incomplete discovery rather than as an account with no resources, which `runCaptured` alone can't tell apart from\n * an untargeted read with no session.\n */\nexport async function discoverResources(\n\tintegration: IssuesIntegration,\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\toptions?: { warnOnMissingSession?: boolean },\n): Promise<{ value?: ResourceDescriptor[]; warning?: ProviderWarning; metadata?: CollectionMetadata }> {\n\tlet noData = false;\n\tconst captured = await runCaptured(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tasync () => {\n\t\t\tconst result = await integration.getResourcesForUserResult(connectionId);\n\t\t\tnoData = result != null && result.error == null && result.value == null;\n\t\t\treturn result;\n\t\t},\n\t\toptions,\n\t);\n\treturn noData ? { ...captured, metadata: { completeness: 'unknown' } } : captured;\n}\n\n/**\n * Drains every page of the user's pull requests for one git-host integration, threading the opaque\n * next-cursor the provider returns (so it works for both page- and cursor-based hosts). Stops at\n * `maxPages` (marking `truncated`) or on a hard read failure (marking `fetchFailed`), keeping the\n * pages fetched so far. A soft warning (`{ value, error }`) is recorded but the drain continues.\n */\nexport async function drainPullRequests(\n\tintegration: GitHostIntegration,\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\trepos: ProviderReposInput,\n\tstate: PullRequestStateFilter[] | undefined,\n\tfilters: PullRequestFilter[] | undefined,\n\t/**\n\t * Breadth opt-ins that only the account-wide read can express. Grouped rather than passed positionally\n\t * because they are same-typed neighbors a transposition would silently swap, and picked from the sweep\n\t * options rather than restated so a third opt-in is declared once.\n\t */\n\taccountWideOptions: Pick<PullRequestSweepOptions, 'includeReviewRequested' | 'includeReviews'> | undefined,\n\tconnectionId: string | undefined,\n\tmaxPages: number,\n\tattributeUnavailableProvider: boolean,\n): Promise<{\n\titems: (ProviderPullRequest | PullRequest)[];\n\twarnings: ProviderWarning[];\n\tfetchFailed: boolean;\n\ttruncated: boolean;\n\tfailedProvider: boolean;\n}> {\n\tconst items: (ProviderPullRequest | PullRequest)[] = [];\n\tconst itemIndexByIdentity = new Map<string, number>();\n\tconst warnings: ProviderWarning[] = [];\n\tlet cursor: string | undefined;\n\tlet page = 0;\n\t// SDK metadata failures across pages mean the collection is incomplete even when no page threw; carry\n\t// this through the terminal returns instead of resetting it to false at the last page.\n\tlet fetchFailed = false;\n\tlet truncated = false;\n\t// A page the provider capped or couldn't vouch for, and which no other warning already explains — decides\n\t// whether this drain raises one of its own for that part.\n\tlet unexplainedTruncation = false;\n\t// The largest match count reported on a truncated page, from which the drain derives the provider's cap.\n\tlet truncatedTotalCount: number | undefined;\n\tconst capWarning = (): ProviderWarning | undefined =>\n\t\tpullRequestSearchCapResultWarning(id, domain, connectionId, truncatedTotalCount, defaultPullRequestSort);\n\tconst appendTruncationWarnings = (cause: 'interrupted' | 'exhausted'): void => {\n\t\tconst cap = capWarning();\n\t\tif (cap != null) {\n\t\t\tappendDedupedWarning(warnings, cap);\n\t\t}\n\t\t// A cap explains the missing tail of a read that succeeded; a failed read still owes its own warning.\n\t\tif (unexplainedTruncation && (cap == null || cause === 'interrupted')) {\n\t\t\tappendDedupedWarning(warnings, truncationWarning(id, domain, connectionId, 'Pull request', cause));\n\t\t}\n\t};\n\n\t// With no repos this is an account-wide \"my PRs\" sweep. The repo-scoped core rejects an empty `repos`\n\t// input, so read the provider-native account-wide core instead.\n\tconst accountWide = repos.length === 0;\n\t// The breadth opt-ins are only expressible on the account-wide core, so gate them here — the single place\n\t// that already knows which core it is about to call.\n\tconst includeReviewRequested = accountWide && (accountWideOptions?.includeReviewRequested ?? false);\n\tconst includeReviews = accountWide && (accountWideOptions?.includeReviews ?? false);\n\t/** Every cursor already followed, so a provider that cycles them can't keep the drain walking in circles. */\n\tconst seenCursors = new Set<string>();\n\n\tfor (;;) {\n\t\tpage++;\n\t\t// Snapshot the mutable loop cursor so the read closure doesn't capture a later-reassigned value.\n\t\tconst pageCursor = cursor;\n\t\tconst { value, warning } = await runCaptured(id, domain, connectionId, () =>\n\t\t\taccountWide\n\t\t\t\t? integration.getMyPullRequestsForUserResult(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstate: state,\n\t\t\t\t\t\t\tcursor: pageCursor,\n\t\t\t\t\t\t\tincludeReviewRequested: includeReviewRequested,\n\t\t\t\t\t\t\tfilters: filters,\n\t\t\t\t\t\t\t// Lite by default; the full projection (reviews) only when a caller opts in.\n\t\t\t\t\t\t\tsummary: !includeReviews,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t)\n\t\t\t\t: integration.getMyPullRequestsForReposResult(\n\t\t\t\t\t\trepos,\n\t\t\t\t\t\t{ state: state, filters: filters, cursor: pageCursor },\n\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t),\n\t\t);\n\t\tif (warning != null) {\n\t\t\tappendDedupedWarning(warnings, warning);\n\t\t}\n\t\tif (value == null) {\n\t\t\t// An implicit sweep may silently skip a provider that has no session before it yields data.\n\t\t\t// Once a page has been returned, however, losing that session leaves an unread tail and must\n\t\t\t// be attributed even when the provider wasn't explicitly requested.\n\t\t\tconst sessionLostAfterProgress = warning == null && page > 1;\n\t\t\tconst unavailable = warning == null && (attributeUnavailableProvider || sessionLostAfterProgress);\n\t\t\tif (unavailable) {\n\t\t\t\tappendDedupedWarning(warnings, noConnectionWarning(id, domain, connectionId));\n\t\t\t}\n\t\t\t// `warning` set → a hard read failure (incomplete items); otherwise not connected / no session.\n\t\t\tconst failed = fetchFailed || warning != null || unavailable;\n\t\t\t// A cap seen on an earlier page still left results out, so it is still worth saying — but as part\n\t\t\t// of a read that failed, never as an omission. (`failed` is always true here when anything was\n\t\t\t// latched: reaching this exit past page 1 means a later page was lost.)\n\t\t\tappendTruncationWarnings(failed ? 'interrupted' : 'exhausted');\n\t\t\t// An earlier page may already have emitted an omission before this one died; it asserts the read\n\t\t\t// succeeded, which is no longer true.\n\t\t\treconcileOmissionsWithFailure(warnings, failed);\n\t\t\treturn {\n\t\t\t\titems: items,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: failed,\n\t\t\t\ttruncated: truncated || sessionLostAfterProgress,\n\t\t\t\t// Only a top-level first-page rejection means the provider itself failed. A later-page or\n\t\t\t\t// per-scope failure still yielded a usable provider slice and stays represented separately.\n\t\t\t\tfailedProvider: page === 1 && (warning != null || unavailable),\n\t\t\t};\n\t\t}\n\n\t\t// Composite account-wide queries can surface the same PR on different relationship/state pages\n\t\t// (for example authored + review-requested, or closed + merged). Keep the first stable position but\n\t\t// replace its value with the latest representation so a later, richer merged state wins. Repository\n\t\t// identity is what keys a row (see `getProviderPullRequestIdentity`), falling back to the canonical\n\t\t// URL; a row with neither stays unkeyed and is retained so unrelated incomplete rows are never\n\t\t// collapsed.\n\t\tfor (const pullRequest of value.values) {\n\t\t\tconst identity = getProviderPullRequestIdentity(pullRequest);\n\t\t\tif (identity == null) {\n\t\t\t\titems.push(pullRequest);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst existingIndex = itemIndexByIdentity.get(identity);\n\t\t\tif (existingIndex == null) {\n\t\t\t\titemIndexByIdentity.set(identity, items.length);\n\t\t\t\titems.push(pullRequest);\n\t\t\t} else {\n\t\t\t\titems[existingIndex] = pullRequest;\n\t\t\t}\n\t\t}\n\n\t\t// Assess this page's SDK metadata: append scope-aware warnings (deduped across pages), and remember\n\t\t// whether a structured failure or incompleteness occurred.\n\t\tconst assessment = mergeAssessmentInto(warnings, id, domain, connectionId, value.metadata);\n\t\tfetchFailed = fetchFailed || assessment.fetchFailed;\n\t\tconst pageTruncated =\n\t\t\t(value as { truncated?: boolean }).truncated === true ||\n\t\t\tvalue.paging?.truncated === true ||\n\t\t\tassessment.truncated;\n\t\ttruncated = truncated || pageTruncated;\n\t\t// Whether this drain owes its OWN warning for a capped page — `mergeAssessmentInto` has already appended\n\t\t// one when the fact came from SDK metadata, and repeating it would be noise.\n\t\tunexplainedTruncation = unexplainedTruncation || (pageTruncated && !assessment.truncated);\n\t\t// Only the account-wide read reports a search match count here; the repo-scoped one reports the item\n\t\t// total, which no cap is derived from. Latched even when SDK metadata explains the page: a composite page\n\t\t// reports a failed or cursorless sibling facet there, which says nothing about the cap another reached.\n\t\tif (accountWide && pageTruncated && value.paging?.totalCount != null) {\n\t\t\ttruncatedTotalCount = Math.max(truncatedTotalCount ?? 0, value.paging.totalCount);\n\t\t}\n\n\t\tif (!(value.paging?.more ?? false)) {\n\t\t\t// A read that can't confirm completeness (single-page provider reads with no `hasNextPage`)\n\t\t\t// sets `paging.truncated`; propagate it (and any top-level `truncated` and SDK incompleteness)\n\t\t\t// so the sweep doesn't claim an all-pages result.\n\t\t\tappendTruncationWarnings(fetchFailed ? 'interrupted' : 'exhausted');\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn {\n\t\t\t\titems: items,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: fetchFailed,\n\t\t\t\ttruncated: truncated,\n\t\t\t\tfailedProvider: false,\n\t\t\t};\n\t\t}\n\n\t\t// Resolve the continuation BEFORE deciding why the drain stops. `page-budget` claims the missing items\n\t\t// are reachable, which is only true with a usable cursor in hand — and a provider can report another\n\t\t// page while handing back none (Bitbucket Server does, when it omits `nextPageStart`). Checking the\n\t\t// budget first would label that unreachable tail as merely unfetched, so raising `maxPages` would\n\t\t// return the identical set.\n\t\tconst nextCursor = value.paging?.cursor;\n\t\t// A cursor already used isn't a continuation either — following it refetches a page we have, and a\n\t\t// provider that cycles (A→B→A) would otherwise burn the whole budget and then be reported as merely\n\t\t// out of budget. Tracked as a SET rather than compared one-back, matching the SDK's own `followCursors`:\n\t\t// `drainToRequestedPage` and `collectProviderPagedResult` compare only the previous cursor, which a\n\t\t// cycle slips past. That is tolerable there and not here, because only this drain reports `page-budget`.\n\t\tconst continuable = nextCursor != null && nextCursor !== '{}' && !seenCursors.has(nextCursor);\n\t\tif (!continuable || page >= maxPages) {\n\t\t\t// A usable cursor in hand means `page-budget`, even when an earlier page was capped: a composite\n\t\t\t// read caps its facets independently, so one facet reaching the provider's ceiling leaves its\n\t\t\t// siblings' cursors live, and raising the budget returns more of them. What no budget returns is the\n\t\t\t// capped part, so that is reported BESIDE the budget stop rather than in place of it — unless an SDK\n\t\t\t// omission already says so. Neither warning then misstates the part the other describes.\n\t\t\tconst budgetStop = !fetchFailed && continuable;\n\t\t\tappendDedupedWarning(\n\t\t\t\twarnings,\n\t\t\t\ttruncationWarning(\n\t\t\t\t\tid,\n\t\t\t\t\tdomain,\n\t\t\t\t\tconnectionId,\n\t\t\t\t\t'Pull request',\n\t\t\t\t\tfetchFailed ? 'interrupted' : budgetStop ? 'page-budget' : 'exhausted',\n\t\t\t\t),\n\t\t\t);\n\t\t\tconst cap = capWarning();\n\t\t\tif (cap != null) {\n\t\t\t\tappendDedupedWarning(warnings, cap);\n\t\t\t} else if (budgetStop && unexplainedTruncation) {\n\t\t\t\tappendDedupedWarning(warnings, cappedBesideBudgetWarning(id, domain, connectionId, 'Pull request'));\n\t\t\t}\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn {\n\t\t\t\titems: items,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: fetchFailed,\n\t\t\t\ttruncated: true,\n\t\t\t\tfailedProvider: false,\n\t\t\t};\n\t\t}\n\n\t\tseenCursors.add(nextCursor);\n\t\tcursor = nextCursor;\n\t}\n}\n\n/**\n * Drains every page of repositories under an org for one git-host integration, threading the opaque\n * next-cursor the provider returns. Stops at `maxPages` (marking `truncated`) or on a hard read failure\n * (marking `fetchFailed`), keeping the pages fetched so far.\n */\nexport async function drainRepositories(\n\tintegration: GitHostIntegration,\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\torg: string,\n\tproject: string | undefined,\n\tconnectionId: string | undefined,\n\tmaxPages: number,\n): Promise<{\n\trepos: ProviderRepository[];\n\twarnings: ProviderWarning[];\n\tfetchFailed: boolean;\n\ttruncated: boolean;\n}> {\n\tconst repos: ProviderRepository[] = [];\n\tconst warnings: ProviderWarning[] = [];\n\tlet fetchFailed = false;\n\tlet truncated = false;\n\tlet cursor: string | undefined;\n\tlet page = 0;\n\n\tfor (;;) {\n\t\tpage++;\n\t\tconst pageCursor = cursor;\n\t\tconst { value, warning } = await runCaptured(\n\t\t\tid,\n\t\t\tdomain,\n\t\t\tconnectionId,\n\t\t\t() =>\n\t\t\t\tintegration.getRepositoriesForOrgResult(org, {\n\t\t\t\t\tproject: project,\n\t\t\t\t\tcursor: pageCursor,\n\t\t\t\t\tconnectionId: connectionId,\n\t\t\t\t}),\n\t\t\t{ warnOnMissingSession: true },\n\t\t);\n\t\tif (warning != null) {\n\t\t\t// Deduped, like every other accumulation here: a soft warning (`{ value, error }`) repeats verbatim on\n\t\t\t// each page that hits the same condition, and the array's contract is that no two entries are equal.\n\t\t\tappendDedupedWarning(warnings, warning);\n\t\t}\n\t\tif (value == null) {\n\t\t\tconst interruptedAfterProgress = page > 1;\n\t\t\tif (interruptedAfterProgress && warning == null) {\n\t\t\t\t// Not a backstop: the read was cut short mid-drain, which is why it also sets `fetchFailed`\n\t\t\t\t// below. A retry may complete it, so this must not claim the succeeded-but-capped omission.\n\t\t\t\tappendDedupedWarning(\n\t\t\t\t\twarnings,\n\t\t\t\t\ttruncationWarning(id, domain, connectionId, 'Repository', 'interrupted'),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst failed = fetchFailed || warning != null || interruptedAfterProgress;\n\t\t\t// An SDK omission from an earlier page asserts the read succeeded; this one didn't.\n\t\t\treconcileOmissionsWithFailure(warnings, failed);\n\t\t\treturn {\n\t\t\t\trepos: repos,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: failed,\n\t\t\t\ttruncated: truncated || interruptedAfterProgress,\n\t\t\t};\n\t\t}\n\n\t\trepos.push(...value.values);\n\t\tconst assessment = mergeAssessmentInto(warnings, id, domain, connectionId, value.metadata);\n\t\tfetchFailed = fetchFailed || assessment.fetchFailed;\n\t\ttruncated = truncated || value.truncated === true || value.paging?.truncated === true || assessment.truncated;\n\t\tif (!(value.paging?.more ?? false)) {\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn {\n\t\t\t\trepos: repos,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: fetchFailed,\n\t\t\t\ttruncated: truncated,\n\t\t\t};\n\t\t}\n\n\t\tif (page >= maxPages) {\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn { repos: repos, warnings: warnings, fetchFailed: fetchFailed, truncated: true };\n\t\t}\n\n\t\tconst nextCursor = value.paging?.cursor;\n\t\tif (nextCursor == null || nextCursor === '{}') {\n\t\t\t// Provider says there is more but didn't return a usable cursor; stop rather than refetch the same page.\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn { repos: repos, warnings: warnings, fetchFailed: fetchFailed, truncated: true };\n\t\t}\n\n\t\tcursor = nextCursor;\n\t}\n}\n\nexport function resolvePullRequestSweepTargets(options: PullRequestSweepOptions | undefined): {\n\ttargets: readonly ProviderSweepTarget[];\n\tattributeUnavailableProviders: boolean;\n} {\n\tif (options?.targets != null) {\n\t\tif (options.providerIds != null || options.connectionId != null) {\n\t\t\tthrow new TypeError(\"Pull request sweep 'targets' cannot be combined with 'providerIds' or 'connectionId'\");\n\t\t}\n\n\t\tconst seenProviderIds = new Set<IntegrationIds>();\n\t\tfor (const target of options.targets) {\n\t\t\tif (seenProviderIds.has(target.providerId)) {\n\t\t\t\tthrow new TypeError(\n\t\t\t\t\t`Pull request sweep targets must contain at most one target per provider; duplicate '${target.providerId}'`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tseenProviderIds.add(target.providerId);\n\t\t}\n\n\t\treturn { targets: options.targets, attributeUnavailableProviders: true };\n\t}\n\n\tconst providerIds =\n\t\toptions?.providerIds ?? supportedOrderedCloudIntegrationIds.filter(id => !isIssuesHostIntegrationId(id));\n\tconst connectionId = providerIds.length === 1 ? options?.connectionId : undefined;\n\treturn {\n\t\ttargets: providerIds.map(providerId => ({ providerId: providerId, connectionId: connectionId })),\n\t\tattributeUnavailableProviders: options?.providerIds != null,\n\t};\n}\n\nexport async function getCurrentAccountIdentity(\n\tintegration: GitHostIntegration,\n\tconnectionId: string | undefined,\n): Promise<{ id: string; username?: string } | undefined> {\n\ttry {\n\t\tconst account = await integration.getCurrentAccount({ connectionId: connectionId });\n\t\treturn account != null ? { id: account.id, username: account.username } : undefined;\n\t} catch {\n\t\t// Authorship is optional enrichment; don't turn a successful PR read into a failure if identity lookup fails.\n\t\treturn undefined;\n\t}\n}\n\n/**\n * The current account to map each raw row against, in order (see {@link GitHostIntegration.getPullRequestViewers}).\n * Rows that name no one can't be matched, so a read of only those resolves nothing; a failed lookup leaves every\n * row's authorship unknown rather than failing the read.\n */\nexport async function getPullRequestViewers(\n\tintegration: GitHostIntegration,\n\tconnectionId: string | undefined,\n\tpullRequests: readonly (ProviderPullRequest | PullRequest)[],\n): Promise<({ id: string; username?: string } | undefined)[]> {\n\tif (!pullRequests.some(pullRequestNamesPeople)) return pullRequests.map(() => undefined);\n\n\ttry {\n\t\treturn await integration.getPullRequestViewers(pullRequests, connectionId);\n\t} catch {\n\t\treturn pullRequests.map(() => undefined);\n\t}\n}\n"],"mappings":"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"}
@@ -148,13 +148,16 @@ async function readRepoScopedIssuesPage({ options, integration, domain, warnOnMi
148
148
  const resolvedRepoScopedSort = resolveIssueSort(metadata?.supportedIssueSorts, options.sort);
149
149
  if (resolvedRepoScopedSort.rejection != null) return refusedPage(page, [unsupportedIssueSortWarning(options.providerId, domain, options.connectionId, resolvedRepoScopedSort.rejection)], true);
150
150
  const issuesPagingMode = metadata?.issuesPagingMode;
151
- let repos = options.repos ?? [];
152
- if (issuesPagingMode === "project" && repos.length) {
153
- const settled = await runCaptured(options.providerId, domain, options.connectionId, () => integration.resolveRepositoryScopes(repos, options.connectionId), { warnOnMissingSession });
151
+ const scopes = options.repos ?? [];
152
+ let repos = scopes;
153
+ let counted = scopes;
154
+ if (issuesPagingMode === "project" && scopes.length) {
155
+ const settled = await runCaptured(options.providerId, domain, options.connectionId, () => integration.resolveRepositoryScopes(scopes, options.connectionId), { warnOnMissingSession });
154
156
  if (settled.value == null) return refusedPage(page, settled.warning != null ? [settled.warning] : [], settled.warning != null);
155
157
  repos = settled.value;
158
+ counted = settled.value.repos;
156
159
  }
157
- const ordering = toIssueOrdering(resolvedRepoScopedSort.sort, mergesProviderQueries(issuesPagingMode, repos));
160
+ const ordering = toIssueOrdering(resolvedRepoScopedSort.sort, mergesProviderQueries(issuesPagingMode, counted));
158
161
  if (ordering.unmergeable != null) return refusedPage(page, [unmergeableIssueSortWarning(options.providerId, domain, options.connectionId, ordering.unmergeable, issuesPagingMode === "project" ? "projects" : "repositories")], true);
159
162
  const cursor = options.cursor ?? pageToCursor(page);
160
163
  const { value, warning } = await runCaptured(options.providerId, domain, options.connectionId, () => integration.getMyIssuesForReposAsShapesResult(repos, {
@@ -1 +1 @@
1
- {"version":3,"file":"issues.js","names":[],"sources":["../../../../../plus/integrations/src/reads/issues.ts"],"sourcesContent":["import type { IssueShape, IssueSorting, IssueStateFilter } from '@gitlens/git/models/issue.js';\nimport { mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { GitCloudHostIntegrationId, GitSelfManagedHostIntegrationId } from '../constants.js';\nimport type { GitHostIntegration } from '../models/gitHostIntegration.js';\nimport type { IssueFilter, ProviderReposInput } from '../providers/models.js';\nimport { isRepoIdsInput, PagingMode, providersMetadata } from '../providers/models.js';\nimport { mergeCollectionMetadata } from '../providers/utils/providerPaging.js';\nimport type { ProviderPagedResult, ProviderWarning } from '../results.js';\nimport { reconcileOmissionsWithFailure } from '../results.js';\nimport {\n\tisGitHostIntegration,\n\tisIssuesHostIntegrationId,\n\twarnOnMissingSessionForDomain,\n} from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { runCaptured } from './drains.js';\nimport { resolveAccountWideIssueFilters } from './filters.js';\nimport { resolveIssueSort, toIssueOrdering } from './ordering.js';\nimport {\n\tdrainFlatPagesToRequestedPage,\n\tdrainToRequestedPage,\n\tisPageNumberAdvanceable,\n\tpageToCursor,\n\trefusedPage,\n\tresolveContinuation,\n\tresolveCurrentPage,\n\ttoProviderPageInfo,\n\tusableCursor,\n} from './paging.js';\nimport {\n\tgitHostOnlySurfaceWarning,\n\tissuesUnsupportedWarning,\n\totherWarning,\n\ttruncationWarning,\n\tunmergeableIssueSortWarning,\n\tunsupportedAccountWideIssueFiltersWarning,\n\tunsupportedIssueSortWarning,\n\tunsupportedWarning,\n} from './warnings.js';\n\nconst issueStateFilters: readonly IssueStateFilter[] = ['open', 'closed', 'all'];\n\n/**\n * Whether a repo-scoped page is assembled from SEVERAL provider queries and merged here — which is what decides\n * whether the requested order can be honored at all.\n *\n * `PagingMode.Repos` (GitHub) sends one search however many repositories are named, so its page arrives ordered by\n * the provider. `Repo` (GitLab) issues one query per repository, and `Project` (Azure) one per PROJECT — several\n * repositories of the same project are still one query, which is why that case counts distinct projects rather than\n * repositories.\n *\n * The `Repo` case has a second way to merge that has nothing to do with the count: given repository IDS rather than\n * descriptors, `getMyIssuesForReposResult` skips its per-repository fan-out entirely (that branch is guarded on\n * `!isRepoIdsInput`) and calls the SDK's `getIssuesForRepos`, which for GitLab is the multi-project aggregate that\n * merges in the SDK and refuses `priority`/`dueDate` however few scopes it was given. Treating that as merged is\n * what makes this facade refuse it up front, with the message that names the reason, instead of letting the SDK\n * reject a read the capability table had just promised.\n */\nfunction mergesProviderQueries(pagingMode: PagingMode | undefined, repos: ProviderReposInput | undefined): boolean {\n\tconst scopes = repos ?? [];\n\tswitch (pagingMode) {\n\t\tcase PagingMode.Repo:\n\t\t\t// `isRepoIdsInput` is the same predicate `ProvidersApi` selects the SDK aggregate with, so this cannot\n\t\t\t// disagree with the branch it is predicting.\n\t\t\treturn isRepoIdsInput(scopes) ? scopes.length > 0 : scopes.length > 1;\n\t\tcase PagingMode.Project:\n\t\t\t// Only the descriptor form carries a project; the id form is refused by the Azure read before it runs.\n\t\t\treturn new Set(scopes.map(r => (typeof r === 'object' ? r.project : undefined))).size > 1;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\n/**\n * What {@link listIssuesPage} accepts, shared by name with the two functions it dispatches to.\n *\n * Named rather than left inline: three functions now take this exact shape, and reaching it from the other two as\n * `Parameters<typeof listIssuesPage>[1]` would define the contract in terms of the function that consumes it —\n * inside out, and unreadable at the point where the fields are actually used.\n */\nexport interface ListIssuesPageOptions {\n\tstate?: IssueStateFilter;\n\tproviderId: IntegrationIds;\n\trepos?: ProviderReposInput;\n\t/** Narrows the account-wide read to one org/account. Requires a host with a project layer (Azure). */\n\torg?: string;\n\t/** Narrows the account-wide read to one project. Requires a host with a project layer (Azure). */\n\tproject?: string;\n\t/**\n\t * Narrows to the requested relationship(s). On the account-wide path this replaces the provider's own\n\t * definition of \"my issues\" (GitHub/GHE: authored ∪ assigned ∪ mentioned; Azure: assigned ∪ authored;\n\t * GitLab: assigned-to-me), so `[Assignee]` yields `assignee:@me` everywhere it's expressible. A set the\n\t * provider can't express server-side is refused whole (warning + `fetchFailed`), never widened — check\n\t * getSupportedFilters first to avoid that path.\n\t */\n\tfilters?: IssueFilter[];\n\t/** Broadens the read to every assignee. Contradicts `filters`; passing both is refused. */\n\tincludeAllAssignees?: boolean;\n\t/**\n\t * How to order the page, as `field:direction`. Omitted orders most-recently-updated-first wherever the\n\t * provider can express that, which is this facade's default rather than the provider's own.\n\t *\n\t * Validated against `getSupportedFilters().issueSorts` on the repo-scoped path and `.issueSortsAccountWide`\n\t * on the account-wide one — two genuinely different vocabularies for GitLab, whose two reads are different\n\t * APIs. A key the provider can't express refuses the read (warning + `fetchFailed`) rather than serving a\n\t * differently-ordered list, for the same reason an inexpressible filter does: the reachable window is\n\t * bounded, so another order is another subset.\n\t *\n\t * On the repo-scoped path with SEVERAL scopes the page is a merge of one query per repository/project, so\n\t * only a key a normalized issue carries can be honored: `priority`, `dueDate` and `resolved` are refused\n\t * there even where the provider supports them on a single scope. Order is per page, and per scope across\n\t * pages — the merge orders what a page contains, not the sequence of pages.\n\t */\n\tsort?: IssueSorting;\n\tpage?: number;\n\tcursor?: string;\n\titemsPerPage?: number;\n\tforceSync?: boolean;\n\tconnectionId?: string;\n\t/**\n\t * Explicit self-managed host domain. Used only when the requested connection has no configured domain;\n\t * it must come from the trusted authentication configuration, not repository or remote data.\n\t */\n\tdomain?: string;\n}\n\n/**\n * What both halves of {@link listIssuesPage} need from its shared prologue.\n *\n * A bundle rather than six parameters because the two reads take exactly the same inputs and differ only in what\n * they do with them; threading them positionally would make the two call sites read as two different reads of two\n * different things, which is the opposite of what is true.\n *\n * Note `ctx` is absent: every use of it — resolving the integration, the domain, the forced refresh, the\n * connection warnings — is in the prologue, which is what makes this the right cut.\n */\ninterface IssueReadContext {\n\toptions: ListIssuesPageOptions;\n\tintegration: GitHostIntegration;\n\tdomain: string | undefined;\n\twarnOnMissingSession: boolean;\n\tpage: number;\n}\n\nexport async function listIssuesPage(\n\tctx: ProviderReadContext,\n\toptions: ListIssuesPageOptions,\n): Promise<ProviderPagedResult<IssueShape>> {\n\t// Truncated as well as floored, like every other paged read here: `page` is reported back as\n\t// `page.currentPage` and compared against the position a drain reached, and neither can be fractional.\n\tconst page = Math.max(1, Math.trunc(options.page ?? 1));\n\tif (isIssuesHostIntegrationId(options.providerId)) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, 'repository issue reads')],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\t// A supplied connection or domain that no longer resolves is a broken target, not an empty account —\n\t\t// surface a no-connection warning + fetchFailed rather than a silent empty page.\n\t\tconst early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);\n\t\treturn refusedPage(page, early.warnings, early.fetchFailed);\n\t}\n\tif (!isGitHostIntegration(integration)) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, 'repository issue reads')],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tawait ctx.forceRefreshIfRequested(integration, options.forceSync, options.connectionId);\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\tconst warnOnMissingSession = warnOnMissingSessionForDomain(options.providerId, options.domain);\n\n\t// A git host whose issue tracker is deprecated (Bitbucket, superseded by dedicated issue integrations)\n\t// reports issues as explicitly unsupported rather than serving a partial/legacy source or a silent empty.\n\tif (!integration.supportsIssues) {\n\t\treturn refusedPage(page, [issuesUnsupportedWarning(options.providerId, domain, options.connectionId)], true);\n\t}\n\n\tif (\n\t\toptions.state != null &&\n\t\t(!issueStateFilters.includes(options.state) ||\n\t\t\t(options.state !== 'open' && !providersMetadata[options.providerId]?.supportsIssueStates))\n\t) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[\n\t\t\t\tissueStateFilters.includes(options.state)\n\t\t\t\t\t? unsupportedWarning(\n\t\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\t\tdomain,\n\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t\t`Issue state '${options.state}' is not supported by '${options.providerId}'.`,\n\t\t\t\t\t\t)\n\t\t\t\t\t: otherWarning(\n\t\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\t\tdomain,\n\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t\t`Unknown issue state; expected one of ${issueStateFilters.join(', ')}.`,\n\t\t\t\t\t\t),\n\t\t\t],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\t// `repos` is what selects between the two: with none there is no scope to query per repository, so the\n\t// provider's own account-wide search answers instead. They are different provider queries with different\n\t// capability tables, cursors and completeness signals — see each function.\n\tconst context: IssueReadContext = {\n\t\toptions: options,\n\t\tintegration: integration,\n\t\tdomain: domain,\n\t\twarnOnMissingSession: warnOnMissingSession,\n\t\tpage: page,\n\t};\n\treturn (options.repos?.length ?? 0) === 0 ? readAccountWideIssuesPage(context) : readRepoScopedIssuesPage(context);\n}\n\n/**\n * The account-wide read: the provider's own definition of \"my issues\", behind a composite cursor that spans\n * several provider searches.\n *\n * Its own function rather than a branch of the paged read, because almost nothing is shared past the guards: it\n * validates a different filter table and a different sort table, it is cursor-only (a page number cannot address\n * a composite cursor), it accumulates per-scope failures across the pages it walks, and it always merges several\n * queries. The two used to share one 460-line function behind a mode flag, and the comments explaining how far\n * apart they are were the clue that they wanted separating.\n */\nasync function readAccountWideIssuesPage({\n\toptions,\n\tintegration,\n\tdomain,\n\twarnOnMissingSession,\n\tpage,\n}: IssueReadContext): Promise<ProviderPagedResult<IssueShape>> {\n\t// The account-wide read is cursor-only, so a refusal can't claim the requested position — it reports\n\t// page 1, per ProviderPageInfo.currentPage.\n\tconst refused = (warning: ProviderWarning) => refusedPage<IssueShape>(1, [warning], true);\n\n\t// Checked before the provider-specific guards below: a caller passing both has a contradictory request\n\t// whatever the provider, and saying so is more useful than reporting one half of it as unsupported.\n\t// `filters` narrows this read to a relationship (`[Assignee]` ⇒ just assigned-to-me); `includeAllAssignees`\n\t// broadens it to every assignee. Honoring either silently would answer a question the caller didn't ask.\n\tif (options.filters?.length && options.includeAllAssignees === true) {\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'`filters` and `includeAllAssignees` are contradictory for an account-wide issue read; pass only one.',\n\t\t\t),\n\t\t);\n\t}\n\n\t// GitHub expresses \"any assignee\" as `assignee:*`, which is meaningless without a scope: unscoped it\n\t// matches millions of issues across all of GitHub instead of the user's own world. ANY scope makes it\n\t// meaningful (one repository, several, or an org) — it is specifically THIS branch, the account-wide read,\n\t// that has none to offer. Scope the read to repositories (or use `searchIssuesPage`, whose criteria model\n\t// takes `any-assignee` alongside a `repos`/`org` scope) rather than reading unscoped.\n\tif (\n\t\toptions.includeAllAssignees === true &&\n\t\t(options.providerId === GitCloudHostIntegrationId.GitHub ||\n\t\t\toptions.providerId === GitSelfManagedHostIntegrationId.CloudGitHubEnterprise)\n\t) {\n\t\treturn refused(\n\t\t\tunsupportedWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'`includeAllAssignees` is not supported for account-wide GitHub issue reads; scope the read to repositories instead.',\n\t\t\t),\n\t\t);\n\t}\n\n\t// Only a host with a project layer can narrow server-side. Reject the request rather than serving an\n\t// unscoped list as if it had been narrowed: the caller would otherwise have to filter client-side,\n\t// which desynchronizes the filtered `items` from this read's `hasMore`/`currentPage` and shows\n\t// \"no issues\" for a page that simply held none of the requested project's.\n\tif ((options.org != null || options.project != null) && !integration.supportsProjectDiscovery) {\n\t\treturn refused(\n\t\t\tunsupportedWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`Project-scoped issue reads are not supported by '${options.providerId}'; scope the read to repositories instead.`,\n\t\t\t),\n\t\t);\n\t}\n\n\t// Narrowing the account-wide read is only honest when the provider can express it server-side: its\n\t// per-relationship queries produced the page and the cursor together, so dropping items afterward would\n\t// leave `items` describing a different result set than `hasMore`/`currentPage`.\n\tconst resolvedIssueFilters = resolveAccountWideIssueFilters(options.providerId, options.filters);\n\tif (resolvedIssueFilters.unsupported) {\n\t\treturn refused(\n\t\t\tunsupportedAccountWideIssueFiltersWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\toptions.filters!,\n\t\t\t),\n\t\t);\n\t}\n\n\t// Every provider's account-wide read is a UNION of several queries — GitHub's three `@me` searches,\n\t// GitLab's one REST call per relationship, Azure's (project x relationship) drains — so it always merges,\n\t// and `supportedAccountWideIssueSorts` already lists only keys a merge can honor. No separate\n\t// mergeability refusal is needed here, unlike on the repo-scoped path below.\n\tconst resolvedSort = resolveIssueSort(\n\t\tprovidersMetadata[options.providerId]?.supportedAccountWideIssueSorts,\n\t\toptions.sort,\n\t);\n\tif (resolvedSort.rejection != null) {\n\t\treturn refused(\n\t\t\tunsupportedIssueSortWarning(options.providerId, domain, options.connectionId, resolvedSort.rejection),\n\t\t);\n\t}\n\n\t// `merged: true` unconditionally: this read has no scope count for a caller to reduce. It is also why no\n\t// `unmergeable` check follows — `supportedAccountWideIssueSorts` declares only keys a merge can honor.\n\tconst accountWideOrdering = toIssueOrdering(resolvedSort.sort, true);\n\n\t// The repo-scoped core rejects empty repos (GitHub/Bitbucket/Azure); read the account-wide,\n\t// already-user-scoped core instead. GitHub exposes a composite cursor across its authored,\n\t// assigned, and mentioned searches. Walk it internally when the caller supplies only page N.\n\tconst readAccountWidePage = (cursor: string | undefined) =>\n\t\trunCaptured(\n\t\t\toptions.providerId,\n\t\t\tdomain,\n\t\t\toptions.connectionId,\n\t\t\t() =>\n\t\t\t\tintegration.searchMyIssuesWithTruncationResult(undefined, undefined, options.connectionId, {\n\t\t\t\t\tincludeAllAssignees: options.includeAllAssignees,\n\t\t\t\t\tstate: options.state,\n\t\t\t\t\tfilters: resolvedIssueFilters.filters,\n\t\t\t\t\tcursor: cursor,\n\t\t\t\t\tpageSize: options.itemsPerPage,\n\t\t\t\t\torg: options.org,\n\t\t\t\t\tproject: options.project,\n\t\t\t\t\tsort: accountWideOrdering.sort,\n\t\t\t\t}),\n\t\t\t{ warnOnMissingSession: warnOnMissingSession },\n\t\t);\n\tconst first = await readAccountWidePage(options.cursor);\n\tconst warnings = first.warning != null ? [first.warning] : [];\n\t// Merged across the walked pages, so a per-scope failure reported on an earlier page isn't lost by\n\t// paging past it — the one thing this read accumulates that the filtered search doesn't.\n\tlet allMetadata = first.value?.metadata;\n\tconst drained = await drainFlatPagesToRequestedPage(first, {\n\t\trequestedPage: page,\n\t\tsuppliedCursor: options.cursor,\n\t\twarnings: warnings,\n\t\treadPage: readAccountWidePage,\n\t\tfold: p => {\n\t\t\tallMetadata = mergeCollectionMetadata(allMetadata, p.metadata, 'disjoint');\n\t\t},\n\t});\n\tconst { value, currentPage, requestedPageMissing } = drained;\n\tconst currentTruncated = drained.truncated;\n\tconst pageFetchFailed = drained.fetchFailed;\n\n\t// GitHub, GitLab, and Azure implement an account-wide issue search; a provider that doesn't (Bitbucket\n\t// exposes no issues at all, and `supportsIssues` already short-circuits it above) returns `undefined`\n\t// with no error. Surface that as an explicit unsupported warning + fetchFailed rather than a silent\n\t// empty success — the caller must fall back (e.g. broadenIssues over repos).\n\tif (value == null && warnings.length === 0) {\n\t\treturn refused(\n\t\t\tunsupportedWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`Account-wide issue search is not supported by '${options.providerId}'; scope the read to repositories instead.`,\n\t\t\t),\n\t\t);\n\t}\n\n\tconst items = requestedPageMissing ? [] : (value?.values ?? []);\n\t// Each query arrived ordered by the provider; their union is not, and the union is what this read publishes.\n\t// Idempotent for GitHub, whose aliased-search engine already ordered the same merge — one call here rather\n\t// than the same logic repeated in each of the three provider implementations.\n\tconst orderedItems = accountWideOrdering.order(items);\n\t// Fold in structured per-scope failures from the account-wide fan-out (e.g. Azure across projects):\n\t// scope-aware warnings + `fetchFailed` when a scope failed, without discarding the successful items.\n\tconst assessment = mergeAssessmentInto(warnings, options.providerId, domain, options.connectionId, allMetadata);\n\t// An account-wide search that couldn't confirm completeness (a provider cap with no cursor, or a\n\t// per-scope backstop/failure) is incomplete and can't be paged; report it as truncated (+ a\n\t// provider-neutral warning, unless a structured failure already explains it) rather than a complete\n\t// list. Don't hard-code GitHub's \"100 per category\" cap here — Azure reaches this via a per-project\n\t// backstop, and other providers may cap differently.\n\t// The account-wide read is cursor-only (its composite cursor spans several provider searches, so a\n\t// page number can't address it): `hasMore` without a real cursor is a dead end, so report it as\n\t// terminal-but-incomplete rather than inviting the caller to page forever.\n\tconst continuation = resolveContinuation(\n\t\t{\n\t\t\thasMore: requestedPageMissing ? false : (value?.hasMore ?? false),\n\t\t\tcursor: requestedPageMissing ? undefined : usableCursor(value?.cursor),\n\t\t\ttruncated: currentTruncated,\n\t\t},\n\t\tundefined,\n\t);\n\tconst truncated = continuation.truncated || assessment.truncated;\n\tif (truncated && warnings.length === 0) {\n\t\twarnings.push(\n\t\t\ttruncationWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'Account-wide issue search',\n\t\t\t\t// `exhausted`: this composite read exposes no budget the caller can raise, so nothing it\n\t\t\t\t// could call would return the withheld items.\n\t\t\t\tassessment.fetchFailed || pageFetchFailed ? 'interrupted' : 'exhausted',\n\t\t\t),\n\t\t);\n\t}\n\t// A metadata omission from an earlier page asserts the read succeeded; a later page may since have failed.\n\treconcileOmissionsWithFailure(warnings, assessment.fetchFailed || pageFetchFailed);\n\treturn {\n\t\titems: orderedItems,\n\t\twarnings: warnings,\n\t\tpage: {\n\t\t\t// Positional, per ProviderPageInfo.currentPage. `currentPage` already carries what the provider\n\t\t\t// reported or what the internal drain counted; a requested page past the terminal cursor is\n\t\t\t// reported as that empty page N. The account-wide read is cursor-only, so a `page` the caller\n\t\t\t// didn't pair with a cursor is never echoed.\n\t\t\tcurrentPage: requestedPageMissing\n\t\t\t\t? page\n\t\t\t\t: resolveCurrentPage({\n\t\t\t\t\t\tproviderPage: currentPage,\n\t\t\t\t\t\trequestedPage: page,\n\t\t\t\t\t\tsuppliedCursor: options.cursor,\n\t\t\t\t\t\tpageAdvanceable: false,\n\t\t\t\t\t}),\n\t\t\titemsPerPage: orderedItems.length,\n\t\t\ttruncated: truncated || undefined,\n\t\t},\n\t\thasMore: continuation.hasMore,\n\t\tcursor: continuation.cursor,\n\t\tfetchFailed: assessment.fetchFailed || pageFetchFailed || undefined,\n\t};\n}\n\n/**\n * The repository-scoped read: one provider query per repository or project, paged by the provider's own\n * continuation and drained internally when the caller supplied only a page number.\n *\n * See {@link readAccountWideIssuesPage} for why the two are separate functions.\n */\nasync function readRepoScopedIssuesPage({\n\toptions,\n\tintegration,\n\tdomain,\n\twarnOnMissingSession,\n\tpage,\n}: IssueReadContext): Promise<ProviderPagedResult<IssueShape>> {\n\tconst metadata = providersMetadata[options.providerId];\n\n\t// How many separate provider queries this page is assembled from, which decides whether the requested order can\n\t// be honored at all. `PagingMode.Repos` (GitHub) sends ONE search however many repositories are named, so its\n\t// page arrives ordered by the provider; `Repo` (GitLab) and `Project` (Azure) issue one query per scope and are\n\t// concatenated here, so a page spanning several of them is only as orderable as a normalized issue is.\n\tconst resolvedRepoScopedSort = resolveIssueSort(metadata?.supportedIssueSorts, options.sort);\n\tif (resolvedRepoScopedSort.rejection != null) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[\n\t\t\t\tunsupportedIssueSortWarning(\n\t\t\t\t\toptions.providerId,\n\t\t\t\t\tdomain,\n\t\t\t\t\toptions.connectionId,\n\t\t\t\t\tresolvedRepoScopedSort.rejection,\n\t\t\t\t),\n\t\t\t],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tconst issuesPagingMode = metadata?.issuesPagingMode;\n\t// Counted, and read, as the host settles them: a scope can name another project than the one it was parsed with\n\t// (see `GitHostIntegration.resolveRepositoryScopes`), which changes how many queries a per-project read makes.\n\tlet repos = options.repos ?? [];\n\tif (issuesPagingMode === PagingMode.Project && repos.length) {\n\t\tconst settled = await runCaptured(\n\t\t\toptions.providerId,\n\t\t\tdomain,\n\t\t\toptions.connectionId,\n\t\t\t() => integration.resolveRepositoryScopes(repos, options.connectionId),\n\t\t\t{ warnOnMissingSession: warnOnMissingSession },\n\t\t);\n\t\t// No session to read with, or the read's own failure resolving it: answered as that read would, without it.\n\t\tif (settled.value == null) {\n\t\t\treturn refusedPage(page, settled.warning != null ? [settled.warning] : [], settled.warning != null);\n\t\t}\n\n\t\trepos = settled.value;\n\t}\n\tconst ordering = toIssueOrdering(resolvedRepoScopedSort.sort, mergesProviderQueries(issuesPagingMode, repos));\n\t// Refused rather than served as concatenated per-scope runs, which would look ordered without being so — and\n\t// only where it actually merges, since the same key against the same provider is perfectly answerable for a\n\t// single repository or project.\n\tif (ordering.unmergeable != null) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[\n\t\t\t\tunmergeableIssueSortWarning(\n\t\t\t\t\toptions.providerId,\n\t\t\t\t\tdomain,\n\t\t\t\t\toptions.connectionId,\n\t\t\t\t\tordering.unmergeable,\n\t\t\t\t\tissuesPagingMode === PagingMode.Project ? 'projects' : 'repositories',\n\t\t\t\t),\n\t\t\t],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tconst cursor = options.cursor ?? pageToCursor(page);\n\tconst { value, warning } = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\t() =>\n\t\t\t// The shapes seam returns normalized IssueShape, and is overridable by a provider whose only issue\n\t\t\t// client already yields shapes (serving this path without a raw ProviderIssue round-trip).\n\t\t\tintegration.getMyIssuesForReposAsShapesResult(\n\t\t\t\trepos,\n\t\t\t\t// Forward `page`/`pageSize` alongside the cursor so PagingMode.Repo/Project hosts honor the\n\t\t\t\t// requested page and page size rather than ignoring a synthesized page-number cursor.\n\t\t\t\t{\n\t\t\t\t\tfilters: options.filters,\n\t\t\t\t\tincludeAllAssignees: options.includeAllAssignees,\n\t\t\t\t\tstate: options.state,\n\t\t\t\t\tcursor: cursor,\n\t\t\t\t\t// The normalized `page`, so the number forwarded to the provider can't disagree with the\n\t\t\t\t\t// page-number cursor beside it — both are built from the same value. Only when the caller asked\n\t\t\t\t\t// for one: an omitted page is the provider's own first page.\n\t\t\t\t\tpage: options.page != null ? page : undefined,\n\t\t\t\t\tpageSize: options.itemsPerPage,\n\t\t\t\t\tsort: ordering.sort,\n\t\t\t\t},\n\t\t\t\toptions.connectionId,\n\t\t\t),\n\t\t{ warnOnMissingSession: warnOnMissingSession },\n\t);\n\n\tlet items = value?.values ?? [];\n\tconst warnings = warning != null ? [warning] : [];\n\tlet pageFetchFailed = warning != null && value == null;\n\tlet paged = toProviderPageInfo(options.itemsPerPage ?? items.length, value?.paging);\n\tlet allMetadata = value?.metadata;\n\n\t// Cursor-only repo-scoped hosts (e.g. GitHub) ignore a synthesized page-number cursor, so a page-only\n\t// request is advanced through the provider's own continuations (see drainToRequestedPage).\n\tif (\n\t\tissuesPagingMode === PagingMode.Repos &&\n\t\toptions.page != null &&\n\t\tpage > 1 &&\n\t\toptions.cursor == null &&\n\t\tpaged.page.currentPage === 1\n\t) {\n\t\tconst drained = await drainToRequestedPage(\n\t\t\t{ items: items, paged: paged, metadata: allMetadata, fetchFailed: pageFetchFailed },\n\t\t\t{\n\t\t\t\trequestedPage: page,\n\t\t\t\titemsPerPage: options.itemsPerPage,\n\t\t\t\twarnings: warnings,\n\t\t\t\treadPage: (cursor: string) =>\n\t\t\t\t\trunCaptured(\n\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tintegration.getMyIssuesForReposAsShapesResult(\n\t\t\t\t\t\t\t\trepos,\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tfilters: options.filters,\n\t\t\t\t\t\t\t\t\tincludeAllAssignees: options.includeAllAssignees,\n\t\t\t\t\t\t\t\t\tstate: options.state,\n\t\t\t\t\t\t\t\t\tcursor: cursor,\n\t\t\t\t\t\t\t\t\tpageSize: options.itemsPerPage,\n\t\t\t\t\t\t\t\t\tsort: ordering.sort,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t{ warnOnMissingSession: warnOnMissingSession },\n\t\t\t\t\t),\n\t\t\t},\n\t\t);\n\t\titems = drained.items;\n\t\tpaged = drained.paged;\n\t\tallMetadata = drained.metadata;\n\t\tpageFetchFailed = drained.fetchFailed;\n\t}\n\n\t// Ordered once the drain (above) has assembled whichever pages this position spans.\n\titems = ordering.order(items);\n\n\t// Convert the SDK collection metadata into scope-aware warnings + failure/truncation flags, appending\n\t// them to any captured thrown-error warning without discarding the partial result's items.\n\tconst assessment = mergeAssessmentInto(warnings, options.providerId, domain, options.connectionId, allMetadata);\n\t// Never advertise `hasMore` without a continuation the caller can act on, and only synthesize a page\n\t// number for a host that reads it as one (see `isPageNumberAdvanceable`).\n\tconst issuesPageAdvanceable = isPageNumberAdvanceable(issuesPagingMode);\n\tconst continuation = resolveContinuation(paged, issuesPageAdvanceable ? paged.page.currentPage + 1 : undefined);\n\t// A provider read that couldn't confirm completeness (e.g. Bitbucket's single-page repo issue read\n\t// that dropped a repo) sets `paging.truncated`; surface it as a terminal `page.truncated` so a partial\n\t// page isn't published as complete. Metadata incompleteness is an independent source of the same signal.\n\tconst truncated = continuation.truncated || assessment.truncated;\n\tif (truncated && warnings.length === 0) {\n\t\twarnings.push(\n\t\t\ttruncationWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'Issue',\n\t\t\t\t// `exhausted`, never `page-budget`: a paged read has no budget the caller can raise, and\n\t\t\t\t// ordinary continuation is already expressed by `hasMore`/`cursor`.\n\t\t\t\tassessment.fetchFailed || pageFetchFailed ? 'interrupted' : 'exhausted',\n\t\t\t),\n\t\t);\n\t}\n\t// A metadata omission from an earlier page asserts the read succeeded; a later page may since have failed.\n\treconcileOmissionsWithFailure(warnings, assessment.fetchFailed || pageFetchFailed);\n\treturn {\n\t\titems: items,\n\t\twarnings: warnings,\n\t\tpage: {\n\t\t\t...paged.page,\n\t\t\t// Positional, per ProviderPageInfo.currentPage: the drain (above) already advanced `paged.page` for\n\t\t\t// a cursor-only host, and a caller-threaded cursor advanced the provider without one.\n\t\t\tcurrentPage: resolveCurrentPage({\n\t\t\t\tproviderPage: paged.page.currentPage,\n\t\t\t\trequestedPage: page,\n\t\t\t\tsuppliedCursor: options.cursor,\n\t\t\t\tpageAdvanceable: issuesPageAdvanceable,\n\t\t\t}),\n\t\t\ttruncated: truncated || undefined,\n\t\t},\n\t\thasMore: continuation.hasMore,\n\t\tcursor: continuation.cursor,\n\t\tfetchFailed: assessment.fetchFailed || pageFetchFailed || undefined,\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"issues.js","names":[],"sources":["../../../../../plus/integrations/src/reads/issues.ts"],"sourcesContent":["import type { IssueShape, IssueSorting, IssueStateFilter } from '@gitlens/git/models/issue.js';\nimport { mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { GitCloudHostIntegrationId, GitSelfManagedHostIntegrationId } from '../constants.js';\nimport type { GitHostIntegration, SettledRepositoryScopes } from '../models/gitHostIntegration.js';\nimport type { IssueFilter, ProviderReposInput } from '../providers/models.js';\nimport { isRepoIdsInput, PagingMode, providersMetadata } from '../providers/models.js';\nimport { mergeCollectionMetadata } from '../providers/utils/providerPaging.js';\nimport type { ProviderPagedResult, ProviderWarning } from '../results.js';\nimport { reconcileOmissionsWithFailure } from '../results.js';\nimport {\n\tisGitHostIntegration,\n\tisIssuesHostIntegrationId,\n\twarnOnMissingSessionForDomain,\n} from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { runCaptured } from './drains.js';\nimport { resolveAccountWideIssueFilters } from './filters.js';\nimport { resolveIssueSort, toIssueOrdering } from './ordering.js';\nimport {\n\tdrainFlatPagesToRequestedPage,\n\tdrainToRequestedPage,\n\tisPageNumberAdvanceable,\n\tpageToCursor,\n\trefusedPage,\n\tresolveContinuation,\n\tresolveCurrentPage,\n\ttoProviderPageInfo,\n\tusableCursor,\n} from './paging.js';\nimport {\n\tgitHostOnlySurfaceWarning,\n\tissuesUnsupportedWarning,\n\totherWarning,\n\ttruncationWarning,\n\tunmergeableIssueSortWarning,\n\tunsupportedAccountWideIssueFiltersWarning,\n\tunsupportedIssueSortWarning,\n\tunsupportedWarning,\n} from './warnings.js';\n\nconst issueStateFilters: readonly IssueStateFilter[] = ['open', 'closed', 'all'];\n\n/**\n * Whether a repo-scoped page is assembled from SEVERAL provider queries and merged here — which is what decides\n * whether the requested order can be honored at all.\n *\n * `PagingMode.Repos` (GitHub) sends one search however many repositories are named, so its page arrives ordered by\n * the provider. `Repo` (GitLab) issues one query per repository, and `Project` (Azure) one per PROJECT — several\n * repositories of the same project are still one query, which is why that case counts distinct projects rather than\n * repositories.\n *\n * The `Repo` case has a second way to merge that has nothing to do with the count: given repository IDS rather than\n * descriptors, `getMyIssuesForReposResult` skips its per-repository fan-out entirely (that branch is guarded on\n * `!isRepoIdsInput`) and calls the SDK's `getIssuesForRepos`, which for GitLab is the multi-project aggregate that\n * merges in the SDK and refuses `priority`/`dueDate` however few scopes it was given. Treating that as merged is\n * what makes this facade refuse it up front, with the message that names the reason, instead of letting the SDK\n * reject a read the capability table had just promised.\n */\nfunction mergesProviderQueries(pagingMode: PagingMode | undefined, repos: ProviderReposInput | undefined): boolean {\n\tconst scopes = repos ?? [];\n\tswitch (pagingMode) {\n\t\tcase PagingMode.Repo:\n\t\t\t// `isRepoIdsInput` is the same predicate `ProvidersApi` selects the SDK aggregate with, so this cannot\n\t\t\t// disagree with the branch it is predicting.\n\t\t\treturn isRepoIdsInput(scopes) ? scopes.length > 0 : scopes.length > 1;\n\t\tcase PagingMode.Project:\n\t\t\t// Only the descriptor form carries a project; the id form is refused by the Azure read before it runs.\n\t\t\treturn new Set(scopes.map(r => (typeof r === 'object' ? r.project : undefined))).size > 1;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\n/**\n * What {@link listIssuesPage} accepts, shared by name with the two functions it dispatches to.\n *\n * Named rather than left inline: three functions now take this exact shape, and reaching it from the other two as\n * `Parameters<typeof listIssuesPage>[1]` would define the contract in terms of the function that consumes it —\n * inside out, and unreadable at the point where the fields are actually used.\n */\nexport interface ListIssuesPageOptions {\n\tstate?: IssueStateFilter;\n\tproviderId: IntegrationIds;\n\trepos?: ProviderReposInput;\n\t/** Narrows the account-wide read to one org/account. Requires a host with a project layer (Azure). */\n\torg?: string;\n\t/** Narrows the account-wide read to one project. Requires a host with a project layer (Azure). */\n\tproject?: string;\n\t/**\n\t * Narrows to the requested relationship(s). On the account-wide path this replaces the provider's own\n\t * definition of \"my issues\" (GitHub/GHE: authored ∪ assigned ∪ mentioned; Azure: assigned ∪ authored;\n\t * GitLab: assigned-to-me), so `[Assignee]` yields `assignee:@me` everywhere it's expressible. A set the\n\t * provider can't express server-side is refused whole (warning + `fetchFailed`), never widened — check\n\t * getSupportedFilters first to avoid that path.\n\t */\n\tfilters?: IssueFilter[];\n\t/** Broadens the read to every assignee. Contradicts `filters`; passing both is refused. */\n\tincludeAllAssignees?: boolean;\n\t/**\n\t * How to order the page, as `field:direction`. Omitted orders most-recently-updated-first wherever the\n\t * provider can express that, which is this facade's default rather than the provider's own.\n\t *\n\t * Validated against `getSupportedFilters().issueSorts` on the repo-scoped path and `.issueSortsAccountWide`\n\t * on the account-wide one — two genuinely different vocabularies for GitLab, whose two reads are different\n\t * APIs. A key the provider can't express refuses the read (warning + `fetchFailed`) rather than serving a\n\t * differently-ordered list, for the same reason an inexpressible filter does: the reachable window is\n\t * bounded, so another order is another subset.\n\t *\n\t * On the repo-scoped path with SEVERAL scopes the page is a merge of one query per repository/project, so\n\t * only a key a normalized issue carries can be honored: `priority`, `dueDate` and `resolved` are refused\n\t * there even where the provider supports them on a single scope. Order is per page, and per scope across\n\t * pages — the merge orders what a page contains, not the sequence of pages.\n\t */\n\tsort?: IssueSorting;\n\tpage?: number;\n\tcursor?: string;\n\titemsPerPage?: number;\n\tforceSync?: boolean;\n\tconnectionId?: string;\n\t/**\n\t * Explicit self-managed host domain. Used only when the requested connection has no configured domain;\n\t * it must come from the trusted authentication configuration, not repository or remote data.\n\t */\n\tdomain?: string;\n}\n\n/**\n * What both halves of {@link listIssuesPage} need from its shared prologue.\n *\n * A bundle rather than six parameters because the two reads take exactly the same inputs and differ only in what\n * they do with them; threading them positionally would make the two call sites read as two different reads of two\n * different things, which is the opposite of what is true.\n *\n * Note `ctx` is absent: every use of it — resolving the integration, the domain, the forced refresh, the\n * connection warnings — is in the prologue, which is what makes this the right cut.\n */\ninterface IssueReadContext {\n\toptions: ListIssuesPageOptions;\n\tintegration: GitHostIntegration;\n\tdomain: string | undefined;\n\twarnOnMissingSession: boolean;\n\tpage: number;\n}\n\nexport async function listIssuesPage(\n\tctx: ProviderReadContext,\n\toptions: ListIssuesPageOptions,\n): Promise<ProviderPagedResult<IssueShape>> {\n\t// Truncated as well as floored, like every other paged read here: `page` is reported back as\n\t// `page.currentPage` and compared against the position a drain reached, and neither can be fractional.\n\tconst page = Math.max(1, Math.trunc(options.page ?? 1));\n\tif (isIssuesHostIntegrationId(options.providerId)) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, 'repository issue reads')],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\t// A supplied connection or domain that no longer resolves is a broken target, not an empty account —\n\t\t// surface a no-connection warning + fetchFailed rather than a silent empty page.\n\t\tconst early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);\n\t\treturn refusedPage(page, early.warnings, early.fetchFailed);\n\t}\n\tif (!isGitHostIntegration(integration)) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, 'repository issue reads')],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tawait ctx.forceRefreshIfRequested(integration, options.forceSync, options.connectionId);\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\tconst warnOnMissingSession = warnOnMissingSessionForDomain(options.providerId, options.domain);\n\n\t// A git host whose issue tracker is deprecated (Bitbucket, superseded by dedicated issue integrations)\n\t// reports issues as explicitly unsupported rather than serving a partial/legacy source or a silent empty.\n\tif (!integration.supportsIssues) {\n\t\treturn refusedPage(page, [issuesUnsupportedWarning(options.providerId, domain, options.connectionId)], true);\n\t}\n\n\tif (\n\t\toptions.state != null &&\n\t\t(!issueStateFilters.includes(options.state) ||\n\t\t\t(options.state !== 'open' && !providersMetadata[options.providerId]?.supportsIssueStates))\n\t) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[\n\t\t\t\tissueStateFilters.includes(options.state)\n\t\t\t\t\t? unsupportedWarning(\n\t\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\t\tdomain,\n\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t\t`Issue state '${options.state}' is not supported by '${options.providerId}'.`,\n\t\t\t\t\t\t)\n\t\t\t\t\t: otherWarning(\n\t\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\t\tdomain,\n\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t\t`Unknown issue state; expected one of ${issueStateFilters.join(', ')}.`,\n\t\t\t\t\t\t),\n\t\t\t],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\t// `repos` is what selects between the two: with none there is no scope to query per repository, so the\n\t// provider's own account-wide search answers instead. They are different provider queries with different\n\t// capability tables, cursors and completeness signals — see each function.\n\tconst context: IssueReadContext = {\n\t\toptions: options,\n\t\tintegration: integration,\n\t\tdomain: domain,\n\t\twarnOnMissingSession: warnOnMissingSession,\n\t\tpage: page,\n\t};\n\treturn (options.repos?.length ?? 0) === 0 ? readAccountWideIssuesPage(context) : readRepoScopedIssuesPage(context);\n}\n\n/**\n * The account-wide read: the provider's own definition of \"my issues\", behind a composite cursor that spans\n * several provider searches.\n *\n * Its own function rather than a branch of the paged read, because almost nothing is shared past the guards: it\n * validates a different filter table and a different sort table, it is cursor-only (a page number cannot address\n * a composite cursor), it accumulates per-scope failures across the pages it walks, and it always merges several\n * queries. The two used to share one 460-line function behind a mode flag, and the comments explaining how far\n * apart they are were the clue that they wanted separating.\n */\nasync function readAccountWideIssuesPage({\n\toptions,\n\tintegration,\n\tdomain,\n\twarnOnMissingSession,\n\tpage,\n}: IssueReadContext): Promise<ProviderPagedResult<IssueShape>> {\n\t// The account-wide read is cursor-only, so a refusal can't claim the requested position — it reports\n\t// page 1, per ProviderPageInfo.currentPage.\n\tconst refused = (warning: ProviderWarning) => refusedPage<IssueShape>(1, [warning], true);\n\n\t// Checked before the provider-specific guards below: a caller passing both has a contradictory request\n\t// whatever the provider, and saying so is more useful than reporting one half of it as unsupported.\n\t// `filters` narrows this read to a relationship (`[Assignee]` ⇒ just assigned-to-me); `includeAllAssignees`\n\t// broadens it to every assignee. Honoring either silently would answer a question the caller didn't ask.\n\tif (options.filters?.length && options.includeAllAssignees === true) {\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'`filters` and `includeAllAssignees` are contradictory for an account-wide issue read; pass only one.',\n\t\t\t),\n\t\t);\n\t}\n\n\t// GitHub expresses \"any assignee\" as `assignee:*`, which is meaningless without a scope: unscoped it\n\t// matches millions of issues across all of GitHub instead of the user's own world. ANY scope makes it\n\t// meaningful (one repository, several, or an org) — it is specifically THIS branch, the account-wide read,\n\t// that has none to offer. Scope the read to repositories (or use `searchIssuesPage`, whose criteria model\n\t// takes `any-assignee` alongside a `repos`/`org` scope) rather than reading unscoped.\n\tif (\n\t\toptions.includeAllAssignees === true &&\n\t\t(options.providerId === GitCloudHostIntegrationId.GitHub ||\n\t\t\toptions.providerId === GitSelfManagedHostIntegrationId.CloudGitHubEnterprise)\n\t) {\n\t\treturn refused(\n\t\t\tunsupportedWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'`includeAllAssignees` is not supported for account-wide GitHub issue reads; scope the read to repositories instead.',\n\t\t\t),\n\t\t);\n\t}\n\n\t// Only a host with a project layer can narrow server-side. Reject the request rather than serving an\n\t// unscoped list as if it had been narrowed: the caller would otherwise have to filter client-side,\n\t// which desynchronizes the filtered `items` from this read's `hasMore`/`currentPage` and shows\n\t// \"no issues\" for a page that simply held none of the requested project's.\n\tif ((options.org != null || options.project != null) && !integration.supportsProjectDiscovery) {\n\t\treturn refused(\n\t\t\tunsupportedWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`Project-scoped issue reads are not supported by '${options.providerId}'; scope the read to repositories instead.`,\n\t\t\t),\n\t\t);\n\t}\n\n\t// Narrowing the account-wide read is only honest when the provider can express it server-side: its\n\t// per-relationship queries produced the page and the cursor together, so dropping items afterward would\n\t// leave `items` describing a different result set than `hasMore`/`currentPage`.\n\tconst resolvedIssueFilters = resolveAccountWideIssueFilters(options.providerId, options.filters);\n\tif (resolvedIssueFilters.unsupported) {\n\t\treturn refused(\n\t\t\tunsupportedAccountWideIssueFiltersWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\toptions.filters!,\n\t\t\t),\n\t\t);\n\t}\n\n\t// Every provider's account-wide read is a UNION of several queries — GitHub's three `@me` searches,\n\t// GitLab's one REST call per relationship, Azure's (project x relationship) drains — so it always merges,\n\t// and `supportedAccountWideIssueSorts` already lists only keys a merge can honor. No separate\n\t// mergeability refusal is needed here, unlike on the repo-scoped path below.\n\tconst resolvedSort = resolveIssueSort(\n\t\tprovidersMetadata[options.providerId]?.supportedAccountWideIssueSorts,\n\t\toptions.sort,\n\t);\n\tif (resolvedSort.rejection != null) {\n\t\treturn refused(\n\t\t\tunsupportedIssueSortWarning(options.providerId, domain, options.connectionId, resolvedSort.rejection),\n\t\t);\n\t}\n\n\t// `merged: true` unconditionally: this read has no scope count for a caller to reduce. It is also why no\n\t// `unmergeable` check follows — `supportedAccountWideIssueSorts` declares only keys a merge can honor.\n\tconst accountWideOrdering = toIssueOrdering(resolvedSort.sort, true);\n\n\t// The repo-scoped core rejects empty repos (GitHub/Bitbucket/Azure); read the account-wide,\n\t// already-user-scoped core instead. GitHub exposes a composite cursor across its authored,\n\t// assigned, and mentioned searches. Walk it internally when the caller supplies only page N.\n\tconst readAccountWidePage = (cursor: string | undefined) =>\n\t\trunCaptured(\n\t\t\toptions.providerId,\n\t\t\tdomain,\n\t\t\toptions.connectionId,\n\t\t\t() =>\n\t\t\t\tintegration.searchMyIssuesWithTruncationResult(undefined, undefined, options.connectionId, {\n\t\t\t\t\tincludeAllAssignees: options.includeAllAssignees,\n\t\t\t\t\tstate: options.state,\n\t\t\t\t\tfilters: resolvedIssueFilters.filters,\n\t\t\t\t\tcursor: cursor,\n\t\t\t\t\tpageSize: options.itemsPerPage,\n\t\t\t\t\torg: options.org,\n\t\t\t\t\tproject: options.project,\n\t\t\t\t\tsort: accountWideOrdering.sort,\n\t\t\t\t}),\n\t\t\t{ warnOnMissingSession: warnOnMissingSession },\n\t\t);\n\tconst first = await readAccountWidePage(options.cursor);\n\tconst warnings = first.warning != null ? [first.warning] : [];\n\t// Merged across the walked pages, so a per-scope failure reported on an earlier page isn't lost by\n\t// paging past it — the one thing this read accumulates that the filtered search doesn't.\n\tlet allMetadata = first.value?.metadata;\n\tconst drained = await drainFlatPagesToRequestedPage(first, {\n\t\trequestedPage: page,\n\t\tsuppliedCursor: options.cursor,\n\t\twarnings: warnings,\n\t\treadPage: readAccountWidePage,\n\t\tfold: p => {\n\t\t\tallMetadata = mergeCollectionMetadata(allMetadata, p.metadata, 'disjoint');\n\t\t},\n\t});\n\tconst { value, currentPage, requestedPageMissing } = drained;\n\tconst currentTruncated = drained.truncated;\n\tconst pageFetchFailed = drained.fetchFailed;\n\n\t// GitHub, GitLab, and Azure implement an account-wide issue search; a provider that doesn't (Bitbucket\n\t// exposes no issues at all, and `supportsIssues` already short-circuits it above) returns `undefined`\n\t// with no error. Surface that as an explicit unsupported warning + fetchFailed rather than a silent\n\t// empty success — the caller must fall back (e.g. broadenIssues over repos).\n\tif (value == null && warnings.length === 0) {\n\t\treturn refused(\n\t\t\tunsupportedWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`Account-wide issue search is not supported by '${options.providerId}'; scope the read to repositories instead.`,\n\t\t\t),\n\t\t);\n\t}\n\n\tconst items = requestedPageMissing ? [] : (value?.values ?? []);\n\t// Each query arrived ordered by the provider; their union is not, and the union is what this read publishes.\n\t// Idempotent for GitHub, whose aliased-search engine already ordered the same merge — one call here rather\n\t// than the same logic repeated in each of the three provider implementations.\n\tconst orderedItems = accountWideOrdering.order(items);\n\t// Fold in structured per-scope failures from the account-wide fan-out (e.g. Azure across projects):\n\t// scope-aware warnings + `fetchFailed` when a scope failed, without discarding the successful items.\n\tconst assessment = mergeAssessmentInto(warnings, options.providerId, domain, options.connectionId, allMetadata);\n\t// An account-wide search that couldn't confirm completeness (a provider cap with no cursor, or a\n\t// per-scope backstop/failure) is incomplete and can't be paged; report it as truncated (+ a\n\t// provider-neutral warning, unless a structured failure already explains it) rather than a complete\n\t// list. Don't hard-code GitHub's \"100 per category\" cap here — Azure reaches this via a per-project\n\t// backstop, and other providers may cap differently.\n\t// The account-wide read is cursor-only (its composite cursor spans several provider searches, so a\n\t// page number can't address it): `hasMore` without a real cursor is a dead end, so report it as\n\t// terminal-but-incomplete rather than inviting the caller to page forever.\n\tconst continuation = resolveContinuation(\n\t\t{\n\t\t\thasMore: requestedPageMissing ? false : (value?.hasMore ?? false),\n\t\t\tcursor: requestedPageMissing ? undefined : usableCursor(value?.cursor),\n\t\t\ttruncated: currentTruncated,\n\t\t},\n\t\tundefined,\n\t);\n\tconst truncated = continuation.truncated || assessment.truncated;\n\tif (truncated && warnings.length === 0) {\n\t\twarnings.push(\n\t\t\ttruncationWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'Account-wide issue search',\n\t\t\t\t// `exhausted`: this composite read exposes no budget the caller can raise, so nothing it\n\t\t\t\t// could call would return the withheld items.\n\t\t\t\tassessment.fetchFailed || pageFetchFailed ? 'interrupted' : 'exhausted',\n\t\t\t),\n\t\t);\n\t}\n\t// A metadata omission from an earlier page asserts the read succeeded; a later page may since have failed.\n\treconcileOmissionsWithFailure(warnings, assessment.fetchFailed || pageFetchFailed);\n\treturn {\n\t\titems: orderedItems,\n\t\twarnings: warnings,\n\t\tpage: {\n\t\t\t// Positional, per ProviderPageInfo.currentPage. `currentPage` already carries what the provider\n\t\t\t// reported or what the internal drain counted; a requested page past the terminal cursor is\n\t\t\t// reported as that empty page N. The account-wide read is cursor-only, so a `page` the caller\n\t\t\t// didn't pair with a cursor is never echoed.\n\t\t\tcurrentPage: requestedPageMissing\n\t\t\t\t? page\n\t\t\t\t: resolveCurrentPage({\n\t\t\t\t\t\tproviderPage: currentPage,\n\t\t\t\t\t\trequestedPage: page,\n\t\t\t\t\t\tsuppliedCursor: options.cursor,\n\t\t\t\t\t\tpageAdvanceable: false,\n\t\t\t\t\t}),\n\t\t\titemsPerPage: orderedItems.length,\n\t\t\ttruncated: truncated || undefined,\n\t\t},\n\t\thasMore: continuation.hasMore,\n\t\tcursor: continuation.cursor,\n\t\tfetchFailed: assessment.fetchFailed || pageFetchFailed || undefined,\n\t};\n}\n\n/**\n * The repository-scoped read: one provider query per repository or project, paged by the provider's own\n * continuation and drained internally when the caller supplied only a page number.\n *\n * See {@link readAccountWideIssuesPage} for why the two are separate functions.\n */\nasync function readRepoScopedIssuesPage({\n\toptions,\n\tintegration,\n\tdomain,\n\twarnOnMissingSession,\n\tpage,\n}: IssueReadContext): Promise<ProviderPagedResult<IssueShape>> {\n\tconst metadata = providersMetadata[options.providerId];\n\n\t// How many separate provider queries this page is assembled from, which decides whether the requested order can\n\t// be honored at all. `PagingMode.Repos` (GitHub) sends ONE search however many repositories are named, so its\n\t// page arrives ordered by the provider; `Repo` (GitLab) and `Project` (Azure) issue one query per scope and are\n\t// concatenated here, so a page spanning several of them is only as orderable as a normalized issue is.\n\tconst resolvedRepoScopedSort = resolveIssueSort(metadata?.supportedIssueSorts, options.sort);\n\tif (resolvedRepoScopedSort.rejection != null) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[\n\t\t\t\tunsupportedIssueSortWarning(\n\t\t\t\t\toptions.providerId,\n\t\t\t\t\tdomain,\n\t\t\t\t\toptions.connectionId,\n\t\t\t\t\tresolvedRepoScopedSort.rejection,\n\t\t\t\t),\n\t\t\t],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tconst issuesPagingMode = metadata?.issuesPagingMode;\n\t// Counted, and read, as the host settles them: a scope can name another project than the one it was parsed with\n\t// (see `GitHostIntegration.resolveRepositoryScopes`), which changes how many queries a per-project read makes. The\n\t// read is handed the settled scopes, flags included, so it reads what was counted rather than settling again.\n\tconst scopes = options.repos ?? [];\n\tlet repos: ProviderReposInput | SettledRepositoryScopes = scopes;\n\tlet counted = scopes;\n\tif (issuesPagingMode === PagingMode.Project && scopes.length) {\n\t\tconst settled = await runCaptured(\n\t\t\toptions.providerId,\n\t\t\tdomain,\n\t\t\toptions.connectionId,\n\t\t\t() => integration.resolveRepositoryScopes(scopes, options.connectionId),\n\t\t\t{ warnOnMissingSession: warnOnMissingSession },\n\t\t);\n\t\t// No session to read with, or the read's own failure resolving it: answered as that read would, without it.\n\t\tif (settled.value == null) {\n\t\t\treturn refusedPage(page, settled.warning != null ? [settled.warning] : [], settled.warning != null);\n\t\t}\n\n\t\trepos = settled.value;\n\t\tcounted = settled.value.repos;\n\t}\n\tconst ordering = toIssueOrdering(resolvedRepoScopedSort.sort, mergesProviderQueries(issuesPagingMode, counted));\n\t// Refused rather than served as concatenated per-scope runs, which would look ordered without being so — and\n\t// only where it actually merges, since the same key against the same provider is perfectly answerable for a\n\t// single repository or project.\n\tif (ordering.unmergeable != null) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[\n\t\t\t\tunmergeableIssueSortWarning(\n\t\t\t\t\toptions.providerId,\n\t\t\t\t\tdomain,\n\t\t\t\t\toptions.connectionId,\n\t\t\t\t\tordering.unmergeable,\n\t\t\t\t\tissuesPagingMode === PagingMode.Project ? 'projects' : 'repositories',\n\t\t\t\t),\n\t\t\t],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tconst cursor = options.cursor ?? pageToCursor(page);\n\tconst { value, warning } = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\t() =>\n\t\t\t// The shapes seam returns normalized IssueShape, and is overridable by a provider whose only issue\n\t\t\t// client already yields shapes (serving this path without a raw ProviderIssue round-trip).\n\t\t\tintegration.getMyIssuesForReposAsShapesResult(\n\t\t\t\trepos,\n\t\t\t\t// Forward `page`/`pageSize` alongside the cursor so PagingMode.Repo/Project hosts honor the\n\t\t\t\t// requested page and page size rather than ignoring a synthesized page-number cursor.\n\t\t\t\t{\n\t\t\t\t\tfilters: options.filters,\n\t\t\t\t\tincludeAllAssignees: options.includeAllAssignees,\n\t\t\t\t\tstate: options.state,\n\t\t\t\t\tcursor: cursor,\n\t\t\t\t\t// The normalized `page`, so the number forwarded to the provider can't disagree with the\n\t\t\t\t\t// page-number cursor beside it — both are built from the same value. Only when the caller asked\n\t\t\t\t\t// for one: an omitted page is the provider's own first page.\n\t\t\t\t\tpage: options.page != null ? page : undefined,\n\t\t\t\t\tpageSize: options.itemsPerPage,\n\t\t\t\t\tsort: ordering.sort,\n\t\t\t\t},\n\t\t\t\toptions.connectionId,\n\t\t\t),\n\t\t{ warnOnMissingSession: warnOnMissingSession },\n\t);\n\n\tlet items = value?.values ?? [];\n\tconst warnings = warning != null ? [warning] : [];\n\tlet pageFetchFailed = warning != null && value == null;\n\tlet paged = toProviderPageInfo(options.itemsPerPage ?? items.length, value?.paging);\n\tlet allMetadata = value?.metadata;\n\n\t// Cursor-only repo-scoped hosts (e.g. GitHub) ignore a synthesized page-number cursor, so a page-only\n\t// request is advanced through the provider's own continuations (see drainToRequestedPage).\n\tif (\n\t\tissuesPagingMode === PagingMode.Repos &&\n\t\toptions.page != null &&\n\t\tpage > 1 &&\n\t\toptions.cursor == null &&\n\t\tpaged.page.currentPage === 1\n\t) {\n\t\tconst drained = await drainToRequestedPage(\n\t\t\t{ items: items, paged: paged, metadata: allMetadata, fetchFailed: pageFetchFailed },\n\t\t\t{\n\t\t\t\trequestedPage: page,\n\t\t\t\titemsPerPage: options.itemsPerPage,\n\t\t\t\twarnings: warnings,\n\t\t\t\treadPage: (cursor: string) =>\n\t\t\t\t\trunCaptured(\n\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tintegration.getMyIssuesForReposAsShapesResult(\n\t\t\t\t\t\t\t\trepos,\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tfilters: options.filters,\n\t\t\t\t\t\t\t\t\tincludeAllAssignees: options.includeAllAssignees,\n\t\t\t\t\t\t\t\t\tstate: options.state,\n\t\t\t\t\t\t\t\t\tcursor: cursor,\n\t\t\t\t\t\t\t\t\tpageSize: options.itemsPerPage,\n\t\t\t\t\t\t\t\t\tsort: ordering.sort,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t{ warnOnMissingSession: warnOnMissingSession },\n\t\t\t\t\t),\n\t\t\t},\n\t\t);\n\t\titems = drained.items;\n\t\tpaged = drained.paged;\n\t\tallMetadata = drained.metadata;\n\t\tpageFetchFailed = drained.fetchFailed;\n\t}\n\n\t// Ordered once the drain (above) has assembled whichever pages this position spans.\n\titems = ordering.order(items);\n\n\t// Convert the SDK collection metadata into scope-aware warnings + failure/truncation flags, appending\n\t// them to any captured thrown-error warning without discarding the partial result's items.\n\tconst assessment = mergeAssessmentInto(warnings, options.providerId, domain, options.connectionId, allMetadata);\n\t// Never advertise `hasMore` without a continuation the caller can act on, and only synthesize a page\n\t// number for a host that reads it as one (see `isPageNumberAdvanceable`).\n\tconst issuesPageAdvanceable = isPageNumberAdvanceable(issuesPagingMode);\n\tconst continuation = resolveContinuation(paged, issuesPageAdvanceable ? paged.page.currentPage + 1 : undefined);\n\t// A provider read that couldn't confirm completeness (e.g. Bitbucket's single-page repo issue read\n\t// that dropped a repo) sets `paging.truncated`; surface it as a terminal `page.truncated` so a partial\n\t// page isn't published as complete. Metadata incompleteness is an independent source of the same signal.\n\tconst truncated = continuation.truncated || assessment.truncated;\n\tif (truncated && warnings.length === 0) {\n\t\twarnings.push(\n\t\t\ttruncationWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'Issue',\n\t\t\t\t// `exhausted`, never `page-budget`: a paged read has no budget the caller can raise, and\n\t\t\t\t// ordinary continuation is already expressed by `hasMore`/`cursor`.\n\t\t\t\tassessment.fetchFailed || pageFetchFailed ? 'interrupted' : 'exhausted',\n\t\t\t),\n\t\t);\n\t}\n\t// A metadata omission from an earlier page asserts the read succeeded; a later page may since have failed.\n\treconcileOmissionsWithFailure(warnings, assessment.fetchFailed || pageFetchFailed);\n\treturn {\n\t\titems: items,\n\t\twarnings: warnings,\n\t\tpage: {\n\t\t\t...paged.page,\n\t\t\t// Positional, per ProviderPageInfo.currentPage: the drain (above) already advanced `paged.page` for\n\t\t\t// a cursor-only host, and a caller-threaded cursor advanced the provider without one.\n\t\t\tcurrentPage: resolveCurrentPage({\n\t\t\t\tproviderPage: paged.page.currentPage,\n\t\t\t\trequestedPage: page,\n\t\t\t\tsuppliedCursor: options.cursor,\n\t\t\t\tpageAdvanceable: issuesPageAdvanceable,\n\t\t\t}),\n\t\t\ttruncated: truncated || undefined,\n\t\t},\n\t\thasMore: continuation.hasMore,\n\t\tcursor: continuation.cursor,\n\t\tfetchFailed: assessment.fetchFailed || pageFetchFailed || undefined,\n\t};\n}\n"],"mappings":"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"}
@@ -9,6 +9,7 @@ import { getIntegrationIdForRemote, getSelfManagedBaseUrl, isGitCloudHostIntegra
9
9
  import { isAzureCloudDomain, isBitbucketCloudDomain, isGitHubDotCom, isGitLabDotCom } from "../providers/models.js";
10
10
  import { noConnectionWarning } from "./warnings.js";
11
11
  import { isIssuesIntegration } from "../models/issuesIntegration.js";
12
+ import { getAzureRepositoryIdentity } from "../providers/azure/azure.utils.js";
12
13
  //#region ../plus/integrations/src/reads/resolveRepository.ts
13
14
  /**
14
15
  * Resolution of a remote URL to a provider repository identity — the facade's equivalent of `gk repo resolve`.
@@ -119,7 +120,10 @@ async function resolveRepository(ctx, options) {
119
120
  try {
120
121
  const failure = await integration.settleRepositoryRefusal({
121
122
  owner,
122
- name: project != null ? getAzureRepositoryName(name) : name,
123
+ name: project != null ? getAzureRepositoryIdentity({
124
+ owner,
125
+ name
126
+ }).repositoryName : name,
123
127
  project,
124
128
  connectionId
125
129
  }, ex);
@@ -145,10 +149,6 @@ async function resolveRepository(ctx, options) {
145
149
  return { resolution };
146
150
  }
147
151
  }
148
- /** The repository an Azure remote's `{project}/_git/{repo}` name addresses, as a batch target names it. */
149
- function getAzureRepositoryName(name) {
150
- return /\/_git\/([^/]+)$/i.exec(name)?.[1] ?? name;
151
- }
152
152
  /** Whether `path` is `{x}/_git/{repo}`, the Azure repository path the remote parser reads without a project segment. */
153
153
  function isProjectlessAzurePath(path) {
154
154
  const segments = path?.split("/");
@@ -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\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
+ {"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 { getAzureRepositoryIdentity } from '../providers/azure/azure.utils.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:\n\t\t\t\t\t\t\tproject != null\n\t\t\t\t\t\t\t\t? getAzureRepositoryIdentity({ owner: owner, name: name }).repositoryName\n\t\t\t\t\t\t\t\t: 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/** 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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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gitkraken/core-gitlens",
3
3
  "description": "GitLens core — shared Git / AI / GitHub primitives for internal GitKraken consumption",
4
- "version": "0.15.3",
4
+ "version": "0.15.4",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": {
7
7
  "name": "GitKraken",