@gitkraken/core-gitlens 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/CHANGELOG.md +48 -1
  2. package/dist/git/cache.d.ts +18 -5
  3. package/dist/git/cache.js +26 -8
  4. package/dist/git/cache.js.map +1 -1
  5. package/dist/git/context.d.ts +5 -1
  6. package/dist/git/context.js.map +1 -1
  7. package/dist/git/errors.d.ts +28 -1
  8. package/dist/git/errors.js +45 -1
  9. package/dist/git/errors.js.map +1 -1
  10. package/dist/git/providers/branches.d.ts +5 -1
  11. package/dist/git/providers/commits.d.ts +24 -1
  12. package/dist/git/providers/config.d.ts +7 -1
  13. package/dist/git/providers/operations.d.ts +6 -0
  14. package/dist/git/providers/provider.d.ts +17 -0
  15. package/dist/git/providers/refs.d.ts +38 -3
  16. package/dist/git/providers/revision.d.ts +5 -1
  17. package/dist/git/providers/staging.d.ts +12 -0
  18. package/dist/git/providers/worktrees.d.ts +23 -3
  19. package/dist/git/run.types.d.ts +6 -0
  20. package/dist/git-cli/cliGitProvider.d.ts +17 -1
  21. package/dist/git-cli/cliGitProvider.js +37 -1
  22. package/dist/git-cli/cliGitProvider.js.map +1 -1
  23. package/dist/git-cli/exec/git.d.ts +12 -2
  24. package/dist/git-cli/exec/git.js +48 -17
  25. package/dist/git-cli/exec/git.js.map +1 -1
  26. package/dist/git-cli/exec/gitQueue.d.ts +4 -1
  27. package/dist/git-cli/exec/gitQueue.js +1 -1
  28. package/dist/git-cli/exec/gitQueue.js.map +1 -1
  29. package/dist/git-cli/providers/branches.d.ts +10 -1
  30. package/dist/git-cli/providers/branches.js +15 -8
  31. package/dist/git-cli/providers/branches.js.map +1 -1
  32. package/dist/git-cli/providers/commits.d.ts +23 -1
  33. package/dist/git-cli/providers/commits.js +57 -8
  34. package/dist/git-cli/providers/commits.js.map +1 -1
  35. package/dist/git-cli/providers/config.js +4 -3
  36. package/dist/git-cli/providers/config.js.map +1 -1
  37. package/dist/git-cli/providers/operations.d.ts +1 -0
  38. package/dist/git-cli/providers/operations.js +39 -3
  39. package/dist/git-cli/providers/operations.js.map +1 -1
  40. package/dist/git-cli/providers/patch.js +20 -41
  41. package/dist/git-cli/providers/patch.js.map +1 -1
  42. package/dist/git-cli/providers/pausedOperations.js +2 -2
  43. package/dist/git-cli/providers/pausedOperations.js.map +1 -1
  44. package/dist/git-cli/providers/refs.d.ts +15 -3
  45. package/dist/git-cli/providers/refs.js +115 -10
  46. package/dist/git-cli/providers/refs.js.map +1 -1
  47. package/dist/git-cli/providers/revision.d.ts +3 -1
  48. package/dist/git-cli/providers/revision.js +9 -5
  49. package/dist/git-cli/providers/revision.js.map +1 -1
  50. package/dist/git-cli/providers/staging.d.ts +8 -0
  51. package/dist/git-cli/providers/staging.js +59 -7
  52. package/dist/git-cli/providers/staging.js.map +1 -1
  53. package/dist/git-cli/providers/worktrees.d.ts +31 -2
  54. package/dist/git-cli/providers/worktrees.js +75 -7
  55. package/dist/git-cli/providers/worktrees.js.map +1 -1
  56. package/dist/ipc/discovery.js +1 -1
  57. package/dist/plus/agents/providers/claudeCodeTranscript.js +6 -6
  58. package/dist/plus/agents/providers/claudeCodeTranscript.js.map +1 -1
  59. package/dist/plus/ai/providers/openAICompatibleProviderBase.js +1 -1
  60. package/dist/plus/git-github/api/github.d.ts +64 -3
  61. package/dist/plus/git-github/api/github.js +237 -24
  62. package/dist/plus/git-github/api/github.js.map +1 -1
  63. package/dist/plus/git-github/providers/github/branches.d.ts +3 -1
  64. package/dist/plus/git-github/providers/github/branches.js +2 -1
  65. package/dist/plus/git-github/providers/github/branches.js.map +1 -1
  66. package/dist/plus/git-github/providers/github/refs.d.ts +10 -3
  67. package/dist/plus/git-github/providers/github/refs.js +4 -4
  68. package/dist/plus/git-github/providers/github/refs.js.map +1 -1
  69. package/dist/plus/git-github/providers/github/revision.d.ts +3 -1
  70. package/dist/plus/git-github/providers/github/revision.js +1 -1
  71. package/dist/plus/git-github/providers/github/revision.js.map +1 -1
  72. package/dist/plus/integrations/collectionMetadata.d.ts +48 -3
  73. package/dist/plus/integrations/collectionMetadata.js +138 -8
  74. package/dist/plus/integrations/collectionMetadata.js.map +1 -1
  75. package/dist/plus/integrations/errors.d.ts +3 -1
  76. package/dist/plus/integrations/errors.js +7 -3
  77. package/dist/plus/integrations/errors.js.map +1 -1
  78. package/dist/plus/integrations/index.d.ts +6 -3
  79. package/dist/plus/integrations/index.js.map +1 -1
  80. package/dist/plus/integrations/integrationService.d.ts +36 -10
  81. package/dist/plus/integrations/integrationService.js +16 -3
  82. package/dist/plus/integrations/integrationService.js.map +1 -1
  83. package/dist/plus/integrations/manager.d.ts +107 -37
  84. package/dist/plus/integrations/models/gitHostIntegration.d.ts +129 -14
  85. package/dist/plus/integrations/models/gitHostIntegration.js +142 -18
  86. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
  87. package/dist/plus/integrations/models/integration.d.ts +117 -11
  88. package/dist/plus/integrations/models/integration.js +175 -13
  89. package/dist/plus/integrations/models/integration.js.map +1 -1
  90. package/dist/plus/integrations/models/issuesIntegration.d.ts +18 -5
  91. package/dist/plus/integrations/models/issuesIntegration.js +42 -10
  92. package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
  93. package/dist/plus/integrations/providers/azure/azure.d.ts +1 -1
  94. package/dist/plus/integrations/providers/azure/azure.js +14 -18
  95. package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
  96. package/dist/plus/integrations/providers/azure/models.d.ts +29 -0
  97. package/dist/plus/integrations/providers/azure/models.js +112 -1
  98. package/dist/plus/integrations/providers/azure/models.js.map +1 -1
  99. package/dist/plus/integrations/providers/azureDevOps.d.ts +73 -3
  100. package/dist/plus/integrations/providers/azureDevOps.js +117 -3
  101. package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
  102. package/dist/plus/integrations/providers/azureDevOpsServer.js +2 -1
  103. package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
  104. package/dist/plus/integrations/providers/bitbucket/bitbucket.d.ts +61 -0
  105. package/dist/plus/integrations/providers/bitbucket/bitbucket.js +165 -23
  106. package/dist/plus/integrations/providers/bitbucket/bitbucket.js.map +1 -1
  107. package/dist/plus/integrations/providers/bitbucket/models.d.ts +6 -1
  108. package/dist/plus/integrations/providers/bitbucket/models.js +4 -3
  109. package/dist/plus/integrations/providers/bitbucket/models.js.map +1 -1
  110. package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js +2 -2
  111. package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js.map +1 -1
  112. package/dist/plus/integrations/providers/bitbucket-server.d.ts +48 -2
  113. package/dist/plus/integrations/providers/bitbucket-server.js +51 -1
  114. package/dist/plus/integrations/providers/bitbucket-server.js.map +1 -1
  115. package/dist/plus/integrations/providers/bitbucket.d.ts +45 -2
  116. package/dist/plus/integrations/providers/bitbucket.js +41 -2
  117. package/dist/plus/integrations/providers/bitbucket.js.map +1 -1
  118. package/dist/plus/integrations/providers/github.d.ts +49 -5
  119. package/dist/plus/integrations/providers/github.js +102 -7
  120. package/dist/plus/integrations/providers/github.js.map +1 -1
  121. package/dist/plus/integrations/providers/gitlab/gitlab.d.ts +35 -0
  122. package/dist/plus/integrations/providers/gitlab/gitlab.js +132 -15
  123. package/dist/plus/integrations/providers/gitlab/gitlab.js.map +1 -1
  124. package/dist/plus/integrations/providers/gitlab/models.d.ts +1 -0
  125. package/dist/plus/integrations/providers/gitlab/models.js +1 -1
  126. package/dist/plus/integrations/providers/gitlab/models.js.map +1 -1
  127. package/dist/plus/integrations/providers/gitlab.d.ts +49 -2
  128. package/dist/plus/integrations/providers/gitlab.js +80 -4
  129. package/dist/plus/integrations/providers/gitlab.js.map +1 -1
  130. package/dist/plus/integrations/providers/jira-server.d.ts +3 -3
  131. package/dist/plus/integrations/providers/jira-server.js +5 -8
  132. package/dist/plus/integrations/providers/jira-server.js.map +1 -1
  133. package/dist/plus/integrations/providers/jira.d.ts +9 -0
  134. package/dist/plus/integrations/providers/jira.js +13 -1
  135. package/dist/plus/integrations/providers/jira.js.map +1 -1
  136. package/dist/plus/integrations/providers/linear.js +1 -1
  137. package/dist/plus/integrations/providers/models.d.ts +13 -2
  138. package/dist/plus/integrations/providers/models.js +11 -1
  139. package/dist/plus/integrations/providers/models.js.map +1 -1
  140. package/dist/plus/integrations/providers/providerErrors.d.ts +9 -1
  141. package/dist/plus/integrations/providers/providerErrors.js +10 -1
  142. package/dist/plus/integrations/providers/providerErrors.js.map +1 -1
  143. package/dist/plus/integrations/providers/providersApi.d.ts +60 -1
  144. package/dist/plus/integrations/providers/providersApi.js +137 -2
  145. package/dist/plus/integrations/providers/providersApi.js.map +1 -1
  146. package/dist/plus/integrations/providers/utils/providerPaging.d.ts +42 -0
  147. package/dist/plus/integrations/providers/utils/providerPaging.js +58 -1
  148. package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
  149. package/dist/plus/integrations/providers/utils.d.ts +1 -1
  150. package/dist/plus/integrations/reads/broaden.js +1 -1
  151. package/dist/plus/integrations/reads/context.d.ts +1 -1
  152. package/dist/plus/integrations/reads/counts.js +1 -1
  153. package/dist/plus/integrations/reads/currentAccount.d.ts +35 -0
  154. package/dist/plus/integrations/reads/currentAccount.js +46 -0
  155. package/dist/plus/integrations/reads/currentAccount.js.map +1 -0
  156. package/dist/plus/integrations/reads/drains.d.ts +5 -2
  157. package/dist/plus/integrations/reads/drains.js +8 -4
  158. package/dist/plus/integrations/reads/drains.js.map +1 -1
  159. package/dist/plus/integrations/reads/hierarchy.js +1 -1
  160. package/dist/plus/integrations/reads/hierarchy.utils.js +1 -1
  161. package/dist/plus/integrations/reads/issueBatch.d.ts +72 -12
  162. package/dist/plus/integrations/reads/issueBatch.js +193 -48
  163. package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
  164. package/dist/plus/integrations/reads/issueTracker.js +1 -1
  165. package/dist/plus/integrations/reads/issues.js +1 -1
  166. package/dist/plus/integrations/reads/pullRequestBatch.d.ts +57 -0
  167. package/dist/plus/integrations/reads/pullRequestBatch.js +80 -0
  168. package/dist/plus/integrations/reads/pullRequestBatch.js.map +1 -0
  169. package/dist/plus/integrations/reads/pullRequestBranches.d.ts +58 -0
  170. package/dist/plus/integrations/reads/pullRequestBranches.js +111 -0
  171. package/dist/plus/integrations/reads/pullRequestBranches.js.map +1 -0
  172. package/dist/plus/integrations/reads/pullRequests.js +3 -3
  173. package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
  174. package/dist/plus/integrations/reads/resolveRepository.js +1 -1
  175. package/dist/plus/integrations/reads/sweeps.js +4 -4
  176. package/dist/plus/integrations/reads/sweeps.js.map +1 -1
  177. package/dist/plus/integrations/results.d.ts +87 -6
  178. package/dist/plus/integrations/results.js +9 -2
  179. package/dist/plus/integrations/results.js.map +1 -1
  180. package/dist/utils/env/node/exec.js +3 -3
  181. package/dist/utils/env/node/exec.js.map +1 -1
  182. package/docs/integrations.md +175 -35
  183. package/docs/kepler-git-write-api-parity.md +239 -0
  184. package/docs/kepler-read-api-parity.md +75 -28
  185. package/package.json +1 -1
  186. package/dist/plus/integrations/reads/trackerIssue.d.ts +0 -33
  187. package/dist/plus/integrations/reads/trackerIssue.js +0 -60
  188. package/dist/plus/integrations/reads/trackerIssue.js.map +0 -1
