@gitkraken/core-gitlens 0.9.0 → 0.9.1

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.
@@ -1,5 +1,6 @@
1
1
  import { chunk } from "../../../utils/array.js";
2
2
  import { batch, mapBounded } from "../../../utils/promise.js";
3
+ import { AuthenticationError, RequestNotFoundError } from "../../../git/errors.js";
3
4
  import { getGitHubNoReplyAddressParts } from "../../../git/remotes/github.js";
4
5
  import "../../../git/models/pullRequest.js";
5
6
  import { IntegrationReadUnavailableError } from "../errors.js";
@@ -191,7 +192,44 @@ var GitHubIntegrationBase = class extends GitHostIntegration {
191
192
  const api = await this.getProvidersApi();
192
193
  const session = await this.resolveReadSession(repo.connectionId, void 0);
193
194
  if (session == null) return void 0;
194
- return api.getRepo(toTokenWithInfo(this.id, session), repo.owner, repo.name, repo.project, { baseUrl: this.apiBaseUrlFor(session) });
195
+ try {
196
+ return await api.getRepo(toTokenWithInfo(this.id, session), repo.owner, repo.name, repo.project, { baseUrl: this.apiBaseUrlFor(session) });
197
+ } catch (ex) {
198
+ if (!(ex instanceof RequestNotFoundError)) throw ex;
199
+ const github = await this.authenticationService.apis.github;
200
+ try {
201
+ await github?.getRepositoryAccess(this, toTokenWithInfo(this.id, session), repo.owner, repo.name, { baseUrl: this.apiBaseUrlFor(session) });
202
+ } catch (refusal) {
203
+ if (refusal instanceof AuthenticationError) throw refusal;
204
+ }
205
+ throw ex;
206
+ }
207
+ }
208
+ /**
209
+ * The profile request that proves the credential; see `IntegrationBase.validateCredential`. Silent: a refusal
210
+ * here is rethrown to the read that asked, which reports it as it reports its own failure (or, for
211
+ * `resolveRepository`, as the resolution's warning), so the check must not raise a prompt of its own.
212
+ */
213
+ async validateCredential(session) {
214
+ if (await (await this.authenticationService.apis.github)?.getCurrentAccount(this, toTokenWithInfo(this.id, session), {
215
+ baseUrl: this.apiBaseUrlFor(session),
216
+ silent: true
217
+ }) == null) throw new Error("GitHub did not confirm the credential");
218
+ }
219
+ /**
220
+ * Names a confirmed credential's refusal from what GitHub said. A `403` whose explanation cites OAuth App access
221
+ * restrictions is an organization that has not approved the OAuth app this token belongs to; its remedy page is
222
+ * that app's page in the user's authorized apps, where they request an organization's approval, addressed by the
223
+ * client id GitHub reports on the refusal. Any other refusal is left unnamed, so the warning keeps GitHub's own
224
+ * words.
225
+ */
226
+ describeRefusal(session, refusal) {
227
+ if (refusal.status !== 403 || !/\bOAuth App access restrictions\b/i.test(refusal.detail ?? "")) return;
228
+ const webUrl = this.id === "cloud-github-enterprise" ? this.getSelfManagedInstallationUrl(session) : `https://${this.domain}`;
229
+ return {
230
+ reason: "oauth-app-not-allowed",
231
+ ...refusal.oauthClientId != null ? { remedyUrl: `${webUrl}/settings/connections/applications/${encodeURIComponent(refusal.oauthClientId)}` } : {}
232
+ };
195
233
  }
