@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.
- package/CHANGELOG.md +48 -1
- package/dist/git/cache.d.ts +18 -5
- package/dist/git/cache.js +26 -8
- package/dist/git/cache.js.map +1 -1
- package/dist/git/context.d.ts +5 -1
- package/dist/git/context.js.map +1 -1
- package/dist/git/errors.d.ts +28 -1
- package/dist/git/errors.js +45 -1
- package/dist/git/errors.js.map +1 -1
- package/dist/git/providers/branches.d.ts +5 -1
- package/dist/git/providers/commits.d.ts +24 -1
- package/dist/git/providers/config.d.ts +7 -1
- package/dist/git/providers/operations.d.ts +6 -0
- package/dist/git/providers/provider.d.ts +17 -0
- package/dist/git/providers/refs.d.ts +38 -3
- package/dist/git/providers/revision.d.ts +5 -1
- package/dist/git/providers/staging.d.ts +12 -0
- package/dist/git/providers/worktrees.d.ts +23 -3
- package/dist/git/run.types.d.ts +6 -0
- package/dist/git-cli/cliGitProvider.d.ts +17 -1
- package/dist/git-cli/cliGitProvider.js +37 -1
- package/dist/git-cli/cliGitProvider.js.map +1 -1
- package/dist/git-cli/exec/git.d.ts +12 -2
- package/dist/git-cli/exec/git.js +48 -17
- package/dist/git-cli/exec/git.js.map +1 -1
- package/dist/git-cli/exec/gitQueue.d.ts +4 -1
- package/dist/git-cli/exec/gitQueue.js +1 -1
- package/dist/git-cli/exec/gitQueue.js.map +1 -1
- package/dist/git-cli/providers/branches.d.ts +10 -1
- package/dist/git-cli/providers/branches.js +15 -8
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/git-cli/providers/commits.d.ts +23 -1
- package/dist/git-cli/providers/commits.js +57 -8
- package/dist/git-cli/providers/commits.js.map +1 -1
- package/dist/git-cli/providers/config.js +4 -3
- package/dist/git-cli/providers/config.js.map +1 -1
- package/dist/git-cli/providers/operations.d.ts +1 -0
- package/dist/git-cli/providers/operations.js +39 -3
- package/dist/git-cli/providers/operations.js.map +1 -1
- package/dist/git-cli/providers/patch.js +20 -41
- package/dist/git-cli/providers/patch.js.map +1 -1
- package/dist/git-cli/providers/pausedOperations.js +2 -2
- package/dist/git-cli/providers/pausedOperations.js.map +1 -1
- package/dist/git-cli/providers/refs.d.ts +15 -3
- package/dist/git-cli/providers/refs.js +115 -10
- package/dist/git-cli/providers/refs.js.map +1 -1
- package/dist/git-cli/providers/revision.d.ts +3 -1
- package/dist/git-cli/providers/revision.js +9 -5
- package/dist/git-cli/providers/revision.js.map +1 -1
- package/dist/git-cli/providers/staging.d.ts +8 -0
- package/dist/git-cli/providers/staging.js +59 -7
- package/dist/git-cli/providers/staging.js.map +1 -1
- package/dist/git-cli/providers/worktrees.d.ts +31 -2
- package/dist/git-cli/providers/worktrees.js +75 -7
- package/dist/git-cli/providers/worktrees.js.map +1 -1
- package/dist/ipc/discovery.js +1 -1
- package/dist/plus/agents/providers/claudeCodeTranscript.js +6 -6
- package/dist/plus/agents/providers/claudeCodeTranscript.js.map +1 -1
- package/dist/plus/ai/providers/openAICompatibleProviderBase.js +1 -1
- package/dist/plus/git-github/api/github.d.ts +64 -3
- package/dist/plus/git-github/api/github.js +237 -24
- package/dist/plus/git-github/api/github.js.map +1 -1
- package/dist/plus/git-github/providers/github/branches.d.ts +3 -1
- package/dist/plus/git-github/providers/github/branches.js +2 -1
- package/dist/plus/git-github/providers/github/branches.js.map +1 -1
- package/dist/plus/git-github/providers/github/refs.d.ts +10 -3
- package/dist/plus/git-github/providers/github/refs.js +4 -4
- package/dist/plus/git-github/providers/github/refs.js.map +1 -1
- package/dist/plus/git-github/providers/github/revision.d.ts +3 -1
- package/dist/plus/git-github/providers/github/revision.js +1 -1
- package/dist/plus/git-github/providers/github/revision.js.map +1 -1
- package/dist/plus/integrations/collectionMetadata.d.ts +48 -3
- package/dist/plus/integrations/collectionMetadata.js +138 -8
- package/dist/plus/integrations/collectionMetadata.js.map +1 -1
- package/dist/plus/integrations/errors.d.ts +3 -1
- package/dist/plus/integrations/errors.js +7 -3
- package/dist/plus/integrations/errors.js.map +1 -1
- package/dist/plus/integrations/index.d.ts +6 -3
- package/dist/plus/integrations/index.js.map +1 -1
- package/dist/plus/integrations/integrationService.d.ts +36 -10
- package/dist/plus/integrations/integrationService.js +16 -3
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/manager.d.ts +107 -37
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +129 -14
- package/dist/plus/integrations/models/gitHostIntegration.js +142 -18
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/integration.d.ts +117 -11
- package/dist/plus/integrations/models/integration.js +175 -13
- package/dist/plus/integrations/models/integration.js.map +1 -1
- package/dist/plus/integrations/models/issuesIntegration.d.ts +18 -5
- package/dist/plus/integrations/models/issuesIntegration.js +42 -10
- package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.d.ts +1 -1
- package/dist/plus/integrations/providers/azure/azure.js +14 -18
- package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
- package/dist/plus/integrations/providers/azure/models.d.ts +29 -0
- package/dist/plus/integrations/providers/azure/models.js +112 -1
- package/dist/plus/integrations/providers/azure/models.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOps.d.ts +73 -3
- package/dist/plus/integrations/providers/azureDevOps.js +117 -3
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.js +2 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket/bitbucket.d.ts +61 -0
- package/dist/plus/integrations/providers/bitbucket/bitbucket.js +165 -23
- package/dist/plus/integrations/providers/bitbucket/bitbucket.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket/models.d.ts +6 -1
- package/dist/plus/integrations/providers/bitbucket/models.js +4 -3
- package/dist/plus/integrations/providers/bitbucket/models.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js +2 -2
- package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket-server.d.ts +48 -2
- package/dist/plus/integrations/providers/bitbucket-server.js +51 -1
- package/dist/plus/integrations/providers/bitbucket-server.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket.d.ts +45 -2
- package/dist/plus/integrations/providers/bitbucket.js +41 -2
- package/dist/plus/integrations/providers/bitbucket.js.map +1 -1
- package/dist/plus/integrations/providers/github.d.ts +49 -5
- package/dist/plus/integrations/providers/github.js +102 -7
- package/dist/plus/integrations/providers/github.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab/gitlab.d.ts +35 -0
- package/dist/plus/integrations/providers/gitlab/gitlab.js +132 -15
- package/dist/plus/integrations/providers/gitlab/gitlab.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab/models.d.ts +1 -0
- package/dist/plus/integrations/providers/gitlab/models.js +1 -1
- package/dist/plus/integrations/providers/gitlab/models.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab.d.ts +49 -2
- package/dist/plus/integrations/providers/gitlab.js +80 -4
- package/dist/plus/integrations/providers/gitlab.js.map +1 -1
- package/dist/plus/integrations/providers/jira-server.d.ts +3 -3
- package/dist/plus/integrations/providers/jira-server.js +5 -8
- package/dist/plus/integrations/providers/jira-server.js.map +1 -1
- package/dist/plus/integrations/providers/jira.d.ts +9 -0
- package/dist/plus/integrations/providers/jira.js +13 -1
- package/dist/plus/integrations/providers/jira.js.map +1 -1
- package/dist/plus/integrations/providers/linear.js +1 -1
- package/dist/plus/integrations/providers/models.d.ts +13 -2
- package/dist/plus/integrations/providers/models.js +11 -1
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/providers/providerErrors.d.ts +9 -1
- package/dist/plus/integrations/providers/providerErrors.js +10 -1
- package/dist/plus/integrations/providers/providerErrors.js.map +1 -1
- package/dist/plus/integrations/providers/providersApi.d.ts +60 -1
- package/dist/plus/integrations/providers/providersApi.js +137 -2
- package/dist/plus/integrations/providers/providersApi.js.map +1 -1
- package/dist/plus/integrations/providers/utils/providerPaging.d.ts +42 -0
- package/dist/plus/integrations/providers/utils/providerPaging.js +58 -1
- package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
- package/dist/plus/integrations/providers/utils.d.ts +1 -1
- package/dist/plus/integrations/reads/broaden.js +1 -1
- package/dist/plus/integrations/reads/context.d.ts +1 -1
- package/dist/plus/integrations/reads/counts.js +1 -1
- package/dist/plus/integrations/reads/currentAccount.d.ts +35 -0
- package/dist/plus/integrations/reads/currentAccount.js +46 -0
- package/dist/plus/integrations/reads/currentAccount.js.map +1 -0
- package/dist/plus/integrations/reads/drains.d.ts +5 -2
- package/dist/plus/integrations/reads/drains.js +8 -4
- package/dist/plus/integrations/reads/drains.js.map +1 -1
- package/dist/plus/integrations/reads/hierarchy.js +1 -1
- package/dist/plus/integrations/reads/hierarchy.utils.js +1 -1
- package/dist/plus/integrations/reads/issueBatch.d.ts +72 -12
- package/dist/plus/integrations/reads/issueBatch.js +193 -48
- package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.js +1 -1
- package/dist/plus/integrations/reads/issues.js +1 -1
- package/dist/plus/integrations/reads/pullRequestBatch.d.ts +57 -0
- package/dist/plus/integrations/reads/pullRequestBatch.js +80 -0
- package/dist/plus/integrations/reads/pullRequestBatch.js.map +1 -0
- package/dist/plus/integrations/reads/pullRequestBranches.d.ts +58 -0
- package/dist/plus/integrations/reads/pullRequestBranches.js +111 -0
- package/dist/plus/integrations/reads/pullRequestBranches.js.map +1 -0
- package/dist/plus/integrations/reads/pullRequests.js +3 -3
- package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
- package/dist/plus/integrations/reads/resolveRepository.js +1 -1
- package/dist/plus/integrations/reads/sweeps.js +4 -4
- package/dist/plus/integrations/reads/sweeps.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +87 -6
- package/dist/plus/integrations/results.js +9 -2
- package/dist/plus/integrations/results.js.map +1 -1
- package/dist/utils/env/node/exec.js +3 -3
- package/dist/utils/env/node/exec.js.map +1 -1
- package/docs/integrations.md +175 -35
- package/docs/kepler-git-write-api-parity.md +239 -0
- package/docs/kepler-read-api-parity.md +75 -28
- package/package.json +1 -1
- package/dist/plus/integrations/reads/trackerIssue.d.ts +0 -33
- package/dist/plus/integrations/reads/trackerIssue.js +0 -60
- 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
|
-
|
|
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
|
-
}):
|
|
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 (
|
|
293
|
-
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
this._session
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|