@@ -3,13 +3,15 @@ import { Emitter, Event } from "../../../utils/event.js";
3
3
  import { ResourceDescriptor } from "../../../git/models/resourceDescriptor.js";
4
4
  import { AutolinkReference, DynamicAutolinkReference } from "../../../git/models/autolink.js";
5
5
  import { IssueOrPullRequest, IssueOrPullRequestType } from "../../../git/models/issueOrPullRequest.js";
6
- import { Issue, IssueShape } from "../../../git/models/issue.js";
6
+ import { Issue as Issue$1, IssueShape } from "../../../git/models/issue.js";
7
7
  import { PullRequest } from "../../../git/models/pullRequest.js";
8
8
  import { ScopedLogger } from "../../../utils/logger.scoped.js";
9
- import { Account } from "../../../git/models/author.js";
9
+ import { Account as Account$1 } from "../../../git/models/author.js";
10
10
  import { ProviderAuthenticationSession } from "../authentication/models.js";
11
11
  import { GitCloudHostIntegrationId, IntegrationIds, IssuesCloudHostIntegrationId, IssuesHostIntegrationIds } from "../constants.js";
12
12
  import { Sources } from "../telemetry.js";
13
+ import { ProviderWarningCause, ProviderWarningScope } from "../results.js";
14
+ import { ProviderRefusal, ProviderScopeFailure } from "../collectionMetadata.js";
13
15
  import { ProviderPullRequestCount, ProviderPullRequestSearchPage } from "./pullRequestReads.js";
