@gitkraken/core-gitlens 0.10.2 → 0.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -1
- package/dist/git/models/pullRequest.d.ts +37 -0
- package/dist/git/models/pullRequest.js.map +1 -1
- package/dist/git-cli/providers/graph.js +14 -8
- package/dist/git-cli/providers/graph.js.map +1 -1
- package/dist/plus/git-github/api/github.d.ts +3 -1
- package/dist/plus/git-github/api/github.js +1 -1
- package/dist/plus/git-github/api/github.js.map +1 -1
- package/dist/plus/integrations/collectionMetadata.d.ts +1 -1
- package/dist/plus/integrations/collectionMetadata.js +19 -8
- package/dist/plus/integrations/collectionMetadata.js.map +1 -1
- package/dist/plus/integrations/errors.d.ts +2 -0
- package/dist/plus/integrations/errors.js +45 -1
- package/dist/plus/integrations/errors.js.map +1 -1
- package/dist/plus/integrations/integrationService.js +2 -2
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +5 -1
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/integration.d.ts +1 -0
- package/dist/plus/integrations/models/integration.js +7 -4
- package/dist/plus/integrations/models/integration.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.js +2 -6
- package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOps.d.ts +1 -2
- package/dist/plus/integrations/providers/azureDevOps.js +12 -5
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket.js +2 -1
- package/dist/plus/integrations/providers/bitbucket.js.map +1 -1
- package/dist/plus/integrations/providers/github.d.ts +12 -1
- package/dist/plus/integrations/providers/github.js +42 -2
- package/dist/plus/integrations/providers/github.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab.d.ts +1 -1
- package/dist/plus/integrations/providers/gitlab.js +3 -2
- package/dist/plus/integrations/providers/gitlab.js.map +1 -1
- package/dist/plus/integrations/providers/models.js +1 -0
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +8 -5
- package/dist/plus/integrations/results.js +3 -1
- package/dist/plus/integrations/results.js.map +1 -1
- package/dist/utils/cancellation.d.ts +2 -3
- package/dist/utils/cancellation.js +2 -3
- package/dist/utils/cancellation.js.map +1 -1
- package/package.json +2 -2
|
@@ -66,7 +66,7 @@ export interface ProviderScopeFailure extends CollectionScopeFailure {
|
|
|
66
66
|
* is the case this failure exists to tell apart, and that sentence says the opposite.
|
|
67
67
|
*/
|
|
68
68
|
export declare function toCollectionScopeFailure(scope: CollectionScopeFailure['scope'], ex: unknown): ProviderScopeFailure;
|
|
69
|
-
export declare function toCollectionFailureError(failure:
|
|
69
|
+
export declare function toCollectionFailureError(failure: ProviderScopeFailure, tokenWithInfo: TokenWithInfo): Error;
|
|
70
70
|
/**
|
|
71
71
|
* Whether `metadata` reports authentication failures only for scopes below the provider, with none for the
|
|
72
72
|
* connection itself. A credential revoked since a provider cached its discovery produces exactly this shape,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AuthenticationError, RequestClientError, RequestNotFoundError, RequestRateLimitError } from "../../git/errors.js";
|
|
2
|
-
import { getResponseHeader, isRateLimitResponse } from "./errors.js";
|
|
2
|
+
import { getResponseHeader, isProviderUnreachableError, isRateLimitResponse } from "./errors.js";
|
|
3
3
|
import { getProviderResponseBodyMessage } from "./providers/providerErrors.js";
|
|
4
4
|
import { appendDedupedWarning, providerWarningMessage } from "./results.js";
|
|
5
5
|
import { isInvalidRequestError, isUnsupportedSortError } from "@gitkraken/provider-apis";
|
|
@@ -82,13 +82,15 @@ function decodeServiceError(value) {
|
|
|
82
82
|
* is the case this failure exists to tell apart, and that sentence says the opposite.
|
|
83
83
|
*/
|
|
84
84
|
function toCollectionScopeFailure(scope, ex) {
|
|
85
|
+
const kind = toCollectionFailureKind(ex);
|
|
85
86
|
const refusal = toProviderRefusal(ex);
|
|
86
87
|
const message = refusal != null ? refusal.detail ?? providerWarningMessage(ex.original) : void 0;
|
|
87
88
|
return {
|
|
88
89
|
scope,
|
|
89
|
-
kind
|
|
90
|
+
kind,
|
|
90
91
|
...message ? { message } : ex instanceof Error && ex.message ? { message: ex.message } : {},
|
|
91
|
-
...refusal != null ? { refusal } : {}
|
|
92
|
+
...refusal != null ? { refusal } : {},
|
|
93
|
+
...kind === "provider" && isProviderUnreachableError(ex) ? { cause: { reason: "unreachable" } } : {}
|
|
92
94
|
};
|
|
93
95
|
}
|
|
94
96
|
function collectionFailureKindToWarningKind(kind) {
|
|
@@ -121,9 +123,15 @@ function toCollectionFailureWarningKind(failure) {
|
|
|
121
123
|
default: return mapped;
|
|
122
124
|
}
|
|
123
125
|
}
|
|
126
|
+
function toCollectionFailureCause(failure, kind) {
|
|
127
|
+
return failure.cause ?? (kind === "other" && isProviderUnreachableError({
|
|
128
|
+
...failure,
|
|
129
|
+
status: getCollectionFailureStatus(failure.message)
|
|
130
|
+
}) ? { reason: "unreachable" } : void 0);
|
|
131
|
+
}
|
|
124
132
|
function toCollectionFailureError(failure, tokenWithInfo) {
|
|
125
|
-
const error = new Error(failure.message ?? "Provider request failed");
|
|
126
133
|
const kind = toCollectionFailureWarningKind(failure);
|
|
134
|
+
const error = new Error(failure.message ?? "Provider request failed", { cause: toCollectionFailureCause(failure, kind) });
|
|
127
135
|
const { accessToken, ...tokenInfo } = tokenWithInfo;
|
|
128
136
|
if (kind === "auth") return new AuthenticationError(tokenInfo, error.message, error);
|
|
129
137
|
if (kind === "rate-limit") return new RequestRateLimitError(error, accessToken, void 0);
|
|
@@ -146,6 +154,7 @@ function causeText(cause) {
|
|
|
146
154
|
case "oauth-app-not-allowed": return "the organization does not allow third-party OAuth apps";
|
|
147
155
|
case "access-denied": return "the account has no access to it";
|
|
148
156
|
case "conditional-access": return "a Conditional Access policy blocked the request";
|
|
157
|
+
case "unreachable": return "the provider server could not be reached";
|
|
149
158
|
default:
|
|
150
159
|
cause.reason;
|
|
151
160
|
return "the provider refused the request";
|
|
@@ -309,19 +318,21 @@ function assessCollectionMetadata(providerId, domain, connectionId, metadata) {
|
|
|
309
318
|
const failures = metadata.failures ?? [];
|
|
310
319
|
for (const failure of failures) {
|
|
311
320
|
const kind = toCollectionFailureWarningKind(failure);
|
|
321
|
+
const cause = toCollectionFailureCause(failure, kind);
|
|
312
322
|
const scope = failure.credentialRefused ? void 0 : toProviderWarningScope(failure.scope);
|
|
313
323
|
appendDedupedWarning(warnings, {
|
|
314
324
|
providerId,
|
|
315
325
|
domain,
|
|
316
326
|
connectionId,
|
|
317
|
-
message: collectionFailureMessage(
|
|
327
|
+
message: collectionFailureMessage({
|
|
318
328
|
...failure,
|
|
319
|
-
|
|
320
|
-
|
|
329
|
+
cause,
|
|
330
|
+
...failure.credentialRefused ? { scope: void 0 } : {}
|
|
331
|
+
}),
|
|
321
332
|
kind,
|
|
322
333
|
isAuth: kind === "auth",
|
|
323
334
|
...scope != null ? { scope } : {},
|
|
324
|
-
...
|
|
335
|
+
...cause != null ? { cause: { ...cause } } : {}
|
|
325
336
|
});
|
|
326
337
|
}
|
|
327
338
|
const omissions = metadata.omissions ?? [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collectionMetadata.js","names":[],"sources":["../../../../plus/integrations/src/collectionMetadata.ts"],"sourcesContent":["import type {\n\tCollectionMetadata,\n\tCollectionOmission,\n\tCollectionScope,\n\tCollectionScopeFailure,\n} from '@gitkraken/provider-apis';\n// Matched by its `code` discriminator rather than by `instanceof`: the SDK ships one bundle per entry point, so\n// the class reached through the root is not the same object as the one reached through `/providers`.\nimport { isInvalidRequestError, isUnsupportedSortError } from '@gitkraken/provider-apis';\nimport {\n\tAuthenticationError,\n\tRequestClientError,\n\tRequestNotFoundError,\n\tRequestRateLimitError,\n} from '@gitlens/git/errors.js';\nimport type { TokenWithInfo } from './authentication/models.js';\nimport type { IntegrationIds } from './constants.js';\nimport type { ResponseHeaders } from './errors.js';\nimport { getResponseHeader, isRateLimitResponse } from './errors.js';\nimport { getProviderResponseBodyMessage } from './providers/providerErrors.js';\nimport type {\n\tProviderWarning,\n\tProviderWarningCause,\n\tProviderWarningOmission,\n\tProviderWarningScope,\n} from './results.js';\nimport { appendDedupedWarning, providerWarningMessage } from './results.js';\n\n/**\n * Re-throws an error that is a fact about the CALL rather than about one scope of a fan-out.\n *\n * Every provider fan-out catches per-scope rejections and records them as {@link CollectionScopeFailure}s, which is\n * right for auth, rate limits and a missing project: those really did happen to one scope, and the siblings that\n * succeeded must survive. A caller-contract error is the opposite — it is identical for every scope and was decided\n * before any request went out — so degrading it would hand back N indistinguishable failures and an empty `partial`\n * page, describing an invalid call as an incomplete read.\n *\n * Today that means the SDK's `UnsupportedSortError` and `InvalidRequestError` — the two codes it lists in its own\n * `CALLER_CONTRACT_ERROR_CODES`, both documented as \"never degraded into a `CollectionScopeFailure` or an\n * omission\". The second covers arguments rejected before the first request goes out: a project key list longer\n * than one Jira search can carry (or an empty one), a non-integer `pageSize`, an unknown current-user filter, or a\n * Jira Server read with no `baseUrl` to address. Called from every fan-out rather than spelled out in each, so a\n * fan-out added later inherits the rule instead of silently omitting it — which is exactly how Azure's\n * account-wide drain came to degrade it while its three siblings did not.\n */\nexport function throwIfCallerContractError(ex: unknown): void {\n\tif (isUnsupportedSortError(ex) || isInvalidRequestError(ex)) throw ex;\n}\n\n/**\n * Whether a failed query that covered several scopes would fail each scope's own read the same way: a rejected or\n * rate-limited token, or a call the SDK refused before sending. Anything else (a query the provider rejects because\n * one scope is gone or no longer visible, a server error) may be one scope's problem, so the scopes are worth\n * reading on their own.\n */\nexport function failsEveryScope(ex: unknown): boolean {\n\tconst kind = toCollectionFailureKind(ex);\n\treturn (\n\t\tkind === 'authentication' || kind === 'rate-limit' || isUnsupportedSortError(ex) || isInvalidRequestError(ex)\n\t);\n}\n\n/**\n * Maps a caught GitLens request error to the SDK collection failure vocabulary used inside provider fan-outs.\n */\nexport function toCollectionFailureKind(ex: unknown): CollectionScopeFailure['kind'] {\n\tif (ex instanceof AuthenticationError) return 'authentication';\n\tif (ex instanceof RequestRateLimitError) return 'rate-limit';\n\tif (ex instanceof RequestNotFoundError) return 'not-found';\n\treturn 'provider';\n}\n\n/**\n * What an authentication refusal's response said, kept on the failure so the provider can name its cause once the\n * credential behind it is confirmed (see `IntegrationBase.confirmScopedAuthFailures`). Only these facts are kept,\n * never the response: its headers carry the provider's session cookies.\n */\nexport interface ProviderRefusal {\n\tstatus: number;\n\t/** The provider's own explanation: Azure DevOps' `X-TFS-ServiceError`, or the error body's message. */\n\tdetail?: string;\n\t/** The provider's error type, when the body names one (Azure DevOps `typeKey`). */\n\ttypeKey?: string;\n\t/**\n\t * The OAuth app the refused token belongs to, when the provider says (GitHub's `X-OAuth-Client-Id`). Not a\n\t * secret: it is the app's public client id, which the page where a user asks an organization to approve the app\n\t * is addressed by.\n\t */\n\toauthClientId?: string;\n}\n\n/** A scope failure as recorded here: the SDK's shape, plus what the refusal said and, once it is known, why. */\nexport interface ProviderScopeFailure extends CollectionScopeFailure {\n\trefusal?: ProviderRefusal;\n\tcause?: ProviderWarningCause;\n\t/** The provider pinned the refusal on the credential itself, so it is published for the connection, unscoped. */\n\tcredentialRefused?: true;\n}\n\n/**\n * Reads the refusal off an `AuthenticationError` that carries the provider's response: the SDK adapter's plain\n * `{ status, headers, body }` (`providersApi.ts`), Octokit's `{ status, headers, data }` (the GitHub client's REST\n * reads), or the `Response` of a `ProviderFetchError`, whose body was already read into its message.\n */\nfunction toProviderRefusal(ex: unknown): ProviderRefusal | undefined {\n\tif (!(ex instanceof AuthenticationError)) return undefined;\n\n\tconst response = (ex.original as { response?: unknown } | undefined)?.response as\n\t\t| { status?: unknown; headers?: ResponseHeaders; body?: unknown; data?: unknown }\n\t\t| undefined;\n\tif (typeof response?.status !== 'number') return undefined;\n\n\t// Only a structured body: a string one is a page (a proxy's, a sign-in form), not an explanation.\n\tconst raw = response.body ?? response.data;\n\tconst body = typeof raw === 'object' && raw != null ? raw : undefined;\n\tconst detail =\n\t\tdecodeServiceError(getResponseHeader(response.headers, 'x-tfs-serviceerror')) ??\n\t\tgetProviderResponseBodyMessage(body);\n\tconst typeKey = body != null && 'typeKey' in body ? body.typeKey : undefined;\n\tconst oauthClientId = getResponseHeader(response.headers, 'x-oauth-client-id');\n\n\treturn {\n\t\tstatus: response.status,\n\t\t...(detail ? { detail: detail } : {}),\n\t\t...(typeof typeKey === 'string' ? { typeKey: typeKey } : {}),\n\t\t...(typeof oauthClientId === 'string' && oauthClientId ? { oauthClientId: oauthClientId } : {}),\n\t};\n}\n\n/** Azure DevOps sends `X-TFS-ServiceError` percent-encoded. */\nfunction decodeServiceError(value: unknown): string | undefined {\n\tif (typeof value !== 'string' || !value) return undefined;\n\n\ttry {\n\t\treturn decodeURIComponent(value).trim() || undefined;\n\t} catch {\n\t\treturn value.trim() || undefined;\n\t}\n}\n\n/**\n * Builds a structured SDK scope failure from a caught GitLens request error.\n *\n * An authentication refusal is described in the provider's own words when it gives any, and otherwise by the\n * error the provider's response raised, sanitized like any warning's (a page becomes its status), rather than by\n * `AuthenticationError`'s generic \"credentials are either invalid or expired\": a scope refusing a sound credential\n * is the case this failure exists to tell apart, and that sentence says the opposite.\n */\nexport function toCollectionScopeFailure(scope: CollectionScopeFailure['scope'], ex: unknown): ProviderScopeFailure {\n\tconst refusal = toProviderRefusal(ex);\n\tconst message =\n\t\trefusal != null ? (refusal.detail ?? providerWarningMessage((ex as AuthenticationError).original)) : undefined;\n\treturn {\n\t\tscope: scope,\n\t\tkind: toCollectionFailureKind(ex),\n\t\t...(message ? { message: message } : ex instanceof Error && ex.message ? { message: ex.message } : {}),\n\t\t...(refusal != null ? { refusal: refusal } : {}),\n\t};\n}\n\nfunction collectionFailureKindToWarningKind(kind: CollectionScopeFailure['kind']): ProviderWarning['kind'] {\n\tswitch (kind) {\n\t\tcase 'authentication':\n\t\t\treturn 'auth';\n\t\tcase 'rate-limit':\n\t\t\treturn 'rate-limit';\n\t\tcase 'not-found':\n\t\t\treturn 'not-found';\n\t\tdefault:\n\t\t\treturn 'other';\n\t}\n}\n\nfunction getCollectionFailureStatus(message: string | undefined): number | undefined {\n\tif (message == null) return undefined;\n\n\tconst match = /\\((\\d{3})\\)/.exec(message) ?? /^(?:HTTP\\s+)?(\\d{3})\\b/i.exec(message.trim());\n\treturn match != null ? Number(match[1]) : undefined;\n}\n\nfunction toCollectionFailureWarningKind(failure: CollectionScopeFailure): ProviderWarning['kind'] {\n\tconst mapped = collectionFailureKindToWarningKind(failure.kind);\n\tif (mapped !== 'other' || (failure.kind !== 'provider' && failure.kind !== 'unknown')) return mapped;\n\n\t// provider-apis currently groups several HTTP statuses under `provider` in partial fan-outs. The custom\n\t// fetch adapter retains the status in `message`, so recover the same neutral classification the thrown-error\n\t// path exposes instead of downgrading these structured failures to `other`.\n\tconst status = getCollectionFailureStatus(failure.message);\n\tswitch (status) {\n\t\tcase 401:\n\t\t\treturn 'auth';\n\t\tcase 403:\n\t\t\treturn isRateLimitResponse({ status: status, message: failure.message }) ? 'rate-limit' : 'auth';\n\t\tcase 404:\n\t\tcase 410:\n\t\tcase 422:\n\t\t\treturn 'not-found';\n\t\tcase 429:\n\t\t\treturn 'rate-limit';\n\t\tdefault:\n\t\t\treturn mapped;\n\t}\n}\n\nexport function toCollectionFailureError(failure: CollectionScopeFailure, tokenWithInfo: TokenWithInfo): Error {\n\tconst error = new Error(failure.message ?? 'Provider request failed');\n\tconst kind = toCollectionFailureWarningKind(failure);\n\tconst { accessToken, ...tokenInfo } = tokenWithInfo;\n\tif (kind === 'auth') return new AuthenticationError(tokenInfo, error.message, error);\n\tif (kind === 'rate-limit') return new RequestRateLimitError(error, accessToken, undefined);\n\tif (kind === 'not-found') return new RequestNotFoundError(error);\n\n\tconst status = getCollectionFailureStatus(failure.message);\n\tif (kind === 'other' && status != null && status >= 400 && status < 500) {\n\t\treturn new RequestClientError(error);\n\t}\n\n\treturn error;\n}\n\n/** ` (resource r, project p, repository o/n)` for the scope IDs present; empty when the scope names none. */\nfunction collectionScopeText(scope: CollectionScope | undefined): string {\n\tconst parts: string[] = [];\n\tif (scope?.resourceId != null) {\n\t\tparts.push(`resource ${scope.resourceId}`);\n\t}\n\tif (scope?.projectId != null) {\n\t\tparts.push(`project ${scope.projectId}`);\n\t}\n\tif (scope?.repositoryId != null) {\n\t\tparts.push(`repository ${scope.repositoryId}`);\n\t}\n\n\treturn parts.length ? ` (${parts.join(', ')})` : '';\n}\n\n/** The cause in the consumer's terms, for the prose that goes with the structured {@link ProviderWarningCause}. */\nfunction causeText(cause: ProviderWarningCause): string {\n\tswitch (cause.reason) {\n\t\tcase 'oauth-app-not-allowed':\n\t\t\treturn 'the organization does not allow third-party OAuth apps';\n\t\tcase 'access-denied':\n\t\t\treturn 'the account has no access to it';\n\t\tcase 'conditional-access':\n\t\t\treturn 'a Conditional Access policy blocked the request';\n\t\tdefault:\n\t\t\tcause.reason satisfies never;\n\t\t\treturn 'the provider refused the request';\n\t}\n}\n\nfunction collectionFailureMessage(failure: ProviderScopeFailure): string {\n\tconst text = failure.cause != null ? causeText(failure.cause) : failure.message;\n\tconst detail = text != null ? `: ${text}` : '';\n\treturn `Failed to read ${failure.kind} scope${collectionScopeText(failure.scope)}${detail}`;\n}\n\n/**\n * Forwards the part of a failure's scope below the provider, so a consumer can tell \"one organization refused\n * this token\" from \"the connection's token is dead\" without parsing {@link collectionFailureMessage}.\n *\n * `providerId` is dropped: the warning already names its provider, and a scope naming nothing below it is a\n * failure of the connection itself. That is the distinction `getOrganizationsForUserResult` also draws before\n * counting an auth failure against the connection, though it reads a scope with no keys but `providerId`, so an\n * absent scope does not count there. Forwarding such a scope would make an account-wide failure look confined.\n *\n * Copied field by field, for the reason {@link toProviderWarningOmission} copies its scope.\n */\nfunction toProviderWarningScope(scope: CollectionScope | undefined): ProviderWarningScope | undefined {\n\tif (scope == null) return undefined;\n\n\tconst { resourceId, projectId, repositoryId } = scope;\n\tif (resourceId == null && projectId == null && repositoryId == null) return undefined;\n\n\treturn {\n\t\t...(resourceId != null ? { resourceId: resourceId } : {}),\n\t\t...(projectId != null ? { projectId: projectId } : {}),\n\t\t...(repositoryId != null ? { repositoryId: repositoryId } : {}),\n\t};\n}\n\n/**\n * Whether `metadata` reports authentication failures only for scopes below the provider, with none for the\n * connection itself. A credential revoked since a provider cached its discovery produces exactly this shape,\n * the same one a scope's own refusal does; `IntegrationBase.confirmScopedAuthFailures` tells them apart.\n *\n * Classified exactly as {@link assessCollectionMetadata} classifies the warnings, so it agrees with what a\n * consumer would be shown.\n */\nexport function hasOnlyScopedAuthFailures(metadata: CollectionMetadata | undefined): boolean {\n\tlet scoped = false;\n\tfor (const failure of metadata?.failures ?? []) {\n\t\tif (toCollectionFailureWarningKind(failure) !== 'auth') continue;\n\t\tif (toProviderWarningScope(failure.scope) == null || (failure as ProviderScopeFailure).credentialRefused) {\n\t\t\treturn false;\n\t\t}\n\n\t\tscoped = true;\n\t}\n\treturn scoped;\n}\n\n/** The scoped authentication failures in `metadata` that kept their refusal, with the scope they are forwarded as. */\nfunction* refusedScopes(\n\tmetadata: CollectionMetadata | undefined,\n): Generator<{ failure: ProviderScopeFailure & { refusal: ProviderRefusal }; scope: ProviderWarningScope }> {\n\tfor (const failure of (metadata?.failures ?? []) as ProviderScopeFailure[]) {\n\t\tif (\n\t\t\tfailure.refusal == null ||\n\t\t\tfailure.credentialRefused ||\n\t\t\ttoCollectionFailureWarningKind(failure) !== 'auth'\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst scope = toProviderWarningScope(failure.scope);\n\t\tif (scope == null) continue;\n\n\t\tyield { failure: failure as ProviderScopeFailure & { refusal: ProviderRefusal }, scope: scope };\n\t}\n}\n\n/**\n * Marks each scoped authentication failure in `metadata` whose refusal the provider pins on the credential, so it is\n * published for the connection rather than for its scope. Returns whether any was.\n */\nexport function markCredentialRefusals(\n\tmetadata: CollectionMetadata | undefined,\n\tisCredentialRefusal: (refusal: ProviderRefusal) => boolean,\n): boolean {\n\tlet marked = false;\n\tfor (const { failure } of refusedScopes(metadata)) {\n\t\tif (!isCredentialRefusal(failure.refusal)) continue;\n\n\t\tfailure.credentialRefused = true;\n\t\tmarked = true;\n\t}\n\treturn marked;\n}\n\n/**\n * Names the cause of each scoped authentication failure in `metadata` that kept its refusal. Only for a read whose\n * credential was just confirmed: before that, the refusals this can name look exactly like a dead credential.\n */\nexport function attributeScopedAuthFailures(\n\tmetadata: CollectionMetadata | undefined,\n\tdescribe: (refusal: ProviderRefusal, scope: ProviderWarningScope) => ProviderWarningCause | undefined,\n): void {\n\tfor (const { failure, scope } of refusedScopes(metadata)) {\n\t\tif (failure.cause != null) continue;\n\n\t\tconst cause = describe(failure.refusal, scope);\n\t\tif (cause != null) {\n\t\t\tfailure.cause = cause;\n\t\t}\n\t}\n}\n\n/**\n * Explains one omission in the consumer's terms — what was left out and, where the SDK reports it, how much.\n *\n * An omission is a completeness fact, never a failure: the read succeeded, and the provider (or the SDK's own\n * recovery budget) is what withheld results. That is why these never contribute to `fetchFailed`. Whether a\n * retry would recover anything is a separate question this layer cannot answer — see the `recovery` note on\n * {@link toProviderWarningOmission}.\n */\nfunction collectionOmissionMessage(omission: CollectionOmission): string {\n\tconst scopeText = collectionScopeText(omission.scope);\n\tswitch (omission.kind) {\n\t\tcase 'provider-limit':\n\t\t\t// `totalCount` is the only figure saying how much was withheld; both fields are optional, so degrade\n\t\t\t// through the shapes the SDK can actually emit rather than printing `undefined`.\n\t\t\tif (omission.totalCount != null && omission.limit != null) {\n\t\t\t\treturn `Search${scopeText} matched ${omission.totalCount} results, but the provider exposes at most ${omission.limit}`;\n\t\t\t}\n\t\t\tif (omission.limit != null) {\n\t\t\t\treturn `Search${scopeText} exceeded the provider limit of ${omission.limit} results`;\n\t\t\t}\n\t\t\treturn `Search${scopeText} exceeded the provider's result limit`;\n\t\tcase 'recovery-budget':\n\t\t\treturn `Stopped recovering omitted results${scopeText} after reaching the request budget${\n\t\t\t\tomission.limit != null ? ` of ${omission.limit} requests` : ''\n\t\t\t}`;\n\t\tcase 'pagination-incomplete':\n\t\t\treturn `More results are available${scopeText} than this read returned`;\n\t\tdefault:\n\t\t\t// `CollectionOmissionKind` is a closed union, so this is unreachable today. Kept as a compile-time\n\t\t\t// guard rather than a silent fallback: an SDK bump that adds a kind breaks the build here instead\n\t\t\t// of quietly degrading it to a vague message.\n\t\t\tomission.kind satisfies never;\n\t\t\treturn `Some results were omitted${scopeText}; the read is incomplete`;\n\t}\n}\n\n/**\n * Forwards an SDK omission as the structured signal consumers read instead of parsing {@link\n * collectionOmissionMessage}'s prose.\n *\n * `results.ts` re-spells `CollectionOmissionKind` rather than importing it, so the published warning surface\n * carries no `@gitkraken/provider-apis` types (see the export block in `index.ts`). The return type is what\n * keeps the two unions honest: an SDK bump that adds a member fails to compile here, alongside\n * `collectionOmissionMessage`'s `satisfies never`.\n *\n * `scope` is copied because the SDK's own object is retained and re-merged across drained pages\n * (`providerPaging.ts`), and this one crosses the package boundary.\n *\n * `recovery` is always `'none'`, deliberately, and a scoped `pagination-incomplete` is why it looks wrong: the\n * SDK emits that one shape from situations with opposite remedies and says so itself — \"Either the read\n * deliberately took one page per scope, or the provider advertised another page it gave no way to reach.\" Its\n * `collectAcrossScopes` producers (Azure, Bitbucket, Bitbucket Server) are the first, its `drainAcrossScopes`\n * producers (GitLab, Jira) the second, and those report an omission only when a cursor STALLED — so re-reading\n * that scope stalls at the identical page. Nothing in `CollectionOmission` separates the two, so claiming\n * recoverability would ship the dead-end button `recovery` exists to prevent. Making the recoverable case\n * claimable needs provider-apis to say whether a scope was drained or merely sampled: a fact to forward, not\n * to guess here.\n */\nfunction toProviderWarningOmission(omission: CollectionOmission): ProviderWarningOmission {\n\treturn {\n\t\tkind: omission.kind,\n\t\trecovery: 'none',\n\t\t...(omission.limit != null ? { limit: omission.limit } : {}),\n\t\t// The SDK types this `number | null | undefined`; normalize to one absence for consumers.\n\t\t...(omission.totalCount != null ? { totalCount: omission.totalCount } : {}),\n\t\t...(omission.scope != null ? { scope: { ...omission.scope } } : {}),\n\t};\n}\n\n/**\n * Whether SDK metadata describes a read that may be missing results.\n *\n * A reported omission counts on its own rather than deferring to `completeness`. The SDK does couple the\n * two — every site that emits an omission degrades completeness in the same call — so today the omission\n * clause changes no outcome. It is defensive: the two facts arrive as independent fields, and a read that\n * names what it left out must not be publishable as whole if a future producer sets only one of them.\n *\n * This is the single definition of \"may be missing results\", shared with `getPagedResult`, so one metadata\n * object cannot be truncated at one layer and complete at another.\n */\nexport function isIncompleteCollection(metadata: CollectionMetadata | undefined): boolean {\n\tif (metadata == null) return false;\n\n\treturn metadata.completeness !== 'complete' || (metadata.omissions?.length ?? 0) > 0;\n}\n\n/** Converts internal SDK collection metadata into neutral provider facade signals. */\nexport function assessCollectionMetadata(\n\tproviderId: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmetadata: CollectionMetadata | undefined,\n): { warnings: ProviderWarning[]; fetchFailed: boolean; truncated: boolean } {\n\tif (metadata == null) return { warnings: [], fetchFailed: false, truncated: false };\n\n\tconst warnings: ProviderWarning[] = [];\n\tconst failures: ProviderScopeFailure[] = metadata.failures ?? [];\n\tfor (const failure of failures) {\n\t\tconst kind = toCollectionFailureWarningKind(failure);\n\t\tconst scope = failure.credentialRefused ? undefined : toProviderWarningScope(failure.scope);\n\t\tappendDedupedWarning(warnings, {\n\t\t\tproviderId: providerId,\n\t\t\tdomain: domain,\n\t\t\tconnectionId: connectionId,\n\t\t\t// A credential-level refusal names no scope in prose either, so every scope's copy of it is one warning.\n\t\t\tmessage: collectionFailureMessage(failure.credentialRefused ? { ...failure, scope: undefined } : failure),\n\t\t\tkind: kind,\n\t\t\tisAuth: kind === 'auth',\n\t\t\t...(scope != null ? { scope: scope } : {}),\n\t\t\t// Copied for the reason the scope is: the failure object is retained and re-merged across pages.\n\t\t\t...(failure.cause != null ? { cause: { ...failure.cause } } : {}),\n\t\t});\n\t}\n\n\t// Omissions explain WHY a read is incomplete when nothing failed — a provider cap, an exhausted recovery\n\t// budget, an undrained scope. They classify as `other`, never `auth`, and are deliberately excluded from\n\t// `fetchFailed` below: the request itself succeeded. `omission` carries that same fact structurally, so a\n\t// consumer can act on it without parsing the message.\n\tconst omissions = metadata.omissions ?? [];\n\tfor (const omission of omissions) {\n\t\tappendDedupedWarning(warnings, {\n\t\t\tproviderId: providerId,\n\t\t\tdomain: domain,\n\t\t\tconnectionId: connectionId,\n\t\t\tmessage: collectionOmissionMessage(omission),\n\t\t\tkind: 'other',\n\t\t\tisAuth: false,\n\t\t\tomission: toProviderWarningOmission(omission),\n\t\t});\n\t}\n\n\tconst incomplete = isIncompleteCollection(metadata);\n\t// Only fall back to the generic message when nothing more specific was reported; an omission already\n\t// explains the incompleteness in the consumer's terms, so adding this on top would be noise.\n\t//\n\t// This one deliberately carries no `omission`: it fires precisely when the SDK reported incompleteness\n\t// WITHOUT saying what was left out, so there is no structured fact to forward and synthesizing one would\n\t// assert a specificity this layer does not have.\n\tif (incomplete && failures.length === 0 && omissions.length === 0) {\n\t\tappendDedupedWarning(warnings, {\n\t\t\tproviderId: providerId,\n\t\t\tdomain: domain,\n\t\t\tconnectionId: connectionId,\n\t\t\tmessage:\n\t\t\t\tmetadata.completeness === 'partial'\n\t\t\t\t\t? 'Some results were omitted; the read is incomplete'\n\t\t\t\t\t: 'Result completeness could not be confirmed',\n\t\t\tkind: 'other',\n\t\t\tisAuth: false,\n\t\t});\n\t}\n\n\treturn { warnings: warnings, fetchFailed: failures.length > 0, truncated: incomplete };\n}\n\n/**\n * Appends neutral warnings derived from SDK metadata and returns its failure/truncation assessment.\n *\n * `reported` says whether the metadata described this read's incompleteness at all. A caller that would\n * otherwise add its own generic \"this was truncated\" warning uses it to stay quiet — the same rule\n * {@link assessCollectionMetadata} applies to its own generic fallback, one layer up: whatever the metadata\n * reported already names this provider and, unless it was that same fallback, says something a consumer can\n * act on. Restating it in a weaker vocabulary is how one gap becomes two warnings with two different verdicts.\n *\n * `truncated` is the near-synonym that must NOT be used for that question: it comes from\n * {@link isIncompleteCollection}, which does not consider failures, so metadata carrying a typed failure\n * alongside `completeness: 'complete'` reports `truncated: false` while having already appended the warning\n * that explains everything. Gating on it would restate exactly the case this field exists to keep quiet.\n */\nexport function mergeAssessmentInto(\n\twarnings: ProviderWarning[],\n\tproviderId: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmetadata: CollectionMetadata | undefined,\n): { fetchFailed: boolean; truncated: boolean; reported: boolean } {\n\tconst assessment = assessCollectionMetadata(providerId, domain, connectionId, metadata);\n\tfor (const warning of assessment.warnings) {\n\t\tappendDedupedWarning(warnings, warning);\n\t}\n\treturn {\n\t\tfetchFailed: assessment.fetchFailed,\n\t\ttruncated: assessment.truncated,\n\t\t// What the assessment PRODUCED, not what `appendDedupedWarning` accepted: a warning dropped as a\n\t\t// duplicate is still one the consumer will see, so it still makes a restatement redundant.\n\t\treported: assessment.warnings.length > 0,\n\t};\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"collectionMetadata.js","names":[],"sources":["../../../../plus/integrations/src/collectionMetadata.ts"],"sourcesContent":["import type {\n\tCollectionMetadata,\n\tCollectionOmission,\n\tCollectionScope,\n\tCollectionScopeFailure,\n} from '@gitkraken/provider-apis';\n// Matched by its `code` discriminator rather than by `instanceof`: the SDK ships one bundle per entry point, so\n// the class reached through the root is not the same object as the one reached through `/providers`.\nimport { isInvalidRequestError, isUnsupportedSortError } from '@gitkraken/provider-apis';\nimport {\n\tAuthenticationError,\n\tRequestClientError,\n\tRequestNotFoundError,\n\tRequestRateLimitError,\n} from '@gitlens/git/errors.js';\nimport type { TokenWithInfo } from './authentication/models.js';\nimport type { IntegrationIds } from './constants.js';\nimport type { ResponseHeaders } from './errors.js';\nimport { getResponseHeader, isProviderUnreachableError, isRateLimitResponse } from './errors.js';\nimport { getProviderResponseBodyMessage } from './providers/providerErrors.js';\nimport type {\n\tProviderWarning,\n\tProviderWarningCause,\n\tProviderWarningOmission,\n\tProviderWarningScope,\n} from './results.js';\nimport { appendDedupedWarning, providerWarningMessage } from './results.js';\n\n/**\n * Re-throws an error that is a fact about the CALL rather than about one scope of a fan-out.\n *\n * Every provider fan-out catches per-scope rejections and records them as {@link CollectionScopeFailure}s, which is\n * right for auth, rate limits and a missing project: those really did happen to one scope, and the siblings that\n * succeeded must survive. A caller-contract error is the opposite — it is identical for every scope and was decided\n * before any request went out — so degrading it would hand back N indistinguishable failures and an empty `partial`\n * page, describing an invalid call as an incomplete read.\n *\n * Today that means the SDK's `UnsupportedSortError` and `InvalidRequestError` — the two codes it lists in its own\n * `CALLER_CONTRACT_ERROR_CODES`, both documented as \"never degraded into a `CollectionScopeFailure` or an\n * omission\". The second covers arguments rejected before the first request goes out: a project key list longer\n * than one Jira search can carry (or an empty one), a non-integer `pageSize`, an unknown current-user filter, or a\n * Jira Server read with no `baseUrl` to address. Called from every fan-out rather than spelled out in each, so a\n * fan-out added later inherits the rule instead of silently omitting it — which is exactly how Azure's\n * account-wide drain came to degrade it while its three siblings did not.\n */\nexport function throwIfCallerContractError(ex: unknown): void {\n\tif (isUnsupportedSortError(ex) || isInvalidRequestError(ex)) throw ex;\n}\n\n/**\n * Whether a failed query that covered several scopes would fail each scope's own read the same way: a rejected or\n * rate-limited token, or a call the SDK refused before sending. Anything else (a query the provider rejects because\n * one scope is gone or no longer visible, a server error) may be one scope's problem, so the scopes are worth\n * reading on their own.\n */\nexport function failsEveryScope(ex: unknown): boolean {\n\tconst kind = toCollectionFailureKind(ex);\n\treturn (\n\t\tkind === 'authentication' || kind === 'rate-limit' || isUnsupportedSortError(ex) || isInvalidRequestError(ex)\n\t);\n}\n\n/**\n * Maps a caught GitLens request error to the SDK collection failure vocabulary used inside provider fan-outs.\n */\nexport function toCollectionFailureKind(ex: unknown): CollectionScopeFailure['kind'] {\n\tif (ex instanceof AuthenticationError) return 'authentication';\n\tif (ex instanceof RequestRateLimitError) return 'rate-limit';\n\tif (ex instanceof RequestNotFoundError) return 'not-found';\n\treturn 'provider';\n}\n\n/**\n * What an authentication refusal's response said, kept on the failure so the provider can name its cause once the\n * credential behind it is confirmed (see `IntegrationBase.confirmScopedAuthFailures`). Only these facts are kept,\n * never the response: its headers carry the provider's session cookies.\n */\nexport interface ProviderRefusal {\n\tstatus: number;\n\t/** The provider's own explanation: Azure DevOps' `X-TFS-ServiceError`, or the error body's message. */\n\tdetail?: string;\n\t/** The provider's error type, when the body names one (Azure DevOps `typeKey`). */\n\ttypeKey?: string;\n\t/**\n\t * The OAuth app the refused token belongs to, when the provider says (GitHub's `X-OAuth-Client-Id`). Not a\n\t * secret: it is the app's public client id, which the page where a user asks an organization to approve the app\n\t * is addressed by.\n\t */\n\toauthClientId?: string;\n}\n\n/** A scope failure as recorded here: the SDK's shape, plus what the refusal said and, once it is known, why. */\nexport interface ProviderScopeFailure extends CollectionScopeFailure {\n\trefusal?: ProviderRefusal;\n\tcause?: ProviderWarningCause;\n\t/** The provider pinned the refusal on the credential itself, so it is published for the connection, unscoped. */\n\tcredentialRefused?: true;\n}\n\n/**\n * Reads the refusal off an `AuthenticationError` that carries the provider's response: the SDK adapter's plain\n * `{ status, headers, body }` (`providersApi.ts`), Octokit's `{ status, headers, data }` (the GitHub client's REST\n * reads), or the `Response` of a `ProviderFetchError`, whose body was already read into its message.\n */\nfunction toProviderRefusal(ex: unknown): ProviderRefusal | undefined {\n\tif (!(ex instanceof AuthenticationError)) return undefined;\n\n\tconst response = (ex.original as { response?: unknown } | undefined)?.response as\n\t\t| { status?: unknown; headers?: ResponseHeaders; body?: unknown; data?: unknown }\n\t\t| undefined;\n\tif (typeof response?.status !== 'number') return undefined;\n\n\t// Only a structured body: a string one is a page (a proxy's, a sign-in form), not an explanation.\n\tconst raw = response.body ?? response.data;\n\tconst body = typeof raw === 'object' && raw != null ? raw : undefined;\n\tconst detail =\n\t\tdecodeServiceError(getResponseHeader(response.headers, 'x-tfs-serviceerror')) ??\n\t\tgetProviderResponseBodyMessage(body);\n\tconst typeKey = body != null && 'typeKey' in body ? body.typeKey : undefined;\n\tconst oauthClientId = getResponseHeader(response.headers, 'x-oauth-client-id');\n\n\treturn {\n\t\tstatus: response.status,\n\t\t...(detail ? { detail: detail } : {}),\n\t\t...(typeof typeKey === 'string' ? { typeKey: typeKey } : {}),\n\t\t...(typeof oauthClientId === 'string' && oauthClientId ? { oauthClientId: oauthClientId } : {}),\n\t};\n}\n\n/** Azure DevOps sends `X-TFS-ServiceError` percent-encoded. */\nfunction decodeServiceError(value: unknown): string | undefined {\n\tif (typeof value !== 'string' || !value) return undefined;\n\n\ttry {\n\t\treturn decodeURIComponent(value).trim() || undefined;\n\t} catch {\n\t\treturn value.trim() || undefined;\n\t}\n}\n\n/**\n * Builds a structured SDK scope failure from a caught GitLens request error.\n *\n * An authentication refusal is described in the provider's own words when it gives any, and otherwise by the\n * error the provider's response raised, sanitized like any warning's (a page becomes its status), rather than by\n * `AuthenticationError`'s generic \"credentials are either invalid or expired\": a scope refusing a sound credential\n * is the case this failure exists to tell apart, and that sentence says the opposite.\n */\nexport function toCollectionScopeFailure(scope: CollectionScopeFailure['scope'], ex: unknown): ProviderScopeFailure {\n\tconst kind = toCollectionFailureKind(ex);\n\tconst refusal = toProviderRefusal(ex);\n\tconst message =\n\t\trefusal != null ? (refusal.detail ?? providerWarningMessage((ex as AuthenticationError).original)) : undefined;\n\treturn {\n\t\tscope: scope,\n\t\tkind: kind,\n\t\t...(message ? { message: message } : ex instanceof Error && ex.message ? { message: ex.message } : {}),\n\t\t...(refusal != null ? { refusal: refusal } : {}),\n\t\t...(kind === 'provider' && isProviderUnreachableError(ex) ? { cause: { reason: 'unreachable' as const } } : {}),\n\t};\n}\n\nfunction collectionFailureKindToWarningKind(kind: CollectionScopeFailure['kind']): ProviderWarning['kind'] {\n\tswitch (kind) {\n\t\tcase 'authentication':\n\t\t\treturn 'auth';\n\t\tcase 'rate-limit':\n\t\t\treturn 'rate-limit';\n\t\tcase 'not-found':\n\t\t\treturn 'not-found';\n\t\tdefault:\n\t\t\treturn 'other';\n\t}\n}\n\nfunction getCollectionFailureStatus(message: string | undefined): number | undefined {\n\tif (message == null) return undefined;\n\n\tconst match = /\\((\\d{3})\\)/.exec(message) ?? /^(?:HTTP\\s+)?(\\d{3})\\b/i.exec(message.trim());\n\treturn match != null ? Number(match[1]) : undefined;\n}\n\nfunction toCollectionFailureWarningKind(failure: CollectionScopeFailure): ProviderWarning['kind'] {\n\tconst mapped = collectionFailureKindToWarningKind(failure.kind);\n\tif (mapped !== 'other' || (failure.kind !== 'provider' && failure.kind !== 'unknown')) return mapped;\n\n\t// provider-apis currently groups several HTTP statuses under `provider` in partial fan-outs. The custom\n\t// fetch adapter retains the status in `message`, so recover the same neutral classification the thrown-error\n\t// path exposes instead of downgrading these structured failures to `other`.\n\tconst status = getCollectionFailureStatus(failure.message);\n\tswitch (status) {\n\t\tcase 401:\n\t\t\treturn 'auth';\n\t\tcase 403:\n\t\t\treturn isRateLimitResponse({ status: status, message: failure.message }) ? 'rate-limit' : 'auth';\n\t\tcase 404:\n\t\tcase 410:\n\t\tcase 422:\n\t\t\treturn 'not-found';\n\t\tcase 429:\n\t\t\treturn 'rate-limit';\n\t\tdefault:\n\t\t\treturn mapped;\n\t}\n}\n\nfunction toCollectionFailureCause(\n\tfailure: ProviderScopeFailure,\n\tkind: ProviderWarning['kind'],\n): ProviderWarningCause | undefined {\n\treturn (\n\t\tfailure.cause ??\n\t\t(kind === 'other' &&\n\t\tisProviderUnreachableError({ ...failure, status: getCollectionFailureStatus(failure.message) })\n\t\t\t? { reason: 'unreachable' }\n\t\t\t: undefined)\n\t);\n}\n\nexport function toCollectionFailureError(failure: ProviderScopeFailure, tokenWithInfo: TokenWithInfo): Error {\n\tconst kind = toCollectionFailureWarningKind(failure);\n\tconst error = new Error(failure.message ?? 'Provider request failed', {\n\t\tcause: toCollectionFailureCause(failure, kind),\n\t});\n\tconst { accessToken, ...tokenInfo } = tokenWithInfo;\n\tif (kind === 'auth') return new AuthenticationError(tokenInfo, error.message, error);\n\tif (kind === 'rate-limit') return new RequestRateLimitError(error, accessToken, undefined);\n\tif (kind === 'not-found') return new RequestNotFoundError(error);\n\n\tconst status = getCollectionFailureStatus(failure.message);\n\tif (kind === 'other' && status != null && status >= 400 && status < 500) {\n\t\treturn new RequestClientError(error);\n\t}\n\n\treturn error;\n}\n\n/** ` (resource r, project p, repository o/n)` for the scope IDs present; empty when the scope names none. */\nfunction collectionScopeText(scope: CollectionScope | undefined): string {\n\tconst parts: string[] = [];\n\tif (scope?.resourceId != null) {\n\t\tparts.push(`resource ${scope.resourceId}`);\n\t}\n\tif (scope?.projectId != null) {\n\t\tparts.push(`project ${scope.projectId}`);\n\t}\n\tif (scope?.repositoryId != null) {\n\t\tparts.push(`repository ${scope.repositoryId}`);\n\t}\n\n\treturn parts.length ? ` (${parts.join(', ')})` : '';\n}\n\n/** The cause in the consumer's terms, for the prose that goes with the structured {@link ProviderWarningCause}. */\nfunction causeText(cause: ProviderWarningCause): string {\n\tswitch (cause.reason) {\n\t\tcase 'oauth-app-not-allowed':\n\t\t\treturn 'the organization does not allow third-party OAuth apps';\n\t\tcase 'access-denied':\n\t\t\treturn 'the account has no access to it';\n\t\tcase 'conditional-access':\n\t\t\treturn 'a Conditional Access policy blocked the request';\n\t\tcase 'unreachable':\n\t\t\treturn 'the provider server could not be reached';\n\t\tdefault:\n\t\t\tcause.reason satisfies never;\n\t\t\treturn 'the provider refused the request';\n\t}\n}\n\nfunction collectionFailureMessage(failure: ProviderScopeFailure): string {\n\tconst text = failure.cause != null ? causeText(failure.cause) : failure.message;\n\tconst detail = text != null ? `: ${text}` : '';\n\treturn `Failed to read ${failure.kind} scope${collectionScopeText(failure.scope)}${detail}`;\n}\n\n/**\n * Forwards the part of a failure's scope below the provider, so a consumer can tell \"one organization refused\n * this token\" from \"the connection's token is dead\" without parsing {@link collectionFailureMessage}.\n *\n * `providerId` is dropped: the warning already names its provider, and a scope naming nothing below it is a\n * failure of the connection itself. That is the distinction `getOrganizationsForUserResult` also draws before\n * counting an auth failure against the connection, though it reads a scope with no keys but `providerId`, so an\n * absent scope does not count there. Forwarding such a scope would make an account-wide failure look confined.\n *\n * Copied field by field, for the reason {@link toProviderWarningOmission} copies its scope.\n */\nfunction toProviderWarningScope(scope: CollectionScope | undefined): ProviderWarningScope | undefined {\n\tif (scope == null) return undefined;\n\n\tconst { resourceId, projectId, repositoryId } = scope;\n\tif (resourceId == null && projectId == null && repositoryId == null) return undefined;\n\n\treturn {\n\t\t...(resourceId != null ? { resourceId: resourceId } : {}),\n\t\t...(projectId != null ? { projectId: projectId } : {}),\n\t\t...(repositoryId != null ? { repositoryId: repositoryId } : {}),\n\t};\n}\n\n/**\n * Whether `metadata` reports authentication failures only for scopes below the provider, with none for the\n * connection itself. A credential revoked since a provider cached its discovery produces exactly this shape,\n * the same one a scope's own refusal does; `IntegrationBase.confirmScopedAuthFailures` tells them apart.\n *\n * Classified exactly as {@link assessCollectionMetadata} classifies the warnings, so it agrees with what a\n * consumer would be shown.\n */\nexport function hasOnlyScopedAuthFailures(metadata: CollectionMetadata | undefined): boolean {\n\tlet scoped = false;\n\tfor (const failure of metadata?.failures ?? []) {\n\t\tif (toCollectionFailureWarningKind(failure) !== 'auth') continue;\n\t\tif (toProviderWarningScope(failure.scope) == null || (failure as ProviderScopeFailure).credentialRefused) {\n\t\t\treturn false;\n\t\t}\n\n\t\tscoped = true;\n\t}\n\treturn scoped;\n}\n\n/** The scoped authentication failures in `metadata` that kept their refusal, with the scope they are forwarded as. */\nfunction* refusedScopes(\n\tmetadata: CollectionMetadata | undefined,\n): Generator<{ failure: ProviderScopeFailure & { refusal: ProviderRefusal }; scope: ProviderWarningScope }> {\n\tfor (const failure of (metadata?.failures ?? []) as ProviderScopeFailure[]) {\n\t\tif (\n\t\t\tfailure.refusal == null ||\n\t\t\tfailure.credentialRefused ||\n\t\t\ttoCollectionFailureWarningKind(failure) !== 'auth'\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst scope = toProviderWarningScope(failure.scope);\n\t\tif (scope == null) continue;\n\n\t\tyield { failure: failure as ProviderScopeFailure & { refusal: ProviderRefusal }, scope: scope };\n\t}\n}\n\n/**\n * Marks each scoped authentication failure in `metadata` whose refusal the provider pins on the credential, so it is\n * published for the connection rather than for its scope. Returns whether any was.\n */\nexport function markCredentialRefusals(\n\tmetadata: CollectionMetadata | undefined,\n\tisCredentialRefusal: (refusal: ProviderRefusal) => boolean,\n): boolean {\n\tlet marked = false;\n\tfor (const { failure } of refusedScopes(metadata)) {\n\t\tif (!isCredentialRefusal(failure.refusal)) continue;\n\n\t\tfailure.credentialRefused = true;\n\t\tmarked = true;\n\t}\n\treturn marked;\n}\n\n/**\n * Names the cause of each scoped authentication failure in `metadata` that kept its refusal. Only for a read whose\n * credential was just confirmed: before that, the refusals this can name look exactly like a dead credential.\n */\nexport function attributeScopedAuthFailures(\n\tmetadata: CollectionMetadata | undefined,\n\tdescribe: (refusal: ProviderRefusal, scope: ProviderWarningScope) => ProviderWarningCause | undefined,\n): void {\n\tfor (const { failure, scope } of refusedScopes(metadata)) {\n\t\tif (failure.cause != null) continue;\n\n\t\tconst cause = describe(failure.refusal, scope);\n\t\tif (cause != null) {\n\t\t\tfailure.cause = cause;\n\t\t}\n\t}\n}\n\n/**\n * Explains one omission in the consumer's terms — what was left out and, where the SDK reports it, how much.\n *\n * An omission is a completeness fact, never a failure: the read succeeded, and the provider (or the SDK's own\n * recovery budget) is what withheld results. That is why these never contribute to `fetchFailed`. Whether a\n * retry would recover anything is a separate question this layer cannot answer — see the `recovery` note on\n * {@link toProviderWarningOmission}.\n */\nfunction collectionOmissionMessage(omission: CollectionOmission): string {\n\tconst scopeText = collectionScopeText(omission.scope);\n\tswitch (omission.kind) {\n\t\tcase 'provider-limit':\n\t\t\t// `totalCount` is the only figure saying how much was withheld; both fields are optional, so degrade\n\t\t\t// through the shapes the SDK can actually emit rather than printing `undefined`.\n\t\t\tif (omission.totalCount != null && omission.limit != null) {\n\t\t\t\treturn `Search${scopeText} matched ${omission.totalCount} results, but the provider exposes at most ${omission.limit}`;\n\t\t\t}\n\t\t\tif (omission.limit != null) {\n\t\t\t\treturn `Search${scopeText} exceeded the provider limit of ${omission.limit} results`;\n\t\t\t}\n\t\t\treturn `Search${scopeText} exceeded the provider's result limit`;\n\t\tcase 'recovery-budget':\n\t\t\treturn `Stopped recovering omitted results${scopeText} after reaching the request budget${\n\t\t\t\tomission.limit != null ? ` of ${omission.limit} requests` : ''\n\t\t\t}`;\n\t\tcase 'pagination-incomplete':\n\t\t\treturn `More results are available${scopeText} than this read returned`;\n\t\tdefault:\n\t\t\t// `CollectionOmissionKind` is a closed union, so this is unreachable today. Kept as a compile-time\n\t\t\t// guard rather than a silent fallback: an SDK bump that adds a kind breaks the build here instead\n\t\t\t// of quietly degrading it to a vague message.\n\t\t\tomission.kind satisfies never;\n\t\t\treturn `Some results were omitted${scopeText}; the read is incomplete`;\n\t}\n}\n\n/**\n * Forwards an SDK omission as the structured signal consumers read instead of parsing {@link\n * collectionOmissionMessage}'s prose.\n *\n * `results.ts` re-spells `CollectionOmissionKind` rather than importing it, so the published warning surface\n * carries no `@gitkraken/provider-apis` types (see the export block in `index.ts`). The return type is what\n * keeps the two unions honest: an SDK bump that adds a member fails to compile here, alongside\n * `collectionOmissionMessage`'s `satisfies never`.\n *\n * `scope` is copied because the SDK's own object is retained and re-merged across drained pages\n * (`providerPaging.ts`), and this one crosses the package boundary.\n *\n * `recovery` is always `'none'`, deliberately, and a scoped `pagination-incomplete` is why it looks wrong: the\n * SDK emits that one shape from situations with opposite remedies and says so itself — \"Either the read\n * deliberately took one page per scope, or the provider advertised another page it gave no way to reach.\" Its\n * `collectAcrossScopes` producers (Azure, Bitbucket, Bitbucket Server) are the first, its `drainAcrossScopes`\n * producers (GitLab, Jira) the second, and those report an omission only when a cursor STALLED — so re-reading\n * that scope stalls at the identical page. Nothing in `CollectionOmission` separates the two, so claiming\n * recoverability would ship the dead-end button `recovery` exists to prevent. Making the recoverable case\n * claimable needs provider-apis to say whether a scope was drained or merely sampled: a fact to forward, not\n * to guess here.\n */\nfunction toProviderWarningOmission(omission: CollectionOmission): ProviderWarningOmission {\n\treturn {\n\t\tkind: omission.kind,\n\t\trecovery: 'none',\n\t\t...(omission.limit != null ? { limit: omission.limit } : {}),\n\t\t// The SDK types this `number | null | undefined`; normalize to one absence for consumers.\n\t\t...(omission.totalCount != null ? { totalCount: omission.totalCount } : {}),\n\t\t...(omission.scope != null ? { scope: { ...omission.scope } } : {}),\n\t};\n}\n\n/**\n * Whether SDK metadata describes a read that may be missing results.\n *\n * A reported omission counts on its own rather than deferring to `completeness`. The SDK does couple the\n * two — every site that emits an omission degrades completeness in the same call — so today the omission\n * clause changes no outcome. It is defensive: the two facts arrive as independent fields, and a read that\n * names what it left out must not be publishable as whole if a future producer sets only one of them.\n *\n * This is the single definition of \"may be missing results\", shared with `getPagedResult`, so one metadata\n * object cannot be truncated at one layer and complete at another.\n */\nexport function isIncompleteCollection(metadata: CollectionMetadata | undefined): boolean {\n\tif (metadata == null) return false;\n\n\treturn metadata.completeness !== 'complete' || (metadata.omissions?.length ?? 0) > 0;\n}\n\n/** Converts internal SDK collection metadata into neutral provider facade signals. */\nexport function assessCollectionMetadata(\n\tproviderId: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmetadata: CollectionMetadata | undefined,\n): { warnings: ProviderWarning[]; fetchFailed: boolean; truncated: boolean } {\n\tif (metadata == null) return { warnings: [], fetchFailed: false, truncated: false };\n\n\tconst warnings: ProviderWarning[] = [];\n\tconst failures: ProviderScopeFailure[] = metadata.failures ?? [];\n\tfor (const failure of failures) {\n\t\tconst kind = toCollectionFailureWarningKind(failure);\n\t\tconst cause = toCollectionFailureCause(failure, kind);\n\t\tconst scope = failure.credentialRefused ? undefined : toProviderWarningScope(failure.scope);\n\t\tappendDedupedWarning(warnings, {\n\t\t\tproviderId: providerId,\n\t\t\tdomain: domain,\n\t\t\tconnectionId: connectionId,\n\t\t\t// A credential-level refusal names no scope in prose either, so every scope's copy of it is one warning.\n\t\t\tmessage: collectionFailureMessage({\n\t\t\t\t...failure,\n\t\t\t\tcause: cause,\n\t\t\t\t...(failure.credentialRefused ? { scope: undefined } : {}),\n\t\t\t}),\n\t\t\tkind: kind,\n\t\t\tisAuth: kind === 'auth',\n\t\t\t...(scope != null ? { scope: scope } : {}),\n\t\t\t// Copied for the reason the scope is: the failure object is retained and re-merged across pages.\n\t\t\t...(cause != null ? { cause: { ...cause } } : {}),\n\t\t});\n\t}\n\n\t// Omissions explain WHY a read is incomplete when nothing failed — a provider cap, an exhausted recovery\n\t// budget, an undrained scope. They classify as `other`, never `auth`, and are deliberately excluded from\n\t// `fetchFailed` below: the request itself succeeded. `omission` carries that same fact structurally, so a\n\t// consumer can act on it without parsing the message.\n\tconst omissions = metadata.omissions ?? [];\n\tfor (const omission of omissions) {\n\t\tappendDedupedWarning(warnings, {\n\t\t\tproviderId: providerId,\n\t\t\tdomain: domain,\n\t\t\tconnectionId: connectionId,\n\t\t\tmessage: collectionOmissionMessage(omission),\n\t\t\tkind: 'other',\n\t\t\tisAuth: false,\n\t\t\tomission: toProviderWarningOmission(omission),\n\t\t});\n\t}\n\n\tconst incomplete = isIncompleteCollection(metadata);\n\t// Only fall back to the generic message when nothing more specific was reported; an omission already\n\t// explains the incompleteness in the consumer's terms, so adding this on top would be noise.\n\t//\n\t// This one deliberately carries no `omission`: it fires precisely when the SDK reported incompleteness\n\t// WITHOUT saying what was left out, so there is no structured fact to forward and synthesizing one would\n\t// assert a specificity this layer does not have.\n\tif (incomplete && failures.length === 0 && omissions.length === 0) {\n\t\tappendDedupedWarning(warnings, {\n\t\t\tproviderId: providerId,\n\t\t\tdomain: domain,\n\t\t\tconnectionId: connectionId,\n\t\t\tmessage:\n\t\t\t\tmetadata.completeness === 'partial'\n\t\t\t\t\t? 'Some results were omitted; the read is incomplete'\n\t\t\t\t\t: 'Result completeness could not be confirmed',\n\t\t\tkind: 'other',\n\t\t\tisAuth: false,\n\t\t});\n\t}\n\n\treturn { warnings: warnings, fetchFailed: failures.length > 0, truncated: incomplete };\n}\n\n/**\n * Appends neutral warnings derived from SDK metadata and returns its failure/truncation assessment.\n *\n * `reported` says whether the metadata described this read's incompleteness at all. A caller that would\n * otherwise add its own generic \"this was truncated\" warning uses it to stay quiet — the same rule\n * {@link assessCollectionMetadata} applies to its own generic fallback, one layer up: whatever the metadata\n * reported already names this provider and, unless it was that same fallback, says something a consumer can\n * act on. Restating it in a weaker vocabulary is how one gap becomes two warnings with two different verdicts.\n *\n * `truncated` is the near-synonym that must NOT be used for that question: it comes from\n * {@link isIncompleteCollection}, which does not consider failures, so metadata carrying a typed failure\n * alongside `completeness: 'complete'` reports `truncated: false` while having already appended the warning\n * that explains everything. Gating on it would restate exactly the case this field exists to keep quiet.\n */\nexport function mergeAssessmentInto(\n\twarnings: ProviderWarning[],\n\tproviderId: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmetadata: CollectionMetadata | undefined,\n): { fetchFailed: boolean; truncated: boolean; reported: boolean } {\n\tconst assessment = assessCollectionMetadata(providerId, domain, connectionId, metadata);\n\tfor (const warning of assessment.warnings) {\n\t\tappendDedupedWarning(warnings, warning);\n\t}\n\treturn {\n\t\tfetchFailed: assessment.fetchFailed,\n\t\ttruncated: assessment.truncated,\n\t\t// What the assessment PRODUCED, not what `appendDedupedWarning` accepted: a warning dropped as a\n\t\t// duplicate is still one the consumer will see, so it still makes a restatement redundant.\n\t\treported: assessment.warnings.length > 0,\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { AuthTokenInfo, AuthenticationError, AuthenticationErrorReason, RequestClientError, RequestNotFoundError, RequestRateLimitError } from "../../git/errors.js";
|
|
2
2
|
//#region ../plus/integrations/src/errors.d.ts
|
|
3
|
+
/** Transport wrappers keep the network failure in `cause` or `original`; inspecting only the wrapper loses it. */
|
|
4
|
+
export declare function isProviderUnreachableError(ex: unknown): boolean;
|
|
3
5
|
/**
|
|
4
6
|
* Thrown by a provider read when it can't produce a result for a reason that is NOT "the account is empty":
|
|
5
7
|
* a prerequisite is missing (e.g. Trello has no app key) or a required scope can't be resolved (e.g. the
|
|
@@ -1,5 +1,49 @@
|
|
|
1
|
+
import { isCancellationError } from "../../utils/cancellation.js";
|
|
1
2
|
import { AuthenticationError, AuthenticationErrorReason, RequestClientError, RequestNotFoundError, RequestRateLimitError } from "../../git/errors.js";
|
|
2
3
|
//#region ../plus/integrations/src/errors.ts
|
|
4
|
+
const unreachableErrorCodes = /* @__PURE__ */ new Set([
|
|
5
|
+
"ECONNREFUSED",
|
|
6
|
+
"ECONNRESET",
|
|
7
|
+
"ECONNABORTED",
|
|
8
|
+
"ENOTFOUND",
|
|
9
|
+
"EAI_AGAIN",
|
|
10
|
+
"ETIMEDOUT",
|
|
11
|
+
"EHOSTUNREACH",
|
|
12
|
+
"ENETUNREACH",
|
|
13
|
+
"EPIPE",
|
|
14
|
+
"ERR_NETWORK",
|
|
15
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
16
|
+
"UND_ERR_HEADERS_TIMEOUT",
|
|
17
|
+
"UND_ERR_BODY_TIMEOUT",
|
|
18
|
+
"UND_ERR_SOCKET",
|
|
19
|
+
"CERT_HAS_EXPIRED",
|
|
20
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
21
|
+
"SELF_SIGNED_CERT_IN_CHAIN",
|
|
22
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
23
|
+
"UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
|
|
24
|
+
"ERR_TLS_CERT_ALTNAME_INVALID"
|
|
25
|
+
]);
|
|
26
|
+
/** Transport wrappers keep the network failure in `cause` or `original`; inspecting only the wrapper loses it. */
|
|
27
|
+
function isProviderUnreachableError(ex) {
|
|
28
|
+
const pending = [ex];
|
|
29
|
+
const seen = /* @__PURE__ */ new Set();
|
|
30
|
+
while (pending.length > 0) {
|
|
31
|
+
const candidate = pending.pop();
|
|
32
|
+
if (candidate == null || typeof candidate !== "object" || seen.has(candidate)) continue;
|
|
33
|
+
seen.add(candidate);
|
|
34
|
+
const error = candidate;
|
|
35
|
+
if (isCancellationError(candidate)) {
|
|
36
|
+
if (error.reason === "aborted") return false;
|
|
37
|
+
if (error.reason === "timeout") return true;
|
|
38
|
+
}
|
|
39
|
+
const status = error.status ?? error.response?.status;
|
|
40
|
+
if (typeof status === "number" && status >= 400) return status >= 500 && status < 600;
|
|
41
|
+
if (error.kind === "network" || error.reason === "unreachable" || error.name === "TimeoutError" || error.name === "AbortError" || error.name === "NetworkError" || typeof error.code === "string" && (unreachableErrorCodes.has(error.code) || error.code.startsWith("ERR_SSL_")) || typeof error.message === "string" && /^(?:fetch failed|failed to fetch|network request failed|load failed|NetworkError when attempting to fetch resource\.?)$/i.test(error.message)) return true;
|
|
42
|
+
pending.push(error.original, error.cause);
|
|
43
|
+
if (Array.isArray(error.errors)) pending.push(...error.errors);
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
3
47
|
/**
|
|
4
48
|
* Thrown by a provider read when it can't produce a result for a reason that is NOT "the account is empty":
|
|
5
49
|
* a prerequisite is missing (e.g. Trello has no app key) or a required scope can't be resolved (e.g. the
|
|
@@ -129,6 +173,6 @@ function toError(ex) {
|
|
|
129
173
|
return ex instanceof Error ? ex : new Error(String(ex));
|
|
130
174
|
}
|
|
131
175
|
//#endregion
|
|
132
|
-
export { AuthenticationError, AuthenticationErrorReason, IntegrationReadUnavailableError, ProviderFetchError, RequestClientError, RequestNotFoundError, RequestRateLimitError, getResponseHeader, isRateLimitResponse, toError, toRateLimitError };
|
|
176
|
+
export { AuthenticationError, AuthenticationErrorReason, IntegrationReadUnavailableError, ProviderFetchError, RequestClientError, RequestNotFoundError, RequestRateLimitError, getResponseHeader, isProviderUnreachableError, isRateLimitResponse, toError, toRateLimitError };
|
|
133
177
|
|
|
134
178
|
//# sourceMappingURL=errors.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","names":[],"sources":["../../../../plus/integrations/src/errors.ts"],"sourcesContent":["export type { AuthTokenInfo } from '@gitlens/git/errors.js';\nexport {\n\tAuthenticationError,\n\tAuthenticationErrorReason,\n\tRequestClientError,\n\tRequestNotFoundError,\n\tRequestRateLimitError,\n} from '@gitlens/git/errors.js';\n\nimport { RequestRateLimitError } from '@gitlens/git/errors.js';\n\n/**\n * Thrown by a provider read when it can't produce a result for a reason that is NOT \"the account is empty\":\n * a prerequisite is missing (e.g. Trello has no app key) or a required scope can't be resolved (e.g. the\n * Linear viewer id, needed to scope \"my issues\"). Returning an empty/undefined result in these cases would be\n * indistinguishable from a genuinely empty account; throwing this lets the result cores recover it into an\n * `{ error }` the facade surfaces as a warning + `fetchFailed`.\n */\nexport class IntegrationReadUnavailableError extends Error {\n\tconstructor(provider: string, reason: string) {\n\t\tsuper(`${provider} read unavailable: ${reason}`);\n\t\tError.captureStackTrace?.(this, new.target);\n\t}\n}\n\n/**\n * Thrown by integration provider HTTP clients when an upstream request fails.\n */\nexport class ProviderFetchError extends Error {\n\tget status(): number {\n\t\treturn this.response.status;\n\t}\n\n\tget statusText(): string {\n\t\treturn this.response.statusText;\n\t}\n\n\t/**\n\t * Builds the error with the upstream body folded into the message, for clients that hold a `Response` whose\n\t * body has not been read (i.e. every non-`ok` path). Use this rather than the constructor whenever the body is\n\t * still available: the status line alone cannot distinguish a throttled request from a permission failure on\n\t * the statuses hosts overload (see {@link isRateLimitResponse}), and only the body says which one it is.\n\t *\n\t * Never throws: the body is best-effort, so an unreadable, empty, or already-consumed one simply yields the\n\t * status-line-only message the constructor would have produced.\n\t */\n\tstatic async fromResponse(provider: string, response: Response): Promise<ProviderFetchError> {\n\t\treturn new ProviderFetchError(provider, response, await readErrorBodyMessage(response));\n\t}\n\n\tconstructor(\n\t\tprovider: string,\n\t\tpublic readonly response: Response,\n\t\terrors?: { message: string }[],\n\t) {\n\t\tsuper(\n\t\t\t`${provider} request failed: ${!response.ok ? `(${response.status}) ${response.statusText}. ` : ''}${\n\t\t\t\terrors?.length ? errors[0].message : ''\n\t\t\t}`,\n\t\t);\n\n\t\tError.captureStackTrace?.(this, new.target);\n\t}\n}\n\n/** Cap on the body text folded into an error message, so an HTML error page can't become the message. */\nconst maxErrorBodyLength = 500;\n\n/**\n * Best-effort human-readable message from an error response body, in the `errors` shape the\n * {@link ProviderFetchError} constructor takes. Tolerant by design — every host in use words its errors\n * differently and some do not send JSON at all:\n * - Azure DevOps — `{ message: \"TF400733: ...\" }`\n * - Bitbucket — `{ error: { message: \"...\" } }`, and bare text for a throttled request\n * - GitLab — `{ message: \"...\" }` or `{ error: \"...\" }`\n * Anything unrecognized falls back to the raw text, which is what the substring check wants anyway.\n */\nasync function readErrorBodyMessage(response: Response): Promise<{ message: string }[] | undefined> {\n\tlet text;\n\ttry {\n\t\t// `bodyUsed` is not a guarantee (a stream can fail mid-read), hence the catch — but it avoids the throw\n\t\t// entirely on the common path where something upstream already consumed the body.\n\t\tif (response.bodyUsed) return undefined;\n\n\t\ttext = (await response.text())?.trim();\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (!text) return undefined;\n\n\tlet message: string | undefined;\n\ttry {\n\t\tconst body: unknown = JSON.parse(text);\n\t\tif (body != null && typeof body === 'object') {\n\t\t\tconst { message: m, error } = body as { message?: unknown; error?: unknown };\n\t\t\tif (typeof m === 'string') {\n\t\t\t\tmessage = m;\n\t\t\t} else if (typeof error === 'string') {\n\t\t\t\tmessage = error;\n\t\t\t} else if (error != null && typeof error === 'object') {\n\t\t\t\t// Some hosts nest it as `{ error: { message } }`; narrow the same way as the outer object.\n\t\t\t\tconst nested = (error as { message?: unknown }).message;\n\t\t\t\tif (typeof nested === 'string') {\n\t\t\t\t\tmessage = nested;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Not JSON — Bitbucket sends bare text for a throttled request, and any host can send an HTML error page.\n\t}\n\n\tmessage = (message ?? text).trim();\n\treturn message ? [{ message: message.slice(0, maxErrorBodyLength) }] : undefined;\n}\n\n/**\n * Whether a provider's error response denotes a rate limit rather than a permission failure. Kept here, in one\n * place, because the distinction is consequential: a rate limit is retryable, while a permission failure is\n * surfaced as `auth` and drives a reconnect prompt — misclassifying one as the other asks the user to\n * re-authenticate a perfectly healthy connection.\n *\n * Which status a throttled request arrives on varies by host, which is why this takes both:\n * - Azure DevOps and Bitbucket Cloud document 429 (learn.microsoft.com .../integrate/concepts/rate-limits).\n * - GitHub documents \"a `403` or `429` response\" for both its primary and secondary limits\n * (docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api).\n * - GitLab uses 403 for some throttles.\n * So 429 alone is decisive, and on 403 the message decides.\n *\n * NOTE the message must actually carry the host's body for the 403 path to work — see\n * {@link ProviderFetchError.fromResponse}. A `ProviderFetchError` built from the status line alone reads\n * \"(403) Forbidden.\" and can never match, which is what made this check dead code on the direct-fetch clients.\n */\nexport function isRateLimitResponse(ex: { status: number; message?: string }): boolean {\n\t// Matches the phrasing hosts actually use: GitHub/Bitbucket \"API rate limit exceeded\", GitLab \"rate limit\n\t// exceeded\", Azure \"Request was blocked due to exceeding usage of resource 'RateLimit'\" / \"TF400733: The\n\t// request has been canceled: Request was blocked due to exceeding usage\".\n\tif (ex.status === 429) return true;\n\n\tconst message = ex.message?.toLowerCase();\n\treturn message != null && (message.includes('rate limit') || message.includes('ratelimit'));\n}\n\n/**\n * A response's headers as either a `fetch` `Headers` instance or the plain object an SDK error carries. Read via\n * duck-typing on `get` rather than `instanceof Headers`, so this works in both the Node and webworker builds\n * without depending on the global being present.\n */\nexport type ResponseHeaders = Pick<Headers, 'get'> | Record<string, unknown>;\n\n/** Reads one header off either shape of {@link ResponseHeaders}; names are the lower-case ones both shapes use. */\nexport function getResponseHeader(headers: ResponseHeaders | undefined, name: string): unknown {\n\tif (headers == null) return undefined;\n\n\treturn typeof headers.get === 'function'\n\t\t? (headers.get as (name: string) => string | null)(name)\n\t\t: (headers as Record<string, unknown>)[name];\n}\n\n/**\n * Builds a {@link RequestRateLimitError} from a provider error response, reading the reset epoch from the\n * standard `x-ratelimit-reset` header when present. Accepts both header shapes in use: the `fetch` `Headers`\n * instance the direct clients get, and the plain object the SDK attaches to its errors.\n */\nexport function toRateLimitError(\n\tex: Error & { response?: { headers?: ResponseHeaders } },\n\ttoken: string | undefined,\n): RequestRateLimitError {\n\tconst raw = getResponseHeader(ex.response?.headers, 'x-ratelimit-reset');\n\n\t// Only a header that actually came back as a string/number can be a reset epoch; anything else (a nested SDK\n\t// object, an array of values) would stringify to garbage and parse to NaN anyway.\n\tlet resetAt: number | undefined;\n\tif (typeof raw === 'string' || typeof raw === 'number') {\n\t\tresetAt = parseInt(String(raw), 10);\n\t\tif (Number.isNaN(resetAt)) {\n\t\t\tresetAt = undefined;\n\t\t}\n\t}\n\n\treturn new RequestRateLimitError(ex, token, resetAt);\n}\n\n/**\n * Normalizes a caught `unknown` into an `Error`. Result cores type their failure channel as `error: Error`,\n * but a `catch` binding is `unknown` and a provider (or a third-party SDK) can throw a non-Error value;\n * returning it raw would leak a non-Error through the public result surface and break downstream `instanceof`\n * classification. Passes an existing `Error` through unchanged and wraps anything else with `String(ex)`.\n */\nexport function toError(ex: unknown): Error {\n\treturn ex instanceof Error ? ex : new Error(String(ex));\n}\n"],"mappings":";;;;;;;;;AAkBA,IAAa,kCAAb,cAAqD,MAAM;CAC1D,YAAY,UAAkB,QAAgB;EAC7C,MAAM,GAAG,SAAS,qBAAqB,QAAQ;EAC/C,MAAM,oBAAoB,MAAM,UAAU;CAC3C;AACD;;;;AAKA,IAAa,qBAAb,MAAa,2BAA2B,MAAM;CAwB5B;CAvBjB,IAAI,SAAiB;EACpB,OAAO,KAAK,SAAS;CACtB;CAEA,IAAI,aAAqB;EACxB,OAAO,KAAK,SAAS;CACtB;;;;;;;;;;CAWA,aAAa,aAAa,UAAkB,UAAiD;EAC5F,OAAO,IAAI,mBAAmB,UAAU,UAAU,MAAM,qBAAqB,QAAQ,CAAC;CACvF;CAEA,YACC,UACA,UACA,QACC;EACD,MACC,GAAG,SAAS,mBAAmB,CAAC,SAAS,KAAK,IAAI,SAAS,OAAO,IAAI,SAAS,WAAW,MAAM,KAC/F,QAAQ,SAAS,OAAO,EAAE,CAAC,UAAU,IAEvC;EAPgB,KAAA,WAAA;EAShB,MAAM,oBAAoB,MAAM,UAAU;CAC3C;AACD;;AAGA,MAAM,qBAAqB;;;;;;;;;;AAW3B,eAAe,qBAAqB,UAAgE;CACnG,IAAI;CACJ,IAAI;EAGH,IAAI,SAAS,UAAU,OAAO,KAAA;EAE9B,QAAQ,MAAM,SAAS,KAAK,EAAA,EAAI,KAAK;CACtC,QAAQ;EACP;CACD;CACA,IAAI,CAAC,MAAM,OAAO,KAAA;CAElB,IAAI;CACJ,IAAI;EACH,MAAM,OAAgB,KAAK,MAAM,IAAI;EACrC,IAAI,QAAQ,QAAQ,OAAO,SAAS,UAAU;GAC7C,MAAM,EAAE,SAAS,GAAG,UAAU;GAC9B,IAAI,OAAO,MAAM,UAChB,UAAU;QACJ,IAAI,OAAO,UAAU,UAC3B,UAAU;QACJ,IAAI,SAAS,QAAQ,OAAO,UAAU,UAAU;IAEtD,MAAM,SAAU,MAAgC;IAChD,IAAI,OAAO,WAAW,UACrB,UAAU;GAEZ;EACD;CACD,QAAQ,CAER;CAEA,WAAW,WAAW,KAAA,CAAM,KAAK;CACjC,OAAO,UAAU,CAAC,EAAE,SAAS,QAAQ,MAAM,GAAG,kBAAkB,EAAE,CAAC,IAAI,KAAA;AACxE;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,oBAAoB,IAAmD;CAItF,IAAI,GAAG,WAAW,KAAK,OAAO;CAE9B,MAAM,UAAU,GAAG,SAAS,YAAY;CACxC,OAAO,WAAW,SAAS,QAAQ,SAAS,YAAY,KAAK,QAAQ,SAAS,WAAW;AAC1F;;AAUA,SAAgB,kBAAkB,SAAsC,MAAuB;CAC9F,IAAI,WAAW,MAAM,OAAO,KAAA;CAE5B,OAAO,OAAO,QAAQ,QAAQ,aAC1B,QAAQ,IAAwC,IAAI,IACpD,QAAoC;AACzC;;;;;;AAOA,SAAgB,iBACf,IACA,OACwB;CACxB,MAAM,MAAM,kBAAkB,GAAG,UAAU,SAAS,mBAAmB;CAIvE,IAAI;CACJ,IAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,UAAU;EACvD,UAAU,SAAS,OAAO,GAAG,GAAG,EAAE;EAClC,IAAI,OAAO,MAAM,OAAO,GACvB,UAAU,KAAA;CAEZ;CAEA,OAAO,IAAI,sBAAsB,IAAI,OAAO,OAAO;AACpD;;;;;;;AAQA,SAAgB,QAAQ,IAAoB;CAC3C,OAAO,cAAc,QAAQ,KAAK,IAAI,MAAM,OAAO,EAAE,CAAC;AACvD"}
|
|
1
|
+
{"version":3,"file":"errors.js","names":[],"sources":["../../../../plus/integrations/src/errors.ts"],"sourcesContent":["export type { AuthTokenInfo } from '@gitlens/git/errors.js';\nexport {\n\tAuthenticationError,\n\tAuthenticationErrorReason,\n\tRequestClientError,\n\tRequestNotFoundError,\n\tRequestRateLimitError,\n} from '@gitlens/git/errors.js';\n\nimport { RequestRateLimitError } from '@gitlens/git/errors.js';\nimport { isCancellationError } from '@gitlens/utils/cancellation.js';\n\nconst unreachableErrorCodes = new Set([\n\t'ECONNREFUSED',\n\t'ECONNRESET',\n\t'ECONNABORTED',\n\t'ENOTFOUND',\n\t'EAI_AGAIN',\n\t'ETIMEDOUT',\n\t'EHOSTUNREACH',\n\t'ENETUNREACH',\n\t'EPIPE',\n\t'ERR_NETWORK',\n\t'UND_ERR_CONNECT_TIMEOUT',\n\t'UND_ERR_HEADERS_TIMEOUT',\n\t'UND_ERR_BODY_TIMEOUT',\n\t'UND_ERR_SOCKET',\n\t'CERT_HAS_EXPIRED',\n\t'DEPTH_ZERO_SELF_SIGNED_CERT',\n\t'SELF_SIGNED_CERT_IN_CHAIN',\n\t'UNABLE_TO_VERIFY_LEAF_SIGNATURE',\n\t'UNABLE_TO_GET_ISSUER_CERT_LOCALLY',\n\t'ERR_TLS_CERT_ALTNAME_INVALID',\n]);\n\n/** Transport wrappers keep the network failure in `cause` or `original`; inspecting only the wrapper loses it. */\nexport function isProviderUnreachableError(ex: unknown): boolean {\n\tconst pending = [ex];\n\tconst seen = new Set<unknown>();\n\twhile (pending.length > 0) {\n\t\tconst candidate = pending.pop();\n\t\tif (candidate == null || typeof candidate !== 'object' || seen.has(candidate)) continue;\n\n\t\tseen.add(candidate);\n\t\tconst error = candidate as {\n\t\t\tname?: string;\n\t\t\tkind?: string;\n\t\t\tmessage?: string;\n\t\t\tcode?: string;\n\t\t\tstatus?: number;\n\t\t\tresponse?: { status?: number };\n\t\t\treason?: string;\n\t\t\toriginal?: unknown;\n\t\t\tcause?: unknown;\n\t\t\terrors?: unknown[];\n\t\t};\n\t\tif (isCancellationError(candidate)) {\n\t\t\tif (error.reason === 'aborted') return false;\n\t\t\tif (error.reason === 'timeout') return true;\n\t\t}\n\n\t\tconst status = error.status ?? error.response?.status;\n\t\tif (typeof status === 'number' && status >= 400) return status >= 500 && status < 600;\n\n\t\tif (\n\t\t\terror.kind === 'network' ||\n\t\t\terror.reason === 'unreachable' ||\n\t\t\terror.name === 'TimeoutError' ||\n\t\t\terror.name === 'AbortError' ||\n\t\t\terror.name === 'NetworkError' ||\n\t\t\t(typeof error.code === 'string' &&\n\t\t\t\t(unreachableErrorCodes.has(error.code) || error.code.startsWith('ERR_SSL_'))) ||\n\t\t\t(typeof error.message === 'string' &&\n\t\t\t\t/^(?:fetch failed|failed to fetch|network request failed|load failed|NetworkError when attempting to fetch resource\\.?)$/i.test(\n\t\t\t\t\terror.message,\n\t\t\t\t))\n\t\t) {\n\t\t\treturn true;\n\t\t}\n\n\t\tpending.push(error.original, error.cause);\n\t\tif (Array.isArray(error.errors)) {\n\t\t\tpending.push(...error.errors);\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * Thrown by a provider read when it can't produce a result for a reason that is NOT \"the account is empty\":\n * a prerequisite is missing (e.g. Trello has no app key) or a required scope can't be resolved (e.g. the\n * Linear viewer id, needed to scope \"my issues\"). Returning an empty/undefined result in these cases would be\n * indistinguishable from a genuinely empty account; throwing this lets the result cores recover it into an\n * `{ error }` the facade surfaces as a warning + `fetchFailed`.\n */\nexport class IntegrationReadUnavailableError extends Error {\n\tconstructor(provider: string, reason: string) {\n\t\tsuper(`${provider} read unavailable: ${reason}`);\n\t\tError.captureStackTrace?.(this, new.target);\n\t}\n}\n\n/**\n * Thrown by integration provider HTTP clients when an upstream request fails.\n */\nexport class ProviderFetchError extends Error {\n\tget status(): number {\n\t\treturn this.response.status;\n\t}\n\n\tget statusText(): string {\n\t\treturn this.response.statusText;\n\t}\n\n\t/**\n\t * Builds the error with the upstream body folded into the message, for clients that hold a `Response` whose\n\t * body has not been read (i.e. every non-`ok` path). Use this rather than the constructor whenever the body is\n\t * still available: the status line alone cannot distinguish a throttled request from a permission failure on\n\t * the statuses hosts overload (see {@link isRateLimitResponse}), and only the body says which one it is.\n\t *\n\t * Never throws: the body is best-effort, so an unreadable, empty, or already-consumed one simply yields the\n\t * status-line-only message the constructor would have produced.\n\t */\n\tstatic async fromResponse(provider: string, response: Response): Promise<ProviderFetchError> {\n\t\treturn new ProviderFetchError(provider, response, await readErrorBodyMessage(response));\n\t}\n\n\tconstructor(\n\t\tprovider: string,\n\t\tpublic readonly response: Response,\n\t\terrors?: { message: string }[],\n\t) {\n\t\tsuper(\n\t\t\t`${provider} request failed: ${!response.ok ? `(${response.status}) ${response.statusText}. ` : ''}${\n\t\t\t\terrors?.length ? errors[0].message : ''\n\t\t\t}`,\n\t\t);\n\n\t\tError.captureStackTrace?.(this, new.target);\n\t}\n}\n\n/** Cap on the body text folded into an error message, so an HTML error page can't become the message. */\nconst maxErrorBodyLength = 500;\n\n/**\n * Best-effort human-readable message from an error response body, in the `errors` shape the\n * {@link ProviderFetchError} constructor takes. Tolerant by design — every host in use words its errors\n * differently and some do not send JSON at all:\n * - Azure DevOps — `{ message: \"TF400733: ...\" }`\n * - Bitbucket — `{ error: { message: \"...\" } }`, and bare text for a throttled request\n * - GitLab — `{ message: \"...\" }` or `{ error: \"...\" }`\n * Anything unrecognized falls back to the raw text, which is what the substring check wants anyway.\n */\nasync function readErrorBodyMessage(response: Response): Promise<{ message: string }[] | undefined> {\n\tlet text;\n\ttry {\n\t\t// `bodyUsed` is not a guarantee (a stream can fail mid-read), hence the catch — but it avoids the throw\n\t\t// entirely on the common path where something upstream already consumed the body.\n\t\tif (response.bodyUsed) return undefined;\n\n\t\ttext = (await response.text())?.trim();\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (!text) return undefined;\n\n\tlet message: string | undefined;\n\ttry {\n\t\tconst body: unknown = JSON.parse(text);\n\t\tif (body != null && typeof body === 'object') {\n\t\t\tconst { message: m, error } = body as { message?: unknown; error?: unknown };\n\t\t\tif (typeof m === 'string') {\n\t\t\t\tmessage = m;\n\t\t\t} else if (typeof error === 'string') {\n\t\t\t\tmessage = error;\n\t\t\t} else if (error != null && typeof error === 'object') {\n\t\t\t\t// Some hosts nest it as `{ error: { message } }`; narrow the same way as the outer object.\n\t\t\t\tconst nested = (error as { message?: unknown }).message;\n\t\t\t\tif (typeof nested === 'string') {\n\t\t\t\t\tmessage = nested;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Not JSON — Bitbucket sends bare text for a throttled request, and any host can send an HTML error page.\n\t}\n\n\tmessage = (message ?? text).trim();\n\treturn message ? [{ message: message.slice(0, maxErrorBodyLength) }] : undefined;\n}\n\n/**\n * Whether a provider's error response denotes a rate limit rather than a permission failure. Kept here, in one\n * place, because the distinction is consequential: a rate limit is retryable, while a permission failure is\n * surfaced as `auth` and drives a reconnect prompt — misclassifying one as the other asks the user to\n * re-authenticate a perfectly healthy connection.\n *\n * Which status a throttled request arrives on varies by host, which is why this takes both:\n * - Azure DevOps and Bitbucket Cloud document 429 (learn.microsoft.com .../integrate/concepts/rate-limits).\n * - GitHub documents \"a `403` or `429` response\" for both its primary and secondary limits\n * (docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api).\n * - GitLab uses 403 for some throttles.\n * So 429 alone is decisive, and on 403 the message decides.\n *\n * NOTE the message must actually carry the host's body for the 403 path to work — see\n * {@link ProviderFetchError.fromResponse}. A `ProviderFetchError` built from the status line alone reads\n * \"(403) Forbidden.\" and can never match, which is what made this check dead code on the direct-fetch clients.\n */\nexport function isRateLimitResponse(ex: { status: number; message?: string }): boolean {\n\t// Matches the phrasing hosts actually use: GitHub/Bitbucket \"API rate limit exceeded\", GitLab \"rate limit\n\t// exceeded\", Azure \"Request was blocked due to exceeding usage of resource 'RateLimit'\" / \"TF400733: The\n\t// request has been canceled: Request was blocked due to exceeding usage\".\n\tif (ex.status === 429) return true;\n\n\tconst message = ex.message?.toLowerCase();\n\treturn message != null && (message.includes('rate limit') || message.includes('ratelimit'));\n}\n\n/**\n * A response's headers as either a `fetch` `Headers` instance or the plain object an SDK error carries. Read via\n * duck-typing on `get` rather than `instanceof Headers`, so this works in both the Node and webworker builds\n * without depending on the global being present.\n */\nexport type ResponseHeaders = Pick<Headers, 'get'> | Record<string, unknown>;\n\n/** Reads one header off either shape of {@link ResponseHeaders}; names are the lower-case ones both shapes use. */\nexport function getResponseHeader(headers: ResponseHeaders | undefined, name: string): unknown {\n\tif (headers == null) return undefined;\n\n\treturn typeof headers.get === 'function'\n\t\t? (headers.get as (name: string) => string | null)(name)\n\t\t: (headers as Record<string, unknown>)[name];\n}\n\n/**\n * Builds a {@link RequestRateLimitError} from a provider error response, reading the reset epoch from the\n * standard `x-ratelimit-reset` header when present. Accepts both header shapes in use: the `fetch` `Headers`\n * instance the direct clients get, and the plain object the SDK attaches to its errors.\n */\nexport function toRateLimitError(\n\tex: Error & { response?: { headers?: ResponseHeaders } },\n\ttoken: string | undefined,\n): RequestRateLimitError {\n\tconst raw = getResponseHeader(ex.response?.headers, 'x-ratelimit-reset');\n\n\t// Only a header that actually came back as a string/number can be a reset epoch; anything else (a nested SDK\n\t// object, an array of values) would stringify to garbage and parse to NaN anyway.\n\tlet resetAt: number | undefined;\n\tif (typeof raw === 'string' || typeof raw === 'number') {\n\t\tresetAt = parseInt(String(raw), 10);\n\t\tif (Number.isNaN(resetAt)) {\n\t\t\tresetAt = undefined;\n\t\t}\n\t}\n\n\treturn new RequestRateLimitError(ex, token, resetAt);\n}\n\n/**\n * Normalizes a caught `unknown` into an `Error`. Result cores type their failure channel as `error: Error`,\n * but a `catch` binding is `unknown` and a provider (or a third-party SDK) can throw a non-Error value;\n * returning it raw would leak a non-Error through the public result surface and break downstream `instanceof`\n * classification. Passes an existing `Error` through unchanged and wraps anything else with `String(ex)`.\n */\nexport function toError(ex: unknown): Error {\n\treturn ex instanceof Error ? ex : new Error(String(ex));\n}\n"],"mappings":"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"}
|
|
@@ -315,7 +315,7 @@ var IntegrationService = class {
|
|
|
315
315
|
async getMyIssues(integrationIds, options) {
|
|
316
316
|
const openRemotesByIntegrationId = /* @__PURE__ */ new Map();
|
|
317
317
|
let hasOpenAzureRepository = false;
|
|
318
|
-
for (const remote of await this.ctx.repositories.getOpenRemotes()) {
|
|
318
|
+
if (options?.openRepositoriesOnly) for (const remote of await this.ctx.repositories.getOpenRemotes()) {
|
|
319
319
|
const integrationId = getIntegrationIdForRemote(remote.provider);
|
|
320
320
|
if (integrationId == null) continue;
|
|
321
321
|
if (integrationId === "azureDevOps") hasOpenAzureRepository = true;
|
|
@@ -342,7 +342,7 @@ var IntegrationService = class {
|
|
|
342
342
|
...Object.values(IssuesSelfManagedHostIntegrationId),
|
|
343
343
|
...Object.values(GitSelfManagedHostIntegrationId)
|
|
344
344
|
]) {
|
|
345
|
-
if (integrationId === "azureDevOps" && !hasOpenAzureRepository) continue;
|
|
345
|
+
if (options?.openRepositoriesOnly && integrationId === "azureDevOps" && !hasOpenAzureRepository) continue;
|
|
346
346
|
const integrationsForId = await this.getIntegrationsForAccountWideRead(integrationId);
|
|
347
347
|
for (const integration of integrationsForId) {
|
|
348
348
|
const openRemotes = this.getOpenRemotesForHost(integration, integrationsForId, openRemotesByIntegrationId.get(integrationId));
|