@gitkraken/core-gitlens 0.5.101 → 0.5.102
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 +18 -1
- package/dist/plus/integrations/collectionMetadata.d.ts +1 -9
- package/dist/plus/integrations/collectionMetadata.d.ts.map +1 -1
- package/dist/plus/integrations/collectionMetadata.js +43 -14
- package/dist/plus/integrations/collectionMetadata.js.map +1 -1
- package/dist/plus/integrations/index.d.ts +1 -1
- package/dist/plus/integrations/index.d.ts.map +1 -1
- package/dist/plus/integrations/index.js.map +1 -1
- package/dist/plus/integrations/providers/utils/providerPaging.d.ts.map +1 -1
- package/dist/plus/integrations/providers/utils/providerPaging.js +2 -1
- package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
- package/dist/plus/integrations/reads/drains.d.ts.map +1 -1
- package/dist/plus/integrations/reads/drains.js +59 -20
- package/dist/plus/integrations/reads/drains.js.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.d.ts.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.js +8 -2
- package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
- package/dist/plus/integrations/reads/issues.d.ts.map +1 -1
- package/dist/plus/integrations/reads/issues.js +13 -3
- package/dist/plus/integrations/reads/issues.js.map +1 -1
- package/dist/plus/integrations/reads/pullRequests.d.ts.map +1 -1
- package/dist/plus/integrations/reads/pullRequests.js +7 -1
- package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
- package/dist/plus/integrations/reads/warnings.d.ts +37 -4
- package/dist/plus/integrations/reads/warnings.d.ts.map +1 -1
- package/dist/plus/integrations/reads/warnings.js +50 -5
- package/dist/plus/integrations/reads/warnings.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +115 -1
- package/dist/plus/integrations/results.d.ts.map +1 -1
- package/dist/plus/integrations/results.js +78 -3
- package/dist/plus/integrations/results.js.map +1 -1
- package/docs/integrations.md +81 -7
- package/package.json +1 -1
- package/src/plus/integrations/collectionMetadata.ts +45 -18
- package/src/plus/integrations/index.ts +4 -0
- package/src/plus/integrations/providers/utils/providerPaging.ts +2 -1
- package/src/plus/integrations/reads/drains.ts +84 -20
- package/src/plus/integrations/reads/issueTracker.ts +8 -2
- package/src/plus/integrations/reads/issues.ts +21 -4
- package/src/plus/integrations/reads/pullRequests.ts +15 -1
- package/src/plus/integrations/reads/warnings.ts +78 -10
- package/src/plus/integrations/results.ts +173 -4
|
@@ -4,6 +4,7 @@ import type { IntegrationIds } from '../constants.js';
|
|
|
4
4
|
import type { ProviderPullRequest, ProviderReposInput, PullRequestFilter } from '../providers/models.js';
|
|
5
5
|
import { fromProviderPullRequest, PagingMode, providersMetadata } from '../providers/models.js';
|
|
6
6
|
import type { ProviderPagedResult } from '../results.js';
|
|
7
|
+
import { reconcileOmissionsWithFailure } from '../results.js';
|
|
7
8
|
import {
|
|
8
9
|
isGitHostIntegration,
|
|
9
10
|
isIssuesHostIntegrationId,
|
|
@@ -226,8 +227,21 @@ export async function listPullRequestsPage(
|
|
|
226
227
|
// the page may be incomplete. Metadata incompleteness is an independent source of the same signal.
|
|
227
228
|
const truncated = continuation.truncated || assessment.truncated;
|
|
228
229
|
if (truncated && warnings.length === 0) {
|
|
229
|
-
|
|
230
|
+
// `exhausted`, never `page-budget`: a paged read has no budget the caller can raise. Ordinary
|
|
231
|
+
// continuation is already expressed by `hasMore`/`cursor`, so reaching here means this page itself
|
|
232
|
+
// couldn't be completed — raising anything would return the same page.
|
|
233
|
+
warnings.push(
|
|
234
|
+
truncationWarning(
|
|
235
|
+
options.providerId,
|
|
236
|
+
domain,
|
|
237
|
+
options.connectionId,
|
|
238
|
+
'Pull request',
|
|
239
|
+
assessment.fetchFailed || pageFetchFailed ? 'interrupted' : 'exhausted',
|
|
240
|
+
),
|
|
241
|
+
);
|
|
230
242
|
}
|
|
243
|
+
// A metadata omission from an earlier page asserts the read succeeded; a later page may since have failed.
|
|
244
|
+
reconcileOmissionsWithFailure(warnings, assessment.fetchFailed || pageFetchFailed);
|
|
231
245
|
const currentAccountId = items.some(pr => pr.author != null)
|
|
232
246
|
? await getCurrentAccountId(integration, options.connectionId)
|
|
233
247
|
: undefined;
|
|
@@ -93,22 +93,90 @@ export function issueTrackerOnlySurfaceWarning(
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
96
|
+
* Why a read the facade drove itself returned less than everything. The two questions a consumer cannot
|
|
97
|
+
* answer from `truncated` or from the message, kept together because they are decided together:
|
|
98
|
+
*
|
|
99
|
+
* - `interrupted`: the read did NOT succeed — the session went away, a later page failed. It leaves an unread
|
|
100
|
+
* tail like the others, but it is a failure: it sets `fetchFailed`, and a retry is exactly the right move.
|
|
101
|
+
* Gets no omission at all, since the omission asserts the opposite.
|
|
102
|
+
* - `page-budget`: the drain stopped at its own `maxPages`, with a usable cursor still in hand. The items ARE
|
|
103
|
+
* reachable and were simply not fetched, so re-running with a higher budget returns them.
|
|
104
|
+
* - `exhausted`: everything else that succeeded and came back short — the provider capped the page it served,
|
|
105
|
+
* advertised another page without a usable cursor (or with one it had already handed out), or a read could
|
|
106
|
+
* not confirm it had drained everything. The default of the three: choose it whenever a raisable budget is
|
|
107
|
+
* not demonstrably what stopped the read, so a consumer is never offered a fetch that cannot deliver.
|
|
108
|
+
*/
|
|
109
|
+
export type IncompleteReadCause = 'interrupted' | 'page-budget' | 'exhausted';
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Builds the warning for a read that returned less than everything, carrying the two facts a consumer cannot
|
|
113
|
+
* recover on its own: did the request succeed, and would anything fetch the rest?
|
|
114
|
+
*
|
|
115
|
+
* Every incompleteness warning the facade raises on its own terms goes through here, so no read can drift
|
|
116
|
+
* into claiming success on a failure or offering a "load more" that cannot deliver. The omission it attaches
|
|
117
|
+
* is the same shape `assessCollectionMetadata` derives from SDK metadata, so a consumer never has to care
|
|
118
|
+
* which layer noticed the gap.
|
|
119
|
+
*
|
|
120
|
+
* No `scope`: this applies to the whole read, not to one repository or project the way an SDK-attributed
|
|
121
|
+
* omission can, so a consumer acting on `recovery` re-runs the read itself.
|
|
122
|
+
*/
|
|
123
|
+
export function incompleteReadWarning(
|
|
124
|
+
id: IntegrationIds,
|
|
125
|
+
domain: string | undefined,
|
|
126
|
+
connectionId: string | undefined,
|
|
127
|
+
message: string,
|
|
128
|
+
cause: IncompleteReadCause,
|
|
129
|
+
): ProviderWarning {
|
|
130
|
+
const warning = otherWarning(id, domain, connectionId, message);
|
|
131
|
+
if (cause === 'interrupted') return warning;
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
...warning,
|
|
135
|
+
omission: {
|
|
136
|
+
kind: 'pagination-incomplete',
|
|
137
|
+
recovery: cause === 'page-budget' ? 'page-budget' : 'none',
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* {@link incompleteReadWarning} for a paged or drained read, phrased per surface. `'Account-wide issue search'`
|
|
144
|
+
* names the composite read that spans several provider searches rather than one surface.
|
|
99
145
|
*/
|
|
100
146
|
export function truncationWarning(
|
|
101
147
|
id: IntegrationIds,
|
|
102
148
|
domain: string | undefined,
|
|
103
149
|
connectionId: string | undefined,
|
|
104
|
-
readKind: 'Pull request' | 'Issue' | 'Repository',
|
|
150
|
+
readKind: 'Pull request' | 'Issue' | 'Repository' | 'Account-wide issue search',
|
|
151
|
+
/**
|
|
152
|
+
* Deliberately REQUIRED and not defaulted: a default would make one of these the silent fallback at any
|
|
153
|
+
* call site that forgot it, and both of the claims it carries — "the request succeeded" and "more can be
|
|
154
|
+
* fetched" — are ones this warning must never make by accident.
|
|
155
|
+
*/
|
|
156
|
+
cause: IncompleteReadCause,
|
|
105
157
|
): ProviderWarning {
|
|
106
|
-
return
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
158
|
+
return incompleteReadWarning(id, domain, connectionId, truncationMessage(id, readKind, cause), cause);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function truncationMessage(
|
|
162
|
+
id: IntegrationIds,
|
|
163
|
+
readKind: 'Pull request' | 'Issue' | 'Repository' | 'Account-wide issue search',
|
|
164
|
+
cause: IncompleteReadCause,
|
|
165
|
+
): string {
|
|
166
|
+
switch (cause) {
|
|
167
|
+
case 'interrupted':
|
|
168
|
+
// Deliberately does not name a mechanism. This fires both when a page was lost mid-drain and when a
|
|
169
|
+
// drain that latched an earlier scope failure later stopped for its own reasons; "was interrupted"
|
|
170
|
+
// would be false in the second. What is true in both is that something failed and results are gone.
|
|
171
|
+
return `${readKind} read for '${id}' did not complete; some results are missing and the read reported a failure.`;
|
|
172
|
+
case 'page-budget':
|
|
173
|
+
return `${readKind} read for '${id}' stopped at its page budget; more results can be read by raising it.`;
|
|
174
|
+
case 'exhausted':
|
|
175
|
+
return `${readKind} read for '${id}' was truncated and cannot be continued; results may be incomplete.`;
|
|
176
|
+
}
|
|
177
|
+
// No `default`: `IncompleteReadCause` is declared in this file, so `noImplicitReturns` already fails the
|
|
178
|
+
// build here if a cause is added without its own wording. (`collectionOmissionMessage`'s `satisfies never`
|
|
179
|
+
// is not the same case — that union is the SDK's, and can widen under a dependency bump.)
|
|
112
180
|
}
|
|
113
181
|
|
|
114
182
|
/** Warning for an account-wide issue read whose requested filters the provider can't express server-side. */
|
|
@@ -15,6 +15,72 @@ export interface ConnectionStateChangeEvent {
|
|
|
15
15
|
*/
|
|
16
16
|
export type ProviderWarningKind = 'auth' | 'rate-limit' | 'not-found' | 'no-connection' | 'other';
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Why a read that SUCCEEDED still withheld results:
|
|
20
|
+
* - `provider-limit`: the provider refuses to serve past a cap it enforces (GitHub search's 1,000-result
|
|
21
|
+
* ceiling, Trello's `cards_limit`), so the excess is unreachable through that query.
|
|
22
|
+
* - `recovery-budget`: the internal partitioned recovery stopped spending upstream requests before it ran
|
|
23
|
+
* out of partitions to visit.
|
|
24
|
+
* - `pagination-incomplete`: pages were left unread — a sub-scope of a multi-scope read that was not drained,
|
|
25
|
+
* a drain that stopped at its own page backstop, or a provider that advertised another page and gave no way
|
|
26
|
+
* to reach it. These have DIFFERENT remedies, so the kind alone does not say whether more can be fetched;
|
|
27
|
+
* read {@link ProviderWarningOmission.recovery} for that.
|
|
28
|
+
*
|
|
29
|
+
* Mirrors the vocabulary the SDK reports. Declared here rather than imported so this module stays free of
|
|
30
|
+
* `@gitkraken/provider-apis` types (see the export block in `index.ts`); `collectionMetadata.ts` holds the
|
|
31
|
+
* compile-time link, so an SDK bump that adds a kind fails the build at that boundary.
|
|
32
|
+
*/
|
|
33
|
+
export type ProviderWarningOmissionKind = 'provider-limit' | 'recovery-budget' | 'pagination-incomplete';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* What, if anything, would fetch the withheld results — the question `kind` cannot answer.
|
|
37
|
+
*
|
|
38
|
+
* `kind` says WHY results are missing, and two omissions of the same kind can need opposite handling: a drain
|
|
39
|
+
* that stopped at a caller-settable page budget and a provider that advertised another page without a usable
|
|
40
|
+
* cursor are both `pagination-incomplete`, but only the first can be fetched. A consumer offering a "load
|
|
41
|
+
* more" affordance gates it on this, never on `kind`.
|
|
42
|
+
*
|
|
43
|
+
* - `none`: nothing the consumer can call returns the missing items — a provider-enforced cap, an internal
|
|
44
|
+
* budget it does not control, or a continuation the provider refused to hand back. Say the results are
|
|
45
|
+
* capped; do not offer to fetch more.
|
|
46
|
+
* - `page-budget`: re-run the SAME read with a higher page budget (`maxPages` on the sweep options). Note this
|
|
47
|
+
* re-reads from the start rather than continuing — a sweep exposes no cursor — so it is a deliberate,
|
|
48
|
+
* user-initiated action, not something to retry automatically.
|
|
49
|
+
*
|
|
50
|
+
* Required, not optional: an absent value would be indistinguishable from `none` while actually meaning "this
|
|
51
|
+
* producer didn't say", which is the ambiguity {@link ProviderWarning.omission} exists to remove. And a
|
|
52
|
+
* conservative union on purpose — it names only what a producer can PROVE. A value is added when some layer
|
|
53
|
+
* can vouch for it, never so that a plausible-looking case has something to map to; see
|
|
54
|
+
* `collectionMetadata.ts` for the SDK shape that looks recoverable and is not.
|
|
55
|
+
*/
|
|
56
|
+
export type ProviderWarningOmissionRecovery = 'none' | 'page-budget';
|
|
57
|
+
|
|
58
|
+
/** Which repository / project / resource an omission is attributed to. All fields optional; a scope may name none. */
|
|
59
|
+
export interface ProviderWarningOmissionScope {
|
|
60
|
+
providerId?: string;
|
|
61
|
+
resourceId?: string;
|
|
62
|
+
projectId?: string;
|
|
63
|
+
repositoryId?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface ProviderWarningOmission {
|
|
67
|
+
kind: ProviderWarningOmissionKind;
|
|
68
|
+
/**
|
|
69
|
+
* Whether anything would fetch the missing items, and what. Gate a "load more" affordance on this rather
|
|
70
|
+
* than on `kind` — see {@link ProviderWarningOmissionRecovery}.
|
|
71
|
+
*/
|
|
72
|
+
recovery: ProviderWarningOmissionRecovery;
|
|
73
|
+
/**
|
|
74
|
+
* The cap the provider enforces, when it reports one. NOT a result count for every kind: on
|
|
75
|
+
* `recovery-budget` this is a REQUEST budget and must not be shown to a user as a number of results.
|
|
76
|
+
*/
|
|
77
|
+
limit?: number;
|
|
78
|
+
/** Total matches the provider reported, when it reports one. Only GitHub's search cap does today. */
|
|
79
|
+
totalCount?: number;
|
|
80
|
+
/** Which repository / project / resource was affected, when one is attributed. */
|
|
81
|
+
scope?: ProviderWarningOmissionScope;
|
|
82
|
+
}
|
|
83
|
+
|
|
18
84
|
export interface ProviderWarning {
|
|
19
85
|
providerId: IntegrationIds;
|
|
20
86
|
/** Disambiguates connections on self-managed hosts (where one provider id spans multiple domains). */
|
|
@@ -25,6 +91,29 @@ export interface ProviderWarning {
|
|
|
25
91
|
kind: ProviderWarningKind;
|
|
26
92
|
/** Convenience mirror of `kind === 'auth'`. */
|
|
27
93
|
isAuth: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Present when this warning describes results the read could not return even though the request itself
|
|
96
|
+
* SUCCEEDED — a provider-enforced cap, an exhausted recovery budget, a page budget, or a sub-scope the read
|
|
97
|
+
* did not drain.
|
|
98
|
+
*
|
|
99
|
+
* Its presence is the signal: an omission is not a failure. Message it as incompleteness rather than as a
|
|
100
|
+
* failed read, and do NOT derive that from `message`, which is English prose and subject to rewording.
|
|
101
|
+
* `kind` stays `'other'` for these, so the failure discriminant keeps meaning exactly what it meant before
|
|
102
|
+
* this field existed.
|
|
103
|
+
*
|
|
104
|
+
* Whether anything would fetch the rest is a SEPARATE question — read {@link ProviderWarningOmission.recovery},
|
|
105
|
+
* not `kind`, and never assume a retry of the same request returns more.
|
|
106
|
+
*
|
|
107
|
+
* `limit` / `totalCount` / `scope` are forwarded only when they are reported; most omissions carry none of
|
|
108
|
+
* the three, so render correctly without them — and see {@link ProviderWarningOmission.limit} before
|
|
109
|
+
* showing that figure as a result count.
|
|
110
|
+
*
|
|
111
|
+
* Its ABSENCE proves nothing. It is never set on a warning derived from a caught exception (see
|
|
112
|
+
* {@link toProviderWarning}) or from a structured scope failure — those are failures, and the field would
|
|
113
|
+
* be a lie there. But it is also absent whenever incompleteness was reported without naming what was left
|
|
114
|
+
* out, so treat a bare `kind: 'other'` warning as unclassified rather than as a proven failure.
|
|
115
|
+
*/
|
|
116
|
+
omission?: ProviderWarningOmission;
|
|
28
117
|
}
|
|
29
118
|
|
|
30
119
|
export interface ProviderPageInfo {
|
|
@@ -259,14 +348,94 @@ export function toProviderWarning(
|
|
|
259
348
|
};
|
|
260
349
|
}
|
|
261
350
|
|
|
262
|
-
/**
|
|
263
|
-
|
|
264
|
-
|
|
351
|
+
/**
|
|
352
|
+
* A scope's identity, as the stable string that keys it.
|
|
353
|
+
*
|
|
354
|
+
* The single definition of what "the same scope" means, shared by the failure and omission dedup keys in
|
|
355
|
+
* `providerPaging.ts` and by {@link providerWarningKey} below, so a scope gaining a field is one edit rather
|
|
356
|
+
* than three. `providerId` is included: the same repository ID under two providers is two scopes.
|
|
357
|
+
*
|
|
358
|
+
* The parameter is structural rather than the SDK's `CollectionScope` so this module keeps naming no
|
|
359
|
+
* `@gitkraken/provider-apis` types (see the export block in `index.ts`) while still serving its SDK-facing
|
|
360
|
+
* callers, which pass that type in unchanged.
|
|
361
|
+
*/
|
|
362
|
+
export function collectionScopeKey(scope: ProviderWarningOmissionScope | undefined): string {
|
|
363
|
+
return [scope?.providerId ?? '', scope?.resourceId ?? '', scope?.projectId ?? '', scope?.repositoryId ?? ''].join(
|
|
265
364
|
' ',
|
|
266
365
|
);
|
|
267
366
|
}
|
|
268
367
|
|
|
269
|
-
/**
|
|
368
|
+
/**
|
|
369
|
+
* The omission's contribution to a warning's identity — empty when there is none, so a warning without an
|
|
370
|
+
* omission keeps deduping exactly as it did before the field existed.
|
|
371
|
+
*
|
|
372
|
+
* Two omissions of different kinds do produce different `message` values today, so message alone would still
|
|
373
|
+
* separate them. That is incidental: the premise of `omission` is that consumers must not depend on prose
|
|
374
|
+
* carrying the distinguishing fact, and this key must not either.
|
|
375
|
+
*/
|
|
376
|
+
function providerWarningOmissionKey(omission: ProviderWarningOmission | undefined): string {
|
|
377
|
+
if (omission == null) return '';
|
|
378
|
+
|
|
379
|
+
return [
|
|
380
|
+
omission.kind,
|
|
381
|
+
omission.recovery,
|
|
382
|
+
omission.limit ?? '',
|
|
383
|
+
omission.totalCount ?? '',
|
|
384
|
+
collectionScopeKey(omission.scope),
|
|
385
|
+
].join(' ');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Strips the omission from every warning in `warnings` when the read as a whole failed.
|
|
390
|
+
*
|
|
391
|
+
* An omission asserts the request SUCCEEDED, and a drain only learns it failed AFTER it may have emitted one:
|
|
392
|
+
* an early page can report its own truncation and then a later page can die. Deciding per warning, at the
|
|
393
|
+
* moment each is built, cannot see that future — so the aggregate is reconciled once, here, where
|
|
394
|
+
* `fetchFailed` is final. Call it at the point a read returns its `fetchFailed`.
|
|
395
|
+
*
|
|
396
|
+
* Re-dedupes as it goes: the omission is part of a warning's identity, so two warnings that differed only
|
|
397
|
+
* there become identical once it is gone, and the array's contract is that no two entries are equal.
|
|
398
|
+
*
|
|
399
|
+
* Mutates in place: the warning array is the one being returned, and callers accumulate into it across pages.
|
|
400
|
+
*/
|
|
401
|
+
export function reconcileOmissionsWithFailure(warnings: ProviderWarning[], fetchFailed: boolean): void {
|
|
402
|
+
if (!fetchFailed || !warnings.some(w => w.omission != null)) return;
|
|
403
|
+
|
|
404
|
+
const reconciled: ProviderWarning[] = [];
|
|
405
|
+
for (const warning of warnings) {
|
|
406
|
+
if (warning.omission == null) {
|
|
407
|
+
appendDedupedWarning(reconciled, warning);
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const { omission: _omission, ...rest } = warning;
|
|
412
|
+
appendDedupedWarning(reconciled, rest);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
warnings.splice(0, warnings.length, ...reconciled);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* A stable key for deduplicating warnings accumulated across drained pages / fan-out scopes.
|
|
420
|
+
*
|
|
421
|
+
* `message` stays LAST. It is the only free-form segment — provider prose, spaces and all — so anything
|
|
422
|
+
* appended after it could be impersonated by a message that happens to end in the same text.
|
|
423
|
+
*/
|
|
424
|
+
function providerWarningKey(warning: ProviderWarning): string {
|
|
425
|
+
return [
|
|
426
|
+
warning.providerId,
|
|
427
|
+
warning.connectionId ?? '',
|
|
428
|
+
warning.domain ?? '',
|
|
429
|
+
warning.kind,
|
|
430
|
+
providerWarningOmissionKey(warning.omission),
|
|
431
|
+
warning.message,
|
|
432
|
+
].join(' ');
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Appends `warning` to `into` only when an equal warning (by provider/connection/domain/kind/message, plus the
|
|
437
|
+
* structured omission when one is present) is absent.
|
|
438
|
+
*/
|
|
270
439
|
export function appendDedupedWarning(into: ProviderWarning[], warning: ProviderWarning): void {
|
|
271
440
|
const key = providerWarningKey(warning);
|
|
272
441
|
if (into.some(existing => providerWarningKey(existing) === key)) return;
|