14
16
  import { ProvidersApi } from "../providers/providersApi.js";
15
17
  import { AccountWideIssuesResult, ProviderIssueSearchPage, ProviderSearchCount, SearchMyIssuesOptions } from "./issueReads.js";
@@ -19,6 +21,7 @@ import { IntegrationConnectionChangeEvent } from "../integrationService.js";
19
21
  import { IntegrationAuthenticationService } from "../authentication/integrationAuthenticationService.js";
20
22
  import { IntegrationAuthenticationProviderDescriptor, IntegrationAuthenticationSessionDescriptor } from "../authentication/integrationAuthenticationProvider.js";
21
23
  import { IntegrationServiceContext } from "../context.js";
24
+ import { CollectionMetadata } from "@gitkraken/provider-apis";
22
25
  //#region ../plus/integrations/src/models/integration.d.ts
23
26
  export type Integration = GitHostIntegration | IssuesIntegration;
24
27
  export type IntegrationById<T extends IntegrationIds> = T extends IssuesHostIntegrationIds ? IssuesIntegration : GitHostIntegration;
@@ -34,7 +37,22 @@ export type IntegrationResult<T> = {
34
37
  duration?: number;
35
38
  value?: never;
36
39
  } | undefined;
37
- type SyncReqUsecase = Exclude<'getAccountForCommit' | 'getAccountForEmail' | 'getAccountForResource' | 'getCurrentAccount' | 'getDefaultBranch' | 'getIssue' | 'getIssueOrPullRequest' | 'getIssuesBatch' | 'getIssuesForProject' | 'getIssuesForRepos' | 'getMyPullRequestsForUser' | 'getOrganizationsForUser' | 'getProjectsForOrg' | 'getProjectsForResources' | 'getPullRequest' | 'getRepositoriesForOrg' | 'getRepositoriesForUser' | 'getPullRequestForBranch' | 'getPullRequestForCommit' | 'getPullRequestsForRepos' | 'getRepositoryMetadata' | 'getResourcesForUser' | 'getSshSigningKeysForEmails' | 'countIssues' | 'countPullRequests' | 'mergePullRequest' | 'searchIssuesPage' | 'searchPullRequestsPage' | 'searchMyIssues' | 'searchMyPullRequests' | 'searchPullRequests', 'all'>;
40
+ /**
41
+ * One target's answer in a batch read. A slot rejected by an authentication refusal carries the scope failure it was
42
+ * recorded as (see `IntegrationBase.settleBatchRefusals`).
43
+ */
44
+ export type BatchSlot<T> = PromiseFulfilledResult<T> | (PromiseRejectedResult & {
45
+ failure?: ProviderScopeFailure;
46
+ });
47
+ /** A batch read's target, in the form its provider addresses it by: a repository coordinate, or a tracker's resource. */
48
+ type BatchTarget = {
49
+ owner: string;
50
+ repo: string;
51
+ project?: string;
52
+ } | {
53
+ resourceId: string;
54
+ };
55
+ type SyncReqUsecase = Exclude<'getAccountForCommit' | 'getAccountForEmail' | 'getAccountForResource' | 'getCurrentAccount' | 'getDefaultBranch' | 'getIssue' | 'getIssueOrPullRequest' | 'getIssuesBatch' | 'getIssuesForProject' | 'getIssuesForRepos' | 'getMyPullRequestsForUser' | 'getOrganizationsForUser' | 'getProjectsForOrg' | 'getProjectsForResources' | 'getPullRequest' | 'getPullRequestsBatch' | 'getPullRequestsForBranches' | 'getRepositoriesForOrg' | 'getRepositoriesForUser' | 'getPullRequestForBranch' | 'getPullRequestForCommit' | 'getPullRequestsForRepos' | 'getRepositoryMetadata' | 'getResourcesForUser' | 'getSshSigningKeysForEmails' | 'countIssues' | 'countPullRequests' | 'mergePullRequest' | 'searchIssuesPage' | 'searchPullRequestsPage' | 'searchMyIssues' | 'searchMyPullRequests' | 'searchPullRequests', 'all'>;
38
56
  export declare abstract class IntegrationBase<ID extends IntegrationIds = IntegrationIds, T extends ResourceDescriptor = ResourceDescriptor> implements Disposable {
39
57
  protected readonly ctx: IntegrationServiceContext;
40
58
  protected readonly authenticationService: IntegrationAuthenticationService;
@@ -130,11 +148,97 @@ export declare abstract class IntegrationBase<ID extends IntegrationIds = Integr
130
148
  private refreshAfterSwitch;
131
149
  private skippedNonCloudReported;
132
150
  syncCloudConnection(state: 'connected' | 'disconnected', forceSync: boolean): Promise<void>;
151
+ /**
152
+ * Proves the credential with an uncached check, rejecting with the provider's `AuthenticationError` when it is
153
+ * refused, and with anything else when the check proves nothing. A `403` proves nothing either: the credential
154
+ * authenticated and was only denied the check's request.
155
+ *
156
+ * Implemented by the providers whose reads can reach their scopes without an uncached request to the connection
157
+ * first: discovery served from a per-token cache (Azure DevOps, Bitbucket, Jira Cloud), or an SDK fan-out across
158
+ * the requested repositories (Bitbucket Data Center). There, a dead token comes back as scoped refusals with no
159
+ * failure of the connection: the same shape as one scope refusing a sound credential.
160
+ * {@link confirmScopedAuthFailures} uses this to tell the two apart.
161
+ */
162
+ protected validateCredential?(session: ProviderAuthenticationSession): Promise<void>;
163
+ /**
164
+ * Credentials that passed {@link validateCredential} within the last minute. A probe already in flight is
165
+ * shared rather than repeated, and a refused one is not kept. Cleared whenever a credential is refused (see
166
+ * {@link handleProviderException}), so a pass is never vouched for past a refusal this instance has seen.
167
+ */
168
+ private readonly _validatedCredentials;
169
+ /**
170
+ * Whether a scope's refusal is the credential's own, by the provider's account of it: e.g. a token missing the
171
+ * OAuth scopes the read needs. The probe cannot see that, because it proves the token authenticates, not that
172
+ * it is authorized. So {@link confirmScopedAuthFailures} publishes one for the connection, unscoped, which asks
173
+ * for the reconnect (consenting to the scopes again) that fixes it, and keeps the results of the scopes that
174
+ * answered.
175
+ */
176
+ protected isCredentialRefusal?(refusal: ProviderRefusal): boolean;
177
+ /**
178
+ * Names why a confirmed credential was refused by one scope, from what the refusal said (see
179
+ * `ProviderWarning.cause`). Called only by {@link confirmScopedAuthFailures}, once the credential passed: the
180
+ * refusals this names are indistinguishable from a dead credential until then.
181
+ */
182
+ protected describeRefusal?(session: ProviderAuthenticationSession, refusal: ProviderRefusal, scope: ProviderWarningScope): ProviderWarningCause | undefined;
183
+ /**
184
+ * Settles what a read's only-scoped authentication failures mean, when the provider can tell (see
185
+ * {@link validateCredential}).
186
+ *
187
+ * A refused credential is thrown: the caller's `catch` then fails the read as a whole, exactly as it does when
188
+ * discovery is not cached and the first request is the one refused. Without this, a token revoked after
189
+ * discovery would publish scoped `auth` warnings, which a consumer must not answer with a reconnect (see
190
+ * `ProviderWarning.scope`), next to results served from the cache, for as long as the token stays stored.
191
+ *
192
+ * A confirmed credential makes each refusal the scope's own, so its cause is named through
193
+ * {@link describeRefusal}. A probe that fails for another reason proves nothing either way and leaves the read
194
+ * as it was.
195
+ *
196
+ * A refusal the provider pins on the credential itself (see {@link isCredentialRefusal}) is published for the
197
+ * connection instead, without a probe.
198
+ *
199
+ * A token that passed within the last minute is not probed again, so a scope that keeps refusing it costs one
200
+ * probe a minute, and a revocation right after a probe can take up to a minute to surface: until then its
201
+ * refusals stay scoped, and may be named. A probe that proved nothing is not remembered, so the next read probes
202
+ * again, and a credential no probe can confirm (e.g. a Bitbucket Data Center project access token) is probed on
203
+ * every read that has scoped refusals.
204
+ */
205
+ protected confirmScopedAuthFailures(session: ProviderAuthenticationSession, metadata: CollectionMetadata | undefined): Promise<void>;
206
+ /**
207
+ * Settles what a batch read's authentication refusals mean before the call is judged, as
208
+ * {@link confirmScopedAuthFailures} settles an account-wide read's: each refused slot comes back carrying the scope
209
+ * failure it was recorded as, against the scope its target names (see {@link batchTargetScope}), and a read
210
+ * publishes that instead of the bare reason.
211
+ *
212
+ * When any target answered, that proved the credential, so each refusal is its own target's. When every target
213
+ * failed, the call fails as a whole, as it always has, unless every failure is a refusal of a credential that checks
214
+ * out. Every target refusing is also what a dead credential looks like, so the credential is checked once first,
215
+ * with {@link validateCredential}. A check that proves nothing leaves the refusals scoped and unnamed, as it leaves
216
+ * an account-wide read's. A provider with no check cannot tell, and a refusal it pins on the credential (see
217
+ * {@link isCredentialRefusal}) needs none, so either still fails the call.
218
+ */
219
+ protected settleBatchRefusals<T>(session: ProviderAuthenticationSession, targets: readonly BatchTarget[], slots: PromiseSettledResult<T>[]): Promise<BatchSlot<T>[]>;
220
+ /**
221
+ * Shows one notice, and spends at most one strike, for a batch read whose targets failed with a server error or a
222
+ * timeout. Our own clients leave both to the batch (see `reportRequestFailure`) instead of spending them per
223
+ * target. `strike` is true only when every target failed and the call's own failure spent none: a call that
224
+ * answered some targets showed the host is up, and budgets strikes the same way as the rest of its failures.
225
+ */
226
+ protected reportDeferredRequestFailures(slots: readonly PromiseSettledResult<unknown>[] | undefined, strike: boolean): void;
227
+ /** Marks the scoped refusals the provider pins on the credential (see {@link isCredentialRefusal}). */
228
+ private markCredentialRefusals;
229
+ /**
230
+ * Checks the credential with {@link validateCredential}, rethrowing the provider's refusal when it is refused.
231
+ * `undefined` when the provider has no check, and `'unproven'` when the check proved nothing either way.
232
+ */
233
+ private confirmCredential;
234
+ /** Names each scoped refusal's cause (see {@link describeRefusal}); only once the credential is proven. */
235
+ private nameRefusalCauses;
236
+ /** Returns whether it spent a strike toward disconnecting (see {@link trackRequestException}). */
133
237
  protected handleProviderException(syncReqUsecase: SyncReqUsecase, ex: Error, options?: {
134
238
  scope?: ScopedLogger | undefined;
135
239
  silent?: boolean;
136
240
  connectionId?: string;
137
- }): void;
241
+ }): boolean;
138
242
  private missingExpirityReported;
