@gitkraken/core-gitlens 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -1
- package/dist/git/cache.d.ts +18 -5
- package/dist/git/cache.js +26 -8
- package/dist/git/cache.js.map +1 -1
- package/dist/git/context.d.ts +5 -1
- package/dist/git/context.js.map +1 -1
- package/dist/git/errors.d.ts +28 -1
- package/dist/git/errors.js +45 -1
- package/dist/git/errors.js.map +1 -1
- package/dist/git/providers/branches.d.ts +5 -1
- package/dist/git/providers/commits.d.ts +24 -1
- package/dist/git/providers/config.d.ts +7 -1
- package/dist/git/providers/operations.d.ts +6 -0
- package/dist/git/providers/provider.d.ts +17 -0
- package/dist/git/providers/refs.d.ts +38 -3
- package/dist/git/providers/revision.d.ts +5 -1
- package/dist/git/providers/staging.d.ts +12 -0
- package/dist/git/providers/worktrees.d.ts +23 -3
- package/dist/git/run.types.d.ts +6 -0
- package/dist/git-cli/cliGitProvider.d.ts +17 -1
- package/dist/git-cli/cliGitProvider.js +37 -1
- package/dist/git-cli/cliGitProvider.js.map +1 -1
- package/dist/git-cli/exec/git.d.ts +12 -2
- package/dist/git-cli/exec/git.js +48 -17
- package/dist/git-cli/exec/git.js.map +1 -1
- package/dist/git-cli/exec/gitQueue.d.ts +4 -1
- package/dist/git-cli/exec/gitQueue.js +1 -1
- package/dist/git-cli/exec/gitQueue.js.map +1 -1
- package/dist/git-cli/providers/branches.d.ts +10 -1
- package/dist/git-cli/providers/branches.js +15 -8
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/git-cli/providers/commits.d.ts +23 -1
- package/dist/git-cli/providers/commits.js +57 -8
- package/dist/git-cli/providers/commits.js.map +1 -1
- package/dist/git-cli/providers/config.js +4 -3
- package/dist/git-cli/providers/config.js.map +1 -1
- package/dist/git-cli/providers/operations.d.ts +1 -0
- package/dist/git-cli/providers/operations.js +39 -3
- package/dist/git-cli/providers/operations.js.map +1 -1
- package/dist/git-cli/providers/patch.js +20 -41
- package/dist/git-cli/providers/patch.js.map +1 -1
- package/dist/git-cli/providers/pausedOperations.js +2 -2
- package/dist/git-cli/providers/pausedOperations.js.map +1 -1
- package/dist/git-cli/providers/refs.d.ts +15 -3
- package/dist/git-cli/providers/refs.js +115 -10
- package/dist/git-cli/providers/refs.js.map +1 -1
- package/dist/git-cli/providers/revision.d.ts +3 -1
- package/dist/git-cli/providers/revision.js +9 -5
- package/dist/git-cli/providers/revision.js.map +1 -1
- package/dist/git-cli/providers/staging.d.ts +8 -0
- package/dist/git-cli/providers/staging.js +59 -7
- package/dist/git-cli/providers/staging.js.map +1 -1
- package/dist/git-cli/providers/worktrees.d.ts +31 -2
- package/dist/git-cli/providers/worktrees.js +75 -7
- package/dist/git-cli/providers/worktrees.js.map +1 -1
- package/dist/ipc/discovery.js +1 -1
- package/dist/plus/agents/providers/claudeCodeTranscript.js +6 -6
- package/dist/plus/agents/providers/claudeCodeTranscript.js.map +1 -1
- package/dist/plus/ai/providers/openAICompatibleProviderBase.js +1 -1
- package/dist/plus/git-github/api/github.d.ts +64 -3
- package/dist/plus/git-github/api/github.js +237 -24
- package/dist/plus/git-github/api/github.js.map +1 -1
- package/dist/plus/git-github/providers/github/branches.d.ts +3 -1
- package/dist/plus/git-github/providers/github/branches.js +2 -1
- package/dist/plus/git-github/providers/github/branches.js.map +1 -1
- package/dist/plus/git-github/providers/github/refs.d.ts +10 -3
- package/dist/plus/git-github/providers/github/refs.js +4 -4
- package/dist/plus/git-github/providers/github/refs.js.map +1 -1
- package/dist/plus/git-github/providers/github/revision.d.ts +3 -1
- package/dist/plus/git-github/providers/github/revision.js +1 -1
- package/dist/plus/git-github/providers/github/revision.js.map +1 -1
- package/dist/plus/integrations/collectionMetadata.d.ts +48 -3
- package/dist/plus/integrations/collectionMetadata.js +138 -8
- package/dist/plus/integrations/collectionMetadata.js.map +1 -1
- package/dist/plus/integrations/errors.d.ts +3 -1
- package/dist/plus/integrations/errors.js +7 -3
- package/dist/plus/integrations/errors.js.map +1 -1
- package/dist/plus/integrations/index.d.ts +6 -3
- package/dist/plus/integrations/index.js.map +1 -1
- package/dist/plus/integrations/integrationService.d.ts +36 -10
- package/dist/plus/integrations/integrationService.js +16 -3
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/manager.d.ts +107 -37
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +129 -14
- package/dist/plus/integrations/models/gitHostIntegration.js +142 -18
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/integration.d.ts +117 -11
- package/dist/plus/integrations/models/integration.js +175 -13
- package/dist/plus/integrations/models/integration.js.map +1 -1
- package/dist/plus/integrations/models/issuesIntegration.d.ts +18 -5
- package/dist/plus/integrations/models/issuesIntegration.js +42 -10
- package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.d.ts +1 -1
- package/dist/plus/integrations/providers/azure/azure.js +14 -18
- package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
- package/dist/plus/integrations/providers/azure/models.d.ts +29 -0
- package/dist/plus/integrations/providers/azure/models.js +112 -1
- package/dist/plus/integrations/providers/azure/models.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOps.d.ts +73 -3
- package/dist/plus/integrations/providers/azureDevOps.js +117 -3
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.js +2 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket/bitbucket.d.ts +61 -0
- package/dist/plus/integrations/providers/bitbucket/bitbucket.js +165 -23
- package/dist/plus/integrations/providers/bitbucket/bitbucket.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket/models.d.ts +6 -1
- package/dist/plus/integrations/providers/bitbucket/models.js +4 -3
- package/dist/plus/integrations/providers/bitbucket/models.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js +2 -2
- package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket-server.d.ts +48 -2
- package/dist/plus/integrations/providers/bitbucket-server.js +51 -1
- package/dist/plus/integrations/providers/bitbucket-server.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket.d.ts +45 -2
- package/dist/plus/integrations/providers/bitbucket.js +41 -2
- package/dist/plus/integrations/providers/bitbucket.js.map +1 -1
- package/dist/plus/integrations/providers/github.d.ts +49 -5
- package/dist/plus/integrations/providers/github.js +102 -7
- package/dist/plus/integrations/providers/github.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab/gitlab.d.ts +35 -0
- package/dist/plus/integrations/providers/gitlab/gitlab.js +132 -15
- package/dist/plus/integrations/providers/gitlab/gitlab.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab/models.d.ts +1 -0
- package/dist/plus/integrations/providers/gitlab/models.js +1 -1
- package/dist/plus/integrations/providers/gitlab/models.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab.d.ts +49 -2
- package/dist/plus/integrations/providers/gitlab.js +80 -4
- package/dist/plus/integrations/providers/gitlab.js.map +1 -1
- package/dist/plus/integrations/providers/jira-server.d.ts +3 -3
- package/dist/plus/integrations/providers/jira-server.js +5 -8
- package/dist/plus/integrations/providers/jira-server.js.map +1 -1
- package/dist/plus/integrations/providers/jira.d.ts +9 -0
- package/dist/plus/integrations/providers/jira.js +13 -1
- package/dist/plus/integrations/providers/jira.js.map +1 -1
- package/dist/plus/integrations/providers/linear.js +1 -1
- package/dist/plus/integrations/providers/models.d.ts +13 -2
- package/dist/plus/integrations/providers/models.js +11 -1
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/providers/providerErrors.d.ts +9 -1
- package/dist/plus/integrations/providers/providerErrors.js +10 -1
- package/dist/plus/integrations/providers/providerErrors.js.map +1 -1
- package/dist/plus/integrations/providers/providersApi.d.ts +60 -1
- package/dist/plus/integrations/providers/providersApi.js +137 -2
- package/dist/plus/integrations/providers/providersApi.js.map +1 -1
- package/dist/plus/integrations/providers/utils/providerPaging.d.ts +42 -0
- package/dist/plus/integrations/providers/utils/providerPaging.js +58 -1
- package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
- package/dist/plus/integrations/providers/utils.d.ts +1 -1
- package/dist/plus/integrations/reads/broaden.js +1 -1
- package/dist/plus/integrations/reads/context.d.ts +1 -1
- package/dist/plus/integrations/reads/counts.js +1 -1
- package/dist/plus/integrations/reads/currentAccount.d.ts +35 -0
- package/dist/plus/integrations/reads/currentAccount.js +46 -0
- package/dist/plus/integrations/reads/currentAccount.js.map +1 -0
- package/dist/plus/integrations/reads/drains.d.ts +5 -2
- package/dist/plus/integrations/reads/drains.js +8 -4
- package/dist/plus/integrations/reads/drains.js.map +1 -1
- package/dist/plus/integrations/reads/hierarchy.js +1 -1
- package/dist/plus/integrations/reads/hierarchy.utils.js +1 -1
- package/dist/plus/integrations/reads/issueBatch.d.ts +72 -12
- package/dist/plus/integrations/reads/issueBatch.js +193 -48
- package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.js +1 -1
- package/dist/plus/integrations/reads/issues.js +1 -1
- package/dist/plus/integrations/reads/pullRequestBatch.d.ts +57 -0
- package/dist/plus/integrations/reads/pullRequestBatch.js +80 -0
- package/dist/plus/integrations/reads/pullRequestBatch.js.map +1 -0
- package/dist/plus/integrations/reads/pullRequestBranches.d.ts +58 -0
- package/dist/plus/integrations/reads/pullRequestBranches.js +111 -0
- package/dist/plus/integrations/reads/pullRequestBranches.js.map +1 -0
- package/dist/plus/integrations/reads/pullRequests.js +3 -3
- package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
- package/dist/plus/integrations/reads/resolveRepository.js +1 -1
- package/dist/plus/integrations/reads/sweeps.js +4 -4
- package/dist/plus/integrations/reads/sweeps.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +87 -6
- package/dist/plus/integrations/results.js +9 -2
- package/dist/plus/integrations/results.js.map +1 -1
- package/dist/utils/env/node/exec.js +3 -3
- package/dist/utils/env/node/exec.js.map +1 -1
- package/docs/integrations.md +175 -35
- package/docs/kepler-git-write-api-parity.md +239 -0
- package/docs/kepler-read-api-parity.md +75 -28
- package/package.json +1 -1
- package/dist/plus/integrations/reads/trackerIssue.d.ts +0 -33
- package/dist/plus/integrations/reads/trackerIssue.js +0 -60
- package/dist/plus/integrations/reads/trackerIssue.js.map +0 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PullRequestShape } from "../../../../git/models/pullRequest.js";
|
|
1
2
|
import { ProviderApiPagedResult, ProviderHierarchyResult } from "../models.js";
|
|
2
3
|
import { CollectionMetadata, CollectionScopeFailure } from "@gitkraken/provider-apis";
|
|
3
4
|
//#region ../plus/integrations/src/providers/utils/providerPaging.d.ts
|
|
@@ -11,6 +12,47 @@ export declare function toPageCursor(page: number): string;
|
|
|
11
12
|
export declare function parsePageCursor(cursor: string | undefined): number | undefined;
|
|
12
13
|
/** Preserves successful sibling scopes, but doesn't turn an all-scope provider failure into an empty success. */
|
|
13
14
|
export declare function throwIfAllSettledFailed<T>(results: PromiseSettledResult<T>[]): void;
|
|
15
|
+
/**
|
|
16
|
+
* The answer a pull-requests-by-branch read gives for one branch: the rows `matchesHead` accepts, newest first, at
|
|
17
|
+
* most `limit` of them, each passed through `map`. Rows are filtered before they're mapped, so a non-matching row
|
|
18
|
+
* can't fail the answer. `more` says the host holds rows it didn't return; the filter can't see those, so they make
|
|
19
|
+
* the answer `truncated` however few rows passed it.
|
|
20
|
+
*/
|
|
21
|
+
export declare function selectBranchPullRequests<T, R>(rows: readonly T[], options: {
|
|
22
|
+
matchesHead: (row: T) => boolean;
|
|
23
|
+
updatedAt: (row: T) => number;
|
|
24
|
+
map: (row: T) => R;
|
|
25
|
+
limit: number;
|
|
26
|
+
more: boolean;
|
|
27
|
+
}): {
|
|
28
|
+
values: R[];
|
|
29
|
+
truncated: boolean;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* The second step of a pull-requests-by-branch read on a host whose branch query can't return the batch read's
|
|
33
|
+
* rows: resolves each target's matched numbers through `resolve` (the host's `getProviderPullRequestsBatch`), so a
|
|
34
|
+
* pull request comes back identical whichever read found it.
|
|
35
|
+
*
|
|
36
|
+
* One `resolve` call for every target's numbers, so the host's own fan-out bounds the concurrency. A number it
|
|
37
|
+
* reports absent (deleted between the two steps) is dropped; one it couldn't check rejects the whole target, since
|
|
38
|
+
* a partial list would read as a complete one. The first step's order is kept.
|
|
39
|
+
*/
|
|
40
|
+
export declare function resolveBranchPullRequests(targets: readonly {
|
|
41
|
+
owner: string;
|
|
42
|
+
repo: string;
|
|
43
|
+
project?: string;
|
|
44
|
+
}[], found: readonly PromiseSettledResult<{
|
|
45
|
+
numbers: number[];
|
|
46
|
+
truncated: boolean;
|
|
47
|
+
}>[], resolve: (coordinates: {
|
|
48
|
+
owner: string;
|
|
49
|
+
repo: string;
|
|
50
|
+
number: number;
|
|
51
|
+
project?: string;
|
|
52
|
+
}[]) => Promise<PromiseSettledResult<PullRequestShape | undefined>[] | undefined>): Promise<PromiseSettledResult<{
|
|
53
|
+
pullRequests: PullRequestShape[];
|
|
54
|
+
truncated: boolean;
|
|
55
|
+
}>[]>;
|
|
14
56
|
export declare function flatSettledResultsOrThrow<T>(results: PromiseSettledResult<T[]>[]): T[];
|
|
15
57
|
export declare function flatSettledOrThrow<T>(promises: Promise<T[]>[]): Promise<T[]>;
|
|
16
58
|
/**
|
|
@@ -28,6 +28,63 @@ function throwIfAllSettledFailed(results) {
|
|
|
28
28
|
const rejected = results.find((result) => result.status === "rejected");
|
|
29
29
|
if (rejected != null) throw rejected.reason;
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* The answer a pull-requests-by-branch read gives for one branch: the rows `matchesHead` accepts, newest first, at
|
|
33
|
+
* most `limit` of them, each passed through `map`. Rows are filtered before they're mapped, so a non-matching row
|
|
34
|
+
* can't fail the answer. `more` says the host holds rows it didn't return; the filter can't see those, so they make
|
|
35
|
+
* the answer `truncated` however few rows passed it.
|
|
36
|
+
*/
|
|
37
|
+
function selectBranchPullRequests(rows, options) {
|
|
38
|
+
const matched = rows.filter(options.matchesHead).sort((a, b) => options.updatedAt(b) - options.updatedAt(a));
|
|
39
|
+
return {
|
|
40
|
+
values: matched.slice(0, options.limit).map(options.map),
|
|
41
|
+
truncated: options.more || matched.length > options.limit
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The second step of a pull-requests-by-branch read on a host whose branch query can't return the batch read's
|
|
46
|
+
* rows: resolves each target's matched numbers through `resolve` (the host's `getProviderPullRequestsBatch`), so a
|
|
47
|
+
* pull request comes back identical whichever read found it.
|
|
48
|
+
*
|
|
49
|
+
* One `resolve` call for every target's numbers, so the host's own fan-out bounds the concurrency. A number it
|
|
50
|
+
* reports absent (deleted between the two steps) is dropped; one it couldn't check rejects the whole target, since
|
|
51
|
+
* a partial list would read as a complete one. The first step's order is kept.
|
|
52
|
+
*/
|
|
53
|
+
async function resolveBranchPullRequests(targets, found, resolve) {
|
|
54
|
+
const coordinates = found.flatMap((slot, i) => slot.status === "fulfilled" ? slot.value.numbers.map((number) => ({
|
|
55
|
+
owner: targets[i].owner,
|
|
56
|
+
repo: targets[i].repo,
|
|
57
|
+
number,
|
|
58
|
+
project: targets[i].project
|
|
59
|
+
})) : []);
|
|
60
|
+
let resolved = [];
|
|
61
|
+
if (coordinates.length > 0) try {
|
|
62
|
+
resolved = await resolve(coordinates) ?? coordinates.map(() => ({
|
|
63
|
+
status: "rejected",
|
|
64
|
+
reason: /* @__PURE__ */ new Error("Pull requests could not be resolved")
|
|
65
|
+
}));
|
|
66
|
+
} catch (ex) {
|
|
67
|
+
resolved = coordinates.map(() => ({
|
|
68
|
+
status: "rejected",
|
|
69
|
+
reason: ex
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
let offset = 0;
|
|
73
|
+
return found.map((slot) => {
|
|
74
|
+
if (slot.status === "rejected") return slot;
|
|
75
|
+
const slots = resolved.slice(offset, offset + slot.value.numbers.length);
|
|
76
|
+
offset += slot.value.numbers.length;
|
|
77
|
+
const failure = slots.find((s) => s.status === "rejected");
|
|
78
|
+
if (failure != null) return failure;
|
|
79
|
+
return {
|
|
80
|
+
status: "fulfilled",
|
|
81
|
+
value: {
|
|
82
|
+
pullRequests: slots.flatMap((s) => s.status === "fulfilled" && s.value != null ? [s.value] : []),
|
|
83
|
+
truncated: slot.value.truncated
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
}
|
|
31
88
|
function flatSettledResultsOrThrow(results) {
|
|
32
89
|
throwIfAllSettledFailed(results);
|
|
33
90
|
return results.filter((result) => result.status === "fulfilled").flatMap((result) => result.value);
|
|
@@ -146,6 +203,6 @@ async function collectProviderPagedResult(fetch, maxPages = 20, scope) {
|
|
|
146
203
|
return build();
|
|
147
204
|
}
|
|
148
205
|
//#endregion
|
|
149
|
-
export { collectProviderPagedResult, flatSettledOrThrow, flatSettledResultsOrThrow, mergeCollectionMetadata, parsePageCursor, throwIfAllSettledFailed, toPageCursor };
|
|
206
|
+
export { collectProviderPagedResult, flatSettledOrThrow, flatSettledResultsOrThrow, mergeCollectionMetadata, parsePageCursor, resolveBranchPullRequests, selectBranchPullRequests, throwIfAllSettledFailed, toPageCursor };
|
|
150
207
|
|
|
151
208
|
//# sourceMappingURL=providerPaging.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"providerPaging.js","names":[],"sources":["../../../../../../plus/integrations/src/providers/utils/providerPaging.ts"],"sourcesContent":["import type {\n\tCollectionCompleteness,\n\tCollectionMetadata,\n\tCollectionOmission,\n\tCollectionScopeFailure,\n} from '@gitkraken/provider-apis';\nimport { isCancellationError } from '@gitlens/utils/cancellation.js';\nimport { uniqueBy } from '@gitlens/utils/iterable.js';\nimport { throwIfCallerContractError, toCollectionScopeFailure } from '../../collectionMetadata.js';\nimport { collectionScopeKey } from '../../results.js';\nimport type { ProviderApiPagedResult, ProviderHierarchyResult } from '../models.js';\n\n/**\n * Encodes an opaque numeric paging token as the `{ value, type: 'page' }` cursor the paging layer uses.\n * The value is whatever the consumer round-trips unchanged: a 1-based page number for numbered-page reads,\n * or a provider offset (e.g. Bitbucket Server's `nextPageStart`) that is never reinterpreted here.\n */\nexport function toPageCursor(page: number): string {\n\treturn JSON.stringify({ value: page, type: 'page' });\n}\n\n/** Extracts the numeric paging token from a `{ value, type: 'page' }` cursor; undefined when absent/malformed. */\nexport function parsePageCursor(cursor: string | undefined): number | undefined {\n\tif (cursor == null || cursor === '{}') return undefined;\n\n\ttry {\n\t\tconst parsed = JSON.parse(cursor) as { value?: unknown; type?: unknown };\n\t\tif (parsed.type === 'page' && typeof parsed.value === 'number') return parsed.value;\n\t} catch {}\n\n\treturn undefined;\n}\n\n/** Preserves successful sibling scopes, but doesn't turn an all-scope provider failure into an empty success. */\nexport function throwIfAllSettledFailed<T>(results: PromiseSettledResult<T>[]): void {\n\tif (results.some(result => result.status === 'fulfilled')) return;\n\n\tconst rejected = results.find((result): result is PromiseRejectedResult => result.status === 'rejected');\n\tif (rejected != null) throw rejected.reason;\n}\n\nexport function flatSettledResultsOrThrow<T>(results: PromiseSettledResult<T[]>[]): T[] {\n\tthrowIfAllSettledFailed(results);\n\n\tconst fulfilled = results.filter((result): result is PromiseFulfilledResult<T[]> => result.status === 'fulfilled');\n\treturn fulfilled.flatMap(result => result.value);\n}\n\nexport async function flatSettledOrThrow<T>(promises: Promise<T[]>[]): Promise<T[]> {\n\treturn flatSettledResultsOrThrow(await Promise.allSettled(promises));\n}\n\n/** Precedence for merged completeness: any known omission (`partial`) wins, then inability to confirm\n * (`unknown`), and only an all-`complete` set of pages stays `complete`. */\nconst completenessRank: Record<CollectionCompleteness, number> = { partial: 2, unknown: 1, complete: 0 };\n\n/** A stable key for deduplicating structurally-identical scope failures accumulated across drained pages. */\nfunction collectionFailureKey(failure: CollectionScopeFailure): string {\n\treturn [failure.kind, collectionScopeKey(failure.scope), failure.message ?? ''].join(' ');\n}\n\n/**\n * A stable key for collapsing omissions accumulated across drained pages, matching the SDK's own\n * `dedupeOmissions`: kind plus scope IDs, deliberately WITHOUT `limit`/`totalCount`.\n *\n * Those counts are a re-measurement, not an identity. GitHub recomputes the match total on every request, so\n * one repository drained over several pages reports the same cap with a drifting total; keying on the total\n * would emit a near-identical warning per page (\"matched 1393…\", \"matched 1402…\") that\n * `appendDedupedWarning` cannot collapse, since the messages genuinely differ. Independently-scoped omissions\n * still stay distinct, which is the case that carries information.\n */\nfunction collectionOmissionKey(omission: CollectionOmission): string {\n\treturn [omission.kind, collectionScopeKey(omission.scope)].join(' ');\n}\n\n/**\n * Merges SDK collection metadata across drained pages. Completeness follows {@link completenessRank};\n * failures are deduplicated by kind, scope IDs, and message, and omissions are collapsed per kind and scope\n * keeping the highest reported total ({@link collectionOmissionKey}). Both preserve first-reported order.\n * Returns `undefined` when no page supplied metadata, so metadata-free providers and test doubles keep\n * behaving as before.\n */\nexport function mergeCollectionMetadata(\n\tbase: CollectionMetadata | undefined,\n\tnext: CollectionMetadata | undefined,\n): CollectionMetadata | undefined {\n\tif (base == null) return next;\n\tif (next == null) return base;\n\n\tconst completeness =\n\t\tcompletenessRank[next.completeness] > completenessRank[base.completeness]\n\t\t\t? next.completeness\n\t\t\t: base.completeness;\n\n\t// First occurrence wins: a repeated failure carries no new information, since the message is part of its key.\n\tconst failures = [\n\t\t...uniqueBy([...(base.failures ?? []), ...(next.failures ?? [])], collectionFailureKey, original => original),\n\t];\n\t// Highest total wins, so re-measuring one cap across pages collapses to a single omission holding the\n\t// largest figure reported for it — the same rule the SDK applies in `createCollectionMetadata`.\n\tconst omissions = [\n\t\t...uniqueBy(\n\t\t\t[...(base.omissions ?? []), ...(next.omissions ?? [])],\n\t\t\tcollectionOmissionKey,\n\t\t\t(original, current) => ((current.totalCount ?? -1) > (original.totalCount ?? -1) ? current : undefined),\n\t\t),\n\t];\n\n\treturn {\n\t\tcompleteness: completeness,\n\t\t...(failures.length ? { failures: failures } : {}),\n\t\t...(omissions.length ? { omissions: omissions } : {}),\n\t};\n}\n\n/**\n * Drains a provider paged fetcher into a single result while preserving enough metadata to\n * signal whether the defensive backstop interrupted the drain, and merging SDK collection metadata\n * ({@link mergeCollectionMetadata}) across the fetched pages.\n *\n * The local `truncated` flag and SDK `metadata` are distinct facts: a page-drain backstop must remain visible\n * even if every fetched page reported `complete`, and SDK incompleteness is preserved even when the drain\n * finished within its page budget.\n */\nexport async function collectProviderPagedResult<T>(\n\tfetch: (cursor: string | undefined) => Promise<ProviderApiPagedResult<T> | undefined>,\n\tmaxPages = 20,\n\tscope?: CollectionScopeFailure['scope'],\n): Promise<ProviderHierarchyResult<T>> {\n\tconst values: NonNullable<T>[] = [];\n\tlet cursor: string | undefined;\n\tlet metadata: CollectionMetadata | undefined;\n\tlet truncated = false;\n\n\t// Omit `metadata` entirely when no page supplied it, so a metadata-free drain stays deep-equal to its\n\t// pre-metadata shape (and consumers never see an explicit `undefined`).\n\tconst build = (extra?: Partial<ProviderHierarchyResult<T>>): ProviderHierarchyResult<T> => {\n\t\tconst mergedMetadata = extra?.metadata ?? metadata;\n\t\treturn {\n\t\t\tvalues: values,\n\t\t\t...extra,\n\t\t\t...(truncated || extra?.truncated === true ? { truncated: true } : {}),\n\t\t\t...(mergedMetadata != null ? { metadata: mergedMetadata } : {}),\n\t\t};\n\t};\n\n\tfor (let page = 0; page < maxPages; page++) {\n\t\tlet result: ProviderApiPagedResult<T> | undefined;\n\t\ttry {\n\t\t\tresult = await fetch(cursor);\n\t\t} catch (ex) {\n\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t// A caller-contract error is not a fact about this scope, so it is never recorded as one — see\n\t\t\t// `throwIfCallerContractError`. Checked alongside cancellation because both are errors that a\n\t\t\t// per-scope failure would misdescribe.\n\t\t\tthrowIfCallerContractError(ex);\n\n\t\t\t// When the caller supplied a scope, preserve the items already fetched from that scope and record the\n\t\t\t// failure in collection metadata rather than re-throwing and discarding the prefix. Callers without a\n\t\t\t// scope keep the legacy throw behavior.\n\t\t\tif (scope == null) throw ex;\n\t\t\treturn build({\n\t\t\t\ttruncated: true,\n\t\t\t\tmetadata: mergeCollectionMetadata(metadata, {\n\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\tfailures: [toCollectionScopeFailure(scope, ex)],\n\t\t\t\t}),\n\t\t\t});\n\t\t}\n\t\tif (result == null) {\n\t\t\tif (page === 0) return build();\n\n\t\t\treturn build({\n\t\t\t\ttruncated: true,\n\t\t\t\t...(scope != null\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tmetadata: mergeCollectionMetadata(metadata, {\n\t\t\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\t\t\tfailures: [\n\t\t\t\t\t\t\t\t\ttoCollectionScopeFailure(\n\t\t\t\t\t\t\t\t\t\tscope,\n\t\t\t\t\t\t\t\t\t\tnew Error('Provider returned no page after advertising a continuation'),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined),\n\t\t\t});\n\t\t}\n\n\t\tvalues.push(...result.values);\n\t\tmetadata = mergeCollectionMetadata(metadata, result.metadata);\n\t\ttruncated ||= result.paging?.truncated === true;\n\n\t\tif (!result.paging?.more) return build();\n\n\t\tif (result.paging.cursor === cursor) {\n\t\t\treturn build({ truncated: true });\n\t\t}\n\n\t\tcursor = result.paging.cursor;\n\t\tif (cursor == null || cursor === '{}') {\n\t\t\treturn build({ truncated: true });\n\t\t}\n\t\tif (page === maxPages - 1) {\n\t\t\treturn build({ paging: result.paging, truncated: true });\n\t\t}\n\t}\n\n\treturn build();\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"providerPaging.js","names":[],"sources":["../../../../../../plus/integrations/src/providers/utils/providerPaging.ts"],"sourcesContent":["import type {\n\tCollectionCompleteness,\n\tCollectionMetadata,\n\tCollectionOmission,\n\tCollectionScopeFailure,\n} from '@gitkraken/provider-apis';\nimport type { PullRequestShape } from '@gitlens/git/models/pullRequest.js';\nimport { isCancellationError } from '@gitlens/utils/cancellation.js';\nimport { uniqueBy } from '@gitlens/utils/iterable.js';\nimport { throwIfCallerContractError, toCollectionScopeFailure } from '../../collectionMetadata.js';\nimport { collectionScopeKey } from '../../results.js';\nimport type { ProviderApiPagedResult, ProviderHierarchyResult } from '../models.js';\n\n/**\n * Encodes an opaque numeric paging token as the `{ value, type: 'page' }` cursor the paging layer uses.\n * The value is whatever the consumer round-trips unchanged: a 1-based page number for numbered-page reads,\n * or a provider offset (e.g. Bitbucket Server's `nextPageStart`) that is never reinterpreted here.\n */\nexport function toPageCursor(page: number): string {\n\treturn JSON.stringify({ value: page, type: 'page' });\n}\n\n/** Extracts the numeric paging token from a `{ value, type: 'page' }` cursor; undefined when absent/malformed. */\nexport function parsePageCursor(cursor: string | undefined): number | undefined {\n\tif (cursor == null || cursor === '{}') return undefined;\n\n\ttry {\n\t\tconst parsed = JSON.parse(cursor) as { value?: unknown; type?: unknown };\n\t\tif (parsed.type === 'page' && typeof parsed.value === 'number') return parsed.value;\n\t} catch {}\n\n\treturn undefined;\n}\n\n/** Preserves successful sibling scopes, but doesn't turn an all-scope provider failure into an empty success. */\nexport function throwIfAllSettledFailed<T>(results: PromiseSettledResult<T>[]): void {\n\tif (results.some(result => result.status === 'fulfilled')) return;\n\n\tconst rejected = results.find((result): result is PromiseRejectedResult => result.status === 'rejected');\n\tif (rejected != null) throw rejected.reason;\n}\n\n/**\n * The answer a pull-requests-by-branch read gives for one branch: the rows `matchesHead` accepts, newest first, at\n * most `limit` of them, each passed through `map`. Rows are filtered before they're mapped, so a non-matching row\n * can't fail the answer. `more` says the host holds rows it didn't return; the filter can't see those, so they make\n * the answer `truncated` however few rows passed it.\n */\nexport function selectBranchPullRequests<T, R>(\n\trows: readonly T[],\n\toptions: {\n\t\tmatchesHead: (row: T) => boolean;\n\t\tupdatedAt: (row: T) => number;\n\t\tmap: (row: T) => R;\n\t\tlimit: number;\n\t\tmore: boolean;\n\t},\n): { values: R[]; truncated: boolean } {\n\tconst matched = rows.filter(options.matchesHead).sort((a, b) => options.updatedAt(b) - options.updatedAt(a));\n\treturn {\n\t\tvalues: matched.slice(0, options.limit).map(options.map),\n\t\ttruncated: options.more || matched.length > options.limit,\n\t};\n}\n\n/**\n * The second step of a pull-requests-by-branch read on a host whose branch query can't return the batch read's\n * rows: resolves each target's matched numbers through `resolve` (the host's `getProviderPullRequestsBatch`), so a\n * pull request comes back identical whichever read found it.\n *\n * One `resolve` call for every target's numbers, so the host's own fan-out bounds the concurrency. A number it\n * reports absent (deleted between the two steps) is dropped; one it couldn't check rejects the whole target, since\n * a partial list would read as a complete one. The first step's order is kept.\n */\nexport async function resolveBranchPullRequests(\n\ttargets: readonly { owner: string; repo: string; project?: string }[],\n\tfound: readonly PromiseSettledResult<{ numbers: number[]; truncated: boolean }>[],\n\tresolve: (\n\t\tcoordinates: { owner: string; repo: string; number: number; project?: string }[],\n\t) => Promise<PromiseSettledResult<PullRequestShape | undefined>[] | undefined>,\n): Promise<PromiseSettledResult<{ pullRequests: PullRequestShape[]; truncated: boolean }>[]> {\n\tconst coordinates = found.flatMap((slot, i) =>\n\t\tslot.status === 'fulfilled'\n\t\t\t? slot.value.numbers.map(number => ({\n\t\t\t\t\towner: targets[i].owner,\n\t\t\t\t\trepo: targets[i].repo,\n\t\t\t\t\tnumber: number,\n\t\t\t\t\tproject: targets[i].project,\n\t\t\t\t}))\n\t\t\t: [],\n\t);\n\n\tlet resolved: PromiseSettledResult<PullRequestShape | undefined>[] = [];\n\tif (coordinates.length > 0) {\n\t\ttry {\n\t\t\tresolved =\n\t\t\t\t(await resolve(coordinates)) ??\n\t\t\t\tcoordinates.map(() => ({\n\t\t\t\t\tstatus: 'rejected',\n\t\t\t\t\treason: new Error('Pull requests could not be resolved'),\n\t\t\t\t}));\n\t\t} catch (ex) {\n\t\t\tresolved = coordinates.map(() => ({ status: 'rejected', reason: ex }));\n\t\t}\n\t}\n\n\tlet offset = 0;\n\treturn found.map(slot => {\n\t\tif (slot.status === 'rejected') return slot;\n\n\t\tconst slots = resolved.slice(offset, offset + slot.value.numbers.length);\n\t\toffset += slot.value.numbers.length;\n\n\t\tconst failure = slots.find((s): s is PromiseRejectedResult => s.status === 'rejected');\n\t\tif (failure != null) return failure;\n\n\t\treturn {\n\t\t\tstatus: 'fulfilled',\n\t\t\tvalue: {\n\t\t\t\tpullRequests: slots.flatMap(s => (s.status === 'fulfilled' && s.value != null ? [s.value] : [])),\n\t\t\t\ttruncated: slot.value.truncated,\n\t\t\t},\n\t\t};\n\t});\n}\n\nexport function flatSettledResultsOrThrow<T>(results: PromiseSettledResult<T[]>[]): T[] {\n\tthrowIfAllSettledFailed(results);\n\n\tconst fulfilled = results.filter((result): result is PromiseFulfilledResult<T[]> => result.status === 'fulfilled');\n\treturn fulfilled.flatMap(result => result.value);\n}\n\nexport async function flatSettledOrThrow<T>(promises: Promise<T[]>[]): Promise<T[]> {\n\treturn flatSettledResultsOrThrow(await Promise.allSettled(promises));\n}\n\n/** Precedence for merged completeness: any known omission (`partial`) wins, then inability to confirm\n * (`unknown`), and only an all-`complete` set of pages stays `complete`. */\nconst completenessRank: Record<CollectionCompleteness, number> = { partial: 2, unknown: 1, complete: 0 };\n\n/** A stable key for deduplicating structurally-identical scope failures accumulated across drained pages. */\nfunction collectionFailureKey(failure: CollectionScopeFailure): string {\n\treturn [failure.kind, collectionScopeKey(failure.scope), failure.message ?? ''].join(' ');\n}\n\n/**\n * A stable key for collapsing omissions accumulated across drained pages, matching the SDK's own\n * `dedupeOmissions`: kind plus scope IDs, deliberately WITHOUT `limit`/`totalCount`.\n *\n * Those counts are a re-measurement, not an identity. GitHub recomputes the match total on every request, so\n * one repository drained over several pages reports the same cap with a drifting total; keying on the total\n * would emit a near-identical warning per page (\"matched 1393…\", \"matched 1402…\") that\n * `appendDedupedWarning` cannot collapse, since the messages genuinely differ. Independently-scoped omissions\n * still stay distinct, which is the case that carries information.\n */\nfunction collectionOmissionKey(omission: CollectionOmission): string {\n\treturn [omission.kind, collectionScopeKey(omission.scope)].join(' ');\n}\n\n/**\n * Merges SDK collection metadata across drained pages. Completeness follows {@link completenessRank};\n * failures are deduplicated by kind, scope IDs, and message, and omissions are collapsed per kind and scope\n * keeping the highest reported total ({@link collectionOmissionKey}). Both preserve first-reported order.\n * Returns `undefined` when no page supplied metadata, so metadata-free providers and test doubles keep\n * behaving as before.\n */\nexport function mergeCollectionMetadata(\n\tbase: CollectionMetadata | undefined,\n\tnext: CollectionMetadata | undefined,\n): CollectionMetadata | undefined {\n\tif (base == null) return next;\n\tif (next == null) return base;\n\n\tconst completeness =\n\t\tcompletenessRank[next.completeness] > completenessRank[base.completeness]\n\t\t\t? next.completeness\n\t\t\t: base.completeness;\n\n\t// First occurrence wins: a repeated failure carries no new information, since the message is part of its key.\n\tconst failures = [\n\t\t...uniqueBy([...(base.failures ?? []), ...(next.failures ?? [])], collectionFailureKey, original => original),\n\t];\n\t// Highest total wins, so re-measuring one cap across pages collapses to a single omission holding the\n\t// largest figure reported for it — the same rule the SDK applies in `createCollectionMetadata`.\n\tconst omissions = [\n\t\t...uniqueBy(\n\t\t\t[...(base.omissions ?? []), ...(next.omissions ?? [])],\n\t\t\tcollectionOmissionKey,\n\t\t\t(original, current) => ((current.totalCount ?? -1) > (original.totalCount ?? -1) ? current : undefined),\n\t\t),\n\t];\n\n\treturn {\n\t\tcompleteness: completeness,\n\t\t...(failures.length ? { failures: failures } : {}),\n\t\t...(omissions.length ? { omissions: omissions } : {}),\n\t};\n}\n\n/**\n * Drains a provider paged fetcher into a single result while preserving enough metadata to\n * signal whether the defensive backstop interrupted the drain, and merging SDK collection metadata\n * ({@link mergeCollectionMetadata}) across the fetched pages.\n *\n * The local `truncated` flag and SDK `metadata` are distinct facts: a page-drain backstop must remain visible\n * even if every fetched page reported `complete`, and SDK incompleteness is preserved even when the drain\n * finished within its page budget.\n */\nexport async function collectProviderPagedResult<T>(\n\tfetch: (cursor: string | undefined) => Promise<ProviderApiPagedResult<T> | undefined>,\n\tmaxPages = 20,\n\tscope?: CollectionScopeFailure['scope'],\n): Promise<ProviderHierarchyResult<T>> {\n\tconst values: NonNullable<T>[] = [];\n\tlet cursor: string | undefined;\n\tlet metadata: CollectionMetadata | undefined;\n\tlet truncated = false;\n\n\t// Omit `metadata` entirely when no page supplied it, so a metadata-free drain stays deep-equal to its\n\t// pre-metadata shape (and consumers never see an explicit `undefined`).\n\tconst build = (extra?: Partial<ProviderHierarchyResult<T>>): ProviderHierarchyResult<T> => {\n\t\tconst mergedMetadata = extra?.metadata ?? metadata;\n\t\treturn {\n\t\t\tvalues: values,\n\t\t\t...extra,\n\t\t\t...(truncated || extra?.truncated === true ? { truncated: true } : {}),\n\t\t\t...(mergedMetadata != null ? { metadata: mergedMetadata } : {}),\n\t\t};\n\t};\n\n\tfor (let page = 0; page < maxPages; page++) {\n\t\tlet result: ProviderApiPagedResult<T> | undefined;\n\t\ttry {\n\t\t\tresult = await fetch(cursor);\n\t\t} catch (ex) {\n\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t// A caller-contract error is not a fact about this scope, so it is never recorded as one — see\n\t\t\t// `throwIfCallerContractError`. Checked alongside cancellation because both are errors that a\n\t\t\t// per-scope failure would misdescribe.\n\t\t\tthrowIfCallerContractError(ex);\n\n\t\t\t// When the caller supplied a scope, preserve the items already fetched from that scope and record the\n\t\t\t// failure in collection metadata rather than re-throwing and discarding the prefix. Callers without a\n\t\t\t// scope keep the legacy throw behavior.\n\t\t\tif (scope == null) throw ex;\n\t\t\treturn build({\n\t\t\t\ttruncated: true,\n\t\t\t\tmetadata: mergeCollectionMetadata(metadata, {\n\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\tfailures: [toCollectionScopeFailure(scope, ex)],\n\t\t\t\t}),\n\t\t\t});\n\t\t}\n\t\tif (result == null) {\n\t\t\tif (page === 0) return build();\n\n\t\t\treturn build({\n\t\t\t\ttruncated: true,\n\t\t\t\t...(scope != null\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tmetadata: mergeCollectionMetadata(metadata, {\n\t\t\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\t\t\tfailures: [\n\t\t\t\t\t\t\t\t\ttoCollectionScopeFailure(\n\t\t\t\t\t\t\t\t\t\tscope,\n\t\t\t\t\t\t\t\t\t\tnew Error('Provider returned no page after advertising a continuation'),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined),\n\t\t\t});\n\t\t}\n\n\t\tvalues.push(...result.values);\n\t\tmetadata = mergeCollectionMetadata(metadata, result.metadata);\n\t\ttruncated ||= result.paging?.truncated === true;\n\n\t\tif (!result.paging?.more) return build();\n\n\t\tif (result.paging.cursor === cursor) {\n\t\t\treturn build({ truncated: true });\n\t\t}\n\n\t\tcursor = result.paging.cursor;\n\t\tif (cursor == null || cursor === '{}') {\n\t\t\treturn build({ truncated: true });\n\t\t}\n\t\tif (page === maxPages - 1) {\n\t\t\treturn build({ paging: result.paging, truncated: true });\n\t\t}\n\t}\n\n\treturn build();\n}\n"],"mappings":"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"}
|
|
@@ -2,8 +2,8 @@ import { IssueResourceDescriptor, RepositoryDescriptor, ResourceDescriptor } fro
|
|
|
2
2
|
import { Issue as Issue$1, IssueShape } from "../../../git/models/issue.js";
|
|
3
3
|
import { PullRequest } from "../../../git/models/pullRequest.js";
|
|
4
4
|
import { IntegrationIds } from "../constants.js";
|
|
5
|
-
import { GitConfigEntityIdentifier } from "./models.js";
|
|
6
5
|
import { AzureProjectInputDescriptor } from "./azure/models.js";
|
|
6
|
+
import { GitConfigEntityIdentifier } from "./models.js";
|
|
7
7
|
import { AnyEntityIdentifierInput, EntityIdentifier } from "@gitkraken/provider-apis";
|
|
8
8
|
//#region ../plus/integrations/src/providers/utils.d.ts
|
|
9
9
|
export declare const EntityIdentifierProviderType: {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { mapBounded } from "../../../utils/promise.js";
|
|
2
2
|
import { effectiveIssueSort } from "../../../git/utils/issue.utils.js";
|
|
3
|
+
import "../constants.js";
|
|
3
4
|
import { appendDedupedWarning } from "../results.js";
|
|
4
5
|
import { mergeAssessmentInto } from "../collectionMetadata.js";
|
|
5
|
-
import "../constants.js";
|
|
6
6
|
import { hostFromDomain } from "../utils/domain.utils.js";
|
|
7
7
|
import { isGitHostIntegration, isIssuesHostIntegrationId } from "../utils/integration.utils.js";
|
|
8
8
|
import { resolveContinuation, toProviderPageInfo, usableCursor } from "./paging.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ConfiguredIntegrationDescriptor } from "../authentication/models.js";
|
|
2
2
|
import { IntegrationIds } from "../constants.js";
|
|
3
|
-
import { Integration } from "../models/integration.js";
|
|
4
3
|
import { ProviderWarning } from "../results.js";
|
|
4
|
+
import { Integration } from "../models/integration.js";
|
|
5
5
|
import { IntegrationsRemoteConfig } from "../context.js";
|
|
6
6
|
//#region ../plus/integrations/src/reads/context.d.ts
|
|
7
7
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { chunk } from "../../../utils/array.js";
|
|
2
2
|
import { mapBounded } from "../../../utils/promise.js";
|
|
3
|
-
import { appendDedupedWarning, toProviderWarning } from "../results.js";
|
|
4
3
|
import "../constants.js";
|
|
4
|
+
import { appendDedupedWarning, toProviderWarning } from "../results.js";
|
|
5
5
|
import { isGitHostIntegration, isIssuesHostIntegrationId, warnOnMissingSessionForDomain } from "../utils/integration.utils.js";
|
|
6
6
|
import { providersMetadata } from "../providers/models.js";
|
|
7
7
|
import { gitHostOnlySurfaceWarning, issuesUnsupportedWarning, otherWarning, unsupportedIssueSearchCriteriaWarning, unsupportedPullRequestSearchCriteriaWarning, unusableSearchScopeMessage } from "./warnings.js";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Account } from "../../../git/models/author.js";
|
|
2
|
+
import { IntegrationIds } from "../constants.js";
|
|
3
|
+
import { ProviderWarning } from "../results.js";
|
|
4
|
+
import { ProviderReadContext } from "./context.js";
|
|
5
|
+
//#region ../plus/integrations/src/reads/currentAccount.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* "Who am I on this provider / connection" — {@link IntegrationBase.getCurrentAccount}'s own answer, exposed on
|
|
8
|
+
* the manager so a caller can ask for it directly instead of waiting for some other read to warm it as a side
|
|
9
|
+
* effect.
|
|
10
|
+
*/
|
|
11
|
+
export interface CurrentAccountResult {
|
|
12
|
+
/** The signed-in account, when the provider answered. */
|
|
13
|
+
account?: Account;
|
|
14
|
+
warnings: ProviderWarning[];
|
|
15
|
+
/**
|
|
16
|
+
* Set whenever `account` is absent. Unlike a list/batch read, this one has no benign empty answer — "who am
|
|
17
|
+
* I" is either resolved or it couldn't be told, never a legitimately empty result — so `account` is never
|
|
18
|
+
* missing without a warning explaining why.
|
|
19
|
+
*/
|
|
20
|
+
fetchFailed?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Resolves the viewer's own account for a git host connection. Issue trackers refuse: they only have a per-resource account.
|
|
24
|
+
*
|
|
25
|
+
* Goes THROUGH `Integration.getCurrentAccount`'s own cache (the hook a host supplies as
|
|
26
|
+
* `IntegrationManagerCacheProvider.getCurrentAccount`) rather than adding a second one, so this read and
|
|
27
|
+
* whatever else warms that cache — today, authorship enrichment on the pull request reads — stay one cache.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getCurrentAccount(ctx: ProviderReadContext, options: {
|
|
30
|
+
providerId: IntegrationIds;
|
|
31
|
+
connectionId?: string;
|
|
32
|
+
domain?: string;
|
|
33
|
+
}): Promise<CurrentAccountResult>;
|
|
34
|
+
//#endregion
|
|
35
|
+
//# sourceMappingURL=currentAccount.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { appendDedupedWarning } from "../results.js";
|
|
2
|
+
import { noConnectionWarning, otherWarning } from "./warnings.js";
|
|
3
|
+
import { runCaptured } from "./drains.js";
|
|
4
|
+
//#region ../plus/integrations/src/reads/currentAccount.ts
|
|
5
|
+
/**
|
|
6
|
+
* Resolves the viewer's own account for a git host connection. Issue trackers refuse: they only have a per-resource account.
|
|
7
|
+
*
|
|
8
|
+
* Goes THROUGH `Integration.getCurrentAccount`'s own cache (the hook a host supplies as
|
|
9
|
+
* `IntegrationManagerCacheProvider.getCurrentAccount`) rather than adding a second one, so this read and
|
|
10
|
+
* whatever else warms that cache — today, authorship enrichment on the pull request reads — stay one cache.
|
|
11
|
+
*/
|
|
12
|
+
async function getCurrentAccount(ctx, options) {
|
|
13
|
+
const integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);
|
|
14
|
+
if (integration == null) {
|
|
15
|
+
const early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);
|
|
16
|
+
if (early.warnings.length > 0) return {
|
|
17
|
+
warnings: early.warnings,
|
|
18
|
+
fetchFailed: true
|
|
19
|
+
};
|
|
20
|
+
const domain = ctx.resolveDomainForRead(options.providerId, options.connectionId, options.domain);
|
|
21
|
+
return {
|
|
22
|
+
warnings: [noConnectionWarning(options.providerId, domain, options.connectionId)],
|
|
23
|
+
fetchFailed: true
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);
|
|
27
|
+
if (!integration.supportsCurrentAccount) return {
|
|
28
|
+
warnings: [otherWarning(options.providerId, domain, options.connectionId, `Current account lookup is not supported by '${options.providerId}'.`)],
|
|
29
|
+
fetchFailed: true
|
|
30
|
+
};
|
|
31
|
+
const { value: account, warning } = await runCaptured(options.providerId, domain, options.connectionId, async () => {
|
|
32
|
+
const resolved = await integration.getCurrentAccount({ connectionId: options.connectionId });
|
|
33
|
+
return resolved != null ? { value: resolved } : void 0;
|
|
34
|
+
}, { warnOnMissingSession: true });
|
|
35
|
+
const warnings = [];
|
|
36
|
+
if (warning != null) appendDedupedWarning(warnings, warning);
|
|
37
|
+
return {
|
|
38
|
+
...account != null ? { account } : {},
|
|
39
|
+
warnings,
|
|
40
|
+
fetchFailed: account == null ? true : void 0
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { getCurrentAccount };
|
|
45
|
+
|
|
46
|
+
//# sourceMappingURL=currentAccount.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"currentAccount.js","names":[],"sources":["../../../../../plus/integrations/src/reads/currentAccount.ts"],"sourcesContent":["import type { Account } from '@gitlens/git/models/author.js';\nimport type { IntegrationIds } from '../constants.js';\nimport type { ProviderWarning } from '../results.js';\nimport { appendDedupedWarning } from '../results.js';\nimport type { ProviderReadContext } from './context.js';\nimport { runCaptured } from './drains.js';\nimport { noConnectionWarning, otherWarning } from './warnings.js';\n\n/**\n * \"Who am I on this provider / connection\" — {@link IntegrationBase.getCurrentAccount}'s own answer, exposed on\n * the manager so a caller can ask for it directly instead of waiting for some other read to warm it as a side\n * effect.\n */\nexport interface CurrentAccountResult {\n\t/** The signed-in account, when the provider answered. */\n\taccount?: Account;\n\twarnings: ProviderWarning[];\n\t/**\n\t * Set whenever `account` is absent. Unlike a list/batch read, this one has no benign empty answer — \"who am\n\t * I\" is either resolved or it couldn't be told, never a legitimately empty result — so `account` is never\n\t * missing without a warning explaining why.\n\t */\n\tfetchFailed?: boolean;\n}\n\n/**\n * Resolves the viewer's own account for a git host connection. Issue trackers refuse: they only have a per-resource account.\n *\n * Goes THROUGH `Integration.getCurrentAccount`'s own cache (the hook a host supplies as\n * `IntegrationManagerCacheProvider.getCurrentAccount`) rather than adding a second one, so this read and\n * whatever else warms that cache — today, authorship enrichment on the pull request reads — stay one cache.\n */\nexport async function getCurrentAccount(\n\tctx: ProviderReadContext,\n\toptions: { providerId: IntegrationIds; connectionId?: string; domain?: string },\n): Promise<CurrentAccountResult> {\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\tconst early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);\n\t\tif (early.warnings.length > 0) return { warnings: early.warnings, fetchFailed: true };\n\n\t\t// The untargeted primary path reads a disconnected provider as a silent empty result on the list reads; an\n\t\t// absent account must still carry a warning here, as on the batch reads — see `CurrentAccountResult.fetchFailed`.\n\t\tconst domain = ctx.resolveDomainForRead(options.providerId, options.connectionId, options.domain);\n\t\treturn { warnings: [noConnectionWarning(options.providerId, domain, options.connectionId)], fetchFailed: true };\n\t}\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\n\tif (!integration.supportsCurrentAccount) {\n\t\treturn {\n\t\t\twarnings: [\n\t\t\t\totherWarning(\n\t\t\t\t\toptions.providerId,\n\t\t\t\t\tdomain,\n\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t`Current account lookup is not supported by '${options.providerId}'.`,\n\t\t\t\t),\n\t\t\t],\n\t\t\tfetchFailed: true,\n\t\t};\n\t}\n\n\tconst { value: account, warning } = await runCaptured(\n\t\toptions.providerId,\n\t\tdomain,\n\t\toptions.connectionId,\n\t\tasync () => {\n\t\t\tconst resolved = await integration.getCurrentAccount({ connectionId: options.connectionId });\n\t\t\t// `undefined` is returned bare (not `{ value: undefined }`) so `runCaptured` treats it the same as\n\t\t\t// a session it couldn't resolve — the only other way this read core answers nothing.\n\t\t\treturn resolved != null ? { value: resolved } : undefined;\n\t\t},\n\t\t{ warnOnMissingSession: true },\n\t);\n\n\tconst warnings: ProviderWarning[] = [];\n\tif (warning != null) {\n\t\tappendDedupedWarning(warnings, warning);\n\t}\n\n\treturn {\n\t\t...(account != null ? { account: account } : {}),\n\t\twarnings: warnings,\n\t\tfetchFailed: account == null ? true : undefined,\n\t};\n}\n"],"mappings":";;;;;;;;;;;AAgCA,eAAsB,kBACrB,KACA,SACgC;CAChC,MAAM,cAAc,MAAM,IAAI,sBAAsB,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;CAC5G,IAAI,eAAe,MAAM;EACxB,MAAM,QAAQ,IAAI,8BAA8B,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;EACxG,IAAI,MAAM,SAAS,SAAS,GAAG,OAAO;GAAE,UAAU,MAAM;GAAU,aAAa;EAAK;EAIpF,MAAM,SAAS,IAAI,qBAAqB,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;EAChG,OAAO;GAAE,UAAU,CAAC,oBAAoB,QAAQ,YAAY,QAAQ,QAAQ,YAAY,CAAC;GAAG,aAAa;EAAK;CAC/G;CAEA,MAAM,SAAS,IAAI,cAAc,aAAa,QAAQ,YAAY,QAAQ,cAAc,QAAQ,MAAM;CAEtG,IAAI,CAAC,YAAY,wBAChB,OAAO;EACN,UAAU,CACT,aACC,QAAQ,YACR,QACA,QAAQ,cACR,+CAA+C,QAAQ,WAAW,GACnE,CACD;EACA,aAAa;CACd;CAGD,MAAM,EAAE,OAAO,SAAS,YAAY,MAAM,YACzC,QAAQ,YACR,QACA,QAAQ,cACR,YAAY;EACX,MAAM,WAAW,MAAM,YAAY,kBAAkB,EAAE,cAAc,QAAQ,aAAa,CAAC;EAG3F,OAAO,YAAY,OAAO,EAAE,OAAO,SAAS,IAAI,KAAA;CACjD,GACA,EAAE,sBAAsB,KAAK,CAC9B;CAEA,MAAM,WAA8B,CAAC;CACrC,IAAI,WAAW,MACd,qBAAqB,UAAU,OAAO;CAGvC,OAAO;EACN,GAAI,WAAW,OAAO,EAAW,QAAQ,IAAI,CAAC;EACpC;EACV,aAAa,WAAW,OAAO,OAAO,KAAA;CACvC;AACD"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { PullRequestFilter, PullRequestStateFilter } from "../../../git/models/pullRequest.js";
|
|
2
2
|
import { IntegrationIds } from "../constants.js";
|
|
3
3
|
import "../providerFilters.js";
|
|
4
|
+
import { ProviderWarning } from "../results.js";
|
|
4
5
|
import { GitHostIntegration } from "../models/gitHostIntegration.js";
|
|
5
6
|
import { IntegrationResult } from "../models/integration.js";
|
|
6
|
-
import { ProviderWarning } from "../results.js";
|
|
7
7
|
import { ProviderPullRequest, ProviderReposInput, ProviderRepository } from "../providers/models.js";
|
|
8
8
|
import { ProviderSweepTarget, PullRequestSweepOptions } from "../manager.js";
|
|
9
9
|
//#region ../plus/integrations/src/reads/drains.d.ts
|
|
@@ -63,6 +63,9 @@ export declare function resolvePullRequestSweepTargets(options: PullRequestSweep
|
|
|
63
63
|
targets: readonly ProviderSweepTarget[];
|
|
64
64
|
attributeUnavailableProviders: boolean;
|
|
65
65
|
};
|
|
66
|
-
export declare function
|
|
66
|
+
export declare function getCurrentAccountIdentity(integration: GitHostIntegration, connectionId: string | undefined): Promise<{
|
|
67
|
+
id: string;
|
|
68
|
+
username?: string;
|
|
69
|
+
} | undefined>;
|
|
67
70
|
//#endregion
|
|
68
71
|
//# sourceMappingURL=drains.d.ts.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { supportedOrderedCloudIntegrationIds } from "../constants.js";
|
|
1
2
|
import { appendDedupedWarning, reconcileOmissionsWithFailure, toProviderWarning } from "../results.js";
|
|
2
3
|
import { mergeAssessmentInto } from "../collectionMetadata.js";
|
|
3
|
-
import { supportedOrderedCloudIntegrationIds } from "../constants.js";
|
|
4
4
|
import { isIssuesHostIntegrationId } from "../utils/integration.utils.js";
|
|
5
5
|
import { getProviderPullRequestIdentity } from "../providers/models.js";
|
|
6
6
|
import { noConnectionWarning, truncationWarning } from "./warnings.js";
|
|
@@ -222,14 +222,18 @@ function resolvePullRequestSweepTargets(options) {
|
|
|
222
222
|
attributeUnavailableProviders: options?.providerIds != null
|
|
223
223
|
};
|
|
224
224
|
}
|
|
225
|
-
async function
|
|
225
|
+
async function getCurrentAccountIdentity(integration, connectionId) {
|
|
226
226
|
try {
|
|
227
|
-
|
|
227
|
+
const account = await integration.getCurrentAccount({ connectionId });
|
|
228
|
+
return account != null ? {
|
|
229
|
+
id: account.id,
|
|
230
|
+
username: account.username
|
|
231
|
+
} : void 0;
|
|
228
232
|
} catch {
|
|
229
233
|
return;
|
|
230
234
|
}
|
|
231
235
|
}
|
|
232
236
|
//#endregion
|
|
233
|
-
export { drainPullRequests, drainRepositories,
|
|
237
|
+
export { drainPullRequests, drainRepositories, getCurrentAccountIdentity, resolvePullRequestSweepTargets, runCaptured };
|
|
234
238
|
|
|
235
239
|
//# sourceMappingURL=drains.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drains.js","names":[],"sources":["../../../../../plus/integrations/src/reads/drains.ts"],"sourcesContent":["import type { PullRequestStateFilter } from '@gitlens/git/models/pullRequest.js';\nimport { mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { supportedOrderedCloudIntegrationIds } from '../constants.js';\nimport type { ProviderSweepTarget, PullRequestSweepOptions } from '../manager.js';\nimport type { GitHostIntegration } from '../models/gitHostIntegration.js';\nimport type { IntegrationResult } from '../models/integration.js';\nimport type { PullRequestFilter } from '../providerFilters.js';\nimport type { ProviderPullRequest, ProviderReposInput, ProviderRepository } from '../providers/models.js';\nimport { getProviderPullRequestIdentity } from '../providers/models.js';\nimport type { ProviderWarning } from '../results.js';\nimport { appendDedupedWarning, reconcileOmissionsWithFailure, toProviderWarning } from '../results.js';\nimport { isIssuesHostIntegrationId } from '../utils/integration.utils.js';\nimport { noConnectionWarning, truncationWarning } from './warnings.js';\n\n/**\n * The all-pages reads: a provider read run repeatedly until it runs out of pages, as opposed to the single-page\n * reads the facade's paged surface exposes. `sweepPullRequests` and `broadenIssues` are built on these.\n *\n * A drain's job is to keep going where a single page would stop, so what distinguishes it is its failure\n * bookkeeping rather than its loop: it must return the prefix it did read plus enough signal for the caller to\n * know the result is not authoritative — `truncated` when pages remain unread (a page backstop, or a provider\n * claiming another page while handing back no usable cursor), `fetchFailed` when a page or scope failed\n * outright. Discarding the prefix on a late failure, or returning it as if complete, are the two mistakes these\n * exist to prevent.\n */\n\n/**\n * Runs a result-returning read and captures failure as a neutral {@link ProviderWarning} rather than\n * letting it throw or silently vanish. Handles both a returned `{ error }` (the read cores' contract)\n * and a hard throw; a soft warning (`{ value, error }`) yields the value *and* a warning.\n */\nexport async function runCaptured<T>(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tfn: () => Promise<IntegrationResult<T>>,\n\toptions?: { warnOnMissingSession?: boolean },\n): Promise<{ value?: T; warning?: ProviderWarning }> {\n\ttry {\n\t\tconst result = await fn();\n\t\tif (result == null) {\n\t\t\t// The read core returns undefined only when it couldn't resolve a session. For a per-connection\n\t\t\t// or explicit-domain read that means the requested target is gone or its authentication is invalid,\n\t\t\t// which must not be reported as an empty account. The untargeted primary path legitimately yields\n\t\t\t// nothing when the provider isn't connected, so leave it as an empty result.\n\t\t\treturn connectionId != null || options?.warnOnMissingSession\n\t\t\t\t? { warning: noConnectionWarning(id, domain, connectionId) }\n\t\t\t\t: {};\n\t\t}\n\t\tif (result.error != null) {\n\t\t\treturn { value: result.value, warning: toProviderWarning(id, domain, connectionId, result.error) };\n\t\t}\n\t\treturn { value: result.value };\n\t} catch (ex) {\n\t\treturn { warning: toProviderWarning(id, domain, connectionId, ex) };\n\t}\n}\n\n/**\n * Drains every page of the user's pull requests for one git-host integration, threading the opaque\n * next-cursor the provider returns (so it works for both page- and cursor-based hosts). Stops at\n * `maxPages` (marking `truncated`) or on a hard read failure (marking `fetchFailed`), keeping the\n * pages fetched so far. A soft warning (`{ value, error }`) is recorded but the drain continues.\n */\nexport async function drainPullRequests(\n\tintegration: GitHostIntegration,\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\trepos: ProviderReposInput,\n\tstate: PullRequestStateFilter[] | undefined,\n\tfilters: PullRequestFilter[] | undefined,\n\t/**\n\t * Breadth opt-ins that only the account-wide read can express. Grouped rather than passed positionally\n\t * because they are same-typed neighbors a transposition would silently swap, and picked from the sweep\n\t * options rather than restated so a third opt-in is declared once.\n\t */\n\taccountWideOptions: Pick<PullRequestSweepOptions, 'includeReviewRequested' | 'includeReviews'> | undefined,\n\tconnectionId: string | undefined,\n\tmaxPages: number,\n\tattributeUnavailableProvider: boolean,\n): Promise<{\n\titems: ProviderPullRequest[];\n\twarnings: ProviderWarning[];\n\tfetchFailed: boolean;\n\ttruncated: boolean;\n\tfailedProvider: boolean;\n}> {\n\tconst items: ProviderPullRequest[] = [];\n\tconst itemIndexByIdentity = new Map<string, number>();\n\tconst warnings: ProviderWarning[] = [];\n\tlet cursor: string | undefined;\n\tlet page = 0;\n\t// SDK metadata failures across pages mean the collection is incomplete even when no page threw; carry\n\t// this through the terminal returns instead of resetting it to false at the last page.\n\tlet fetchFailed = false;\n\tlet truncated = false;\n\t// A page the provider capped or couldn't vouch for. Decides the CAUSE the terminal warning reports: a cap\n\t// outranks a budget stop, because raising a budget cannot un-cap a page.\n\tlet providerTruncated = false;\n\t// The subset of that which no other warning already explains — decides whether this drain raises one of\n\t// its own on an otherwise clean exit.\n\tlet unexplainedTruncation = false;\n\n\t// With no repos this is an account-wide \"my PRs\" sweep. The repo-scoped core rejects an empty `repos`\n\t// input, so read the provider-native account-wide core instead.\n\tconst accountWide = repos.length === 0;\n\t// The breadth opt-ins are only expressible on the account-wide core, so gate them here — the single place\n\t// that already knows which core it is about to call.\n\tconst includeReviewRequested = accountWide && (accountWideOptions?.includeReviewRequested ?? false);\n\tconst includeReviews = accountWide && (accountWideOptions?.includeReviews ?? false);\n\t/** Every cursor already followed, so a provider that cycles them can't keep the drain walking in circles. */\n\tconst seenCursors = new Set<string>();\n\n\tfor (;;) {\n\t\tpage++;\n\t\t// Snapshot the mutable loop cursor so the read closure doesn't capture a later-reassigned value.\n\t\tconst pageCursor = cursor;\n\t\tconst { value, warning } = await runCaptured(id, domain, connectionId, () =>\n\t\t\taccountWide\n\t\t\t\t? integration.getMyPullRequestsForUserResult(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstate: state,\n\t\t\t\t\t\t\tcursor: pageCursor,\n\t\t\t\t\t\t\tincludeReviewRequested: includeReviewRequested,\n\t\t\t\t\t\t\tfilters: filters,\n\t\t\t\t\t\t\t// Lite by default; the full projection (reviews) only when a caller opts in.\n\t\t\t\t\t\t\tsummary: !includeReviews,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t)\n\t\t\t\t: integration.getMyPullRequestsForReposResult(\n\t\t\t\t\t\trepos,\n\t\t\t\t\t\t{ state: state, filters: filters, cursor: pageCursor },\n\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t),\n\t\t);\n\t\tif (warning != null) {\n\t\t\tappendDedupedWarning(warnings, warning);\n\t\t}\n\t\tif (value == null) {\n\t\t\t// An implicit sweep may silently skip a provider that has no session before it yields data.\n\t\t\t// Once a page has been returned, however, losing that session leaves an unread tail and must\n\t\t\t// be attributed even when the provider wasn't explicitly requested.\n\t\t\tconst sessionLostAfterProgress = warning == null && page > 1;\n\t\t\tconst unavailable = warning == null && (attributeUnavailableProvider || sessionLostAfterProgress);\n\t\t\tif (unavailable) {\n\t\t\t\tappendDedupedWarning(warnings, noConnectionWarning(id, domain, connectionId));\n\t\t\t}\n\t\t\t// `warning` set → a hard read failure (incomplete items); otherwise not connected / no session.\n\t\t\tconst failed = fetchFailed || warning != null || unavailable;\n\t\t\t// A cap seen on an earlier page still left results out, so it is still worth saying — but as part\n\t\t\t// of a read that failed, never as an omission. (`failed` is always true here when anything was\n\t\t\t// latched: reaching this exit past page 1 means a later page was lost.)\n\t\t\tif (unexplainedTruncation) {\n\t\t\t\tappendDedupedWarning(\n\t\t\t\t\twarnings,\n\t\t\t\t\ttruncationWarning(id, domain, connectionId, 'Pull request', failed ? 'interrupted' : 'exhausted'),\n\t\t\t\t);\n\t\t\t}\n\t\t\t// An earlier page may already have emitted an omission before this one died; it asserts the read\n\t\t\t// succeeded, which is no longer true.\n\t\t\treconcileOmissionsWithFailure(warnings, failed);\n\t\t\treturn {\n\t\t\t\titems: items,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: failed,\n\t\t\t\ttruncated: truncated || sessionLostAfterProgress,\n\t\t\t\t// Only a top-level first-page rejection means the provider itself failed. A later-page or\n\t\t\t\t// per-scope failure still yielded a usable provider slice and stays represented separately.\n\t\t\t\tfailedProvider: page === 1 && (warning != null || unavailable),\n\t\t\t};\n\t\t}\n\n\t\t// Composite account-wide queries can surface the same PR on different relationship/state pages\n\t\t// (for example authored + review-requested, or closed + merged). Keep the first stable position but\n\t\t// replace its value with the latest representation so a later, richer merged state wins. Repository\n\t\t// identity is what keys a row (see `getProviderPullRequestIdentity`), falling back to the canonical\n\t\t// URL; a row with neither stays unkeyed and is retained so unrelated incomplete rows are never\n\t\t// collapsed.\n\t\tfor (const pullRequest of value.values) {\n\t\t\tconst identity = getProviderPullRequestIdentity(pullRequest);\n\t\t\tif (identity == null) {\n\t\t\t\titems.push(pullRequest);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst existingIndex = itemIndexByIdentity.get(identity);\n\t\t\tif (existingIndex == null) {\n\t\t\t\titemIndexByIdentity.set(identity, items.length);\n\t\t\t\titems.push(pullRequest);\n\t\t\t} else {\n\t\t\t\titems[existingIndex] = pullRequest;\n\t\t\t}\n\t\t}\n\n\t\t// Assess this page's SDK metadata: append scope-aware warnings (deduped across pages), and remember\n\t\t// whether a structured failure or incompleteness occurred.\n\t\tconst assessment = mergeAssessmentInto(warnings, id, domain, connectionId, value.metadata);\n\t\tfetchFailed = fetchFailed || assessment.fetchFailed;\n\t\tconst pageTruncated =\n\t\t\t(value as { truncated?: boolean }).truncated === true ||\n\t\t\tvalue.paging?.truncated === true ||\n\t\t\tassessment.truncated;\n\t\ttruncated = truncated || pageTruncated;\n\t\t// A page the provider itself capped, or one whose completeness it couldn't confirm. Latched from ANY\n\t\t// source, SDK metadata included: no budget of ours un-caps a page, so a later `maxPages` hit must not\n\t\t// claim raising it would help.\n\t\tproviderTruncated = providerTruncated || pageTruncated;\n\t\t// Whether this drain owes its OWN warning for that is a separate question — `mergeAssessmentInto` has\n\t\t// already appended one when the fact came from SDK metadata, and repeating it would be noise.\n\t\tunexplainedTruncation = unexplainedTruncation || (pageTruncated && !assessment.truncated);\n\n\t\tif (!(value.paging?.more ?? false)) {\n\t\t\t// A read that can't confirm completeness (single-page provider reads with no `hasNextPage`)\n\t\t\t// sets `paging.truncated`; propagate it (and any top-level `truncated` and SDK incompleteness)\n\t\t\t// so the sweep doesn't claim an all-pages result.\n\t\t\tif (unexplainedTruncation) {\n\t\t\t\tappendDedupedWarning(\n\t\t\t\t\twarnings,\n\t\t\t\t\ttruncationWarning(\n\t\t\t\t\t\tid,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t\t'Pull request',\n\t\t\t\t\t\tfetchFailed ? 'interrupted' : 'exhausted',\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn {\n\t\t\t\titems: items,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: fetchFailed,\n\t\t\t\ttruncated: truncated,\n\t\t\t\tfailedProvider: false,\n\t\t\t};\n\t\t}\n\n\t\t// Resolve the continuation BEFORE deciding why the drain stops. `page-budget` claims the missing items\n\t\t// are reachable, which is only true with a usable cursor in hand — and a provider can report another\n\t\t// page while handing back none (Bitbucket Server does, when it omits `nextPageStart`). Checking the\n\t\t// budget first would label that unreachable tail as merely unfetched, so raising `maxPages` would\n\t\t// return the identical set.\n\t\tconst nextCursor = value.paging?.cursor;\n\t\t// A cursor already used isn't a continuation either — following it refetches a page we have, and a\n\t\t// provider that cycles (A→B→A) would otherwise burn the whole budget and then be reported as merely\n\t\t// out of budget. Tracked as a SET rather than compared one-back, matching the SDK's own `followCursors`:\n\t\t// `drainToRequestedPage` and `collectProviderPagedResult` compare only the previous cursor, which a\n\t\t// cycle slips past. That is tolerable there and not here, because only this drain reports `page-budget`.\n\t\tconst continuable = nextCursor != null && nextCursor !== '{}' && !seenCursors.has(nextCursor);\n\t\tif (!continuable || page >= maxPages) {\n\t\t\t// `providerTruncated` outranks the budget: a page the provider capped stays capped however many\n\t\t\t// pages we are allowed to read, so promising `page-budget` on top of it would be a load-more that\n\t\t\t// cannot deliver the capped part.\n\t\t\tappendDedupedWarning(\n\t\t\t\twarnings,\n\t\t\t\ttruncationWarning(\n\t\t\t\t\tid,\n\t\t\t\t\tdomain,\n\t\t\t\t\tconnectionId,\n\t\t\t\t\t'Pull request',\n\t\t\t\t\tfetchFailed ? 'interrupted' : continuable && !providerTruncated ? 'page-budget' : 'exhausted',\n\t\t\t\t),\n\t\t\t);\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn {\n\t\t\t\titems: items,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: fetchFailed,\n\t\t\t\ttruncated: true,\n\t\t\t\tfailedProvider: false,\n\t\t\t};\n\t\t}\n\n\t\tseenCursors.add(nextCursor);\n\t\tcursor = nextCursor;\n\t}\n}\n\n/**\n * Drains every page of repositories under an org for one git-host integration, threading the opaque\n * next-cursor the provider returns. Stops at `maxPages` (marking `truncated`) or on a hard read failure\n * (marking `fetchFailed`), keeping the pages fetched so far.\n */\nexport async function drainRepositories(\n\tintegration: GitHostIntegration,\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\torg: string,\n\tproject: string | undefined,\n\tconnectionId: string | undefined,\n\tmaxPages: number,\n): Promise<{\n\trepos: ProviderRepository[];\n\twarnings: ProviderWarning[];\n\tfetchFailed: boolean;\n\ttruncated: boolean;\n}> {\n\tconst repos: ProviderRepository[] = [];\n\tconst warnings: ProviderWarning[] = [];\n\tlet fetchFailed = false;\n\tlet truncated = false;\n\tlet cursor: string | undefined;\n\tlet page = 0;\n\n\tfor (;;) {\n\t\tpage++;\n\t\tconst pageCursor = cursor;\n\t\tconst { value, warning } = await runCaptured(\n\t\t\tid,\n\t\t\tdomain,\n\t\t\tconnectionId,\n\t\t\t() =>\n\t\t\t\tintegration.getRepositoriesForOrgResult(org, {\n\t\t\t\t\tproject: project,\n\t\t\t\t\tcursor: pageCursor,\n\t\t\t\t\tconnectionId: connectionId,\n\t\t\t\t}),\n\t\t\t{ warnOnMissingSession: true },\n\t\t);\n\t\tif (warning != null) {\n\t\t\t// Deduped, like every other accumulation here: a soft warning (`{ value, error }`) repeats verbatim on\n\t\t\t// each page that hits the same condition, and the array's contract is that no two entries are equal.\n\t\t\tappendDedupedWarning(warnings, warning);\n\t\t}\n\t\tif (value == null) {\n\t\t\tconst interruptedAfterProgress = page > 1;\n\t\t\tif (interruptedAfterProgress && warning == null) {\n\t\t\t\t// Not a backstop: the read was cut short mid-drain, which is why it also sets `fetchFailed`\n\t\t\t\t// below. A retry may complete it, so this must not claim the succeeded-but-capped omission.\n\t\t\t\tappendDedupedWarning(\n\t\t\t\t\twarnings,\n\t\t\t\t\ttruncationWarning(id, domain, connectionId, 'Repository', 'interrupted'),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst failed = fetchFailed || warning != null || interruptedAfterProgress;\n\t\t\t// An SDK omission from an earlier page asserts the read succeeded; this one didn't.\n\t\t\treconcileOmissionsWithFailure(warnings, failed);\n\t\t\treturn {\n\t\t\t\trepos: repos,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: failed,\n\t\t\t\ttruncated: truncated || interruptedAfterProgress,\n\t\t\t};\n\t\t}\n\n\t\trepos.push(...value.values);\n\t\tconst assessment = mergeAssessmentInto(warnings, id, domain, connectionId, value.metadata);\n\t\tfetchFailed = fetchFailed || assessment.fetchFailed;\n\t\ttruncated = truncated || value.truncated === true || value.paging?.truncated === true || assessment.truncated;\n\t\tif (!(value.paging?.more ?? false)) {\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn {\n\t\t\t\trepos: repos,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: fetchFailed,\n\t\t\t\ttruncated: truncated,\n\t\t\t};\n\t\t}\n\n\t\tif (page >= maxPages) {\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn { repos: repos, warnings: warnings, fetchFailed: fetchFailed, truncated: true };\n\t\t}\n\n\t\tconst nextCursor = value.paging?.cursor;\n\t\tif (nextCursor == null || nextCursor === '{}') {\n\t\t\t// Provider says there is more but didn't return a usable cursor; stop rather than refetch the same page.\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn { repos: repos, warnings: warnings, fetchFailed: fetchFailed, truncated: true };\n\t\t}\n\n\t\tcursor = nextCursor;\n\t}\n}\n\nexport function resolvePullRequestSweepTargets(options: PullRequestSweepOptions | undefined): {\n\ttargets: readonly ProviderSweepTarget[];\n\tattributeUnavailableProviders: boolean;\n} {\n\tif (options?.targets != null) {\n\t\tif (options.providerIds != null || options.connectionId != null) {\n\t\t\tthrow new TypeError(\"Pull request sweep 'targets' cannot be combined with 'providerIds' or 'connectionId'\");\n\t\t}\n\n\t\tconst seenProviderIds = new Set<IntegrationIds>();\n\t\tfor (const target of options.targets) {\n\t\t\tif (seenProviderIds.has(target.providerId)) {\n\t\t\t\tthrow new TypeError(\n\t\t\t\t\t`Pull request sweep targets must contain at most one target per provider; duplicate '${target.providerId}'`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tseenProviderIds.add(target.providerId);\n\t\t}\n\n\t\treturn { targets: options.targets, attributeUnavailableProviders: true };\n\t}\n\n\tconst providerIds =\n\t\toptions?.providerIds ?? supportedOrderedCloudIntegrationIds.filter(id => !isIssuesHostIntegrationId(id));\n\tconst connectionId = providerIds.length === 1 ? options?.connectionId : undefined;\n\treturn {\n\t\ttargets: providerIds.map(providerId => ({ providerId: providerId, connectionId: connectionId })),\n\t\tattributeUnavailableProviders: options?.providerIds != null,\n\t};\n}\n\nexport async function getCurrentAccountId(\n\tintegration: GitHostIntegration,\n\tconnectionId: string | undefined,\n): Promise<string | undefined> {\n\ttry {\n\t\treturn (await integration.getCurrentAccount({ connectionId: connectionId }))?.id;\n\t} catch {\n\t\t// Authorship is optional enrichment; don't turn a successful PR read into a failure if identity lookup fails.\n\t\treturn undefined;\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"drains.js","names":[],"sources":["../../../../../plus/integrations/src/reads/drains.ts"],"sourcesContent":["import type { PullRequestStateFilter } from '@gitlens/git/models/pullRequest.js';\nimport { mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { supportedOrderedCloudIntegrationIds } from '../constants.js';\nimport type { ProviderSweepTarget, PullRequestSweepOptions } from '../manager.js';\nimport type { GitHostIntegration } from '../models/gitHostIntegration.js';\nimport type { IntegrationResult } from '../models/integration.js';\nimport type { PullRequestFilter } from '../providerFilters.js';\nimport type { ProviderPullRequest, ProviderReposInput, ProviderRepository } from '../providers/models.js';\nimport { getProviderPullRequestIdentity } from '../providers/models.js';\nimport type { ProviderWarning } from '../results.js';\nimport { appendDedupedWarning, reconcileOmissionsWithFailure, toProviderWarning } from '../results.js';\nimport { isIssuesHostIntegrationId } from '../utils/integration.utils.js';\nimport { noConnectionWarning, truncationWarning } from './warnings.js';\n\n/**\n * The all-pages reads: a provider read run repeatedly until it runs out of pages, as opposed to the single-page\n * reads the facade's paged surface exposes. `sweepPullRequests` and `broadenIssues` are built on these.\n *\n * A drain's job is to keep going where a single page would stop, so what distinguishes it is its failure\n * bookkeeping rather than its loop: it must return the prefix it did read plus enough signal for the caller to\n * know the result is not authoritative — `truncated` when pages remain unread (a page backstop, or a provider\n * claiming another page while handing back no usable cursor), `fetchFailed` when a page or scope failed\n * outright. Discarding the prefix on a late failure, or returning it as if complete, are the two mistakes these\n * exist to prevent.\n */\n\n/**\n * Runs a result-returning read and captures failure as a neutral {@link ProviderWarning} rather than\n * letting it throw or silently vanish. Handles both a returned `{ error }` (the read cores' contract)\n * and a hard throw; a soft warning (`{ value, error }`) yields the value *and* a warning.\n */\nexport async function runCaptured<T>(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tfn: () => Promise<IntegrationResult<T>>,\n\toptions?: { warnOnMissingSession?: boolean },\n): Promise<{ value?: T; warning?: ProviderWarning }> {\n\ttry {\n\t\tconst result = await fn();\n\t\tif (result == null) {\n\t\t\t// The read core returns undefined only when it couldn't resolve a session. For a per-connection\n\t\t\t// or explicit-domain read that means the requested target is gone or its authentication is invalid,\n\t\t\t// which must not be reported as an empty account. The untargeted primary path legitimately yields\n\t\t\t// nothing when the provider isn't connected, so leave it as an empty result.\n\t\t\treturn connectionId != null || options?.warnOnMissingSession\n\t\t\t\t? { warning: noConnectionWarning(id, domain, connectionId) }\n\t\t\t\t: {};\n\t\t}\n\t\tif (result.error != null) {\n\t\t\treturn { value: result.value, warning: toProviderWarning(id, domain, connectionId, result.error) };\n\t\t}\n\t\treturn { value: result.value };\n\t} catch (ex) {\n\t\treturn { warning: toProviderWarning(id, domain, connectionId, ex) };\n\t}\n}\n\n/**\n * Drains every page of the user's pull requests for one git-host integration, threading the opaque\n * next-cursor the provider returns (so it works for both page- and cursor-based hosts). Stops at\n * `maxPages` (marking `truncated`) or on a hard read failure (marking `fetchFailed`), keeping the\n * pages fetched so far. A soft warning (`{ value, error }`) is recorded but the drain continues.\n */\nexport async function drainPullRequests(\n\tintegration: GitHostIntegration,\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\trepos: ProviderReposInput,\n\tstate: PullRequestStateFilter[] | undefined,\n\tfilters: PullRequestFilter[] | undefined,\n\t/**\n\t * Breadth opt-ins that only the account-wide read can express. Grouped rather than passed positionally\n\t * because they are same-typed neighbors a transposition would silently swap, and picked from the sweep\n\t * options rather than restated so a third opt-in is declared once.\n\t */\n\taccountWideOptions: Pick<PullRequestSweepOptions, 'includeReviewRequested' | 'includeReviews'> | undefined,\n\tconnectionId: string | undefined,\n\tmaxPages: number,\n\tattributeUnavailableProvider: boolean,\n): Promise<{\n\titems: ProviderPullRequest[];\n\twarnings: ProviderWarning[];\n\tfetchFailed: boolean;\n\ttruncated: boolean;\n\tfailedProvider: boolean;\n}> {\n\tconst items: ProviderPullRequest[] = [];\n\tconst itemIndexByIdentity = new Map<string, number>();\n\tconst warnings: ProviderWarning[] = [];\n\tlet cursor: string | undefined;\n\tlet page = 0;\n\t// SDK metadata failures across pages mean the collection is incomplete even when no page threw; carry\n\t// this through the terminal returns instead of resetting it to false at the last page.\n\tlet fetchFailed = false;\n\tlet truncated = false;\n\t// A page the provider capped or couldn't vouch for. Decides the CAUSE the terminal warning reports: a cap\n\t// outranks a budget stop, because raising a budget cannot un-cap a page.\n\tlet providerTruncated = false;\n\t// The subset of that which no other warning already explains — decides whether this drain raises one of\n\t// its own on an otherwise clean exit.\n\tlet unexplainedTruncation = false;\n\n\t// With no repos this is an account-wide \"my PRs\" sweep. The repo-scoped core rejects an empty `repos`\n\t// input, so read the provider-native account-wide core instead.\n\tconst accountWide = repos.length === 0;\n\t// The breadth opt-ins are only expressible on the account-wide core, so gate them here — the single place\n\t// that already knows which core it is about to call.\n\tconst includeReviewRequested = accountWide && (accountWideOptions?.includeReviewRequested ?? false);\n\tconst includeReviews = accountWide && (accountWideOptions?.includeReviews ?? false);\n\t/** Every cursor already followed, so a provider that cycles them can't keep the drain walking in circles. */\n\tconst seenCursors = new Set<string>();\n\n\tfor (;;) {\n\t\tpage++;\n\t\t// Snapshot the mutable loop cursor so the read closure doesn't capture a later-reassigned value.\n\t\tconst pageCursor = cursor;\n\t\tconst { value, warning } = await runCaptured(id, domain, connectionId, () =>\n\t\t\taccountWide\n\t\t\t\t? integration.getMyPullRequestsForUserResult(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstate: state,\n\t\t\t\t\t\t\tcursor: pageCursor,\n\t\t\t\t\t\t\tincludeReviewRequested: includeReviewRequested,\n\t\t\t\t\t\t\tfilters: filters,\n\t\t\t\t\t\t\t// Lite by default; the full projection (reviews) only when a caller opts in.\n\t\t\t\t\t\t\tsummary: !includeReviews,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t)\n\t\t\t\t: integration.getMyPullRequestsForReposResult(\n\t\t\t\t\t\trepos,\n\t\t\t\t\t\t{ state: state, filters: filters, cursor: pageCursor },\n\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t),\n\t\t);\n\t\tif (warning != null) {\n\t\t\tappendDedupedWarning(warnings, warning);\n\t\t}\n\t\tif (value == null) {\n\t\t\t// An implicit sweep may silently skip a provider that has no session before it yields data.\n\t\t\t// Once a page has been returned, however, losing that session leaves an unread tail and must\n\t\t\t// be attributed even when the provider wasn't explicitly requested.\n\t\t\tconst sessionLostAfterProgress = warning == null && page > 1;\n\t\t\tconst unavailable = warning == null && (attributeUnavailableProvider || sessionLostAfterProgress);\n\t\t\tif (unavailable) {\n\t\t\t\tappendDedupedWarning(warnings, noConnectionWarning(id, domain, connectionId));\n\t\t\t}\n\t\t\t// `warning` set → a hard read failure (incomplete items); otherwise not connected / no session.\n\t\t\tconst failed = fetchFailed || warning != null || unavailable;\n\t\t\t// A cap seen on an earlier page still left results out, so it is still worth saying — but as part\n\t\t\t// of a read that failed, never as an omission. (`failed` is always true here when anything was\n\t\t\t// latched: reaching this exit past page 1 means a later page was lost.)\n\t\t\tif (unexplainedTruncation) {\n\t\t\t\tappendDedupedWarning(\n\t\t\t\t\twarnings,\n\t\t\t\t\ttruncationWarning(id, domain, connectionId, 'Pull request', failed ? 'interrupted' : 'exhausted'),\n\t\t\t\t);\n\t\t\t}\n\t\t\t// An earlier page may already have emitted an omission before this one died; it asserts the read\n\t\t\t// succeeded, which is no longer true.\n\t\t\treconcileOmissionsWithFailure(warnings, failed);\n\t\t\treturn {\n\t\t\t\titems: items,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: failed,\n\t\t\t\ttruncated: truncated || sessionLostAfterProgress,\n\t\t\t\t// Only a top-level first-page rejection means the provider itself failed. A later-page or\n\t\t\t\t// per-scope failure still yielded a usable provider slice and stays represented separately.\n\t\t\t\tfailedProvider: page === 1 && (warning != null || unavailable),\n\t\t\t};\n\t\t}\n\n\t\t// Composite account-wide queries can surface the same PR on different relationship/state pages\n\t\t// (for example authored + review-requested, or closed + merged). Keep the first stable position but\n\t\t// replace its value with the latest representation so a later, richer merged state wins. Repository\n\t\t// identity is what keys a row (see `getProviderPullRequestIdentity`), falling back to the canonical\n\t\t// URL; a row with neither stays unkeyed and is retained so unrelated incomplete rows are never\n\t\t// collapsed.\n\t\tfor (const pullRequest of value.values) {\n\t\t\tconst identity = getProviderPullRequestIdentity(pullRequest);\n\t\t\tif (identity == null) {\n\t\t\t\titems.push(pullRequest);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst existingIndex = itemIndexByIdentity.get(identity);\n\t\t\tif (existingIndex == null) {\n\t\t\t\titemIndexByIdentity.set(identity, items.length);\n\t\t\t\titems.push(pullRequest);\n\t\t\t} else {\n\t\t\t\titems[existingIndex] = pullRequest;\n\t\t\t}\n\t\t}\n\n\t\t// Assess this page's SDK metadata: append scope-aware warnings (deduped across pages), and remember\n\t\t// whether a structured failure or incompleteness occurred.\n\t\tconst assessment = mergeAssessmentInto(warnings, id, domain, connectionId, value.metadata);\n\t\tfetchFailed = fetchFailed || assessment.fetchFailed;\n\t\tconst pageTruncated =\n\t\t\t(value as { truncated?: boolean }).truncated === true ||\n\t\t\tvalue.paging?.truncated === true ||\n\t\t\tassessment.truncated;\n\t\ttruncated = truncated || pageTruncated;\n\t\t// A page the provider itself capped, or one whose completeness it couldn't confirm. Latched from ANY\n\t\t// source, SDK metadata included: no budget of ours un-caps a page, so a later `maxPages` hit must not\n\t\t// claim raising it would help.\n\t\tproviderTruncated = providerTruncated || pageTruncated;\n\t\t// Whether this drain owes its OWN warning for that is a separate question — `mergeAssessmentInto` has\n\t\t// already appended one when the fact came from SDK metadata, and repeating it would be noise.\n\t\tunexplainedTruncation = unexplainedTruncation || (pageTruncated && !assessment.truncated);\n\n\t\tif (!(value.paging?.more ?? false)) {\n\t\t\t// A read that can't confirm completeness (single-page provider reads with no `hasNextPage`)\n\t\t\t// sets `paging.truncated`; propagate it (and any top-level `truncated` and SDK incompleteness)\n\t\t\t// so the sweep doesn't claim an all-pages result.\n\t\t\tif (unexplainedTruncation) {\n\t\t\t\tappendDedupedWarning(\n\t\t\t\t\twarnings,\n\t\t\t\t\ttruncationWarning(\n\t\t\t\t\t\tid,\n\t\t\t\t\t\tdomain,\n\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t\t'Pull request',\n\t\t\t\t\t\tfetchFailed ? 'interrupted' : 'exhausted',\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn {\n\t\t\t\titems: items,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: fetchFailed,\n\t\t\t\ttruncated: truncated,\n\t\t\t\tfailedProvider: false,\n\t\t\t};\n\t\t}\n\n\t\t// Resolve the continuation BEFORE deciding why the drain stops. `page-budget` claims the missing items\n\t\t// are reachable, which is only true with a usable cursor in hand — and a provider can report another\n\t\t// page while handing back none (Bitbucket Server does, when it omits `nextPageStart`). Checking the\n\t\t// budget first would label that unreachable tail as merely unfetched, so raising `maxPages` would\n\t\t// return the identical set.\n\t\tconst nextCursor = value.paging?.cursor;\n\t\t// A cursor already used isn't a continuation either — following it refetches a page we have, and a\n\t\t// provider that cycles (A→B→A) would otherwise burn the whole budget and then be reported as merely\n\t\t// out of budget. Tracked as a SET rather than compared one-back, matching the SDK's own `followCursors`:\n\t\t// `drainToRequestedPage` and `collectProviderPagedResult` compare only the previous cursor, which a\n\t\t// cycle slips past. That is tolerable there and not here, because only this drain reports `page-budget`.\n\t\tconst continuable = nextCursor != null && nextCursor !== '{}' && !seenCursors.has(nextCursor);\n\t\tif (!continuable || page >= maxPages) {\n\t\t\t// `providerTruncated` outranks the budget: a page the provider capped stays capped however many\n\t\t\t// pages we are allowed to read, so promising `page-budget` on top of it would be a load-more that\n\t\t\t// cannot deliver the capped part.\n\t\t\tappendDedupedWarning(\n\t\t\t\twarnings,\n\t\t\t\ttruncationWarning(\n\t\t\t\t\tid,\n\t\t\t\t\tdomain,\n\t\t\t\t\tconnectionId,\n\t\t\t\t\t'Pull request',\n\t\t\t\t\tfetchFailed ? 'interrupted' : continuable && !providerTruncated ? 'page-budget' : 'exhausted',\n\t\t\t\t),\n\t\t\t);\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn {\n\t\t\t\titems: items,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: fetchFailed,\n\t\t\t\ttruncated: true,\n\t\t\t\tfailedProvider: false,\n\t\t\t};\n\t\t}\n\n\t\tseenCursors.add(nextCursor);\n\t\tcursor = nextCursor;\n\t}\n}\n\n/**\n * Drains every page of repositories under an org for one git-host integration, threading the opaque\n * next-cursor the provider returns. Stops at `maxPages` (marking `truncated`) or on a hard read failure\n * (marking `fetchFailed`), keeping the pages fetched so far.\n */\nexport async function drainRepositories(\n\tintegration: GitHostIntegration,\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\torg: string,\n\tproject: string | undefined,\n\tconnectionId: string | undefined,\n\tmaxPages: number,\n): Promise<{\n\trepos: ProviderRepository[];\n\twarnings: ProviderWarning[];\n\tfetchFailed: boolean;\n\ttruncated: boolean;\n}> {\n\tconst repos: ProviderRepository[] = [];\n\tconst warnings: ProviderWarning[] = [];\n\tlet fetchFailed = false;\n\tlet truncated = false;\n\tlet cursor: string | undefined;\n\tlet page = 0;\n\n\tfor (;;) {\n\t\tpage++;\n\t\tconst pageCursor = cursor;\n\t\tconst { value, warning } = await runCaptured(\n\t\t\tid,\n\t\t\tdomain,\n\t\t\tconnectionId,\n\t\t\t() =>\n\t\t\t\tintegration.getRepositoriesForOrgResult(org, {\n\t\t\t\t\tproject: project,\n\t\t\t\t\tcursor: pageCursor,\n\t\t\t\t\tconnectionId: connectionId,\n\t\t\t\t}),\n\t\t\t{ warnOnMissingSession: true },\n\t\t);\n\t\tif (warning != null) {\n\t\t\t// Deduped, like every other accumulation here: a soft warning (`{ value, error }`) repeats verbatim on\n\t\t\t// each page that hits the same condition, and the array's contract is that no two entries are equal.\n\t\t\tappendDedupedWarning(warnings, warning);\n\t\t}\n\t\tif (value == null) {\n\t\t\tconst interruptedAfterProgress = page > 1;\n\t\t\tif (interruptedAfterProgress && warning == null) {\n\t\t\t\t// Not a backstop: the read was cut short mid-drain, which is why it also sets `fetchFailed`\n\t\t\t\t// below. A retry may complete it, so this must not claim the succeeded-but-capped omission.\n\t\t\t\tappendDedupedWarning(\n\t\t\t\t\twarnings,\n\t\t\t\t\ttruncationWarning(id, domain, connectionId, 'Repository', 'interrupted'),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst failed = fetchFailed || warning != null || interruptedAfterProgress;\n\t\t\t// An SDK omission from an earlier page asserts the read succeeded; this one didn't.\n\t\t\treconcileOmissionsWithFailure(warnings, failed);\n\t\t\treturn {\n\t\t\t\trepos: repos,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: failed,\n\t\t\t\ttruncated: truncated || interruptedAfterProgress,\n\t\t\t};\n\t\t}\n\n\t\trepos.push(...value.values);\n\t\tconst assessment = mergeAssessmentInto(warnings, id, domain, connectionId, value.metadata);\n\t\tfetchFailed = fetchFailed || assessment.fetchFailed;\n\t\ttruncated = truncated || value.truncated === true || value.paging?.truncated === true || assessment.truncated;\n\t\tif (!(value.paging?.more ?? false)) {\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn {\n\t\t\t\trepos: repos,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: fetchFailed,\n\t\t\t\ttruncated: truncated,\n\t\t\t};\n\t\t}\n\n\t\tif (page >= maxPages) {\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn { repos: repos, warnings: warnings, fetchFailed: fetchFailed, truncated: true };\n\t\t}\n\n\t\tconst nextCursor = value.paging?.cursor;\n\t\tif (nextCursor == null || nextCursor === '{}') {\n\t\t\t// Provider says there is more but didn't return a usable cursor; stop rather than refetch the same page.\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn { repos: repos, warnings: warnings, fetchFailed: fetchFailed, truncated: true };\n\t\t}\n\n\t\tcursor = nextCursor;\n\t}\n}\n\nexport function resolvePullRequestSweepTargets(options: PullRequestSweepOptions | undefined): {\n\ttargets: readonly ProviderSweepTarget[];\n\tattributeUnavailableProviders: boolean;\n} {\n\tif (options?.targets != null) {\n\t\tif (options.providerIds != null || options.connectionId != null) {\n\t\t\tthrow new TypeError(\"Pull request sweep 'targets' cannot be combined with 'providerIds' or 'connectionId'\");\n\t\t}\n\n\t\tconst seenProviderIds = new Set<IntegrationIds>();\n\t\tfor (const target of options.targets) {\n\t\t\tif (seenProviderIds.has(target.providerId)) {\n\t\t\t\tthrow new TypeError(\n\t\t\t\t\t`Pull request sweep targets must contain at most one target per provider; duplicate '${target.providerId}'`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tseenProviderIds.add(target.providerId);\n\t\t}\n\n\t\treturn { targets: options.targets, attributeUnavailableProviders: true };\n\t}\n\n\tconst providerIds =\n\t\toptions?.providerIds ?? supportedOrderedCloudIntegrationIds.filter(id => !isIssuesHostIntegrationId(id));\n\tconst connectionId = providerIds.length === 1 ? options?.connectionId : undefined;\n\treturn {\n\t\ttargets: providerIds.map(providerId => ({ providerId: providerId, connectionId: connectionId })),\n\t\tattributeUnavailableProviders: options?.providerIds != null,\n\t};\n}\n\nexport async function getCurrentAccountIdentity(\n\tintegration: GitHostIntegration,\n\tconnectionId: string | undefined,\n): Promise<{ id: string; username?: string } | undefined> {\n\ttry {\n\t\tconst account = await integration.getCurrentAccount({ connectionId: connectionId });\n\t\treturn account != null ? { id: account.id, username: account.username } : undefined;\n\t} catch {\n\t\t// Authorship is optional enrichment; don't turn a successful PR read into a failure if identity lookup fails.\n\t\treturn undefined;\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mapBounded } from "../../../utils/promise.js";
|
|
2
|
+
import { supportedOrderedCloudIntegrationIds, supportedOrderedCloudIssuesIntegrationIds } from "../constants.js";
|
|
2
3
|
import { appendDedupedWarning } from "../results.js";
|
|
3
4
|
import { mergeAssessmentInto } from "../collectionMetadata.js";
|
|
4
|
-
import { supportedOrderedCloudIntegrationIds, supportedOrderedCloudIssuesIntegrationIds } from "../constants.js";
|
|
5
5
|
import { isGitHostIntegration, isIssuesHostIntegrationId, warnOnMissingSessionForDomain } from "../utils/integration.utils.js";
|
|
6
6
|
import { toProviderRepositoryShape } from "../providers/models.js";
|
|
7
7
|
import { pageToCursor, refusedPage, resolveContinuation, resolveCurrentPage, toProviderPageInfo } from "./paging.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { appendDedupedWarning } from "../results.js";
|
|
2
1
|
import "../constants.js";
|
|
2
|
+
import { appendDedupedWarning } from "../results.js";
|
|
3
3
|
//#region ../plus/integrations/src/reads/hierarchy.utils.ts
|
|
4
4
|
/**
|
|
5
5
|
* Mapping of a provider's own hierarchy vocabulary onto the single {@link ProviderOrganization} shape the facade
|