196
234
  async searchProviderMyPullRequests(session, repos, cancellation, options) {
197
235
  return (await this.authenticationService.apis.github)?.searchMyPullRequests(this, toTokenWithInfo(this.id, session), {
@@ -1 +1 @@
1
- {"version":3,"file":"github.js","names":[],"sources":["../../../../../plus/integrations/src/providers/github.ts"],"sourcesContent":["import { gitHubPullRequestRelationshipQualifiers } from '@gitlens/git-github/api/pullRequestSearchQuery.js';\nimport type { Account, UnidentifiedAuthor } from '@gitlens/git/models/author.js';\nimport type { DefaultBranch } from '@gitlens/git/models/defaultBranch.js';\nimport type { Issue, IssueSearchCriteria, IssueShape } from '@gitlens/git/models/issue.js';\nimport type { IssueOrPullRequest } from '@gitlens/git/models/issueOrPullRequest.js';\nimport type {\n\tPullRequest,\n\tPullRequestMergeMethod,\n\tPullRequestSearchCriteria,\n\tPullRequestShape,\n\tPullRequestStackInfo,\n\tPullRequestState,\n\tPullRequestStateFilter,\n} from '@gitlens/git/models/pullRequest.js';\nimport type { RepositoryMetadata } from '@gitlens/git/models/repositoryMetadata.js';\nimport type { RepositoryDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { getGitHubNoReplyAddressParts } from '@gitlens/git/remotes/github.js';\nimport type { PullRequestUrlIdentity } from '@gitlens/git/utils/pullRequest.utils.js';\nimport { chunk } from '@gitlens/utils/array.js';\nimport type { Emitter } from '@gitlens/utils/event.js';\nimport { batch, mapBounded } from '@gitlens/utils/promise.js';\nimport type { IntegrationAuthenticationProviderDescriptor } from '../authentication/integrationAuthenticationProvider.js';\nimport type { IntegrationAuthenticationService } from '../authentication/integrationAuthenticationService.js';\nimport type { ProviderAuthenticationSession } from '../authentication/models.js';\nimport { toTokenWithInfo } from '../authentication/models.js';\nimport { toCollectionScopeFailure } from '../collectionMetadata.js';\nimport { GitCloudHostIntegrationId, GitSelfManagedHostIntegrationId, providerFanOutConcurrency } from '../constants.js';\nimport type { IntegrationServiceContext } from '../context.js';\nimport { IntegrationReadUnavailableError } from '../errors.js';\nimport type { IntegrationConnectionChangeEvent } from '../integrationService.js';\nimport type { SearchMyPullRequestsOptions, SearchPullRequestsOptions } from '../models/gitHostIntegration.js';\nimport { GitHostIntegration } from '../models/gitHostIntegration.js';\nimport type {\n\tProviderIssueSearchPage,\n\tProviderPullRequestCount,\n\tProviderPullRequestSearchPage,\n\tSearchMyIssuesOptions,\n} from '../models/integration.js';\nimport type { GitHubIntegrationIds } from './github/github.utils.js';\nimport { getGitHubPullRequestIdentityFromMaybeUrl } from './github/github.utils.js';\nimport type {\n\tProviderApiPagedResult,\n\tProviderHierarchyResult,\n\tProviderOrganization,\n\tProviderPullRequest,\n\tProviderRepoInput,\n\tProviderRepository,\n} from './models.js';\nimport {\n\tfromProviderPullRequest,\n\tgetProviderPullRequestIdentity,\n\tIssueFilter,\n\tprovidersMetadata,\n\tPullRequestFilter,\n\ttoProviderPullRequest,\n} from './models.js';\nimport type { ProvidersApi } from './providersApi.js';\n\ntype GitHubPullRequestFacetCursor = Record<string, string>;\n\nfunction toPullRequestFacetCursor(value: unknown): GitHubPullRequestFacetCursor {\n\tif (value == null || typeof value !== 'object' || Array.isArray(value)) return {};\n\n\treturn Object.fromEntries(\n\t\tObject.entries(value).filter(\n\t\t\t([key, cursor]) => key.length !== 0 && typeof cursor === 'string' && cursor.length !== 0,\n\t\t),\n\t);\n}\n\nfunction parsePullRequestFacetCursor(cursor: string | undefined): GitHubPullRequestFacetCursor {\n\tif (!cursor) return {};\n\n\ttry {\n\t\tconst parsed = JSON.parse(cursor) as unknown;\n\t\tif (parsed != null && typeof parsed === 'object' && !Array.isArray(parsed) && 'cursors' in parsed) {\n\t\t\tconst wrapped = parsed as { type?: unknown; cursors?: unknown };\n\t\t\tif (wrapped.type === 'cursor') {\n\t\t\t\treturn toPullRequestFacetCursor(wrapped.cursors);\n\t\t\t}\n\t\t}\n\n\t\treturn toPullRequestFacetCursor(parsed);\n\t} catch {\n\t\treturn {};\n\t}\n}\n\nconst metadata = providersMetadata[GitCloudHostIntegrationId.GitHub];\nconst authProvider: IntegrationAuthenticationProviderDescriptor = Object.freeze({\n\tid: metadata.id,\n\tscopes: metadata.scopes,\n});\n\nconst cloudEnterpriseMetadata = providersMetadata[GitSelfManagedHostIntegrationId.CloudGitHubEnterprise];\nconst cloudEnterpriseAuthProvider: IntegrationAuthenticationProviderDescriptor = Object.freeze({\n\tid: cloudEnterpriseMetadata.id,\n\tscopes: cloudEnterpriseMetadata.scopes,\n});\n\nexport type GitHubRepositoryDescriptor = RepositoryDescriptor;\n\n/** How many per-login SSH signing-key lookups to run concurrently, to avoid a request burst that trips rate limiting. */\nconst sshSigningKeyResolveBatchSize = 10;\n\n// One aliased document costs about the same as a single read up to 25 targets; past that, latency climbs.\nconst pullRequestsBatchChunkSize = 25;\n\n/**\n * How many coordinates go into one aliased issue request.\n *\n * Measured rather than inherited from `issueCountChunkSize`, since that one rests on a zero-node selection and\n * this carries the full issue projection per alias. Against the live API on all-resolving coordinates: 10 took\n * 875ms, 25 took 890ms — so up to 25 is effectively free — 50 took 1.3s, and 75 took 2.7s. No complexity refusal\n * appeared up to 100.\n *\n * 25 is where latency is still flat, with room before it degrades. It also puts any realistic correlation batch\n * in ONE request, which is the whole point of the read.\n */\nconst issuesBatchChunkSize = 25;\n\n/**\n * Runs an aliased batch read over `items` in chunks of `chunkSize`, with bounded concurrency, converting each\n * fulfilled slot with `map`. Settled per target throughout: a chunk that throws rejects only its own slots, and a\n * slot whose conversion throws rejects only itself, so one bad row never takes its chunk's siblings down with it.\n */\nasync function readChunked<T, V, R>(\n\titems: readonly T[],\n\tchunkSize: number,\n\tread: (chunk: T[]) => Promise<PromiseSettledResult<V>[]>,\n\tmap: (value: V) => R,\n): Promise<PromiseSettledResult<R>[]> {\n\tconst settledChunks = await mapBounded(\n\t\tchunk([...items], chunkSize),\n\t\tproviderFanOutConcurrency,\n\t\tasync (chunkItems): Promise<PromiseSettledResult<R>[]> => {\n\t\t\tlet slots: PromiseSettledResult<V>[];\n\t\t\ttry {\n\t\t\t\tslots = await read(chunkItems);\n\t\t\t} catch (ex) {\n\t\t\t\treturn chunkItems.map(() => ({ status: 'rejected', reason: ex }));\n\t\t\t}\n\n\t\t\treturn slots.map((slot): PromiseSettledResult<R> => {\n\t\t\t\tif (slot.status === 'rejected') return slot;\n\n\t\t\t\ttry {\n\t\t\t\t\treturn { status: 'fulfilled', value: map(slot.value) };\n\t\t\t\t} catch (ex) {\n\t\t\t\t\treturn { status: 'rejected', reason: ex };\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t);\n\treturn settledChunks.flat();\n}\n\nabstract class GitHubIntegrationBase<ID extends GitHubIntegrationIds> extends GitHostIntegration<\n\tID,\n\tGitHubRepositoryDescriptor\n> {\n\t/**\n\t * Base URL handed to `@gitkraken/provider-apis`, which derives BOTH the REST and the GraphQL endpoint from it\n\t * by appending GitHub Enterprise's paths. Only a GHE instance base belongs here; cloud passes `undefined`,\n\t * the sole value that selects the cloud endpoints (see the cloud subclass).\n\t */\n\tprotected abstract apiBaseUrlFor(session: ProviderAuthenticationSession): string | undefined;\n\n\tprotected override async getProviderAccountForCommit(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitHubRepositoryDescriptor,\n\t\trev: string,\n\t\toptions?: {\n\t\t\tavatarSize?: number;\n\t\t},\n\t): Promise<Account | UnidentifiedAuthor | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.getAccountForCommit(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\trev,\n\t\t\t{\n\t\t\t\t...options,\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderAccountForEmail(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitHubRepositoryDescriptor,\n\t\temail: string,\n\t\toptions?: {\n\t\t\tavatarSize?: number;\n\t\t},\n\t): Promise<Account | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.getAccountForEmail(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\temail,\n\t\t\t{\n\t\t\t\t...options,\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderSshSigningKeysForEmails(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitHubRepositoryDescriptor,\n\t\temails: string[],\n\t): Promise<Map<string, string[]>> {\n\t\tconst result = new Map<string, string[]>();\n\n\t\tconst api = await this.authenticationService.apis.github;\n\t\tif (api == null) return result;\n\n\t\tconst token = toTokenWithInfo(this.id, session);\n\n\t\t// Resolve each email to a login: GitHub noreply addresses encode it locally; the rest are resolved in a single\n\t\t// batched GraphQL request rather than one round-trip per email.\n\t\tconst loginByEmail = new Map<string, string>();\n\t\tconst toResolve: string[] = [];\n\t\tfor (const email of emails) {\n\t\t\tconst login = getGitHubNoReplyAddressParts(email)?.login;\n\t\t\tif (login != null) {\n\t\t\t\tloginByEmail.set(email.toLowerCase(), login);\n\t\t\t} else {\n\t\t\t\ttoResolve.push(email);\n\t\t\t}\n\t\t}\n\n\t\tif (toResolve.length) {\n\t\t\tconst resolved = await api.getAccountsForEmails(this, token, toResolve, {\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t});\n\t\t\tfor (const [emailLower, login] of resolved) {\n\t\t\t\tloginByEmail.set(emailLower, login);\n\t\t\t}\n\t\t}\n\n\t\t// Fetch signing keys once per distinct login (REST has no batch endpoint), in bounded batches rather than firing\n\t\t// all lookups at once, to avoid a request burst that could trip secondary rate limiting. Then map keys to each email.\n\t\tconst keysByLogin = new Map<string, string[]>();\n\t\tawait batch([...new Set(loginByEmail.values())], sshSigningKeyResolveBatchSize, async login => {\n\t\t\tconst keys = await api.getUserSshSigningKeys(this, token, login, { baseUrl: this.apiBaseUrlFor(session) });\n\t\t\tkeysByLogin.set(\n\t\t\t\tlogin,\n\t\t\t\tkeys.map(k => k.key),\n\t\t\t);\n\t\t});\n\n\t\tfor (const [emailLower, login] of loginByEmail) {\n\t\t\tresult.set(emailLower, keysByLogin.get(login) ?? []);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tprotected override async getProviderDefaultBranch(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitHubRepositoryDescriptor,\n\t): Promise<DefaultBranch | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.getDefaultBranch(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\t{\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderLinkedIssueOrPullRequest(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitHubRepositoryDescriptor,\n\t\t{ id }: { id: string; key: string },\n\t): Promise<IssueOrPullRequest | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.getIssueOrPullRequest(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\tNumber(id),\n\t\t\t{\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderIssue(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitHubRepositoryDescriptor,\n\t\tid: string,\n\t): Promise<Issue | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.getIssue(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\tNumber(id),\n\t\t\t{\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t\tincludeBody: true,\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderPullRequest(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitHubRepositoryDescriptor,\n\t\tid: string,\n\t): Promise<PullRequest | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.getPullRequest(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\tparseInt(id, 10),\n\t\t\t{\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderPullRequestForBranch(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitHubRepositoryDescriptor,\n\t\tbranch: string,\n\t\toptions?: {\n\t\t\tavatarSize?: number;\n\t\t\tinclude?: PullRequestState[];\n\t\t},\n\t): Promise<PullRequest | undefined> {\n\t\tconst { include, ...opts } = options ?? {};\n\n\t\tconst toGitHubPullRequestState = (await import(/* webpackChunkName: \"integrations\" */ './github/models.js'))\n\t\t\t.toGitHubPullRequestState;\n\t\treturn (await this.authenticationService.apis.github)?.getPullRequestForBranch(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\tbranch,\n\t\t\t{\n\t\t\t\t...opts,\n\t\t\t\tinclude: include?.map(s => toGitHubPullRequestState(s)),\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderPullRequestForCommit(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitHubRepositoryDescriptor,\n\t\trev: string,\n\t): Promise<PullRequest | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.getPullRequestForCommit(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\trev,\n\t\t\t{\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderRepositoryMetadata(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitHubRepositoryDescriptor,\n\t\tcancellation?: AbortSignal,\n\t): Promise<RepositoryMetadata | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.getRepositoryMetadata(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\t{\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\tprotected override async getProviderOrganizationsForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<ProviderHierarchyResult<ProviderOrganization> | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst result = await api.getGitHubOrgsForCurrentUser(toTokenWithInfo(this.id, session), {\n\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t});\n\t\tconst webUrl =\n\t\t\tthis.id === GitSelfManagedHostIntegrationId.CloudGitHubEnterprise\n\t\t\t\t? this.getSelfManagedInstallationUrl(session)\n\t\t\t\t: `https://${this.domain}`;\n\t\treturn {\n\t\t\tvalues: result.values.map(o => ({\n\t\t\t\tid: o.id,\n\t\t\t\tproviderId: this.id,\n\t\t\t\tname: o.username,\n\t\t\t\turl: `${webUrl}/${o.username}`,\n\t\t\t})),\n\t\t\t...(result.truncated ? { truncated: true } : {}),\n\t\t\t...(result.metadata != null ? { metadata: result.metadata } : {}),\n\t\t};\n\t}\n\n\tprotected override async getProviderRepositoriesForOrg(\n\t\tsession: ProviderAuthenticationSession,\n\t\torg: string,\n\t\toptions?: { cursor?: string },\n\t): Promise<ProviderHierarchyResult<ProviderRepository> | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\treturn api.getReposForOrg(toTokenWithInfo(this.id, session), org, {\n\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\tcursor: options?.cursor,\n\t\t});\n\t}\n\n\tprotected override async getProviderRepositoriesForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t\toptions?: { cursor?: string },\n\t): Promise<ProviderHierarchyResult<ProviderRepository> | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\t// `/user/repos` with the full affiliation set: the user's own repos, collaborations, and org-member\n\t\t// repos — matching gkcli's org-less `provider repos github` walk (not every repo of every org).\n\t\treturn api.getReposForCurrentUser(toTokenWithInfo(this.id, session), {\n\t\t\taffiliations: ['owner', 'collaborator', 'organization_member'],\n\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\tcursor: options?.cursor,\n\t\t});\n\t}\n\n\tpublic override async getRepoInfo(repo: {\n\t\towner: string;\n\t\tname: string;\n\t\tproject?: string;\n\t\tconnectionId?: string;\n\t}): Promise<ProviderRepository | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\t// `connectionId` targets a specific account (multi-account); omitted reads the primary.\n\t\tconst session = await this.resolveReadSession(repo.connectionId, undefined);\n\t\tif (session == null) return undefined;\n\n\t\t// `apiBaseUrlFor` is undefined for cloud (which is what selects the cloud endpoints) and the GHE instance base\n\t\t// for enterprise (inherited override).\n\t\treturn api.getRepo(toTokenWithInfo(this.id, session), repo.owner, repo.name, repo.project, {\n\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t});\n\t}\n\n\tprotected override async searchProviderMyPullRequests(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepos?: GitHubRepositoryDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\toptions?: SearchMyPullRequestsOptions,\n\t): Promise<PullRequest[] | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.searchMyPullRequests(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t{\n\t\t\t\trepos: repos?.map(r => `${r.owner}/${r.name}`),\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t\tsilent: options?.silent,\n\t\t\t\tstate: options?.state,\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\tprotected override async getProviderMyPullRequestsForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t\toptions?: {\n\t\t\tstate?: PullRequestStateFilter[];\n\t\t\tcursor?: string;\n\t\t\tincludeReviewRequested?: boolean;\n\t\t\tfilters?: PullRequestFilter[];\n\t\t\tsummary?: boolean;\n\t\t},\n\t): Promise<ProviderApiPagedResult<ProviderPullRequest> | undefined> {\n\t\tconst explicitFilters = options?.filters?.length ? [...new Set(options.filters)] : undefined;\n\t\t// Every account-wide read goes through our own facet search — there is deliberately no SDK\n\t\t// `getPullRequestsForUser` fallback. It could express neither a state set, nor exact relationships (its\n\t\t// `involves:` is a different question), nor the full projection, so which reads it could serve depended on\n\t\t// the options, and the two routes disagreed on more than they had to: only this one applies the Launchpad\n\t\t// ignored/included repository and organization qualifiers, so the same read returned different pull\n\t\t// requests depending on options that were supposed to affect only projection or relationship. With no\n\t\t// state and no filters this emits a single facet with no relationship qualifier, which is GitHub's own\n\t\t// `involves:@me` and `is:open` default — the same question the fallback asked, now on one route.\n\t\tconst github = await this.authenticationService.apis.github;\n\t\tif (github == null) return undefined;\n\n\t\tconst requestedStates: PullRequestStateFilter[] =\n\t\t\toptions?.state != null && options.state.length > 0 ? [...new Set(options.state)] : ['open'];\n\t\tconst facets =\n\t\t\texplicitFilters?.length != null\n\t\t\t\t? requestedStates.flatMap(state =>\n\t\t\t\t\t\texplicitFilters.map(filter => ({\n\t\t\t\t\t\t\tkey: `${state}:${filter}`,\n\t\t\t\t\t\t\tstate: state,\n\t\t\t\t\t\t\tsearch: gitHubPullRequestRelationshipQualifiers[filter],\n\t\t\t\t\t\t})),\n\t\t\t\t\t)\n\t\t\t\t: requestedStates.flatMap(state => [\n\t\t\t\t\t\t{ key: state, state: state, search: undefined },\n\t\t\t\t\t\t...(options?.includeReviewRequested === true\n\t\t\t\t\t\t\t? [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tkey: `${state}:${PullRequestFilter.ReviewRequested}`,\n\t\t\t\t\t\t\t\t\t\tstate: state,\n\t\t\t\t\t\t\t\t\t\tsearch: gitHubPullRequestRelationshipQualifiers[\n\t\t\t\t\t\t\t\t\t\t\tPullRequestFilter.ReviewRequested\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t: []),\n\t\t\t\t\t]);\n\t\tconst cursors = parsePullRequestFacetCursor(options?.cursor);\n\t\tconst hasResumableFacetCursor = Object.keys(cursors).length !== 0;\n\t\tconst facetsWithCursor = facets.filter(facet => cursors[facet.key] != null);\n\t\t// The first call has no cursor, so query every requested state. A continuation only happens after a\n\t\t// prior page reported `more:true`, whose bundle carries a cursor for each facet still in flight;\n\t\t// facets absent from the bundle are exhausted, so re-querying them from scratch would refetch the\n\t\t// same PRs (duplicated by the dedup-free sweep) and waste an API call per page. Query only the\n\t\t// states that still have a cursor, but degrade a malformed/empty cursor bundle, or one that doesn't\n\t\t// apply to the current requested states, to the first page rather than returning an empty page.\n\t\tconst facetsToQuery =\n\t\t\toptions?.cursor != null && hasResumableFacetCursor && facetsWithCursor.length !== 0\n\t\t\t\t? facetsWithCursor\n\t\t\t\t: facets;\n\t\tconst results = await Promise.allSettled(\n\t\t\tfacetsToQuery.map(async facet => ({\n\t\t\t\tkey: facet.key,\n\t\t\t\tresult: await github.searchMyPullRequestsPage(this, toTokenWithInfo(this.id, session), {\n\t\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t\t\tstate: facet.state,\n\t\t\t\t\tcursor: cursors[facet.key],\n\t\t\t\t\tsummary: options?.summary,\n\t\t\t\t\t...(facet.search != null ? { search: facet.search, includeDefaultInvolvement: false } : undefined),\n\t\t\t\t}),\n\t\t\t})),\n\t\t);\n\t\tif (results.every(result => result.status === 'rejected')) {\n\t\t\tconst first = results[0];\n\t\t\tif (first?.status === 'rejected') throw first.reason;\n\t\t}\n\n\t\tconst values = new Map<string, ProviderPullRequest>();\n\t\tconst nextCursors: GitHubPullRequestFacetCursor = {};\n\t\tconst failures = [];\n\t\tlet hasMore = false;\n\t\tlet truncated = false;\n\t\tlet structuralIncompleteness = false;\n\t\tlet unkeyedPullRequest = 0;\n\t\tfor (const outcome of results) {\n\t\t\tif (outcome.status === 'rejected') {\n\t\t\t\tfailures.push(toCollectionScopeFailure({ providerId: this.id }, outcome.reason));\n\t\t\t\ttruncated = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst { key, result } = outcome.value;\n\t\t\tfor (const pr of result.values) {\n\t\t\t\tconst mapped = toProviderPullRequest(pr);\n\t\t\t\tconst identity = getProviderPullRequestIdentity(mapped) ?? `unkeyed:${unkeyedPullRequest++}`;\n\t\t\t\tif (!values.has(identity)) {\n\t\t\t\t\tvalues.set(identity, mapped);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (result.hasMore) {\n\t\t\t\tif (result.cursor == null || result.cursor === '{}' || result.cursor === cursors[key]) {\n\t\t\t\t\ttruncated = true;\n\t\t\t\t\tstructuralIncompleteness = true;\n\t\t\t\t} else {\n\t\t\t\t\thasMore = true;\n\t\t\t\t\tnextCursors[key] = result.cursor;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (result.truncated) {\n\t\t\t\ttruncated = true;\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tvalues: [...values.values()],\n\t\t\tpaging: {\n\t\t\t\tmore: hasMore,\n\t\t\t\tcursor: hasMore ? JSON.stringify({ type: 'cursor', cursors: nextCursors }) : '{}',\n\t\t\t\ttruncated: truncated || undefined,\n\t\t\t},\n\t\t\t...(failures.length || structuralIncompleteness\n\t\t\t\t? {\n\t\t\t\t\t\tmetadata: {\n\t\t\t\t\t\t\tcompleteness: 'partial' as const,\n\t\t\t\t\t\t\t...(failures.length ? { failures: failures } : {}),\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t: undefined),\n\t\t};\n\t}\n\n\tprotected override async searchProviderMyIssues(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepos?: GitHubRepositoryDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t): Promise<IssueShape[] | undefined> {\n\t\treturn (await this.searchProviderMyIssuesWithTruncation(session, repos, cancellation))?.values;\n\t}\n\n\t/**\n\t * GitHub's account-wide issue search pages authored/assigned/mentioned independently behind one composite\n\t * cursor. This variant preserves that cursor and the provider's 1,000-result search ceiling signal.\n\t *\n\t * `options.filters` selects which of those three searches run, so a caller can narrow the union to just its\n\t * own slice (e.g. `[Assignee]` ⇒ `assignee:@me`). Omitted keeps GitHub's own definition of \"mine\" (all three).\n\t */\n\tprotected override async searchProviderMyIssuesWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepos?: GitHubRepositoryDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\toptions?: SearchMyIssuesOptions,\n\t): Promise<{ values: IssueShape[]; truncated: boolean } | undefined> {\n\t\t// `includeAllAssignees` becomes `assignee:*`, which needs a scope to mean anything — with none it matches\n\t\t// millions of issues across all of GitHub rather than \"assigned to anyone in the user's world\". ANY scope\n\t\t// works (one repo, several, or an org), so this refuses only the genuinely unscoped read; the scoped\n\t\t// \"assigned to anyone over these repos\" request is served by `searchIssuesPage`.\n\t\tif ((repos == null || repos.length === 0) && options?.includeAllAssignees) {\n\t\t\tthrow new IntegrationReadUnavailableError(\n\t\t\t\tthis.name,\n\t\t\t\t'`includeAllAssignees` is not supported for account-wide issue reads; scope the read to repositories instead.',\n\t\t\t);\n\t\t}\n\n\t\treturn (await this.authenticationService.apis.github)?.searchMyIssues(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t{\n\t\t\t\trepos: repos?.map(r => `${r.owner}/${r.name}`),\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t\tincludeBody: true,\n\t\t\t\tincludeAllAssignees: options?.includeAllAssignees,\n\t\t\t\tcursor: options?.cursor,\n\t\t\t\tpageSize: options?.pageSize,\n\t\t\t\tsort: options?.sort,\n\t\t\t\tcategories: options?.filters?.length\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tauthored: options.filters.includes(IssueFilter.Author),\n\t\t\t\t\t\t\tassigned: options.filters.includes(IssueFilter.Assignee),\n\t\t\t\t\t\t\tmentioned: options.filters.includes(IssueFilter.Mention),\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined,\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\t/**\n\t * Filtered pull-request search over a repository/organization or explicit current-user relationship scope. The\n\t * API client owns criteria sanitizing, facet unioning, ordering, and the composite cursor that carries GitHub's\n\t * result-ceiling signal across pages.\n\t */\n\tprotected override async searchProviderPullRequestsPage(\n\t\tsession: ProviderAuthenticationSession,\n\t\toptions: {\n\t\t\trepos?: ProviderRepoInput[];\n\t\t\torg?: string;\n\t\t\tcriteria?: PullRequestSearchCriteria;\n\t\t\tcursor?: string;\n\t\t\tpageSize?: number;\n\t\t\tsummary?: boolean;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t): Promise<ProviderPullRequestSearchPage | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.searchPullRequestsPage(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t{\n\t\t\t\trepos: options.repos?.map(r => `${r.namespace}/${r.name}`),\n\t\t\t\torg: options.org,\n\t\t\t\tcriteria: options.criteria,\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t\tcursor: options.cursor,\n\t\t\t\tpageSize: options.pageSize,\n\t\t\t\tsummary: options.summary,\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\t/**\n\t * The filtered issue search: one GraphQL request per page, no forced relationship to the current user, and no\n\t * route through the SDK's repo-scoped read (whose over-limit recovery walk can spend up to 128 requests).\n\t *\n\t * The criteria → qualifier translation and the ordering guarantee live in the API client\n\t * (`GitHubApi.searchIssuesPage`), which is also where user input is sanitized so it can't inject a qualifier.\n\t */\n\tprotected override async searchProviderIssuesPage(\n\t\tsession: ProviderAuthenticationSession,\n\t\toptions: {\n\t\t\trepos?: ProviderRepoInput[];\n\t\t\torg?: string;\n\t\t\tcriteria?: IssueSearchCriteria;\n\t\t\tcursor?: string;\n\t\t\tpageSize?: number;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t): Promise<ProviderIssueSearchPage | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.searchIssuesPage(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t{\n\t\t\t\t// `namespace` is the owner for GitHub, which is how a `repo:` qualifier names a repository.\n\t\t\t\trepos: options.repos?.map(r => `${r.namespace}/${r.name}`),\n\t\t\t\torg: options.org,\n\t\t\t\tcriteria: options.criteria,\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t\tincludeBody: true,\n\t\t\t\tcursor: options.cursor,\n\t\t\t\tpageSize: options.pageSize,\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\t/**\n\t * Counts several scopes in ONE request. GitHub's `search` reports `issueCount` on a zero-node selection, so a\n\t * count preview costs no issue transfer — measured, 30 aliased counts are a single rate-limit point.\n\t */\n\tprotected override async countProviderIssues(\n\t\tsession: ProviderAuthenticationSession,\n\t\tscopes: readonly { repos?: ProviderRepoInput[]; org?: string; criteria?: IssueSearchCriteria }[],\n\t\tcancellation?: AbortSignal,\n\t): Promise<(number | undefined)[] | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.countIssues(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\tscopes.map(s => ({\n\t\t\t\trepos: s.repos?.map(r => `${r.namespace}/${r.name}`),\n\t\t\t\torg: s.org,\n\t\t\t\tcriteria: s.criteria,\n\t\t\t})),\n\t\t\t{ baseUrl: this.apiBaseUrlFor(session) },\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\t/**\n\t * Resolves several issues by `(owner, repo, number)` by aliasing the point read rather than a search — see\n\t * {@link GitHubApi.getIssuesBatch} for why that distinction is the whole design — chunked into requests of up\n\t * to {@link issuesBatchChunkSize} coordinates, run with bounded concurrency. Settled per target already: a\n\t * coordinate whose alias fails on its own (e.g. an org enforcing SAML SSO the token isn't authorized for)\n\t * rejects only that slot, and a chunk that throws rejects only its own slots.\n\t */\n\tprotected override async getProviderIssuesBatch(\n\t\tsession: ProviderAuthenticationSession,\n\t\tcoordinates: readonly { owner: string; repo: string; number: number; project?: string }[],\n\t\tcancellation?: AbortSignal,\n\t): Promise<PromiseSettledResult<IssueShape | undefined>[] | undefined> {\n\t\tconst github = await this.authenticationService.apis.github;\n\t\tif (github == null) return undefined;\n\n\t\treturn readChunked(\n\t\t\tcoordinates,\n\t\t\tissuesBatchChunkSize,\n\t\t\tchunkCoordinates =>\n\t\t\t\tgithub.getIssuesBatch(\n\t\t\t\t\tthis,\n\t\t\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t\t\tchunkCoordinates.map(c => ({ owner: c.owner, repo: c.repo, number: c.number })),\n\t\t\t\t\t{ baseUrl: this.apiBaseUrlFor(session), includeBody: true },\n\t\t\t\t\tcancellation,\n\t\t\t\t),\n\t\t\tissue => issue,\n\t\t);\n\t}\n\n\t/**\n\t * Resolves several pull requests by `(owner, repo, number)` by aliasing the point read — see\n\t * {@link GitHubApi.getPullRequestsBatch} — chunked into requests of up to {@link pullRequestsBatchChunkSize}\n\t * coordinates, run with bounded concurrency. Rows take the same conversion as the account-wide list rows, so a\n\t * pull request reads the same whichever of the two returned it.\n\t *\n\t * A chunk that throws rejects only its own slots, so its sibling chunks still answer.\n\t */\n\tprotected override async getProviderPullRequestsBatch(\n\t\tsession: ProviderAuthenticationSession,\n\t\tcoordinates: readonly { owner: string; repo: string; number: number; project?: string }[],\n\t\toptions: { currentAccount?: { id: string; username?: string } } | undefined,\n\t\tcancellation?: AbortSignal,\n\t): Promise<PromiseSettledResult<PullRequestShape | undefined>[] | undefined> {\n\t\tconst github = await this.authenticationService.apis.github;\n\t\tif (github == null) return undefined;\n\n\t\tconst currentAccount = options?.currentAccount;\n\t\treturn readChunked(\n\t\t\tcoordinates,\n\t\t\tpullRequestsBatchChunkSize,\n\t\t\tchunkCoordinates =>\n\t\t\t\tgithub.getPullRequestsBatch(\n\t\t\t\t\tthis,\n\t\t\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t\t\tchunkCoordinates.map(c => ({ owner: c.owner, repo: c.repo, number: c.number })),\n\t\t\t\t\t{ baseUrl: this.apiBaseUrlFor(session) },\n\t\t\t\t\tcancellation,\n\t\t\t\t),\n\t\t\t(pr): PullRequestShape | undefined =>\n\t\t\t\tpr != null\n\t\t\t\t\t? fromProviderPullRequest(toProviderPullRequest(pr), this, { currentAccount: currentAccount })\n\t\t\t\t\t: undefined,\n\t\t);\n\t}\n\n\t/**\n\t * Finds each branch's pull requests by aliasing a head-ref-name query per target — see\n\t * {@link GitHubApi.getPullRequestsForBranches} — chunked and converted exactly as\n\t * {@link getProviderPullRequestsBatch} is, with the same per-target and per-chunk failure isolation.\n\t */\n\tprotected override async getProviderPullRequestsForBranches(\n\t\tsession: ProviderAuthenticationSession,\n\t\ttargets: readonly { owner: string; repo: string; project?: string; branch: string; headOwner?: string }[],\n\t\toptions: { currentAccount?: { id: string; username?: string }; limit: number },\n\t\tcancellation?: AbortSignal,\n\t): Promise<PromiseSettledResult<{ pullRequests: PullRequestShape[]; truncated: boolean }>[] | undefined> {\n\t\tconst github = await this.authenticationService.apis.github;\n\t\tif (github == null) return undefined;\n\n\t\tconst currentAccount = options.currentAccount;\n\t\treturn readChunked(\n\t\t\ttargets,\n\t\t\tpullRequestsBatchChunkSize,\n\t\t\tchunkTargets =>\n\t\t\t\tgithub.getPullRequestsForBranches(\n\t\t\t\t\tthis,\n\t\t\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t\t\tchunkTargets.map(t => ({ owner: t.owner, repo: t.repo, branch: t.branch, headOwner: t.headOwner })),\n\t\t\t\t\t{ baseUrl: this.apiBaseUrlFor(session), limit: options.limit },\n\t\t\t\t\tcancellation,\n\t\t\t\t),\n\t\t\t(found): { pullRequests: PullRequestShape[]; truncated: boolean } => ({\n\t\t\t\tpullRequests: found.pullRequests.map(pr =>\n\t\t\t\t\tfromProviderPullRequest(toProviderPullRequest(pr), this, { currentAccount: currentAccount }),\n\t\t\t\t),\n\t\t\t\ttruncated: found.truncated,\n\t\t\t}),\n\t\t);\n\t}\n\n\t/**\n\t * Counts several pull-request scopes in ONE request. Like {@link countProviderIssues}, GitHub's `search`\n\t * reports `issueCount` on a zero-node selection, so a count preview costs no pull-request transfer.\n\t */\n\tprotected override async countProviderPullRequests(\n\t\tsession: ProviderAuthenticationSession,\n\t\tscopes: readonly { repos?: ProviderRepoInput[]; org?: string; criteria?: PullRequestSearchCriteria }[],\n\t\tcancellation?: AbortSignal,\n\t): Promise<ProviderPullRequestCount[] | undefined> {\n\t\tconst counts = await (\n\t\t\tawait this.authenticationService.apis.github\n\t\t)?.countPullRequests(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\tscopes.map(s => ({\n\t\t\t\trepos: s.repos?.map(r => `${r.namespace}/${r.name}`),\n\t\t\t\torg: s.org,\n\t\t\t\tcriteria: s.criteria,\n\t\t\t})),\n\t\t\t{ baseUrl: this.apiBaseUrlFor(session) },\n\t\t\tcancellation,\n\t\t);\n\t\t// `issueCount` is the search's own total, never a floor, so no slot is a lower bound.\n\t\treturn counts?.map(count => ({ count: count }));\n\t}\n\n\tprotected override async searchProviderPullRequests(\n\t\tsession: ProviderAuthenticationSession,\n\t\tsearchQuery: string,\n\t\trepos?: GitHubRepositoryDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\toptions?: SearchPullRequestsOptions,\n\t): Promise<PullRequest[] | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.searchPullRequests(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t{\n\t\t\t\tsearch: searchQuery,\n\t\t\t\trepos: repos?.map(r => `${r.owner}/${r.name}`),\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t\t...options,\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\t/**\n\t * Stack membership for every stacked pull request in the repository, keyed by pull request number.\n\t *\n\t * List surfaces get their pull requests from the shared providers API, whose type carries no stack\n\t * membership, so the `stack`/`stackEntry` fields selected on the native per-pull-request reads never\n\t * reach them. This fills that gap in a single request for the whole repository, rather than a\n\t * per-pull-request enrichment that would scale with the list.\n\t *\n\t * `undefined` means unavailable (not enrolled in the preview, or not connected) as distinct from an\n\t * empty map, which means the repository genuinely has no stacks.\n\t */\n\toverride async getStacksByPullRequestNumber(\n\t\towner: string,\n\t\trepo: string,\n\t\tcancellation?: AbortSignal,\n\t): Promise<Map<number, PullRequestStackInfo> | undefined> {\n\t\t// The shared read path — it refreshes an expired session before use, which a bare `getSession()`\n\t\t// does not (it returns the cached session verbatim once one exists, expired or not).\n\t\tconst session = await this.resolveReadSession(undefined, undefined);\n\t\tif (session == null) return undefined;\n\n\t\tconst stacks = await (\n\t\t\tawait this.authenticationService.apis.github\n\t\t)?.getRepositoryStacks(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\towner,\n\t\t\trepo,\n\t\t\t{\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t\tif (stacks == null) return undefined;\n\n\t\tconst byNumber = new Map<number, PullRequestStackInfo>();\n\t\tfor (const stack of stacks) {\n\t\t\t// Defensive: this is a public-preview payload reaching us through an unvalidated cast, and a\n\t\t\t// malformed entry must cost its own stack's badges, not the whole panel.\n\t\t\tconst members = stack?.pull_requests;\n\t\t\tconst baseRef = stack?.base?.ref;\n\t\t\tif (members == null || baseRef == null) continue;\n\n\t\t\t// `pull_requests` is ordered bottom to top, and `position` is 1-based from the bottom.\n\t\t\tmembers.forEach((pr, i) => {\n\t\t\t\tbyNumber.set(pr.number, {\n\t\t\t\t\tid: stack.id,\n\t\t\t\t\tnumber: stack.number,\n\t\t\t\t\tsize: members.length,\n\t\t\t\t\tposition: i + 1,\n\t\t\t\t\tbaseRef: baseRef,\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\treturn byNumber;\n\t}\n\n\tprotected override async mergeProviderPullRequest(\n\t\tsession: ProviderAuthenticationSession,\n\t\tpr: PullRequest,\n\t\toptions?: {\n\t\t\tmergeMethod?: PullRequestMergeMethod;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t): Promise<boolean> {\n\t\tconst id = pr.nodeId;\n\t\tconst headRefSha = pr.refs?.head?.sha;\n\t\tif (id == null || headRefSha == null) return false;\n\n\t\tconst api = await this.authenticationService.apis.github;\n\n\t\t// GitHub rejects stacked pull requests on the legacy merge mutation, so they take the\n\t\t// asynchronous merge instead — which also lands every layer below this one.\n\t\tif (pr.stack != null) {\n\t\t\treturn (\n\t\t\t\t(await api?.mergeStackedPullRequest(\n\t\t\t\t\tthis,\n\t\t\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t\t\tpr.repository.owner,\n\t\t\t\t\tpr.repository.repo,\n\t\t\t\t\tNumber(pr.id),\n\t\t\t\t\theadRefSha,\n\t\t\t\t\t{\n\t\t\t\t\t\tmergeMethod: options?.mergeMethod,\n\t\t\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t\t\t},\n\t\t\t\t\tcancellation,\n\t\t\t\t)) ?? false\n\t\t\t);\n\t\t}\n\n\t\treturn (\n\t\t\tapi?.mergePullRequest(\n\t\t\t\tthis,\n\t\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t\tid,\n\t\t\t\theadRefSha,\n\t\t\t\t{\n\t\t\t\t\tmergeMethod: options?.mergeMethod,\n\t\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t\t},\n\t\t\t\tcancellation,\n\t\t\t) ?? false\n\t\t);\n\t}\n\n\tprotected override async getProviderCurrentAccount(\n\t\tsession: ProviderAuthenticationSession,\n\t\toptions?: { avatarSize?: number },\n\t): Promise<Account | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.getCurrentAccount(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t{\n\t\t\t\t...options,\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override getProviderPullRequestIdentityFromMaybeUrl(search: string): PullRequestUrlIdentity | undefined {\n\t\treturn getGitHubPullRequestIdentityFromMaybeUrl(search, this.id);\n\t}\n}\n\nexport class GitHubIntegration extends GitHubIntegrationBase<GitCloudHostIntegrationId.GitHub> {\n\treadonly authProvider = authProvider;\n\treadonly id = GitCloudHostIntegrationId.GitHub;\n\tprotected readonly key = this.id;\n\treadonly name: string = 'GitHub';\n\tget domain(): string {\n\t\treturn metadata.domain;\n\t}\n\n\tprotected override apiBaseUrlFor(_session: ProviderAuthenticationSession): string | undefined {\n\t\t// Undefined on purpose, NOT 'https://api.github.com'. `@gitkraken/provider-apis` treats this value as an\n\t\t// *enterprise* base and derives both endpoints from it by appending GitHub Enterprise's paths:\n\t\t// `getRESTBaseUrl` appends `/api/v3` and `getGraphQLEndpoint` appends `/api/graphql` (githubHelpers.ts).\n\t\t// Handing it the cloud host therefore built `https://api.github.com/api/graphql`, which GitHub answers\n\t\t// 404 — every cloud GraphQL read (orgs, repos, PR/issue search) failed as `not-found`. Omitting it is\n\t\t// what selects the cloud endpoints (`GITHUB_API_URL` / `GITHUB_GRAPHQL_API_URL`), so cloud must pass\n\t\t// nothing and only GHE passes its instance base.\n\t\treturn undefined;\n\t}\n\n\toverride access(): Promise<boolean> {\n\t\t// Always allow GitHub cloud integration access\n\t\treturn Promise.resolve(true);\n\t}\n\n\t// This is a special case for GitHub because we use VSCode's GitHub session, and it can be disconnected\n\t// outside of the extension.\n\toverride async refresh(): Promise<void> {\n\t\tconst authProvider = await this.authenticationService.get(this.authProvider.id);\n\t\tconst session = await authProvider.getSession(this.authProviderDescriptor);\n\t\tif (session == null && this.maybeConnected) {\n\t\t\tvoid this.disconnect({ silent: true });\n\t\t} else {\n\t\t\tif (session?.accessToken !== this._session?.accessToken) {\n\t\t\t\tthis._session = undefined;\n\t\t\t}\n\t\t\tsuper.refresh();\n\t\t}\n\t}\n}\n\nexport class GitHubEnterpriseIntegration extends GitHubIntegrationBase<GitSelfManagedHostIntegrationId.CloudGitHubEnterprise> {\n\treadonly authProvider = cloudEnterpriseAuthProvider;\n\treadonly id = GitSelfManagedHostIntegrationId.CloudGitHubEnterprise;\n\tprotected readonly key;\n\treadonly name = 'GitHub Enterprise';\n\tget domain(): string {\n\t\treturn this._domain;\n\t}\n\n\tprotected override apiBaseUrlFor(session: ProviderAuthenticationSession): string {\n\t\treturn this.getSelfManagedApiBaseUrl(session);\n\t}\n\n\tconstructor(\n\t\tctx: IntegrationServiceContext,\n\t\tauthenticationService: IntegrationAuthenticationService,\n\t\tgetProvidersApi: () => Promise<ProvidersApi>,\n\t\tdidChangeConnection: Emitter<IntegrationConnectionChangeEvent>,\n\t\tprivate readonly _domain: string,\n\t) {\n\t\tsuper(ctx, authenticationService, getProvidersApi, didChangeConnection);\n\t\tthis.key = `${this.id}:${this.domain}` as const;\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"github.js","names":[],"sources":["../../../../../plus/integrations/src/providers/github.ts"],"sourcesContent":["import { gitHubPullRequestRelationshipQualifiers } from '@gitlens/git-github/api/pullRequestSearchQuery.js';\nimport type { Account, UnidentifiedAuthor } from '@gitlens/git/models/author.js';\nimport type { DefaultBranch } from '@gitlens/git/models/defaultBranch.js';\nimport type { Issue, IssueSearchCriteria, IssueShape } from '@gitlens/git/models/issue.js';\nimport type { IssueOrPullRequest } from '@gitlens/git/models/issueOrPullRequest.js';\nimport type {\n\tPullRequest,\n\tPullRequestMergeMethod,\n\tPullRequestSearchCriteria,\n\tPullRequestShape,\n\tPullRequestStackInfo,\n\tPullRequestState,\n\tPullRequestStateFilter,\n} from '@gitlens/git/models/pullRequest.js';\nimport type { RepositoryMetadata } from '@gitlens/git/models/repositoryMetadata.js';\nimport type { RepositoryDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { getGitHubNoReplyAddressParts } from '@gitlens/git/remotes/github.js';\nimport type { PullRequestUrlIdentity } from '@gitlens/git/utils/pullRequest.utils.js';\nimport { chunk } from '@gitlens/utils/array.js';\nimport type { Emitter } from '@gitlens/utils/event.js';\nimport { batch, mapBounded } from '@gitlens/utils/promise.js';\nimport type { IntegrationAuthenticationProviderDescriptor } from '../authentication/integrationAuthenticationProvider.js';\nimport type { IntegrationAuthenticationService } from '../authentication/integrationAuthenticationService.js';\nimport type { ProviderAuthenticationSession } from '../authentication/models.js';\nimport { toTokenWithInfo } from '../authentication/models.js';\nimport type { ProviderRefusal } from '../collectionMetadata.js';\nimport { toCollectionScopeFailure } from '../collectionMetadata.js';\nimport { GitCloudHostIntegrationId, GitSelfManagedHostIntegrationId, providerFanOutConcurrency } from '../constants.js';\nimport type { IntegrationServiceContext } from '../context.js';\nimport { AuthenticationError, IntegrationReadUnavailableError, RequestNotFoundError } from '../errors.js';\nimport type { IntegrationConnectionChangeEvent } from '../integrationService.js';\nimport type { SearchMyPullRequestsOptions, SearchPullRequestsOptions } from '../models/gitHostIntegration.js';\nimport { GitHostIntegration } from '../models/gitHostIntegration.js';\nimport type {\n\tProviderIssueSearchPage,\n\tProviderPullRequestCount,\n\tProviderPullRequestSearchPage,\n\tSearchMyIssuesOptions,\n} from '../models/integration.js';\nimport type { ProviderWarningCause } from '../results.js';\nimport type { GitHubIntegrationIds } from './github/github.utils.js';\nimport { getGitHubPullRequestIdentityFromMaybeUrl } from './github/github.utils.js';\nimport type {\n\tProviderApiPagedResult,\n\tProviderHierarchyResult,\n\tProviderOrganization,\n\tProviderPullRequest,\n\tProviderRepoInput,\n\tProviderRepository,\n} from './models.js';\nimport {\n\tfromProviderPullRequest,\n\tgetProviderPullRequestIdentity,\n\tIssueFilter,\n\tprovidersMetadata,\n\tPullRequestFilter,\n\ttoProviderPullRequest,\n} from './models.js';\nimport type { ProvidersApi } from './providersApi.js';\n\ntype GitHubPullRequestFacetCursor = Record<string, string>;\n\nfunction toPullRequestFacetCursor(value: unknown): GitHubPullRequestFacetCursor {\n\tif (value == null || typeof value !== 'object' || Array.isArray(value)) return {};\n\n\treturn Object.fromEntries(\n\t\tObject.entries(value).filter(\n\t\t\t([key, cursor]) => key.length !== 0 && typeof cursor === 'string' && cursor.length !== 0,\n\t\t),\n\t);\n}\n\nfunction parsePullRequestFacetCursor(cursor: string | undefined): GitHubPullRequestFacetCursor {\n\tif (!cursor) return {};\n\n\ttry {\n\t\tconst parsed = JSON.parse(cursor) as unknown;\n\t\tif (parsed != null && typeof parsed === 'object' && !Array.isArray(parsed) && 'cursors' in parsed) {\n\t\t\tconst wrapped = parsed as { type?: unknown; cursors?: unknown };\n\t\t\tif (wrapped.type === 'cursor') {\n\t\t\t\treturn toPullRequestFacetCursor(wrapped.cursors);\n\t\t\t}\n\t\t}\n\n\t\treturn toPullRequestFacetCursor(parsed);\n\t} catch {\n\t\treturn {};\n\t}\n}\n\nconst metadata = providersMetadata[GitCloudHostIntegrationId.GitHub];\nconst authProvider: IntegrationAuthenticationProviderDescriptor = Object.freeze({\n\tid: metadata.id,\n\tscopes: metadata.scopes,\n});\n\nconst cloudEnterpriseMetadata = providersMetadata[GitSelfManagedHostIntegrationId.CloudGitHubEnterprise];\nconst cloudEnterpriseAuthProvider: IntegrationAuthenticationProviderDescriptor = Object.freeze({\n\tid: cloudEnterpriseMetadata.id,\n\tscopes: cloudEnterpriseMetadata.scopes,\n});\n\nexport type GitHubRepositoryDescriptor = RepositoryDescriptor;\n\n/** How many per-login SSH signing-key lookups to run concurrently, to avoid a request burst that trips rate limiting. */\nconst sshSigningKeyResolveBatchSize = 10;\n\n// One aliased document costs about the same as a single read up to 25 targets; past that, latency climbs.\nconst pullRequestsBatchChunkSize = 25;\n\n/**\n * How many coordinates go into one aliased issue request.\n *\n * Measured rather than inherited from `issueCountChunkSize`, since that one rests on a zero-node selection and\n * this carries the full issue projection per alias. Against the live API on all-resolving coordinates: 10 took\n * 875ms, 25 took 890ms — so up to 25 is effectively free — 50 took 1.3s, and 75 took 2.7s. No complexity refusal\n * appeared up to 100.\n *\n * 25 is where latency is still flat, with room before it degrades. It also puts any realistic correlation batch\n * in ONE request, which is the whole point of the read.\n */\nconst issuesBatchChunkSize = 25;\n\n/**\n * Runs an aliased batch read over `items` in chunks of `chunkSize`, with bounded concurrency, converting each\n * fulfilled slot with `map`. Settled per target throughout: a chunk that throws rejects only its own slots, and a\n * slot whose conversion throws rejects only itself, so one bad row never takes its chunk's siblings down with it.\n */\nasync function readChunked<T, V, R>(\n\titems: readonly T[],\n\tchunkSize: number,\n\tread: (chunk: T[]) => Promise<PromiseSettledResult<V>[]>,\n\tmap: (value: V) => R,\n): Promise<PromiseSettledResult<R>[]> {\n\tconst settledChunks = await mapBounded(\n\t\tchunk([...items], chunkSize),\n\t\tproviderFanOutConcurrency,\n\t\tasync (chunkItems): Promise<PromiseSettledResult<R>[]> => {\n\t\t\tlet slots: PromiseSettledResult<V>[];\n\t\t\ttry {\n\t\t\t\tslots = await read(chunkItems);\n\t\t\t} catch (ex) {\n\t\t\t\treturn chunkItems.map(() => ({ status: 'rejected', reason: ex }));\n\t\t\t}\n\n\t\t\treturn slots.map((slot): PromiseSettledResult<R> => {\n\t\t\t\tif (slot.status === 'rejected') return slot;\n\n\t\t\t\ttry {\n\t\t\t\t\treturn { status: 'fulfilled', value: map(slot.value) };\n\t\t\t\t} catch (ex) {\n\t\t\t\t\treturn { status: 'rejected', reason: ex };\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t);\n\treturn settledChunks.flat();\n}\n\nabstract class GitHubIntegrationBase<ID extends GitHubIntegrationIds> extends GitHostIntegration<\n\tID,\n\tGitHubRepositoryDescriptor\n> {\n\t/**\n\t * Base URL handed to `@gitkraken/provider-apis`, which derives BOTH the REST and the GraphQL endpoint from it\n\t * by appending GitHub Enterprise's paths. Only a GHE instance base belongs here; cloud passes `undefined`,\n\t * the sole value that selects the cloud endpoints (see the cloud subclass).\n\t */\n\tprotected abstract apiBaseUrlFor(session: ProviderAuthenticationSession): string | undefined;\n\n\tprotected override async getProviderAccountForCommit(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitHubRepositoryDescriptor,\n\t\trev: string,\n\t\toptions?: {\n\t\t\tavatarSize?: number;\n\t\t},\n\t): Promise<Account | UnidentifiedAuthor | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.getAccountForCommit(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\trev,\n\t\t\t{\n\t\t\t\t...options,\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderAccountForEmail(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitHubRepositoryDescriptor,\n\t\temail: string,\n\t\toptions?: {\n\t\t\tavatarSize?: number;\n\t\t},\n\t): Promise<Account | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.getAccountForEmail(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\temail,\n\t\t\t{\n\t\t\t\t...options,\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderSshSigningKeysForEmails(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitHubRepositoryDescriptor,\n\t\temails: string[],\n\t): Promise<Map<string, string[]>> {\n\t\tconst result = new Map<string, string[]>();\n\n\t\tconst api = await this.authenticationService.apis.github;\n\t\tif (api == null) return result;\n\n\t\tconst token = toTokenWithInfo(this.id, session);\n\n\t\t// Resolve each email to a login: GitHub noreply addresses encode it locally; the rest are resolved in a single\n\t\t// batched GraphQL request rather than one round-trip per email.\n\t\tconst loginByEmail = new Map<string, string>();\n\t\tconst toResolve: string[] = [];\n\t\tfor (const email of emails) {\n\t\t\tconst login = getGitHubNoReplyAddressParts(email)?.login;\n\t\t\tif (login != null) {\n\t\t\t\tloginByEmail.set(email.toLowerCase(), login);\n\t\t\t} else {\n\t\t\t\ttoResolve.push(email);\n\t\t\t}\n\t\t}\n\n\t\tif (toResolve.length) {\n\t\t\tconst resolved = await api.getAccountsForEmails(this, token, toResolve, {\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t});\n\t\t\tfor (const [emailLower, login] of resolved) {\n\t\t\t\tloginByEmail.set(emailLower, login);\n\t\t\t}\n\t\t}\n\n\t\t// Fetch signing keys once per distinct login (REST has no batch endpoint), in bounded batches rather than firing\n\t\t// all lookups at once, to avoid a request burst that could trip secondary rate limiting. Then map keys to each email.\n\t\tconst keysByLogin = new Map<string, string[]>();\n\t\tawait batch([...new Set(loginByEmail.values())], sshSigningKeyResolveBatchSize, async login => {\n\t\t\tconst keys = await api.getUserSshSigningKeys(this, token, login, { baseUrl: this.apiBaseUrlFor(session) });\n\t\t\tkeysByLogin.set(\n\t\t\t\tlogin,\n\t\t\t\tkeys.map(k => k.key),\n\t\t\t);\n\t\t});\n\n\t\tfor (const [emailLower, login] of loginByEmail) {\n\t\t\tresult.set(emailLower, keysByLogin.get(login) ?? []);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tprotected override async getProviderDefaultBranch(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitHubRepositoryDescriptor,\n\t): Promise<DefaultBranch | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.getDefaultBranch(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\t{\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderLinkedIssueOrPullRequest(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitHubRepositoryDescriptor,\n\t\t{ id }: { id: string; key: string },\n\t): Promise<IssueOrPullRequest | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.getIssueOrPullRequest(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\tNumber(id),\n\t\t\t{\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderIssue(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitHubRepositoryDescriptor,\n\t\tid: string,\n\t): Promise<Issue | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.getIssue(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\tNumber(id),\n\t\t\t{\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t\tincludeBody: true,\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderPullRequest(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitHubRepositoryDescriptor,\n\t\tid: string,\n\t): Promise<PullRequest | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.getPullRequest(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\tparseInt(id, 10),\n\t\t\t{\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderPullRequestForBranch(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitHubRepositoryDescriptor,\n\t\tbranch: string,\n\t\toptions?: {\n\t\t\tavatarSize?: number;\n\t\t\tinclude?: PullRequestState[];\n\t\t},\n\t): Promise<PullRequest | undefined> {\n\t\tconst { include, ...opts } = options ?? {};\n\n\t\tconst toGitHubPullRequestState = (await import(/* webpackChunkName: \"integrations\" */ './github/models.js'))\n\t\t\t.toGitHubPullRequestState;\n\t\treturn (await this.authenticationService.apis.github)?.getPullRequestForBranch(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\tbranch,\n\t\t\t{\n\t\t\t\t...opts,\n\t\t\t\tinclude: include?.map(s => toGitHubPullRequestState(s)),\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderPullRequestForCommit(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitHubRepositoryDescriptor,\n\t\trev: string,\n\t): Promise<PullRequest | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.getPullRequestForCommit(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\trev,\n\t\t\t{\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override async getProviderRepositoryMetadata(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepo: GitHubRepositoryDescriptor,\n\t\tcancellation?: AbortSignal,\n\t): Promise<RepositoryMetadata | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.getRepositoryMetadata(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\trepo.owner,\n\t\t\trepo.name,\n\t\t\t{\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\tprotected override async getProviderOrganizationsForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<ProviderHierarchyResult<ProviderOrganization> | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst result = await api.getGitHubOrgsForCurrentUser(toTokenWithInfo(this.id, session), {\n\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t});\n\t\tconst webUrl =\n\t\t\tthis.id === GitSelfManagedHostIntegrationId.CloudGitHubEnterprise\n\t\t\t\t? this.getSelfManagedInstallationUrl(session)\n\t\t\t\t: `https://${this.domain}`;\n\t\treturn {\n\t\t\tvalues: result.values.map(o => ({\n\t\t\t\tid: o.id,\n\t\t\t\tproviderId: this.id,\n\t\t\t\tname: o.username,\n\t\t\t\turl: `${webUrl}/${o.username}`,\n\t\t\t})),\n\t\t\t...(result.truncated ? { truncated: true } : {}),\n\t\t\t...(result.metadata != null ? { metadata: result.metadata } : {}),\n\t\t};\n\t}\n\n\tprotected override async getProviderRepositoriesForOrg(\n\t\tsession: ProviderAuthenticationSession,\n\t\torg: string,\n\t\toptions?: { cursor?: string },\n\t): Promise<ProviderHierarchyResult<ProviderRepository> | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\treturn api.getReposForOrg(toTokenWithInfo(this.id, session), org, {\n\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\tcursor: options?.cursor,\n\t\t});\n\t}\n\n\tprotected override async getProviderRepositoriesForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t\toptions?: { cursor?: string },\n\t): Promise<ProviderHierarchyResult<ProviderRepository> | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\t// `/user/repos` with the full affiliation set: the user's own repos, collaborations, and org-member\n\t\t// repos — matching gkcli's org-less `provider repos github` walk (not every repo of every org).\n\t\treturn api.getReposForCurrentUser(toTokenWithInfo(this.id, session), {\n\t\t\taffiliations: ['owner', 'collaborator', 'organization_member'],\n\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\tcursor: options?.cursor,\n\t\t});\n\t}\n\n\tpublic override async getRepoInfo(repo: {\n\t\towner: string;\n\t\tname: string;\n\t\tproject?: string;\n\t\tconnectionId?: string;\n\t}): Promise<ProviderRepository | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\t// `connectionId` targets a specific account (multi-account); omitted reads the primary.\n\t\tconst session = await this.resolveReadSession(repo.connectionId, undefined);\n\t\tif (session == null) return undefined;\n\n\t\t// `apiBaseUrlFor` is undefined for cloud (which is what selects the cloud endpoints) and the GHE instance base\n\t\t// for enterprise (inherited override).\n\t\ttry {\n\t\t\treturn await api.getRepo(toTokenWithInfo(this.id, session), repo.owner, repo.name, repo.project, {\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t});\n\t\t} catch (ex) {\n\t\t\tif (!(ex instanceof RequestNotFoundError)) throw ex;\n\n\t\t\t// GraphQL answers a repository its organization hides from this OAuth app (OAuth App access restrictions)\n\t\t\t// exactly as it answers a missing one, so a miss is confirmed over REST, which refuses such a repository\n\t\t\t// with a `403` saying why. Only that refusal replaces the miss: a REST read that cannot confirm either way\n\t\t\t// leaves the answer GraphQL gave.\n\t\t\tconst github = await this.authenticationService.apis.github;\n\t\t\ttry {\n\t\t\t\tawait github?.getRepositoryAccess(this, toTokenWithInfo(this.id, session), repo.owner, repo.name, {\n\t\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t\t});\n\t\t\t} catch (refusal) {\n\t\t\t\tif (refusal instanceof AuthenticationError) throw refusal;\n\t\t\t}\n\t\t\tthrow ex;\n\t\t}\n\t}\n\n\t/**\n\t * The profile request that proves the credential; see `IntegrationBase.validateCredential`. Silent: a refusal\n\t * here is rethrown to the read that asked, which reports it as it reports its own failure (or, for\n\t * `resolveRepository`, as the resolution's warning), so the check must not raise a prompt of its own.\n\t */\n\tprotected override async validateCredential(session: ProviderAuthenticationSession): Promise<void> {\n\t\tconst github = await this.authenticationService.apis.github;\n\t\tconst account = await github?.getCurrentAccount(this, toTokenWithInfo(this.id, session), {\n\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\tsilent: true,\n\t\t});\n\t\tif (account == null) throw new Error('GitHub did not confirm the credential');\n\t}\n\n\t/**\n\t * Names a confirmed credential's refusal from what GitHub said. A `403` whose explanation cites OAuth App access\n\t * restrictions is an organization that has not approved the OAuth app this token belongs to; its remedy page is\n\t * that app's page in the user's authorized apps, where they request an organization's approval, addressed by the\n\t * client id GitHub reports on the refusal. Any other refusal is left unnamed, so the warning keeps GitHub's own\n\t * words.\n\t */\n\tprotected override describeRefusal(\n\t\tsession: ProviderAuthenticationSession,\n\t\trefusal: ProviderRefusal,\n\t): ProviderWarningCause | undefined {\n\t\tif (refusal.status !== 403 || !/\\bOAuth App access restrictions\\b/i.test(refusal.detail ?? '')) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst webUrl =\n\t\t\tthis.id === GitSelfManagedHostIntegrationId.CloudGitHubEnterprise\n\t\t\t\t? this.getSelfManagedInstallationUrl(session)\n\t\t\t\t: `https://${this.domain}`;\n\t\treturn {\n\t\t\treason: 'oauth-app-not-allowed',\n\t\t\t...(refusal.oauthClientId != null\n\t\t\t\t? {\n\t\t\t\t\t\tremedyUrl: `${webUrl}/settings/connections/applications/${encodeURIComponent(refusal.oauthClientId)}`,\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t};\n\t}\n\n\tprotected override async searchProviderMyPullRequests(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepos?: GitHubRepositoryDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\toptions?: SearchMyPullRequestsOptions,\n\t): Promise<PullRequest[] | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.searchMyPullRequests(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t{\n\t\t\t\trepos: repos?.map(r => `${r.owner}/${r.name}`),\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t\tsilent: options?.silent,\n\t\t\t\tstate: options?.state,\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\tprotected override async getProviderMyPullRequestsForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t\toptions?: {\n\t\t\tstate?: PullRequestStateFilter[];\n\t\t\tcursor?: string;\n\t\t\tincludeReviewRequested?: boolean;\n\t\t\tfilters?: PullRequestFilter[];\n\t\t\tsummary?: boolean;\n\t\t},\n\t): Promise<ProviderApiPagedResult<ProviderPullRequest> | undefined> {\n\t\tconst explicitFilters = options?.filters?.length ? [...new Set(options.filters)] : undefined;\n\t\t// Every account-wide read goes through our own facet search — there is deliberately no SDK\n\t\t// `getPullRequestsForUser` fallback. It could express neither a state set, nor exact relationships (its\n\t\t// `involves:` is a different question), nor the full projection, so which reads it could serve depended on\n\t\t// the options, and the two routes disagreed on more than they had to: only this one applies the Launchpad\n\t\t// ignored/included repository and organization qualifiers, so the same read returned different pull\n\t\t// requests depending on options that were supposed to affect only projection or relationship. With no\n\t\t// state and no filters this emits a single facet with no relationship qualifier, which is GitHub's own\n\t\t// `involves:@me` and `is:open` default — the same question the fallback asked, now on one route.\n\t\tconst github = await this.authenticationService.apis.github;\n\t\tif (github == null) return undefined;\n\n\t\tconst requestedStates: PullRequestStateFilter[] =\n\t\t\toptions?.state != null && options.state.length > 0 ? [...new Set(options.state)] : ['open'];\n\t\tconst facets =\n\t\t\texplicitFilters?.length != null\n\t\t\t\t? requestedStates.flatMap(state =>\n\t\t\t\t\t\texplicitFilters.map(filter => ({\n\t\t\t\t\t\t\tkey: `${state}:${filter}`,\n\t\t\t\t\t\t\tstate: state,\n\t\t\t\t\t\t\tsearch: gitHubPullRequestRelationshipQualifiers[filter],\n\t\t\t\t\t\t})),\n\t\t\t\t\t)\n\t\t\t\t: requestedStates.flatMap(state => [\n\t\t\t\t\t\t{ key: state, state: state, search: undefined },\n\t\t\t\t\t\t...(options?.includeReviewRequested === true\n\t\t\t\t\t\t\t? [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tkey: `${state}:${PullRequestFilter.ReviewRequested}`,\n\t\t\t\t\t\t\t\t\t\tstate: state,\n\t\t\t\t\t\t\t\t\t\tsearch: gitHubPullRequestRelationshipQualifiers[\n\t\t\t\t\t\t\t\t\t\t\tPullRequestFilter.ReviewRequested\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t: []),\n\t\t\t\t\t]);\n\t\tconst cursors = parsePullRequestFacetCursor(options?.cursor);\n\t\tconst hasResumableFacetCursor = Object.keys(cursors).length !== 0;\n\t\tconst facetsWithCursor = facets.filter(facet => cursors[facet.key] != null);\n\t\t// The first call has no cursor, so query every requested state. A continuation only happens after a\n\t\t// prior page reported `more:true`, whose bundle carries a cursor for each facet still in flight;\n\t\t// facets absent from the bundle are exhausted, so re-querying them from scratch would refetch the\n\t\t// same PRs (duplicated by the dedup-free sweep) and waste an API call per page. Query only the\n\t\t// states that still have a cursor, but degrade a malformed/empty cursor bundle, or one that doesn't\n\t\t// apply to the current requested states, to the first page rather than returning an empty page.\n\t\tconst facetsToQuery =\n\t\t\toptions?.cursor != null && hasResumableFacetCursor && facetsWithCursor.length !== 0\n\t\t\t\t? facetsWithCursor\n\t\t\t\t: facets;\n\t\tconst results = await Promise.allSettled(\n\t\t\tfacetsToQuery.map(async facet => ({\n\t\t\t\tkey: facet.key,\n\t\t\t\tresult: await github.searchMyPullRequestsPage(this, toTokenWithInfo(this.id, session), {\n\t\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t\t\tstate: facet.state,\n\t\t\t\t\tcursor: cursors[facet.key],\n\t\t\t\t\tsummary: options?.summary,\n\t\t\t\t\t...(facet.search != null ? { search: facet.search, includeDefaultInvolvement: false } : undefined),\n\t\t\t\t}),\n\t\t\t})),\n\t\t);\n\t\tif (results.every(result => result.status === 'rejected')) {\n\t\t\tconst first = results[0];\n\t\t\tif (first?.status === 'rejected') throw first.reason;\n\t\t}\n\n\t\tconst values = new Map<string, ProviderPullRequest>();\n\t\tconst nextCursors: GitHubPullRequestFacetCursor = {};\n\t\tconst failures = [];\n\t\tlet hasMore = false;\n\t\tlet truncated = false;\n\t\tlet structuralIncompleteness = false;\n\t\tlet unkeyedPullRequest = 0;\n\t\tfor (const outcome of results) {\n\t\t\tif (outcome.status === 'rejected') {\n\t\t\t\tfailures.push(toCollectionScopeFailure({ providerId: this.id }, outcome.reason));\n\t\t\t\ttruncated = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst { key, result } = outcome.value;\n\t\t\tfor (const pr of result.values) {\n\t\t\t\tconst mapped = toProviderPullRequest(pr);\n\t\t\t\tconst identity = getProviderPullRequestIdentity(mapped) ?? `unkeyed:${unkeyedPullRequest++}`;\n\t\t\t\tif (!values.has(identity)) {\n\t\t\t\t\tvalues.set(identity, mapped);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (result.hasMore) {\n\t\t\t\tif (result.cursor == null || result.cursor === '{}' || result.cursor === cursors[key]) {\n\t\t\t\t\ttruncated = true;\n\t\t\t\t\tstructuralIncompleteness = true;\n\t\t\t\t} else {\n\t\t\t\t\thasMore = true;\n\t\t\t\t\tnextCursors[key] = result.cursor;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (result.truncated) {\n\t\t\t\ttruncated = true;\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tvalues: [...values.values()],\n\t\t\tpaging: {\n\t\t\t\tmore: hasMore,\n\t\t\t\tcursor: hasMore ? JSON.stringify({ type: 'cursor', cursors: nextCursors }) : '{}',\n\t\t\t\ttruncated: truncated || undefined,\n\t\t\t},\n\t\t\t...(failures.length || structuralIncompleteness\n\t\t\t\t? {\n\t\t\t\t\t\tmetadata: {\n\t\t\t\t\t\t\tcompleteness: 'partial' as const,\n\t\t\t\t\t\t\t...(failures.length ? { failures: failures } : {}),\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t: undefined),\n\t\t};\n\t}\n\n\tprotected override async searchProviderMyIssues(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepos?: GitHubRepositoryDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t): Promise<IssueShape[] | undefined> {\n\t\treturn (await this.searchProviderMyIssuesWithTruncation(session, repos, cancellation))?.values;\n\t}\n\n\t/**\n\t * GitHub's account-wide issue search pages authored/assigned/mentioned independently behind one composite\n\t * cursor. This variant preserves that cursor and the provider's 1,000-result search ceiling signal.\n\t *\n\t * `options.filters` selects which of those three searches run, so a caller can narrow the union to just its\n\t * own slice (e.g. `[Assignee]` ⇒ `assignee:@me`). Omitted keeps GitHub's own definition of \"mine\" (all three).\n\t */\n\tprotected override async searchProviderMyIssuesWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\trepos?: GitHubRepositoryDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\toptions?: SearchMyIssuesOptions,\n\t): Promise<{ values: IssueShape[]; truncated: boolean } | undefined> {\n\t\t// `includeAllAssignees` becomes `assignee:*`, which needs a scope to mean anything — with none it matches\n\t\t// millions of issues across all of GitHub rather than \"assigned to anyone in the user's world\". ANY scope\n\t\t// works (one repo, several, or an org), so this refuses only the genuinely unscoped read; the scoped\n\t\t// \"assigned to anyone over these repos\" request is served by `searchIssuesPage`.\n\t\tif ((repos == null || repos.length === 0) && options?.includeAllAssignees) {\n\t\t\tthrow new IntegrationReadUnavailableError(\n\t\t\t\tthis.name,\n\t\t\t\t'`includeAllAssignees` is not supported for account-wide issue reads; scope the read to repositories instead.',\n\t\t\t);\n\t\t}\n\n\t\treturn (await this.authenticationService.apis.github)?.searchMyIssues(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t{\n\t\t\t\trepos: repos?.map(r => `${r.owner}/${r.name}`),\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t\tincludeBody: true,\n\t\t\t\tincludeAllAssignees: options?.includeAllAssignees,\n\t\t\t\tcursor: options?.cursor,\n\t\t\t\tpageSize: options?.pageSize,\n\t\t\t\tsort: options?.sort,\n\t\t\t\tcategories: options?.filters?.length\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tauthored: options.filters.includes(IssueFilter.Author),\n\t\t\t\t\t\t\tassigned: options.filters.includes(IssueFilter.Assignee),\n\t\t\t\t\t\t\tmentioned: options.filters.includes(IssueFilter.Mention),\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined,\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\t/**\n\t * Filtered pull-request search over a repository/organization or explicit current-user relationship scope. The\n\t * API client owns criteria sanitizing, facet unioning, ordering, and the composite cursor that carries GitHub's\n\t * result-ceiling signal across pages.\n\t */\n\tprotected override async searchProviderPullRequestsPage(\n\t\tsession: ProviderAuthenticationSession,\n\t\toptions: {\n\t\t\trepos?: ProviderRepoInput[];\n\t\t\torg?: string;\n\t\t\tcriteria?: PullRequestSearchCriteria;\n\t\t\tcursor?: string;\n\t\t\tpageSize?: number;\n\t\t\tsummary?: boolean;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t): Promise<ProviderPullRequestSearchPage | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.searchPullRequestsPage(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t{\n\t\t\t\trepos: options.repos?.map(r => `${r.namespace}/${r.name}`),\n\t\t\t\torg: options.org,\n\t\t\t\tcriteria: options.criteria,\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t\tcursor: options.cursor,\n\t\t\t\tpageSize: options.pageSize,\n\t\t\t\tsummary: options.summary,\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\t/**\n\t * The filtered issue search: one GraphQL request per page, no forced relationship to the current user, and no\n\t * route through the SDK's repo-scoped read (whose over-limit recovery walk can spend up to 128 requests).\n\t *\n\t * The criteria → qualifier translation and the ordering guarantee live in the API client\n\t * (`GitHubApi.searchIssuesPage`), which is also where user input is sanitized so it can't inject a qualifier.\n\t */\n\tprotected override async searchProviderIssuesPage(\n\t\tsession: ProviderAuthenticationSession,\n\t\toptions: {\n\t\t\trepos?: ProviderRepoInput[];\n\t\t\torg?: string;\n\t\t\tcriteria?: IssueSearchCriteria;\n\t\t\tcursor?: string;\n\t\t\tpageSize?: number;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t): Promise<ProviderIssueSearchPage | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.searchIssuesPage(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t{\n\t\t\t\t// `namespace` is the owner for GitHub, which is how a `repo:` qualifier names a repository.\n\t\t\t\trepos: options.repos?.map(r => `${r.namespace}/${r.name}`),\n\t\t\t\torg: options.org,\n\t\t\t\tcriteria: options.criteria,\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t\tincludeBody: true,\n\t\t\t\tcursor: options.cursor,\n\t\t\t\tpageSize: options.pageSize,\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\t/**\n\t * Counts several scopes in ONE request. GitHub's `search` reports `issueCount` on a zero-node selection, so a\n\t * count preview costs no issue transfer — measured, 30 aliased counts are a single rate-limit point.\n\t */\n\tprotected override async countProviderIssues(\n\t\tsession: ProviderAuthenticationSession,\n\t\tscopes: readonly { repos?: ProviderRepoInput[]; org?: string; criteria?: IssueSearchCriteria }[],\n\t\tcancellation?: AbortSignal,\n\t): Promise<(number | undefined)[] | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.countIssues(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\tscopes.map(s => ({\n\t\t\t\trepos: s.repos?.map(r => `${r.namespace}/${r.name}`),\n\t\t\t\torg: s.org,\n\t\t\t\tcriteria: s.criteria,\n\t\t\t})),\n\t\t\t{ baseUrl: this.apiBaseUrlFor(session) },\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\t/**\n\t * Resolves several issues by `(owner, repo, number)` by aliasing the point read rather than a search — see\n\t * {@link GitHubApi.getIssuesBatch} for why that distinction is the whole design — chunked into requests of up\n\t * to {@link issuesBatchChunkSize} coordinates, run with bounded concurrency. Settled per target already: a\n\t * coordinate whose alias fails on its own (e.g. an org enforcing SAML SSO the token isn't authorized for)\n\t * rejects only that slot, and a chunk that throws rejects only its own slots.\n\t */\n\tprotected override async getProviderIssuesBatch(\n\t\tsession: ProviderAuthenticationSession,\n\t\tcoordinates: readonly { owner: string; repo: string; number: number; project?: string }[],\n\t\tcancellation?: AbortSignal,\n\t): Promise<PromiseSettledResult<IssueShape | undefined>[] | undefined> {\n\t\tconst github = await this.authenticationService.apis.github;\n\t\tif (github == null) return undefined;\n\n\t\treturn readChunked(\n\t\t\tcoordinates,\n\t\t\tissuesBatchChunkSize,\n\t\t\tchunkCoordinates =>\n\t\t\t\tgithub.getIssuesBatch(\n\t\t\t\t\tthis,\n\t\t\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t\t\tchunkCoordinates.map(c => ({ owner: c.owner, repo: c.repo, number: c.number })),\n\t\t\t\t\t{ baseUrl: this.apiBaseUrlFor(session), includeBody: true },\n\t\t\t\t\tcancellation,\n\t\t\t\t),\n\t\t\tissue => issue,\n\t\t);\n\t}\n\n\t/**\n\t * Resolves several pull requests by `(owner, repo, number)` by aliasing the point read — see\n\t * {@link GitHubApi.getPullRequestsBatch} — chunked into requests of up to {@link pullRequestsBatchChunkSize}\n\t * coordinates, run with bounded concurrency. Rows take the same conversion as the account-wide list rows, so a\n\t * pull request reads the same whichever of the two returned it.\n\t *\n\t * A chunk that throws rejects only its own slots, so its sibling chunks still answer.\n\t */\n\tprotected override async getProviderPullRequestsBatch(\n\t\tsession: ProviderAuthenticationSession,\n\t\tcoordinates: readonly { owner: string; repo: string; number: number; project?: string }[],\n\t\toptions: { currentAccount?: { id: string; username?: string } } | undefined,\n\t\tcancellation?: AbortSignal,\n\t): Promise<PromiseSettledResult<PullRequestShape | undefined>[] | undefined> {\n\t\tconst github = await this.authenticationService.apis.github;\n\t\tif (github == null) return undefined;\n\n\t\tconst currentAccount = options?.currentAccount;\n\t\treturn readChunked(\n\t\t\tcoordinates,\n\t\t\tpullRequestsBatchChunkSize,\n\t\t\tchunkCoordinates =>\n\t\t\t\tgithub.getPullRequestsBatch(\n\t\t\t\t\tthis,\n\t\t\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t\t\tchunkCoordinates.map(c => ({ owner: c.owner, repo: c.repo, number: c.number })),\n\t\t\t\t\t{ baseUrl: this.apiBaseUrlFor(session) },\n\t\t\t\t\tcancellation,\n\t\t\t\t),\n\t\t\t(pr): PullRequestShape | undefined =>\n\t\t\t\tpr != null\n\t\t\t\t\t? fromProviderPullRequest(toProviderPullRequest(pr), this, { currentAccount: currentAccount })\n\t\t\t\t\t: undefined,\n\t\t);\n\t}\n\n\t/**\n\t * Finds each branch's pull requests by aliasing a head-ref-name query per target — see\n\t * {@link GitHubApi.getPullRequestsForBranches} — chunked and converted exactly as\n\t * {@link getProviderPullRequestsBatch} is, with the same per-target and per-chunk failure isolation.\n\t */\n\tprotected override async getProviderPullRequestsForBranches(\n\t\tsession: ProviderAuthenticationSession,\n\t\ttargets: readonly { owner: string; repo: string; project?: string; branch: string; headOwner?: string }[],\n\t\toptions: { currentAccount?: { id: string; username?: string }; limit: number },\n\t\tcancellation?: AbortSignal,\n\t): Promise<PromiseSettledResult<{ pullRequests: PullRequestShape[]; truncated: boolean }>[] | undefined> {\n\t\tconst github = await this.authenticationService.apis.github;\n\t\tif (github == null) return undefined;\n\n\t\tconst currentAccount = options.currentAccount;\n\t\treturn readChunked(\n\t\t\ttargets,\n\t\t\tpullRequestsBatchChunkSize,\n\t\t\tchunkTargets =>\n\t\t\t\tgithub.getPullRequestsForBranches(\n\t\t\t\t\tthis,\n\t\t\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t\t\tchunkTargets.map(t => ({ owner: t.owner, repo: t.repo, branch: t.branch, headOwner: t.headOwner })),\n\t\t\t\t\t{ baseUrl: this.apiBaseUrlFor(session), limit: options.limit },\n\t\t\t\t\tcancellation,\n\t\t\t\t),\n\t\t\t(found): { pullRequests: PullRequestShape[]; truncated: boolean } => ({\n\t\t\t\tpullRequests: found.pullRequests.map(pr =>\n\t\t\t\t\tfromProviderPullRequest(toProviderPullRequest(pr), this, { currentAccount: currentAccount }),\n\t\t\t\t),\n\t\t\t\ttruncated: found.truncated,\n\t\t\t}),\n\t\t);\n\t}\n\n\t/**\n\t * Counts several pull-request scopes in ONE request. Like {@link countProviderIssues}, GitHub's `search`\n\t * reports `issueCount` on a zero-node selection, so a count preview costs no pull-request transfer.\n\t */\n\tprotected override async countProviderPullRequests(\n\t\tsession: ProviderAuthenticationSession,\n\t\tscopes: readonly { repos?: ProviderRepoInput[]; org?: string; criteria?: PullRequestSearchCriteria }[],\n\t\tcancellation?: AbortSignal,\n\t): Promise<ProviderPullRequestCount[] | undefined> {\n\t\tconst counts = await (\n\t\t\tawait this.authenticationService.apis.github\n\t\t)?.countPullRequests(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\tscopes.map(s => ({\n\t\t\t\trepos: s.repos?.map(r => `${r.namespace}/${r.name}`),\n\t\t\t\torg: s.org,\n\t\t\t\tcriteria: s.criteria,\n\t\t\t})),\n\t\t\t{ baseUrl: this.apiBaseUrlFor(session) },\n\t\t\tcancellation,\n\t\t);\n\t\t// `issueCount` is the search's own total, never a floor, so no slot is a lower bound.\n\t\treturn counts?.map(count => ({ count: count }));\n\t}\n\n\tprotected override async searchProviderPullRequests(\n\t\tsession: ProviderAuthenticationSession,\n\t\tsearchQuery: string,\n\t\trepos?: GitHubRepositoryDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\toptions?: SearchPullRequestsOptions,\n\t): Promise<PullRequest[] | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.searchPullRequests(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t{\n\t\t\t\tsearch: searchQuery,\n\t\t\t\trepos: repos?.map(r => `${r.owner}/${r.name}`),\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t\t...options,\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\t/**\n\t * Stack membership for every stacked pull request in the repository, keyed by pull request number.\n\t *\n\t * List surfaces get their pull requests from the shared providers API, whose type carries no stack\n\t * membership, so the `stack`/`stackEntry` fields selected on the native per-pull-request reads never\n\t * reach them. This fills that gap in a single request for the whole repository, rather than a\n\t * per-pull-request enrichment that would scale with the list.\n\t *\n\t * `undefined` means unavailable (not enrolled in the preview, or not connected) as distinct from an\n\t * empty map, which means the repository genuinely has no stacks.\n\t */\n\toverride async getStacksByPullRequestNumber(\n\t\towner: string,\n\t\trepo: string,\n\t\tcancellation?: AbortSignal,\n\t): Promise<Map<number, PullRequestStackInfo> | undefined> {\n\t\t// The shared read path — it refreshes an expired session before use, which a bare `getSession()`\n\t\t// does not (it returns the cached session verbatim once one exists, expired or not).\n\t\tconst session = await this.resolveReadSession(undefined, undefined);\n\t\tif (session == null) return undefined;\n\n\t\tconst stacks = await (\n\t\t\tawait this.authenticationService.apis.github\n\t\t)?.getRepositoryStacks(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\towner,\n\t\t\trepo,\n\t\t\t{\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t\tif (stacks == null) return undefined;\n\n\t\tconst byNumber = new Map<number, PullRequestStackInfo>();\n\t\tfor (const stack of stacks) {\n\t\t\t// Defensive: this is a public-preview payload reaching us through an unvalidated cast, and a\n\t\t\t// malformed entry must cost its own stack's badges, not the whole panel.\n\t\t\tconst members = stack?.pull_requests;\n\t\t\tconst baseRef = stack?.base?.ref;\n\t\t\tif (members == null || baseRef == null) continue;\n\n\t\t\t// `pull_requests` is ordered bottom to top, and `position` is 1-based from the bottom.\n\t\t\tmembers.forEach((pr, i) => {\n\t\t\t\tbyNumber.set(pr.number, {\n\t\t\t\t\tid: stack.id,\n\t\t\t\t\tnumber: stack.number,\n\t\t\t\t\tsize: members.length,\n\t\t\t\t\tposition: i + 1,\n\t\t\t\t\tbaseRef: baseRef,\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\treturn byNumber;\n\t}\n\n\tprotected override async mergeProviderPullRequest(\n\t\tsession: ProviderAuthenticationSession,\n\t\tpr: PullRequest,\n\t\toptions?: {\n\t\t\tmergeMethod?: PullRequestMergeMethod;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t): Promise<boolean> {\n\t\tconst id = pr.nodeId;\n\t\tconst headRefSha = pr.refs?.head?.sha;\n\t\tif (id == null || headRefSha == null) return false;\n\n\t\tconst api = await this.authenticationService.apis.github;\n\n\t\t// GitHub rejects stacked pull requests on the legacy merge mutation, so they take the\n\t\t// asynchronous merge instead — which also lands every layer below this one.\n\t\tif (pr.stack != null) {\n\t\t\treturn (\n\t\t\t\t(await api?.mergeStackedPullRequest(\n\t\t\t\t\tthis,\n\t\t\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t\t\tpr.repository.owner,\n\t\t\t\t\tpr.repository.repo,\n\t\t\t\t\tNumber(pr.id),\n\t\t\t\t\theadRefSha,\n\t\t\t\t\t{\n\t\t\t\t\t\tmergeMethod: options?.mergeMethod,\n\t\t\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t\t\t},\n\t\t\t\t\tcancellation,\n\t\t\t\t)) ?? false\n\t\t\t);\n\t\t}\n\n\t\treturn (\n\t\t\tapi?.mergePullRequest(\n\t\t\t\tthis,\n\t\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t\tid,\n\t\t\t\theadRefSha,\n\t\t\t\t{\n\t\t\t\t\tmergeMethod: options?.mergeMethod,\n\t\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t\t},\n\t\t\t\tcancellation,\n\t\t\t) ?? false\n\t\t);\n\t}\n\n\tprotected override async getProviderCurrentAccount(\n\t\tsession: ProviderAuthenticationSession,\n\t\toptions?: { avatarSize?: number },\n\t): Promise<Account | undefined> {\n\t\treturn (await this.authenticationService.apis.github)?.getCurrentAccount(\n\t\t\tthis,\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t{\n\t\t\t\t...options,\n\t\t\t\tbaseUrl: this.apiBaseUrlFor(session),\n\t\t\t},\n\t\t);\n\t}\n\n\tprotected override getProviderPullRequestIdentityFromMaybeUrl(search: string): PullRequestUrlIdentity | undefined {\n\t\treturn getGitHubPullRequestIdentityFromMaybeUrl(search, this.id);\n\t}\n}\n\nexport class GitHubIntegration extends GitHubIntegrationBase<GitCloudHostIntegrationId.GitHub> {\n\treadonly authProvider = authProvider;\n\treadonly id = GitCloudHostIntegrationId.GitHub;\n\tprotected readonly key = this.id;\n\treadonly name: string = 'GitHub';\n\tget domain(): string {\n\t\treturn metadata.domain;\n\t}\n\n\tprotected override apiBaseUrlFor(_session: ProviderAuthenticationSession): string | undefined {\n\t\t// Undefined on purpose, NOT 'https://api.github.com'. `@gitkraken/provider-apis` treats this value as an\n\t\t// *enterprise* base and derives both endpoints from it by appending GitHub Enterprise's paths:\n\t\t// `getRESTBaseUrl` appends `/api/v3` and `getGraphQLEndpoint` appends `/api/graphql` (githubHelpers.ts).\n\t\t// Handing it the cloud host therefore built `https://api.github.com/api/graphql`, which GitHub answers\n\t\t// 404 — every cloud GraphQL read (orgs, repos, PR/issue search) failed as `not-found`. Omitting it is\n\t\t// what selects the cloud endpoints (`GITHUB_API_URL` / `GITHUB_GRAPHQL_API_URL`), so cloud must pass\n\t\t// nothing and only GHE passes its instance base.\n\t\treturn undefined;\n\t}\n\n\toverride access(): Promise<boolean> {\n\t\t// Always allow GitHub cloud integration access\n\t\treturn Promise.resolve(true);\n\t}\n\n\t// This is a special case for GitHub because we use VSCode's GitHub session, and it can be disconnected\n\t// outside of the extension.\n\toverride async refresh(): Promise<void> {\n\t\tconst authProvider = await this.authenticationService.get(this.authProvider.id);\n\t\tconst session = await authProvider.getSession(this.authProviderDescriptor);\n\t\tif (session == null && this.maybeConnected) {\n\t\t\tvoid this.disconnect({ silent: true });\n\t\t} else {\n\t\t\tif (session?.accessToken !== this._session?.accessToken) {\n\t\t\t\tthis._session = undefined;\n\t\t\t}\n\t\t\tsuper.refresh();\n\t\t}\n\t}\n}\n\nexport class GitHubEnterpriseIntegration extends GitHubIntegrationBase<GitSelfManagedHostIntegrationId.CloudGitHubEnterprise> {\n\treadonly authProvider = cloudEnterpriseAuthProvider;\n\treadonly id = GitSelfManagedHostIntegrationId.CloudGitHubEnterprise;\n\tprotected readonly key;\n\treadonly name = 'GitHub Enterprise';\n\tget domain(): string {\n\t\treturn this._domain;\n\t}\n\n\tprotected override apiBaseUrlFor(session: ProviderAuthenticationSession): string {\n\t\treturn this.getSelfManagedApiBaseUrl(session);\n\t}\n\n\tconstructor(\n\t\tctx: IntegrationServiceContext,\n\t\tauthenticationService: IntegrationAuthenticationService,\n\t\tgetProvidersApi: () => Promise<ProvidersApi>,\n\t\tdidChangeConnection: Emitter<IntegrationConnectionChangeEvent>,\n\t\tprivate readonly _domain: string,\n\t) {\n\t\tsuper(ctx, authenticationService, getProvidersApi, didChangeConnection);\n\t\tthis.key = `${this.id}:${this.domain}` as const;\n\t}\n}\n"],"mappings":"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"}
@@ -3,6 +3,7 @@ import { parseGitRemoteUrl } from "../../../git/utils/remote.utils.js";
3
3
  import { createRemoteProviderMatcher } from "../../../git/remotes/matcher.js";
4
4
  import "../constants.js";
5
5
  import { toProviderWarning } from "../results.js";
6
+ import { mergeAssessmentInto } from "../collectionMetadata.js";
6
7
  import { decodePathSegment, getRemoteHostMatcher, hostFromDomain, isWebRemoteScheme } from "../utils/domain.utils.js";
7
8
  import { getIntegrationIdForRemote, getSelfManagedBaseUrl, isGitCloudHostIntegrationId, isGitSelfManagedHostIntegrationId, remoteProviderTypeForConfig, remoteProviderTypeForIntegration } from "../utils/integration.utils.js";
8
9
  import { isAzureCloudDomain, isBitbucketCloudDomain, isGitHubDotCom, isGitLabDotCom } from "../providers/models.js";
@@ -113,11 +114,31 @@ async function resolveRepository(ctx, options) {
113
114
  } catch (ex) {
114
115
  let resolution;
115
116
  if (ex instanceof RequestNotFoundError) resolution = { status: "not-found" };
116
- else if (ex instanceof AuthenticationError) resolution = {
117
- status: "unauthorized",
118
- warning: toProviderWarning(id, domain, connectionId, ex)
119
- };
120
- else resolution = {
117
+ else if (ex instanceof AuthenticationError) {
118
+ let warning = toProviderWarning(id, domain, connectionId, ex);
119
+ try {
120
+ const failure = await integration.settleRepositoryRefusal({
121
+ owner,
122
+ name,
123
+ project,
124
+ connectionId
125
+ }, ex);
126
+ if (failure != null) {
127
+ const warnings = [];
128
+ mergeAssessmentInto(warnings, id, domain, connectionId, {
129
+ completeness: "partial",
130
+ failures: [failure]
131
+ });
132
+ warning = warnings[0] ?? warning;
133
+ }
134
+ } catch (credentialError) {
135
+ if (credentialError instanceof AuthenticationError) warning = toProviderWarning(id, domain, connectionId, credentialError);
136
+ }
137
+ resolution = {
138
+ status: "unauthorized",
139
+ warning
140
+ };
141
+ } else resolution = {
121
142
  status: "undetermined",
122
143
  warning: toProviderWarning(id, domain, connectionId, ex)
123
144
  };
@@ -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 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 { RepositoryIdentity, RepositoryResolution, ResolveRepositoryResult } from '../results.js';\nimport { toProviderWarning } from '../results.js';\nimport { decodePathSegment, getRemoteHostMatcher, hostFromDomain, isWebRemoteScheme } from '../utils/domain.utils.js';\nimport {\n\tgetIntegrationIdForRemote,\n\tgetSelfManagedBaseUrl,\n\tisGitCloudHostIntegrationId,\n\tisGitSelfManagedHostIntegrationId,\n\tremoteProviderTypeForConfig,\n\tremoteProviderTypeForIntegration,\n} from '../utils/integration.utils.js';\nimport type { RepositoryResolutionContext } from './context.js';\nimport { noConnectionWarning } from './warnings.js';\n\ntype ResolveRepositoryOptions = {\n\tproviderId?: IntegrationIds;\n\tremoteUrl: string;\n\thost?: string;\n\tconnectionId?: string;\n\t/**\n\t * Explicit self-managed host domain used to select the integration instance. Used only when the requested\n\t * connection has no configured domain; it must come from the trusted authentication configuration, not\n\t * repository or remote data.\n\t */\n\tdomain?: string;\n};\n\n/**\n * Resolution of a remote URL to a provider repository identity — the facade's equivalent of `gk repo resolve`.\n *\n * Unlike every other read here, this one's input is REPOSITORY data, so most of the code below is about not\n * letting that data choose the credentials: an explicit cloud `providerId` must agree with the remote's canonical\n * host, and a self-managed host resolves only against a domain the user already authenticated. The\n * `host-mismatch` returns are those guards, not edge cases — resolving `owner/repo` against the wrong account\n * would hand back a confidently wrong identity and seed the domain-keyed instance cache from remote data.\n */\nexport async function resolveRepository(\n\tctx: RepositoryResolutionContext,\n\toptions: ResolveRepositoryOptions,\n): Promise<ResolveRepositoryResult> {\n\tconst result = (status: RepositoryResolution['status']): ResolveRepositoryResult => ({\n\t\tresolution: { status: status },\n\t});\n\n\tconst [scheme, parsedDomain, path] = parseGitRemoteUrl(options.remoteUrl);\n\t// An SSH remote's port belongs to its SSH daemon, not to the web authority a connection is keyed by.\n\tconst hostsMatch = getRemoteHostMatcher(scheme);\n\tconst parsedHost = hostFromDomain(parsedDomain);\n\tconst explicitHost = hostFromDomain(options.host);\n\tif (parsedHost != null && explicitHost != null && !hostsMatch(parsedHost, explicitHost)) {\n\t\treturn result('host-mismatch');\n\t}\n\n\tconst matcherDomain = parsedDomain || options.host || '';\n\n\t// An explicit cloud provider must agree with the remote's canonical cloud host. Without this guard the\n\t// synthetic matcher below can reinterpret, for example, a gitlab.com URL as GitHub and then resolve a\n\t// homonymous owner/repo through the wrong account. Match once without caller configs/synthetic entries;\n\t// Azure's matcher also normalizes ssh.dev.azure.com and vs-ssh.visualstudio.com to their web host.\n\tif (options.providerId != null && isGitCloudHostIntegrationId(options.providerId)) {\n\t\tconst nativeProvider = createRemoteProviderMatcher([])(options.remoteUrl, matcherDomain, path, scheme);\n\t\tconst nativeId = nativeProvider != null ? getIntegrationIdForRemote(nativeProvider) : undefined;\n\t\tconst nativeDomain = nativeProvider?.domain ?? matcherDomain;\n\t\tconst canonicalHost =\n\t\t\toptions.providerId === GitCloudHostIntegrationId.GitHub\n\t\t\t\t? isGitHubDotCom(nativeDomain)\n\t\t\t\t: options.providerId === GitCloudHostIntegrationId.GitLab\n\t\t\t\t\t? isGitLabDotCom(nativeDomain)\n\t\t\t\t\t: options.providerId === GitCloudHostIntegrationId.Bitbucket\n\t\t\t\t\t\t? isBitbucketCloudDomain(nativeDomain)\n\t\t\t\t\t\t: isAzureCloudDomain(nativeDomain);\n\t\tif (nativeId !== options.providerId || !canonicalHost) return result('host-mismatch');\n\t}\n\n\tlet provider = createRemoteProviderMatcher(buildMatcherConfigs(ctx, options, parsedDomain))(\n\t\toptions.remoteUrl,\n\t\tmatcherDomain,\n\t\tpath,\n\t\tscheme,\n\t);\n\tif (provider == null) return result('invalid-remote-url');\n\n\tlet id = options.providerId ?? getIntegrationIdForRemote(provider);\n\t// Custom Azure DevOps Server domains matched via getRemoteConfigs return undefined from\n\t// getIntegrationIdForRemote because the provider is marked custom; map them to the server id so the\n\t// project-scoped lookup uses that host's configured connection and API base URL.\n\tif (id == null && provider.id === 'azure-devops' && provider.custom) {\n\t\tid = GitSelfManagedHostIntegrationId.AzureDevOpsServer;\n\t}\n\tif (id == null) return result('unsupported-provider');\n\n\t// On a self-managed host, resolve only against a TRUSTED host: the pinned connection's configured\n\t// domain, the explicit `domain`, or — when neither was supplied — a configured host matching the\n\t// remote's. That last case keeps `remoteUrl` (repository-supplied) out of the trusted path: it selects\n\t// among hosts the user already authenticated and can never introduce a new one. Resolving `owner/repo`\n\t// against some other host's account would, if that host has the same owner/repo, return a confidently\n\t// wrong identity — and would seed the domain-keyed integration cache (never evicted before dispose) with\n\t// an entry derived from repository data.\n\tconst urlHost = hostFromDomain(provider.domain);\n\t// Normalize BOTH sides before comparing: a stored connection domain is usually a full URL\n\t// (`https://git.example.com`) while `urlHost` is already a bare host, so a raw compare would fail on\n\t// scheme/trailing-slash alone and wrongly reject a correctly-configured connection.\n\tlet trustedHost =\n\t\toptions.connectionId != null || options.domain != null\n\t\t\t? hostFromDomain(ctx.resolveDomainForRead(id, options.connectionId, options.domain))\n\t\t\t: undefined;\n\tlet connectionId = options.connectionId;\n\tlet providerRepo: ResourceDescriptor | undefined;\n\tif (isGitSelfManagedHostIntegrationId(id)) {\n\t\tif (path.split('/').some(segment => /^(?:\\.|%2e){1,2}$/i.test(segment) || /%2f|%5c|\\\\/i.test(segment))) {\n\t\t\treturn result('invalid-remote-url');\n\t\t}\n\n\t\tconst configured = ctx.getConfigured(id);\n\t\tconst configuredHosts = configured.map(c => hostFromDomain(c.domain));\n\t\tif (trustedHost == null && urlHost != null) {\n\t\t\tconst matchingHosts = new Set(configuredHosts.filter(host => hostsMatch(host, urlHost)));\n\t\t\tif (matchingHosts.size > 1) return result('host-mismatch');\n\n\t\t\ttrustedHost = matchingHosts.values().next().value;\n\t\t}\n\t\t// A configured host that doesn't match the remote's is a genuine mismatch. With nothing configured\n\t\t// there is no host to mismatch against — it's simply not connected, so fall through to the\n\t\t// `unauthorized` path below (still WITHOUT resolving by the URL domain, so repo data can't seed the\n\t\t// cache).\n\t\tif (trustedHost == null && configuredHosts.length !== 0) return result('host-mismatch');\n\t\tif (trustedHost != null && urlHost != null && !hostsMatch(trustedHost, urlHost)) {\n\t\t\treturn result('host-mismatch');\n\t\t}\n\n\t\tconst installation = matchInstallation(\n\t\t\tctx,\n\t\t\toptions,\n\t\t\tid,\n\t\t\tconfigured.filter(c => hostFromDomain(c.domain) === trustedHost),\n\t\t\t{ scheme: scheme, domain: parsedDomain, path: path },\n\t\t);\n\t\tif (typeof installation === 'string') return result(installation);\n\n\t\tif (installation != null) {\n\t\t\tprovider = installation.provider;\n\t\t\tconnectionId = installation.connectionId;\n\t\t\tproviderRepo = installation.repoDesc;\n\t\t}\n\t}\n\n\tconst owner = provider.owner;\n\tconst name = provider.repoName;\n\tif (owner == null || name == null) return result('invalid-remote-url');\n\n\tproviderRepo ??= provider.repoDesc;\n\n\tlet integration;\n\ttry {\n\t\t// Resolve the instance through the trusted target (as `getIntegrationForRead` does):\n\t\t// `resolveReadSession` looks the session up against the instance's domain-scoped descriptor, so\n\t\t// selecting the instance by the URL domain could miss the session and degrade to `no-connection`.\n\t\tintegration = isGitSelfManagedHostIntegrationId(id)\n\t\t\t? trustedHost != null\n\t\t\t\t? await ctx.getIntegrationForRead(id, connectionId, trustedHost)\n\t\t\t\t: undefined\n\t\t\t: await ctx.getIntegrationForDomain(id, provider.domain);\n\t} catch {\n\t\tintegration = undefined;\n\t}\n\tif (integration == null) {\n\t\t// Attach a warning, as the thrown-AuthenticationError branch below does: a consumer driving auth\n\t\t// recovery off `warning.kind` would otherwise get nothing for an unresolvable target.\n\t\treturn {\n\t\t\tresolution: {\n\t\t\t\tstatus: 'unauthorized',\n\t\t\t\twarning: noConnectionWarning(id, provider.domain, connectionId),\n\t\t\t},\n\t\t};\n\t}\n\t// Issue trackers have no `getRepo` client; a git host without `getRepoFn` leaves `getRepoInfo`\n\t// undefined. Either way this provider can't resolve repositories.\n\tif (isIssuesIntegration(integration) || integration.getRepoInfo == null) {\n\t\treturn result('unsupported-provider');\n\t}\n\n\t// Azure repos are org + project scoped; the remote provider exposes project as `providerDesc.repoDomain`.\n\tconst project = provider.id === 'azure-devops' ? provider.providerDesc?.repoDomain : undefined;\n\tconst domain = trustedHost ?? provider.domain;\n\n\t// Azure's lookup is project-scoped and returns `undefined` without one, which the connection-gap branch\n\t// below would misreport as `unauthorized` (driving a pointless reconnect). A remote URL that carries no\n\t// project simply can't address an Azure repo.\n\tif (provider.id === 'azure-devops' && project == null) return result('invalid-remote-url');\n\n\ttry {\n\t\tconst repo = await integration.getRepoInfo({\n\t\t\t...providerRepo,\n\t\t\towner: owner,\n\t\t\tname: name,\n\t\t\tproject: project,\n\t\t\tconnectionId: connectionId,\n\t\t});\n\t\tif (repo == null) {\n\t\t\t// `getRepoInfo` returns undefined only when no session could be resolved (not connected, or the\n\t\t\t// requested connection is gone) — a real 404 throws below. So this is a connection gap. Attach a\n\t\t\t// warning so a consumer driving auth recovery off `warning.kind` sees the same signal it gets from\n\t\t\t// the thrown-AuthenticationError branch below; without it, the most common unauthorized case is\n\t\t\t// silent.\n\t\t\treturn {\n\t\t\t\tresolution: {\n\t\t\t\t\tstatus: 'unauthorized',\n\t\t\t\t\twarning: noConnectionWarning(id, domain, connectionId),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Prefer the provider's canonical namespace/name (GitHub's REST/GraphQL lookup follows the 301\n\t\t// rename redirect, so a stale old name resolves to the new canonical identity), falling back to the\n\t\t// parsed remote when the response omits them. `renamed` is a case-insensitive compare of input vs\n\t\t// canonical, mirroring gkcli's `EqualFold`, so hosts that merely echo the input casing (e.g.\n\t\t// Bitbucket Server/Azure) don't get spuriously flagged.\n\t\tconst canonicalOwner = repo.namespace || owner;\n\t\tconst canonicalName = repo.name || name;\n\t\tconst renamed =\n\t\t\tcanonicalOwner.toLowerCase() !== owner.toLowerCase() || canonicalName.toLowerCase() !== name.toLowerCase();\n\n\t\tconst identity: RepositoryIdentity = {\n\t\t\tproviderId: id,\n\t\t\tdomain: domain,\n\t\t\towner: canonicalOwner,\n\t\t\tname: canonicalName,\n\t\t\tproject: project,\n\t\t\tremoteUrl: options.remoteUrl,\n\t\t\trenamed: renamed,\n\t\t};\n\t\treturn { resolution: { status: 'resolved', identity: identity } };\n\t} catch (ex) {\n\t\t// Order matters: 404 throws RequestNotFoundError (not `undefined`), so check not-found before auth\n\t\t// and before the generic 5xx/unknown bucket — never classify a 401/403 as not-found.\n\t\tlet resolution: RepositoryResolution;\n\t\tif (ex instanceof RequestNotFoundError) {\n\t\t\tresolution = { status: 'not-found' };\n\t\t} else if (ex instanceof AuthenticationError) {\n\t\t\tresolution = { status: 'unauthorized', warning: toProviderWarning(id, domain, connectionId, ex) };\n\t\t} else {\n\t\t\tresolution = { status: 'undetermined', warning: toProviderWarning(id, domain, connectionId, ex) };\n\t\t}\n\t\treturn { resolution: resolution };\n\t}\n}\n\n/**\n * Narrows a web remote on a self-managed host to the configured installation it lives under: the pinned\n * connection's or, unpinned, the one whose installation path is the longest prefix of the remote's, so two\n * installations behind one host each resolve with their own credentials. The provider is re-matched against the\n * path relative to that installation, because the remote parsers expect the repository path to start at the\n * host root. An SSH remote carries no installation path, so it is left to the host-level checks.\n */\nfunction matchInstallation(\n\tctx: RepositoryResolutionContext,\n\toptions: ResolveRepositoryOptions,\n\tid: GitSelfManagedHostIntegrationId,\n\tconnections: ConfiguredIntegrationDescriptor[],\n\tremote: { scheme: string; domain: string; path: string },\n):\n\t| { provider: RemoteProvider; connectionId: string | undefined; repoDesc: ResourceDescriptor }\n\t| 'host-mismatch'\n\t| 'invalid-remote-url'\n\t| undefined {\n\tif (!isWebRemoteScheme(remote.scheme)) return undefined;\n\n\tconst installationPath = (connection: ConfiguredIntegrationDescriptor | undefined): string | undefined => {\n\t\tconst baseUrl = getSelfManagedBaseUrl(\n\t\t\tid,\n\t\t\tconnection != null ? (connection.baseUrl ?? connection.domain) : options.domain,\n\t\t);\n\t\treturn baseUrl != null ? new URL(baseUrl).pathname.replace(/\\/+$/, '') : undefined;\n\t};\n\n\t// The connection an unpinned read already targets comes first, so it wins a tie between installations.\n\tconst defaultConnection = connections.find(c => c.primary) ?? connections[0];\n\t// A cloud sync serves the host's cloud-scoped default instead, even over a local primary (see\n\t// `getStoredSession`), so an unpinned read reaches the default's installation only when both agree.\n\tconst cloudConnections = connections.filter(c => c.cloud);\n\tconst cloudDefault = cloudConnections.find(c => c.primary) ?? cloudConnections[0];\n\tconst isDefaultReadable =\n\t\tdefaultConnection?.cloud !== false ||\n\t\tcloudDefault == null ||\n\t\tinstallationPath(cloudDefault) === installationPath(defaultConnection);\n\t// Whether an unpinned read can reach this connection's session: the default one is read without an id, any\n\t// other only by a real cloud token id. Session lookup treats every connection id as one, and a local (PAT)\n\t// descriptor's or domain-derived legacy id is not (see `getConfiguredConnectionId`).\n\tconst isReadable = (connection: ConfiguredIntegrationDescriptor | undefined): boolean =>\n\t\toptions.connectionId != null ||\n\t\t(connection === defaultConnection\n\t\t\t? isDefaultReadable\n\t\t\t: connection?.cloud === true && hostFromDomain(connection.id) !== connection.domain);\n\n\tconst candidates: (ConfiguredIntegrationDescriptor | undefined)[] =\n\t\toptions.connectionId != null\n\t\t\t? connections.filter(c => c.id === options.connectionId)\n\t\t\t: [defaultConnection, ...connections.filter(c => c !== defaultConnection)];\n\t// With no configured candidate, the explicit (trusted) `domain` is the only installation address there is.\n\tif (!candidates.length) {\n\t\tcandidates.push(undefined);\n\t}\n\n\tconst remotePath = `/${remote.path}`;\n\tconst remoteSegments = remote.path.split('/');\n\t// The remote's path below an installation, or `undefined` when it isn't under it. Azure DevOps Server runs on\n\t// IIS, which serves its paths decoded and case-insensitively, so it compares segment by segment.\n\tconst relativeTo = (basePath: string): string | undefined => {\n\t\tif (id !== GitSelfManagedHostIntegrationId.AzureDevOpsServer) {\n\t\t\treturn remotePath.startsWith(`${basePath}/`) ? remotePath.slice(basePath.length + 1) : undefined;\n\t\t}\n\n\t\tconst baseSegments = basePath.split('/').filter(Boolean);\n\t\tconst isUnder =\n\t\t\tremoteSegments.length > baseSegments.length &&\n\t\t\tbaseSegments.every(\n\t\t\t\t(segment, i) =>\n\t\t\t\t\tdecodePathSegment(segment).toLowerCase() === decodePathSegment(remoteSegments[i]).toLowerCase(),\n\t\t\t);\n\t\treturn isUnder ? remoteSegments.slice(baseSegments.length).join('/') : undefined;\n\t};\n\tlet match: { connection: ConfiguredIntegrationDescriptor | undefined; basePath: string } | undefined;\n\tlet checked = false;\n\tfor (const connection of candidates) {\n\t\tconst basePath = installationPath(connection);\n\t\tif (basePath == null) continue;\n\n\t\tchecked = true;\n\t\tif (relativeTo(basePath) == null) continue;\n\n\t\t// The longest installation wins; on a tie, a readable connection replaces an unreadable one.\n\t\tconst isBetter =\n\t\t\tmatch == null ||\n\t\t\tbasePath.length > match.basePath.length ||\n\t\t\t(basePath.length === match.basePath.length && !isReadable(match.connection) && isReadable(connection));\n\t\tif (isBetter) {\n\t\t\tmatch = { connection: connection, basePath: basePath };\n\t\t}\n\t}\n\tif (match == null) return checked ? 'host-mismatch' : undefined;\n\t// Reading through the default connection instead would address its installation with a path relative to\n\t// another one, so an unreachable installation fails closed.\n\tif (!isReadable(match.connection)) return 'host-mismatch';\n\n\tconst connectionId =\n\t\toptions.connectionId ?? (match.connection !== defaultConnection ? match.connection?.id : undefined);\n\n\tconst matcher = createRemoteProviderMatcher(\n\t\tbuildMatcherConfigs(ctx, { ...options, providerId: id }, remote.domain),\n\t);\n\tconst rematch = (path: string) => matcher(options.remoteUrl, remote.domain, relativeTo(path) ?? '', remote.scheme);\n\tlet basePath = match.basePath;\n\tlet provider = rematch(basePath);\n\tif (id !== GitSelfManagedHostIntegrationId.AzureDevOpsServer) {\n\t\treturn provider != null\n\t\t\t? { provider: provider, connectionId: connectionId, repoDesc: provider.repoDesc }\n\t\t\t: 'invalid-remote-url';\n\t}\n\n\t// An Azure DevOps Server address may name the collection as well as the virtual directory; the parser expects\n\t// the collection in the path, so re-base on the address's parent when no project was found.\n\tif (basePath && provider?.providerDesc?.repoDomain == null) {\n\t\tbasePath = basePath.slice(0, basePath.lastIndexOf('/'));\n\t\tprovider = rematch(basePath);\n\t}\n\tif (provider == null) return 'invalid-remote-url';\n\n\t// Requests address the repository's virtual directory from the host root, not from the installation the\n\t// re-match made it relative to; the absolute form also tells a directory apart from a collection of that name.\n\tconst repoDesc = provider.repoDesc;\n\tconst relative = typeof repoDesc.virtualDirectory === 'string' ? repoDesc.virtualDirectory : undefined;\n\tconst virtualDirectory =\n\t\t[...basePath.split('/').filter(Boolean).map(decodePathSegment), ...(relative?.split('/') ?? [])].join('/') ||\n\t\tundefined;\n\treturn {\n\t\tprovider: provider,\n\t\tconnectionId: connectionId,\n\t\trepoDesc: { ...repoDesc, virtualDirectory: virtualDirectory },\n\t};\n}\n\n/**\n * The remote-matcher configs for this resolution: the user's own host remote configs (self-managed/custom\n * domains), plus a synthetic exact-domain entry for an explicit `providerId` + host so a custom domain still maps\n * to the right provider for path parsing.\n */\nfunction buildMatcherConfigs(\n\tctx: RepositoryResolutionContext,\n\toptions: { providerId?: IntegrationIds; host?: string },\n\tparsedDomain: string,\n): RemoteProviderConfig[] {\n\tconst configs: RemoteProviderConfig[] = [];\n\tfor (const cfg of ctx.getRemoteConfigs()) {\n\t\tconst type = remoteProviderTypeForConfig(cfg.type);\n\t\tif (type == null) continue;\n\n\t\t// Forward both domain- and regex-based custom remotes (carrying any protocol override), so a\n\t\t// regex-configured host resolves instead of falling through to `unsupported`.\n\t\tif (cfg.domain) {\n\t\t\tconfigs.push({ type: type, domain: cfg.domain, protocol: cfg.protocol });\n\t\t} else if (cfg.regex) {\n\t\t\tconfigs.push({ type: type, regex: cfg.regex, protocol: cfg.protocol });\n\t\t}\n\t}\n\tif (options.providerId == null) return configs;\n\n\tconst type = remoteProviderTypeForIntegration(options.providerId);\n\tconst domain = parsedDomain || options.host;\n\tif (type == null || !domain) return configs;\n\n\t// The synthetic exact-domain entry is unshifted to the front, so it wins the match over the user's own\n\t// config for the same host. Carry that config's protocol override across (matched by domain or regex,\n\t// mirroring `ignoreSSLErrors`) so a self-managed host configured for a custom protocol — e.g. plain\n\t// `http` — isn't silently downgraded to the provider default here.\n\tconst lowerDomain = domain.toLowerCase();\n\tconst protocol = configs.find(c => {\n\t\tif (c.type !== type) return false;\n\t\tif (c.domain != null) return c.domain.toLowerCase() === lowerDomain;\n\n\t\t// Truthy (not just non-null): an empty regex would compile to a match-everything pattern.\n\t\tif (c.regex) {\n\t\t\ttry {\n\t\t\t\treturn new RegExp(c.regex, 'i').test(lowerDomain);\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t})?.protocol;\n\tconfigs.unshift({ type: type, domain: domain, protocol: protocol });\n\treturn configs;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"resolveRepository.js","names":[],"sources":["../../../../../plus/integrations/src/reads/resolveRepository.ts"],"sourcesContent":["import type { RemoteProvider } from '@gitlens/git/models/remoteProvider.js';\nimport type { ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport type { RemoteProviderConfig } from '@gitlens/git/remotes/matcher.js';\nimport { createRemoteProviderMatcher } from '@gitlens/git/remotes/matcher.js';\nimport { parseGitRemoteUrl } from '@gitlens/git/utils/remote.utils.js';\nimport type { ConfiguredIntegrationDescriptor } from '../authentication/models.js';\nimport { mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { GitCloudHostIntegrationId, GitSelfManagedHostIntegrationId } from '../constants.js';\nimport { AuthenticationError, RequestNotFoundError } from '../errors.js';\nimport { isIssuesIntegration } from '../models/issuesIntegration.js';\nimport { isAzureCloudDomain, isBitbucketCloudDomain, isGitHubDotCom, isGitLabDotCom } from '../providers/models.js';\nimport type { ProviderWarning, RepositoryIdentity, RepositoryResolution, ResolveRepositoryResult } from '../results.js';\nimport { toProviderWarning } from '../results.js';\nimport { decodePathSegment, getRemoteHostMatcher, hostFromDomain, isWebRemoteScheme } from '../utils/domain.utils.js';\nimport {\n\tgetIntegrationIdForRemote,\n\tgetSelfManagedBaseUrl,\n\tisGitCloudHostIntegrationId,\n\tisGitSelfManagedHostIntegrationId,\n\tremoteProviderTypeForConfig,\n\tremoteProviderTypeForIntegration,\n} from '../utils/integration.utils.js';\nimport type { RepositoryResolutionContext } from './context.js';\nimport { noConnectionWarning } from './warnings.js';\n\ntype ResolveRepositoryOptions = {\n\tproviderId?: IntegrationIds;\n\tremoteUrl: string;\n\thost?: string;\n\tconnectionId?: string;\n\t/**\n\t * Explicit self-managed host domain used to select the integration instance. Used only when the requested\n\t * connection has no configured domain; it must come from the trusted authentication configuration, not\n\t * repository or remote data.\n\t */\n\tdomain?: string;\n};\n\n/**\n * Resolution of a remote URL to a provider repository identity — the facade's equivalent of `gk repo resolve`.\n *\n * Unlike every other read here, this one's input is REPOSITORY data, so most of the code below is about not\n * letting that data choose the credentials: an explicit cloud `providerId` must agree with the remote's canonical\n * host, and a self-managed host resolves only against a domain the user already authenticated. The\n * `host-mismatch` returns are those guards, not edge cases — resolving `owner/repo` against the wrong account\n * would hand back a confidently wrong identity and seed the domain-keyed instance cache from remote data.\n */\nexport async function resolveRepository(\n\tctx: RepositoryResolutionContext,\n\toptions: ResolveRepositoryOptions,\n): Promise<ResolveRepositoryResult> {\n\tconst result = (status: RepositoryResolution['status']): ResolveRepositoryResult => ({\n\t\tresolution: { status: status },\n\t});\n\n\tconst [scheme, parsedDomain, path] = parseGitRemoteUrl(options.remoteUrl);\n\t// An SSH remote's port belongs to its SSH daemon, not to the web authority a connection is keyed by.\n\tconst hostsMatch = getRemoteHostMatcher(scheme);\n\tconst parsedHost = hostFromDomain(parsedDomain);\n\tconst explicitHost = hostFromDomain(options.host);\n\tif (parsedHost != null && explicitHost != null && !hostsMatch(parsedHost, explicitHost)) {\n\t\treturn result('host-mismatch');\n\t}\n\n\tconst matcherDomain = parsedDomain || options.host || '';\n\n\t// An explicit cloud provider must agree with the remote's canonical cloud host. Without this guard the\n\t// synthetic matcher below can reinterpret, for example, a gitlab.com URL as GitHub and then resolve a\n\t// homonymous owner/repo through the wrong account. Match once without caller configs/synthetic entries;\n\t// Azure's matcher also normalizes ssh.dev.azure.com and vs-ssh.visualstudio.com to their web host.\n\tif (options.providerId != null && isGitCloudHostIntegrationId(options.providerId)) {\n\t\tconst nativeProvider = createRemoteProviderMatcher([])(options.remoteUrl, matcherDomain, path, scheme);\n\t\tconst nativeId = nativeProvider != null ? getIntegrationIdForRemote(nativeProvider) : undefined;\n\t\tconst nativeDomain = nativeProvider?.domain ?? matcherDomain;\n\t\tconst canonicalHost =\n\t\t\toptions.providerId === GitCloudHostIntegrationId.GitHub\n\t\t\t\t? isGitHubDotCom(nativeDomain)\n\t\t\t\t: options.providerId === GitCloudHostIntegrationId.GitLab\n\t\t\t\t\t? isGitLabDotCom(nativeDomain)\n\t\t\t\t\t: options.providerId === GitCloudHostIntegrationId.Bitbucket\n\t\t\t\t\t\t? isBitbucketCloudDomain(nativeDomain)\n\t\t\t\t\t\t: isAzureCloudDomain(nativeDomain);\n\t\tif (nativeId !== options.providerId || !canonicalHost) return result('host-mismatch');\n\t}\n\n\tlet provider = createRemoteProviderMatcher(buildMatcherConfigs(ctx, options, parsedDomain))(\n\t\toptions.remoteUrl,\n\t\tmatcherDomain,\n\t\tpath,\n\t\tscheme,\n\t);\n\tif (provider == null) return result('invalid-remote-url');\n\n\tlet id = options.providerId ?? getIntegrationIdForRemote(provider);\n\t// Custom Azure DevOps Server domains matched via getRemoteConfigs return undefined from\n\t// getIntegrationIdForRemote because the provider is marked custom; map them to the server id so the\n\t// project-scoped lookup uses that host's configured connection and API base URL.\n\tif (id == null && provider.id === 'azure-devops' && provider.custom) {\n\t\tid = GitSelfManagedHostIntegrationId.AzureDevOpsServer;\n\t}\n\tif (id == null) return result('unsupported-provider');\n\n\t// On a self-managed host, resolve only against a TRUSTED host: the pinned connection's configured\n\t// domain, the explicit `domain`, or — when neither was supplied — a configured host matching the\n\t// remote's. That last case keeps `remoteUrl` (repository-supplied) out of the trusted path: it selects\n\t// among hosts the user already authenticated and can never introduce a new one. Resolving `owner/repo`\n\t// against some other host's account would, if that host has the same owner/repo, return a confidently\n\t// wrong identity — and would seed the domain-keyed integration cache (never evicted before dispose) with\n\t// an entry derived from repository data.\n\tconst urlHost = hostFromDomain(provider.domain);\n\t// Normalize BOTH sides before comparing: a stored connection domain is usually a full URL\n\t// (`https://git.example.com`) while `urlHost` is already a bare host, so a raw compare would fail on\n\t// scheme/trailing-slash alone and wrongly reject a correctly-configured connection.\n\tlet trustedHost =\n\t\toptions.connectionId != null || options.domain != null\n\t\t\t? hostFromDomain(ctx.resolveDomainForRead(id, options.connectionId, options.domain))\n\t\t\t: undefined;\n\tlet connectionId = options.connectionId;\n\tlet providerRepo: ResourceDescriptor | undefined;\n\tif (isGitSelfManagedHostIntegrationId(id)) {\n\t\tif (path.split('/').some(segment => /^(?:\\.|%2e){1,2}$/i.test(segment) || /%2f|%5c|\\\\/i.test(segment))) {\n\t\t\treturn result('invalid-remote-url');\n\t\t}\n\n\t\tconst configured = ctx.getConfigured(id);\n\t\tconst configuredHosts = configured.map(c => hostFromDomain(c.domain));\n\t\tif (trustedHost == null && urlHost != null) {\n\t\t\tconst matchingHosts = new Set(configuredHosts.filter(host => hostsMatch(host, urlHost)));\n\t\t\tif (matchingHosts.size > 1) return result('host-mismatch');\n\n\t\t\ttrustedHost = matchingHosts.values().next().value;\n\t\t}\n\t\t// A configured host that doesn't match the remote's is a genuine mismatch. With nothing configured\n\t\t// there is no host to mismatch against — it's simply not connected, so fall through to the\n\t\t// `unauthorized` path below (still WITHOUT resolving by the URL domain, so repo data can't seed the\n\t\t// cache).\n\t\tif (trustedHost == null && configuredHosts.length !== 0) return result('host-mismatch');\n\t\tif (trustedHost != null && urlHost != null && !hostsMatch(trustedHost, urlHost)) {\n\t\t\treturn result('host-mismatch');\n\t\t}\n\n\t\tconst installation = matchInstallation(\n\t\t\tctx,\n\t\t\toptions,\n\t\t\tid,\n\t\t\tconfigured.filter(c => hostFromDomain(c.domain) === trustedHost),\n\t\t\t{ scheme: scheme, domain: parsedDomain, path: path },\n\t\t);\n\t\tif (typeof installation === 'string') return result(installation);\n\n\t\tif (installation != null) {\n\t\t\tprovider = installation.provider;\n\t\t\tconnectionId = installation.connectionId;\n\t\t\tproviderRepo = installation.repoDesc;\n\t\t}\n\t}\n\n\tconst owner = provider.owner;\n\tconst name = provider.repoName;\n\tif (owner == null || name == null) return result('invalid-remote-url');\n\n\tproviderRepo ??= provider.repoDesc;\n\n\tlet integration;\n\ttry {\n\t\t// Resolve the instance through the trusted target (as `getIntegrationForRead` does):\n\t\t// `resolveReadSession` looks the session up against the instance's domain-scoped descriptor, so\n\t\t// selecting the instance by the URL domain could miss the session and degrade to `no-connection`.\n\t\tintegration = isGitSelfManagedHostIntegrationId(id)\n\t\t\t? trustedHost != null\n\t\t\t\t? await ctx.getIntegrationForRead(id, connectionId, trustedHost)\n\t\t\t\t: undefined\n\t\t\t: await ctx.getIntegrationForDomain(id, provider.domain);\n\t} catch {\n\t\tintegration = undefined;\n\t}\n\tif (integration == null) {\n\t\t// Attach a warning, as the thrown-AuthenticationError branch below does: a consumer driving auth\n\t\t// recovery off `warning.kind` would otherwise get nothing for an unresolvable target.\n\t\treturn {\n\t\t\tresolution: {\n\t\t\t\tstatus: 'unauthorized',\n\t\t\t\twarning: noConnectionWarning(id, provider.domain, connectionId),\n\t\t\t},\n\t\t};\n\t}\n\t// Issue trackers have no `getRepo` client; a git host without `getRepoFn` leaves `getRepoInfo`\n\t// undefined. Either way this provider can't resolve repositories.\n\tif (isIssuesIntegration(integration) || integration.getRepoInfo == null) {\n\t\treturn result('unsupported-provider');\n\t}\n\n\t// Azure repos are org + project scoped; the remote provider exposes project as `providerDesc.repoDomain`.\n\tconst project = provider.id === 'azure-devops' ? provider.providerDesc?.repoDomain : undefined;\n\tconst domain = trustedHost ?? provider.domain;\n\n\t// Azure's lookup is project-scoped and returns `undefined` without one, which the connection-gap branch\n\t// below would misreport as `unauthorized` (driving a pointless reconnect). A remote URL that carries no\n\t// project simply can't address an Azure repo.\n\tif (provider.id === 'azure-devops' && project == null) return result('invalid-remote-url');\n\n\ttry {\n\t\tconst repo = await integration.getRepoInfo({\n\t\t\t...providerRepo,\n\t\t\towner: owner,\n\t\t\tname: name,\n\t\t\tproject: project,\n\t\t\tconnectionId: connectionId,\n\t\t});\n\t\tif (repo == null) {\n\t\t\t// `getRepoInfo` returns undefined only when no session could be resolved (not connected, or the\n\t\t\t// requested connection is gone) — a real 404 throws below. So this is a connection gap. Attach a\n\t\t\t// warning so a consumer driving auth recovery off `warning.kind` sees the same signal it gets from\n\t\t\t// the thrown-AuthenticationError branch below; without it, the most common unauthorized case is\n\t\t\t// silent.\n\t\t\treturn {\n\t\t\t\tresolution: {\n\t\t\t\t\tstatus: 'unauthorized',\n\t\t\t\t\twarning: noConnectionWarning(id, domain, connectionId),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Prefer the provider's canonical namespace/name (GitHub's REST/GraphQL lookup follows the 301\n\t\t// rename redirect, so a stale old name resolves to the new canonical identity), falling back to the\n\t\t// parsed remote when the response omits them. `renamed` is a case-insensitive compare of input vs\n\t\t// canonical, mirroring gkcli's `EqualFold`, so hosts that merely echo the input casing (e.g.\n\t\t// Bitbucket Server/Azure) don't get spuriously flagged.\n\t\tconst canonicalOwner = repo.namespace || owner;\n\t\tconst canonicalName = repo.name || name;\n\t\tconst renamed =\n\t\t\tcanonicalOwner.toLowerCase() !== owner.toLowerCase() || canonicalName.toLowerCase() !== name.toLowerCase();\n\n\t\tconst identity: RepositoryIdentity = {\n\t\t\tproviderId: id,\n\t\t\tdomain: domain,\n\t\t\towner: canonicalOwner,\n\t\t\tname: canonicalName,\n\t\t\tproject: project,\n\t\t\tremoteUrl: options.remoteUrl,\n\t\t\trenamed: renamed,\n\t\t};\n\t\treturn { resolution: { status: 'resolved', identity: identity } };\n\t} catch (ex) {\n\t\t// Order matters: 404 throws RequestNotFoundError (not `undefined`), so check not-found before auth\n\t\t// and before the generic 5xx/unknown bucket — never classify a 401/403 as not-found.\n\t\tlet resolution: RepositoryResolution;\n\t\tif (ex instanceof RequestNotFoundError) {\n\t\t\tresolution = { status: 'not-found' };\n\t\t} else if (ex instanceof AuthenticationError) {\n\t\t\t// One repository refusing a credential that works (e.g. GitHub's OAuth App access restrictions) is that\n\t\t\t// repository's warning, scoped and with its cause, not a dead connection's.\n\t\t\tlet warning = toProviderWarning(id, domain, connectionId, ex);\n\t\t\ttry {\n\t\t\t\tconst failure = await integration.settleRepositoryRefusal(\n\t\t\t\t\t{ owner: owner, name: name, project: project, connectionId: connectionId },\n\t\t\t\t\tex,\n\t\t\t\t);\n\t\t\t\tif (failure != null) {\n\t\t\t\t\tconst warnings: ProviderWarning[] = [];\n\t\t\t\t\tmergeAssessmentInto(warnings, id, domain, connectionId, {\n\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\tfailures: [failure],\n\t\t\t\t\t});\n\t\t\t\t\twarning = warnings[0] ?? warning;\n\t\t\t\t}\n\t\t\t} catch (credentialError) {\n\t\t\t\tif (credentialError instanceof AuthenticationError) {\n\t\t\t\t\twarning = toProviderWarning(id, domain, connectionId, credentialError);\n\t\t\t\t}\n\t\t\t}\n\t\t\tresolution = {\n\t\t\t\tstatus: 'unauthorized',\n\t\t\t\twarning: warning,\n\t\t\t};\n\t\t} else {\n\t\t\tresolution = { status: 'undetermined', warning: toProviderWarning(id, domain, connectionId, ex) };\n\t\t}\n\t\treturn { resolution: resolution };\n\t}\n}\n\n/**\n * Narrows a web remote on a self-managed host to the configured installation it lives under: the pinned\n * connection's or, unpinned, the one whose installation path is the longest prefix of the remote's, so two\n * installations behind one host each resolve with their own credentials. The provider is re-matched against the\n * path relative to that installation, because the remote parsers expect the repository path to start at the\n * host root. An SSH remote carries no installation path, so it is left to the host-level checks.\n */\nfunction matchInstallation(\n\tctx: RepositoryResolutionContext,\n\toptions: ResolveRepositoryOptions,\n\tid: GitSelfManagedHostIntegrationId,\n\tconnections: ConfiguredIntegrationDescriptor[],\n\tremote: { scheme: string; domain: string; path: string },\n):\n\t| { provider: RemoteProvider; connectionId: string | undefined; repoDesc: ResourceDescriptor }\n\t| 'host-mismatch'\n\t| 'invalid-remote-url'\n\t| undefined {\n\tif (!isWebRemoteScheme(remote.scheme)) return undefined;\n\n\tconst installationPath = (connection: ConfiguredIntegrationDescriptor | undefined): string | undefined => {\n\t\tconst baseUrl = getSelfManagedBaseUrl(\n\t\t\tid,\n\t\t\tconnection != null ? (connection.baseUrl ?? connection.domain) : options.domain,\n\t\t);\n\t\treturn baseUrl != null ? new URL(baseUrl).pathname.replace(/\\/+$/, '') : undefined;\n\t};\n\n\t// The connection an unpinned read already targets comes first, so it wins a tie between installations.\n\tconst defaultConnection = connections.find(c => c.primary) ?? connections[0];\n\t// A cloud sync serves the host's cloud-scoped default instead, even over a local primary (see\n\t// `getStoredSession`), so an unpinned read reaches the default's installation only when both agree.\n\tconst cloudConnections = connections.filter(c => c.cloud);\n\tconst cloudDefault = cloudConnections.find(c => c.primary) ?? cloudConnections[0];\n\tconst isDefaultReadable =\n\t\tdefaultConnection?.cloud !== false ||\n\t\tcloudDefault == null ||\n\t\tinstallationPath(cloudDefault) === installationPath(defaultConnection);\n\t// Whether an unpinned read can reach this connection's session: the default one is read without an id, any\n\t// other only by a real cloud token id. Session lookup treats every connection id as one, and a local (PAT)\n\t// descriptor's or domain-derived legacy id is not (see `getConfiguredConnectionId`).\n\tconst isReadable = (connection: ConfiguredIntegrationDescriptor | undefined): boolean =>\n\t\toptions.connectionId != null ||\n\t\t(connection === defaultConnection\n\t\t\t? isDefaultReadable\n\t\t\t: connection?.cloud === true && hostFromDomain(connection.id) !== connection.domain);\n\n\tconst candidates: (ConfiguredIntegrationDescriptor | undefined)[] =\n\t\toptions.connectionId != null\n\t\t\t? connections.filter(c => c.id === options.connectionId)\n\t\t\t: [defaultConnection, ...connections.filter(c => c !== defaultConnection)];\n\t// With no configured candidate, the explicit (trusted) `domain` is the only installation address there is.\n\tif (!candidates.length) {\n\t\tcandidates.push(undefined);\n\t}\n\n\tconst remotePath = `/${remote.path}`;\n\tconst remoteSegments = remote.path.split('/');\n\t// The remote's path below an installation, or `undefined` when it isn't under it. Azure DevOps Server runs on\n\t// IIS, which serves its paths decoded and case-insensitively, so it compares segment by segment.\n\tconst relativeTo = (basePath: string): string | undefined => {\n\t\tif (id !== GitSelfManagedHostIntegrationId.AzureDevOpsServer) {\n\t\t\treturn remotePath.startsWith(`${basePath}/`) ? remotePath.slice(basePath.length + 1) : undefined;\n\t\t}\n\n\t\tconst baseSegments = basePath.split('/').filter(Boolean);\n\t\tconst isUnder =\n\t\t\tremoteSegments.length > baseSegments.length &&\n\t\t\tbaseSegments.every(\n\t\t\t\t(segment, i) =>\n\t\t\t\t\tdecodePathSegment(segment).toLowerCase() === decodePathSegment(remoteSegments[i]).toLowerCase(),\n\t\t\t);\n\t\treturn isUnder ? remoteSegments.slice(baseSegments.length).join('/') : undefined;\n\t};\n\tlet match: { connection: ConfiguredIntegrationDescriptor | undefined; basePath: string } | undefined;\n\tlet checked = false;\n\tfor (const connection of candidates) {\n\t\tconst basePath = installationPath(connection);\n\t\tif (basePath == null) continue;\n\n\t\tchecked = true;\n\t\tif (relativeTo(basePath) == null) continue;\n\n\t\t// The longest installation wins; on a tie, a readable connection replaces an unreadable one.\n\t\tconst isBetter =\n\t\t\tmatch == null ||\n\t\t\tbasePath.length > match.basePath.length ||\n\t\t\t(basePath.length === match.basePath.length && !isReadable(match.connection) && isReadable(connection));\n\t\tif (isBetter) {\n\t\t\tmatch = { connection: connection, basePath: basePath };\n\t\t}\n\t}\n\tif (match == null) return checked ? 'host-mismatch' : undefined;\n\t// Reading through the default connection instead would address its installation with a path relative to\n\t// another one, so an unreachable installation fails closed.\n\tif (!isReadable(match.connection)) return 'host-mismatch';\n\n\tconst connectionId =\n\t\toptions.connectionId ?? (match.connection !== defaultConnection ? match.connection?.id : undefined);\n\n\tconst matcher = createRemoteProviderMatcher(\n\t\tbuildMatcherConfigs(ctx, { ...options, providerId: id }, remote.domain),\n\t);\n\tconst rematch = (path: string) => matcher(options.remoteUrl, remote.domain, relativeTo(path) ?? '', remote.scheme);\n\tlet basePath = match.basePath;\n\tlet provider = rematch(basePath);\n\tif (id !== GitSelfManagedHostIntegrationId.AzureDevOpsServer) {\n\t\treturn provider != null\n\t\t\t? { provider: provider, connectionId: connectionId, repoDesc: provider.repoDesc }\n\t\t\t: 'invalid-remote-url';\n\t}\n\n\t// An Azure DevOps Server address may name the collection as well as the virtual directory; the parser expects\n\t// the collection in the path, so re-base on the address's parent when no project was found.\n\tif (basePath && provider?.providerDesc?.repoDomain == null) {\n\t\tbasePath = basePath.slice(0, basePath.lastIndexOf('/'));\n\t\tprovider = rematch(basePath);\n\t}\n\tif (provider == null) return 'invalid-remote-url';\n\n\t// Requests address the repository's virtual directory from the host root, not from the installation the\n\t// re-match made it relative to; the absolute form also tells a directory apart from a collection of that name.\n\tconst repoDesc = provider.repoDesc;\n\tconst relative = typeof repoDesc.virtualDirectory === 'string' ? repoDesc.virtualDirectory : undefined;\n\tconst virtualDirectory =\n\t\t[...basePath.split('/').filter(Boolean).map(decodePathSegment), ...(relative?.split('/') ?? [])].join('/') ||\n\t\tundefined;\n\treturn {\n\t\tprovider: provider,\n\t\tconnectionId: connectionId,\n\t\trepoDesc: { ...repoDesc, virtualDirectory: virtualDirectory },\n\t};\n}\n\n/**\n * The remote-matcher configs for this resolution: the user's own host remote configs (self-managed/custom\n * domains), plus a synthetic exact-domain entry for an explicit `providerId` + host so a custom domain still maps\n * to the right provider for path parsing.\n */\nfunction buildMatcherConfigs(\n\tctx: RepositoryResolutionContext,\n\toptions: { providerId?: IntegrationIds; host?: string },\n\tparsedDomain: string,\n): RemoteProviderConfig[] {\n\tconst configs: RemoteProviderConfig[] = [];\n\tfor (const cfg of ctx.getRemoteConfigs()) {\n\t\tconst type = remoteProviderTypeForConfig(cfg.type);\n\t\tif (type == null) continue;\n\n\t\t// Forward both domain- and regex-based custom remotes (carrying any protocol override), so a\n\t\t// regex-configured host resolves instead of falling through to `unsupported`.\n\t\tif (cfg.domain) {\n\t\t\tconfigs.push({ type: type, domain: cfg.domain, protocol: cfg.protocol });\n\t\t} else if (cfg.regex) {\n\t\t\tconfigs.push({ type: type, regex: cfg.regex, protocol: cfg.protocol });\n\t\t}\n\t}\n\tif (options.providerId == null) return configs;\n\n\tconst type = remoteProviderTypeForIntegration(options.providerId);\n\tconst domain = parsedDomain || options.host;\n\tif (type == null || !domain) return configs;\n\n\t// The synthetic exact-domain entry is unshifted to the front, so it wins the match over the user's own\n\t// config for the same host. Carry that config's protocol override across (matched by domain or regex,\n\t// mirroring `ignoreSSLErrors`) so a self-managed host configured for a custom protocol — e.g. plain\n\t// `http` — isn't silently downgraded to the provider default here.\n\tconst lowerDomain = domain.toLowerCase();\n\tconst protocol = configs.find(c => {\n\t\tif (c.type !== type) return false;\n\t\tif (c.domain != null) return c.domain.toLowerCase() === lowerDomain;\n\n\t\t// Truthy (not just non-null): an empty regex would compile to a match-everything pattern.\n\t\tif (c.regex) {\n\t\t\ttry {\n\t\t\t\treturn new RegExp(c.regex, 'i').test(lowerDomain);\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t})?.protocol;\n\tconfigs.unshift({ type: type, domain: domain, protocol: protocol });\n\treturn configs;\n}\n"],"mappings":"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"}
@@ -443,7 +443,11 @@ least one ID when present; a failure attributed to nothing below the provider ca
443
443
  from a caught exception never carries one, even when that call targeted a single organization, and an omission
444
444
  keeps its attribution in `omission.scope` instead. The batch reads (`getIssuesBatch`, `getPullRequestsBatch`,
445
445
  `getPullRequestsForBranches`) are the exception: a refused target's warning names the scope its target names —
446
- the Azure DevOps organization and project, the repository on other git hosts, or the tracker's resource.
446
+ the Azure DevOps organization and project, the repository on other git hosts, or the tracker's resource. So is
447
+ `resolveRepository`: a `401`/`403` for the one repository it resolves settles as a batch read's refused target does,
448
+ so on a host that can check the credential (Azure DevOps, Azure DevOps Server, Bitbucket, Bitbucket Data Center,
449
+ GitHub, GitHub Enterprise) an `unauthorized` resolution whose credential checks out carries a warning scoped to that
450
+ repository, while on one that cannot (GitLab) it stays the connection's.
447
451
 
448
452
  A scoped `auth` failure also means **the credential itself was accepted**. A dead token can come back as
449
453
  nothing but scoped refusals wherever a read reaches its scopes without an uncached request to the connection
@@ -454,8 +458,8 @@ with one uncached check before reporting them; a batch read does so only when ev
454
458
  target that answered already proved the credential. A refused credential fails the whole read instead: an
455
459
  unscoped `auth` warning, `fetchFailed`, no results served from the cache, and the usual connection recovery.
456
460
  A refusal the provider pins on the credential itself, like Bitbucket's or Jira Cloud's for a token missing the
457
- OAuth scopes the read needs, is published unscoped too, once for the connection, because a reconnect
458
- (consenting to them again) is what fixes it; the scopes that answered keep their results.
461
+ OAuth scopes the read needs, is published unscoped too, once for the connection, because a reconnect (consenting
462
+ to them again) is what fixes it; the scopes that answered keep their results.
459
463
 
460
464
  The promise holds for a read that carries no unscoped `auth` warning of its own: one that does already asks for a
461
465
  reconnect, and its other scoped refusals are not checked. Two cases stay scoped even then. A check that could not
@@ -502,11 +506,24 @@ switch (warning.cause?.reason) {
502
506
 
503
507
  It is set **only on a scoped `auth` warning whose credential was confirmed** (see `scope` above), because until
504
508
  then these refusals look exactly like a dead credential: Azure DevOps answers a third-party OAuth app its
505
- organization disallows with the same bare `401` it gives an expired token. Only Azure DevOps names causes today,
506
- from answers captured against the live service. **Its absence proves nothing**: a refusal this layer cannot name
509
+ organization disallows with the same bare `401` it gives an expired token. Azure DevOps and GitHub name causes
510
+ today, from answers captured against the live services. **Its absence proves nothing**: a refusal this layer cannot name
507
511
  still carries the provider's own explanation, when it gave one, in `message`, e.g. an organization that only
508
512
  allowlists global personal access tokens.
509
513
 
514
+ GitHub's `oauth-app-not-allowed` is an organization with **OAuth App access restrictions** that has not approved the
515
+ OAuth app the token belongs to. GitHub leaves that organization's private repositories out of every listing and search
516
+ without an error, and GraphQL answers a lookup of one exactly as it answers a missing repository, so the only read that
517
+ can report it is `resolveRepository`: a GitHub miss is confirmed over REST, which refuses such a repository with a
518
+ `403` that says why. The resolution is then `unauthorized` with the warning scoped to the repository
519
+ (`scope.repositoryId`), and `cause.remedyUrl` is the app's page in the user's authorized OAuth apps, where they ask the
520
+ organization to approve it. The restriction belongs to the organization, so one approval fixes every repository it
521
+ hid. A GitHub organization enforcing SAML SSO is not this path: GraphQL answers it `FORBIDDEN`, not `NOT_FOUND`, so
522
+ its repository resolves `undetermined`, as before. Neither the confirming read nor the credential check raises the
523
+ reauthentication prompt (`onReauthenticationRequired`): an organization's restriction is not fixed by
524
+ reauthenticating, and a credential the check refuses is reported by the resolution itself, as an unscoped `auth`
525
+ warning.
526
+
510
527
  ### `omission` — succeeded, but withheld results
511
528
 
512
529
  `other` covers two facts with **opposite remedies**: a request that failed, and a request that succeeded while
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.9.0",
4
+ "version": "0.9.1",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": {
7
7
  "name": "GitKraken",