139
243
  protected refreshSessionIfExpired(scope?: ScopedLogger): Promise<void>;
140
244
  trackRequestException(options?: {
@@ -176,30 +280,32 @@ export declare abstract class IntegrationBase<ID extends IntegrationIds = Integr
176
280
  getIssue(resource: T, id: string, options?: {
177
281
  connectionId?: string;
178
282
  expiryOverride?: boolean | number;
179
- }): Promise<Issue | undefined>;
283
+ }): Promise<Issue$1 | undefined>;
180
284
  getIssueResult(resource: T, id: string, options?: {
181
285
  connectionId?: string;
182
286
  expiryOverride?: boolean | number;
183
- }): Promise<IntegrationResult<Issue | undefined>>;
287
+ }): Promise<IntegrationResult<Issue$1 | undefined>>;
184
288
  protected getIssueResultCore(resource: ResourceDescriptor, id: string, options: {
185
289
  connectionId?: string;
186
290
  expiryOverride?: boolean | number;
187
- } | undefined, getProviderIssue: (session: ProviderAuthenticationSession) => Promise<Issue | undefined>): Promise<IntegrationResult<Issue | undefined>>;
188
- protected abstract getProviderIssue(session: ProviderAuthenticationSession, resource: T, id: string): Promise<Issue | undefined>;
291
+ } | undefined, getProviderIssue: (session: ProviderAuthenticationSession) => Promise<Issue$1 | undefined>): Promise<IntegrationResult<Issue$1 | undefined>>;
292
+ protected abstract getProviderIssue(session: ProviderAuthenticationSession, resource: T, id: string): Promise<Issue$1 | undefined>;
189
293
  getCurrentAccount(options?: {
190
294
  avatarSize?: number;
191
295
  connectionId?: string;
192
296
  expiryOverride?: boolean | number;
193
- }): Promise<Account | undefined>;
297
+ }): Promise<Account$1 | undefined>;
194
298
  protected getProviderCurrentAccount?(session: ProviderAuthenticationSession, options?: {
195
299
  avatarSize?: number;
196
- }): Promise<Account | undefined>;
300
+ }): Promise<Account$1 | undefined>;
301
+ /** Whether this integration can answer {@link getCurrentAccount} at all — Jira, Linear and Trello cannot. */
302
+ get supportsCurrentAccount(): boolean;
197
303
  /**
198
304
  * Resolves the account for a specific session/token — including connections other than the current
199
305
  * primary (multi-account) — using this integration's provider API base URL and auth type. Returns
200
306
  * undefined when the provider doesn't support account lookup. Uncached (callers cache per connection).
201
307
  */
