@gitkraken/core-gitlens 0.20.2 → 0.22.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.
Files changed (67) hide show
  1. package/CHANGELOG.md +21 -1
  2. package/dist/git/models/issue.d.ts +30 -1
  3. package/dist/git/models/issue.js +3 -1
  4. package/dist/git/models/issue.js.map +1 -1
  5. package/dist/git/utils/autolink.utils.d.ts +4 -1
  6. package/dist/git/utils/autolink.utils.js +12 -3
  7. package/dist/git/utils/autolink.utils.js.map +1 -1
  8. package/dist/git/utils/issue.utils.js +2 -1
  9. package/dist/git/utils/issue.utils.js.map +1 -1
  10. package/dist/plus/integrations/authentication/configuredIntegrationService.d.ts +7 -0
  11. package/dist/plus/integrations/authentication/configuredIntegrationService.js +12 -0
  12. package/dist/plus/integrations/authentication/configuredIntegrationService.js.map +1 -1
  13. package/dist/plus/integrations/authentication/integrationAuthenticationService.js +5 -3
  14. package/dist/plus/integrations/authentication/integrationAuthenticationService.js.map +1 -1
  15. package/dist/plus/integrations/authentication/models.d.ts +9 -0
  16. package/dist/plus/integrations/authentication/models.js +13 -1
  17. package/dist/plus/integrations/authentication/models.js.map +1 -1
  18. package/dist/plus/integrations/authentication/observedAuthenticationProvider.d.ts +34 -0
  19. package/dist/plus/integrations/authentication/observedAuthenticationProvider.js +227 -0
  20. package/dist/plus/integrations/authentication/observedAuthenticationProvider.js.map +1 -0
  21. package/dist/plus/integrations/index.d.ts +4 -2
  22. package/dist/plus/integrations/index.js.map +1 -1
  23. package/dist/plus/integrations/integrationService.d.ts +9 -4
  24. package/dist/plus/integrations/integrationService.js +30 -7
  25. package/dist/plus/integrations/integrationService.js.map +1 -1
  26. package/dist/plus/integrations/manager.d.ts +18 -1
  27. package/dist/plus/integrations/models/integration.d.ts +21 -2
  28. package/dist/plus/integrations/models/integration.js +87 -11
  29. package/dist/plus/integrations/models/integration.js.map +1 -1
  30. package/dist/plus/integrations/models/issuesIntegration.d.ts +20 -1
  31. package/dist/plus/integrations/models/issuesIntegration.js +106 -2
  32. package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
  33. package/dist/plus/integrations/providers/jira-server.d.ts +10 -2
  34. package/dist/plus/integrations/providers/jira-server.js +74 -7
  35. package/dist/plus/integrations/providers/jira-server.js.map +1 -1
  36. package/dist/plus/integrations/providers/jira.d.ts +1 -1
  37. package/dist/plus/integrations/providers/jira.js +3 -25
  38. package/dist/plus/integrations/providers/jira.js.map +1 -1
  39. package/dist/plus/integrations/providers/linear.d.ts +1 -1
  40. package/dist/plus/integrations/providers/linear.js +3 -27
  41. package/dist/plus/integrations/providers/linear.js.map +1 -1
  42. package/dist/plus/integrations/providers/linearIssuesEtag.d.ts +20 -4
  43. package/dist/plus/integrations/providers/linearIssuesEtag.js +42 -6
  44. package/dist/plus/integrations/providers/linearIssuesEtag.js.map +1 -1
  45. package/dist/plus/integrations/providers/models.js +15 -2
  46. package/dist/plus/integrations/providers/models.js.map +1 -1
  47. package/dist/plus/integrations/providers/providerErrors.d.ts +2 -0
  48. package/dist/plus/integrations/providers/providerErrors.js +5 -2
  49. package/dist/plus/integrations/providers/providerErrors.js.map +1 -1
  50. package/dist/plus/integrations/providers/trello.d.ts +1 -1
  51. package/dist/plus/integrations/providers/trello.js +1 -1
  52. package/dist/plus/integrations/providers/trello.js.map +1 -1
  53. package/dist/plus/integrations/providers/utils/issueAutolinks.d.ts +8 -0
  54. package/dist/plus/integrations/providers/utils/issueAutolinks.js +20 -0
  55. package/dist/plus/integrations/providers/utils/issueAutolinks.js.map +1 -0
  56. package/dist/plus/integrations/reads/autolinks.d.ts +7 -0
  57. package/dist/plus/integrations/reads/autolinks.js +40 -0
  58. package/dist/plus/integrations/reads/autolinks.js.map +1 -0
  59. package/dist/plus/integrations/reads/context.d.ts +4 -2
  60. package/dist/plus/integrations/reads/etag.js +3 -1
  61. package/dist/plus/integrations/reads/etag.js.map +1 -1
  62. package/dist/plus/integrations/reads/hierarchy.js +1 -1
  63. package/dist/plus/integrations/reads/issueBatch.js +1 -1
  64. package/dist/plus/integrations/reads/issueTracker.js +1 -1
  65. package/dist/plus/integrations/reads/resolveRepository.js +1 -1
  66. package/docs/integrations.md +34 -2
  67. package/package.json +2 -2
