@gitkraken/core-gitlens 0.22.0 → 0.22.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.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.22.1] - 2026-10-28
10
+
11
+ ### Fixed
12
+
13
+ - Reports Jira issue reads against unknown sites or invalid installation paths as failures, preserving genuine missing-issue responses as absences ([#5981](https://github.com/gitkraken/vscode-gitlens/issues/5981)) (plus/integrations)
14
+
9
15
  ## [0.22.0] - 2026-10-27
10
16
 
11
17
  ### Added
@@ -1004,7 +1010,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
1004
1010
 
1005
1011
  - Initial release. Bundles `@gitlens/utils`, `@gitlens/git`, `@gitlens/git-cli`, `@gitlens/ai`, and `@gitlens/git-github` into a single core npm package with subpath exports.
1006
1012
 
1007
- [unreleased]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.21.0...HEAD
1013
+ [unreleased]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.22.1...HEAD
1014
+ [0.22.1]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.22.0...gitkraken:releases/core/v0.22.1
1008
1015
  [0.22.0]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.21.0...gitkraken:releases/core/v0.22.0
1009
1016
  [0.21.0]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.20.2...gitkraken:releases/core/v0.21.0
1010
1017
  [0.20.2]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.20.1...gitkraken:releases/core/v0.20.2
@@ -106,8 +106,8 @@ export declare class JiraIntegration extends IssuesIntegration<IssuesCloudHostIn
106
106
  *
107
107
  * Never proves an absence. Jira silently omits a key it can't find or show (listing it in no `issueErrors` either),
108
108
  * and answers a moved issue under its new key, which no longer matches the key asked for. Either way the target's
109
- * slot is rejected, so the full read, whose 404 does prove an absence, decides. When no target was answered and
110
- * nothing failed, the check declines instead, so a batch of only such keys costs a full read, not a failure.
109
+ * slot is rejected, so the full read distinguishes a missing issue from a site failure. When no target was
110
+ * answered and nothing failed, the check declines, so a batch of only such keys costs a full read, not a failure.
111
111
  */
112
112
  protected getProviderIssuesEtagFieldsByResourceId(session: ProviderAuthenticationSession, targets: readonly {
113
113
  resourceId: string;
@@ -428,8 +428,8 @@ var JiraIntegration = class extends IssuesIntegration {
428
428
  *
429
429
  * Never proves an absence. Jira silently omits a key it can't find or show (listing it in no `issueErrors` either),
430
430
  * and answers a moved issue under its new key, which no longer matches the key asked for. Either way the target's
431
- * slot is rejected, so the full read, whose 404 does prove an absence, decides. When no target was answered and
432
- * nothing failed, the check declines instead, so a batch of only such keys costs a full read, not a failure.
431
+ * slot is rejected, so the full read distinguishes a missing issue from a site failure. When no target was
432
+ * answered and nothing failed, the check declines, so a batch of only such keys costs a full read, not a failure.
433
433
  */
434
434
  async getProviderIssuesEtagFieldsByResourceId(session, targets) {
435
435
  const slots = new Array(targets.length);
@@ -1 +1 @@
1
- {"version":3,"file":"jira.js","names":[],"sources":["../../../../../plus/integrations/src/providers/jira.ts"],"sourcesContent":["import type { CollectionMetadata } from '@gitkraken/provider-apis';\nimport { JIRA_MAX_PROJECT_KEYS_PER_REQUEST } from '@gitkraken/provider-apis';\nimport type { Account } from '@gitlens/git/models/author.js';\nimport type { AutolinkReference, DynamicAutolinkReference } from '@gitlens/git/models/autolink.js';\nimport type { Issue, IssueShape } from '@gitlens/git/models/issue.js';\nimport type { IssueOrPullRequest } from '@gitlens/git/models/issueOrPullRequest.js';\nimport type { IssueResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { chunk } from '@gitlens/utils/array.js';\nimport { Logger } from '@gitlens/utils/logger.js';\nimport { mapSettledBounded } from '@gitlens/utils/promise.js';\nimport type { IntegrationAuthenticationProviderDescriptor } from '../authentication/integrationAuthenticationProvider.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 { IssuesCloudHostIntegrationId, providerFanOutConcurrency } from '../constants.js';\nimport type { IssueEtagFields } from '../models/integration.js';\nimport type {\n\tAccountWideIssuesResult,\n\tIssuesForProjectOptions,\n\tProjectIssuesDrain,\n\tProjectIssuesRequest,\n\tSearchMyIssuesOptions,\n} from '../models/issueReads.js';\nimport { groupProjectIssuesSearches, IssuesIntegration } from '../models/issuesIntegration.js';\nimport type { JiraIssueEtagResponse } from './jiraIssueByKey.js';\nimport { jiraBulkFetchMaxKeys, toJiraIssueEtagFields } from './jiraIssueByKey.js';\nimport type { ProviderApiCollectionResult } from './models.js';\nimport { providersMetadata, toAccount, toIssueShape, toProviderIssueStates } from './models.js';\nimport { isJiraMissingProjectError } from './providerErrors.js';\nimport { DiscoveryCache, discoveryCacheTtl } from './utils/discoveryCache.js';\nimport { createIssueAutolinkReferences } from './utils/issueAutolinks.js';\nimport type { JiraDrainOptions, JiraIssuePageFetcher } from './utils/jiraIssueDrains.js';\nimport {\n\tdrainJiraIssues,\n\treadUserScopedJiraIssues,\n\tsearchJiraProjectIssues,\n\tsummarizePageMetadata,\n\ttoUniqueJiraIssueShapes,\n} from './utils/jiraIssueDrains.js';\nimport { collectProviderPagedResult, mergeCollectionMetadata } from './utils/providerPaging.js';\n\nconst metadata = providersMetadata[IssuesCloudHostIntegrationId.Jira];\nconst authProvider = Object.freeze({ id: metadata.id, scopes: metadata.scopes });\nconst maxPagesPerRequest = 10;\n/** A numeric issue id, which Jira's issue reads accept in place of a key. */\nconst numericIssueId = /^\\d+$/;\n\n/**\n * `assignee` and `creator` are user FIELDS: Jira resolves an accountId against the directory, which is the identity\n * that keeps matching once a display name cannot be looked up — a deactivated account, or a profile whose visibility\n * hides the name. Measured against a live site: a deactivated assignee returns its issues by accountId and an empty\n * page by display name, and Jira reports that miss as a successful empty search rather than an error, so the list\n * simply appears empty.\n *\n * `mention` is NOT a user field. It is `comment ~ \"...\"`, a free-text search over comment bodies, so an accountId\n * matches nothing there and the display name is the only value that can. Hence the id is applied to the first two\n * and the handle is kept for the third, rather than swapping `user` wholesale.\n */\nfunction toUserIdentities(user: string, options: IssuesForProjectOptions): { userField: string; mention: string } {\n\treturn { userField: options.userId ?? user, mention: user };\n}\n\nexport type JiraBaseDescriptor = IssueResourceDescriptor;\n\nexport interface JiraOrganizationDescriptor extends JiraBaseDescriptor {\n\turl: string;\n\tavatarUrl: string;\n}\n\nexport interface JiraProjectDescriptor extends JiraBaseDescriptor {\n\tresourceId: string;\n}\n\nexport class JiraIntegration extends IssuesIntegration<IssuesCloudHostIntegrationId.Jira> {\n\treadonly authProvider: IntegrationAuthenticationProviderDescriptor = authProvider;\n\treadonly id = IssuesCloudHostIntegrationId.Jira;\n\tprotected readonly key = this.id;\n\treadonly name: string = 'Jira';\n\n\tget domain(): string {\n\t\treturn metadata.domain;\n\t}\n\n\tprotected get apiBaseUrl(): string {\n\t\treturn 'https://api.atlassian.com';\n\t}\n\n\t/**\n\t * Built from the sites and their projects, so it follows them: rebuilt when a site's project list changes or the\n\t * caches are dropped, and once older than {@link discoveryCacheTtl}. An expired set is still served while it is\n\t * rebuilt in the background, because autolinks are read on every render of a commit message and must not wait\n\t * on the network, nor go empty just because nothing read the projects for a while (#5907).\n\t */\n\tprivate readonly _autolinks = new Map<\n\t\tstring,\n\t\t{ autolinks: (AutolinkReference | DynamicAutolinkReference)[]; builtAt: number }\n\t>();\n\tprivate readonly _autolinksBuilds = new Map<string, Promise<(AutolinkReference | DynamicAutolinkReference)[]>>();\n\n\toverride async autolinks(): Promise<(AutolinkReference | DynamicAutolinkReference)[]> {\n\t\tconst connected = this.maybeConnected ?? (await this.isConnected());\n\t\tif (!connected || this._session == null) return [];\n\n\t\tconst session = this._session;\n\t\tconst cached = this._autolinks.get(session.accessToken);\n\t\t// A session kept through a failed refresh is expired: building with it would send that token to the provider\n\t\t// on every render, so serve whatever was built before (none after a forced re-sync) until a read refreshes it.\n\t\tif (this.connectionExpired === true) return cached?.autolinks ?? [];\n\t\tif (cached == null) return this.buildAutolinks(session);\n\n\t\tif (Date.now() - cached.builtAt >= discoveryCacheTtl) {\n\t\t\tvoid this.buildAutolinks(session);\n\t\t}\n\t\treturn cached.autolinks;\n\t}\n\n\t/** One build per token at a time: concurrent renders share it rather than each re-reading sites and projects. */\n\tprivate buildAutolinks(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<(AutolinkReference | DynamicAutolinkReference)[]> {\n\t\tconst { accessToken } = session;\n\t\tlet build = this._autolinksBuilds.get(accessToken);\n\t\tif (build == null) {\n\t\t\tbuild = this.buildAutolinksCore(session).finally(() => this._autolinksBuilds.delete(accessToken));\n\t\t\tthis._autolinksBuilds.set(accessToken, build);\n\t\t}\n\t\treturn build;\n\t}\n\n\tprivate async buildAutolinksCore(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<(AutolinkReference | DynamicAutolinkReference)[]> {\n\t\tconst { accessToken } = session;\n\t\tconst generation = this._projects.generation;\n\t\ttry {\n\t\t\tconst organizations = await this.getProviderResourcesForUser(session);\n\t\t\tif (!organizations?.length) return [];\n\n\t\t\t// Served from the cache for every site whose projects are still fresh; only the rest are read.\n\t\t\tconst { values: projects, metadata } = await this.getProviderProjectsForResourcesWithMetadata(\n\t\t\t\tsession,\n\t\t\t\torganizations,\n\t\t\t);\n\n\t\t\tconst autolinks: (AutolinkReference | DynamicAutolinkReference)[] = [];\n\t\t\tfor (const project of projects) {\n\t\t\t\tconst organization = organizations.find(o => o.id === project.resourceId);\n\t\t\t\tif (organization == null) continue;\n\n\t\t\t\tautolinks.push(\n\t\t\t\t\t...createIssueAutolinkReferences(\n\t\t\t\t\t\tproject.key,\n\t\t\t\t\t\t`${organization.url}/browse/${project.key}-<num>`,\n\t\t\t\t\t\torganization,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// A set missing a site's projects is served but not kept, so the next call tries that site again; one\n\t\t\t// built from projects read before the caches were dropped is not kept either.\n\t\t\tconst complete = metadata == null || metadata.completeness === 'complete';\n\t\t\tif (complete && this._projects.generation === generation) {\n\t\t\t\tthis._autolinks.set(accessToken, { autolinks: autolinks, builtAt: Date.now() });\n\t\t\t}\n\t\t\treturn autolinks;\n\t\t} catch (ex) {\n\t\t\t// Autolinks are decoration: keep serving the last set rather than failing the render that asked.\n\t\t\tLogger.error(ex, 'JiraIntegration.autolinks');\n\t\t\treturn this._autolinks.get(accessToken)?.autolinks ?? [];\n\t\t}\n\t}\n\n\tprotected override async getProviderAccountForResource(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: JiraOrganizationDescriptor,\n\t): Promise<Account | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst user = await api.getCurrentUserForResource(toTokenWithInfo(this.id, session), resource.id);\n\n\t\tif (user == null) return undefined;\n\t\treturn toAccount(user, this);\n\t}\n\n\t/** Atlassian's account id is the same on every site, so the first site answers for all of them. */\n\tprotected override async getProviderCurrentAccount(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<Account | undefined> {\n\t\tconst resource = (await this.getProviderResourcesForUser(session))?.[0];\n\t\treturn resource != null ? this.getProviderAccountForResource(session, resource) : undefined;\n\t}\n\n\t/** The site-list request the resource cache would otherwise answer; see `IntegrationBase.validateCredential`. */\n\tprotected override async validateCredential(session: ProviderAuthenticationSession): Promise<void> {\n\t\tconst api = await this.getProvidersApi();\n\t\tif ((await api.getJiraResourcesForCurrentUser(toTokenWithInfo(this.id, session))) == null) {\n\t\t\tthrow new Error('Jira did not confirm the credential');\n\t\t}\n\t}\n\n\t/**\n\t * Atlassian's `401` for a token missing a scope the request needs, which a reconnect fixes by consenting to it\n\t * again; see `IntegrationBase.isCredentialRefusal`. Atlassian gives the same answer to a malformed request URL,\n\t * so a request the SDK built wrong would read as the credential's too.\n\t */\n\tprotected override isCredentialRefusal(refusal: ProviderRefusal): boolean {\n\t\treturn refusal.status === 401 && /scope does not match/i.test(refusal.detail ?? '');\n\t}\n\n\t/**\n\t * The sites and the projects discovered under them, both of which expire and are dropped on a re-sync, so a site\n\t * or project created or deleted mid-session is picked up (#5907). Keyed by token, and projects by token and site.\n\t */\n\tprivate readonly _organizations = new DiscoveryCache<JiraOrganizationDescriptor[]>();\n\tprivate readonly _projects = new DiscoveryCache<JiraProjectDescriptor[]>();\n\t/** When the caches were last dropped; a persisted discovery read before then is as stale as they were. */\n\tprivate _discoveryInvalidatedAt = 0;\n\n\tprotected override async getProviderResourcesForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t\tforce: boolean = false,\n\t): Promise<JiraOrganizationDescriptor[] | undefined> {\n\t\tconst { accessToken } = session;\n\n\t\tconst cachedResources = this._organizations.get(accessToken);\n\t\tif (cachedResources != null && !force) return cachedResources;\n\n\t\tconst generation = this._organizations.generation;\n\t\tconst organizations = await this.readOrganizations(session);\n\t\tif (organizations == null) {\n\t\t\tthis._organizations.delete(accessToken);\n\t\t\treturn undefined;\n\t\t}\n\n\t\tthis._organizations.set(accessToken, organizations, { generation: generation });\n\t\treturn organizations;\n\t}\n\n\tprotected override getProviderUncachedResourcesForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<JiraOrganizationDescriptor[] | undefined> {\n\t\treturn this.readOrganizations(session);\n\t}\n\n\tprivate async readOrganizations(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<JiraOrganizationDescriptor[] | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst resources = await api.getJiraResourcesForCurrentUser(toTokenWithInfo(this.id, session));\n\t\treturn resources?.map(r => ({ ...r, key: r.id }));\n\t}\n\n\tprotected override async getProviderProjectsForResources(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources: JiraOrganizationDescriptor[],\n\t\tforce: boolean = false,\n\t): Promise<JiraProjectDescriptor[] | undefined> {\n\t\treturn (await this.getProviderProjectsForResourcesWithMetadata(session, resources, force)).values;\n\t}\n\n\tprotected override async getProviderProjectsForResourcesWithMetadata(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources: JiraOrganizationDescriptor[],\n\t\tforce: boolean = false,\n\t): Promise<ProviderApiCollectionResult<JiraProjectDescriptor>> {\n\t\tconst { accessToken } = session;\n\t\tconst projectsCache = this._projects;\n\t\tconst generation = projectsCache.generation;\n\n\t\t// Taken before the read, so a clear while it is in flight can't empty what was cached when it started. A forced\n\t\t// read reads every resource, and still keeps a cached list for one it can't read completely.\n\t\tconst cachedProjects = new Map<string, JiraProjectDescriptor[]>();\n\t\tfor (const resource of resources) {\n\t\t\tconst projects = projectsCache.get(`${accessToken}:${resource.id}`);\n\t\t\tif (projects != null) {\n\t\t\t\tcachedProjects.set(resource.id, projects);\n\t\t\t}\n\t\t}\n\t\tconst resourcesWithoutProjects = force\n\t\t\t? resources\n\t\t\t: resources.filter(resource => !cachedProjects.has(resource.id));\n\n\t\tconst read = await this.readProjects(session, resourcesWithoutProjects);\n\t\tconst values = [...read.partial];\n\t\tfor (const [resourceId, projects] of read.complete) {\n\t\t\t// Returned either way; not stored when the cache was dropped while this read was in flight: it is what this\n\t\t\t// read found, only no longer trusted for the next one.\n\t\t\tif (projectsCache.set(`${accessToken}:${resourceId}`, projects, { generation: generation })) {\n\t\t\t\tthis._autolinks.delete(accessToken);\n\t\t\t}\n\t\t}\n\t\tfor (const resource of resources) {\n\t\t\tconst resourceProjects = read.complete.get(resource.id) ?? cachedProjects.get(resource.id);\n\t\t\tif (resourceProjects != null) {\n\t\t\t\tvalues.push(...resourceProjects);\n\t\t\t}\n\t\t}\n\t\treturn { values: uniqueProjects(values), metadata: read.metadata };\n\t}\n\n\tprotected override async getProviderUncachedProjectsForResourcesWithMetadata(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources: JiraOrganizationDescriptor[],\n\t): Promise<ProviderApiCollectionResult<JiraProjectDescriptor>> {\n\t\tconst read = await this.readProjects(session, resources);\n\t\treturn {\n\t\t\tvalues: uniqueProjects([...read.partial, ...[...read.complete.values()].flat()]),\n\t\t\tmetadata: read.metadata,\n\t\t};\n\t}\n\n\t/**\n\t * Reads the projects of each resource from the provider, apart by whether a resource's list came back complete:\n\t * only a complete one may be cached as the resource's project set.\n\t */\n\tprivate async readProjects(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources: JiraOrganizationDescriptor[],\n\t): Promise<{\n\t\tcomplete: Map<string, JiraProjectDescriptor[]>;\n\t\tpartial: JiraProjectDescriptor[];\n\t\tmetadata: CollectionMetadata | undefined;\n\t}> {\n\t\tconst complete = new Map<string, JiraProjectDescriptor[]>();\n\t\tconst partial: JiraProjectDescriptor[] = [];\n\t\tlet metadata: CollectionMetadata | undefined;\n\t\tif (resources.length === 0) return { complete: complete, partial: partial, metadata: metadata };\n\n\t\tconst api = await this.getProvidersApi();\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\tconst drains = await Promise.allSettled(\n\t\t\tresources.map(async resource => ({\n\t\t\t\tresource: resource,\n\t\t\t\tresult: await collectProviderPagedResult(\n\t\t\t\t\tcursor => api.getJiraProjectsForResource(tokenWithInfo, resource.id, { cursor: cursor }),\n\t\t\t\t\tmaxPagesPerRequest,\n\t\t\t\t\t{ providerId: this.id, resourceId: resource.id },\n\t\t\t\t),\n\t\t\t})),\n\t\t);\n\n\t\tdrains.forEach((drain, index) => {\n\t\t\tif (drain.status === 'rejected') {\n\t\t\t\tconst resource = resources[index];\n\t\t\t\tif (resource == null) return;\n\n\t\t\t\tmetadata = mergeCollectionMetadata(metadata, {\n\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\tfailures: [\n\t\t\t\t\t\ttoCollectionScopeFailure({ providerId: this.id, resourceId: resource.id }, drain.reason),\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst { resource, result } = drain.value;\n\t\t\tmetadata = mergeCollectionMetadata(metadata, result.metadata);\n\t\t\tconst projects = result.values\n\t\t\t\t.filter(project => project.resourceId === resource.id)\n\t\t\t\t.map(project => ({ ...project }));\n\t\t\tconst incomplete =\n\t\t\t\tresult.truncated === true || (result.metadata != null && result.metadata.completeness !== 'complete');\n\t\t\tif (incomplete) {\n\t\t\t\tpartial.push(...projects);\n\t\t\t\tif (\n\t\t\t\t\tresult.truncated === true &&\n\t\t\t\t\t(result.metadata == null || result.metadata.completeness === 'complete')\n\t\t\t\t) {\n\t\t\t\t\tmetadata = mergeCollectionMetadata(metadata, { completeness: 'partial' });\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tcomplete.set(resource.id, projects);\n\t\t});\n\t\treturn { complete: complete, partial: partial, metadata: metadata };\n\t}\n\n\tprotected override async getProviderIssuesForProject(\n\t\tsession: ProviderAuthenticationSession,\n\t\tproject: JiraProjectDescriptor,\n\t\toptions?: IssuesForProjectOptions,\n\t): Promise<IssueShape[] | undefined> {\n\t\treturn (await this.getProviderIssuesForProjectWithTruncation(session, project, options))?.values;\n\t}\n\n\tprotected override async getProviderIssuesForProjectWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\tproject: JiraProjectDescriptor,\n\t\toptions?: IssuesForProjectOptions,\n\t): Promise<ProjectIssuesDrain | undefined> {\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\n\t\tconst api = await this.getProvidersApi();\n\n\t\tconst states = toProviderIssueStates(options?.state);\n\t\t// By key, not name, as the multi-project search: JQL's `project = \"<value>\"` also accepts a name or id, so a\n\t\t// display name equal to another project's key would address that project. The key is the only unambiguous one.\n\t\tconst fetchPage: JiraIssuePageFetcher = (scope, cursor) =>\n\t\t\tapi.getIssuesForProjectPaged(tokenWithInfo, project.key, project.resourceId, {\n\t\t\t\t...scope,\n\t\t\t\tcursor: cursor,\n\t\t\t\tstates: states,\n\t\t\t\tsort: options?.sort,\n\t\t\t});\n\t\tconst drainOptions: JiraDrainOptions = {\n\t\t\tmaxPages: maxPagesPerRequest,\n\t\t\tfailureScope: { providerId: this.id, resourceId: project.resourceId, projectId: project.key },\n\t\t\tproviderName: 'Jira',\n\t\t\t// A project this token can no longer see (deleted, or its browse permission removed) holds no issues\n\t\t\t// for it, and the site's project list that named it is stale: drop that list so the next read\n\t\t\t// re-reads it, instead of failing the whole provider on every read until a restart (#5907).\n\t\t\tisEmptyFailure: ex => {\n\t\t\t\tif (!isJiraMissingProjectError(ex)) return false;\n\n\t\t\t\tLogger.warn(`Jira project '${project.key}' no longer exists or is not visible`);\n\t\t\t\tthis._projects.delete(`${session.accessToken}:${project.resourceId}`);\n\t\t\t\tthis._autolinks.delete(session.accessToken);\n\t\t\t\treturn true;\n\t\t\t},\n\t\t};\n\n\t\tif (options?.user != null) {\n\t\t\tconst read = await readUserScopedJiraIssues(\n\t\t\t\tfetchPage,\n\t\t\t\toptions.filters,\n\t\t\t\ttoUserIdentities(options.user, options),\n\t\t\t\tdrainOptions,\n\t\t\t);\n\t\t\tconst values = toUniqueJiraIssueShapes(read.issues, this);\n\t\t\tconst drain = { values: values, metadata: read.metadata, scope: drainOptions.failureScope };\n\t\t\treturn read.truncated ? { ...drain, truncated: true, recovery: 'none' } : { ...drain, truncated: false };\n\t\t}\n\n\t\tconst unscoped = await drainJiraIssues(fetchPage, {}, drainOptions);\n\t\tconst values = unscoped.issues\n\t\t\t.map(issue => toIssueShape(issue, this, { projection: 'project' }))\n\t\t\t.filter((result): result is IssueShape => result !== undefined);\n\t\tconst drain = { values: values, metadata: unscoped.metadata, scope: drainOptions.failureScope };\n\t\treturn unscoped.status !== 'complete'\n\t\t\t? { ...drain, truncated: true, recovery: unscoped.status === 'backstop' ? 'narrow-scope' : 'none' }\n\t\t\t: { ...drain, truncated: false };\n\t}\n\n\t/**\n\t * Searches a site's projects together for a user-scoped read: one JQL names up to\n\t * {@link JIRA_MAX_PROJECT_KEYS_PER_REQUEST} projects of one site and one user scope, past which the SDK refuses\n\t * the call rather than risk a query string Jira rejects.\n\t *\n\t * An unscoped read is never searched across projects: it drains every issue in the project, and its backstop is\n\t * reported as `narrow-scope` per project, which one search across projects could not attribute.\n\t */\n\tprotected override getProjectIssuesSearches(\n\t\trequests: readonly ProjectIssuesRequest<JiraProjectDescriptor>[],\n\t): number[][] | undefined {\n\t\treturn groupProjectIssuesSearches(\n\t\t\trequests,\n\t\t\t({ project, options }) =>\n\t\t\t\toptions.user != null\n\t\t\t\t\t? [\n\t\t\t\t\t\t\tproject.resourceId,\n\t\t\t\t\t\t\toptions.user,\n\t\t\t\t\t\t\toptions.userId ?? '',\n\t\t\t\t\t\t\toptions.sort ?? '',\n\t\t\t\t\t\t\toptions.state ?? '',\n\t\t\t\t\t\t\toptions.filters?.join(',') ?? '',\n\t\t\t\t\t\t].join('\\0')\n\t\t\t\t\t: undefined,\n\t\t\tJIRA_MAX_PROJECT_KEYS_PER_REQUEST,\n\t\t);\n\t}\n\n\tprotected override async searchProviderProjectIssues(\n\t\tsession: ProviderAuthenticationSession,\n\t\trequests: readonly ProjectIssuesRequest<JiraProjectDescriptor>[],\n\t): Promise<IssueShape[][] | undefined> {\n\t\t// Every request of a search shares its site and user scope (see `getProjectIssuesSearches`).\n\t\tconst { project, options } = requests[0];\n\t\tif (options.user == null) return undefined;\n\t\t// One project's search is its own read, by the same key and user clauses, so it costs no more to read it as one;\n\t\t// and that read attributes a failure to the project and drops a project list that no longer has it.\n\t\tif (requests.length === 1) return undefined;\n\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\tconst api = await this.getProvidersApi();\n\t\t// By key, not name: JQL resolves `project = \"<value>\"` against key first, so a name could read another\n\t\t// project's issues.\n\t\tconst projectKeys = requests.map(request => request.project.key);\n\t\tconst states = toProviderIssueStates(options.state);\n\n\t\treturn searchJiraProjectIssues(\n\t\t\t(userScope, cursor) =>\n\t\t\t\tapi.getIssuesForProjectsPaged(tokenWithInfo, projectKeys, project.resourceId, {\n\t\t\t\t\t...userScope,\n\t\t\t\t\tcursor: cursor,\n\t\t\t\t\tstates: states,\n\t\t\t\t\tsort: options.sort,\n\t\t\t\t}),\n\t\t\trequests.map(request => request.project.id),\n\t\t\toptions.filters,\n\t\t\ttoUserIdentities(options.user, options),\n\t\t\t{\n\t\t\t\tmaxPages: maxPagesPerRequest,\n\t\t\t\tfailureScope: { providerId: this.id, resourceId: project.resourceId },\n\t\t\t\tproviderName: 'Jira',\n\t\t\t},\n\t\t\tthis,\n\t\t);\n\t}\n\n\tprotected override async searchProviderMyIssues(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources?: JiraOrganizationDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t): Promise<IssueShape[] | undefined> {\n\t\tconst result = await this.searchProviderMyIssuesWithTruncation(session, resources, cancellation);\n\t\t// This read can only return values, so what the account-wide read reports as missing is at least logged.\n\t\tfor (const failure of result?.metadata?.failures ?? []) {\n\t\t\tLogger.warn(\n\t\t\t\t`searchProviderMyIssues: Jira site '${failure.scope?.resourceId ?? '?'}' was not fully read: ${failure.message ?? failure.kind}`,\n\t\t\t);\n\t\t}\n\t\treturn result?.values;\n\t}\n\n\t/**\n\t * Overridden so `state` reaches the per-site read, which the default drops. Each site is drained on its own, so a\n\t * site that fails keeps what it read and is recorded as that site's failure instead of discarding the sites that\n\t * answered; so is an issue a page dropped because it could not be read. A page backstop or a stalled continuation\n\t * makes the read `truncated`. A refused site therefore goes through `confirmScopedAuthFailures`, which publishes it\n\t * as that site's auth warning or, if the credential itself is refused, as the connection's.\n\t */\n\tprotected override async searchProviderMyIssuesWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources?: JiraOrganizationDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\toptions?: SearchMyIssuesOptions,\n\t): Promise<AccountWideIssuesResult | undefined> {\n\t\tconst states = toProviderIssueStates(options?.state);\n\t\tconst myResources = resources ?? (await this.getProviderResourcesForUser(session));\n\t\tif (!myResources) return undefined;\n\n\t\tconst api = await this.getProvidersApi();\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\n\t\tconst results: IssueShape[] = [];\n\t\tlet truncated = false;\n\t\tlet metadata: CollectionMetadata | undefined;\n\t\t// What each page reported failing, summarized at the end so a site whose many pages dropped issues reports\n\t\t// it once (see `summarizePageMetadata`); the pages keep the rest of their metadata, so `truncated` holds.\n\t\tconst pages: CollectionMetadata[] = [];\n\t\tconst readPage = async (resourceId: string, cursor: string | undefined) => {\n\t\t\tconst page = await api.getIssuesForResourceForCurrentUser(tokenWithInfo, resourceId, {\n\t\t\t\tcursor: cursor,\n\t\t\t\tstates: states,\n\t\t\t});\n\t\t\tif (!page.metadata?.failures?.length) return page;\n\n\t\t\tconst { failures: _failures, ...rest } = page.metadata;\n\t\t\tpages.push(page.metadata);\n\t\t\treturn { ...page, metadata: rest };\n\t\t};\n\t\tfor (const resource of myResources) {\n\t\t\tif (cancellation?.aborted) break;\n\n\t\t\tconst site = await collectProviderPagedResult(\n\t\t\t\tcursor =>\n\t\t\t\t\t// Cancellation is not truncation: the caller asked for the stop, so the site ends where it is.\n\t\t\t\t\tcancellation?.aborted\n\t\t\t\t\t\t? Promise.resolve({ values: [], paging: { cursor: '{}', more: false } })\n\t\t\t\t\t\t: readPage(resource.id, cursor),\n\t\t\t\tmaxPagesPerRequest,\n\t\t\t\t{ providerId: this.id, resourceId: resource.id },\n\t\t\t);\n\t\t\tresults.push(\n\t\t\t\t...site.values\n\t\t\t\t\t.map(issue => toIssueShape(issue, this, { projection: 'account' }))\n\t\t\t\t\t.filter((result): result is IssueShape => result != null),\n\t\t\t);\n\t\t\ttruncated ||= site.truncated === true;\n\t\t\tmetadata = mergeCollectionMetadata(metadata, site.metadata);\n\t\t}\n\n\t\treturn {\n\t\t\tvalues: results,\n\t\t\ttruncated: truncated,\n\t\t\tmetadata: mergeCollectionMetadata(summarizePageMetadata(pages), metadata),\n\t\t};\n\t}\n\n\tprotected override async getProviderLinkedIssueOrPullRequest(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: JiraOrganizationDescriptor,\n\t\t{ key }: { id: string; key: string },\n\t): Promise<IssueOrPullRequest | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst issue = await api.getIssue(toTokenWithInfo(this.id, session), {\n\t\t\tresourceId: resource.id,\n\t\t\tnumber: key,\n\t\t});\n\t\treturn issue != null ? toIssueShape(issue, this, { projection: 'point' }) : undefined;\n\t}\n\n\tprotected override async getProviderIssue(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: JiraOrganizationDescriptor,\n\t\tid: string,\n\t): Promise<Issue | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst apiResult = await api.getIssue(toTokenWithInfo(this.id, session), {\n\t\t\tresourceId: resource.id,\n\t\t\tnumber: id,\n\t\t});\n\t\tconst issue = apiResult != null ? toIssueShape(apiResult, this, { projection: 'point' }) : undefined;\n\t\treturn issue != null ? { ...issue, type: 'issue' } : undefined;\n\t}\n\n\tprotected override async getProviderIssueByResourceId(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresourceId: string,\n\t\tid: string,\n\t\tresourceUrl: string | undefined,\n\t): Promise<Issue | undefined> {\n\t\tif (resourceUrl == null) {\n\t\t\tthrow new Error('Jira direct issue reads require a resource URL');\n\t\t}\n\n\t\tconst api = await this.getProvidersApi();\n\t\tconst apiResult = await api.getJiraIssueByKey(toTokenWithInfo(this.id, session), resourceId, resourceUrl, id);\n\t\tconst issue = apiResult != null ? toIssueShape(apiResult, this, { projection: 'batch' }) : undefined;\n\t\treturn issue != null ? { ...issue, type: 'issue' } : undefined;\n\t}\n\n\t/**\n\t * The cheap check behind the batch issue read's etags: one bulk fetch per site and {@link jiraBulkFetchMaxKeys}\n\t * distinct keys, where {@link getProviderIssueByResourceId} sends one request per issue. A request that throws\n\t * rejects only its own targets' slots, with its error classified as the full read's would be.\n\t *\n\t * A target identified by a numeric issue id, which both reads accept as well as a key, is matched by the returned\n\t * issue's `id`; every other target by its key.\n\t *\n\t * Never proves an absence. Jira silently omits a key it can't find or show (listing it in no `issueErrors` either),\n\t * and answers a moved issue under its new key, which no longer matches the key asked for. Either way the target's\n\t * slot is rejected, so the full read, whose 404 does prove an absence, decides. When no target was answered and\n\t * nothing failed, the check declines instead, so a batch of only such keys costs a full read, not a failure.\n\t */\n\tprotected override async getProviderIssuesEtagFieldsByResourceId(\n\t\tsession: ProviderAuthenticationSession,\n\t\ttargets: readonly { resourceId: string; identifier: string; resourceUrl?: string }[],\n\t): Promise<PromiseSettledResult<IssueEtagFields | undefined>[] | undefined> {\n\t\tconst slots = new Array<PromiseSettledResult<IssueEtagFields | undefined>>(targets.length);\n\t\tlet answered = false;\n\t\tlet failed = false;\n\n\t\t// Each site's distinct keys, matched case-insensitively as Jira matches them, with the targets asking for each.\n\t\tconst bySite = new Map<string, Map<string, number[]>>();\n\t\tfor (const [index, target] of targets.entries()) {\n\t\t\tif (target.resourceUrl == null) {\n\t\t\t\t// Required as the full read requires it, so a check never answers for a target the full read refuses.\n\t\t\t\tslots[index] = {\n\t\t\t\t\tstatus: 'rejected',\n\t\t\t\t\treason: new Error('Jira direct issue reads require a resource URL'),\n\t\t\t\t};\n\t\t\t\tfailed = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet keys = bySite.get(target.resourceId);\n\t\t\tif (keys == null) {\n\t\t\t\tkeys = new Map();\n\t\t\t\tbySite.set(target.resourceId, keys);\n\t\t\t}\n\n\t\t\tconst key = target.identifier.toUpperCase();\n\t\t\tlet indices = keys.get(key);\n\t\t\tif (indices == null) {\n\t\t\t\tindices = [];\n\t\t\t\tkeys.set(key, indices);\n\t\t\t}\n\t\t\tindices.push(index);\n\t\t}\n\n\t\tconst requests = [...bySite].flatMap(([resourceId, keys]) =>\n\t\t\tchunk([...keys], jiraBulkFetchMaxKeys).map(entries => ({ resourceId: resourceId, entries: entries })),\n\t\t);\n\t\tif (!requests.length) return slots;\n\n\t\tconst api = await this.getProvidersApi();\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\tconst answers = await mapSettledBounded(requests, providerFanOutConcurrency, r =>\n\t\t\tapi.getJiraIssuesEtagFields(\n\t\t\t\ttokenWithInfo,\n\t\t\t\tr.resourceId,\n\t\t\t\tr.entries.map(([key]) => key),\n\t\t\t),\n\t\t);\n\n\t\tfor (const [i, answer] of answers.entries()) {\n\t\t\tconst { entries } = requests[i];\n\t\t\tif (answer.status === 'rejected') {\n\t\t\t\tfailed = true;\n\t\t\t\tfor (const [, indices] of entries) {\n\t\t\t\t\tfor (const index of indices) {\n\t\t\t\t\t\tslots[index] = answer;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (answer.value.errorCount > 0) {\n\t\t\t\tfailed = true;\n\t\t\t}\n\n\t\t\t// By key or id, never by position: Jira returns the issues in its own order, and only the ones it found.\n\t\t\tconst byKey = new Map<string, JiraIssueEtagResponse>();\n\t\t\tconst byId = new Map<string, JiraIssueEtagResponse>();\n\t\t\tfor (const issue of answer.value.issues) {\n\t\t\t\tbyKey.set(issue.key.toUpperCase(), issue);\n\t\t\t\tbyId.set(issue.id, issue);\n\t\t\t}\n\n\t\t\tfor (const [key, indices] of entries) {\n\t\t\t\t// A key target never matches by id, so an issue returned under a different key (moved) is rejected.\n\t\t\t\tconst issue = numericIssueId.test(key) ? byId.get(key) : byKey.get(key);\n\t\t\t\tlet slot: PromiseSettledResult<IssueEtagFields | undefined>;\n\t\t\t\tif (issue == null) {\n\t\t\t\t\tslot = { status: 'rejected', reason: new Error(`Jira bulk fetch did not return ${key}`) };\n\t\t\t\t} else {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tslot = { status: 'fulfilled', value: toJiraIssueEtagFields(issue) };\n\t\t\t\t\t\tanswered = true;\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\tslot = { status: 'rejected', reason: ex };\n\t\t\t\t\t\tfailed = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfor (const index of indices) {\n\t\t\t\t\tslots[index] = slot;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn answered || failed ? slots : undefined;\n\t}\n\n\tprotected override async providerOnConnect(): Promise<void> {\n\t\tthis._autolinks.clear();\n\t\tif (this._session == null) return;\n\n\t\tconst session = this._session;\n\t\tconst { accessToken } = session;\n\t\tconst organizationsGeneration = this._organizations.generation;\n\t\tconst projectsGeneration = this._projects.generation;\n\n\t\t// Persisted discovery seeds the caches only while it would still be fresh in them: read within the TTL, and\n\t\t// after the caches were last dropped. Otherwise a re-sync, which runs this again, would put back the very\n\t\t// project list it just dropped, and a restart would trust one read any time before (#5907).\n\t\tconst isFresh = (timestamp: unknown): timestamp is number =>\n\t\t\ttypeof timestamp === 'number' &&\n\t\t\ttimestamp > this._discoveryInvalidatedAt &&\n\t\t\tDate.now() - timestamp < discoveryCacheTtl;\n\n\t\tconst storedOrganizations = this.ctx.storage.get(`jira:${accessToken}:organizations`);\n\t\t// Projects persisted without their sites belong to a session whose sites were cleared below; ignore them.\n\t\tconst storedProjects =\n\t\t\tstoredOrganizations != null ? this.ctx.storage.get(`jira:${accessToken}:projects`) : undefined;\n\n\t\tlet organizations: JiraOrganizationDescriptor[] | undefined;\n\t\tif (storedOrganizations?.data != null && isFresh(storedOrganizations.timestamp)) {\n\t\t\tconst seeded = storedOrganizations.data.map((o: JiraOrganizationDescriptor) => ({ ...o }));\n\t\t\torganizations = seeded;\n\t\t\tthis._organizations.set(accessToken, seeded, {\n\t\t\t\tgeneration: organizationsGeneration,\n\t\t\t\tstoredAt: storedOrganizations.timestamp,\n\t\t\t});\n\t\t} else {\n\t\t\t// Stamped with when the read started, so the TTL counts from what it actually saw.\n\t\t\tconst readAt = Date.now();\n\t\t\torganizations = await this.getProviderResourcesForUser(session, true);\n\t\t\tif (storedOrganizations == null) {\n\t\t\t\t// Clear all other stored organizations and projects when our session changes\n\t\t\t\tawait this.ctx.storage.deleteWithPrefix('jira');\n\t\t\t}\n\t\t\t// Not persisted when the caches were dropped while it was in flight: it is then a read from before the\n\t\t\t// refresh, and the next start, which knows nothing of that refresh, would take it for fresh.\n\t\t\tif (this._organizations.generation === organizationsGeneration) {\n\t\t\t\tawait this.ctx.storage.store(`jira:${accessToken}:organizations`, {\n\t\t\t\t\tv: 1,\n\t\t\t\t\ttimestamp: readAt,\n\t\t\t\t\tdata: organizations,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tif (storedProjects?.data != null && isFresh(storedProjects.timestamp)) {\n\t\t\tconst projectsByResource = new Map<string, JiraProjectDescriptor[]>();\n\t\t\tfor (const project of storedProjects.data as JiraProjectDescriptor[]) {\n\t\t\t\tconst projects = projectsByResource.get(project.resourceId);\n\t\t\t\tif (projects == null) {\n\t\t\t\t\tprojectsByResource.set(project.resourceId, [{ ...project }]);\n\t\t\t\t} else if (!projects.some(p => p.id === project.id)) {\n\t\t\t\t\tprojects.push({ ...project });\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const [resourceId, projects] of projectsByResource) {\n\t\t\t\tthis._projects.set(`${accessToken}:${resourceId}`, projects, {\n\t\t\t\t\tgeneration: projectsGeneration,\n\t\t\t\t\tstoredAt: storedProjects.timestamp,\n\t\t\t\t});\n\t\t\t}\n\t\t} else if (organizations?.length) {\n\t\t\tconst readAt = Date.now();\n\t\t\tconst projects = await this.getProviderProjectsForResources(session, organizations, true);\n\t\t\t// See the organizations above.\n\t\t\tif (this._projects.generation === projectsGeneration) {\n\t\t\t\tawait this.ctx.storage.store(`jira:${accessToken}:projects`, {\n\t\t\t\t\tv: 1,\n\t\t\t\t\ttimestamp: readAt,\n\t\t\t\t\tdata: projects,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tprotected override clearDiscoveryCaches(): void {\n\t\tthis._discoveryInvalidatedAt = Date.now();\n\t\tthis._organizations.clear();\n\t\tthis._projects.clear();\n\t\tthis._autolinks.clear();\n\t}\n\n\tprotected override providerOnDisconnect(): void {\n\t\tthis._organizations.clear();\n\t\tthis._projects.clear();\n\t\tthis._autolinks.clear();\n\t}\n}\n\n/** The projects once each, by site and id. */\nfunction uniqueProjects(projects: JiraProjectDescriptor[]): JiraProjectDescriptor[] {\n\tconst projectsByIdentity = new Map<string, JiraProjectDescriptor>();\n\tfor (const project of projects) {\n\t\tconst identity = `${project.resourceId}:${project.id}`;\n\t\tif (!projectsByIdentity.has(identity)) {\n\t\t\tprojectsByIdentity.set(identity, project);\n\t\t}\n\t}\n\treturn [...projectsByIdentity.values()];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"jira.js","names":[],"sources":["../../../../../plus/integrations/src/providers/jira.ts"],"sourcesContent":["import type { CollectionMetadata } from '@gitkraken/provider-apis';\nimport { JIRA_MAX_PROJECT_KEYS_PER_REQUEST } from '@gitkraken/provider-apis';\nimport type { Account } from '@gitlens/git/models/author.js';\nimport type { AutolinkReference, DynamicAutolinkReference } from '@gitlens/git/models/autolink.js';\nimport type { Issue, IssueShape } from '@gitlens/git/models/issue.js';\nimport type { IssueOrPullRequest } from '@gitlens/git/models/issueOrPullRequest.js';\nimport type { IssueResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { chunk } from '@gitlens/utils/array.js';\nimport { Logger } from '@gitlens/utils/logger.js';\nimport { mapSettledBounded } from '@gitlens/utils/promise.js';\nimport type { IntegrationAuthenticationProviderDescriptor } from '../authentication/integrationAuthenticationProvider.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 { IssuesCloudHostIntegrationId, providerFanOutConcurrency } from '../constants.js';\nimport type { IssueEtagFields } from '../models/integration.js';\nimport type {\n\tAccountWideIssuesResult,\n\tIssuesForProjectOptions,\n\tProjectIssuesDrain,\n\tProjectIssuesRequest,\n\tSearchMyIssuesOptions,\n} from '../models/issueReads.js';\nimport { groupProjectIssuesSearches, IssuesIntegration } from '../models/issuesIntegration.js';\nimport type { JiraIssueEtagResponse } from './jiraIssueByKey.js';\nimport { jiraBulkFetchMaxKeys, toJiraIssueEtagFields } from './jiraIssueByKey.js';\nimport type { ProviderApiCollectionResult } from './models.js';\nimport { providersMetadata, toAccount, toIssueShape, toProviderIssueStates } from './models.js';\nimport { isJiraMissingProjectError } from './providerErrors.js';\nimport { DiscoveryCache, discoveryCacheTtl } from './utils/discoveryCache.js';\nimport { createIssueAutolinkReferences } from './utils/issueAutolinks.js';\nimport type { JiraDrainOptions, JiraIssuePageFetcher } from './utils/jiraIssueDrains.js';\nimport {\n\tdrainJiraIssues,\n\treadUserScopedJiraIssues,\n\tsearchJiraProjectIssues,\n\tsummarizePageMetadata,\n\ttoUniqueJiraIssueShapes,\n} from './utils/jiraIssueDrains.js';\nimport { collectProviderPagedResult, mergeCollectionMetadata } from './utils/providerPaging.js';\n\nconst metadata = providersMetadata[IssuesCloudHostIntegrationId.Jira];\nconst authProvider = Object.freeze({ id: metadata.id, scopes: metadata.scopes });\nconst maxPagesPerRequest = 10;\n/** A numeric issue id, which Jira's issue reads accept in place of a key. */\nconst numericIssueId = /^\\d+$/;\n\n/**\n * `assignee` and `creator` are user FIELDS: Jira resolves an accountId against the directory, which is the identity\n * that keeps matching once a display name cannot be looked up — a deactivated account, or a profile whose visibility\n * hides the name. Measured against a live site: a deactivated assignee returns its issues by accountId and an empty\n * page by display name, and Jira reports that miss as a successful empty search rather than an error, so the list\n * simply appears empty.\n *\n * `mention` is NOT a user field. It is `comment ~ \"...\"`, a free-text search over comment bodies, so an accountId\n * matches nothing there and the display name is the only value that can. Hence the id is applied to the first two\n * and the handle is kept for the third, rather than swapping `user` wholesale.\n */\nfunction toUserIdentities(user: string, options: IssuesForProjectOptions): { userField: string; mention: string } {\n\treturn { userField: options.userId ?? user, mention: user };\n}\n\nexport type JiraBaseDescriptor = IssueResourceDescriptor;\n\nexport interface JiraOrganizationDescriptor extends JiraBaseDescriptor {\n\turl: string;\n\tavatarUrl: string;\n}\n\nexport interface JiraProjectDescriptor extends JiraBaseDescriptor {\n\tresourceId: string;\n}\n\nexport class JiraIntegration extends IssuesIntegration<IssuesCloudHostIntegrationId.Jira> {\n\treadonly authProvider: IntegrationAuthenticationProviderDescriptor = authProvider;\n\treadonly id = IssuesCloudHostIntegrationId.Jira;\n\tprotected readonly key = this.id;\n\treadonly name: string = 'Jira';\n\n\tget domain(): string {\n\t\treturn metadata.domain;\n\t}\n\n\tprotected get apiBaseUrl(): string {\n\t\treturn 'https://api.atlassian.com';\n\t}\n\n\t/**\n\t * Built from the sites and their projects, so it follows them: rebuilt when a site's project list changes or the\n\t * caches are dropped, and once older than {@link discoveryCacheTtl}. An expired set is still served while it is\n\t * rebuilt in the background, because autolinks are read on every render of a commit message and must not wait\n\t * on the network, nor go empty just because nothing read the projects for a while (#5907).\n\t */\n\tprivate readonly _autolinks = new Map<\n\t\tstring,\n\t\t{ autolinks: (AutolinkReference | DynamicAutolinkReference)[]; builtAt: number }\n\t>();\n\tprivate readonly _autolinksBuilds = new Map<string, Promise<(AutolinkReference | DynamicAutolinkReference)[]>>();\n\n\toverride async autolinks(): Promise<(AutolinkReference | DynamicAutolinkReference)[]> {\n\t\tconst connected = this.maybeConnected ?? (await this.isConnected());\n\t\tif (!connected || this._session == null) return [];\n\n\t\tconst session = this._session;\n\t\tconst cached = this._autolinks.get(session.accessToken);\n\t\t// A session kept through a failed refresh is expired: building with it would send that token to the provider\n\t\t// on every render, so serve whatever was built before (none after a forced re-sync) until a read refreshes it.\n\t\tif (this.connectionExpired === true) return cached?.autolinks ?? [];\n\t\tif (cached == null) return this.buildAutolinks(session);\n\n\t\tif (Date.now() - cached.builtAt >= discoveryCacheTtl) {\n\t\t\tvoid this.buildAutolinks(session);\n\t\t}\n\t\treturn cached.autolinks;\n\t}\n\n\t/** One build per token at a time: concurrent renders share it rather than each re-reading sites and projects. */\n\tprivate buildAutolinks(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<(AutolinkReference | DynamicAutolinkReference)[]> {\n\t\tconst { accessToken } = session;\n\t\tlet build = this._autolinksBuilds.get(accessToken);\n\t\tif (build == null) {\n\t\t\tbuild = this.buildAutolinksCore(session).finally(() => this._autolinksBuilds.delete(accessToken));\n\t\t\tthis._autolinksBuilds.set(accessToken, build);\n\t\t}\n\t\treturn build;\n\t}\n\n\tprivate async buildAutolinksCore(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<(AutolinkReference | DynamicAutolinkReference)[]> {\n\t\tconst { accessToken } = session;\n\t\tconst generation = this._projects.generation;\n\t\ttry {\n\t\t\tconst organizations = await this.getProviderResourcesForUser(session);\n\t\t\tif (!organizations?.length) return [];\n\n\t\t\t// Served from the cache for every site whose projects are still fresh; only the rest are read.\n\t\t\tconst { values: projects, metadata } = await this.getProviderProjectsForResourcesWithMetadata(\n\t\t\t\tsession,\n\t\t\t\torganizations,\n\t\t\t);\n\n\t\t\tconst autolinks: (AutolinkReference | DynamicAutolinkReference)[] = [];\n\t\t\tfor (const project of projects) {\n\t\t\t\tconst organization = organizations.find(o => o.id === project.resourceId);\n\t\t\t\tif (organization == null) continue;\n\n\t\t\t\tautolinks.push(\n\t\t\t\t\t...createIssueAutolinkReferences(\n\t\t\t\t\t\tproject.key,\n\t\t\t\t\t\t`${organization.url}/browse/${project.key}-<num>`,\n\t\t\t\t\t\torganization,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// A set missing a site's projects is served but not kept, so the next call tries that site again; one\n\t\t\t// built from projects read before the caches were dropped is not kept either.\n\t\t\tconst complete = metadata == null || metadata.completeness === 'complete';\n\t\t\tif (complete && this._projects.generation === generation) {\n\t\t\t\tthis._autolinks.set(accessToken, { autolinks: autolinks, builtAt: Date.now() });\n\t\t\t}\n\t\t\treturn autolinks;\n\t\t} catch (ex) {\n\t\t\t// Autolinks are decoration: keep serving the last set rather than failing the render that asked.\n\t\t\tLogger.error(ex, 'JiraIntegration.autolinks');\n\t\t\treturn this._autolinks.get(accessToken)?.autolinks ?? [];\n\t\t}\n\t}\n\n\tprotected override async getProviderAccountForResource(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: JiraOrganizationDescriptor,\n\t): Promise<Account | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst user = await api.getCurrentUserForResource(toTokenWithInfo(this.id, session), resource.id);\n\n\t\tif (user == null) return undefined;\n\t\treturn toAccount(user, this);\n\t}\n\n\t/** Atlassian's account id is the same on every site, so the first site answers for all of them. */\n\tprotected override async getProviderCurrentAccount(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<Account | undefined> {\n\t\tconst resource = (await this.getProviderResourcesForUser(session))?.[0];\n\t\treturn resource != null ? this.getProviderAccountForResource(session, resource) : undefined;\n\t}\n\n\t/** The site-list request the resource cache would otherwise answer; see `IntegrationBase.validateCredential`. */\n\tprotected override async validateCredential(session: ProviderAuthenticationSession): Promise<void> {\n\t\tconst api = await this.getProvidersApi();\n\t\tif ((await api.getJiraResourcesForCurrentUser(toTokenWithInfo(this.id, session))) == null) {\n\t\t\tthrow new Error('Jira did not confirm the credential');\n\t\t}\n\t}\n\n\t/**\n\t * Atlassian's `401` for a token missing a scope the request needs, which a reconnect fixes by consenting to it\n\t * again; see `IntegrationBase.isCredentialRefusal`. Atlassian gives the same answer to a malformed request URL,\n\t * so a request the SDK built wrong would read as the credential's too.\n\t */\n\tprotected override isCredentialRefusal(refusal: ProviderRefusal): boolean {\n\t\treturn refusal.status === 401 && /scope does not match/i.test(refusal.detail ?? '');\n\t}\n\n\t/**\n\t * The sites and the projects discovered under them, both of which expire and are dropped on a re-sync, so a site\n\t * or project created or deleted mid-session is picked up (#5907). Keyed by token, and projects by token and site.\n\t */\n\tprivate readonly _organizations = new DiscoveryCache<JiraOrganizationDescriptor[]>();\n\tprivate readonly _projects = new DiscoveryCache<JiraProjectDescriptor[]>();\n\t/** When the caches were last dropped; a persisted discovery read before then is as stale as they were. */\n\tprivate _discoveryInvalidatedAt = 0;\n\n\tprotected override async getProviderResourcesForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t\tforce: boolean = false,\n\t): Promise<JiraOrganizationDescriptor[] | undefined> {\n\t\tconst { accessToken } = session;\n\n\t\tconst cachedResources = this._organizations.get(accessToken);\n\t\tif (cachedResources != null && !force) return cachedResources;\n\n\t\tconst generation = this._organizations.generation;\n\t\tconst organizations = await this.readOrganizations(session);\n\t\tif (organizations == null) {\n\t\t\tthis._organizations.delete(accessToken);\n\t\t\treturn undefined;\n\t\t}\n\n\t\tthis._organizations.set(accessToken, organizations, { generation: generation });\n\t\treturn organizations;\n\t}\n\n\tprotected override getProviderUncachedResourcesForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<JiraOrganizationDescriptor[] | undefined> {\n\t\treturn this.readOrganizations(session);\n\t}\n\n\tprivate async readOrganizations(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<JiraOrganizationDescriptor[] | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst resources = await api.getJiraResourcesForCurrentUser(toTokenWithInfo(this.id, session));\n\t\treturn resources?.map(r => ({ ...r, key: r.id }));\n\t}\n\n\tprotected override async getProviderProjectsForResources(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources: JiraOrganizationDescriptor[],\n\t\tforce: boolean = false,\n\t): Promise<JiraProjectDescriptor[] | undefined> {\n\t\treturn (await this.getProviderProjectsForResourcesWithMetadata(session, resources, force)).values;\n\t}\n\n\tprotected override async getProviderProjectsForResourcesWithMetadata(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources: JiraOrganizationDescriptor[],\n\t\tforce: boolean = false,\n\t): Promise<ProviderApiCollectionResult<JiraProjectDescriptor>> {\n\t\tconst { accessToken } = session;\n\t\tconst projectsCache = this._projects;\n\t\tconst generation = projectsCache.generation;\n\n\t\t// Taken before the read, so a clear while it is in flight can't empty what was cached when it started. A forced\n\t\t// read reads every resource, and still keeps a cached list for one it can't read completely.\n\t\tconst cachedProjects = new Map<string, JiraProjectDescriptor[]>();\n\t\tfor (const resource of resources) {\n\t\t\tconst projects = projectsCache.get(`${accessToken}:${resource.id}`);\n\t\t\tif (projects != null) {\n\t\t\t\tcachedProjects.set(resource.id, projects);\n\t\t\t}\n\t\t}\n\t\tconst resourcesWithoutProjects = force\n\t\t\t? resources\n\t\t\t: resources.filter(resource => !cachedProjects.has(resource.id));\n\n\t\tconst read = await this.readProjects(session, resourcesWithoutProjects);\n\t\tconst values = [...read.partial];\n\t\tfor (const [resourceId, projects] of read.complete) {\n\t\t\t// Returned either way; not stored when the cache was dropped while this read was in flight: it is what this\n\t\t\t// read found, only no longer trusted for the next one.\n\t\t\tif (projectsCache.set(`${accessToken}:${resourceId}`, projects, { generation: generation })) {\n\t\t\t\tthis._autolinks.delete(accessToken);\n\t\t\t}\n\t\t}\n\t\tfor (const resource of resources) {\n\t\t\tconst resourceProjects = read.complete.get(resource.id) ?? cachedProjects.get(resource.id);\n\t\t\tif (resourceProjects != null) {\n\t\t\t\tvalues.push(...resourceProjects);\n\t\t\t}\n\t\t}\n\t\treturn { values: uniqueProjects(values), metadata: read.metadata };\n\t}\n\n\tprotected override async getProviderUncachedProjectsForResourcesWithMetadata(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources: JiraOrganizationDescriptor[],\n\t): Promise<ProviderApiCollectionResult<JiraProjectDescriptor>> {\n\t\tconst read = await this.readProjects(session, resources);\n\t\treturn {\n\t\t\tvalues: uniqueProjects([...read.partial, ...[...read.complete.values()].flat()]),\n\t\t\tmetadata: read.metadata,\n\t\t};\n\t}\n\n\t/**\n\t * Reads the projects of each resource from the provider, apart by whether a resource's list came back complete:\n\t * only a complete one may be cached as the resource's project set.\n\t */\n\tprivate async readProjects(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources: JiraOrganizationDescriptor[],\n\t): Promise<{\n\t\tcomplete: Map<string, JiraProjectDescriptor[]>;\n\t\tpartial: JiraProjectDescriptor[];\n\t\tmetadata: CollectionMetadata | undefined;\n\t}> {\n\t\tconst complete = new Map<string, JiraProjectDescriptor[]>();\n\t\tconst partial: JiraProjectDescriptor[] = [];\n\t\tlet metadata: CollectionMetadata | undefined;\n\t\tif (resources.length === 0) return { complete: complete, partial: partial, metadata: metadata };\n\n\t\tconst api = await this.getProvidersApi();\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\tconst drains = await Promise.allSettled(\n\t\t\tresources.map(async resource => ({\n\t\t\t\tresource: resource,\n\t\t\t\tresult: await collectProviderPagedResult(\n\t\t\t\t\tcursor => api.getJiraProjectsForResource(tokenWithInfo, resource.id, { cursor: cursor }),\n\t\t\t\t\tmaxPagesPerRequest,\n\t\t\t\t\t{ providerId: this.id, resourceId: resource.id },\n\t\t\t\t),\n\t\t\t})),\n\t\t);\n\n\t\tdrains.forEach((drain, index) => {\n\t\t\tif (drain.status === 'rejected') {\n\t\t\t\tconst resource = resources[index];\n\t\t\t\tif (resource == null) return;\n\n\t\t\t\tmetadata = mergeCollectionMetadata(metadata, {\n\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\tfailures: [\n\t\t\t\t\t\ttoCollectionScopeFailure({ providerId: this.id, resourceId: resource.id }, drain.reason),\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst { resource, result } = drain.value;\n\t\t\tmetadata = mergeCollectionMetadata(metadata, result.metadata);\n\t\t\tconst projects = result.values\n\t\t\t\t.filter(project => project.resourceId === resource.id)\n\t\t\t\t.map(project => ({ ...project }));\n\t\t\tconst incomplete =\n\t\t\t\tresult.truncated === true || (result.metadata != null && result.metadata.completeness !== 'complete');\n\t\t\tif (incomplete) {\n\t\t\t\tpartial.push(...projects);\n\t\t\t\tif (\n\t\t\t\t\tresult.truncated === true &&\n\t\t\t\t\t(result.metadata == null || result.metadata.completeness === 'complete')\n\t\t\t\t) {\n\t\t\t\t\tmetadata = mergeCollectionMetadata(metadata, { completeness: 'partial' });\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tcomplete.set(resource.id, projects);\n\t\t});\n\t\treturn { complete: complete, partial: partial, metadata: metadata };\n\t}\n\n\tprotected override async getProviderIssuesForProject(\n\t\tsession: ProviderAuthenticationSession,\n\t\tproject: JiraProjectDescriptor,\n\t\toptions?: IssuesForProjectOptions,\n\t): Promise<IssueShape[] | undefined> {\n\t\treturn (await this.getProviderIssuesForProjectWithTruncation(session, project, options))?.values;\n\t}\n\n\tprotected override async getProviderIssuesForProjectWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\tproject: JiraProjectDescriptor,\n\t\toptions?: IssuesForProjectOptions,\n\t): Promise<ProjectIssuesDrain | undefined> {\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\n\t\tconst api = await this.getProvidersApi();\n\n\t\tconst states = toProviderIssueStates(options?.state);\n\t\t// By key, not name, as the multi-project search: JQL's `project = \"<value>\"` also accepts a name or id, so a\n\t\t// display name equal to another project's key would address that project. The key is the only unambiguous one.\n\t\tconst fetchPage: JiraIssuePageFetcher = (scope, cursor) =>\n\t\t\tapi.getIssuesForProjectPaged(tokenWithInfo, project.key, project.resourceId, {\n\t\t\t\t...scope,\n\t\t\t\tcursor: cursor,\n\t\t\t\tstates: states,\n\t\t\t\tsort: options?.sort,\n\t\t\t});\n\t\tconst drainOptions: JiraDrainOptions = {\n\t\t\tmaxPages: maxPagesPerRequest,\n\t\t\tfailureScope: { providerId: this.id, resourceId: project.resourceId, projectId: project.key },\n\t\t\tproviderName: 'Jira',\n\t\t\t// A project this token can no longer see (deleted, or its browse permission removed) holds no issues\n\t\t\t// for it, and the site's project list that named it is stale: drop that list so the next read\n\t\t\t// re-reads it, instead of failing the whole provider on every read until a restart (#5907).\n\t\t\tisEmptyFailure: ex => {\n\t\t\t\tif (!isJiraMissingProjectError(ex)) return false;\n\n\t\t\t\tLogger.warn(`Jira project '${project.key}' no longer exists or is not visible`);\n\t\t\t\tthis._projects.delete(`${session.accessToken}:${project.resourceId}`);\n\t\t\t\tthis._autolinks.delete(session.accessToken);\n\t\t\t\treturn true;\n\t\t\t},\n\t\t};\n\n\t\tif (options?.user != null) {\n\t\t\tconst read = await readUserScopedJiraIssues(\n\t\t\t\tfetchPage,\n\t\t\t\toptions.filters,\n\t\t\t\ttoUserIdentities(options.user, options),\n\t\t\t\tdrainOptions,\n\t\t\t);\n\t\t\tconst values = toUniqueJiraIssueShapes(read.issues, this);\n\t\t\tconst drain = { values: values, metadata: read.metadata, scope: drainOptions.failureScope };\n\t\t\treturn read.truncated ? { ...drain, truncated: true, recovery: 'none' } : { ...drain, truncated: false };\n\t\t}\n\n\t\tconst unscoped = await drainJiraIssues(fetchPage, {}, drainOptions);\n\t\tconst values = unscoped.issues\n\t\t\t.map(issue => toIssueShape(issue, this, { projection: 'project' }))\n\t\t\t.filter((result): result is IssueShape => result !== undefined);\n\t\tconst drain = { values: values, metadata: unscoped.metadata, scope: drainOptions.failureScope };\n\t\treturn unscoped.status !== 'complete'\n\t\t\t? { ...drain, truncated: true, recovery: unscoped.status === 'backstop' ? 'narrow-scope' : 'none' }\n\t\t\t: { ...drain, truncated: false };\n\t}\n\n\t/**\n\t * Searches a site's projects together for a user-scoped read: one JQL names up to\n\t * {@link JIRA_MAX_PROJECT_KEYS_PER_REQUEST} projects of one site and one user scope, past which the SDK refuses\n\t * the call rather than risk a query string Jira rejects.\n\t *\n\t * An unscoped read is never searched across projects: it drains every issue in the project, and its backstop is\n\t * reported as `narrow-scope` per project, which one search across projects could not attribute.\n\t */\n\tprotected override getProjectIssuesSearches(\n\t\trequests: readonly ProjectIssuesRequest<JiraProjectDescriptor>[],\n\t): number[][] | undefined {\n\t\treturn groupProjectIssuesSearches(\n\t\t\trequests,\n\t\t\t({ project, options }) =>\n\t\t\t\toptions.user != null\n\t\t\t\t\t? [\n\t\t\t\t\t\t\tproject.resourceId,\n\t\t\t\t\t\t\toptions.user,\n\t\t\t\t\t\t\toptions.userId ?? '',\n\t\t\t\t\t\t\toptions.sort ?? '',\n\t\t\t\t\t\t\toptions.state ?? '',\n\t\t\t\t\t\t\toptions.filters?.join(',') ?? '',\n\t\t\t\t\t\t].join('\\0')\n\t\t\t\t\t: undefined,\n\t\t\tJIRA_MAX_PROJECT_KEYS_PER_REQUEST,\n\t\t);\n\t}\n\n\tprotected override async searchProviderProjectIssues(\n\t\tsession: ProviderAuthenticationSession,\n\t\trequests: readonly ProjectIssuesRequest<JiraProjectDescriptor>[],\n\t): Promise<IssueShape[][] | undefined> {\n\t\t// Every request of a search shares its site and user scope (see `getProjectIssuesSearches`).\n\t\tconst { project, options } = requests[0];\n\t\tif (options.user == null) return undefined;\n\t\t// One project's search is its own read, by the same key and user clauses, so it costs no more to read it as one;\n\t\t// and that read attributes a failure to the project and drops a project list that no longer has it.\n\t\tif (requests.length === 1) return undefined;\n\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\tconst api = await this.getProvidersApi();\n\t\t// By key, not name: JQL resolves `project = \"<value>\"` against key first, so a name could read another\n\t\t// project's issues.\n\t\tconst projectKeys = requests.map(request => request.project.key);\n\t\tconst states = toProviderIssueStates(options.state);\n\n\t\treturn searchJiraProjectIssues(\n\t\t\t(userScope, cursor) =>\n\t\t\t\tapi.getIssuesForProjectsPaged(tokenWithInfo, projectKeys, project.resourceId, {\n\t\t\t\t\t...userScope,\n\t\t\t\t\tcursor: cursor,\n\t\t\t\t\tstates: states,\n\t\t\t\t\tsort: options.sort,\n\t\t\t\t}),\n\t\t\trequests.map(request => request.project.id),\n\t\t\toptions.filters,\n\t\t\ttoUserIdentities(options.user, options),\n\t\t\t{\n\t\t\t\tmaxPages: maxPagesPerRequest,\n\t\t\t\tfailureScope: { providerId: this.id, resourceId: project.resourceId },\n\t\t\t\tproviderName: 'Jira',\n\t\t\t},\n\t\t\tthis,\n\t\t);\n\t}\n\n\tprotected override async searchProviderMyIssues(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources?: JiraOrganizationDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t): Promise<IssueShape[] | undefined> {\n\t\tconst result = await this.searchProviderMyIssuesWithTruncation(session, resources, cancellation);\n\t\t// This read can only return values, so what the account-wide read reports as missing is at least logged.\n\t\tfor (const failure of result?.metadata?.failures ?? []) {\n\t\t\tLogger.warn(\n\t\t\t\t`searchProviderMyIssues: Jira site '${failure.scope?.resourceId ?? '?'}' was not fully read: ${failure.message ?? failure.kind}`,\n\t\t\t);\n\t\t}\n\t\treturn result?.values;\n\t}\n\n\t/**\n\t * Overridden so `state` reaches the per-site read, which the default drops. Each site is drained on its own, so a\n\t * site that fails keeps what it read and is recorded as that site's failure instead of discarding the sites that\n\t * answered; so is an issue a page dropped because it could not be read. A page backstop or a stalled continuation\n\t * makes the read `truncated`. A refused site therefore goes through `confirmScopedAuthFailures`, which publishes it\n\t * as that site's auth warning or, if the credential itself is refused, as the connection's.\n\t */\n\tprotected override async searchProviderMyIssuesWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources?: JiraOrganizationDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\toptions?: SearchMyIssuesOptions,\n\t): Promise<AccountWideIssuesResult | undefined> {\n\t\tconst states = toProviderIssueStates(options?.state);\n\t\tconst myResources = resources ?? (await this.getProviderResourcesForUser(session));\n\t\tif (!myResources) return undefined;\n\n\t\tconst api = await this.getProvidersApi();\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\n\t\tconst results: IssueShape[] = [];\n\t\tlet truncated = false;\n\t\tlet metadata: CollectionMetadata | undefined;\n\t\t// What each page reported failing, summarized at the end so a site whose many pages dropped issues reports\n\t\t// it once (see `summarizePageMetadata`); the pages keep the rest of their metadata, so `truncated` holds.\n\t\tconst pages: CollectionMetadata[] = [];\n\t\tconst readPage = async (resourceId: string, cursor: string | undefined) => {\n\t\t\tconst page = await api.getIssuesForResourceForCurrentUser(tokenWithInfo, resourceId, {\n\t\t\t\tcursor: cursor,\n\t\t\t\tstates: states,\n\t\t\t});\n\t\t\tif (!page.metadata?.failures?.length) return page;\n\n\t\t\tconst { failures: _failures, ...rest } = page.metadata;\n\t\t\tpages.push(page.metadata);\n\t\t\treturn { ...page, metadata: rest };\n\t\t};\n\t\tfor (const resource of myResources) {\n\t\t\tif (cancellation?.aborted) break;\n\n\t\t\tconst site = await collectProviderPagedResult(\n\t\t\t\tcursor =>\n\t\t\t\t\t// Cancellation is not truncation: the caller asked for the stop, so the site ends where it is.\n\t\t\t\t\tcancellation?.aborted\n\t\t\t\t\t\t? Promise.resolve({ values: [], paging: { cursor: '{}', more: false } })\n\t\t\t\t\t\t: readPage(resource.id, cursor),\n\t\t\t\tmaxPagesPerRequest,\n\t\t\t\t{ providerId: this.id, resourceId: resource.id },\n\t\t\t);\n\t\t\tresults.push(\n\t\t\t\t...site.values\n\t\t\t\t\t.map(issue => toIssueShape(issue, this, { projection: 'account' }))\n\t\t\t\t\t.filter((result): result is IssueShape => result != null),\n\t\t\t);\n\t\t\ttruncated ||= site.truncated === true;\n\t\t\tmetadata = mergeCollectionMetadata(metadata, site.metadata);\n\t\t}\n\n\t\treturn {\n\t\t\tvalues: results,\n\t\t\ttruncated: truncated,\n\t\t\tmetadata: mergeCollectionMetadata(summarizePageMetadata(pages), metadata),\n\t\t};\n\t}\n\n\tprotected override async getProviderLinkedIssueOrPullRequest(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: JiraOrganizationDescriptor,\n\t\t{ key }: { id: string; key: string },\n\t): Promise<IssueOrPullRequest | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst issue = await api.getIssue(toTokenWithInfo(this.id, session), {\n\t\t\tresourceId: resource.id,\n\t\t\tnumber: key,\n\t\t});\n\t\treturn issue != null ? toIssueShape(issue, this, { projection: 'point' }) : undefined;\n\t}\n\n\tprotected override async getProviderIssue(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: JiraOrganizationDescriptor,\n\t\tid: string,\n\t): Promise<Issue | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst apiResult = await api.getIssue(toTokenWithInfo(this.id, session), {\n\t\t\tresourceId: resource.id,\n\t\t\tnumber: id,\n\t\t});\n\t\tconst issue = apiResult != null ? toIssueShape(apiResult, this, { projection: 'point' }) : undefined;\n\t\treturn issue != null ? { ...issue, type: 'issue' } : undefined;\n\t}\n\n\tprotected override async getProviderIssueByResourceId(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresourceId: string,\n\t\tid: string,\n\t\tresourceUrl: string | undefined,\n\t): Promise<Issue | undefined> {\n\t\tif (resourceUrl == null) {\n\t\t\tthrow new Error('Jira direct issue reads require a resource URL');\n\t\t}\n\n\t\tconst api = await this.getProvidersApi();\n\t\tconst apiResult = await api.getJiraIssueByKey(toTokenWithInfo(this.id, session), resourceId, resourceUrl, id);\n\t\tconst issue = apiResult != null ? toIssueShape(apiResult, this, { projection: 'batch' }) : undefined;\n\t\treturn issue != null ? { ...issue, type: 'issue' } : undefined;\n\t}\n\n\t/**\n\t * The cheap check behind the batch issue read's etags: one bulk fetch per site and {@link jiraBulkFetchMaxKeys}\n\t * distinct keys, where {@link getProviderIssueByResourceId} sends one request per issue. A request that throws\n\t * rejects only its own targets' slots, with its error classified as the full read's would be.\n\t *\n\t * A target identified by a numeric issue id, which both reads accept as well as a key, is matched by the returned\n\t * issue's `id`; every other target by its key.\n\t *\n\t * Never proves an absence. Jira silently omits a key it can't find or show (listing it in no `issueErrors` either),\n\t * and answers a moved issue under its new key, which no longer matches the key asked for. Either way the target's\n\t * slot is rejected, so the full read distinguishes a missing issue from a site failure. When no target was\n\t * answered and nothing failed, the check declines, so a batch of only such keys costs a full read, not a failure.\n\t */\n\tprotected override async getProviderIssuesEtagFieldsByResourceId(\n\t\tsession: ProviderAuthenticationSession,\n\t\ttargets: readonly { resourceId: string; identifier: string; resourceUrl?: string }[],\n\t): Promise<PromiseSettledResult<IssueEtagFields | undefined>[] | undefined> {\n\t\tconst slots = new Array<PromiseSettledResult<IssueEtagFields | undefined>>(targets.length);\n\t\tlet answered = false;\n\t\tlet failed = false;\n\n\t\t// Each site's distinct keys, matched case-insensitively as Jira matches them, with the targets asking for each.\n\t\tconst bySite = new Map<string, Map<string, number[]>>();\n\t\tfor (const [index, target] of targets.entries()) {\n\t\t\tif (target.resourceUrl == null) {\n\t\t\t\t// Required as the full read requires it, so a check never answers for a target the full read refuses.\n\t\t\t\tslots[index] = {\n\t\t\t\t\tstatus: 'rejected',\n\t\t\t\t\treason: new Error('Jira direct issue reads require a resource URL'),\n\t\t\t\t};\n\t\t\t\tfailed = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet keys = bySite.get(target.resourceId);\n\t\t\tif (keys == null) {\n\t\t\t\tkeys = new Map();\n\t\t\t\tbySite.set(target.resourceId, keys);\n\t\t\t}\n\n\t\t\tconst key = target.identifier.toUpperCase();\n\t\t\tlet indices = keys.get(key);\n\t\t\tif (indices == null) {\n\t\t\t\tindices = [];\n\t\t\t\tkeys.set(key, indices);\n\t\t\t}\n\t\t\tindices.push(index);\n\t\t}\n\n\t\tconst requests = [...bySite].flatMap(([resourceId, keys]) =>\n\t\t\tchunk([...keys], jiraBulkFetchMaxKeys).map(entries => ({ resourceId: resourceId, entries: entries })),\n\t\t);\n\t\tif (!requests.length) return slots;\n\n\t\tconst api = await this.getProvidersApi();\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\tconst answers = await mapSettledBounded(requests, providerFanOutConcurrency, r =>\n\t\t\tapi.getJiraIssuesEtagFields(\n\t\t\t\ttokenWithInfo,\n\t\t\t\tr.resourceId,\n\t\t\t\tr.entries.map(([key]) => key),\n\t\t\t),\n\t\t);\n\n\t\tfor (const [i, answer] of answers.entries()) {\n\t\t\tconst { entries } = requests[i];\n\t\t\tif (answer.status === 'rejected') {\n\t\t\t\tfailed = true;\n\t\t\t\tfor (const [, indices] of entries) {\n\t\t\t\t\tfor (const index of indices) {\n\t\t\t\t\t\tslots[index] = answer;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (answer.value.errorCount > 0) {\n\t\t\t\tfailed = true;\n\t\t\t}\n\n\t\t\t// By key or id, never by position: Jira returns the issues in its own order, and only the ones it found.\n\t\t\tconst byKey = new Map<string, JiraIssueEtagResponse>();\n\t\t\tconst byId = new Map<string, JiraIssueEtagResponse>();\n\t\t\tfor (const issue of answer.value.issues) {\n\t\t\t\tbyKey.set(issue.key.toUpperCase(), issue);\n\t\t\t\tbyId.set(issue.id, issue);\n\t\t\t}\n\n\t\t\tfor (const [key, indices] of entries) {\n\t\t\t\t// A key target never matches by id, so an issue returned under a different key (moved) is rejected.\n\t\t\t\tconst issue = numericIssueId.test(key) ? byId.get(key) : byKey.get(key);\n\t\t\t\tlet slot: PromiseSettledResult<IssueEtagFields | undefined>;\n\t\t\t\tif (issue == null) {\n\t\t\t\t\tslot = { status: 'rejected', reason: new Error(`Jira bulk fetch did not return ${key}`) };\n\t\t\t\t} else {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tslot = { status: 'fulfilled', value: toJiraIssueEtagFields(issue) };\n\t\t\t\t\t\tanswered = true;\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\tslot = { status: 'rejected', reason: ex };\n\t\t\t\t\t\tfailed = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfor (const index of indices) {\n\t\t\t\t\tslots[index] = slot;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn answered || failed ? slots : undefined;\n\t}\n\n\tprotected override async providerOnConnect(): Promise<void> {\n\t\tthis._autolinks.clear();\n\t\tif (this._session == null) return;\n\n\t\tconst session = this._session;\n\t\tconst { accessToken } = session;\n\t\tconst organizationsGeneration = this._organizations.generation;\n\t\tconst projectsGeneration = this._projects.generation;\n\n\t\t// Persisted discovery seeds the caches only while it would still be fresh in them: read within the TTL, and\n\t\t// after the caches were last dropped. Otherwise a re-sync, which runs this again, would put back the very\n\t\t// project list it just dropped, and a restart would trust one read any time before (#5907).\n\t\tconst isFresh = (timestamp: unknown): timestamp is number =>\n\t\t\ttypeof timestamp === 'number' &&\n\t\t\ttimestamp > this._discoveryInvalidatedAt &&\n\t\t\tDate.now() - timestamp < discoveryCacheTtl;\n\n\t\tconst storedOrganizations = this.ctx.storage.get(`jira:${accessToken}:organizations`);\n\t\t// Projects persisted without their sites belong to a session whose sites were cleared below; ignore them.\n\t\tconst storedProjects =\n\t\t\tstoredOrganizations != null ? this.ctx.storage.get(`jira:${accessToken}:projects`) : undefined;\n\n\t\tlet organizations: JiraOrganizationDescriptor[] | undefined;\n\t\tif (storedOrganizations?.data != null && isFresh(storedOrganizations.timestamp)) {\n\t\t\tconst seeded = storedOrganizations.data.map((o: JiraOrganizationDescriptor) => ({ ...o }));\n\t\t\torganizations = seeded;\n\t\t\tthis._organizations.set(accessToken, seeded, {\n\t\t\t\tgeneration: organizationsGeneration,\n\t\t\t\tstoredAt: storedOrganizations.timestamp,\n\t\t\t});\n\t\t} else {\n\t\t\t// Stamped with when the read started, so the TTL counts from what it actually saw.\n\t\t\tconst readAt = Date.now();\n\t\t\torganizations = await this.getProviderResourcesForUser(session, true);\n\t\t\tif (storedOrganizations == null) {\n\t\t\t\t// Clear all other stored organizations and projects when our session changes\n\t\t\t\tawait this.ctx.storage.deleteWithPrefix('jira');\n\t\t\t}\n\t\t\t// Not persisted when the caches were dropped while it was in flight: it is then a read from before the\n\t\t\t// refresh, and the next start, which knows nothing of that refresh, would take it for fresh.\n\t\t\tif (this._organizations.generation === organizationsGeneration) {\n\t\t\t\tawait this.ctx.storage.store(`jira:${accessToken}:organizations`, {\n\t\t\t\t\tv: 1,\n\t\t\t\t\ttimestamp: readAt,\n\t\t\t\t\tdata: organizations,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tif (storedProjects?.data != null && isFresh(storedProjects.timestamp)) {\n\t\t\tconst projectsByResource = new Map<string, JiraProjectDescriptor[]>();\n\t\t\tfor (const project of storedProjects.data as JiraProjectDescriptor[]) {\n\t\t\t\tconst projects = projectsByResource.get(project.resourceId);\n\t\t\t\tif (projects == null) {\n\t\t\t\t\tprojectsByResource.set(project.resourceId, [{ ...project }]);\n\t\t\t\t} else if (!projects.some(p => p.id === project.id)) {\n\t\t\t\t\tprojects.push({ ...project });\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const [resourceId, projects] of projectsByResource) {\n\t\t\t\tthis._projects.set(`${accessToken}:${resourceId}`, projects, {\n\t\t\t\t\tgeneration: projectsGeneration,\n\t\t\t\t\tstoredAt: storedProjects.timestamp,\n\t\t\t\t});\n\t\t\t}\n\t\t} else if (organizations?.length) {\n\t\t\tconst readAt = Date.now();\n\t\t\tconst projects = await this.getProviderProjectsForResources(session, organizations, true);\n\t\t\t// See the organizations above.\n\t\t\tif (this._projects.generation === projectsGeneration) {\n\t\t\t\tawait this.ctx.storage.store(`jira:${accessToken}:projects`, {\n\t\t\t\t\tv: 1,\n\t\t\t\t\ttimestamp: readAt,\n\t\t\t\t\tdata: projects,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tprotected override clearDiscoveryCaches(): void {\n\t\tthis._discoveryInvalidatedAt = Date.now();\n\t\tthis._organizations.clear();\n\t\tthis._projects.clear();\n\t\tthis._autolinks.clear();\n\t}\n\n\tprotected override providerOnDisconnect(): void {\n\t\tthis._organizations.clear();\n\t\tthis._projects.clear();\n\t\tthis._autolinks.clear();\n\t}\n}\n\n/** The projects once each, by site and id. */\nfunction uniqueProjects(projects: JiraProjectDescriptor[]): JiraProjectDescriptor[] {\n\tconst projectsByIdentity = new Map<string, JiraProjectDescriptor>();\n\tfor (const project of projects) {\n\t\tconst identity = `${project.resourceId}:${project.id}`;\n\t\tif (!projectsByIdentity.has(identity)) {\n\t\t\tprojectsByIdentity.set(identity, project);\n\t\t}\n\t}\n\treturn [...projectsByIdentity.values()];\n}\n"],"mappings":"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"}
@@ -14,7 +14,10 @@ function createProviderApiRequest(fetch, userAgent) {
14
14
  }
15
15
  }), () => hasAuthorization(options.headers));
16
16
  } catch (ex) {
17
- throw markRequestError(ex);
17
+ throw markRequestError(ex, {
18
+ url,
19
+ method: options.method
20
+ });
18
21
  }
19
22
  };
20
23
  }
@@ -1 +1 @@
1
- {"version":3,"file":"providerApiClient.js","names":[],"sources":["../../../../../plus/integrations/src/providers/providerApiClient.ts"],"sourcesContent":["import ProviderApis from '@gitkraken/provider-apis';\nimport { getAuthenticatedUser } from '../errors.js';\nimport type { ProviderRequestFunction, ProviderRequestOptions, ProviderRequestResponse } from './models.js';\nimport {\n\tAnonymousResponseError,\n\tgetProviderResponseBodyMessage,\n\tmarkRequestError,\n\tUnexpectedHtmlResponseError,\n} from './providerErrors.js';\n\n// `@gitkraken/provider-apis` is published as CommonJS with its factory on the `default` export.\n// How that surfaces depends on the consuming bundler's CJS->ESM interop: esbuild yields the\n// callable factory directly, while webpack and Node surface the module namespace (with the\n// factory under `.default`). Normalize to the callable factory so every consumer resolves it.\ntype ProviderApisFactory = typeof ProviderApis;\nexport const createProviderApis: ProviderApisFactory =\n\t(ProviderApis as ProviderApisFactory & { default?: ProviderApisFactory }).default ?? ProviderApis;\n\nexport function createProviderApiRequest(\n\tfetch: (input: string | URL, init?: RequestInit) => Promise<Response>,\n\tuserAgent?: string,\n): ProviderRequestFunction {\n\treturn async <T>({ url, ...options }: ProviderRequestOptions): Promise<ProviderRequestResponse<T>> => {\n\t\ttry {\n\t\t\tconst response = await fetch(url, {\n\t\t\t\t...options,\n\t\t\t\theaders: { ...(userAgent != null ? { 'User-Agent': userAgent } : {}), ...options.headers },\n\t\t\t});\n\t\t\treturn await parseFetchResponseForApi<T>(response, () => hasAuthorization(options.headers));\n\t\t} catch (ex) {\n\t\t\tthrow markRequestError(ex);\n\t\t}\n\t};\n}\n\nexport async function parseFetchResponseForApi<T>(\n\tresponse: Response,\n\tsentCredential?: () => boolean,\n): Promise<ProviderRequestResponse<T>> {\n\t// Media types are case-insensitive (RFC 9110 §8.3.1) and `fetch` hands the header back exactly as the server\n\t// wrote it, so match on a lowercased copy rather than the raw value.\n\tconst contentType = (response.headers.get('content-type') || '').toLowerCase();\n\n\t// A credential the server served as the anonymous user was rejected, whatever the body says; reading it as data\n\t// reports an account with nothing in it (see `AnonymousResponseError`). Checked before the body is parsed, which\n\t// may itself fail. A 401 already fails as the credential's rejection, with its body, and a throttle or a server\n\t// failure keeps its own error: neither proves anything about the credential.\n\tif (\n\t\tresponse.status !== 401 &&\n\t\tresponse.status !== 429 &&\n\t\tresponse.status < 500 &&\n\t\tgetAuthenticatedUser(response.headers) === 'anonymous' &&\n\t\tsentCredential?.() === true\n\t) {\n\t\t// Released rather than left for the collector: an unread body holds its connection.\n\t\tvoid response.body?.cancel().catch(() => undefined);\n\t\tthrow new AnonymousResponseError(response.status, {\n\t\t\theaders: Object.fromEntries(response.headers.entries()),\n\t\t\tstatus: response.status,\n\t\t});\n\t}\n\n\tlet body;\n\tlet servedHtml = false;\n\n\t// parse the response body\n\tif (contentType.startsWith('application/json')) {\n\t\tconst text = await response.text();\n\t\t// A sign-in page a server mislabels as JSON is the same page under another header, and `<` can never open\n\t\t// valid JSON — so recognize it rather than letting JSON.parse throw a SyntaxError carrying no status.\n\t\tif (text.trimStart().startsWith('<')) {\n\t\t\tservedHtml = true;\n\t\t\tbody = text;\n\t\t} else {\n\t\t\tbody = text.trim().length > 0 ? JSON.parse(text) : null;\n\t\t}\n\t} else if (contentType.startsWith('text/') || contentType === '') {\n\t\tbody = await response.text();\n\t\t// An empty body is never a sign-in page, and a write answering `204` can carry a stale `text/html` from\n\t\t// whatever its endpoint usually returns — so require actual markup, not just the header.\n\t\tservedHtml = contentType.startsWith('text/html') && body.trim().length > 0;\n\t} else if (contentType.startsWith('application/vnd.github.raw+json')) {\n\t\tbody = await response.arrayBuffer();\n\t} else {\n\t\tthrow new Error(`Unsupported content-type: ${contentType}`);\n\t}\n\n\tconst result = {\n\t\tbody: body,\n\t\theaders: Object.fromEntries(response.headers.entries()),\n\t\tstatus: response.status,\n\t\tstatusText: response.statusText,\n\t};\n\n\t// A 2xx carrying a page rather than data is a rejected credential wearing a success status (GKDEV-3617); a\n\t// non-2xx keeps its own status error below, which is the better diagnostic.\n\tif (response.ok && servedHtml) {\n\t\tthrow new UnexpectedHtmlResponseError(response.status, contentType, result);\n\t}\n\n\t// throw an error if the response is not ok\n\tif (!response.ok) {\n\t\tconst status = `(${response.status})${response.statusText ? ` ${response.statusText}` : ''}.`;\n\t\tconst detail = getProviderResponseBodyMessage(body);\n\t\tconst error = new Error(detail != null ? `${status} ${detail}` : status);\n\t\tObject.assign(error, { response: result });\n\t\tthrow error;\n\t}\n\n\treturn result;\n}\n\nfunction hasAuthorization(headers: ProviderRequestOptions['headers']): boolean {\n\tif (headers == null) return false;\n\n\treturn Object.entries(headers).some(([name, value]) => name.toLowerCase() === 'authorization' && Boolean(value));\n}\n"],"mappings":";;;;AAeA,MAAa,qBACX,aAAyE,WAAW;AAEtF,SAAgB,yBACf,OACA,WAC0B;CAC1B,OAAO,OAAU,EAAE,KAAK,GAAG,cAA2E;EACrG,IAAI;GAKH,OAAO,MAAM,yBAA4B,MAJlB,MAAM,KAAK;IACjC,GAAG;IACH,SAAS;KAAE,GAAI,aAAa,OAAO,EAAE,cAAc,UAAU,IAAI,CAAC;KAAI,GAAG,QAAQ;IAAQ;GAC1F,CAAC,SACwD,iBAAiB,QAAQ,OAAO,CAAC;EAC3F,SAAS,IAAI;GACZ,MAAM,iBAAiB,EAAE;EAC1B;CACD;AACD;AAEA,eAAsB,yBACrB,UACA,gBACsC;CAGtC,MAAM,eAAe,SAAS,QAAQ,IAAI,cAAc,KAAK,GAAA,CAAI,YAAY;CAM7E,IACC,SAAS,WAAW,OACpB,SAAS,WAAW,OACpB,SAAS,SAAS,OAClB,qBAAqB,SAAS,OAAO,MAAM,eAC3C,iBAAiB,MAAM,MACtB;EAED,SAAc,MAAM,OAAO,CAAC,CAAC,YAAY,KAAA,CAAS;EAClD,MAAM,IAAI,uBAAuB,SAAS,QAAQ;GACjD,SAAS,OAAO,YAAY,SAAS,QAAQ,QAAQ,CAAC;GACtD,QAAQ,SAAS;EAClB,CAAC;CACF;CAEA,IAAI;CACJ,IAAI,aAAa;CAGjB,IAAI,YAAY,WAAW,kBAAkB,GAAG;EAC/C,MAAM,OAAO,MAAM,SAAS,KAAK;EAGjC,IAAI,KAAK,UAAU,CAAC,CAAC,WAAW,GAAG,GAAG;GACrC,aAAa;GACb,OAAO;EACR,OACC,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,IAAI,KAAK,MAAM,IAAI,IAAI;CAErD,OAAO,IAAI,YAAY,WAAW,OAAO,KAAK,gBAAgB,IAAI;EACjE,OAAO,MAAM,SAAS,KAAK;EAG3B,aAAa,YAAY,WAAW,WAAW,KAAK,KAAK,KAAK,CAAC,CAAC,SAAS;CAC1E,OAAO,IAAI,YAAY,WAAW,iCAAiC,GAClE,OAAO,MAAM,SAAS,YAAY;MAElC,MAAM,IAAI,MAAM,6BAA6B,aAAa;CAG3D,MAAM,SAAS;EACR;EACN,SAAS,OAAO,YAAY,SAAS,QAAQ,QAAQ,CAAC;EACtD,QAAQ,SAAS;EACjB,YAAY,SAAS;CACtB;CAIA,IAAI,SAAS,MAAM,YAClB,MAAM,IAAI,4BAA4B,SAAS,QAAQ,aAAa,MAAM;CAI3E,IAAI,CAAC,SAAS,IAAI;EACjB,MAAM,SAAS,IAAI,SAAS,OAAO,GAAG,SAAS,aAAa,IAAI,SAAS,eAAe,GAAG;EAC3F,MAAM,SAAS,+BAA+B,IAAI;EAClD,MAAM,QAAQ,IAAI,MAAM,UAAU,OAAO,GAAG,OAAO,GAAG,WAAW,MAAM;EACvE,OAAO,OAAO,OAAO,EAAE,UAAU,OAAO,CAAC;EACzC,MAAM;CACP;CAEA,OAAO;AACR;AAEA,SAAS,iBAAiB,SAAqD;CAC9E,IAAI,WAAW,MAAM,OAAO;CAE5B,OAAO,OAAO,QAAQ,OAAO,CAAC,CAAC,MAAM,CAAC,MAAM,WAAW,KAAK,YAAY,MAAM,mBAAmB,QAAQ,KAAK,CAAC;AAChH"}
1
+ {"version":3,"file":"providerApiClient.js","names":[],"sources":["../../../../../plus/integrations/src/providers/providerApiClient.ts"],"sourcesContent":["import ProviderApis from '@gitkraken/provider-apis';\nimport { getAuthenticatedUser } from '../errors.js';\nimport type { ProviderRequestFunction, ProviderRequestOptions, ProviderRequestResponse } from './models.js';\nimport {\n\tAnonymousResponseError,\n\tgetProviderResponseBodyMessage,\n\tmarkRequestError,\n\tUnexpectedHtmlResponseError,\n} from './providerErrors.js';\n\n// `@gitkraken/provider-apis` is published as CommonJS with its factory on the `default` export.\n// How that surfaces depends on the consuming bundler's CJS->ESM interop: esbuild yields the\n// callable factory directly, while webpack and Node surface the module namespace (with the\n// factory under `.default`). Normalize to the callable factory so every consumer resolves it.\ntype ProviderApisFactory = typeof ProviderApis;\nexport const createProviderApis: ProviderApisFactory =\n\t(ProviderApis as ProviderApisFactory & { default?: ProviderApisFactory }).default ?? ProviderApis;\n\nexport function createProviderApiRequest(\n\tfetch: (input: string | URL, init?: RequestInit) => Promise<Response>,\n\tuserAgent?: string,\n): ProviderRequestFunction {\n\treturn async <T>({ url, ...options }: ProviderRequestOptions): Promise<ProviderRequestResponse<T>> => {\n\t\ttry {\n\t\t\tconst response = await fetch(url, {\n\t\t\t\t...options,\n\t\t\t\theaders: { ...(userAgent != null ? { 'User-Agent': userAgent } : {}), ...options.headers },\n\t\t\t});\n\t\t\treturn await parseFetchResponseForApi<T>(response, () => hasAuthorization(options.headers));\n\t\t} catch (ex) {\n\t\t\tthrow markRequestError(ex, { url: url, method: options.method });\n\t\t}\n\t};\n}\n\nexport async function parseFetchResponseForApi<T>(\n\tresponse: Response,\n\tsentCredential?: () => boolean,\n): Promise<ProviderRequestResponse<T>> {\n\t// Media types are case-insensitive (RFC 9110 §8.3.1) and `fetch` hands the header back exactly as the server\n\t// wrote it, so match on a lowercased copy rather than the raw value.\n\tconst contentType = (response.headers.get('content-type') || '').toLowerCase();\n\n\t// A credential the server served as the anonymous user was rejected, whatever the body says; reading it as data\n\t// reports an account with nothing in it (see `AnonymousResponseError`). Checked before the body is parsed, which\n\t// may itself fail. A 401 already fails as the credential's rejection, with its body, and a throttle or a server\n\t// failure keeps its own error: neither proves anything about the credential.\n\tif (\n\t\tresponse.status !== 401 &&\n\t\tresponse.status !== 429 &&\n\t\tresponse.status < 500 &&\n\t\tgetAuthenticatedUser(response.headers) === 'anonymous' &&\n\t\tsentCredential?.() === true\n\t) {\n\t\t// Released rather than left for the collector: an unread body holds its connection.\n\t\tvoid response.body?.cancel().catch(() => undefined);\n\t\tthrow new AnonymousResponseError(response.status, {\n\t\t\theaders: Object.fromEntries(response.headers.entries()),\n\t\t\tstatus: response.status,\n\t\t});\n\t}\n\n\tlet body;\n\tlet servedHtml = false;\n\n\t// parse the response body\n\tif (contentType.startsWith('application/json')) {\n\t\tconst text = await response.text();\n\t\t// A sign-in page a server mislabels as JSON is the same page under another header, and `<` can never open\n\t\t// valid JSON — so recognize it rather than letting JSON.parse throw a SyntaxError carrying no status.\n\t\tif (text.trimStart().startsWith('<')) {\n\t\t\tservedHtml = true;\n\t\t\tbody = text;\n\t\t} else {\n\t\t\tbody = text.trim().length > 0 ? JSON.parse(text) : null;\n\t\t}\n\t} else if (contentType.startsWith('text/') || contentType === '') {\n\t\tbody = await response.text();\n\t\t// An empty body is never a sign-in page, and a write answering `204` can carry a stale `text/html` from\n\t\t// whatever its endpoint usually returns — so require actual markup, not just the header.\n\t\tservedHtml = contentType.startsWith('text/html') && body.trim().length > 0;\n\t} else if (contentType.startsWith('application/vnd.github.raw+json')) {\n\t\tbody = await response.arrayBuffer();\n\t} else {\n\t\tthrow new Error(`Unsupported content-type: ${contentType}`);\n\t}\n\n\tconst result = {\n\t\tbody: body,\n\t\theaders: Object.fromEntries(response.headers.entries()),\n\t\tstatus: response.status,\n\t\tstatusText: response.statusText,\n\t};\n\n\t// A 2xx carrying a page rather than data is a rejected credential wearing a success status (GKDEV-3617); a\n\t// non-2xx keeps its own status error below, which is the better diagnostic.\n\tif (response.ok && servedHtml) {\n\t\tthrow new UnexpectedHtmlResponseError(response.status, contentType, result);\n\t}\n\n\t// throw an error if the response is not ok\n\tif (!response.ok) {\n\t\tconst status = `(${response.status})${response.statusText ? ` ${response.statusText}` : ''}.`;\n\t\tconst detail = getProviderResponseBodyMessage(body);\n\t\tconst error = new Error(detail != null ? `${status} ${detail}` : status);\n\t\tObject.assign(error, { response: result });\n\t\tthrow error;\n\t}\n\n\treturn result;\n}\n\nfunction hasAuthorization(headers: ProviderRequestOptions['headers']): boolean {\n\tif (headers == null) return false;\n\n\treturn Object.entries(headers).some(([name, value]) => name.toLowerCase() === 'authorization' && Boolean(value));\n}\n"],"mappings":";;;;AAeA,MAAa,qBACX,aAAyE,WAAW;AAEtF,SAAgB,yBACf,OACA,WAC0B;CAC1B,OAAO,OAAU,EAAE,KAAK,GAAG,cAA2E;EACrG,IAAI;GAKH,OAAO,MAAM,yBAA4B,MAJlB,MAAM,KAAK;IACjC,GAAG;IACH,SAAS;KAAE,GAAI,aAAa,OAAO,EAAE,cAAc,UAAU,IAAI,CAAC;KAAI,GAAG,QAAQ;IAAQ;GAC1F,CAAC,SACwD,iBAAiB,QAAQ,OAAO,CAAC;EAC3F,SAAS,IAAI;GACZ,MAAM,iBAAiB,IAAI;IAAO;IAAK,QAAQ,QAAQ;GAAO,CAAC;EAChE;CACD;AACD;AAEA,eAAsB,yBACrB,UACA,gBACsC;CAGtC,MAAM,eAAe,SAAS,QAAQ,IAAI,cAAc,KAAK,GAAA,CAAI,YAAY;CAM7E,IACC,SAAS,WAAW,OACpB,SAAS,WAAW,OACpB,SAAS,SAAS,OAClB,qBAAqB,SAAS,OAAO,MAAM,eAC3C,iBAAiB,MAAM,MACtB;EAED,SAAc,MAAM,OAAO,CAAC,CAAC,YAAY,KAAA,CAAS;EAClD,MAAM,IAAI,uBAAuB,SAAS,QAAQ;GACjD,SAAS,OAAO,YAAY,SAAS,QAAQ,QAAQ,CAAC;GACtD,QAAQ,SAAS;EAClB,CAAC;CACF;CAEA,IAAI;CACJ,IAAI,aAAa;CAGjB,IAAI,YAAY,WAAW,kBAAkB,GAAG;EAC/C,MAAM,OAAO,MAAM,SAAS,KAAK;EAGjC,IAAI,KAAK,UAAU,CAAC,CAAC,WAAW,GAAG,GAAG;GACrC,aAAa;GACb,OAAO;EACR,OACC,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,IAAI,KAAK,MAAM,IAAI,IAAI;CAErD,OAAO,IAAI,YAAY,WAAW,OAAO,KAAK,gBAAgB,IAAI;EACjE,OAAO,MAAM,SAAS,KAAK;EAG3B,aAAa,YAAY,WAAW,WAAW,KAAK,KAAK,KAAK,CAAC,CAAC,SAAS;CAC1E,OAAO,IAAI,YAAY,WAAW,iCAAiC,GAClE,OAAO,MAAM,SAAS,YAAY;MAElC,MAAM,IAAI,MAAM,6BAA6B,aAAa;CAG3D,MAAM,SAAS;EACR;EACN,SAAS,OAAO,YAAY,SAAS,QAAQ,QAAQ,CAAC;EACtD,QAAQ,SAAS;EACjB,YAAY,SAAS;CACtB;CAIA,IAAI,SAAS,MAAM,YAClB,MAAM,IAAI,4BAA4B,SAAS,QAAQ,aAAa,MAAM;CAI3E,IAAI,CAAC,SAAS,IAAI;EACjB,MAAM,SAAS,IAAI,SAAS,OAAO,GAAG,SAAS,aAAa,IAAI,SAAS,eAAe,GAAG;EAC3F,MAAM,SAAS,+BAA+B,IAAI;EAClD,MAAM,QAAQ,IAAI,MAAM,UAAU,OAAO,GAAG,OAAO,GAAG,WAAW,MAAM;EACvE,OAAO,OAAO,OAAO,EAAE,UAAU,OAAO,CAAC;EACzC,MAAM;CACP;CAEA,OAAO;AACR;AAEA,SAAS,iBAAiB,SAAqD;CAC9E,IAAI,WAAW,MAAM,OAAO;CAE5B,OAAO,OAAO,QAAQ,OAAO,CAAC,CAAC,MAAM,CAAC,MAAM,WAAW,KAAK,YAAY,MAAM,mBAAmB,QAAQ,KAAK,CAAC;AAChH"}
@@ -43,7 +43,10 @@ export declare class UnexpectedResponseError extends Error {
43
43
  * {@link throwProviderError} never reads it as data a read could not handle, whatever the host's fetch throws.
44
44
  * Returns `ex`, to rethrow.
45
45
  */
46
- export declare function markRequestError(ex: unknown): unknown;
46
+ export declare function markRequestError(ex: unknown, request?: {
47
+ url: string;
48
+ method?: string;
49
+ }): unknown;
47
50
  /**
48
51
  * A response that names the anonymous user (`X-AUSERNAME: anonymous`) to a request that carried a credential.
49
52
  *
@@ -56,14 +56,17 @@ var UnexpectedResponseError = class UnexpectedResponseError extends Error {
56
56
  }
57
57
  };
58
58
  /** What the request function `ProvidersApi` hands `provider-apis` threw itself: see {@link markRequestError}. */
59
- const requestErrors = /* @__PURE__ */ new WeakSet();
59
+ const requestErrors = /* @__PURE__ */ new WeakMap();
60
60
  /**
61
61
  * Records an error the request function threw itself (a transport failure, or a response it refused), so
62
62
  * {@link throwProviderError} never reads it as data a read could not handle, whatever the host's fetch throws.
63
63
  * Returns `ex`, to rethrow.
64
64
  */
65
- function markRequestError(ex) {
66
- if (ex != null && typeof ex === "object") requestErrors.add(ex);
65
+ function markRequestError(ex, request) {
66
+ if (ex != null && typeof ex === "object") requestErrors.set(ex, request != null && URL.canParse(request.url) ? {
67
+ path: new URL(request.url).pathname,
68
+ method: (request.method ?? "GET").toUpperCase()
69
+ } : void 0);
67
70
  return ex;
68
71
  }
69
72
  /**
@@ -156,7 +159,13 @@ function isLinearRateLimitedBody(body) {
156
159
  function isProviderIssueNotFoundError(providerId, ex) {
157
160
  if (AnonymousResponseError.is(ex)) return false;
158
161
  if (providerId === "linear") return ex instanceof Error && (linearIssueNotFoundMessage.test(ex.message) || linearIssueEntityNotFoundMessage.test(ex.message));
159
- const status = ex.response?.status;
162
+ const response = ex?.response;
163
+ const status = response?.status;
164
+ if (providerId === "jira" || providerId === "jira-server") {
165
+ const request = ex != null && typeof ex === "object" ? requestErrors.get(ex) : void 0;
166
+ const messages = (response?.body)?.errorMessages;
167
+ return status === 404 && request?.method === "GET" && /\/rest\/api\/[23]\/issue\/[^/]+$/.test(request.path) && Array.isArray(messages) && messages.length > 0 && messages.every((message) => typeof message === "string" && message.trim().length > 0);
168
+ }
160
169
  return status === 404 || status === 410 || status === 422;
161
170
  }
162
171
  const jiraMissingProjectMessage = /^The value '.*' does not exist for the field 'project'\.?$/i;
@@ -1 +1 @@
1
- {"version":3,"file":"providerErrors.js","names":[],"sources":["../../../../../plus/integrations/src/providers/providerErrors.ts"],"sourcesContent":["import { Logger } from '@gitlens/utils/logger.js';\nimport type { TokenWithInfo } from '../authentication/models.js';\nimport type { IntegrationIds } from '../constants.js';\nimport {\n\tGitCloudHostIntegrationId,\n\tGitSelfManagedHostIntegrationId,\n\tIssuesCloudHostIntegrationId,\n} from '../constants.js';\nimport type { ResponseHeaders } from '../errors.js';\nimport {\n\tAuthenticationError,\n\tAuthenticationErrorReason,\n\tgetAuthenticatedUser,\n\tisProviderUnreachableError,\n\tisRateLimitResponse,\n\tRequestClientError,\n\tRequestNotFoundError,\n\ttoError,\n\ttoRateLimitError,\n} from '../errors.js';\n\nconst maxProviderErrorBodyLength = 500;\n\n/**\n * A 2xx response carrying an HTML page instead of data.\n *\n * Azure DevOps answers a rejected credential by redirecting to its sign-in page, which then returns\n * `203 text/html` rather than `401`. `response.ok` spans the whole 200-299 range, so that page used to be read as\n * a successful body and handed to provider code typed as the JSON it is not — surfacing as an opaque `TypeError`\n * deep inside a provider, or as nothing at all on a route that returns `void`, which made a bad credential look\n * like a successful write (GKDEV-3617).\n *\n * `provider-apis` 0.59.0 raises its own error for this, but only from the transport it wraps; this package passes\n * its own already-parsed request function, so the check has to live here too.\n *\n * The message carries no URL, and the attached response is stripped of its credential-bearing headers and cut to\n * a diagnostic prefix: an error is something a consumer may reasonably log or forward, and the response it is\n * built from is a sign-in page carrying a session cookie.\n */\nexport class UnexpectedHtmlResponseError extends Error {\n\tstatic is(ex: unknown): ex is UnexpectedHtmlResponseError {\n\t\treturn ex instanceof UnexpectedHtmlResponseError;\n\t}\n\n\treadonly response: SanitizedErrorResponse;\n\n\tconstructor(status: number, contentType: string, response: ProviderErrorResponse | undefined) {\n\t\tsuper(`(${status}) Expected data but the provider returned an HTML page (content-type: ${contentType})`);\n\n\t\tthis.response = sanitizeErrorResponse(status, response);\n\n\t\tError.captureStackTrace?.(this, new.target);\n\t}\n}\n\n/**\n * A provider read that failed on the data a request returned rather than on the request: `provider-apis` reads a\n * response body as the shape it expects without checking it, so a 2xx carrying anything else (a gateway's JSON\n * error, `{}`, `null`, a GraphQL answer with neither data nor errors) surfaces as a `TypeError` thrown deep inside the\n * read, such as `response.body.map is not a function` from Jira Cloud's site list or Trello's boards, or\n * `Cannot read properties of undefined (reading 'id')` from Linear's organization. See {@link throwProviderError}.\n *\n * The message is the readable one a warning carries. The `TypeError` is kept as `original`, and a log line, which\n * prints the error through `toString()`, names it too.\n */\nexport class UnexpectedResponseError extends Error {\n\tstatic is(ex: unknown): ex is UnexpectedResponseError {\n\t\treturn ex instanceof UnexpectedResponseError;\n\t}\n\n\tconstructor(readonly original: Error) {\n\t\tsuper('Expected data but the provider returned an unexpected response');\n\n\t\tError.captureStackTrace?.(this, new.target);\n\t}\n\n\toverride toString(): string {\n\t\treturn `${super.toString()} (${String(this.original)})`;\n\t}\n}\n\n/** What the request function `ProvidersApi` hands `provider-apis` threw itself: see {@link markRequestError}. */\nconst requestErrors = new WeakSet<object>();\n\n/**\n * Records an error the request function threw itself (a transport failure, or a response it refused), so\n * {@link throwProviderError} never reads it as data a read could not handle, whatever the host's fetch throws.\n * Returns `ex`, to rethrow.\n */\nexport function markRequestError(ex: unknown): unknown {\n\tif (ex != null && typeof ex === 'object') {\n\t\trequestErrors.add(ex);\n\t}\n\treturn ex;\n}\n\n/**\n * A response that names the anonymous user (`X-AUSERNAME: anonymous`) to a request that carried a credential.\n *\n * Jira Data Center serves a token it rejects as the anonymous user rather than refusing it: measured against Jira\n * Data Center 10.7.3 with a revoked token, `/rest/api/2/project` answers `200 []` and a project search answers the\n * same `400` it gives a deleted project. Read as data, that is an account with no projects, and a selected project\n * looks deleted. The server only names `anonymous` when the credential did not authenticate, so the response is the\n * credential's rejection whatever its status, except a 401, which already fails as one, and a 429 or a 5xx, which\n * prove nothing about the credential.\n *\n * Its response keeps only the status and headers, stripped like {@link UnexpectedHtmlResponseError}'s.\n */\nexport class AnonymousResponseError extends Error {\n\tstatic is(ex: unknown): ex is AnonymousResponseError {\n\t\treturn ex instanceof AnonymousResponseError;\n\t}\n\n\treadonly response: SanitizedErrorResponse;\n\n\tconstructor(status: number, response: ProviderErrorResponse | undefined) {\n\t\tsuper(`(${status}) The provider served a request sent with a credential as the anonymous user`);\n\n\t\tthis.response = sanitizeErrorResponse(status, response);\n\n\t\tError.captureStackTrace?.(this, new.target);\n\t}\n}\n\ntype ProviderErrorResponse = { body?: unknown; headers?: Record<string, string>; status?: number };\ntype SanitizedErrorResponse = { body?: string; headers: Record<string, string>; status: number };\n\n/**\n * Response headers a rejected-credential page carries that must not ride along on an error: the session cookie it\n * sets, the challenges whose base64 payload decodes to the tenant or the internal domain, and the request-side\n * names a substituted fetch can echo back onto a response.\n */\nconst unloggableResponseHeaders = new Set([\n\t'authentication-info',\n\t'authorization',\n\t'cookie',\n\t'proxy-authenticate',\n\t'proxy-authentication-info',\n\t'proxy-authorization',\n\t'set-cookie',\n\t'www-authenticate',\n\t'x-api-key',\n\t'x-auth-token',\n]);\n\n/** Strips the credential-bearing headers from a response and cuts its body to a diagnostic prefix. */\nfunction sanitizeErrorResponse(status: number, response: ProviderErrorResponse | undefined): SanitizedErrorResponse {\n\tconst headers: Record<string, string> = {};\n\tfor (const [name, value] of Object.entries(response?.headers ?? {})) {\n\t\tif (unloggableResponseHeaders.has(name.toLowerCase())) continue;\n\n\t\theaders[name] = value;\n\t}\n\n\tconst body = response?.body;\n\treturn {\n\t\t// A sign-in page runs to tens of kilobytes and none of it is a diagnostic; keep only what identifies it.\n\t\t...(typeof body === 'string' ? { body: body.slice(0, maxProviderErrorBodyLength) } : {}),\n\t\theaders: headers,\n\t\tstatus: response?.status ?? status,\n\t};\n}\n\nconst linearIssueNotFoundMessage = /^Linear issue not found: .+$/i;\n\n/**\n * What Linear actually answers for an identifier it can't resolve: HTTP 200 with the single GraphQL error\n * `Entity not found: Issue` (`INPUT_ERROR`), which provider-apis rethrows as `Linear GraphQL errors: <messages>`, the\n * response not attached. Only that one message proves the miss; any other error, or another alongside it, is a\n * failure.\n */\nconst linearIssueEntityNotFoundMessage = /^Linear GraphQL errors: Entity not found: Issue$/;\n\ntype ProviderGraphQLError = {\n\tmessage?: unknown;\n\textensions?: { code?: unknown };\n};\n\nfunction getProviderGraphQLErrors(body: unknown): ProviderGraphQLError[] {\n\tif (body == null || typeof body !== 'object') return [];\n\n\tconst errors = (body as { errors?: unknown }).errors;\n\treturn Array.isArray(errors)\n\t\t? errors.filter((error): error is ProviderGraphQLError => error != null && typeof error === 'object')\n\t\t: [];\n}\n\nexport function isAzureProviderId(\n\tproviderId: IntegrationIds,\n): providerId is GitCloudHostIntegrationId.AzureDevOps | GitSelfManagedHostIntegrationId.AzureDevOpsServer {\n\treturn (\n\t\tproviderId === GitCloudHostIntegrationId.AzureDevOps ||\n\t\tproviderId === GitSelfManagedHostIntegrationId.AzureDevOpsServer\n\t);\n}\n\nexport function isGitHubProviderId(\n\tproviderId: IntegrationIds,\n): providerId is GitCloudHostIntegrationId.GitHub | GitSelfManagedHostIntegrationId.CloudGitHubEnterprise {\n\treturn (\n\t\tproviderId === GitCloudHostIntegrationId.GitHub ||\n\t\tproviderId === GitSelfManagedHostIntegrationId.CloudGitHubEnterprise\n\t);\n}\n\n/**\n * GitHub's GraphQL schema declares coordinate variables (issue/PR number) as `Int!`, a 32-bit signed integer.\n * A number above this coerces with no `path` on the error, which fails every aliased target in the chunk rather\n * than just the one with the bad number — so the read layer refuses the whole call up front instead.\n */\nexport const githubGraphQLInt32Max = 2147483647;\n\nfunction isLinearRateLimitError(providerId: IntegrationIds, ex: unknown): boolean {\n\tif (providerId !== IssuesCloudHostIntegrationId.Linear) return false;\n\n\treturn isLinearRateLimitedBody((ex as { response?: { body?: unknown } }).response?.body);\n}\n\n/** Whether a Linear GraphQL response body reports throttling. */\nexport function isLinearRateLimitedBody(body: unknown): boolean {\n\treturn getProviderGraphQLErrors(body).some(error => error.extensions?.code === 'RATELIMITED');\n}\n\nexport function isProviderIssueNotFoundError(providerId: IntegrationIds, ex: unknown): boolean {\n\t// A rejected credential is not a missing issue, whatever status the server served it with.\n\tif (AnonymousResponseError.is(ex)) return false;\n\n\tif (providerId === IssuesCloudHostIntegrationId.Linear) {\n\t\treturn (\n\t\t\tex instanceof Error &&\n\t\t\t(linearIssueNotFoundMessage.test(ex.message) || linearIssueEntityNotFoundMessage.test(ex.message))\n\t\t);\n\t}\n\n\tconst status = (ex as { response?: { status?: unknown } }).response?.status;\n\treturn status === 404 || status === 410 || status === 422;\n}\n\nconst jiraMissingProjectMessage = /^The value '.*' does not exist for the field 'project'\\.?$/i;\n\n/**\n * Jira's refusal of a search scoped to a project it cannot find: `400` with\n * `{\"errorMessages\":[\"The value 'KPSC290' does not exist for the field 'project'.\"]}`, measured against Jira Data\n * Center 10.7.3 for a deleted project key and for a numeric id that names no project. Jira answers a project the\n * user lost browse permission on the same way, since it will not confirm a project the user cannot see.\n *\n * Only a `400` whose EVERY message is that one: a JQL that is wrong in some other way also answers `400`, and that\n * is a real failure rather than a project that is simply gone. Accepts the SDK's error or the `RequestClientError`\n * `throwProviderError` wraps it in, whose `original` keeps the response.\n */\nexport function isJiraMissingProjectError(ex: unknown): boolean {\n\treturn getJiraMissingProjectResponse(ex) != null;\n}\n\n/**\n * {@link isJiraMissingProjectError} for Jira Data Center, which serves a credential it rejects as the anonymous user:\n * the search then names a project the anonymous user cannot see and is refused with the very same `400`, measured\n * against Jira Data Center 10.7.3 with a revoked token. Only the user it names in `X-AUSERNAME` tells the two apart,\n * so a refusal that names no user, or `anonymous`, is a failure rather than a project that is gone.\n */\nexport function isJiraServerMissingProjectError(ex: unknown): boolean {\n\tconst user = getAuthenticatedUser(getJiraMissingProjectResponse(ex)?.headers);\n\treturn user != null && user !== 'anonymous';\n}\n\nfunction getJiraMissingProjectResponse(ex: unknown): { headers?: ResponseHeaders } | undefined {\n\tfor (const candidate of [ex, (ex as { original?: unknown } | undefined)?.original]) {\n\t\tconst response = (\n\t\t\tcandidate as { response?: { status?: unknown; body?: unknown; headers?: ResponseHeaders } } | undefined\n\t\t)?.response;\n\t\tif (response?.status !== 400) continue;\n\n\t\tconst messages = (response.body as { errorMessages?: unknown } | null | undefined)?.errorMessages;\n\t\tif (!Array.isArray(messages) || messages.length === 0) continue;\n\n\t\treturn messages.every(m => typeof m === 'string' && jiraMissingProjectMessage.test(m.trim()))\n\t\t\t? response\n\t\t\t: undefined;\n\t}\n\n\treturn undefined;\n}\n\nexport function throwProviderError(tokenWithInfo: TokenWithInfo, error: unknown): never {\n\tconst { accessToken: token, ...tokenInfo } = tokenWithInfo;\n\tconst providerId = tokenWithInfo.providerId;\n\tconst normalizedError = toError(error);\n\t// A page served with a 2xx is only known to mean a rejected credential on Azure DevOps, which answers one by\n\t// redirecting to its sign-in page (GKDEV-3617). Elsewhere it is far more likely a maintenance or WAF page in\n\t// front of a provider, and calling that an authentication failure is not free: `handleProviderException`\n\t// expires a valid cloud session on one, and counts it against the budget that disconnects the integration.\n\t// So Azure gets the specific diagnosis and every other provider gets the honest one — the response was not\n\t// data, which is a protocol failure.\n\tif (UnexpectedHtmlResponseError.is(error)) {\n\t\tthrow isAzureProviderId(providerId)\n\t\t\t? new AuthenticationError(tokenInfo, AuthenticationErrorReason.Unauthorized, normalizedError)\n\t\t\t: new RequestClientError(normalizedError);\n\t}\n\n\tif (AnonymousResponseError.is(error)) {\n\t\tthrow new AuthenticationError(tokenInfo, AuthenticationErrorReason.Unauthorized, normalizedError);\n\t}\n\n\t// Linear reports GraphQL throttling as HTTP 400, so its structured code must win over generic 4xx handling.\n\tif (isLinearRateLimitError(providerId, error)) {\n\t\tthrow toRateLimitError(normalizedError, token);\n\t}\n\n\tconst status = (error as { response?: { status?: unknown } }).response?.status;\n\tif (typeof status === 'number') {\n\t\tswitch (status) {\n\t\t\tcase 404:\n\t\t\tcase 410:\n\t\t\tcase 422:\n\t\t\t\tthrow new RequestNotFoundError(normalizedError);\n\t\t\tcase 429:\n\t\t\t\tthrow toRateLimitError(normalizedError, token);\n\t\t\tcase 401:\n\t\t\tcase 403:\n\t\t\t\t// Some hosts overload 403 for throttling; classify that before asking the user to reconnect.\n\t\t\t\tif (isRateLimitResponse({ status: status, message: normalizedError.message })) {\n\t\t\t\t\tthrow toRateLimitError(normalizedError, token);\n\t\t\t\t}\n\t\t\t\tthrow new AuthenticationError(\n\t\t\t\t\ttokenInfo,\n\t\t\t\t\tstatus === 401 ? AuthenticationErrorReason.Unauthorized : AuthenticationErrorReason.Forbidden,\n\t\t\t\t\tnormalizedError,\n\t\t\t\t);\n\t\t\tdefault:\n\t\t\t\tif (status >= 400 && status < 500) {\n\t\t\t\t\tthrow new RequestClientError(normalizedError);\n\t\t\t\t}\n\t\t}\n\t}\n\n\t// A `TypeError` the request function didn't throw was thrown while reading what it returned: a body that isn't the\n\t// shape the read expects (see `UnexpectedResponseError`). A transport failure that reached here some other way stays\n\t// one. Neither a session expiry nor a strike toward disconnecting, as before: nothing says the credential is at\n\t// fault, and the provider may answer properly next time. Logged here because a warning or a fan-out's scope failure\n\t// built from it carries only the readable message, and a fan-out never reaches `handleProviderException`'s log.\n\tif (error instanceof TypeError && !requestErrors.has(error) && !isProviderUnreachableError(error)) {\n\t\tconst unexpected = new UnexpectedResponseError(error);\n\t\tLogger.warn(`${tokenWithInfo.providerId}: ${String(unexpected)}`);\n\t\tthrow unexpected;\n\t}\n\n\tthrow error;\n}\n\n/** Extracts the useful provider prose from an already-parsed SDK response body. */\nexport function getProviderResponseBodyMessage(body: unknown): string | undefined {\n\tlet message: string | undefined;\n\tif (typeof body === 'string') {\n\t\tmessage = body;\n\t} else if (body != null && typeof body === 'object') {\n\t\tconst { message: direct, error } = body as { message?: unknown; error?: unknown };\n\t\tif (typeof direct === 'string') {\n\t\t\tmessage = direct;\n\t\t} else if (typeof error === 'string') {\n\t\t\tmessage = error;\n\t\t} else if (error != null && typeof error === 'object') {\n\t\t\tconst nested = (error as { message?: unknown }).message;\n\t\t\tif (typeof nested === 'string') {\n\t\t\t\tmessage = nested;\n\t\t\t}\n\t\t}\n\t\tif (message == null) {\n\t\t\tconst graphQLError = getProviderGraphQLErrors(body)[0];\n\t\t\tif (typeof graphQLError?.message === 'string') {\n\t\t\t\tmessage = graphQLError.message;\n\t\t\t} else if (typeof graphQLError?.extensions?.code === 'string') {\n\t\t\t\tmessage = graphQLError.extensions.code;\n\t\t\t}\n\t\t}\n\t}\n\n\tmessage = message?.trim();\n\treturn message ? message.slice(0, maxProviderErrorBodyLength) : undefined;\n}\n"],"mappings":";;;;;AAqBA,MAAM,6BAA6B;;;;;;;;;;;;;;;;;AAkBnC,IAAa,8BAAb,MAAa,oCAAoC,MAAM;CACtD,OAAO,GAAG,IAAgD;EACzD,OAAO,cAAc;CACtB;CAEA;CAEA,YAAY,QAAgB,aAAqB,UAA6C;EAC7F,MAAM,IAAI,OAAO,wEAAwE,YAAY,EAAE;EAEvG,KAAK,WAAW,sBAAsB,QAAQ,QAAQ;EAEtD,MAAM,oBAAoB,MAAM,UAAU;CAC3C;AACD;;;;;;;;;;;AAYA,IAAa,0BAAb,MAAa,gCAAgC,MAAM;CAK7B;CAJrB,OAAO,GAAG,IAA4C;EACrD,OAAO,cAAc;CACtB;CAEA,YAAY,UAA0B;EACrC,MAAM,gEAAgE;EADlD,KAAA,WAAA;EAGpB,MAAM,oBAAoB,MAAM,UAAU;CAC3C;CAEA,WAA4B;EAC3B,OAAO,GAAG,MAAM,SAAS,EAAE,IAAI,OAAO,KAAK,QAAQ,EAAE;CACtD;AACD;;AAGA,MAAM,gCAAgB,IAAI,QAAgB;;;;;;AAO1C,SAAgB,iBAAiB,IAAsB;CACtD,IAAI,MAAM,QAAQ,OAAO,OAAO,UAC/B,cAAc,IAAI,EAAE;CAErB,OAAO;AACR;;;;;;;;;;;;;AAcA,IAAa,yBAAb,MAAa,+BAA+B,MAAM;CACjD,OAAO,GAAG,IAA2C;EACpD,OAAO,cAAc;CACtB;CAEA;CAEA,YAAY,QAAgB,UAA6C;EACxE,MAAM,IAAI,OAAO,6EAA6E;EAE9F,KAAK,WAAW,sBAAsB,QAAQ,QAAQ;EAEtD,MAAM,oBAAoB,MAAM,UAAU;CAC3C;AACD;;;;;;AAUA,MAAM,4CAA4B,IAAI,IAAI;CACzC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACD,CAAC;;AAGD,SAAS,sBAAsB,QAAgB,UAAqE;CACnH,MAAM,UAAkC,CAAC;CACzC,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,UAAU,WAAW,CAAC,CAAC,GAAG;EACpE,IAAI,0BAA0B,IAAI,KAAK,YAAY,CAAC,GAAG;EAEvD,QAAQ,QAAQ;CACjB;CAEA,MAAM,OAAO,UAAU;CACvB,OAAO;EAEN,GAAI,OAAO,SAAS,WAAW,EAAE,MAAM,KAAK,MAAM,GAAG,0BAA0B,EAAE,IAAI,CAAC;EAC7E;EACT,QAAQ,UAAU,UAAU;CAC7B;AACD;AAEA,MAAM,6BAA6B;;;;;;;AAQnC,MAAM,mCAAmC;AAOzC,SAAS,yBAAyB,MAAuC;CACxE,IAAI,QAAQ,QAAQ,OAAO,SAAS,UAAU,OAAO,CAAC;CAEtD,MAAM,SAAU,KAA8B;CAC9C,OAAO,MAAM,QAAQ,MAAM,IACxB,OAAO,QAAQ,UAAyC,SAAS,QAAQ,OAAO,UAAU,QAAQ,IAClG,CAAC;AACL;AAEA,SAAgB,kBACf,YAC0G;CAC1G,OACC,eAAA,iBACA,eAAA;AAEF;AAEA,SAAgB,mBACf,YACyG;CACzG,OACC,eAAA,YACA,eAAA;AAEF;;;;;;AAOA,MAAa,wBAAwB;AAErC,SAAS,uBAAuB,YAA4B,IAAsB;CACjF,IAAI,eAAA,UAAoD,OAAO;CAE/D,OAAO,wBAAyB,GAAyC,UAAU,IAAI;AACxF;;AAGA,SAAgB,wBAAwB,MAAwB;CAC/D,OAAO,yBAAyB,IAAI,CAAC,CAAC,MAAK,UAAS,MAAM,YAAY,SAAS,aAAa;AAC7F;AAEA,SAAgB,6BAA6B,YAA4B,IAAsB;CAE9F,IAAI,uBAAuB,GAAG,EAAE,GAAG,OAAO;CAE1C,IAAI,eAAA,UACH,OACC,cAAc,UACb,2BAA2B,KAAK,GAAG,OAAO,KAAK,iCAAiC,KAAK,GAAG,OAAO;CAIlG,MAAM,SAAU,GAA2C,UAAU;CACrE,OAAO,WAAW,OAAO,WAAW,OAAO,WAAW;AACvD;AAEA,MAAM,4BAA4B;;;;;;;;;;;AAYlC,SAAgB,0BAA0B,IAAsB;CAC/D,OAAO,8BAA8B,EAAE,KAAK;AAC7C;;;;;;;AAQA,SAAgB,gCAAgC,IAAsB;CACrE,MAAM,OAAO,qBAAqB,8BAA8B,EAAE,CAAC,EAAE,OAAO;CAC5E,OAAO,QAAQ,QAAQ,SAAS;AACjC;AAEA,SAAS,8BAA8B,IAAwD;CAC9F,KAAK,MAAM,aAAa,CAAC,IAAK,IAA2C,QAAQ,GAAG;EACnF,MAAM,WACL,WACE;EACH,IAAI,UAAU,WAAW,KAAK;EAE9B,MAAM,WAAY,SAAS,MAAyD;EACpF,IAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,SAAS,WAAW,GAAG;EAEvD,OAAO,SAAS,OAAM,MAAK,OAAO,MAAM,YAAY,0BAA0B,KAAK,EAAE,KAAK,CAAC,CAAC,IACzF,WACA,KAAA;CACJ;AAGD;AAEA,SAAgB,mBAAmB,eAA8B,OAAuB;CACvF,MAAM,EAAE,aAAa,OAAO,GAAG,cAAc;CAC7C,MAAM,aAAa,cAAc;CACjC,MAAM,kBAAkB,QAAQ,KAAK;CAOrC,IAAI,4BAA4B,GAAG,KAAK,GACvC,MAAM,kBAAkB,UAAU,IAC/B,IAAI,oBAAoB,WAAA,GAAmD,eAAe,IAC1F,IAAI,mBAAmB,eAAe;CAG1C,IAAI,uBAAuB,GAAG,KAAK,GAClC,MAAM,IAAI,oBAAoB,WAAA,GAAmD,eAAe;CAIjG,IAAI,uBAAuB,YAAY,KAAK,GAC3C,MAAM,iBAAiB,iBAAiB,KAAK;CAG9C,MAAM,SAAU,MAA8C,UAAU;CACxE,IAAI,OAAO,WAAW,UACrB,QAAQ,QAAR;EACC,KAAK;EACL,KAAK;EACL,KAAK,KACJ,MAAM,IAAI,qBAAqB,eAAe;EAC/C,KAAK,KACJ,MAAM,iBAAiB,iBAAiB,KAAK;EAC9C,KAAK;EACL,KAAK;GAEJ,IAAI,oBAAoB;IAAU;IAAQ,SAAS,gBAAgB;GAAQ,CAAC,GAC3E,MAAM,iBAAiB,iBAAiB,KAAK;GAE9C,MAAM,IAAI,oBACT,WACA,WAAW,MAAA,IAAA,GACX,eACD;EACD,SACC,IAAI,UAAU,OAAO,SAAS,KAC7B,MAAM,IAAI,mBAAmB,eAAe;CAE/C;CAQD,IAAI,iBAAiB,aAAa,CAAC,cAAc,IAAI,KAAK,KAAK,CAAC,2BAA2B,KAAK,GAAG;EAClG,MAAM,aAAa,IAAI,wBAAwB,KAAK;EACpD,QAAO,KAAK,GAAG,cAAc,WAAW,IAAI,OAAO,UAAU,GAAG;EAChE,MAAM;CACP;CAEA,MAAM;AACP;;AAGA,SAAgB,+BAA+B,MAAmC;CACjF,IAAI;CACJ,IAAI,OAAO,SAAS,UACnB,UAAU;MACJ,IAAI,QAAQ,QAAQ,OAAO,SAAS,UAAU;EACpD,MAAM,EAAE,SAAS,QAAQ,UAAU;EACnC,IAAI,OAAO,WAAW,UACrB,UAAU;OACJ,IAAI,OAAO,UAAU,UAC3B,UAAU;OACJ,IAAI,SAAS,QAAQ,OAAO,UAAU,UAAU;GACtD,MAAM,SAAU,MAAgC;GAChD,IAAI,OAAO,WAAW,UACrB,UAAU;EAEZ;EACA,IAAI,WAAW,MAAM;GACpB,MAAM,eAAe,yBAAyB,IAAI,CAAC,CAAC;GACpD,IAAI,OAAO,cAAc,YAAY,UACpC,UAAU,aAAa;QACjB,IAAI,OAAO,cAAc,YAAY,SAAS,UACpD,UAAU,aAAa,WAAW;EAEpC;CACD;CAEA,UAAU,SAAS,KAAK;CACxB,OAAO,UAAU,QAAQ,MAAM,GAAG,0BAA0B,IAAI,KAAA;AACjE"}
1
+ {"version":3,"file":"providerErrors.js","names":[],"sources":["../../../../../plus/integrations/src/providers/providerErrors.ts"],"sourcesContent":["import { Logger } from '@gitlens/utils/logger.js';\nimport type { TokenWithInfo } from '../authentication/models.js';\nimport type { IntegrationIds } from '../constants.js';\nimport {\n\tGitCloudHostIntegrationId,\n\tGitSelfManagedHostIntegrationId,\n\tIssuesCloudHostIntegrationId,\n\tIssuesSelfManagedHostIntegrationId,\n} from '../constants.js';\nimport type { ResponseHeaders } from '../errors.js';\nimport {\n\tAuthenticationError,\n\tAuthenticationErrorReason,\n\tgetAuthenticatedUser,\n\tisProviderUnreachableError,\n\tisRateLimitResponse,\n\tRequestClientError,\n\tRequestNotFoundError,\n\ttoError,\n\ttoRateLimitError,\n} from '../errors.js';\n\nconst maxProviderErrorBodyLength = 500;\n\n/**\n * A 2xx response carrying an HTML page instead of data.\n *\n * Azure DevOps answers a rejected credential by redirecting to its sign-in page, which then returns\n * `203 text/html` rather than `401`. `response.ok` spans the whole 200-299 range, so that page used to be read as\n * a successful body and handed to provider code typed as the JSON it is not — surfacing as an opaque `TypeError`\n * deep inside a provider, or as nothing at all on a route that returns `void`, which made a bad credential look\n * like a successful write (GKDEV-3617).\n *\n * `provider-apis` 0.59.0 raises its own error for this, but only from the transport it wraps; this package passes\n * its own already-parsed request function, so the check has to live here too.\n *\n * The message carries no URL, and the attached response is stripped of its credential-bearing headers and cut to\n * a diagnostic prefix: an error is something a consumer may reasonably log or forward, and the response it is\n * built from is a sign-in page carrying a session cookie.\n */\nexport class UnexpectedHtmlResponseError extends Error {\n\tstatic is(ex: unknown): ex is UnexpectedHtmlResponseError {\n\t\treturn ex instanceof UnexpectedHtmlResponseError;\n\t}\n\n\treadonly response: SanitizedErrorResponse;\n\n\tconstructor(status: number, contentType: string, response: ProviderErrorResponse | undefined) {\n\t\tsuper(`(${status}) Expected data but the provider returned an HTML page (content-type: ${contentType})`);\n\n\t\tthis.response = sanitizeErrorResponse(status, response);\n\n\t\tError.captureStackTrace?.(this, new.target);\n\t}\n}\n\n/**\n * A provider read that failed on the data a request returned rather than on the request: `provider-apis` reads a\n * response body as the shape it expects without checking it, so a 2xx carrying anything else (a gateway's JSON\n * error, `{}`, `null`, a GraphQL answer with neither data nor errors) surfaces as a `TypeError` thrown deep inside the\n * read, such as `response.body.map is not a function` from Jira Cloud's site list or Trello's boards, or\n * `Cannot read properties of undefined (reading 'id')` from Linear's organization. See {@link throwProviderError}.\n *\n * The message is the readable one a warning carries. The `TypeError` is kept as `original`, and a log line, which\n * prints the error through `toString()`, names it too.\n */\nexport class UnexpectedResponseError extends Error {\n\tstatic is(ex: unknown): ex is UnexpectedResponseError {\n\t\treturn ex instanceof UnexpectedResponseError;\n\t}\n\n\tconstructor(readonly original: Error) {\n\t\tsuper('Expected data but the provider returned an unexpected response');\n\n\t\tError.captureStackTrace?.(this, new.target);\n\t}\n\n\toverride toString(): string {\n\t\treturn `${super.toString()} (${String(this.original)})`;\n\t}\n}\n\n/** What the request function `ProvidersApi` hands `provider-apis` threw itself: see {@link markRequestError}. */\nconst requestErrors = new WeakMap<object, { path: string; method: string } | undefined>();\n\n/**\n * Records an error the request function threw itself (a transport failure, or a response it refused), so\n * {@link throwProviderError} never reads it as data a read could not handle, whatever the host's fetch throws.\n * Returns `ex`, to rethrow.\n */\nexport function markRequestError(ex: unknown, request?: { url: string; method?: string }): unknown {\n\tif (ex != null && typeof ex === 'object') {\n\t\trequestErrors.set(\n\t\t\tex,\n\t\t\trequest != null && URL.canParse(request.url)\n\t\t\t\t? { path: new URL(request.url).pathname, method: (request.method ?? 'GET').toUpperCase() }\n\t\t\t\t: undefined,\n\t\t);\n\t}\n\treturn ex;\n}\n\n/**\n * A response that names the anonymous user (`X-AUSERNAME: anonymous`) to a request that carried a credential.\n *\n * Jira Data Center serves a token it rejects as the anonymous user rather than refusing it: measured against Jira\n * Data Center 10.7.3 with a revoked token, `/rest/api/2/project` answers `200 []` and a project search answers the\n * same `400` it gives a deleted project. Read as data, that is an account with no projects, and a selected project\n * looks deleted. The server only names `anonymous` when the credential did not authenticate, so the response is the\n * credential's rejection whatever its status, except a 401, which already fails as one, and a 429 or a 5xx, which\n * prove nothing about the credential.\n *\n * Its response keeps only the status and headers, stripped like {@link UnexpectedHtmlResponseError}'s.\n */\nexport class AnonymousResponseError extends Error {\n\tstatic is(ex: unknown): ex is AnonymousResponseError {\n\t\treturn ex instanceof AnonymousResponseError;\n\t}\n\n\treadonly response: SanitizedErrorResponse;\n\n\tconstructor(status: number, response: ProviderErrorResponse | undefined) {\n\t\tsuper(`(${status}) The provider served a request sent with a credential as the anonymous user`);\n\n\t\tthis.response = sanitizeErrorResponse(status, response);\n\n\t\tError.captureStackTrace?.(this, new.target);\n\t}\n}\n\ntype ProviderErrorResponse = { body?: unknown; headers?: Record<string, string>; status?: number };\ntype SanitizedErrorResponse = { body?: string; headers: Record<string, string>; status: number };\n\n/**\n * Response headers a rejected-credential page carries that must not ride along on an error: the session cookie it\n * sets, the challenges whose base64 payload decodes to the tenant or the internal domain, and the request-side\n * names a substituted fetch can echo back onto a response.\n */\nconst unloggableResponseHeaders = new Set([\n\t'authentication-info',\n\t'authorization',\n\t'cookie',\n\t'proxy-authenticate',\n\t'proxy-authentication-info',\n\t'proxy-authorization',\n\t'set-cookie',\n\t'www-authenticate',\n\t'x-api-key',\n\t'x-auth-token',\n]);\n\n/** Strips the credential-bearing headers from a response and cuts its body to a diagnostic prefix. */\nfunction sanitizeErrorResponse(status: number, response: ProviderErrorResponse | undefined): SanitizedErrorResponse {\n\tconst headers: Record<string, string> = {};\n\tfor (const [name, value] of Object.entries(response?.headers ?? {})) {\n\t\tif (unloggableResponseHeaders.has(name.toLowerCase())) continue;\n\n\t\theaders[name] = value;\n\t}\n\n\tconst body = response?.body;\n\treturn {\n\t\t// A sign-in page runs to tens of kilobytes and none of it is a diagnostic; keep only what identifies it.\n\t\t...(typeof body === 'string' ? { body: body.slice(0, maxProviderErrorBodyLength) } : {}),\n\t\theaders: headers,\n\t\tstatus: response?.status ?? status,\n\t};\n}\n\nconst linearIssueNotFoundMessage = /^Linear issue not found: .+$/i;\n\n/**\n * What Linear actually answers for an identifier it can't resolve: HTTP 200 with the single GraphQL error\n * `Entity not found: Issue` (`INPUT_ERROR`), which provider-apis rethrows as `Linear GraphQL errors: <messages>`, the\n * response not attached. Only that one message proves the miss; any other error, or another alongside it, is a\n * failure.\n */\nconst linearIssueEntityNotFoundMessage = /^Linear GraphQL errors: Entity not found: Issue$/;\n\ntype ProviderGraphQLError = {\n\tmessage?: unknown;\n\textensions?: { code?: unknown };\n};\n\nfunction getProviderGraphQLErrors(body: unknown): ProviderGraphQLError[] {\n\tif (body == null || typeof body !== 'object') return [];\n\n\tconst errors = (body as { errors?: unknown }).errors;\n\treturn Array.isArray(errors)\n\t\t? errors.filter((error): error is ProviderGraphQLError => error != null && typeof error === 'object')\n\t\t: [];\n}\n\nexport function isAzureProviderId(\n\tproviderId: IntegrationIds,\n): providerId is GitCloudHostIntegrationId.AzureDevOps | GitSelfManagedHostIntegrationId.AzureDevOpsServer {\n\treturn (\n\t\tproviderId === GitCloudHostIntegrationId.AzureDevOps ||\n\t\tproviderId === GitSelfManagedHostIntegrationId.AzureDevOpsServer\n\t);\n}\n\nexport function isGitHubProviderId(\n\tproviderId: IntegrationIds,\n): providerId is GitCloudHostIntegrationId.GitHub | GitSelfManagedHostIntegrationId.CloudGitHubEnterprise {\n\treturn (\n\t\tproviderId === GitCloudHostIntegrationId.GitHub ||\n\t\tproviderId === GitSelfManagedHostIntegrationId.CloudGitHubEnterprise\n\t);\n}\n\n/**\n * GitHub's GraphQL schema declares coordinate variables (issue/PR number) as `Int!`, a 32-bit signed integer.\n * A number above this coerces with no `path` on the error, which fails every aliased target in the chunk rather\n * than just the one with the bad number — so the read layer refuses the whole call up front instead.\n */\nexport const githubGraphQLInt32Max = 2147483647;\n\nfunction isLinearRateLimitError(providerId: IntegrationIds, ex: unknown): boolean {\n\tif (providerId !== IssuesCloudHostIntegrationId.Linear) return false;\n\n\treturn isLinearRateLimitedBody((ex as { response?: { body?: unknown } }).response?.body);\n}\n\n/** Whether a Linear GraphQL response body reports throttling. */\nexport function isLinearRateLimitedBody(body: unknown): boolean {\n\treturn getProviderGraphQLErrors(body).some(error => error.extensions?.code === 'RATELIMITED');\n}\n\nexport function isProviderIssueNotFoundError(providerId: IntegrationIds, ex: unknown): boolean {\n\t// A rejected credential is not a missing issue, whatever status the server served it with.\n\tif (AnonymousResponseError.is(ex)) return false;\n\n\tif (providerId === IssuesCloudHostIntegrationId.Linear) {\n\t\treturn (\n\t\t\tex instanceof Error &&\n\t\t\t(linearIssueNotFoundMessage.test(ex.message) || linearIssueEntityNotFoundMessage.test(ex.message))\n\t\t);\n\t}\n\n\tconst response = (ex as { response?: { status?: unknown; body?: unknown } } | null | undefined)?.response;\n\tconst status = response?.status;\n\tif (\n\t\tproviderId === IssuesCloudHostIntegrationId.Jira ||\n\t\tproviderId === IssuesSelfManagedHostIntegrationId.JiraServer\n\t) {\n\t\t// SDK reads discover fields and sites first; only the issue endpoint can prove an issue's absence.\n\t\tconst request = ex != null && typeof ex === 'object' ? requestErrors.get(ex) : undefined;\n\t\tconst messages = (response?.body as { errorMessages?: unknown } | null | undefined)?.errorMessages;\n\t\treturn (\n\t\t\tstatus === 404 &&\n\t\t\trequest?.method === 'GET' &&\n\t\t\t/\\/rest\\/api\\/[23]\\/issue\\/[^/]+$/.test(request.path) &&\n\t\t\tArray.isArray(messages) &&\n\t\t\tmessages.length > 0 &&\n\t\t\tmessages.every(message => typeof message === 'string' && message.trim().length > 0)\n\t\t);\n\t}\n\n\treturn status === 404 || status === 410 || status === 422;\n}\n\nconst jiraMissingProjectMessage = /^The value '.*' does not exist for the field 'project'\\.?$/i;\n\n/**\n * Jira's refusal of a search scoped to a project it cannot find: `400` with\n * `{\"errorMessages\":[\"The value 'KPSC290' does not exist for the field 'project'.\"]}`, measured against Jira Data\n * Center 10.7.3 for a deleted project key and for a numeric id that names no project. Jira answers a project the\n * user lost browse permission on the same way, since it will not confirm a project the user cannot see.\n *\n * Only a `400` whose EVERY message is that one: a JQL that is wrong in some other way also answers `400`, and that\n * is a real failure rather than a project that is simply gone. Accepts the SDK's error or the `RequestClientError`\n * `throwProviderError` wraps it in, whose `original` keeps the response.\n */\nexport function isJiraMissingProjectError(ex: unknown): boolean {\n\treturn getJiraMissingProjectResponse(ex) != null;\n}\n\n/**\n * {@link isJiraMissingProjectError} for Jira Data Center, which serves a credential it rejects as the anonymous user:\n * the search then names a project the anonymous user cannot see and is refused with the very same `400`, measured\n * against Jira Data Center 10.7.3 with a revoked token. Only the user it names in `X-AUSERNAME` tells the two apart,\n * so a refusal that names no user, or `anonymous`, is a failure rather than a project that is gone.\n */\nexport function isJiraServerMissingProjectError(ex: unknown): boolean {\n\tconst user = getAuthenticatedUser(getJiraMissingProjectResponse(ex)?.headers);\n\treturn user != null && user !== 'anonymous';\n}\n\nfunction getJiraMissingProjectResponse(ex: unknown): { headers?: ResponseHeaders } | undefined {\n\tfor (const candidate of [ex, (ex as { original?: unknown } | undefined)?.original]) {\n\t\tconst response = (\n\t\t\tcandidate as { response?: { status?: unknown; body?: unknown; headers?: ResponseHeaders } } | undefined\n\t\t)?.response;\n\t\tif (response?.status !== 400) continue;\n\n\t\tconst messages = (response.body as { errorMessages?: unknown } | null | undefined)?.errorMessages;\n\t\tif (!Array.isArray(messages) || messages.length === 0) continue;\n\n\t\treturn messages.every(m => typeof m === 'string' && jiraMissingProjectMessage.test(m.trim()))\n\t\t\t? response\n\t\t\t: undefined;\n\t}\n\n\treturn undefined;\n}\n\nexport function throwProviderError(tokenWithInfo: TokenWithInfo, error: unknown): never {\n\tconst { accessToken: token, ...tokenInfo } = tokenWithInfo;\n\tconst providerId = tokenWithInfo.providerId;\n\tconst normalizedError = toError(error);\n\t// A page served with a 2xx is only known to mean a rejected credential on Azure DevOps, which answers one by\n\t// redirecting to its sign-in page (GKDEV-3617). Elsewhere it is far more likely a maintenance or WAF page in\n\t// front of a provider, and calling that an authentication failure is not free: `handleProviderException`\n\t// expires a valid cloud session on one, and counts it against the budget that disconnects the integration.\n\t// So Azure gets the specific diagnosis and every other provider gets the honest one — the response was not\n\t// data, which is a protocol failure.\n\tif (UnexpectedHtmlResponseError.is(error)) {\n\t\tthrow isAzureProviderId(providerId)\n\t\t\t? new AuthenticationError(tokenInfo, AuthenticationErrorReason.Unauthorized, normalizedError)\n\t\t\t: new RequestClientError(normalizedError);\n\t}\n\n\tif (AnonymousResponseError.is(error)) {\n\t\tthrow new AuthenticationError(tokenInfo, AuthenticationErrorReason.Unauthorized, normalizedError);\n\t}\n\n\t// Linear reports GraphQL throttling as HTTP 400, so its structured code must win over generic 4xx handling.\n\tif (isLinearRateLimitError(providerId, error)) {\n\t\tthrow toRateLimitError(normalizedError, token);\n\t}\n\n\tconst status = (error as { response?: { status?: unknown } }).response?.status;\n\tif (typeof status === 'number') {\n\t\tswitch (status) {\n\t\t\tcase 404:\n\t\t\tcase 410:\n\t\t\tcase 422:\n\t\t\t\tthrow new RequestNotFoundError(normalizedError);\n\t\t\tcase 429:\n\t\t\t\tthrow toRateLimitError(normalizedError, token);\n\t\t\tcase 401:\n\t\t\tcase 403:\n\t\t\t\t// Some hosts overload 403 for throttling; classify that before asking the user to reconnect.\n\t\t\t\tif (isRateLimitResponse({ status: status, message: normalizedError.message })) {\n\t\t\t\t\tthrow toRateLimitError(normalizedError, token);\n\t\t\t\t}\n\t\t\t\tthrow new AuthenticationError(\n\t\t\t\t\ttokenInfo,\n\t\t\t\t\tstatus === 401 ? AuthenticationErrorReason.Unauthorized : AuthenticationErrorReason.Forbidden,\n\t\t\t\t\tnormalizedError,\n\t\t\t\t);\n\t\t\tdefault:\n\t\t\t\tif (status >= 400 && status < 500) {\n\t\t\t\t\tthrow new RequestClientError(normalizedError);\n\t\t\t\t}\n\t\t}\n\t}\n\n\t// A `TypeError` the request function didn't throw was thrown while reading what it returned: a body that isn't the\n\t// shape the read expects (see `UnexpectedResponseError`). A transport failure that reached here some other way stays\n\t// one. Neither a session expiry nor a strike toward disconnecting, as before: nothing says the credential is at\n\t// fault, and the provider may answer properly next time. Logged here because a warning or a fan-out's scope failure\n\t// built from it carries only the readable message, and a fan-out never reaches `handleProviderException`'s log.\n\tif (error instanceof TypeError && !requestErrors.has(error) && !isProviderUnreachableError(error)) {\n\t\tconst unexpected = new UnexpectedResponseError(error);\n\t\tLogger.warn(`${tokenWithInfo.providerId}: ${String(unexpected)}`);\n\t\tthrow unexpected;\n\t}\n\n\tthrow error;\n}\n\n/** Extracts the useful provider prose from an already-parsed SDK response body. */\nexport function getProviderResponseBodyMessage(body: unknown): string | undefined {\n\tlet message: string | undefined;\n\tif (typeof body === 'string') {\n\t\tmessage = body;\n\t} else if (body != null && typeof body === 'object') {\n\t\tconst { message: direct, error } = body as { message?: unknown; error?: unknown };\n\t\tif (typeof direct === 'string') {\n\t\t\tmessage = direct;\n\t\t} else if (typeof error === 'string') {\n\t\t\tmessage = error;\n\t\t} else if (error != null && typeof error === 'object') {\n\t\t\tconst nested = (error as { message?: unknown }).message;\n\t\t\tif (typeof nested === 'string') {\n\t\t\t\tmessage = nested;\n\t\t\t}\n\t\t}\n\t\tif (message == null) {\n\t\t\tconst graphQLError = getProviderGraphQLErrors(body)[0];\n\t\t\tif (typeof graphQLError?.message === 'string') {\n\t\t\t\tmessage = graphQLError.message;\n\t\t\t} else if (typeof graphQLError?.extensions?.code === 'string') {\n\t\t\t\tmessage = graphQLError.extensions.code;\n\t\t\t}\n\t\t}\n\t}\n\n\tmessage = message?.trim();\n\treturn message ? message.slice(0, maxProviderErrorBodyLength) : undefined;\n}\n"],"mappings":"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"}
@@ -1534,7 +1534,7 @@ var ProvidersApi = class {
1534
1534
  try {
1535
1535
  return await requestJiraIssueByKey(this.request, tokenWithInfo.accessToken, resourceId, resourceUrl, key);
1536
1536
  } catch (e) {
1537
- if (e.response?.status === 404) return void 0;
1537
+ if (isProviderIssueNotFoundError(tokenWithInfo.providerId, e)) return void 0;
1538
1538
  return this.handleProviderError(tokenWithInfo, e);
1539
1539
  }
1540
1540
  }