202
- getProviderAccountForSession(session: ProviderAuthenticationSession): Promise<Account | undefined>;
308
+ getProviderAccountForSession(session: ProviderAuthenticationSession): Promise<Account$1 | undefined>;
203
309
  getPullRequest(resource: T, id: string, options?: {
204
310
  expiryOverride?: boolean | number;
205
311
  throwOnError?: boolean;
@@ -2,19 +2,45 @@ import { isCancellationError } from "../../../utils/cancellation.js";
2
2
  import { Logger as _logger } from "../../../utils/logger.js";
3
3
  import { getScopedLogger } from "../../../utils/logger.scoped.js";
4
4
  import { debug, trace } from "../../../utils/decorators/log.js";
5
+ import { PromiseCache } from "../../../utils/promiseCache.js";
5
6
  import __decorate from "../../../_virtual/@oxc-project+runtime/helpers/esm/decorate.js";
6
- import { AuthenticationError, RequestClientError } from "../../../git/errors.js";
7
+ import { AuthenticationError, RequestClientError, getDeferredRequestFailure } from "../../../git/errors.js";
7
8
  import { Emitter } from "../../../utils/event.js";
8
9
  import { fnv1aHash64 } from "../../../utils/hash.js";
9
10
  import { gate } from "../../../utils/decorators/gate.js";
10
11
  import { toError } from "../errors.js";
11
12
  import "../constants.js";
13
+ import { isAzureProviderId } from "../providers/providerErrors.js";
14
+ import { attributeScopedAuthFailures, hasOnlyScopedAuthFailures, markCredentialRefusals, toCollectionScopeFailure } from "../collectionMetadata.js";
15
+ import { throwIfAllSettledFailed } from "../providers/utils/providerPaging.js";
12
16
  import { areDomainsOnSameHost } from "../utils/domain.utils.js";
13
17
  import { isSelfManagedHostIntegrationId } from "../utils/integration.utils.js";
14
18
  import { providersMetadata } from "../providers/models.js";
15
19
  import { RejectedTokenTracker } from "../authentication/rejectedTokenTracker.js";
16
20
  import { getCachedIssue } from "./issueCache.js";
17
21
  //#region ../plus/integrations/src/models/integration.ts
22
+ /**
23
+ * The scope a batch read records a refused target under. It is decided here rather than in the read, which has the
24
+ * same targets, because naming the refusal's cause (`describeRefusal`) needs it too. Azure DevOps: the organization
25
+ * and project, since its refusals come from those (an organization's OAuth policy or tenant, a project's permissions)
26
+ * and its work items belong to no repository. Every other git host: the repository, `owner/repo`, which is how its
27
+ * repo-scoped reads record one. A tracker: the resource.
28
+ */
29
+ function batchTargetScope(providerId, target) {
30
+ if ("resourceId" in target) return {
31
+ providerId,
32
+ resourceId: target.resourceId
33
+ };
34
+ if (isAzureProviderId(providerId)) return {
35
+ providerId,
36
+ resourceId: target.owner,
37
+ projectId: target.project
38
+ };
39
+ return {
40
+ providerId,
41
+ repositoryId: `${target.owner}/${target.repo}`
42
+ };
43
+ }
18
44
  var IntegrationBase = class IntegrationBase {
19
45
  ctx;
20
46
  authenticationService;
@@ -284,23 +310,154 @@ var IntegrationBase = class IntegrationBase {
284
310
  case "disconnected": await this.disconnect({ silent: true });
285
311
  }
286
312
  }
313
+ /**
314
+ * Credentials that passed {@link validateCredential} within the last minute. A probe already in flight is
315
+ * shared rather than repeated, and a refused one is not kept. Cleared whenever a credential is refused (see
316
+ * {@link handleProviderException}), so a pass is never vouched for past a refusal this instance has seen.
317
+ */
318
+ _validatedCredentials = new PromiseCache({
319
+ createTTL: 6e4,
320
+ capacity: 10
321
+ });
322
+ /**
323
+ * Settles what a read's only-scoped authentication failures mean, when the provider can tell (see
324
+ * {@link validateCredential}).
325
+ *
326
+ * A refused credential is thrown: the caller's `catch` then fails the read as a whole, exactly as it does when
327
+ * discovery is not cached and the first request is the one refused. Without this, a token revoked after
328
+ * discovery would publish scoped `auth` warnings, which a consumer must not answer with a reconnect (see
329
+ * `ProviderWarning.scope`), next to results served from the cache, for as long as the token stays stored.
330
+ *
331
+ * A confirmed credential makes each refusal the scope's own, so its cause is named through
332
+ * {@link describeRefusal}. A probe that fails for another reason proves nothing either way and leaves the read
333
+ * as it was.
334
+ *
335
+ * A refusal the provider pins on the credential itself (see {@link isCredentialRefusal}) is published for the
336
+ * connection instead, without a probe.
337
+ *
338
+ * A token that passed within the last minute is not probed again, so a scope that keeps refusing it costs one
339
+ * probe a minute, and a revocation right after a probe can take up to a minute to surface: until then its
340
+ * refusals stay scoped, and may be named. A probe that proved nothing is not remembered, so the next read probes
341
+ * again, and a credential no probe can confirm (e.g. a Bitbucket Data Center project access token) is probed on
342
+ * every read that has scoped refusals.
343
+ */
344
+ async confirmScopedAuthFailures(session, metadata) {
345
+ if (!hasOnlyScopedAuthFailures(metadata)) return;
346
+ if (this.markCredentialRefusals(metadata)) return;
347
+ if (await this.confirmCredential(session) !== "confirmed") return;
348
+ this.nameRefusalCauses(session, metadata);
349
+ }
350
+ /**
351
+ * Settles what a batch read's authentication refusals mean before the call is judged, as
352
+ * {@link confirmScopedAuthFailures} settles an account-wide read's: each refused slot comes back carrying the scope
353
+ * failure it was recorded as, against the scope its target names (see {@link batchTargetScope}), and a read
354
+ * publishes that instead of the bare reason.
355
+ *
356
+ * When any target answered, that proved the credential, so each refusal is its own target's. When every target
357
+ * failed, the call fails as a whole, as it always has, unless every failure is a refusal of a credential that checks
358
+ * out. Every target refusing is also what a dead credential looks like, so the credential is checked once first,
359
+ * with {@link validateCredential}. A check that proves nothing leaves the refusals scoped and unnamed, as it leaves
360
+ * an account-wide read's. A provider with no check cannot tell, and a refusal it pins on the credential (see
361
+ * {@link isCredentialRefusal}) needs none, so either still fails the call.
362
+ */
363
+ async settleBatchRefusals(session, targets, slots) {
364
+ const failures = slots.map((slot, i) => slot.status === "rejected" && slot.reason instanceof AuthenticationError ? toCollectionScopeFailure(batchTargetScope(this.id, targets[i]), slot.reason) : void 0);
365
+ const refused = failures.filter((f) => f != null);
366
+ if (!refused.length) {
367
+ throwIfAllSettledFailed(slots);
368
+ return slots;
369
+ }
370
+ const metadata = {
371
+ completeness: "partial",
372
+ failures: refused
373
+ };
374
+ const credentialRefused = this.markCredentialRefusals(metadata);
375
+ if (slots.some((slot) => slot.status === "fulfilled")) this.nameRefusalCauses(session, metadata);
376
+ else if (credentialRefused) throwIfAllSettledFailed(slots);
377
+ else if (refused.length < slots.length) throwIfAllSettledFailed(slots.filter((_, i) => failures[i] == null));
378
+ else {
379
+ const confirmation = await this.confirmCredential(session);
380
+ if (confirmation == null) throwIfAllSettledFailed(slots);
381
+ else if (confirmation === "confirmed") this.nameRefusalCauses(session, metadata);
382
+ }
383
+ return slots.map((slot, i) => {
384
+ const failure = failures[i];
385
+ return slot.status === "rejected" && failure != null ? {
386
+ ...slot,
387
+ failure
388
+ } : slot;
389
+ });
390
+ }
391
+ /**
392
+ * Shows one notice, and spends at most one strike, for a batch read whose targets failed with a server error or a
393
+ * timeout. Our own clients leave both to the batch (see `reportRequestFailure`) instead of spending them per
394
+ * target. `strike` is true only when every target failed and the call's own failure spent none: a call that
395
+ * answered some targets showed the host is up, and budgets strikes the same way as the rest of its failures.
396
+ */
397
+ reportDeferredRequestFailures(slots, strike) {
398
+ let notify;
399
+ for (const slot of slots ?? []) {
400
+ if (slot.status !== "rejected") continue;
401
+ notify = getDeferredRequestFailure(slot.reason);
402
+ if (notify != null) break;
403
+ }
404
+ if (notify == null) return;
405
+ if (strike) this.trackRequestException();
406
+ notify();
407
+ }
408
+ /** Marks the scoped refusals the provider pins on the credential (see {@link isCredentialRefusal}). */
409
+ markCredentialRefusals(metadata) {
410
+ const isCredentialRefusal = this.isCredentialRefusal?.bind(this);
411
+ return isCredentialRefusal != null && markCredentialRefusals(metadata, isCredentialRefusal);
412
+ }
413
+ /**
414
+ * Checks the credential with {@link validateCredential}, rethrowing the provider's refusal when it is refused.
415
+ * `undefined` when the provider has no check, and `'unproven'` when the check proved nothing either way.
416
+ */
417
+ async confirmCredential(session) {
418
+ const validateCredential = this.validateCredential?.bind(this);
419
+ if (validateCredential == null) return void 0;
420
+ const key = [
421
+ session.domain,
422
+ session.baseUrl ?? "",
423
+ session.accessToken
424
+ ].join("\n");
425
+ try {
426
+ await this._validatedCredentials.getOrCreate(key, () => validateCredential(session));
427
+ } catch (ex) {
428
+ if (ex instanceof AuthenticationError && ex.reason !== 3) throw ex;
429
+ return "unproven";
430
+ }
431
+ return "confirmed";
432
+ }
433
+ /** Names each scoped refusal's cause (see {@link describeRefusal}); only once the credential is proven. */
434
+ nameRefusalCauses(session, metadata) {
435
+ const describeRefusal = this.describeRefusal?.bind(this);
436
+ if (describeRefusal == null) return;
437
+ attributeScopedAuthFailures(metadata, (refusal, scope) => describeRefusal(session, refusal, scope));
438
+ }
439
+ /** Returns whether it spent a strike toward disconnecting (see {@link trackRequestException}). */
287
440
  handleProviderException(syncReqUsecase, ex, options) {
288
- if (isCancellationError(ex)) return;
441
+ if (isCancellationError(ex)) return false;
442
+ if (ex instanceof AuthenticationError) this._validatedCredentials.clear();
289
443
  if (options?.scope != null) options.scope.error(ex);
290
444
  else _logger.error(ex);
291
445
  if (ex instanceof AuthenticationError && options?.connectionId) {
292
- if (!this._rejectedTokens.recordRejection(options.connectionId)) this.trackRequestException(options);
293
- return;
446
+ if (this._rejectedTokens.recordRejection(options.connectionId)) return false;
447
+ this.trackRequestException(options);
448
+ return true;
294
449
  }
295
- if (ex instanceof AuthenticationError && this._session?.cloud) {
296
- if (!this.hasSessionSyncRequests()) {
297
- this.requestSessionSyncForUsecase(syncReqUsecase);
298
- this._session = {
299
- ...this._session,
300
- expiresAt: /* @__PURE__ */ new Date(Date.now() - 1)
301
- };
302
- } else this.trackRequestException(options);
303
- } else if (ex instanceof AuthenticationError || ex instanceof RequestClientError) this.trackRequestException(options);
450
+ if (ex instanceof AuthenticationError && this._session?.cloud && !this.hasSessionSyncRequests()) {
451
+ this.requestSessionSyncForUsecase(syncReqUsecase);
452
+ this._session = {
453
+ ...this._session,
454
+ expiresAt: /* @__PURE__ */ new Date(Date.now() - 1)
455
+ };
456
+ return false;
457
+ }
458
+ if (!(ex instanceof AuthenticationError) && !(ex instanceof RequestClientError)) return false;
459
+ this.trackRequestException(options);
460
+ return true;
304
461
  }
305
462
  missingExpirityReported = false;
306
463
  async refreshSessionIfExpired(scope) {
@@ -438,6 +595,7 @@ var IntegrationBase = class IntegrationBase {
438
595
  if (session == null) return void 0;
439
596
  try {
440
597
  const result = await this.searchProviderMyIssuesWithTruncation(session, resources != null ? Array.isArray(resources) ? resources : [resources] : void 0, cancellation, options);
598
+ await this.confirmScopedAuthFailures(session, result?.metadata);
441
599
  this.resetRequestExceptionCount("searchMyIssues");
442
600
  return { value: result };
443
601
  } catch (ex) {
@@ -535,6 +693,10 @@ var IntegrationBase = class IntegrationBase {
535
693
  etag: `${this.id}:${this.maybeConnected ?? false}:${sessionFingerprint}`
536
694
  });
537
695
  }
696
+ /** Whether this integration can answer {@link getCurrentAccount} at all — Jira, Linear and Trello cannot. */
697
+ get supportsCurrentAccount() {
698
+ return this.getProviderCurrentAccount != null;
699
+ }
538
700
  /**
539
701
  * Resolves the account for a specific session/token — including connections other than the current
540
702
  * primary (multi-account) — using this integration's provider API base URL and auth type. Returns