@@ -1,8 +1,9 @@
1
1
  import { Event } from "../../utils/event.js";
2
+ import { RefSet } from "../../git/models/autolink.js";
2
3
  import { IssueSearchCriteria, IssueShape, IssueSorting, IssueStateFilter } from "../../git/models/issue.js";
3
4
  import { PullRequestFilter, PullRequestSearchCriteria, PullRequestShape, PullRequestStateFilter } from "../../git/models/pullRequest.js";
4
5
  import { ConfiguredIntegrationDescriptor } from "./authentication/models.js";
5
- import { IntegrationIds } from "./constants.js";
6
+ import { IntegrationIds, IssuesCloudHostIntegrationId, IssuesSelfManagedHostIntegrationId } from "./constants.js";
6
7
  import { ConfiguredIntegrationsChangeEvent } from "./authentication/configuredIntegrationService.js";
7
8
  import { ConnectionStateChangeEvent, ProviderBroadenResult, ProviderOrganization, ProviderPagedResult, ProviderRepositoryShape, ProviderResult, ProviderSweepResult, ResolveRepositoryResult } from "./results.js";
8
9
  import { IssueFilter } from "./providerFilters.js";
@@ -214,6 +215,14 @@ export type ListProjectsOptions = {
214
215
  connectionId?: never;
215
216
  domain?: never;
216
217
  };
