@gitkraken/core-gitlens 0.7.0 → 0.7.2
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 +28 -2
- package/dist/git/remotes/azure-devops.js +3 -3
- package/dist/git/remotes/azure-devops.js.map +1 -1
- package/dist/plus/integrations/authentication/configuredIntegrationService.js +9 -2
- package/dist/plus/integrations/authentication/configuredIntegrationService.js.map +1 -1
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js +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 +3 -3
- package/dist/plus/integrations/index.js.map +1 -1
- package/dist/plus/integrations/integrationService.d.ts +35 -1
- package/dist/plus/integrations/integrationService.js +90 -43
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/lite.js +5 -4
- package/dist/plus/integrations/lite.js.map +1 -1
- package/dist/plus/integrations/manager.d.ts +68 -23
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +31 -15
- package/dist/plus/integrations/models/gitHostIntegration.js +71 -22
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/integration.d.ts +70 -12
- package/dist/plus/integrations/models/integration.js +56 -1
- package/dist/plus/integrations/models/integration.js.map +1 -1
- package/dist/plus/integrations/models/issueReads.d.ts +17 -0
- package/dist/plus/integrations/models/issuesIntegration.js +2 -0
- package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
- package/dist/plus/integrations/models/pullRequestReads.d.ts +26 -0
- package/dist/plus/integrations/providers/azure/azure.d.ts +66 -1
- package/dist/plus/integrations/providers/azure/azure.js +192 -12
- package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
- package/dist/plus/integrations/providers/azure/search.d.ts +144 -0
- package/dist/plus/integrations/providers/azure/search.js +250 -0
- package/dist/plus/integrations/providers/azure/search.js.map +1 -0
- package/dist/plus/integrations/providers/azureDevOps.d.ts +73 -36
- package/dist/plus/integrations/providers/azureDevOps.js +191 -122
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.d.ts +176 -0
- package/dist/plus/integrations/providers/azureDevOpsServer.js +560 -0
- package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -0
- package/dist/plus/integrations/providers/bitbucket-server/discovery.d.ts +12 -0
- package/dist/plus/integrations/providers/bitbucket-server/discovery.js +128 -0
- package/dist/plus/integrations/providers/bitbucket-server/discovery.js.map +1 -0
- package/dist/plus/integrations/providers/bitbucket-server/models.d.ts +10 -1
- package/dist/plus/integrations/providers/bitbucket-server/models.js +3 -2
- package/dist/plus/integrations/providers/bitbucket-server/models.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.d.ts +58 -0
- package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js +485 -0
- package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js.map +1 -0
- package/dist/plus/integrations/providers/bitbucket-server.d.ts +61 -3
- package/dist/plus/integrations/providers/bitbucket-server.js +114 -20
- package/dist/plus/integrations/providers/bitbucket-server.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket.d.ts +9 -1
- package/dist/plus/integrations/providers/bitbucket.js +13 -2
- package/dist/plus/integrations/providers/bitbucket.js.map +1 -1
- package/dist/plus/integrations/providers/github.d.ts +6 -6
- package/dist/plus/integrations/providers/github.js +35 -34
- package/dist/plus/integrations/providers/github.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab.d.ts +6 -4
- package/dist/plus/integrations/providers/gitlab.js +32 -28
- package/dist/plus/integrations/providers/gitlab.js.map +1 -1
- package/dist/plus/integrations/providers/issueSorts.d.ts +1 -0
- package/dist/plus/integrations/providers/jira-server.d.ts +9 -0
- package/dist/plus/integrations/providers/jira-server.js +15 -7
- 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 +14 -2
- package/dist/plus/integrations/providers/jira.js.map +1 -1
- package/dist/plus/integrations/providers/linear.js +2 -2
- package/dist/plus/integrations/providers/models.d.ts +10 -1
- package/dist/plus/integrations/providers/models.js +86 -0
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/providers/providersApi.d.ts +10 -0
- package/dist/plus/integrations/providers/providersApi.js +18 -3
- package/dist/plus/integrations/providers/providersApi.js.map +1 -1
- package/dist/plus/integrations/providers/utils.d.ts +12 -1
- package/dist/plus/integrations/providers/utils.js +36 -6
- package/dist/plus/integrations/providers/utils.js.map +1 -1
- package/dist/plus/integrations/reads/broaden.js +6 -6
- package/dist/plus/integrations/reads/broaden.js.map +1 -1
- package/dist/plus/integrations/reads/context.d.ts +1 -1
- package/dist/plus/integrations/reads/counts.d.ts +18 -4
- package/dist/plus/integrations/reads/counts.js +66 -15
- package/dist/plus/integrations/reads/counts.js.map +1 -1
- package/dist/plus/integrations/reads/cursors.js +1 -1
- package/dist/plus/integrations/reads/drains.d.ts +2 -1
- package/dist/plus/integrations/reads/drains.js +1 -1
- package/dist/plus/integrations/reads/filters.d.ts +1 -1
- package/dist/plus/integrations/reads/filters.js +17 -12
- package/dist/plus/integrations/reads/filters.js.map +1 -1
- package/dist/plus/integrations/reads/hierarchy.js +2 -2
- package/dist/plus/integrations/reads/hierarchy.js.map +1 -1
- package/dist/plus/integrations/reads/hierarchy.utils.js +1 -1
- package/dist/plus/integrations/reads/issueBatch.js +1 -1
- package/dist/plus/integrations/reads/issueTracker.js +2 -2
- package/dist/plus/integrations/reads/issues.js +2 -2
- package/dist/plus/integrations/reads/paging.js +1 -1
- package/dist/plus/integrations/reads/resolveRepository.d.ts +12 -11
- package/dist/plus/integrations/reads/resolveRepository.js +110 -19
- package/dist/plus/integrations/reads/resolveRepository.js.map +1 -1
- package/dist/plus/integrations/reads/searchIssues.js +4 -2
- package/dist/plus/integrations/reads/searchIssues.js.map +1 -1
- package/dist/plus/integrations/reads/searchPullRequests.js +9 -3
- package/dist/plus/integrations/reads/searchPullRequests.js.map +1 -1
- package/dist/plus/integrations/reads/sweeps.js +1 -1
- package/dist/plus/integrations/reads/trackerIssue.d.ts +9 -5
- package/dist/plus/integrations/reads/trackerIssue.js +15 -9
- package/dist/plus/integrations/reads/trackerIssue.js.map +1 -1
- package/dist/plus/integrations/reads/warnings.d.ts +6 -2
- package/dist/plus/integrations/reads/warnings.js +15 -19
- package/dist/plus/integrations/reads/warnings.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +84 -6
- package/dist/plus/integrations/results.js +9 -2
- package/dist/plus/integrations/results.js.map +1 -1
- package/dist/plus/integrations/utils/domain.utils.d.ts +19 -0
- package/dist/plus/integrations/utils/domain.utils.js +36 -1
- package/dist/plus/integrations/utils/domain.utils.js.map +1 -1
- package/dist/plus/integrations/utils/integration.utils.d.ts +1 -0
- package/dist/plus/integrations/utils/integration.utils.js +12 -1
- package/dist/plus/integrations/utils/integration.utils.js.map +1 -1
- package/docs/integrations.md +263 -74
- package/docs/kepler-read-api-parity.md +19 -6
- package/package.json +1 -1
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import "../constants.js";
|
|
2
|
-
import {
|
|
2
|
+
import { areDomainsOnSameHost, hostFromDomain } from "../utils/domain.utils.js";
|
|
3
|
+
import { isIssuesHostIntegrationId, isIssuesSelfManagedHostIntegrationId } from "../utils/integration.utils.js";
|
|
3
4
|
import { issueTrackerOnlySurfaceWarning, otherWarning } from "./warnings.js";
|
|
4
5
|
import { runCaptured } from "./drains.js";
|
|
5
6
|
import { isIssuesIntegration } from "../models/issuesIntegration.js";
|
|
6
7
|
//#region ../plus/integrations/src/reads/trackerIssue.ts
|
|
7
8
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* `domain` selects the host of a self-managed tracker (Jira Data Center), as it does for
|
|
10
|
+
* `listIssueTrackerIssuesPage`, and is ignored for the cloud trackers, which have a single canonical host.
|
|
11
|
+
*
|
|
12
|
+
* Unlike its siblings, this read never falls back to the primary connection for a self-managed tracker: it
|
|
13
|
+
* requires a `domain` or a `connectionId` with a configured host and refuses otherwise. Two self-hosted
|
|
14
|
+
* instances routinely issue the same project and issue keys, and this read's `issue: undefined` is a proven
|
|
15
|
+
* absence a caller may cache — an answer from whichever host happens to be primary would be cached under a key
|
|
16
|
+
* that names a different instance (#5872).
|
|
13
17
|
*/
|
|
14
18
|
async function getTrackerIssue(ctx, options) {
|
|
15
19
|
const refused = (warning) => ({
|
|
@@ -23,9 +27,10 @@ async function getTrackerIssue(ctx, options) {
|
|
|
23
27
|
if (options.key.trim().length === 0) return refused(otherWarning(options.providerId, void 0, options.connectionId, `${surface} requires an issue key.`));
|
|
24
28
|
const resourceUrl = options.resourceUrl?.trim() || void 0;
|
|
25
29
|
if (options.providerId === "jira" && resourceUrl == null) return refused(otherWarning(options.providerId, void 0, options.connectionId, `${surface} requires the Jira resource URL so the result contains a browser link without resource discovery.`));
|
|
26
|
-
|
|
30
|
+
if (isIssuesSelfManagedHostIntegrationId(options.providerId) && (options.domain != null ? hostFromDomain(options.domain) == null : options.connectionId == null || !ctx.getConfigured(options.providerId).some((c) => c.id === options.connectionId && hostFromDomain(c.domain) != null))) return refused(otherWarning(options.providerId, void 0, options.connectionId, `${surface} requires a domain or a configured connection id for '${options.providerId}': every configured host can hold a different issue under the same key, so the read does not answer from the primary connection.`));
|
|
31
|
+
const integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);
|
|
27
32
|
if (integration == null) {
|
|
28
|
-
const early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId);
|
|
33
|
+
const early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);
|
|
29
34
|
return {
|
|
30
35
|
items: [],
|
|
31
36
|
warnings: early.warnings,
|
|
@@ -34,7 +39,8 @@ async function getTrackerIssue(ctx, options) {
|
|
|
34
39
|
}
|
|
35
40
|
if (!isIssuesIntegration(integration)) return refused(issueTrackerOnlySurfaceWarning(options.providerId, options.connectionId, surface));
|
|
36
41
|
if (!integration.supportsIssueLookupByResourceId) return refused(otherWarning(options.providerId, void 0, options.connectionId, `${surface} is not supported by '${options.providerId}'; its single-issue read cannot prove an absence, so a miss would not be safe to cache.`));
|
|
37
|
-
|
|
42
|
+
if (isIssuesSelfManagedHostIntegrationId(options.providerId) && !areDomainsOnSameHost(options.resourceId, integration.domain)) return refused(otherWarning(options.providerId, void 0, options.connectionId, `${surface} requires the resource id of '${options.providerId}' to name the host the read resolved to.`));
|
|
43
|
+
const domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);
|
|
38
44
|
const issue = await runCaptured(options.providerId, domain, options.connectionId, () => integration.getIssueByResourceIdResult(options.resourceId, options.key, {
|
|
39
45
|
connectionId: options.connectionId,
|
|
40
46
|
resourceUrl
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trackerIssue.js","names":[],"sources":["../../../../../plus/integrations/src/reads/trackerIssue.ts"],"sourcesContent":["import type { IssueShape } from '@gitlens/git/models/issue.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { IssuesCloudHostIntegrationId } from '../constants.js';\nimport { isIssuesIntegration } from '../models/issuesIntegration.js';\nimport type { ProviderResult, ProviderWarning } from '../results.js';\nimport { isIssuesHostIntegrationId } from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { runCaptured } from './drains.js';\nimport { issueTrackerOnlySurfaceWarning, otherWarning } from './warnings.js';\n\nexport interface TrackerIssueResult {\n\tkey: string;\n\t/**\n\t * The resolved issue, or `undefined` when it provably does not exist or is not visible to this connection.\n\t * A failed read returns no item and sets `fetchFailed`.\n\t */\n\tissue?: IssueShape;\n}\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"trackerIssue.js","names":[],"sources":["../../../../../plus/integrations/src/reads/trackerIssue.ts"],"sourcesContent":["import type { IssueShape } from '@gitlens/git/models/issue.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { IssuesCloudHostIntegrationId } from '../constants.js';\nimport { isIssuesIntegration } from '../models/issuesIntegration.js';\nimport type { ProviderResult, ProviderWarning } from '../results.js';\nimport { areDomainsOnSameHost, hostFromDomain } from '../utils/domain.utils.js';\nimport { isIssuesHostIntegrationId, isIssuesSelfManagedHostIntegrationId } from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { runCaptured } from './drains.js';\nimport { issueTrackerOnlySurfaceWarning, otherWarning } from './warnings.js';\n\nexport interface TrackerIssueResult {\n\tkey: string;\n\t/**\n\t * The resolved issue, or `undefined` when it provably does not exist or is not visible to this connection.\n\t * A failed read returns no item and sets `fetchFailed`.\n\t */\n\tissue?: IssueShape;\n}\n\n/**\n * `domain` selects the host of a self-managed tracker (Jira Data Center), as it does for\n * `listIssueTrackerIssuesPage`, and is ignored for the cloud trackers, which have a single canonical host.\n *\n * Unlike its siblings, this read never falls back to the primary connection for a self-managed tracker: it\n * requires a `domain` or a `connectionId` with a configured host and refuses otherwise. Two self-hosted\n * instances routinely issue the same project and issue keys, and this read's `issue: undefined` is a proven\n * absence a caller may cache — an answer from whichever host happens to be primary would be cached under a key\n * that names a different instance (#5872).\n */\nexport async function getTrackerIssue(\n\tctx: ProviderReadContext,\n\toptions: {\n\t\tproviderId: IntegrationIds;\n\t\tresourceId: string;\n\t\tresourceUrl?: string;\n\t\tkey: string;\n\t\tconnectionId?: string;\n\t\tdomain?: string;\n\t},\n): Promise<ProviderResult<TrackerIssueResult>> {\n\tconst refused = (warning: ProviderWarning): ProviderResult<TrackerIssueResult> => ({\n\t\titems: [],\n\t\twarnings: [warning],\n\t\tfetchFailed: true,\n\t});\n\tconst surface = 'Issue resolution by key';\n\n\tif (!isIssuesHostIntegrationId(options.providerId)) {\n\t\treturn refused(issueTrackerOnlySurfaceWarning(options.providerId, options.connectionId, surface));\n\t}\n\n\tif (options.resourceId.trim().length === 0) {\n\t\treturn refused(\n\t\t\totherWarning(options.providerId, undefined, options.connectionId, `${surface} requires a resource id.`),\n\t\t);\n\t}\n\n\tif (options.key.trim().length === 0) {\n\t\treturn refused(\n\t\t\totherWarning(options.providerId, undefined, options.connectionId, `${surface} requires an issue key.`),\n\t\t);\n\t}\n\n\tconst resourceUrl = options.resourceUrl?.trim() || undefined;\n\tif (options.providerId === IssuesCloudHostIntegrationId.Jira && resourceUrl == null) {\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tundefined,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`${surface} requires the Jira resource URL so the result contains a browser link without resource discovery.`,\n\t\t\t),\n\t\t);\n\t}\n\n\t// A `domain` only selects a host when it parses to one, and a `connectionId` only when it names a configured\n\t// connection that has one; anything else would resolve the primary host instead, which is the fallback this\n\t// read refuses.\n\tif (\n\t\tisIssuesSelfManagedHostIntegrationId(options.providerId) &&\n\t\t(options.domain != null\n\t\t\t? hostFromDomain(options.domain) == null\n\t\t\t: options.connectionId == null ||\n\t\t\t\t!ctx\n\t\t\t\t\t.getConfigured(options.providerId)\n\t\t\t\t\t.some(c => c.id === options.connectionId && hostFromDomain(c.domain) != null))\n\t) {\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tundefined,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`${surface} requires a domain or a configured connection id for '${options.providerId}': every configured host can hold a different issue under the same key, so the read does not answer from the primary connection.`,\n\t\t\t),\n\t\t);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\t// A supplied connectionId or domain that no longer resolves is a broken target, not an empty account.\n\t\tconst early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);\n\t\treturn { items: [], warnings: early.warnings, fetchFailed: early.fetchFailed || undefined };\n\t}\n\tif (!isIssuesIntegration(integration)) {\n\t\treturn refused(issueTrackerOnlySurfaceWarning(options.providerId, options.connectionId, surface));\n\t}\n\tif (!integration.supportsIssueLookupByResourceId) {\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tundefined,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`${surface} is not supported by '${options.providerId}'; its single-issue read cannot prove an absence, so a miss would not be safe to cache.`,\n\t\t\t),\n\t\t);\n\t}\n\n\t// A self-managed tracker's single resource is its host, and the read is keyed (and cached) by `resourceId`\n\t// while it is addressed to the host `domain`/`connectionId` resolved. When they disagree, host B's answer —\n\t// including a cacheable absence — would be stored under host A's key.\n\tif (\n\t\tisIssuesSelfManagedHostIntegrationId(options.providerId) &&\n\t\t!areDomainsOnSameHost(options.resourceId, integration.domain)\n\t) {\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tundefined,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`${surface} requires the resource id of '${options.providerId}' to name the host the read resolved to.`,\n\t\t\t),\n\t\t);\n\t}\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\tconst issue = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\t() =>\n\t\t\tintegration.getIssueByResourceIdResult(options.resourceId, options.key, {\n\t\t\t\tconnectionId: options.connectionId,\n\t\t\t\tresourceUrl: resourceUrl,\n\t\t\t}),\n\t\t{ warnOnMissingSession: true },\n\t);\n\tif (issue.warning != null) {\n\t\treturn refused(issue.warning);\n\t}\n\n\treturn {\n\t\titems: [{ key: options.key, ...(issue.value != null ? { issue: issue.value } : {}) }],\n\t\twarnings: [],\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA8BA,eAAsB,gBACrB,KACA,SAQ8C;CAC9C,MAAM,WAAW,aAAkE;EAClF,OAAO,CAAC;EACR,UAAU,CAAC,OAAO;EAClB,aAAa;CACd;CACA,MAAM,UAAU;CAEhB,IAAI,CAAC,0BAA0B,QAAQ,UAAU,GAChD,OAAO,QAAQ,+BAA+B,QAAQ,YAAY,QAAQ,cAAc,OAAO,CAAC;CAGjG,IAAI,QAAQ,WAAW,KAAK,CAAC,CAAC,WAAW,GACxC,OAAO,QACN,aAAa,QAAQ,YAAY,KAAA,GAAW,QAAQ,cAAc,GAAG,QAAQ,yBAAyB,CACvG;CAGD,IAAI,QAAQ,IAAI,KAAK,CAAC,CAAC,WAAW,GACjC,OAAO,QACN,aAAa,QAAQ,YAAY,KAAA,GAAW,QAAQ,cAAc,GAAG,QAAQ,wBAAwB,CACtG;CAGD,MAAM,cAAc,QAAQ,aAAa,KAAK,KAAK,KAAA;CACnD,IAAI,QAAQ,eAAA,UAAoD,eAAe,MAC9E,OAAO,QACN,aACC,QAAQ,YACR,KAAA,GACA,QAAQ,cACR,GAAG,QAAQ,kGACZ,CACD;CAMD,IACC,qCAAqC,QAAQ,UAAU,MACtD,QAAQ,UAAU,OAChB,eAAe,QAAQ,MAAM,KAAK,OAClC,QAAQ,gBAAgB,QACzB,CAAC,IACC,cAAc,QAAQ,UAAU,CAAC,CACjC,MAAK,MAAK,EAAE,OAAO,QAAQ,gBAAgB,eAAe,EAAE,MAAM,KAAK,IAAI,IAE/E,OAAO,QACN,aACC,QAAQ,YACR,KAAA,GACA,QAAQ,cACR,GAAG,QAAQ,wDAAwD,QAAQ,WAAW,iIACvF,CACD;CAGD,MAAM,cAAc,MAAM,IAAI,sBAAsB,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;CAC5G,IAAI,eAAe,MAAM;EAExB,MAAM,QAAQ,IAAI,8BAA8B,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;EACxG,OAAO;GAAE,OAAO,CAAC;GAAG,UAAU,MAAM;GAAU,aAAa,MAAM,eAAe,KAAA;EAAU;CAC3F;CACA,IAAI,CAAC,oBAAoB,WAAW,GACnC,OAAO,QAAQ,+BAA+B,QAAQ,YAAY,QAAQ,cAAc,OAAO,CAAC;CAEjG,IAAI,CAAC,YAAY,iCAChB,OAAO,QACN,aACC,QAAQ,YACR,KAAA,GACA,QAAQ,cACR,GAAG,QAAQ,wBAAwB,QAAQ,WAAW,wFACvD,CACD;CAMD,IACC,qCAAqC,QAAQ,UAAU,KACvD,CAAC,qBAAqB,QAAQ,YAAY,YAAY,MAAM,GAE5D,OAAO,QACN,aACC,QAAQ,YACR,KAAA,GACA,QAAQ,cACR,GAAG,QAAQ,gCAAgC,QAAQ,WAAW,yCAC/D,CACD;CAGD,MAAM,SAAS,IAAI,cAAc,aAAa,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;CACtG,MAAM,QAAQ,MAAM,YACnB,QAAQ,YACR,QACA,QAAQ,oBAEP,YAAY,2BAA2B,QAAQ,YAAY,QAAQ,KAAK;EACvE,cAAc,QAAQ;EACT;CACd,CAAC,GACF,EAAE,sBAAsB,KAAK,CAC9B;CACA,IAAI,MAAM,WAAW,MACpB,OAAO,QAAQ,MAAM,OAAO;CAG7B,OAAO;EACN,OAAO,CAAC;GAAE,KAAK,QAAQ;GAAK,GAAI,MAAM,SAAS,OAAO,EAAE,OAAO,MAAM,MAAM,IAAI,CAAC;EAAG,CAAC;EACpF,UAAU,CAAC;CACZ;AACD"}
|
|
@@ -145,9 +145,13 @@ export declare function unmergeableIssueSortWarning(id: IntegrationIds, domain:
|
|
|
145
145
|
* Returns `undefined` when the provider declares no ceiling, so the caller falls back to the generic wording
|
|
146
146
|
* rather than reporting a limit it invented.
|
|
147
147
|
*/
|
|
148
|
-
export declare function issueSearchCapResultWarning(id: IntegrationIds, domain: string | undefined, connectionId: string | undefined, totalCount: number | undefined, sort: IssueSorting
|
|
148
|
+
export declare function issueSearchCapResultWarning(id: IntegrationIds, domain: string | undefined, connectionId: string | undefined, totalCount: number | undefined, sort: IssueSorting,
|
|
149
|
+
/** The provider proved the ceiling was reached without reporting how many matched; see `limitReached`. */
|
|
150
|
+
limitReached?: boolean): ProviderWarning | undefined;
|
|
149
151
|
/** Quantified omission for a pull-request search that exceeded the provider's reachable result window. */
|
|
150
|
-
export declare function pullRequestSearchCapResultWarning(id: IntegrationIds, domain: string | undefined, connectionId: string | undefined, totalCount: number | undefined, sort: PullRequestSorting
|
|
152
|
+
export declare function pullRequestSearchCapResultWarning(id: IntegrationIds, domain: string | undefined, connectionId: string | undefined, totalCount: number | undefined, sort: PullRequestSorting,
|
|
153
|
+
/** See {@link issueSearchCapResultWarning}. */
|
|
154
|
+
limitReached?: boolean): ProviderWarning | undefined;
|
|
151
155
|
/** Warning for a git host that doesn't expose issues on this surface (e.g. Bitbucket, deprecated in favor of Jira). */
|
|
152
156
|
export declare function issuesUnsupportedWarning(id: IntegrationIds, domain: string | undefined, connectionId: string | undefined): ProviderWarning;
|
|
153
157
|
//#endregion
|
|
@@ -270,37 +270,33 @@ function unmergeableIssueSortWarning(id, domain, connectionId, unmergeable, scop
|
|
|
270
270
|
* Returns `undefined` when the provider declares no ceiling, so the caller falls back to the generic wording
|
|
271
271
|
* rather than reporting a limit it invented.
|
|
272
272
|
*/
|
|
273
|
-
function issueSearchCapResultWarning(id, domain, connectionId, totalCount, sort) {
|
|
274
|
-
|
|
273
|
+
function issueSearchCapResultWarning(id, domain, connectionId, totalCount, sort, limitReached) {
|
|
274
|
+
return searchCapResultWarning(id, domain, connectionId, "Issue search", providersMetadata[id]?.issueSearchResultLimit, totalCount, sort, limitReached);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* The shared body of the two ceiling warnings. Only a ceiling that was actually reached warns: either a reported
|
|
278
|
+
* total above it, or a provider that proved the ceiling was reached but can't count past it — in which case the
|
|
279
|
+
* omission carries the limit and the order but no `totalCount`, since any figure would be invented.
|
|
280
|
+
*/
|
|
281
|
+
function searchCapResultWarning(id, domain, connectionId, readKind, limit, totalCount, sort, limitReached) {
|
|
275
282
|
if (limit == null) return void 0;
|
|
276
|
-
|
|
283
|
+
const counted = totalCount != null && totalCount > limit;
|
|
284
|
+
if (!counted && limitReached !== true) return void 0;
|
|
277
285
|
const [field, direction] = sort.split(":");
|
|
278
286
|
return {
|
|
279
|
-
...otherWarning(id, domain, connectionId,
|
|
287
|
+
...otherWarning(id, domain, connectionId, `${readKind} ${counted ? `matched ${totalCount} results` : `matched more than ${limit} results`}, but '${id}' serves at most ${limit}, ordered by ${field} ${direction}ending; narrow the search to read the rest.`),
|
|
280
288
|
omission: {
|
|
281
289
|
kind: "provider-limit",
|
|
282
290
|
recovery: "none",
|
|
283
291
|
limit,
|
|
284
|
-
totalCount,
|
|
292
|
+
...counted ? { totalCount } : {},
|
|
285
293
|
sort
|
|
286
294
|
}
|
|
287
295
|
};
|
|
288
296
|
}
|
|
289
297
|
/** Quantified omission for a pull-request search that exceeded the provider's reachable result window. */
|
|
290
|
-
function pullRequestSearchCapResultWarning(id, domain, connectionId, totalCount, sort) {
|
|
291
|
-
|
|
292
|
-
if (limit == null || totalCount == null || totalCount <= limit) return void 0;
|
|
293
|
-
const [field, direction] = sort.split(":");
|
|
294
|
-
return {
|
|
295
|
-
...otherWarning(id, domain, connectionId, `Pull request search matched ${totalCount} results, but '${id}' serves at most ${limit}, ordered by ${field} ${direction}ending; narrow the search to read the rest.`),
|
|
296
|
-
omission: {
|
|
297
|
-
kind: "provider-limit",
|
|
298
|
-
recovery: "none",
|
|
299
|
-
limit,
|
|
300
|
-
totalCount,
|
|
301
|
-
sort
|
|
302
|
-
}
|
|
303
|
-
};
|
|
298
|
+
function pullRequestSearchCapResultWarning(id, domain, connectionId, totalCount, sort, limitReached) {
|
|
299
|
+
return searchCapResultWarning(id, domain, connectionId, "Pull request search", providersMetadata[id]?.pullRequestSearchResultLimit, totalCount, sort, limitReached);
|
|
304
300
|
}
|
|
305
301
|
/** Warning for a git host that doesn't expose issues on this surface (e.g. Bitbucket, deprecated in favor of Jira). */
|
|
306
302
|
function issuesUnsupportedWarning(id, domain, connectionId) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"warnings.js","names":[],"sources":["../../../../../plus/integrations/src/reads/warnings.ts"],"sourcesContent":["import type { IntegrationIds } from '../constants.js';\nimport type {\n\tIssueFilter,\n\tIssueSearchCapabilities,\n\tIssueSorting,\n\tPullRequestFilter,\n\tPullRequestSearchCapabilities,\n\tPullRequestSorting,\n} from '../providerFilters.js';\nimport { providersMetadata } from '../providers/models.js';\nimport type { ProviderWarning, ProviderWarningOmissionRecovery } from '../results.js';\nimport type { IssueSearchCriteriaRejection, PullRequestSearchCriteriaRejection } from './filters.js';\nimport type { UnmergeableIssueSort, UnsupportedIssueSortRejection } from './ordering.js';\n\n/**\n * The warnings the provider facade raises on its OWN terms, as opposed to the ones derived from a caught\n * provider error ({@link toProviderWarning}) or from SDK collection metadata (`collectionMetadata.ts`).\n *\n * These are pure message builders, kept together and out of the service so a read method reads as the decision\n * it makes (\"this surface doesn't apply, refuse it\") rather than as the wording of the refusal, and so the same\n * refusal can't be phrased two ways in two reads.\n */\n\n/**\n * The single builder for a provider-neutral, non-auth warning: an unsupported capability, a\n * contradictory/inexpressible request, or a read that couldn't confirm completeness. Every `kind: 'other'`\n * warning the facade raises on its own terms goes through here (directly, or via one of the named builders\n * below that pin a recurring message), so the discriminant is assigned in one place. The warnings mapped from\n * SDK collection metadata are the documented exception — `collectionMetadata.ts` builds those from the\n * structured failure/omission it is given.\n *\n * The kinds that carry a programmatic remedy — `auth`, `rate-limit`, `not-found`, `no-connection` — are derived\n * from the caught error's type instead and never come from here; see `ProviderWarningKind` and\n * {@link noConnectionWarning}.\n */\nexport function otherWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmessage: string,\n): ProviderWarning {\n\treturn {\n\t\tproviderId: id,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage: message,\n\t\tkind: 'other',\n\t\tisAuth: false,\n\t};\n}\n\n/**\n * Builds a `no-connection` warning for a per-connection read that resolved neither a session nor an error: the\n * requested `connectionId` no longer resolves (deleted, or its authentication is invalid). Consumers use this\n * to tell a truly empty account apart from a broken connection.\n */\nexport function noConnectionWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId?: string,\n): ProviderWarning {\n\treturn {\n\t\tproviderId: id,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage:\n\t\t\tconnectionId != null\n\t\t\t\t? `Connection '${connectionId}' for '${id}' could not be resolved (deleted or invalid authentication).`\n\t\t\t\t: `No active connection for '${id}' could be resolved.`,\n\t\tkind: 'no-connection',\n\t\tisAuth: false,\n\t};\n}\n\n/** Warning for an issue-tracker provider asked for a surface only a git host serves (repos, PRs, sweeps). */\nexport function gitHostOnlySurfaceWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tsurface: string,\n): ProviderWarning {\n\treturn otherWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`${surface} is not supported by '${id}'; use a git-host integration instead.`,\n\t);\n}\n\n/** Warning for a git host asked for a surface only an issue tracker serves (resource → project issue reads). */\nexport function issueTrackerOnlySurfaceWarning(\n\tid: IntegrationIds,\n\tconnectionId: string | undefined,\n\tsurface: string,\n): ProviderWarning {\n\treturn otherWarning(\n\t\tid,\n\t\tundefined,\n\t\tconnectionId,\n\t\t`${surface} is not supported by '${id}'; use an issue-tracker integration instead.`,\n\t);\n}\n\n/** Why a facade-driven read returned fewer results than requested. */\nexport type IncompleteReadCause = 'interrupted' | 'page-budget' | 'exhausted' | 'scope-too-large';\n\nfunction recoveryForIncompleteReadCause(cause: IncompleteReadCause): ProviderWarningOmissionRecovery {\n\tswitch (cause) {\n\t\tcase 'page-budget':\n\t\t\treturn 'page-budget';\n\t\tcase 'scope-too-large':\n\t\t\treturn 'narrow-scope';\n\t\tcase 'interrupted':\n\t\tcase 'exhausted':\n\t\t\treturn 'none';\n\t\tdefault:\n\t\t\tcause satisfies never;\n\t\t\treturn 'none';\n\t}\n}\n\n/**\n * Builds the warning for a read that returned less than everything, carrying the two facts a consumer cannot\n * recover on its own: did the request succeed, and would anything fetch the rest?\n *\n * Every incompleteness warning the facade raises on its own terms goes through here, so no read can drift\n * into claiming success on a failure or offering a \"load more\" that cannot deliver. The omission it attaches\n * is the same shape `assessCollectionMetadata` derives from SDK metadata, so a consumer never has to care\n * which layer noticed the gap.\n *\n * No `scope`: this applies to the whole read, not to one repository or project the way an SDK-attributed\n * omission can, so a consumer acting on `recovery` re-runs the read itself.\n */\nexport function incompleteReadWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmessage: string,\n\tcause: IncompleteReadCause,\n): ProviderWarning {\n\tconst warning = otherWarning(id, domain, connectionId, message);\n\tif (cause === 'interrupted') return warning;\n\n\treturn {\n\t\t...warning,\n\t\tomission: {\n\t\t\tkind: 'pagination-incomplete',\n\t\t\trecovery: recoveryForIncompleteReadCause(cause),\n\t\t},\n\t};\n}\n\n/**\n * The read surfaces {@link truncationWarning} phrases for. `'Account-wide issue search'` names the composite\n * read that spans several provider searches rather than one surface.\n *\n * Deliberately not exported: every caller passes a literal, and publishing it would invite a consumer to switch\n * on a set that exists only to word a message.\n */\ntype TruncatedReadKind =\n\t| 'Pull request'\n\t| 'Issue'\n\t| 'Repository'\n\t| 'Organization'\n\t| 'Project'\n\t| 'Account-wide issue search'\n\t| 'Issue search'\n\t| 'Pull request search';\n\n/** {@link incompleteReadWarning} for a paged or drained read, phrased per surface. */\nexport function truncationWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\treadKind: TruncatedReadKind,\n\t/**\n\t * Deliberately REQUIRED and not defaulted: a default would make one of these the silent fallback at any\n\t * call site that forgot it, and both of the claims it carries — \"the request succeeded\" and \"more can be\n\t * fetched\" — are ones this warning must never make by accident.\n\t */\n\tcause: IncompleteReadCause,\n): ProviderWarning {\n\treturn incompleteReadWarning(id, domain, connectionId, truncationMessage(id, readKind, cause), cause);\n}\n\nfunction truncationMessage(id: IntegrationIds, readKind: TruncatedReadKind, cause: IncompleteReadCause): string {\n\tswitch (cause) {\n\t\tcase 'interrupted':\n\t\t\t// Deliberately does not name a mechanism. This fires both when a page was lost mid-drain and when a\n\t\t\t// drain that latched an earlier scope failure later stopped for its own reasons; \"was interrupted\"\n\t\t\t// would be false in the second. What is true in both is that something failed and results are gone.\n\t\t\treturn `${readKind} read for '${id}' did not complete; some results are missing and the read reported a failure.`;\n\t\tcase 'page-budget':\n\t\t\treturn `${readKind} read for '${id}' stopped at its page budget; more results can be read by raising it.`;\n\t\tcase 'exhausted':\n\t\t\treturn `${readKind} read for '${id}' was truncated and cannot be continued; results may be incomplete.`;\n\t\tcase 'scope-too-large':\n\t\t\t// Says what is true — the scope is larger than a read of it — rather than blaming the provider for a\n\t\t\t// ceiling that is ours, and names the remedy that exists, since no retry or budget has one.\n\t\t\treturn `${readKind} read for '${id}' covers more than one read can return; narrow the scope to read the rest.`;\n\t}\n\t// No `default`: `IncompleteReadCause` is declared in this file, so `noImplicitReturns` already fails the\n\t// build here if a cause is added without its own wording. (`collectionOmissionMessage`'s `satisfies never`\n\t// is not the same case — that union is the SDK's, and can widen under a dependency bump.)\n}\n\n/**\n * The one sentence every \"you asked for a narrowing this provider can't express\" refusal uses.\n *\n * Shared because the three callers below differ only in a noun: the wording is consumer-visible, and three\n * hand-written copies drift three ways — including the `(supported: …)` clause, which exists to avoid emitting an\n * empty parenthetical and has to be the same guard in all of them.\n */\nfunction unsupportedNarrowingMessage(id: IntegrationIds, noun: string, requested: string, supported: string): string {\n\treturn `The requested ${noun} (${requested}) are not supported by '${id}'${supported.length > 0 ? ` (supported: ${supported})` : ''}; skipped to avoid returning a wider result than requested.`;\n}\n\n/** Warning for an account-wide issue read whose requested filters the provider can't express server-side. */\nexport function unsupportedAccountWideIssueFiltersWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tfilters: IssueFilter[],\n): ProviderWarning {\n\tconst supported = providersMetadata[id]?.supportedAccountWideIssueFilters ?? [];\n\treturn otherWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tunsupportedNarrowingMessage(id, 'account-wide issue filters', filters.join(', '), supported.join(', ')),\n\t);\n}\n\n/** Warning for an account-wide PR read whose requested relationship union cannot be expressed exactly. */\nexport function unsupportedAccountWidePullRequestFiltersWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tfilters: PullRequestFilter[],\n): ProviderWarning {\n\tconst supported = providersMetadata[id]?.supportedAccountWidePullRequestFilters ?? [];\n\treturn otherWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tunsupportedNarrowingMessage(id, 'account-wide pull request filters', filters.join(', '), supported.join(', ')),\n\t);\n}\n\n/** Warning for a repo-scoped PR read whose requested filters the provider supports none of. */\nexport function unsupportedFiltersWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n): ProviderWarning {\n\treturn otherWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`The requested pull request filters are not supported by '${id}'; skipped to avoid returning unfiltered results.`,\n\t);\n}\n\n/**\n * The one sentence every `unusable-scope` refusal uses — the five surfaces that validate a search scope (both\n * searches, both count probes, and issue broadening) differ only in the noun and in whether they name a count\n * key, so the reason a scope is unusable is written once. `prefix` is what the count probes put in front of it\n * to name their key.\n *\n * NAMES the offending values, because which failure mode applied is not deducible from the value: a quote makes\n * `git\"kraken` a request for the real and different org `gitkraken`, a space makes `my org` a search of `my`\n * filtered by the text `org`, and a value of only quotes or whitespace leaves no scope at all. The caller is the\n * only layer that knows which scope it meant.\n *\n * Says how to fix it rather than what was stripped: a consumer cannot mirror this rule (unlike free-text\n * sanitizing, whose rules are published for exactly that), so \"pass the scope as the provider names it\" is the\n * actionable half.\n */\nexport function unusableSearchScopeMessage(noun: string, scopes: readonly string[], prefix?: string): string {\n\treturn `${prefix != null ? `${prefix}: ` : ''}The requested ${noun} (${scopes.map(s => JSON.stringify(s)).join(', ')}) cannot be used as given: a scope name cannot contain quotes, or an inner space or control character, which a query would drop or read as a second qualifier — searching a different scope, or none. Pass the scope exactly as the provider names it.`;\n}\n\n/** Warning for a filtered pull-request search the provider cannot run as requested. */\nexport function unsupportedPullRequestSearchCriteriaWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\trejection: PullRequestSearchCriteriaRejection,\n): ProviderWarning {\n\tlet message: string;\n\tswitch (rejection.reason) {\n\t\tcase 'unsupported-search':\n\t\t\tmessage = `Filtered pull request search is not supported by '${id}'; use \\`listPullRequestsPage\\` for its own pull request reads.`;\n\t\t\tbreak;\n\t\tcase 'unsupported-criteria': {\n\t\t\tconst supported = providersMetadata[id]?.supportedPullRequestSearch;\n\t\t\tmessage = unsupportedNarrowingMessage(\n\t\t\t\tid,\n\t\t\t\t'pull request search criteria',\n\t\t\t\trejection.criteria.join(', '),\n\t\t\t\tsupported != null ? describePullRequestSearchCapabilities(supported) : '',\n\t\t\t);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn otherWarning(id, domain, connectionId, message);\n}\n\nfunction describePullRequestSearchCapabilities(capabilities: PullRequestSearchCapabilities): string {\n\treturn [\n\t\t...(capabilities.relationships.length ? [`relationships:${capabilities.relationships.join('|')}`] : []),\n\t\t...(capabilities.states.length ? [`states:${capabilities.states.join('|')}`] : []),\n\t\t...(capabilities.text ? ['text'] : []),\n\t\t...(capabilities.updatedAfter ? ['updatedAfter'] : []),\n\t\t...(capabilities.createdAfter ? ['createdAfter'] : []),\n\t\t...(capabilities.includeArchived ? ['includeArchived'] : []),\n\t\t...(capabilities.draft ? ['draft'] : []),\n\t\t...(capabilities.repositoryScope ? ['repository scope'] : []),\n\t\t...(capabilities.organizationScope ? ['organization scope'] : []),\n\t\t...(capabilities.sorts.length ? [`sorts:${capabilities.sorts.join('|')}`] : []),\n\t].join(', ');\n}\n\n/**\n * Warning for a filtered issue search whose criteria the provider can't serve — either it has no such search at\n * all, or it can't express what was asked for, or the set contradicts itself.\n *\n * One builder for all three because they are one decision from the caller's side (\"this search can't be run as\n * asked\") and the remedy differs only in what the message says. The rejection shape carries which case it is, so\n * the wording can't drift from the check that produced it (see {@link resolveIssueSearchCriteria}).\n */\nexport function unsupportedIssueSearchCriteriaWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\trejection: IssueSearchCriteriaRejection,\n): ProviderWarning {\n\tlet message: string;\n\tswitch (rejection.reason) {\n\t\tcase 'unsupported-search':\n\t\t\tmessage = `Filtered issue search is not supported by '${id}'; use \\`listIssuesPage\\` for its own issue reads.`;\n\t\t\tbreak;\n\t\tcase 'unsupported-criteria': {\n\t\t\tconst supported = providersMetadata[id]?.supportedIssueSearch;\n\t\t\tmessage = unsupportedNarrowingMessage(\n\t\t\t\tid,\n\t\t\t\t'issue search criteria',\n\t\t\t\trejection.criteria.join(', '),\n\t\t\t\tsupported != null ? describeIssueSearchCapabilities(supported) : '',\n\t\t\t);\n\t\t\tbreak;\n\t\t}\n\t\tcase 'contradictory-relationships':\n\t\t\tmessage = `The relationships \\`any-assignee\\` and \\`unassigned\\` are contradictory — they partition the scope between them, so no issue satisfies both; pass only one.`;\n\t\t\tbreak;\n\t}\n\t// No `default`: the union is declared alongside the validator, so `noImplicitReturns` fails the build here if\n\t// a rejection reason is added without its own wording.\n\n\treturn otherWarning(id, domain, connectionId, message);\n}\n\n/** The criteria a provider CAN express, for the \"supported: …\" half of a refusal message. */\nfunction describeIssueSearchCapabilities(capabilities: IssueSearchCapabilities): string {\n\tconst flags: [name: string, supported: boolean][] = [\n\t\t['text', capabilities.text],\n\t\t['labels', capabilities.labels],\n\t\t['milestone', capabilities.milestone],\n\t\t['updatedAfter', capabilities.updatedAfter],\n\t\t['createdAfter', capabilities.createdAfter],\n\t\t['withoutLinkedPullRequest', capabilities.withoutLinkedPullRequest],\n\t\t['state', capabilities.states],\n\t];\n\treturn [\n\t\t...capabilities.relationships.map(r => `relationships:${r}`),\n\t\t...flags.filter(([, supported]) => supported).map(([name]) => name),\n\t\t...capabilities.sorts.map(s => `sort:${s}`),\n\t].join(', ');\n}\n\n/**\n * Warning for an issue read asked to order by a key its provider can't express server-side.\n *\n * Separate from {@link unsupportedIssueSearchCriteriaWarning} because it serves the reads that take `sort` as an\n * OPTION rather than as a criterion — repo-scoped, account-wide, and issue-tracker — where there is no criteria\n * set to fold it into. The filtered search keeps reporting it as one more `unsupported-criteria` entry, so a\n * caller there still gets a single refusal listing everything at once.\n *\n * Takes the rejection the check produced rather than a re-read supported list: which of the two tables applies is\n * the caller's own branch (`repos` present or not), so reading it back here — or threading it by hand — would be\n * free to disagree with the check that refused.\n */\nexport function unsupportedIssueSortWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\trejection: UnsupportedIssueSortRejection,\n): ProviderWarning {\n\treturn otherWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tunsupportedNarrowingMessage(id, 'issue sort', rejection.requested, (rejection.supported ?? []).join(', ')),\n\t);\n}\n\n/**\n * Warning for a merged issue read asked to order by a key that no normalized issue carries.\n *\n * The distinction this exists to make is not about the provider: the provider orders by `priority` perfectly well\n * within ONE project. It is about the read, which fans out over several and merges the results here, where the\n * only fields available are the ones {@link IssueShape} models. Serving the concatenated per-scope runs would look\n * ordered without being so, which is why this refuses instead — and why the message says WHY, since the same key\n * against the same provider succeeds on a single-scope read.\n *\n * What a read fans out OVER differs by read, hence the `scope`: repositories, projects, or — where one query is\n * issued per requested relationship and the runs are unioned within a single project (Jira) — the filters\n * themselves. Naming the wrong one would tell a caller to narrow something that isn't what merged.\n */\nexport function unmergeableIssueSortWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tunmergeable: UnmergeableIssueSort,\n\tscope: 'repositories' | 'projects' | 'filters',\n): ProviderWarning {\n\t// Both forms are spelled out rather than depluralized at runtime: the message needs each, and\n\t// `'repositories'.replace(/s$/, '')` is `'repositorie'`.\n\tconst nouns = {\n\t\trepositories: { many: 'repositories', one: 'repository' },\n\t\tprojects: { many: 'projects', one: 'project' },\n\t\tfilters: { many: 'issue relationships', one: 'relationship' },\n\t}[scope];\n\treturn otherWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`Cannot order by '${unmergeable.requested}' across several ${nouns.many}: this read merges their results, and a normalized issue carries no such field to merge on. Read one ${nouns.one} at a time, or order by a date, title, comment or reaction count.`,\n\t);\n}\n\n/**\n * The omission for a filtered issue search that hit the provider's RESULT CEILING: more issues matched than the\n * provider will ever serve for one query, however it is paged.\n *\n * Distinct from {@link truncationWarning} because this is the one incompleteness this read can quantify —\n * `totalCount` is how many matched, `limit` how many are reachable — which is what lets a consumer say \"19.240\n * matched, showing the 1.000 most recently updated\" instead of a bare \"results may be incomplete\". `recovery:\n * 'none'` because the items past the ceiling are UNREACHABLE, not merely unfetched: no budget, no retry, and no\n * cursor would return them. Narrowing the criteria is the only way through, which is a decision for the caller.\n *\n * The message names the ORDER, and has to now that ordering is an option: the reachable window is the top `limit`\n * under that key, so \"the 1.000 most recent\" is a true sentence only while the key is the default one. The same\n * value goes on the omission, so a consumer wording its own sentence doesn't have to remember what it asked for.\n *\n * Returns `undefined` when the provider declares no ceiling, so the caller falls back to the generic wording\n * rather than reporting a limit it invented.\n */\nexport function issueSearchCapResultWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\ttotalCount: number | undefined,\n\tsort: IssueSorting,\n): ProviderWarning | undefined {\n\tconst limit = providersMetadata[id]?.issueSearchResultLimit;\n\tif (limit == null) return undefined;\n\t// Below the ceiling this warning would be a false claim; the caller's truncation had another cause.\n\tif (totalCount == null || totalCount <= limit) return undefined;\n\n\tconst [field, direction] = sort.split(':');\n\treturn {\n\t\t...otherWarning(\n\t\t\tid,\n\t\t\tdomain,\n\t\t\tconnectionId,\n\t\t\t`Issue search matched ${totalCount} results, but '${id}' serves at most ${limit}, ordered by ${field} ${direction}ending; narrow the search to read the rest.`,\n\t\t),\n\t\tomission: {\n\t\t\tkind: 'provider-limit',\n\t\t\t// The items past the ceiling can't be fetched by anything, so never offer a \"load more\".\n\t\t\trecovery: 'none',\n\t\t\tlimit: limit,\n\t\t\ttotalCount: totalCount,\n\t\t\tsort: sort,\n\t\t},\n\t};\n}\n\n/** Quantified omission for a pull-request search that exceeded the provider's reachable result window. */\nexport function pullRequestSearchCapResultWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\ttotalCount: number | undefined,\n\tsort: PullRequestSorting,\n): ProviderWarning | undefined {\n\tconst limit = providersMetadata[id]?.pullRequestSearchResultLimit;\n\tif (limit == null || totalCount == null || totalCount <= limit) return undefined;\n\n\t// Name the order: the reachable window is the first `limit` under THIS sort, and a different key reaches a\n\t// different subset, so a ceiling message that omits it can't say which slice was served. Same reasoning as\n\t// `issueSearchCapResultWarning`.\n\tconst [field, direction] = sort.split(':');\n\treturn {\n\t\t...otherWarning(\n\t\t\tid,\n\t\t\tdomain,\n\t\t\tconnectionId,\n\t\t\t`Pull request search matched ${totalCount} results, but '${id}' serves at most ${limit}, ordered by ${field} ${direction}ending; narrow the search to read the rest.`,\n\t\t),\n\t\tomission: {\n\t\t\tkind: 'provider-limit',\n\t\t\trecovery: 'none',\n\t\t\tlimit: limit,\n\t\t\ttotalCount: totalCount,\n\t\t\tsort: sort,\n\t\t},\n\t};\n}\n\n/** Warning for a git host that doesn't expose issues on this surface (e.g. Bitbucket, deprecated in favor of Jira). */\nexport function issuesUnsupportedWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n): ProviderWarning {\n\treturn otherWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`Issues are not supported by '${id}'; use a dedicated issue integration (e.g. Jira) instead.`,\n\t);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"warnings.js","names":[],"sources":["../../../../../plus/integrations/src/reads/warnings.ts"],"sourcesContent":["import type { IntegrationIds } from '../constants.js';\nimport type {\n\tIssueFilter,\n\tIssueSearchCapabilities,\n\tIssueSorting,\n\tPullRequestFilter,\n\tPullRequestSearchCapabilities,\n\tPullRequestSorting,\n} from '../providerFilters.js';\nimport { providersMetadata } from '../providers/models.js';\nimport type { ProviderWarning, ProviderWarningOmissionRecovery } from '../results.js';\nimport type { IssueSearchCriteriaRejection, PullRequestSearchCriteriaRejection } from './filters.js';\nimport type { UnmergeableIssueSort, UnsupportedIssueSortRejection } from './ordering.js';\n\n/**\n * The warnings the provider facade raises on its OWN terms, as opposed to the ones derived from a caught\n * provider error ({@link toProviderWarning}) or from SDK collection metadata (`collectionMetadata.ts`).\n *\n * These are pure message builders, kept together and out of the service so a read method reads as the decision\n * it makes (\"this surface doesn't apply, refuse it\") rather than as the wording of the refusal, and so the same\n * refusal can't be phrased two ways in two reads.\n */\n\n/**\n * The single builder for a provider-neutral, non-auth warning: an unsupported capability, a\n * contradictory/inexpressible request, or a read that couldn't confirm completeness. Every `kind: 'other'`\n * warning the facade raises on its own terms goes through here (directly, or via one of the named builders\n * below that pin a recurring message), so the discriminant is assigned in one place. The warnings mapped from\n * SDK collection metadata are the documented exception — `collectionMetadata.ts` builds those from the\n * structured failure/omission it is given.\n *\n * The kinds that carry a programmatic remedy — `auth`, `rate-limit`, `not-found`, `no-connection` — are derived\n * from the caught error's type instead and never come from here; see `ProviderWarningKind` and\n * {@link noConnectionWarning}.\n */\nexport function otherWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmessage: string,\n): ProviderWarning {\n\treturn {\n\t\tproviderId: id,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage: message,\n\t\tkind: 'other',\n\t\tisAuth: false,\n\t};\n}\n\n/**\n * Builds a `no-connection` warning for a per-connection read that resolved neither a session nor an error: the\n * requested `connectionId` no longer resolves (deleted, or its authentication is invalid). Consumers use this\n * to tell a truly empty account apart from a broken connection.\n */\nexport function noConnectionWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId?: string,\n): ProviderWarning {\n\treturn {\n\t\tproviderId: id,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage:\n\t\t\tconnectionId != null\n\t\t\t\t? `Connection '${connectionId}' for '${id}' could not be resolved (deleted or invalid authentication).`\n\t\t\t\t: `No active connection for '${id}' could be resolved.`,\n\t\tkind: 'no-connection',\n\t\tisAuth: false,\n\t};\n}\n\n/** Warning for an issue-tracker provider asked for a surface only a git host serves (repos, PRs, sweeps). */\nexport function gitHostOnlySurfaceWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tsurface: string,\n): ProviderWarning {\n\treturn otherWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`${surface} is not supported by '${id}'; use a git-host integration instead.`,\n\t);\n}\n\n/** Warning for a git host asked for a surface only an issue tracker serves (resource → project issue reads). */\nexport function issueTrackerOnlySurfaceWarning(\n\tid: IntegrationIds,\n\tconnectionId: string | undefined,\n\tsurface: string,\n): ProviderWarning {\n\treturn otherWarning(\n\t\tid,\n\t\tundefined,\n\t\tconnectionId,\n\t\t`${surface} is not supported by '${id}'; use an issue-tracker integration instead.`,\n\t);\n}\n\n/** Why a facade-driven read returned fewer results than requested. */\nexport type IncompleteReadCause = 'interrupted' | 'page-budget' | 'exhausted' | 'scope-too-large';\n\nfunction recoveryForIncompleteReadCause(cause: IncompleteReadCause): ProviderWarningOmissionRecovery {\n\tswitch (cause) {\n\t\tcase 'page-budget':\n\t\t\treturn 'page-budget';\n\t\tcase 'scope-too-large':\n\t\t\treturn 'narrow-scope';\n\t\tcase 'interrupted':\n\t\tcase 'exhausted':\n\t\t\treturn 'none';\n\t\tdefault:\n\t\t\tcause satisfies never;\n\t\t\treturn 'none';\n\t}\n}\n\n/**\n * Builds the warning for a read that returned less than everything, carrying the two facts a consumer cannot\n * recover on its own: did the request succeed, and would anything fetch the rest?\n *\n * Every incompleteness warning the facade raises on its own terms goes through here, so no read can drift\n * into claiming success on a failure or offering a \"load more\" that cannot deliver. The omission it attaches\n * is the same shape `assessCollectionMetadata` derives from SDK metadata, so a consumer never has to care\n * which layer noticed the gap.\n *\n * No `scope`: this applies to the whole read, not to one repository or project the way an SDK-attributed\n * omission can, so a consumer acting on `recovery` re-runs the read itself.\n */\nexport function incompleteReadWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmessage: string,\n\tcause: IncompleteReadCause,\n): ProviderWarning {\n\tconst warning = otherWarning(id, domain, connectionId, message);\n\tif (cause === 'interrupted') return warning;\n\n\treturn {\n\t\t...warning,\n\t\tomission: {\n\t\t\tkind: 'pagination-incomplete',\n\t\t\trecovery: recoveryForIncompleteReadCause(cause),\n\t\t},\n\t};\n}\n\n/**\n * The read surfaces {@link truncationWarning} phrases for. `'Account-wide issue search'` names the composite\n * read that spans several provider searches rather than one surface.\n *\n * Deliberately not exported: every caller passes a literal, and publishing it would invite a consumer to switch\n * on a set that exists only to word a message.\n */\ntype TruncatedReadKind =\n\t| 'Pull request'\n\t| 'Issue'\n\t| 'Repository'\n\t| 'Organization'\n\t| 'Project'\n\t| 'Account-wide issue search'\n\t| 'Issue search'\n\t| 'Pull request search';\n\n/** {@link incompleteReadWarning} for a paged or drained read, phrased per surface. */\nexport function truncationWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\treadKind: TruncatedReadKind,\n\t/**\n\t * Deliberately REQUIRED and not defaulted: a default would make one of these the silent fallback at any\n\t * call site that forgot it, and both of the claims it carries — \"the request succeeded\" and \"more can be\n\t * fetched\" — are ones this warning must never make by accident.\n\t */\n\tcause: IncompleteReadCause,\n): ProviderWarning {\n\treturn incompleteReadWarning(id, domain, connectionId, truncationMessage(id, readKind, cause), cause);\n}\n\nfunction truncationMessage(id: IntegrationIds, readKind: TruncatedReadKind, cause: IncompleteReadCause): string {\n\tswitch (cause) {\n\t\tcase 'interrupted':\n\t\t\t// Deliberately does not name a mechanism. This fires both when a page was lost mid-drain and when a\n\t\t\t// drain that latched an earlier scope failure later stopped for its own reasons; \"was interrupted\"\n\t\t\t// would be false in the second. What is true in both is that something failed and results are gone.\n\t\t\treturn `${readKind} read for '${id}' did not complete; some results are missing and the read reported a failure.`;\n\t\tcase 'page-budget':\n\t\t\treturn `${readKind} read for '${id}' stopped at its page budget; more results can be read by raising it.`;\n\t\tcase 'exhausted':\n\t\t\treturn `${readKind} read for '${id}' was truncated and cannot be continued; results may be incomplete.`;\n\t\tcase 'scope-too-large':\n\t\t\t// Says what is true — the scope is larger than a read of it — rather than blaming the provider for a\n\t\t\t// ceiling that is ours, and names the remedy that exists, since no retry or budget has one.\n\t\t\treturn `${readKind} read for '${id}' covers more than one read can return; narrow the scope to read the rest.`;\n\t}\n\t// No `default`: `IncompleteReadCause` is declared in this file, so `noImplicitReturns` already fails the\n\t// build here if a cause is added without its own wording. (`collectionOmissionMessage`'s `satisfies never`\n\t// is not the same case — that union is the SDK's, and can widen under a dependency bump.)\n}\n\n/**\n * The one sentence every \"you asked for a narrowing this provider can't express\" refusal uses.\n *\n * Shared because the three callers below differ only in a noun: the wording is consumer-visible, and three\n * hand-written copies drift three ways — including the `(supported: …)` clause, which exists to avoid emitting an\n * empty parenthetical and has to be the same guard in all of them.\n */\nfunction unsupportedNarrowingMessage(id: IntegrationIds, noun: string, requested: string, supported: string): string {\n\treturn `The requested ${noun} (${requested}) are not supported by '${id}'${supported.length > 0 ? ` (supported: ${supported})` : ''}; skipped to avoid returning a wider result than requested.`;\n}\n\n/** Warning for an account-wide issue read whose requested filters the provider can't express server-side. */\nexport function unsupportedAccountWideIssueFiltersWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tfilters: IssueFilter[],\n): ProviderWarning {\n\tconst supported = providersMetadata[id]?.supportedAccountWideIssueFilters ?? [];\n\treturn otherWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tunsupportedNarrowingMessage(id, 'account-wide issue filters', filters.join(', '), supported.join(', ')),\n\t);\n}\n\n/** Warning for an account-wide PR read whose requested relationship union cannot be expressed exactly. */\nexport function unsupportedAccountWidePullRequestFiltersWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tfilters: PullRequestFilter[],\n): ProviderWarning {\n\tconst supported = providersMetadata[id]?.supportedAccountWidePullRequestFilters ?? [];\n\treturn otherWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tunsupportedNarrowingMessage(id, 'account-wide pull request filters', filters.join(', '), supported.join(', ')),\n\t);\n}\n\n/** Warning for a repo-scoped PR read whose requested filters the provider supports none of. */\nexport function unsupportedFiltersWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n): ProviderWarning {\n\treturn otherWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`The requested pull request filters are not supported by '${id}'; skipped to avoid returning unfiltered results.`,\n\t);\n}\n\n/**\n * The one sentence every `unusable-scope` refusal uses — the five surfaces that validate a search scope (both\n * searches, both count probes, and issue broadening) differ only in the noun and in whether they name a count\n * key, so the reason a scope is unusable is written once. `prefix` is what the count probes put in front of it\n * to name their key.\n *\n * NAMES the offending values, because which failure mode applied is not deducible from the value: a quote makes\n * `git\"kraken` a request for the real and different org `gitkraken`, a space makes `my org` a search of `my`\n * filtered by the text `org`, and a value of only quotes or whitespace leaves no scope at all. The caller is the\n * only layer that knows which scope it meant.\n *\n * Says how to fix it rather than what was stripped: a consumer cannot mirror this rule (unlike free-text\n * sanitizing, whose rules are published for exactly that), so \"pass the scope as the provider names it\" is the\n * actionable half.\n */\nexport function unusableSearchScopeMessage(noun: string, scopes: readonly string[], prefix?: string): string {\n\treturn `${prefix != null ? `${prefix}: ` : ''}The requested ${noun} (${scopes.map(s => JSON.stringify(s)).join(', ')}) cannot be used as given: a scope name cannot contain quotes, or an inner space or control character, which a query would drop or read as a second qualifier — searching a different scope, or none. Pass the scope exactly as the provider names it.`;\n}\n\n/** Warning for a filtered pull-request search the provider cannot run as requested. */\nexport function unsupportedPullRequestSearchCriteriaWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\trejection: PullRequestSearchCriteriaRejection,\n): ProviderWarning {\n\tlet message: string;\n\tswitch (rejection.reason) {\n\t\tcase 'unsupported-search':\n\t\t\tmessage = `Filtered pull request search is not supported by '${id}'; use \\`listPullRequestsPage\\` for its own pull request reads.`;\n\t\t\tbreak;\n\t\tcase 'unsupported-criteria': {\n\t\t\tconst supported = providersMetadata[id]?.supportedPullRequestSearch;\n\t\t\tmessage = unsupportedNarrowingMessage(\n\t\t\t\tid,\n\t\t\t\t'pull request search criteria',\n\t\t\t\trejection.criteria.join(', '),\n\t\t\t\tsupported != null ? describePullRequestSearchCapabilities(supported) : '',\n\t\t\t);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn otherWarning(id, domain, connectionId, message);\n}\n\nfunction describePullRequestSearchCapabilities(capabilities: PullRequestSearchCapabilities): string {\n\treturn [\n\t\t...(capabilities.relationships.length ? [`relationships:${capabilities.relationships.join('|')}`] : []),\n\t\t...(capabilities.states.length ? [`states:${capabilities.states.join('|')}`] : []),\n\t\t...(capabilities.text ? ['text'] : []),\n\t\t...(capabilities.updatedAfter ? ['updatedAfter'] : []),\n\t\t...(capabilities.createdAfter ? ['createdAfter'] : []),\n\t\t...(capabilities.includeArchived ? ['includeArchived'] : []),\n\t\t...(capabilities.draft ? ['draft'] : []),\n\t\t...(capabilities.repositoryScope ? ['repository scope'] : []),\n\t\t...(capabilities.organizationScope ? ['organization scope'] : []),\n\t\t...(capabilities.sorts.length ? [`sorts:${capabilities.sorts.join('|')}`] : []),\n\t].join(', ');\n}\n\n/**\n * Warning for a filtered issue search whose criteria the provider can't serve — either it has no such search at\n * all, or it can't express what was asked for, or the set contradicts itself.\n *\n * One builder for all three because they are one decision from the caller's side (\"this search can't be run as\n * asked\") and the remedy differs only in what the message says. The rejection shape carries which case it is, so\n * the wording can't drift from the check that produced it (see {@link resolveIssueSearchCriteria}).\n */\nexport function unsupportedIssueSearchCriteriaWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\trejection: IssueSearchCriteriaRejection,\n): ProviderWarning {\n\tlet message: string;\n\tswitch (rejection.reason) {\n\t\tcase 'unsupported-search':\n\t\t\tmessage = `Filtered issue search is not supported by '${id}'; use \\`listIssuesPage\\` for its own issue reads.`;\n\t\t\tbreak;\n\t\tcase 'unsupported-criteria': {\n\t\t\tconst supported = providersMetadata[id]?.supportedIssueSearch;\n\t\t\tmessage = unsupportedNarrowingMessage(\n\t\t\t\tid,\n\t\t\t\t'issue search criteria',\n\t\t\t\trejection.criteria.join(', '),\n\t\t\t\tsupported != null ? describeIssueSearchCapabilities(supported) : '',\n\t\t\t);\n\t\t\tbreak;\n\t\t}\n\t\tcase 'contradictory-relationships':\n\t\t\tmessage = `The relationships \\`any-assignee\\` and \\`unassigned\\` are contradictory — they partition the scope between them, so no issue satisfies both; pass only one.`;\n\t\t\tbreak;\n\t}\n\t// No `default`: the union is declared alongside the validator, so `noImplicitReturns` fails the build here if\n\t// a rejection reason is added without its own wording.\n\n\treturn otherWarning(id, domain, connectionId, message);\n}\n\n/** The criteria a provider CAN express, for the \"supported: …\" half of a refusal message. */\nfunction describeIssueSearchCapabilities(capabilities: IssueSearchCapabilities): string {\n\tconst flags: [name: string, supported: boolean][] = [\n\t\t['text', capabilities.text],\n\t\t['labels', capabilities.labels],\n\t\t['milestone', capabilities.milestone],\n\t\t['updatedAfter', capabilities.updatedAfter],\n\t\t['createdAfter', capabilities.createdAfter],\n\t\t['withoutLinkedPullRequest', capabilities.withoutLinkedPullRequest],\n\t\t['state', capabilities.states],\n\t];\n\treturn [\n\t\t...capabilities.relationships.map(r => `relationships:${r}`),\n\t\t...flags.filter(([, supported]) => supported).map(([name]) => name),\n\t\t...capabilities.sorts.map(s => `sort:${s}`),\n\t].join(', ');\n}\n\n/**\n * Warning for an issue read asked to order by a key its provider can't express server-side.\n *\n * Separate from {@link unsupportedIssueSearchCriteriaWarning} because it serves the reads that take `sort` as an\n * OPTION rather than as a criterion — repo-scoped, account-wide, and issue-tracker — where there is no criteria\n * set to fold it into. The filtered search keeps reporting it as one more `unsupported-criteria` entry, so a\n * caller there still gets a single refusal listing everything at once.\n *\n * Takes the rejection the check produced rather than a re-read supported list: which of the two tables applies is\n * the caller's own branch (`repos` present or not), so reading it back here — or threading it by hand — would be\n * free to disagree with the check that refused.\n */\nexport function unsupportedIssueSortWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\trejection: UnsupportedIssueSortRejection,\n): ProviderWarning {\n\treturn otherWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tunsupportedNarrowingMessage(id, 'issue sort', rejection.requested, (rejection.supported ?? []).join(', ')),\n\t);\n}\n\n/**\n * Warning for a merged issue read asked to order by a key that no normalized issue carries.\n *\n * The distinction this exists to make is not about the provider: the provider orders by `priority` perfectly well\n * within ONE project. It is about the read, which fans out over several and merges the results here, where the\n * only fields available are the ones {@link IssueShape} models. Serving the concatenated per-scope runs would look\n * ordered without being so, which is why this refuses instead — and why the message says WHY, since the same key\n * against the same provider succeeds on a single-scope read.\n *\n * What a read fans out OVER differs by read, hence the `scope`: repositories, projects, or — where one query is\n * issued per requested relationship and the runs are unioned within a single project (Jira) — the filters\n * themselves. Naming the wrong one would tell a caller to narrow something that isn't what merged.\n */\nexport function unmergeableIssueSortWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tunmergeable: UnmergeableIssueSort,\n\tscope: 'repositories' | 'projects' | 'filters',\n): ProviderWarning {\n\t// Both forms are spelled out rather than depluralized at runtime: the message needs each, and\n\t// `'repositories'.replace(/s$/, '')` is `'repositorie'`.\n\tconst nouns = {\n\t\trepositories: { many: 'repositories', one: 'repository' },\n\t\tprojects: { many: 'projects', one: 'project' },\n\t\tfilters: { many: 'issue relationships', one: 'relationship' },\n\t}[scope];\n\treturn otherWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`Cannot order by '${unmergeable.requested}' across several ${nouns.many}: this read merges their results, and a normalized issue carries no such field to merge on. Read one ${nouns.one} at a time, or order by a date, title, comment or reaction count.`,\n\t);\n}\n\n/**\n * The omission for a filtered issue search that hit the provider's RESULT CEILING: more issues matched than the\n * provider will ever serve for one query, however it is paged.\n *\n * Distinct from {@link truncationWarning} because this is the one incompleteness this read can quantify —\n * `totalCount` is how many matched, `limit` how many are reachable — which is what lets a consumer say \"19.240\n * matched, showing the 1.000 most recently updated\" instead of a bare \"results may be incomplete\". `recovery:\n * 'none'` because the items past the ceiling are UNREACHABLE, not merely unfetched: no budget, no retry, and no\n * cursor would return them. Narrowing the criteria is the only way through, which is a decision for the caller.\n *\n * The message names the ORDER, and has to now that ordering is an option: the reachable window is the top `limit`\n * under that key, so \"the 1.000 most recent\" is a true sentence only while the key is the default one. The same\n * value goes on the omission, so a consumer wording its own sentence doesn't have to remember what it asked for.\n *\n * Returns `undefined` when the provider declares no ceiling, so the caller falls back to the generic wording\n * rather than reporting a limit it invented.\n */\nexport function issueSearchCapResultWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\ttotalCount: number | undefined,\n\tsort: IssueSorting,\n\t/** The provider proved the ceiling was reached without reporting how many matched; see `limitReached`. */\n\tlimitReached?: boolean,\n): ProviderWarning | undefined {\n\treturn searchCapResultWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t'Issue search',\n\t\tprovidersMetadata[id]?.issueSearchResultLimit,\n\t\ttotalCount,\n\t\tsort,\n\t\tlimitReached,\n\t);\n}\n\n/**\n * The shared body of the two ceiling warnings. Only a ceiling that was actually reached warns: either a reported\n * total above it, or a provider that proved the ceiling was reached but can't count past it — in which case the\n * omission carries the limit and the order but no `totalCount`, since any figure would be invented.\n */\nfunction searchCapResultWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\treadKind: 'Issue search' | 'Pull request search',\n\tlimit: number | undefined,\n\ttotalCount: number | undefined,\n\tsort: IssueSorting | PullRequestSorting,\n\tlimitReached: boolean | undefined,\n): ProviderWarning | undefined {\n\tif (limit == null) return undefined;\n\n\t// Below the ceiling this warning would be a false claim; the caller's truncation had another cause.\n\tconst counted = totalCount != null && totalCount > limit;\n\tif (!counted && limitReached !== true) return undefined;\n\n\tconst [field, direction] = sort.split(':');\n\tconst matched = counted ? `matched ${totalCount} results` : `matched more than ${limit} results`;\n\treturn {\n\t\t...otherWarning(\n\t\t\tid,\n\t\t\tdomain,\n\t\t\tconnectionId,\n\t\t\t`${readKind} ${matched}, but '${id}' serves at most ${limit}, ordered by ${field} ${direction}ending; narrow the search to read the rest.`,\n\t\t),\n\t\tomission: {\n\t\t\tkind: 'provider-limit',\n\t\t\t// The items past the ceiling can't be fetched by anything, so never offer a \"load more\".\n\t\t\trecovery: 'none',\n\t\t\tlimit: limit,\n\t\t\t...(counted ? { totalCount: totalCount } : {}),\n\t\t\tsort: sort,\n\t\t},\n\t};\n}\n\n/** Quantified omission for a pull-request search that exceeded the provider's reachable result window. */\nexport function pullRequestSearchCapResultWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\ttotalCount: number | undefined,\n\tsort: PullRequestSorting,\n\t/** See {@link issueSearchCapResultWarning}. */\n\tlimitReached?: boolean,\n): ProviderWarning | undefined {\n\t// Named order for the same reason as the issue twin: the reachable window is the first `limit` under THIS sort.\n\treturn searchCapResultWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t'Pull request search',\n\t\tprovidersMetadata[id]?.pullRequestSearchResultLimit,\n\t\ttotalCount,\n\t\tsort,\n\t\tlimitReached,\n\t);\n}\n\n/** Warning for a git host that doesn't expose issues on this surface (e.g. Bitbucket, deprecated in favor of Jira). */\nexport function issuesUnsupportedWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n): ProviderWarning {\n\treturn otherWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`Issues are not supported by '${id}'; use a dedicated issue integration (e.g. Jira) instead.`,\n\t);\n}\n"],"mappings":"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"}
|
|
@@ -11,7 +11,8 @@ export interface ConnectionStateChangeEvent {
|
|
|
11
11
|
* these to drive auth recovery, retry, or truncation messaging without the read itself throwing.
|
|
12
12
|
*
|
|
13
13
|
* `kind` is the programmatic discriminant (a rate-limit is retryable, a 404 is not, an auth failure
|
|
14
|
-
* needs re-connection); `isAuth` is retained as a
|
|
14
|
+
* needs re-connection unless {@link ProviderWarning.scope} confines it); `isAuth` is retained as a
|
|
15
|
+
* convenience mirror of `kind === 'auth'`.
|
|
15
16
|
*/
|
|
16
17
|
export type ProviderWarningKind = 'auth' | 'rate-limit' | 'not-found' | 'no-connection' | 'other';
|
|
17
18
|
/**
|
|
@@ -36,13 +37,46 @@ export type ProviderWarningOmissionKind = 'provider-limit' | 'recovery-budget' |
|
|
|
36
37
|
* `page-budget` is a fetch-more action.
|
|
37
38
|
*/
|
|
38
39
|
export type ProviderWarningOmissionRecovery = 'none' | 'page-budget' | 'narrow-scope';
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
/**
|
|
41
|
+
* The part of a provider a warning is confined to: one resource (organization, workspace, site), project or
|
|
42
|
+
* repository. All fields optional; see {@link ProviderWarning.scope} for when one is forwarded.
|
|
43
|
+
*/
|
|
44
|
+
export interface ProviderWarningScope {
|
|
45
|
+
/**
|
|
46
|
+
* The resource as the read addressed it: its id on most reads, its name on the few that address it by name
|
|
47
|
+
* (Azure DevOps' repo-scoped reads, Bitbucket's workspace reads). Match it against both
|
|
48
|
+
* `ProviderOrganization.id` and `name`.
|
|
49
|
+
*/
|
|
42
50
|
resourceId?: string;
|
|
43
51
|
projectId?: string;
|
|
44
52
|
repositoryId?: string;
|
|
45
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Why a provider refused a credential it otherwise accepts, when the refusal itself says so:
|
|
56
|
+
* - `oauth-app-not-allowed`: the organization does not let third-party OAuth apps in. On Azure DevOps this is the
|
|
57
|
+
* organization policy "Third-party application access via OAuth", off by default for new organizations. An
|
|
58
|
+
* organization admin enables it, or the user connects with a personal access token instead, which the policy
|
|
59
|
+
* does not govern.
|
|
60
|
+
* - `access-denied`: the account has no access to that organization or project (not a member, or lacking the
|
|
61
|
+
* permission). Someone who administers it has to grant access.
|
|
62
|
+
* - `conditional-access`: a Microsoft Entra Conditional Access policy blocked the request (Azure DevOps
|
|
63
|
+
* `VS403463`). Its admin has to exempt the request, e.g. by location or device.
|
|
64
|
+
*
|
|
65
|
+
* None of these is healed by reconnecting, which is the point of naming them.
|
|
66
|
+
*/
|
|
67
|
+
export type ProviderWarningCauseReason = 'oauth-app-not-allowed' | 'access-denied' | 'conditional-access';
|
|
68
|
+
/** See {@link ProviderWarning.cause}. */
|
|
69
|
+
export interface ProviderWarningCause {
|
|
70
|
+
reason: ProviderWarningCauseReason;
|
|
71
|
+
/** The provider's own error code, when it reports one (Azure DevOps `TF400813`, `VS403463`). */
|
|
72
|
+
code?: string;
|
|
73
|
+
/** Where the setting behind the refusal is changed, when this layer can address it. */
|
|
74
|
+
remedyUrl?: string;
|
|
75
|
+
}
|
|
76
|
+
/** Which repository / project / resource an omission is attributed to. All fields optional; a scope may name none. */
|
|
77
|
+
export interface ProviderWarningOmissionScope extends ProviderWarningScope {
|
|
78
|
+
providerId?: string;
|
|
79
|
+
}
|
|
46
80
|
export interface ProviderWarningOmission {
|
|
47
81
|
kind: ProviderWarningOmissionKind;
|
|
48
82
|
/**
|
|
@@ -81,8 +115,50 @@ export interface ProviderWarning {
|
|
|
81
115
|
connectionId?: string;
|
|
82
116
|
message: string;
|
|
83
117
|
kind: ProviderWarningKind;
|
|
84
|
-
/** Convenience mirror of `kind === 'auth'
|
|
118
|
+
/** Convenience mirror of `kind === 'auth'`, scoped or not; read {@link scope} before prompting to reconnect. */
|
|
85
119
|
isAuth: boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Present when the failure was recorded against one sub-scope of the read (an organization, project or
|
|
122
|
+
* repository) rather than against the connection. A scoped `auth` failure is that one scope refusing this
|
|
123
|
+
* credential, e.g. an Azure DevOps organization that disallows third-party OAuth apps, one in another Entra
|
|
124
|
+
* tenant, or a Conditional Access policy. Reconnecting cannot fix those, so this warning alone is no reason
|
|
125
|
+
* to prompt for it.
|
|
126
|
+
*
|
|
127
|
+
* `kind` and `isAuth` are unaffected: a scoped 401 is still an authentication failure, and this field says
|
|
128
|
+
* how far it reaches. It names at least one of its IDs, and is never set on an omission (which carries its
|
|
129
|
+
* own {@link ProviderWarningOmission.scope}) or on a warning derived from a caught exception (see
|
|
130
|
+
* {@link toProviderWarning}), even when that call targeted a single organization.
|
|
131
|
+
*
|
|
132
|
+
* Its ABSENCE means account-wide or unattributed, so existing handling stays correct without it.
|
|
133
|
+
*
|
|
134
|
+
* A scoped `auth` failure also means the credential itself was accepted. A dead token can come back as nothing
|
|
135
|
+
* but scoped refusals wherever a read reaches its scopes without an uncached request to the connection first:
|
|
136
|
+
* discovery served from a per-token cache (Azure DevOps, Bitbucket, Jira Cloud) or an SDK fan-out across the
|
|
137
|
+
* requested repositories (Bitbucket Data Center). So when a read's only auth failures are scoped, those providers
|
|
138
|
+
* confirm the credential with one uncached check first, and a refused credential fails the whole read with an
|
|
139
|
+
* unscoped `auth` warning. A refusal the provider attributes to the credential itself, like Bitbucket's or Jira
|
|
140
|
+
* Cloud's for a token missing the OAuth scopes a read needs, which a reconnect fixes, is published unscoped too,
|
|
141
|
+
* once for the connection, while the scopes that answered keep their results.
|
|
142
|
+
*
|
|
143
|
+
* So the promise holds for a read that carries no unscoped `auth` warning of its own; one that does already asks
|
|
144
|
+
* for a reconnect, and its other scoped refusals are not checked. Two exceptions stay scoped even then. A check
|
|
145
|
+
* that could not complete or was denied (a network error, a throttle, a `403`, Bitbucket Data Center refusing a
|
|
146
|
+
* credential it authenticated, or a Bitbucket Data Center project access token, whose user the check cannot
|
|
147
|
+
* find) proves nothing, so the warnings are published unconfirmed, with no {@link cause}, and the next read
|
|
148
|
+
* checks again. And a credential confirmed within the last minute is not probed again, so a revocation can take
|
|
149
|
+
* up to a minute to surface.
|
|
150
|
+
*/
|
|
151
|
+
scope?: ProviderWarningScope;
|
|
152
|
+
/**
|
|
153
|
+
* Why the provider refused, when the refusal itself says so: see {@link ProviderWarningCauseReason}. Switch on
|
|
154
|
+
* `reason` to recommend a fix instead of a reconnect; `message` then carries the same explanation as prose.
|
|
155
|
+
*
|
|
156
|
+
* Set only on a scoped `auth` warning whose credential was confirmed first (see {@link scope}), because the
|
|
157
|
+
* refusals it names are indistinguishable from a dead credential until then: Azure DevOps answers a
|
|
158
|
+
* third-party OAuth app its organization disallows exactly as it answers an expired token. Only Azure DevOps
|
|
159
|
+
* reports one today. Its absence proves nothing about the cause.
|
|
160
|
+
*/
|
|
161
|
+
cause?: ProviderWarningCause;
|
|
86
162
|
/**
|
|
87
163
|
* Present when this warning describes results the read could not return even though the request itself
|
|
88
164
|
* SUCCEEDED — a provider-enforced cap, an exhausted recovery budget, a page budget, or a sub-scope the read
|
|
@@ -236,6 +312,8 @@ export interface RepositoryResolution {
|
|
|
236
312
|
export interface ResolveRepositoryResult {
|
|
237
313
|
resolution: RepositoryResolution;
|
|
238
314
|
}
|
|
315
|
+
/** A caught error's message as a warning can carry it: an HTML page becomes its status, and prose is capped. */
|
|
316
|
+
export declare function providerWarningMessage(ex: unknown): string;
|
|
239
317
|
/**
|
|
240
318
|
* Normalized org/workspace/group shape returned by the provider facade.
|
|
241
319
|
* `name` is the provider identifier to pass to follow-up reads, while `org` is an optional display label.
|
|
@@ -297,7 +375,7 @@ export declare function collectionScopeKey(scope: ProviderWarningOmissionScope |
|
|
|
297
375
|
export declare function reconcileOmissionsWithFailure(warnings: ProviderWarning[], fetchFailed: boolean): void;
|
|
298
376
|
/**
|
|
299
377
|
* Appends `warning` to `into` only when an equal warning (by provider/connection/domain/kind/message, plus the
|
|
300
|
-
* structured omission when
|
|
378
|
+
* structured scope, cause and omission when present) is absent.
|
|
301
379
|
*/
|
|
302
380
|
export declare function appendDedupedWarning(into: ProviderWarning[], warning: ProviderWarning): void;
|
|
303
381
|
//#endregion
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AuthenticationError, RequestNotFoundError, RequestRateLimitError } from "../../git/errors.js";
|
|
2
2
|
//#region ../plus/integrations/src/results.ts
|
|
3
3
|
const maxProviderWarningMessageLength = 500;
|
|
4
|
+
/** A caught error's message as a warning can carry it: an HTML page becomes its status, and prose is capped. */
|
|
4
5
|
function providerWarningMessage(ex) {
|
|
5
6
|
const raw = (ex instanceof Error ? ex.message : String(ex)).trim();
|
|
6
7
|
const carrier = ex;
|
|
@@ -95,6 +96,10 @@ function reconcileOmissionsWithFailure(warnings, fetchFailed) {
|
|
|
95
96
|
/**
|
|
96
97
|
* A stable key for deduplicating warnings accumulated across drained pages / fan-out scopes.
|
|
97
98
|
*
|
|
99
|
+
* The scope and the cause are keyed for the reason the omission is (see {@link providerWarningOmissionKey}): today's
|
|
100
|
+
* failure messages happen to spell them out, but two failures that differ in either must stay two warnings even if
|
|
101
|
+
* they don't.
|
|
102
|
+
*
|
|
98
103
|
* `message` stays LAST. It is the only free-form segment — provider prose, spaces and all — so anything
|
|
99
104
|
* appended after it could be impersonated by a message that happens to end in the same text.
|
|
100
105
|
*/
|
|
@@ -104,13 +109,15 @@ function providerWarningKey(warning) {
|
|
|
104
109
|
warning.connectionId ?? "",
|
|
105
110
|
warning.domain ?? "",
|
|
106
111
|
warning.kind,
|
|
112
|
+
collectionScopeKey(warning.scope),
|
|
113
|
+
warning.cause?.reason ?? "",
|
|
107
114
|
providerWarningOmissionKey(warning.omission),
|
|
108
115
|
warning.message
|
|
109
116
|
].join(" ");
|
|
110
117
|
}
|
|
111
118
|
/**
|
|
112
119
|
* Appends `warning` to `into` only when an equal warning (by provider/connection/domain/kind/message, plus the
|
|
113
|
-
* structured omission when
|
|
120
|
+
* structured scope, cause and omission when present) is absent.
|
|
114
121
|
*/
|
|
115
122
|
function appendDedupedWarning(into, warning) {
|
|
116
123
|
const key = providerWarningKey(warning);
|
|
@@ -118,6 +125,6 @@ function appendDedupedWarning(into, warning) {
|
|
|
118
125
|
into.push(warning);
|
|
119
126
|
}
|
|
120
127
|
//#endregion
|
|
121
|
-
export { appendDedupedWarning, collectionScopeKey, reconcileOmissionsWithFailure, toProviderWarning };
|
|
128
|
+
export { appendDedupedWarning, collectionScopeKey, providerWarningMessage, reconcileOmissionsWithFailure, toProviderWarning };
|
|
122
129
|
|
|
123
130
|
//# sourceMappingURL=results.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"results.js","names":[],"sources":["../../../../plus/integrations/src/results.ts"],"sourcesContent":["import { AuthenticationError, RequestNotFoundError, RequestRateLimitError } from '@gitlens/git/errors.js';\nimport type { IssueSorting } from '@gitlens/git/models/issue.js';\nimport type { PullRequestSorting } from '@gitlens/git/models/pullRequest.js';\nimport type { IntegrationIds } from './constants.js';\n\nexport interface ConnectionStateChangeEvent {\n\tkey: string;\n\treason: 'connected' | 'disconnected';\n}\n\n/**\n * A per-provider, per-connection warning surfaced alongside (partial) read results. Consumers use\n * these to drive auth recovery, retry, or truncation messaging without the read itself throwing.\n *\n * `kind` is the programmatic discriminant (a rate-limit is retryable, a 404 is not, an auth failure\n * needs re-connection); `isAuth` is retained as a convenience mirror of `kind === 'auth'`.\n */\nexport type ProviderWarningKind = 'auth' | 'rate-limit' | 'not-found' | 'no-connection' | 'other';\n\n/**\n * Why a read that SUCCEEDED still withheld results:\n * - `provider-limit`: the provider refuses to serve past a cap it enforces (GitHub search's 1,000-result\n * ceiling, Trello's `cards_limit`), so the excess is unreachable through that query.\n * - `recovery-budget`: the internal partitioned recovery stopped spending upstream requests before it ran\n * out of partitions to visit.\n * - `pagination-incomplete`: pages were left unread — a sub-scope of a multi-scope read that was not drained,\n * a drain that stopped at its own page backstop, or a provider that advertised another page and gave no way\n * to reach it. These have DIFFERENT remedies, so the kind alone does not say whether more can be fetched;\n * read {@link ProviderWarningOmission.recovery} for that.\n *\n * Mirrors the vocabulary the SDK reports. Declared here rather than imported so this module stays free of\n * `@gitkraken/provider-apis` types (see the export block in `index.ts`); `collectionMetadata.ts` holds the\n * compile-time link, so an SDK bump that adds a kind fails the build at that boundary.\n */\nexport type ProviderWarningOmissionKind = 'provider-limit' | 'recovery-budget' | 'pagination-incomplete';\n\n/**\n * How a consumer can recover omitted results. `page-budget` reruns with a larger budget; `narrow-scope`\n * requests a smaller server-side scope; `none` promises neither. Switch on the exact value because only\n * `page-budget` is a fetch-more action.\n */\nexport type ProviderWarningOmissionRecovery = 'none' | 'page-budget' | 'narrow-scope';\n\n/** Which repository / project / resource an omission is attributed to. All fields optional; a scope may name none. */\nexport interface ProviderWarningOmissionScope {\n\tproviderId?: string;\n\tresourceId?: string;\n\tprojectId?: string;\n\trepositoryId?: string;\n}\n\nexport interface ProviderWarningOmission {\n\tkind: ProviderWarningOmissionKind;\n\t/**\n\t * Whether anything would fetch the missing items, and what. Gate a \"load more\" affordance on this rather\n\t * than on `kind` — see {@link ProviderWarningOmissionRecovery}.\n\t */\n\trecovery: ProviderWarningOmissionRecovery;\n\t/**\n\t * The cap the provider enforces, when it reports one. NOT a result count for every kind: on\n\t * `recovery-budget` this is a REQUEST budget and must not be shown to a user as a number of results.\n\t */\n\tlimit?: number;\n\t/** Total matches the provider reported, when it reports one. Only GitHub's search cap does today. */\n\ttotalCount?: number;\n\t/**\n\t * The order the reachable window was selected under, as `field:direction`, when the omission is a result\n\t * ceiling.\n\t *\n\t * Present so a consumer can word its own \"N matched, showing the first M\" without assuming WHICH M: the\n\t * provider serves the top M under this key, so the sentence is only true if it names it. Absent for every\n\t * omission that isn't a ceiling, where no window was selected by an order.\n\t *\n\t * A union of both vocabularies rather than the issue one alone: `PullRequestSorting` happens to be a subset\n\t * today, so narrowing this to {@link IssueSorting} would still compile while describing only half the reads\n\t * that set it — and would break the day the PR search gains a key the issue search has no name for.\n\t */\n\tsort?: IssueSorting | PullRequestSorting;\n\t/** Which repository / project / resource was affected, when one is attributed. */\n\tscope?: ProviderWarningOmissionScope;\n}\n\nexport interface ProviderWarning {\n\tproviderId: IntegrationIds;\n\t/** Disambiguates connections on self-managed hosts (where one provider id spans multiple domains). */\n\tdomain?: string;\n\t/** The specific token/connection the read was pinned to, when a `connectionId` was supplied. */\n\tconnectionId?: string;\n\tmessage: string;\n\tkind: ProviderWarningKind;\n\t/** Convenience mirror of `kind === 'auth'`. */\n\tisAuth: boolean;\n\t/**\n\t * Present when this warning describes results the read could not return even though the request itself\n\t * SUCCEEDED — a provider-enforced cap, an exhausted recovery budget, a page budget, or a sub-scope the read\n\t * did not drain.\n\t *\n\t * Its presence is the signal: an omission is not a failure. Message it as incompleteness rather than as a\n\t * failed read, and do NOT derive that from `message`, which is English prose and subject to rewording.\n\t * `kind` stays `'other'` for these, so the failure discriminant keeps meaning exactly what it meant before\n\t * this field existed.\n\t *\n\t * Whether anything would fetch the rest is a SEPARATE question — read {@link ProviderWarningOmission.recovery},\n\t * not `kind`, and never assume a retry of the same request returns more.\n\t *\n\t * `limit` / `totalCount` / `scope` are forwarded only when they are reported; most omissions carry none of\n\t * the three, so render correctly without them — and see {@link ProviderWarningOmission.limit} before\n\t * showing that figure as a result count.\n\t *\n\t * Its ABSENCE proves nothing. It is never set on a warning derived from a caught exception (see\n\t * {@link toProviderWarning}) or from a structured scope failure — those are failures, and the field would\n\t * be a lie there. But it is also absent whenever incompleteness was reported without naming what was left\n\t * out, so treat a bare `kind: 'other'` warning as unclassified rather than as a proven failure.\n\t */\n\tomission?: ProviderWarningOmission;\n}\n\nexport interface ProviderPageInfo {\n\t/**\n\t * The 1-based POSITION of the returned page. One convention across every paged read, page-number and\n\t * cursor paging alike, so a `getNextPageParam`-style consumer can key off it uniformly:\n\t * - the page number the provider itself reported, when it reports one (authoritative);\n\t * - else the position the caller addressed: the page encoded in a threaded page cursor, or the `page`\n\t * supplied alongside an opaque cursor (1 when the caller supplied no `page`);\n\t * - else, for a read that can be advanced by page number, the requested `page` — the provider either\n\t * honored it or the internal drain reached it (see the paging contract on `IntegrationManager`);\n\t * - else 1: a cursor-only read with no cursor threaded back ignores a requested page number and answers\n\t * with its first page, so echoing the request would mislabel page 1 as page N.\n\t *\n\t * A page past the provider's terminal cursor still reports the requested `page` with empty `items` — an\n\t * empty page N, never page N-1 relabeled.\n\t *\n\t * Continue a cursor-paged read from `cursor`, NOT from `currentPage + 1`: a cursor-only host can't be\n\t * addressed by number, so this is a position label and not always a resumable request parameter.\n\t */\n\tcurrentPage: number;\n\t/**\n\t * The page size IN EFFECT, which is not always the one requested — it is whichever of these the read can\n\t * actually vouch for:\n\t * - the size the provider reported for the page it served (numbered-page hosts), else\n\t * - the `itemsPerPage` that was applied to the request (repo-scoped reads, which forward it), else\n\t * - `items.length`, for a read that takes no page size at all: the account-wide PR/issue reads, `listRepos`\n\t * (cursor-only), and `listIssueTrackerIssuesPage`, whose `itemsPerPage` counts PROJECTS rather than issues.\n\t *\n\t * So it is a description of the page, not an echo of the request: don't derive a total from\n\t * `currentPage * itemsPerPage`, and don't read `items.length < itemsPerPage` as \"last page\" — `hasMore` /\n\t * `cursor` are the only continuation signals.\n\t */\n\titemsPerPage: number;\n\t/**\n\t * A COMPLETENESS assertion, not a mode flag: `true` only when a sweep drained every page of every target\n\t * without truncating or failing (`!truncated && !fetchFailed`). Set exclusively by `sweepPullRequests` /\n\t * `sweepClosedPullRequests` and absent on every paged read, so `allPages !== true` does NOT by itself mean\n\t * \"this was a single page\".\n\t *\n\t * This is the signal to gate on before caching a sweep as authoritative: unlike `truncated` it is `false`\n\t * for BOTH kinds of incompleteness, so it can't be misread as a benign cap.\n\t */\n\tallPages?: boolean;\n\t/**\n\t * True when the read stopped before it could confirm it had everything. Deliberately does NOT distinguish\n\t * WHY, and a consumer must not assume it means a provider ceiling — a sweep sets it from three different\n\t * origins:\n\t * - a provider-side ceiling or per-scope backstop propagated from below (e.g. GitHub Search's 1000-result\n\t * cap): the missing items are NOT reachable, so this really is \"complete to the provider's limit\";\n\t * - the sweep's OWN `maxPages` cap (default 100) with a usable cursor still in hand: the missing items ARE\n\t * reachable and were simply not fetched — raise `maxPages` to get them;\n\t * - the provider reporting another page but handing back no usable cursor: not reachable.\n\t *\n\t * Only the second is recoverable, and a sweep exposes no continuation cursor, so treating a truncated sweep\n\t * as a complete set silently drops reachable data in that case. Prefer `allPages` for the cache decision.\n\t */\n\ttruncated?: boolean;\n}\n\nexport interface ProviderResult<T> {\n\titems: T[];\n\twarnings: ProviderWarning[];\n\t/**\n\t * True when `items` is incomplete — distinguishes a failed or truncated flat read from a genuinely empty\n\t * result. Paged results additionally use `page.truncated` when a completed page could not confirm it\n\t * drained everything.\n\t */\n\tfetchFailed?: boolean;\n}\n\nexport interface ProviderPagedResult<T> extends ProviderResult<T> {\n\tpage: ProviderPageInfo;\n\thasMore: boolean;\n\t/**\n\t * Opaque cursor for cursor-based paging (GraphQL, per-repo/per-project cursors, etc.). For hosts that\n\t * require the previous page's cursor, consumers should pass this value back to the next `cursor` option.\n\t * A retry-only cursor may remain when `hasMore` is false: `hasMore` means automatic forward progress,\n\t * while explicitly reusing such a cursor retries failed work without creating an infinite paging loop.\n\t */\n\tcursor?: string;\n}\n\nexport interface ProviderSweepResult<T> extends ProviderResult<T> {\n\tpage: ProviderPageInfo;\n\thasMore: boolean;\n\t/**\n\t * Providers whose top-level read failed before returning any usable page. Partial per-scope failures stay\n\t * represented by `warnings` + `fetchFailed` and are intentionally excluded.\n\t */\n\tfailedProviderIds: IntegrationIds[];\n\t/**\n\t * Providers whose read started successfully but could not produce an authoritative complete slice (for\n\t * example, a later page failed, collection metadata reported a partial scope, or a paging backstop was hit).\n\t * Kept separate from\n\t * `failedProviderIds` so consumers can accept healthy sibling providers while preserving an older snapshot\n\t * for a provider whose returned slice has a gap.\n\t */\n\tincompleteProviderIds: IntegrationIds[];\n}\n\nexport interface ProviderBroadenResult<T> extends ProviderPagedResult<T> {\n\tbroadenedProviderIds: IntegrationIds[];\n\t/** Providers for which no requested org produced a usable issue result. */\n\tfailedProviderIds: IntegrationIds[];\n\t/** Providers with both a usable org result and at least one failed or truncated org slice. */\n\tincompleteProviderIds: IntegrationIds[];\n\tfanOutCount: number;\n}\n\nexport type RepositoryResolutionStatus =\n\t| 'resolved'\n\t| 'not-found'\n\t| 'unauthorized'\n\t| 'unsupported-provider'\n\t| 'invalid-remote-url'\n\t| 'host-mismatch'\n\t| 'undetermined';\n\nexport interface RepositoryIdentity {\n\tproviderId: IntegrationIds;\n\tdomain: string;\n\t/** The provider's canonical owner/namespace (follows renames), falling back to the parsed remote when omitted. */\n\towner: string;\n\t/** The provider's canonical repo name (follows renames), falling back to the parsed remote when omitted. */\n\tname: string;\n\tproject?: string;\n\t/** The original input remote URL, so the caller can key the resolution back to the remote it asked about. */\n\tremoteUrl: string;\n\t/**\n\t * True when the provider's canonical owner/name differ from what the input remote URL carried — i.e. the\n\t * repo was renamed/moved host-side and the local remote is stale. Case-insensitive, mirroring gkcli's\n\t * `EqualFold` compare so hosts that echo the input casing (e.g. Bitbucket Server/Azure) aren't flagged.\n\t */\n\trenamed: boolean;\n}\n\nexport interface RepositoryResolution {\n\tstatus: RepositoryResolutionStatus;\n\tidentity?: RepositoryIdentity;\n\twarning?: ProviderWarning;\n}\n\nexport interface ResolveRepositoryResult {\n\tresolution: RepositoryResolution;\n}\n\nconst maxProviderWarningMessageLength = 500;\n\nfunction providerWarningMessage(ex: unknown): string {\n\tconst raw = (ex instanceof Error ? ex.message : String(ex)).trim();\n\tconst carrier = ex as { status?: unknown; response?: { status?: unknown } } | null | undefined;\n\tconst status =\n\t\ttypeof carrier?.status === 'number'\n\t\t\t? carrier.status\n\t\t\t: typeof carrier?.response?.status === 'number'\n\t\t\t\t? carrier.response.status\n\t\t\t\t: undefined;\n\n\t// Gateways commonly answer API requests with a complete HTML error page. That payload is neither actionable\n\t// nor suitable for a warning DTO consumed by UIs and logs; preserve the status without leaking the document.\n\tif (/<(?:!doctype\\s+html|html|head|body)\\b/i.test(raw)) {\n\t\treturn status != null ? `Provider request failed with status ${status}.` : 'Provider request failed.';\n\t}\n\n\tif (!raw) {\n\t\treturn status != null ? `Provider request failed with status ${status}.` : 'Provider request failed.';\n\t}\n\treturn raw.length > maxProviderWarningMessageLength\n\t\t? `${raw.slice(0, maxProviderWarningMessageLength - 3)}...`\n\t\t: raw;\n}\n\n/**\n * Normalized org/workspace/group shape returned by the provider facade.\n * `name` is the provider identifier to pass to follow-up reads, while `org` is an optional display label.\n */\nexport interface ProviderOrganization {\n\tid: string;\n\tproviderId: IntegrationIds;\n\tname: string;\n\torg?: string;\n\turl: string;\n}\n\n/** Normalized repository shape returned by the provider facade. */\nexport interface ProviderRepositoryShape {\n\tid: string;\n\tnamespace: string;\n\tname: string;\n\t/** Azure DevOps project; `undefined` for hosts without a project layer. */\n\tproject?: string;\n\t/** Web (browser) URL, when the provider exposes it. */\n\turl?: string;\n\t/** HTTPS clone URL, when available. */\n\tcloneUrlHttps?: string;\n\t/** SSH clone URL, when available. */\n\tcloneUrlSsh?: string;\n\t/** Default branch name, when the provider reports it. */\n\tdefaultBranch?: string;\n}\n\n/**\n * Classifies a caught provider exception into a neutral {@link ProviderWarning}. Ordering matters:\n * `instanceof` is checked most-specific-first so a rate-limit isn't mislabeled as a generic error and\n * a 404 isn't mislabeled as auth.\n */\nexport function toProviderWarning(\n\tproviderId: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tex: unknown,\n): ProviderWarning {\n\tlet kind: ProviderWarning['kind'];\n\tif (ex instanceof AuthenticationError) {\n\t\tkind = 'auth';\n\t} else if (ex instanceof RequestRateLimitError) {\n\t\tkind = 'rate-limit';\n\t} else if (ex instanceof RequestNotFoundError) {\n\t\tkind = 'not-found';\n\t} else {\n\t\tkind = 'other';\n\t}\n\n\treturn {\n\t\tproviderId: providerId,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage: providerWarningMessage(ex),\n\t\tkind: kind,\n\t\tisAuth: kind === 'auth',\n\t};\n}\n\n/**\n * A scope's identity, as the stable string that keys it.\n *\n * The single definition of what \"the same scope\" means, shared by the failure and omission dedup keys in\n * `providerPaging.ts` and by {@link providerWarningKey} below, so a scope gaining a field is one edit rather\n * than three. `providerId` is included: the same repository ID under two providers is two scopes.\n *\n * The parameter is structural rather than the SDK's `CollectionScope` so this module keeps naming no\n * `@gitkraken/provider-apis` types (see the export block in `index.ts`) while still serving its SDK-facing\n * callers, which pass that type in unchanged.\n */\nexport function collectionScopeKey(scope: ProviderWarningOmissionScope | undefined): string {\n\treturn [scope?.providerId ?? '', scope?.resourceId ?? '', scope?.projectId ?? '', scope?.repositoryId ?? ''].join(\n\t\t' ',\n\t);\n}\n\n/**\n * The omission's contribution to a warning's identity — empty when there is none, so a warning without an\n * omission keeps deduping exactly as it did before the field existed.\n *\n * Two omissions of different kinds do produce different `message` values today, so message alone would still\n * separate them. That is incidental: the premise of `omission` is that consumers must not depend on prose\n * carrying the distinguishing fact, and this key must not either.\n */\nfunction providerWarningOmissionKey(omission: ProviderWarningOmission | undefined): string {\n\tif (omission == null) return '';\n\n\treturn [\n\t\tomission.kind,\n\t\tomission.recovery,\n\t\tomission.limit ?? '',\n\t\tomission.totalCount ?? '',\n\t\tcollectionScopeKey(omission.scope),\n\t].join(' ');\n}\n\n/**\n * Strips the omission from every warning in `warnings` when the read as a whole failed.\n *\n * An omission asserts the request SUCCEEDED, and a drain only learns it failed AFTER it may have emitted one:\n * an early page can report its own truncation and then a later page can die. Deciding per warning, at the\n * moment each is built, cannot see that future — so the aggregate is reconciled once, here, where\n * `fetchFailed` is final. Call it at the point a read returns its `fetchFailed`.\n *\n * Re-dedupes as it goes: the omission is part of a warning's identity, so two warnings that differed only\n * there become identical once it is gone, and the array's contract is that no two entries are equal.\n *\n * Mutates in place: the warning array is the one being returned, and callers accumulate into it across pages.\n */\nexport function reconcileOmissionsWithFailure(warnings: ProviderWarning[], fetchFailed: boolean): void {\n\tif (!fetchFailed || !warnings.some(w => w.omission != null)) return;\n\n\tconst reconciled: ProviderWarning[] = [];\n\tfor (const warning of warnings) {\n\t\tif (warning.omission == null) {\n\t\t\tappendDedupedWarning(reconciled, warning);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst { omission: _omission, ...rest } = warning;\n\t\tappendDedupedWarning(reconciled, rest);\n\t}\n\n\twarnings.splice(0, warnings.length, ...reconciled);\n}\n\n/**\n * A stable key for deduplicating warnings accumulated across drained pages / fan-out scopes.\n *\n * `message` stays LAST. It is the only free-form segment — provider prose, spaces and all — so anything\n * appended after it could be impersonated by a message that happens to end in the same text.\n */\nfunction providerWarningKey(warning: ProviderWarning): string {\n\treturn [\n\t\twarning.providerId,\n\t\twarning.connectionId ?? '',\n\t\twarning.domain ?? '',\n\t\twarning.kind,\n\t\tproviderWarningOmissionKey(warning.omission),\n\t\twarning.message,\n\t].join(' ');\n}\n\n/**\n * Appends `warning` to `into` only when an equal warning (by provider/connection/domain/kind/message, plus the\n * structured omission when one is present) is absent.\n */\nexport function appendDedupedWarning(into: ProviderWarning[], warning: ProviderWarning): void {\n\tconst key = providerWarningKey(warning);\n\tif (into.some(existing => providerWarningKey(existing) === key)) return;\n\n\tinto.push(warning);\n}\n"],"mappings":";;AAsQA,MAAM,kCAAkC;AAExC,SAAS,uBAAuB,IAAqB;CACpD,MAAM,OAAO,cAAc,QAAQ,GAAG,UAAU,OAAO,EAAE,EAAA,CAAG,KAAK;CACjE,MAAM,UAAU;CAChB,MAAM,SACL,OAAO,SAAS,WAAW,WACxB,QAAQ,SACR,OAAO,SAAS,UAAU,WAAW,WACpC,QAAQ,SAAS,SACjB,KAAA;CAIL,IAAI,yCAAyC,KAAK,GAAG,GACpD,OAAO,UAAU,OAAO,uCAAuC,OAAO,KAAK;CAG5E,IAAI,CAAC,KACJ,OAAO,UAAU,OAAO,uCAAuC,OAAO,KAAK;CAE5E,OAAO,IAAI,SAAS,kCACjB,GAAG,IAAI,MAAM,GAAG,GAAmC,EAAE,OACrD;AACJ;;;;;;AAoCA,SAAgB,kBACf,YACA,QACA,cACA,IACkB;CAClB,IAAI;CACJ,IAAI,cAAc,qBACjB,OAAO;MACD,IAAI,cAAc,uBACxB,OAAO;MACD,IAAI,cAAc,sBACxB,OAAO;MAEP,OAAO;CAGR,OAAO;EACM;EACJ;EACM;EACd,SAAS,uBAAuB,EAAE;EAC5B;EACN,QAAQ,SAAS;CAClB;AACD;;;;;;;;;;;;AAaA,SAAgB,mBAAmB,OAAyD;CAC3F,OAAO;EAAC,OAAO,cAAc;EAAI,OAAO,cAAc;EAAI,OAAO,aAAa;EAAI,OAAO,gBAAgB;CAAE,CAAC,CAAC,KAC5G,GACD;AACD;;;;;;;;;AAUA,SAAS,2BAA2B,UAAuD;CAC1F,IAAI,YAAY,MAAM,OAAO;CAE7B,OAAO;EACN,SAAS;EACT,SAAS;EACT,SAAS,SAAS;EAClB,SAAS,cAAc;EACvB,mBAAmB,SAAS,KAAK;CAClC,CAAC,CAAC,KAAK,GAAG;AACX;;;;;;;;;;;;;;AAeA,SAAgB,8BAA8B,UAA6B,aAA4B;CACtG,IAAI,CAAC,eAAe,CAAC,SAAS,MAAK,MAAK,EAAE,YAAY,IAAI,GAAG;CAE7D,MAAM,aAAgC,CAAC;CACvC,KAAK,MAAM,WAAW,UAAU;EAC/B,IAAI,QAAQ,YAAY,MAAM;GAC7B,qBAAqB,YAAY,OAAO;GACxC;EACD;EAEA,MAAM,EAAE,UAAU,WAAW,GAAG,SAAS;EACzC,qBAAqB,YAAY,IAAI;CACtC;CAEA,SAAS,OAAO,GAAG,SAAS,QAAQ,GAAG,UAAU;AAClD;;;;;;;AAQA,SAAS,mBAAmB,SAAkC;CAC7D,OAAO;EACN,QAAQ;EACR,QAAQ,gBAAgB;EACxB,QAAQ,UAAU;EAClB,QAAQ;EACR,2BAA2B,QAAQ,QAAQ;EAC3C,QAAQ;CACT,CAAC,CAAC,KAAK,GAAG;AACX;;;;;AAMA,SAAgB,qBAAqB,MAAyB,SAAgC;CAC7F,MAAM,MAAM,mBAAmB,OAAO;CACtC,IAAI,KAAK,MAAK,aAAY,mBAAmB,QAAQ,MAAM,GAAG,GAAG;CAEjE,KAAK,KAAK,OAAO;AAClB"}
|
|
1
|
+
{"version":3,"file":"results.js","names":[],"sources":["../../../../plus/integrations/src/results.ts"],"sourcesContent":["import { AuthenticationError, RequestNotFoundError, RequestRateLimitError } from '@gitlens/git/errors.js';\nimport type { IssueSorting } from '@gitlens/git/models/issue.js';\nimport type { PullRequestSorting } from '@gitlens/git/models/pullRequest.js';\nimport type { IntegrationIds } from './constants.js';\n\nexport interface ConnectionStateChangeEvent {\n\tkey: string;\n\treason: 'connected' | 'disconnected';\n}\n\n/**\n * A per-provider, per-connection warning surfaced alongside (partial) read results. Consumers use\n * these to drive auth recovery, retry, or truncation messaging without the read itself throwing.\n *\n * `kind` is the programmatic discriminant (a rate-limit is retryable, a 404 is not, an auth failure\n * needs re-connection unless {@link ProviderWarning.scope} confines it); `isAuth` is retained as a\n * convenience mirror of `kind === 'auth'`.\n */\nexport type ProviderWarningKind = 'auth' | 'rate-limit' | 'not-found' | 'no-connection' | 'other';\n\n/**\n * Why a read that SUCCEEDED still withheld results:\n * - `provider-limit`: the provider refuses to serve past a cap it enforces (GitHub search's 1,000-result\n * ceiling, Trello's `cards_limit`), so the excess is unreachable through that query.\n * - `recovery-budget`: the internal partitioned recovery stopped spending upstream requests before it ran\n * out of partitions to visit.\n * - `pagination-incomplete`: pages were left unread — a sub-scope of a multi-scope read that was not drained,\n * a drain that stopped at its own page backstop, or a provider that advertised another page and gave no way\n * to reach it. These have DIFFERENT remedies, so the kind alone does not say whether more can be fetched;\n * read {@link ProviderWarningOmission.recovery} for that.\n *\n * Mirrors the vocabulary the SDK reports. Declared here rather than imported so this module stays free of\n * `@gitkraken/provider-apis` types (see the export block in `index.ts`); `collectionMetadata.ts` holds the\n * compile-time link, so an SDK bump that adds a kind fails the build at that boundary.\n */\nexport type ProviderWarningOmissionKind = 'provider-limit' | 'recovery-budget' | 'pagination-incomplete';\n\n/**\n * How a consumer can recover omitted results. `page-budget` reruns with a larger budget; `narrow-scope`\n * requests a smaller server-side scope; `none` promises neither. Switch on the exact value because only\n * `page-budget` is a fetch-more action.\n */\nexport type ProviderWarningOmissionRecovery = 'none' | 'page-budget' | 'narrow-scope';\n\n/**\n * The part of a provider a warning is confined to: one resource (organization, workspace, site), project or\n * repository. All fields optional; see {@link ProviderWarning.scope} for when one is forwarded.\n */\nexport interface ProviderWarningScope {\n\t/**\n\t * The resource as the read addressed it: its id on most reads, its name on the few that address it by name\n\t * (Azure DevOps' repo-scoped reads, Bitbucket's workspace reads). Match it against both\n\t * `ProviderOrganization.id` and `name`.\n\t */\n\tresourceId?: string;\n\tprojectId?: string;\n\trepositoryId?: string;\n}\n\n/**\n * Why a provider refused a credential it otherwise accepts, when the refusal itself says so:\n * - `oauth-app-not-allowed`: the organization does not let third-party OAuth apps in. On Azure DevOps this is the\n * organization policy \"Third-party application access via OAuth\", off by default for new organizations. An\n * organization admin enables it, or the user connects with a personal access token instead, which the policy\n * does not govern.\n * - `access-denied`: the account has no access to that organization or project (not a member, or lacking the\n * permission). Someone who administers it has to grant access.\n * - `conditional-access`: a Microsoft Entra Conditional Access policy blocked the request (Azure DevOps\n * `VS403463`). Its admin has to exempt the request, e.g. by location or device.\n *\n * None of these is healed by reconnecting, which is the point of naming them.\n */\nexport type ProviderWarningCauseReason = 'oauth-app-not-allowed' | 'access-denied' | 'conditional-access';\n\n/** See {@link ProviderWarning.cause}. */\nexport interface ProviderWarningCause {\n\treason: ProviderWarningCauseReason;\n\t/** The provider's own error code, when it reports one (Azure DevOps `TF400813`, `VS403463`). */\n\tcode?: string;\n\t/** Where the setting behind the refusal is changed, when this layer can address it. */\n\tremedyUrl?: string;\n}\n\n/** Which repository / project / resource an omission is attributed to. All fields optional; a scope may name none. */\nexport interface ProviderWarningOmissionScope extends ProviderWarningScope {\n\tproviderId?: string;\n}\n\nexport interface ProviderWarningOmission {\n\tkind: ProviderWarningOmissionKind;\n\t/**\n\t * Whether anything would fetch the missing items, and what. Gate a \"load more\" affordance on this rather\n\t * than on `kind` — see {@link ProviderWarningOmissionRecovery}.\n\t */\n\trecovery: ProviderWarningOmissionRecovery;\n\t/**\n\t * The cap the provider enforces, when it reports one. NOT a result count for every kind: on\n\t * `recovery-budget` this is a REQUEST budget and must not be shown to a user as a number of results.\n\t */\n\tlimit?: number;\n\t/** Total matches the provider reported, when it reports one. Only GitHub's search cap does today. */\n\ttotalCount?: number;\n\t/**\n\t * The order the reachable window was selected under, as `field:direction`, when the omission is a result\n\t * ceiling.\n\t *\n\t * Present so a consumer can word its own \"N matched, showing the first M\" without assuming WHICH M: the\n\t * provider serves the top M under this key, so the sentence is only true if it names it. Absent for every\n\t * omission that isn't a ceiling, where no window was selected by an order.\n\t *\n\t * A union of both vocabularies rather than the issue one alone: `PullRequestSorting` happens to be a subset\n\t * today, so narrowing this to {@link IssueSorting} would still compile while describing only half the reads\n\t * that set it — and would break the day the PR search gains a key the issue search has no name for.\n\t */\n\tsort?: IssueSorting | PullRequestSorting;\n\t/** Which repository / project / resource was affected, when one is attributed. */\n\tscope?: ProviderWarningOmissionScope;\n}\n\nexport interface ProviderWarning {\n\tproviderId: IntegrationIds;\n\t/** Disambiguates connections on self-managed hosts (where one provider id spans multiple domains). */\n\tdomain?: string;\n\t/** The specific token/connection the read was pinned to, when a `connectionId` was supplied. */\n\tconnectionId?: string;\n\tmessage: string;\n\tkind: ProviderWarningKind;\n\t/** Convenience mirror of `kind === 'auth'`, scoped or not; read {@link scope} before prompting to reconnect. */\n\tisAuth: boolean;\n\t/**\n\t * Present when the failure was recorded against one sub-scope of the read (an organization, project or\n\t * repository) rather than against the connection. A scoped `auth` failure is that one scope refusing this\n\t * credential, e.g. an Azure DevOps organization that disallows third-party OAuth apps, one in another Entra\n\t * tenant, or a Conditional Access policy. Reconnecting cannot fix those, so this warning alone is no reason\n\t * to prompt for it.\n\t *\n\t * `kind` and `isAuth` are unaffected: a scoped 401 is still an authentication failure, and this field says\n\t * how far it reaches. It names at least one of its IDs, and is never set on an omission (which carries its\n\t * own {@link ProviderWarningOmission.scope}) or on a warning derived from a caught exception (see\n\t * {@link toProviderWarning}), even when that call targeted a single organization.\n\t *\n\t * Its ABSENCE means account-wide or unattributed, so existing handling stays correct without it.\n\t *\n\t * A scoped `auth` failure also means the credential itself was accepted. A dead token can come back as nothing\n\t * but scoped refusals wherever a read reaches its scopes without an uncached request to the connection first:\n\t * discovery served from a per-token cache (Azure DevOps, Bitbucket, Jira Cloud) or an SDK fan-out across the\n\t * requested repositories (Bitbucket Data Center). So when a read's only auth failures are scoped, those providers\n\t * confirm the credential with one uncached check first, and a refused credential fails the whole read with an\n\t * unscoped `auth` warning. A refusal the provider attributes to the credential itself, like Bitbucket's or Jira\n\t * Cloud's for a token missing the OAuth scopes a read needs, which a reconnect fixes, is published unscoped too,\n\t * once for the connection, while the scopes that answered keep their results.\n\t *\n\t * So the promise holds for a read that carries no unscoped `auth` warning of its own; one that does already asks\n\t * for a reconnect, and its other scoped refusals are not checked. Two exceptions stay scoped even then. A check\n\t * that could not complete or was denied (a network error, a throttle, a `403`, Bitbucket Data Center refusing a\n\t * credential it authenticated, or a Bitbucket Data Center project access token, whose user the check cannot\n\t * find) proves nothing, so the warnings are published unconfirmed, with no {@link cause}, and the next read\n\t * checks again. And a credential confirmed within the last minute is not probed again, so a revocation can take\n\t * up to a minute to surface.\n\t */\n\tscope?: ProviderWarningScope;\n\t/**\n\t * Why the provider refused, when the refusal itself says so: see {@link ProviderWarningCauseReason}. Switch on\n\t * `reason` to recommend a fix instead of a reconnect; `message` then carries the same explanation as prose.\n\t *\n\t * Set only on a scoped `auth` warning whose credential was confirmed first (see {@link scope}), because the\n\t * refusals it names are indistinguishable from a dead credential until then: Azure DevOps answers a\n\t * third-party OAuth app its organization disallows exactly as it answers an expired token. Only Azure DevOps\n\t * reports one today. Its absence proves nothing about the cause.\n\t */\n\tcause?: ProviderWarningCause;\n\t/**\n\t * Present when this warning describes results the read could not return even though the request itself\n\t * SUCCEEDED — a provider-enforced cap, an exhausted recovery budget, a page budget, or a sub-scope the read\n\t * did not drain.\n\t *\n\t * Its presence is the signal: an omission is not a failure. Message it as incompleteness rather than as a\n\t * failed read, and do NOT derive that from `message`, which is English prose and subject to rewording.\n\t * `kind` stays `'other'` for these, so the failure discriminant keeps meaning exactly what it meant before\n\t * this field existed.\n\t *\n\t * Whether anything would fetch the rest is a SEPARATE question — read {@link ProviderWarningOmission.recovery},\n\t * not `kind`, and never assume a retry of the same request returns more.\n\t *\n\t * `limit` / `totalCount` / `scope` are forwarded only when they are reported; most omissions carry none of\n\t * the three, so render correctly without them — and see {@link ProviderWarningOmission.limit} before\n\t * showing that figure as a result count.\n\t *\n\t * Its ABSENCE proves nothing. It is never set on a warning derived from a caught exception (see\n\t * {@link toProviderWarning}) or from a structured scope failure — those are failures, and the field would\n\t * be a lie there. But it is also absent whenever incompleteness was reported without naming what was left\n\t * out, so treat a bare `kind: 'other'` warning as unclassified rather than as a proven failure.\n\t */\n\tomission?: ProviderWarningOmission;\n}\n\nexport interface ProviderPageInfo {\n\t/**\n\t * The 1-based POSITION of the returned page. One convention across every paged read, page-number and\n\t * cursor paging alike, so a `getNextPageParam`-style consumer can key off it uniformly:\n\t * - the page number the provider itself reported, when it reports one (authoritative);\n\t * - else the position the caller addressed: the page encoded in a threaded page cursor, or the `page`\n\t * supplied alongside an opaque cursor (1 when the caller supplied no `page`);\n\t * - else, for a read that can be advanced by page number, the requested `page` — the provider either\n\t * honored it or the internal drain reached it (see the paging contract on `IntegrationManager`);\n\t * - else 1: a cursor-only read with no cursor threaded back ignores a requested page number and answers\n\t * with its first page, so echoing the request would mislabel page 1 as page N.\n\t *\n\t * A page past the provider's terminal cursor still reports the requested `page` with empty `items` — an\n\t * empty page N, never page N-1 relabeled.\n\t *\n\t * Continue a cursor-paged read from `cursor`, NOT from `currentPage + 1`: a cursor-only host can't be\n\t * addressed by number, so this is a position label and not always a resumable request parameter.\n\t */\n\tcurrentPage: number;\n\t/**\n\t * The page size IN EFFECT, which is not always the one requested — it is whichever of these the read can\n\t * actually vouch for:\n\t * - the size the provider reported for the page it served (numbered-page hosts), else\n\t * - the `itemsPerPage` that was applied to the request (repo-scoped reads, which forward it), else\n\t * - `items.length`, for a read that takes no page size at all: the account-wide PR/issue reads, `listRepos`\n\t * (cursor-only), and `listIssueTrackerIssuesPage`, whose `itemsPerPage` counts PROJECTS rather than issues.\n\t *\n\t * So it is a description of the page, not an echo of the request: don't derive a total from\n\t * `currentPage * itemsPerPage`, and don't read `items.length < itemsPerPage` as \"last page\" — `hasMore` /\n\t * `cursor` are the only continuation signals.\n\t */\n\titemsPerPage: number;\n\t/**\n\t * A COMPLETENESS assertion, not a mode flag: `true` only when a sweep drained every page of every target\n\t * without truncating or failing (`!truncated && !fetchFailed`). Set exclusively by `sweepPullRequests` /\n\t * `sweepClosedPullRequests` and absent on every paged read, so `allPages !== true` does NOT by itself mean\n\t * \"this was a single page\".\n\t *\n\t * This is the signal to gate on before caching a sweep as authoritative: unlike `truncated` it is `false`\n\t * for BOTH kinds of incompleteness, so it can't be misread as a benign cap.\n\t */\n\tallPages?: boolean;\n\t/**\n\t * True when the read stopped before it could confirm it had everything. Deliberately does NOT distinguish\n\t * WHY, and a consumer must not assume it means a provider ceiling — a sweep sets it from three different\n\t * origins:\n\t * - a provider-side ceiling or per-scope backstop propagated from below (e.g. GitHub Search's 1000-result\n\t * cap): the missing items are NOT reachable, so this really is \"complete to the provider's limit\";\n\t * - the sweep's OWN `maxPages` cap (default 100) with a usable cursor still in hand: the missing items ARE\n\t * reachable and were simply not fetched — raise `maxPages` to get them;\n\t * - the provider reporting another page but handing back no usable cursor: not reachable.\n\t *\n\t * Only the second is recoverable, and a sweep exposes no continuation cursor, so treating a truncated sweep\n\t * as a complete set silently drops reachable data in that case. Prefer `allPages` for the cache decision.\n\t */\n\ttruncated?: boolean;\n}\n\nexport interface ProviderResult<T> {\n\titems: T[];\n\twarnings: ProviderWarning[];\n\t/**\n\t * True when `items` is incomplete — distinguishes a failed or truncated flat read from a genuinely empty\n\t * result. Paged results additionally use `page.truncated` when a completed page could not confirm it\n\t * drained everything.\n\t */\n\tfetchFailed?: boolean;\n}\n\nexport interface ProviderPagedResult<T> extends ProviderResult<T> {\n\tpage: ProviderPageInfo;\n\thasMore: boolean;\n\t/**\n\t * Opaque cursor for cursor-based paging (GraphQL, per-repo/per-project cursors, etc.). For hosts that\n\t * require the previous page's cursor, consumers should pass this value back to the next `cursor` option.\n\t * A retry-only cursor may remain when `hasMore` is false: `hasMore` means automatic forward progress,\n\t * while explicitly reusing such a cursor retries failed work without creating an infinite paging loop.\n\t */\n\tcursor?: string;\n}\n\nexport interface ProviderSweepResult<T> extends ProviderResult<T> {\n\tpage: ProviderPageInfo;\n\thasMore: boolean;\n\t/**\n\t * Providers whose top-level read failed before returning any usable page. Partial per-scope failures stay\n\t * represented by `warnings` + `fetchFailed` and are intentionally excluded.\n\t */\n\tfailedProviderIds: IntegrationIds[];\n\t/**\n\t * Providers whose read started successfully but could not produce an authoritative complete slice (for\n\t * example, a later page failed, collection metadata reported a partial scope, or a paging backstop was hit).\n\t * Kept separate from\n\t * `failedProviderIds` so consumers can accept healthy sibling providers while preserving an older snapshot\n\t * for a provider whose returned slice has a gap.\n\t */\n\tincompleteProviderIds: IntegrationIds[];\n}\n\nexport interface ProviderBroadenResult<T> extends ProviderPagedResult<T> {\n\tbroadenedProviderIds: IntegrationIds[];\n\t/** Providers for which no requested org produced a usable issue result. */\n\tfailedProviderIds: IntegrationIds[];\n\t/** Providers with both a usable org result and at least one failed or truncated org slice. */\n\tincompleteProviderIds: IntegrationIds[];\n\tfanOutCount: number;\n}\n\nexport type RepositoryResolutionStatus =\n\t| 'resolved'\n\t| 'not-found'\n\t| 'unauthorized'\n\t| 'unsupported-provider'\n\t| 'invalid-remote-url'\n\t| 'host-mismatch'\n\t| 'undetermined';\n\nexport interface RepositoryIdentity {\n\tproviderId: IntegrationIds;\n\tdomain: string;\n\t/** The provider's canonical owner/namespace (follows renames), falling back to the parsed remote when omitted. */\n\towner: string;\n\t/** The provider's canonical repo name (follows renames), falling back to the parsed remote when omitted. */\n\tname: string;\n\tproject?: string;\n\t/** The original input remote URL, so the caller can key the resolution back to the remote it asked about. */\n\tremoteUrl: string;\n\t/**\n\t * True when the provider's canonical owner/name differ from what the input remote URL carried — i.e. the\n\t * repo was renamed/moved host-side and the local remote is stale. Case-insensitive, mirroring gkcli's\n\t * `EqualFold` compare so hosts that echo the input casing (e.g. Bitbucket Server/Azure) aren't flagged.\n\t */\n\trenamed: boolean;\n}\n\nexport interface RepositoryResolution {\n\tstatus: RepositoryResolutionStatus;\n\tidentity?: RepositoryIdentity;\n\twarning?: ProviderWarning;\n}\n\nexport interface ResolveRepositoryResult {\n\tresolution: RepositoryResolution;\n}\n\nconst maxProviderWarningMessageLength = 500;\n\n/** A caught error's message as a warning can carry it: an HTML page becomes its status, and prose is capped. */\nexport function providerWarningMessage(ex: unknown): string {\n\tconst raw = (ex instanceof Error ? ex.message : String(ex)).trim();\n\tconst carrier = ex as { status?: unknown; response?: { status?: unknown } } | null | undefined;\n\tconst status =\n\t\ttypeof carrier?.status === 'number'\n\t\t\t? carrier.status\n\t\t\t: typeof carrier?.response?.status === 'number'\n\t\t\t\t? carrier.response.status\n\t\t\t\t: undefined;\n\n\t// Gateways commonly answer API requests with a complete HTML error page. That payload is neither actionable\n\t// nor suitable for a warning DTO consumed by UIs and logs; preserve the status without leaking the document.\n\tif (/<(?:!doctype\\s+html|html|head|body)\\b/i.test(raw)) {\n\t\treturn status != null ? `Provider request failed with status ${status}.` : 'Provider request failed.';\n\t}\n\n\tif (!raw) {\n\t\treturn status != null ? `Provider request failed with status ${status}.` : 'Provider request failed.';\n\t}\n\treturn raw.length > maxProviderWarningMessageLength\n\t\t? `${raw.slice(0, maxProviderWarningMessageLength - 3)}...`\n\t\t: raw;\n}\n\n/**\n * Normalized org/workspace/group shape returned by the provider facade.\n * `name` is the provider identifier to pass to follow-up reads, while `org` is an optional display label.\n */\nexport interface ProviderOrganization {\n\tid: string;\n\tproviderId: IntegrationIds;\n\tname: string;\n\torg?: string;\n\turl: string;\n}\n\n/** Normalized repository shape returned by the provider facade. */\nexport interface ProviderRepositoryShape {\n\tid: string;\n\tnamespace: string;\n\tname: string;\n\t/** Azure DevOps project; `undefined` for hosts without a project layer. */\n\tproject?: string;\n\t/** Web (browser) URL, when the provider exposes it. */\n\turl?: string;\n\t/** HTTPS clone URL, when available. */\n\tcloneUrlHttps?: string;\n\t/** SSH clone URL, when available. */\n\tcloneUrlSsh?: string;\n\t/** Default branch name, when the provider reports it. */\n\tdefaultBranch?: string;\n}\n\n/**\n * Classifies a caught provider exception into a neutral {@link ProviderWarning}. Ordering matters:\n * `instanceof` is checked most-specific-first so a rate-limit isn't mislabeled as a generic error and\n * a 404 isn't mislabeled as auth.\n */\nexport function toProviderWarning(\n\tproviderId: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tex: unknown,\n): ProviderWarning {\n\tlet kind: ProviderWarning['kind'];\n\tif (ex instanceof AuthenticationError) {\n\t\tkind = 'auth';\n\t} else if (ex instanceof RequestRateLimitError) {\n\t\tkind = 'rate-limit';\n\t} else if (ex instanceof RequestNotFoundError) {\n\t\tkind = 'not-found';\n\t} else {\n\t\tkind = 'other';\n\t}\n\n\treturn {\n\t\tproviderId: providerId,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage: providerWarningMessage(ex),\n\t\tkind: kind,\n\t\tisAuth: kind === 'auth',\n\t};\n}\n\n/**\n * A scope's identity, as the stable string that keys it.\n *\n * The single definition of what \"the same scope\" means, shared by the failure and omission dedup keys in\n * `providerPaging.ts` and by {@link providerWarningKey} below, so a scope gaining a field is one edit rather\n * than three. `providerId` is included: the same repository ID under two providers is two scopes.\n *\n * The parameter is structural rather than the SDK's `CollectionScope` so this module keeps naming no\n * `@gitkraken/provider-apis` types (see the export block in `index.ts`) while still serving its SDK-facing\n * callers, which pass that type in unchanged.\n */\nexport function collectionScopeKey(scope: ProviderWarningOmissionScope | undefined): string {\n\treturn [scope?.providerId ?? '', scope?.resourceId ?? '', scope?.projectId ?? '', scope?.repositoryId ?? ''].join(\n\t\t' ',\n\t);\n}\n\n/**\n * The omission's contribution to a warning's identity — empty when there is none, so a warning without an\n * omission keeps deduping exactly as it did before the field existed.\n *\n * Two omissions of different kinds do produce different `message` values today, so message alone would still\n * separate them. That is incidental: the premise of `omission` is that consumers must not depend on prose\n * carrying the distinguishing fact, and this key must not either.\n */\nfunction providerWarningOmissionKey(omission: ProviderWarningOmission | undefined): string {\n\tif (omission == null) return '';\n\n\treturn [\n\t\tomission.kind,\n\t\tomission.recovery,\n\t\tomission.limit ?? '',\n\t\tomission.totalCount ?? '',\n\t\tcollectionScopeKey(omission.scope),\n\t].join(' ');\n}\n\n/**\n * Strips the omission from every warning in `warnings` when the read as a whole failed.\n *\n * An omission asserts the request SUCCEEDED, and a drain only learns it failed AFTER it may have emitted one:\n * an early page can report its own truncation and then a later page can die. Deciding per warning, at the\n * moment each is built, cannot see that future — so the aggregate is reconciled once, here, where\n * `fetchFailed` is final. Call it at the point a read returns its `fetchFailed`.\n *\n * Re-dedupes as it goes: the omission is part of a warning's identity, so two warnings that differed only\n * there become identical once it is gone, and the array's contract is that no two entries are equal.\n *\n * Mutates in place: the warning array is the one being returned, and callers accumulate into it across pages.\n */\nexport function reconcileOmissionsWithFailure(warnings: ProviderWarning[], fetchFailed: boolean): void {\n\tif (!fetchFailed || !warnings.some(w => w.omission != null)) return;\n\n\tconst reconciled: ProviderWarning[] = [];\n\tfor (const warning of warnings) {\n\t\tif (warning.omission == null) {\n\t\t\tappendDedupedWarning(reconciled, warning);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst { omission: _omission, ...rest } = warning;\n\t\tappendDedupedWarning(reconciled, rest);\n\t}\n\n\twarnings.splice(0, warnings.length, ...reconciled);\n}\n\n/**\n * A stable key for deduplicating warnings accumulated across drained pages / fan-out scopes.\n *\n * The scope and the cause are keyed for the reason the omission is (see {@link providerWarningOmissionKey}): today's\n * failure messages happen to spell them out, but two failures that differ in either must stay two warnings even if\n * they don't.\n *\n * `message` stays LAST. It is the only free-form segment — provider prose, spaces and all — so anything\n * appended after it could be impersonated by a message that happens to end in the same text.\n */\nfunction providerWarningKey(warning: ProviderWarning): string {\n\treturn [\n\t\twarning.providerId,\n\t\twarning.connectionId ?? '',\n\t\twarning.domain ?? '',\n\t\twarning.kind,\n\t\tcollectionScopeKey(warning.scope),\n\t\twarning.cause?.reason ?? '',\n\t\tproviderWarningOmissionKey(warning.omission),\n\t\twarning.message,\n\t].join(' ');\n}\n\n/**\n * Appends `warning` to `into` only when an equal warning (by provider/connection/domain/kind/message, plus the\n * structured scope, cause and omission when present) is absent.\n */\nexport function appendDedupedWarning(into: ProviderWarning[], warning: ProviderWarning): void {\n\tconst key = providerWarningKey(warning);\n\tif (into.some(existing => providerWarningKey(existing) === key)) return;\n\n\tinto.push(warning);\n}\n"],"mappings":";;AAqVA,MAAM,kCAAkC;;AAGxC,SAAgB,uBAAuB,IAAqB;CAC3D,MAAM,OAAO,cAAc,QAAQ,GAAG,UAAU,OAAO,EAAE,EAAA,CAAG,KAAK;CACjE,MAAM,UAAU;CAChB,MAAM,SACL,OAAO,SAAS,WAAW,WACxB,QAAQ,SACR,OAAO,SAAS,UAAU,WAAW,WACpC,QAAQ,SAAS,SACjB,KAAA;CAIL,IAAI,yCAAyC,KAAK,GAAG,GACpD,OAAO,UAAU,OAAO,uCAAuC,OAAO,KAAK;CAG5E,IAAI,CAAC,KACJ,OAAO,UAAU,OAAO,uCAAuC,OAAO,KAAK;CAE5E,OAAO,IAAI,SAAS,kCACjB,GAAG,IAAI,MAAM,GAAG,GAAmC,EAAE,OACrD;AACJ;;;;;;AAoCA,SAAgB,kBACf,YACA,QACA,cACA,IACkB;CAClB,IAAI;CACJ,IAAI,cAAc,qBACjB,OAAO;MACD,IAAI,cAAc,uBACxB,OAAO;MACD,IAAI,cAAc,sBACxB,OAAO;MAEP,OAAO;CAGR,OAAO;EACM;EACJ;EACM;EACd,SAAS,uBAAuB,EAAE;EAC5B;EACN,QAAQ,SAAS;CAClB;AACD;;;;;;;;;;;;AAaA,SAAgB,mBAAmB,OAAyD;CAC3F,OAAO;EAAC,OAAO,cAAc;EAAI,OAAO,cAAc;EAAI,OAAO,aAAa;EAAI,OAAO,gBAAgB;CAAE,CAAC,CAAC,KAC5G,GACD;AACD;;;;;;;;;AAUA,SAAS,2BAA2B,UAAuD;CAC1F,IAAI,YAAY,MAAM,OAAO;CAE7B,OAAO;EACN,SAAS;EACT,SAAS;EACT,SAAS,SAAS;EAClB,SAAS,cAAc;EACvB,mBAAmB,SAAS,KAAK;CAClC,CAAC,CAAC,KAAK,GAAG;AACX;;;;;;;;;;;;;;AAeA,SAAgB,8BAA8B,UAA6B,aAA4B;CACtG,IAAI,CAAC,eAAe,CAAC,SAAS,MAAK,MAAK,EAAE,YAAY,IAAI,GAAG;CAE7D,MAAM,aAAgC,CAAC;CACvC,KAAK,MAAM,WAAW,UAAU;EAC/B,IAAI,QAAQ,YAAY,MAAM;GAC7B,qBAAqB,YAAY,OAAO;GACxC;EACD;EAEA,MAAM,EAAE,UAAU,WAAW,GAAG,SAAS;EACzC,qBAAqB,YAAY,IAAI;CACtC;CAEA,SAAS,OAAO,GAAG,SAAS,QAAQ,GAAG,UAAU;AAClD;;;;;;;;;;;AAYA,SAAS,mBAAmB,SAAkC;CAC7D,OAAO;EACN,QAAQ;EACR,QAAQ,gBAAgB;EACxB,QAAQ,UAAU;EAClB,QAAQ;EACR,mBAAmB,QAAQ,KAAK;EAChC,QAAQ,OAAO,UAAU;EACzB,2BAA2B,QAAQ,QAAQ;EAC3C,QAAQ;CACT,CAAC,CAAC,KAAK,GAAG;AACX;;;;;AAMA,SAAgB,qBAAqB,MAAyB,SAAgC;CAC7F,MAAM,MAAM,mBAAmB,OAAO;CACtC,IAAI,KAAK,MAAK,aAAY,mBAAmB,QAAQ,MAAM,GAAG,GAAG;CAEjE,KAAK,KAAK,OAAO;AAClB"}
|