218
+ export type GetAutolinkReferencesOptions = {
219
+ providerId: IssuesCloudHostIntegrationId.Jira | IssuesSelfManagedHostIntegrationId.JiraServer;
220
+ connectionId?: string;
221
+ /** Trusted self-managed domain fallback, as on the other provider reads. */
222
+ domain?: string;
223
+ /** Drops discovery and awaits a fresh build; retires builds started before the refresh. */
224
+ forceSync?: boolean;
225
+ };
217
226
  /**
218
227
  * Public, provider-neutral integration facade. Provider clients and integration model instances remain private
219
228
  * implementation details so SDK changes don't expand this contract.
@@ -281,6 +290,14 @@ export interface IntegrationManager {
281
290
  deleteConnection(id: IntegrationIds, connectionId: string): Promise<void>;
282
291
  listOrgs(options?: ListOrgsOptions): Promise<ProviderResult<ProviderOrganization>>;
283
292
  listProjects(options?: ListProjectsOptions): Promise<ProviderResult<ProviderOrganization>>;
293
+ /**
294
+ * Jira project references for one connection. Cold discovery is awaited. Returns `undefined` for an
295
+ * unresolved target or failed/incomplete discovery; an empty set means complete discovery found no links.
296
+ * Each call reads the provider; callers own coalescing and caching.
297
+ * Match with `getAutolinks` or `getBranchAutolinks` from `git/utils/autolink.utils.js` in the host process;
298
+ * references can contain compiled regular expressions and are not a wire format.
299
+ */
300
+ getAutolinkReferences(options: GetAutolinkReferencesOptions): Promise<RefSet | undefined>;
284
301
  listRepos(options: {
285
302
  providerId: IntegrationIds;
286
303
  org?: string;
@@ -3,11 +3,11 @@ import { Emitter, Event } from "../../../utils/event.js";
3
3
  import { ResourceDescriptor } from "../../../git/models/resourceDescriptor.js";
4
4
  import { AutolinkReference, DynamicAutolinkReference } from "../../../git/models/autolink.js";
5
5
  import { IssueOrPullRequest, IssueOrPullRequestState, IssueOrPullRequestType } from "../../../git/models/issueOrPullRequest.js";
6
- import { Issue as Issue$1, IssueShape } from "../../../git/models/issue.js";
6
+ import { Issue as Issue$1, IssueShape, IssueSyncedWith } from "../../../git/models/issue.js";
7
7
  import { PullRequest, PullRequestMergeableState, PullRequestReviewDecision, PullRequestReviewState, PullRequestStatusCheckRollupState } from "../../../git/models/pullRequest.js";
8
8
  import { ScopedLogger } from "../../../utils/logger.scoped.js";
9
9
  import { Account as Account$1 } from "../../../git/models/author.js";
10
- import { ConnectionResource, ProviderAuthenticationSession } from "../authentication/models.js";
10
+ import { ConnectionResource, ProviderAuthenticationSession, SessionPublication } from "../authentication/models.js";
11
11
  import { GitCloudHostIntegrationId, IntegrationIds, IssuesCloudHostIntegrationId, IssuesHostIntegrationIds } from "../constants.js";
12
12
  import { Sources } from "../telemetry.js";
13
13
  import { ProviderWarningCause, ProviderWarningScope } from "../results.js";
@@ -135,6 +135,13 @@ export interface IssueEtagFields {
135
135
  * it never compares unchanged and is read in full again rather than kept without one.
136
136
  */
137
137
  viewerRule?: number;
138
+ /**
139
+ * Linear only: the row's `syncedWith` links as {@link linearSyncedWithEtagKeys} spells them, so a link added, removed
140
+ * or moved to other coordinates changes the etag whether or not the issue's update time moves. `null` on a
141
+ * full row without `syncedWith` (held from before it existed, or read while Linear failed to resolve it), which
142
+ * never compares unchanged with a cheap check's list.
143
+ */
144
+ syncedWithKeys?: readonly string[] | null;
138
145
  }
139
146
  /**
140
147
  * {@link IssueEtagFields.descriptionRule}: the version of the rule an Azure DevOps work item's body is read by, stamped
@@ -154,6 +161,11 @@ export declare const azureWorkItemDescriptionRule = 1;
154
161
  * identity in the work item's organization (gitkraken/kepler#4291).
155
162
  */
156
163
  export declare const azureWorkItemViewerRule = 1;
164
+ /**
165
+ * A Linear row's sync links in its etag, sorted: each as its GitHub id and, when known, the coordinates a host matches
166
+ * the GitHub row by, so a link whose coordinates Linear fills in or changes later compares changed.
167
+ */
168
+ export declare function linearSyncedWithEtagKeys(links: readonly IssueSyncedWith[]): string[];
157
169
  /** A batch read's target, in the form its provider addresses it by: a repository coordinate, or a tracker's resource. */
158
170
  type BatchTarget = {
159
171
  owner: string;
@@ -288,8 +300,15 @@ export declare abstract class IntegrationBase<ID extends IntegrationIds = Integr
288
300
  * otherwise stay invisible, or keep being searched, for the rest of the session (#5907).
289
301
  */
290
302
  invalidateDiscoveryCaches(): void;
303
+ invalidateDiscoveryForConnection(_connectionId?: string): void;
304
+ getSessionDiscoveryGeneration(_sessionId?: string, _connectionId?: string, _cloud?: boolean): number;
305
+ get discoveryGeneration(): number;
306
+ protected isResolvedSessionCurrent(_session: ProviderAuthenticationSession | undefined, _generation: number): boolean;
307
+ onSessionPublishing(publication: SessionPublication): void;
308
+ protected invalidatePublishedSession(_publication: SessionPublication, _affectsPrimary: boolean): void;
291
309
  private static readonly requestExceptionLimit;
292
310
  private requestExceptionCount;
311
+ private resetRecoveryAccounting;
293
312
  resetRequestExceptionCount(syncReqUsecase: SyncReqUsecase | 'all'): void;
294
313
  /**
295
314
  * Resets request exceptions without resetting the amount of syncs
@@ -1,4 +1,4 @@
1
- import { isCancellationError } from "../../../utils/cancellation.js";
1
+ import { CancellationError, isCancellationError } from "../../../utils/cancellation.js";
2
2
  import { Logger as _logger } from "../../../utils/logger.js";
3
3
  import { getScopedLogger } from "../../../utils/logger.scoped.js";
4
4
  import { debug, trace } from "../../../utils/decorators/log.js";
@@ -12,6 +12,7 @@ import { isProviderUnreachableError, toError } from "../errors.js";
12
12
  import "../constants.js";
13
13
  import { isAzureProviderId } from "../providers/providerErrors.js";
14
14
  import { attributeScopedAuthFailures, hasOnlyScopedAuthFailures, markCredentialRefusals, toCollectionScopeFailure } from "../collectionMetadata.js";
15
+ import { getSessionIdentity } from "../authentication/models.js";
15
16
  import { areDomainsOnSameHost } from "../utils/domain.utils.js";
16
17
  import { isSelfManagedHostIntegrationId } from "../utils/integration.utils.js";
17
18
  import { throwIfAllSettledFailed } from "../providers/utils/providerPaging.js";
@@ -74,6 +75,13 @@ const azureWorkItemDescriptionRule = 1;
74
75
  */
75
76
  const azureWorkItemViewerRule = 1;
76
77
  /**
78
+ * A Linear row's sync links in its etag, sorted: each as its GitHub id and, when known, the coordinates a host matches
79
+ * the GitHub row by, so a link whose coordinates Linear fills in or changes later compares changed.
80
+ */
81
+ function linearSyncedWithEtagKeys(links) {
82
+ return links.map((link) => link.owner != null ? `${link.id}:${link.owner}/${link.repo}#${link.number}` : `${link.id}:`).sort();
83
+ }
84
+ /**
77
85
  * The scope a batch read records a refused target under. It is decided here rather than in the read, which has the
78
86
  * same targets, because naming the refusal's cause (`describeRefusal`) needs it too. Azure DevOps: the organization
79
87
  * and project, since its refusals come from those (an organization's OAuth policy or tenant, a project's permissions)
@@ -111,6 +119,7 @@ var IntegrationBase = class IntegrationBase {
111
119
  this.didChangeConnection = didChangeConnection;
112
120
  }
113
121
  dispose() {
122
+ this.invalidateDiscoveryCaches();
114
123
  this._onDidChange.dispose();
115
124
  }
116
125
  get authProviderDescriptor() {
@@ -358,13 +367,41 @@ var IntegrationBase = class IntegrationBase {
358
367
  * otherwise stay invisible, or keep being searched, for the rest of the session (#5907).
359
368
  */
360
369
  invalidateDiscoveryCaches() {}
370
+ invalidateDiscoveryForConnection(_connectionId) {
371
+ this.invalidateDiscoveryCaches();
372
+ }
373
+ getSessionDiscoveryGeneration(_sessionId, _connectionId, _cloud) {
374
+ return this.discoveryGeneration;
375
+ }
376
+ get discoveryGeneration() {
377
+ return 0;
378
+ }
379
+ isResolvedSessionCurrent(_session, _generation) {
380
+ return true;
381
+ }
382
+ onSessionPublishing(publication) {
383
+ const { session, previousSessionId, replacePrimary } = publication;
384
+ const cloud = session != null ? session.cloud : publication.cloud;
385
+ const sameVariant = cloud == null || this._session?.cloud == null || cloud === this._session.cloud;
386
+ const affectsPrimary = replacePrimary === true && (session != null || cloud == null || this._session != null && sameVariant) || this._session === null && session != null || this._session != null && sameVariant && (session?.id === this._session.id || previousSessionId === this._session.id);
387
+ if (affectsPrimary && getSessionIdentity(this._session) !== getSessionIdentity(session)) this._session = void 0;
388
+ this.invalidatePublishedSession(publication, affectsPrimary);
389
+ }
390
+ invalidatePublishedSession(_publication, _affectsPrimary) {
391
+ this.invalidateDiscoveryCaches();
392
+ }
361
393
  static requestExceptionLimit = 5;
362
394
  requestExceptionCount = 0;
395
+ resetRecoveryAccounting() {
396
+ this.requestExceptionCount = 0;
397
+ this._syncRequestsPerFailedUsecase.clear();
398
+ this._rejectedTokens.clear();
399
+ }
363
400
  resetRequestExceptionCount(syncReqUsecase) {
364
401
  this.requestExceptionCount = 0;
365
402
  if (syncReqUsecase === "all") {
366
- this._syncRequestsPerFailedUsecase.clear();
367
- this.onStoredTokensReplaced();
403
+ this.resetRecoveryAccounting();
404
+ this.invalidateDiscoveryCaches();
368
405
  } else this._syncRequestsPerFailedUsecase.delete(syncReqUsecase);
369
406
  }
370
407
  /**
@@ -409,7 +446,7 @@ var IntegrationBase = class IntegrationBase {
409
446
  syncCloudConnection(state, forceSync) {
410
447
  return this.runSessionTransition(() => this.syncCloudConnectionLocked(state, forceSync));
411
448
  }
412
- async syncCloudConnectionLocked(state, forceSync) {
449
+ async syncCloudConnectionLocked(state, forceSync, reason = "explicit") {
413
450
  const scope = getScopedLogger();
414
451
  if (this._session?.cloud === false && state !== "connected") {
415
452
  if (this.id !== "github" && !this.skippedNonCloudReported) {
@@ -428,7 +465,10 @@ var IntegrationBase = class IntegrationBase {
428
465
  const oldSession = this._session;
429
466
  let resyncing = false;
430
467
  if (forceSync) {
431
- this.onStoredTokensReplaced();
468
+ if (reason === "explicit") {
469
+ this._rejectedTokens.clear();
470
+ this.invalidateDiscoveryForConnection();
471
+ }
432
472
  await this.ctx.storage.deleteWorkspace(this.connectedKey);
433
473
  resyncing = true;
434
474
  } else {
@@ -447,7 +487,7 @@ var IntegrationBase = class IntegrationBase {
447
487
  if (!isCancellationError(ex)) scope?.error(ex);
448
488
  failure = toError(ex);
449
489
  }
450
- if (oldSession && newSession && newSession.accessToken !== oldSession.accessToken) this.resetRequestExceptionCount("all");
490
+ if (oldSession && newSession && newSession === this._session && newSession.accessToken !== oldSession.accessToken) this.resetRecoveryAccounting();
451
491
  if (resyncing && newSession == null && !refetchFailed) await (await this.authenticationService.get(this.authProvider.id)).deleteSession(this.authProviderDescriptor, { preserveConfigured: false });
452
492
  break;
453
493
  }
@@ -653,7 +693,7 @@ var IntegrationBase = class IntegrationBase {
653
693
  let failure;
654
694
  if (this._resyncs !== resyncs) failure = this._lastResyncFailure;
655
695
  else if (this._session != null && this.connectionExpired === true) try {
656
- failure = await this.syncCloudConnectionLocked("connected", true);
696
+ failure = await this.syncCloudConnectionLocked("connected", true, "expired");
657
697
  } catch (ex) {
658
698
  failure = toError(ex);
659
699
  }
@@ -712,8 +752,14 @@ var IntegrationBase = class IntegrationBase {
712
752
  this._session = null;
713
753
  }
714
754
  if (this.ctx.config.isIntegrationsEnabled?.() === false) return void 0;
715
- if (createIfNeeded || sync) await this.ctx.storage.deleteWorkspace(this.connectedKey);
716
- else if (this.ctx.storage.getWorkspace(this.connectedKey) === false) return;
755
+ const generation = this.discoveryGeneration;
756
+ if (createIfNeeded || sync) {
757
+ await this.ctx.storage.deleteWorkspace(this.connectedKey);
758
+ if (this.getSessionDiscoveryGeneration(this._session?.id, void 0, this._session?.cloud) > generation) {
759
+ if (sync) throw new CancellationError();
760
+ return;
761
+ }
762
+ } else if (this.ctx.storage.getWorkspace(this.connectedKey) === false) return;
717
763
  let session;
718
764
  try {
719
765
  session = await (await this.authenticationService.get(this.authProvider.id)).getSession(this.authProviderDescriptor, sync ? {
@@ -727,7 +773,15 @@ var IntegrationBase = class IntegrationBase {
727
773
  if (session?.expiresAt != null && session.expiresAt < /* @__PURE__ */ new Date()) session = null;
728
774
  if (session != null && !this.isSessionForIntegrationHost(session)) session = void 0;
729
775
  } catch (ex) {
776
+ if (isCancellationError(ex) || !this.isResolvedSessionCurrent(void 0, generation)) {
777
+ if (sync) throw new CancellationError();
778
+ return;
779
+ }
730
780
  await this.ctx.storage.deleteWorkspace(this.connectedKey);
781
+ if (!this.isResolvedSessionCurrent(void 0, generation)) {
782
+ if (sync) throw new CancellationError();
783
+ return;
784
+ }
731
785
  if (!sync && ex instanceof Error && ex.message.includes("User did not consent")) return;
732
786
  if (sync) {
733
787
  this.smoothifyRequestExceptionCount();
@@ -735,18 +789,40 @@ var IntegrationBase = class IntegrationBase {
735
789
  }
736
790
  session = null;
737
791
  }
738
- if (session === void 0 && !createIfNeeded && !sync) await this.ctx.storage.deleteWorkspace(this.connectedKey);
792
+ if (!this.isResolvedSessionCurrent(session ?? void 0, generation)) {
793
+ if (sync) throw new CancellationError();
794
+ return;
795
+ }
796
+ if (session === void 0 && !createIfNeeded && !sync) {
797
+ await this.ctx.storage.deleteWorkspace(this.connectedKey);
798
+ if (!this.isResolvedSessionCurrent(void 0, generation)) return void 0;
799
+ }
800
+ if (getSessionIdentity(this._session) !== getSessionIdentity(session)) this.onSessionPublishing({
801
+ session: session ?? void 0,
802
+ replacePrimary: true
803
+ });
804
+ else if (refetch && session != null) this.onSessionPublishing({
805
+ session,
806
+ replacePrimary: true
807
+ });
739
808
  this._session = session ?? null;
740
809
  this.smoothifyRequestExceptionCount();
741
810
  if (session != null) {
811
+ const publishedGeneration = this.getSessionDiscoveryGeneration(session.id, void 0, session.cloud);
742
812
  await this.ctx.storage.storeWorkspace(this.connectedKey, true);
813
+ if (this._session !== session || publishedGeneration !== this.getSessionDiscoveryGeneration(session.id, void 0, session.cloud)) {
814
+ if (sync) throw new CancellationError();
815
+ return;
816
+ }
743
817
  queueMicrotask(() => {
818
+ if (this._session !== session || publishedGeneration !== this.getSessionDiscoveryGeneration(session.id, void 0, session.cloud)) return;
744
819
  this._onDidChange.fire();
745
820
  this.didChangeConnection?.fire({
746
821
  integration: this,
747
822
  key: this.key,
748
823
  reason: "connected"
749
824
  });
825
+ if (this._session !== session || publishedGeneration !== this.getSessionDiscoveryGeneration(session.id, void 0, session.cloud)) return;
750
826
  (async () => {
751
827
  try {
752
828
  await this.providerOnConnect?.();
@@ -968,6 +1044,6 @@ __decorate([trace()], IntegrationBase.prototype, "getLinkedIssueOrPullRequest",
968
1044
  __decorate([trace()], IntegrationBase.prototype, "getIssueResultCore", null);
969
1045
  __decorate([trace()], IntegrationBase.prototype, "getPullRequest", null);
970
1046
  //#endregion
971
- export { IntegrationBase, ReadSessionFailure, azureWorkItemDescriptionRule, azureWorkItemViewerRule, isReadSessionFailure, issueEtagIncludes, pullRequestEtagIncludes, pullRequestRevision };
1047
+ export { IntegrationBase, ReadSessionFailure, azureWorkItemDescriptionRule, azureWorkItemViewerRule, isReadSessionFailure, issueEtagIncludes, linearSyncedWithEtagKeys, pullRequestEtagIncludes, pullRequestRevision };
972
1048
 
973
1049
  //# sourceMappingURL=integration.js.map