@gitkraken/core-gitlens 0.3.1 → 0.4.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 (230) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/README.md +13 -10
  3. package/dist/git/context.d.ts +4 -3
  4. package/dist/git/context.d.ts.map +1 -1
  5. package/dist/git/context.js.map +1 -1
  6. package/dist/git/providers/operations.d.ts +1 -0
  7. package/dist/git/providers/operations.d.ts.map +1 -1
  8. package/dist/git/providers/worktrees.d.ts +2 -0
  9. package/dist/git/providers/worktrees.d.ts.map +1 -1
  10. package/dist/git/utils/conflictResolution.utils.d.ts +32 -0
  11. package/dist/git/utils/conflictResolution.utils.d.ts.map +1 -1
  12. package/dist/git/utils/conflictResolution.utils.js +95 -0
  13. package/dist/git/utils/conflictResolution.utils.js.map +1 -1
  14. package/dist/git-cli/providers/operations.d.ts +1 -0
  15. package/dist/git-cli/providers/operations.d.ts.map +1 -1
  16. package/dist/git-cli/providers/operations.js +22 -1
  17. package/dist/git-cli/providers/operations.js.map +1 -1
  18. package/dist/git-cli/providers/worktrees.d.ts +2 -0
  19. package/dist/git-cli/providers/worktrees.d.ts.map +1 -1
  20. package/dist/git-cli/providers/worktrees.js +3 -0
  21. package/dist/git-cli/providers/worktrees.js.map +1 -1
  22. package/dist/plus/ai/utils/ai.utils.js +5 -5
  23. package/dist/plus/ai/utils/ai.utils.js.map +1 -1
  24. package/dist/plus/git-github/api/github.d.ts +6 -13
  25. package/dist/plus/git-github/api/github.d.ts.map +1 -1
  26. package/dist/plus/git-github/api/github.js +5 -10
  27. package/dist/plus/git-github/api/github.js.map +1 -1
  28. package/dist/plus/integrations/authentication/cloudIntegrationService.d.ts +11 -0
  29. package/dist/plus/integrations/authentication/cloudIntegrationService.d.ts.map +1 -0
  30. package/dist/plus/integrations/authentication/cloudIntegrationService.js +90 -0
  31. package/dist/plus/integrations/authentication/cloudIntegrationService.js.map +1 -0
  32. package/dist/plus/integrations/authentication/configuredIntegrationService.d.ts +67 -0
  33. package/dist/plus/integrations/authentication/configuredIntegrationService.d.ts.map +1 -0
  34. package/dist/plus/integrations/authentication/configuredIntegrationService.js +295 -0
  35. package/dist/plus/integrations/authentication/configuredIntegrationService.js.map +1 -0
  36. package/dist/plus/integrations/authentication/integrationAuthenticationProvider.d.ts +92 -0
  37. package/dist/plus/integrations/authentication/integrationAuthenticationProvider.d.ts.map +1 -0
  38. package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js +185 -0
  39. package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js.map +1 -0
  40. package/dist/plus/integrations/authentication/integrationAuthenticationService.d.ts +62 -0
  41. package/dist/plus/integrations/authentication/integrationAuthenticationService.d.ts.map +1 -0
  42. package/dist/plus/integrations/authentication/integrationAuthenticationService.js +122 -0
  43. package/dist/plus/integrations/authentication/integrationAuthenticationService.js.map +1 -0
  44. package/dist/plus/integrations/authentication/manualTokenProvider.d.ts +15 -0
  45. package/dist/plus/integrations/authentication/manualTokenProvider.d.ts.map +1 -0
  46. package/dist/plus/integrations/authentication/manualTokenProvider.js +49 -0
  47. package/dist/plus/integrations/authentication/manualTokenProvider.js.map +1 -0
  48. package/dist/plus/integrations/authentication/models.d.ts +87 -0
  49. package/dist/plus/integrations/authentication/models.d.ts.map +1 -0
  50. package/dist/plus/integrations/authentication/models.js +64 -0
  51. package/dist/plus/integrations/authentication/models.js.map +1 -0
  52. package/dist/plus/integrations/constants.d.ts +47 -0
  53. package/dist/plus/integrations/constants.d.ts.map +1 -0
  54. package/dist/plus/integrations/constants.js +128 -0
  55. package/dist/plus/integrations/constants.js.map +1 -0
  56. package/dist/plus/integrations/context.d.ts +354 -0
  57. package/dist/plus/integrations/context.d.ts.map +1 -0
  58. package/dist/plus/integrations/context.js +2 -0
  59. package/dist/plus/integrations/context.js.map +1 -0
  60. package/dist/plus/integrations/errors.d.ts +14 -0
  61. package/dist/plus/integrations/errors.d.ts.map +1 -0
  62. package/dist/plus/integrations/errors.js +19 -0
  63. package/dist/plus/integrations/errors.js.map +1 -0
  64. package/dist/plus/integrations/index.d.ts +32 -0
  65. package/dist/plus/integrations/index.d.ts.map +1 -0
  66. package/dist/plus/integrations/index.js +62 -0
  67. package/dist/plus/integrations/index.js.map +1 -0
  68. package/dist/plus/integrations/integrationService.d.ts +98 -0
  69. package/dist/plus/integrations/integrationService.d.ts.map +1 -0
  70. package/dist/plus/integrations/integrationService.js +627 -0
  71. package/dist/plus/integrations/integrationService.js.map +1 -0
  72. package/dist/plus/integrations/models/gitHostIntegration.d.ts +82 -0
  73. package/dist/plus/integrations/models/gitHostIntegration.d.ts.map +1 -0
  74. package/dist/plus/integrations/models/gitHostIntegration.js +522 -0
  75. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -0
  76. package/dist/plus/integrations/models/integration.d.ts +127 -0
  77. package/dist/plus/integrations/models/integration.d.ts.map +1 -0
  78. package/dist/plus/integrations/models/integration.js +456 -0
  79. package/dist/plus/integrations/models/integration.js.map +1 -0
  80. package/dist/plus/integrations/models/issuesIntegration.d.ts +28 -0
  81. package/dist/plus/integrations/models/issuesIntegration.d.ts.map +1 -0
  82. package/dist/plus/integrations/models/issuesIntegration.js +101 -0
  83. package/dist/plus/integrations/models/issuesIntegration.js.map +1 -0
  84. package/dist/plus/integrations/providers/apiClients.d.ts +25 -0
  85. package/dist/plus/integrations/providers/apiClients.d.ts.map +1 -0
  86. package/dist/plus/integrations/providers/apiClients.js +39 -0
  87. package/dist/plus/integrations/providers/apiClients.js.map +1 -0
  88. package/dist/plus/integrations/providers/azure/azure.d.ts +47 -0
  89. package/dist/plus/integrations/providers/azure/azure.d.ts.map +1 -0
  90. package/dist/plus/integrations/providers/azure/azure.js +444 -0
  91. package/dist/plus/integrations/providers/azure/azure.js.map +1 -0
  92. package/dist/plus/integrations/providers/azure/models.d.ts +286 -0
  93. package/dist/plus/integrations/providers/azure/models.d.ts.map +1 -0
  94. package/dist/plus/integrations/providers/azure/models.js +203 -0
  95. package/dist/plus/integrations/providers/azure/models.js.map +1 -0
  96. package/dist/plus/integrations/providers/azureDevOps.d.ts +95 -0
  97. package/dist/plus/integrations/providers/azureDevOps.d.ts.map +1 -0
  98. package/dist/plus/integrations/providers/azureDevOps.js +432 -0
  99. package/dist/plus/integrations/providers/azureDevOps.js.map +1 -0
  100. package/dist/plus/integrations/providers/bitbucket/bitbucket.d.ts +42 -0
  101. package/dist/plus/integrations/providers/bitbucket/bitbucket.d.ts.map +1 -0
  102. package/dist/plus/integrations/providers/bitbucket/bitbucket.js +513 -0
  103. package/dist/plus/integrations/providers/bitbucket/bitbucket.js.map +1 -0
  104. package/dist/plus/integrations/providers/bitbucket/models.d.ts +222 -0
  105. package/dist/plus/integrations/providers/bitbucket/models.d.ts.map +1 -0
  106. package/dist/plus/integrations/providers/bitbucket/models.js +158 -0
  107. package/dist/plus/integrations/providers/bitbucket/models.js.map +1 -0
  108. package/dist/plus/integrations/providers/bitbucket-server/models.d.ts +120 -0
  109. package/dist/plus/integrations/providers/bitbucket-server/models.d.ts.map +1 -0
  110. package/dist/plus/integrations/providers/bitbucket-server/models.js +111 -0
  111. package/dist/plus/integrations/providers/bitbucket-server/models.js.map +1 -0
  112. package/dist/plus/integrations/providers/bitbucket-server.d.ts +60 -0
  113. package/dist/plus/integrations/providers/bitbucket-server.d.ts.map +1 -0
  114. package/dist/plus/integrations/providers/bitbucket-server.js +138 -0
  115. package/dist/plus/integrations/providers/bitbucket-server.js.map +1 -0
  116. package/dist/plus/integrations/providers/bitbucket.d.ts +50 -0
  117. package/dist/plus/integrations/providers/bitbucket.d.ts.map +1 -0
  118. package/dist/plus/integrations/providers/bitbucket.js +181 -0
  119. package/dist/plus/integrations/providers/bitbucket.js.map +1 -0
  120. package/dist/plus/integrations/providers/github/github.d.ts +5 -0
  121. package/dist/plus/integrations/providers/github/github.d.ts.map +1 -0
  122. package/dist/plus/integrations/providers/github/github.js +47 -0
  123. package/dist/plus/integrations/providers/github/github.js.map +1 -0
  124. package/dist/plus/integrations/providers/github/github.utils.d.ts +11 -0
  125. package/dist/plus/integrations/providers/github/github.utils.d.ts.map +1 -0
  126. package/dist/plus/integrations/providers/github/github.utils.js +6 -0
  127. package/dist/plus/integrations/providers/github/github.utils.js.map +1 -0
  128. package/dist/plus/integrations/providers/github/models.d.ts +3 -0
  129. package/dist/plus/integrations/providers/github/models.d.ts.map +1 -0
  130. package/dist/plus/integrations/providers/github/models.js +2 -0
  131. package/dist/plus/integrations/providers/github/models.js.map +1 -0
  132. package/dist/plus/integrations/providers/github.d.ts +73 -0
  133. package/dist/plus/integrations/providers/github.d.ts.map +1 -0
  134. package/dist/plus/integrations/providers/github.js +160 -0
  135. package/dist/plus/integrations/providers/github.js.map +1 -0
  136. package/dist/plus/integrations/providers/gitlab/gitlab.d.ts +63 -0
  137. package/dist/plus/integrations/providers/gitlab/gitlab.d.ts.map +1 -0
  138. package/dist/plus/integrations/providers/gitlab/gitlab.js +828 -0
  139. package/dist/plus/integrations/providers/gitlab/gitlab.js.map +1 -0
  140. package/dist/plus/integrations/providers/gitlab/gitlab.utils.d.ts +21 -0
  141. package/dist/plus/integrations/providers/gitlab/gitlab.utils.d.ts.map +1 -0
  142. package/dist/plus/integrations/providers/gitlab/gitlab.utils.js +45 -0
  143. package/dist/plus/integrations/providers/gitlab/gitlab.utils.js.map +1 -0
  144. package/dist/plus/integrations/providers/gitlab/models.d.ts +128 -0
  145. package/dist/plus/integrations/providers/gitlab/models.d.ts.map +1 -0
  146. package/dist/plus/integrations/providers/gitlab/models.js +96 -0
  147. package/dist/plus/integrations/providers/gitlab/models.js.map +1 -0
  148. package/dist/plus/integrations/providers/gitlab.d.ts +79 -0
  149. package/dist/plus/integrations/providers/gitlab.d.ts.map +1 -0
  150. package/dist/plus/integrations/providers/gitlab.js +253 -0
  151. package/dist/plus/integrations/providers/gitlab.js.map +1 -0
  152. package/dist/plus/integrations/providers/jira.d.ts +46 -0
  153. package/dist/plus/integrations/providers/jira.d.ts.map +1 -0
  154. package/dist/plus/integrations/providers/jira.js +255 -0
  155. package/dist/plus/integrations/providers/jira.js.map +1 -0
  156. package/dist/plus/integrations/providers/linear.d.ts +47 -0
  157. package/dist/plus/integrations/providers/linear.d.ts.map +1 -0
  158. package/dist/plus/integrations/providers/linear.js +198 -0
  159. package/dist/plus/integrations/providers/linear.js.map +1 -0
  160. package/dist/plus/integrations/providers/models.d.ts +433 -0
  161. package/dist/plus/integrations/providers/models.d.ts.map +1 -0
  162. package/dist/plus/integrations/providers/models.js +602 -0
  163. package/dist/plus/integrations/providers/models.js.map +1 -0
  164. package/dist/plus/integrations/providers/providersApi.d.ts +129 -0
  165. package/dist/plus/integrations/providers/providersApi.d.ts.map +1 -0
  166. package/dist/plus/integrations/providers/providersApi.js +602 -0
  167. package/dist/plus/integrations/providers/providersApi.js.map +1 -0
  168. package/dist/plus/integrations/providers/utils.d.ts +54 -0
  169. package/dist/plus/integrations/providers/utils.d.ts.map +1 -0
  170. package/dist/plus/integrations/providers/utils.js +262 -0
  171. package/dist/plus/integrations/providers/utils.js.map +1 -0
  172. package/dist/plus/integrations/telemetry.d.ts +26 -0
  173. package/dist/plus/integrations/telemetry.d.ts.map +1 -0
  174. package/dist/plus/integrations/telemetry.js +11 -0
  175. package/dist/plus/integrations/telemetry.js.map +1 -0
  176. package/dist/plus/integrations/utils/integration.utils.d.ts +14 -0
  177. package/dist/plus/integrations/utils/integration.utils.d.ts.map +1 -0
  178. package/dist/plus/integrations/utils/integration.utils.js +111 -0
  179. package/dist/plus/integrations/utils/integration.utils.js.map +1 -0
  180. package/dist/utils/disposable.d.ts +15 -2
  181. package/dist/utils/disposable.d.ts.map +1 -1
  182. package/dist/utils/disposable.js.map +1 -1
  183. package/package.json +43 -2
  184. package/src/git/context.ts +4 -3
  185. package/src/git/providers/operations.ts +1 -1
  186. package/src/git/providers/worktrees.ts +14 -2
  187. package/src/git/utils/conflictResolution.utils.ts +126 -0
  188. package/src/git-cli/providers/operations.ts +22 -2
  189. package/src/git-cli/providers/worktrees.ts +17 -2
  190. package/src/plus/ai/utils/ai.utils.ts +5 -5
  191. package/src/plus/git-github/api/github.ts +13 -25
  192. package/src/plus/integrations/authentication/cloudIntegrationService.ts +120 -0
  193. package/src/plus/integrations/authentication/configuredIntegrationService.ts +414 -0
  194. package/src/plus/integrations/authentication/integrationAuthenticationProvider.ts +289 -0
  195. package/src/plus/integrations/authentication/integrationAuthenticationService.ts +144 -0
  196. package/src/plus/integrations/authentication/manualTokenProvider.ts +72 -0
  197. package/src/plus/integrations/authentication/models.ts +168 -0
  198. package/src/plus/integrations/constants.ts +169 -0
  199. package/src/plus/integrations/context.ts +383 -0
  200. package/src/plus/integrations/errors.ts +35 -0
  201. package/src/plus/integrations/index.ts +119 -0
  202. package/src/plus/integrations/integrationService.ts +924 -0
  203. package/src/plus/integrations/models/gitHostIntegration.ts +738 -0
  204. package/src/plus/integrations/models/integration.ts +657 -0
  205. package/src/plus/integrations/models/issuesIntegration.ts +124 -0
  206. package/src/plus/integrations/providers/apiClients.ts +72 -0
  207. package/src/plus/integrations/providers/azure/azure.ts +691 -0
  208. package/src/plus/integrations/providers/azure/models.ts +605 -0
  209. package/src/plus/integrations/providers/azureDevOps.ts +677 -0
  210. package/src/plus/integrations/providers/bitbucket/bitbucket.ts +774 -0
  211. package/src/plus/integrations/providers/bitbucket/models.ts +445 -0
  212. package/src/plus/integrations/providers/bitbucket-server/models.ts +252 -0
  213. package/src/plus/integrations/providers/bitbucket-server.ts +282 -0
  214. package/src/plus/integrations/providers/bitbucket.ts +350 -0
  215. package/src/plus/integrations/providers/github/github.ts +64 -0
  216. package/src/plus/integrations/providers/github/github.utils.ts +23 -0
  217. package/src/plus/integrations/providers/github/models.ts +29 -0
  218. package/src/plus/integrations/providers/github.ts +366 -0
  219. package/src/plus/integrations/providers/gitlab/gitlab.ts +1210 -0
  220. package/src/plus/integrations/providers/gitlab/gitlab.utils.ts +71 -0
  221. package/src/plus/integrations/providers/gitlab/models.ts +257 -0
  222. package/src/plus/integrations/providers/gitlab.ts +473 -0
  223. package/src/plus/integrations/providers/jira.ts +359 -0
  224. package/src/plus/integrations/providers/linear.ts +275 -0
  225. package/src/plus/integrations/providers/models.ts +1114 -0
  226. package/src/plus/integrations/providers/providersApi.ts +1126 -0
  227. package/src/plus/integrations/providers/utils.ts +359 -0
  228. package/src/plus/integrations/telemetry.ts +29 -0
  229. package/src/plus/integrations/utils/integration.utils.ts +138 -0
  230. package/src/utils/disposable.ts +15 -2
@@ -0,0 +1,924 @@
1
+ import type { Account } from '../../git/models/author.js';
2
+ import type { IssueShape } from '../../git/models/issue.js';
3
+ import type { PullRequest } from '../../git/models/pullRequest.js';
4
+ import type { GitRemote } from '../../git/models/remote.js';
5
+ import type { RemoteProviderId } from '../../git/models/remoteProvider.js';
6
+ import type { ResourceDescriptor } from '../../git/models/resourceDescriptor.js';
7
+ import { gate } from '../../utils/decorators/gate.js';
8
+ import { debug, trace } from '../../utils/decorators/log.js';
9
+ import type { Disposable } from '../../utils/disposable.js';
10
+ import { fromDisposables } from '../../utils/disposable.js';
11
+ import type { Event } from '../../utils/event.js';
12
+ import { Emitter } from '../../utils/event.js';
13
+ import { filterMap, flatten } from '../../utils/iterable.js';
14
+ import { getScopedLogger } from '../../utils/logger.scoped.js';
15
+ import type {
16
+ ConfiguredIntegrationsChangeEvent,
17
+ ConfiguredIntegrationService,
18
+ } from './authentication/configuredIntegrationService.js';
19
+ import type { IntegrationAuthenticationService } from './authentication/integrationAuthenticationService.js';
20
+ import type { ConfiguredIntegrationDescriptor } from './authentication/models.js';
21
+ import {
22
+ getSupportedCloudIntegrationIds,
23
+ isSupportedCloudIntegrationId,
24
+ toIntegrationId,
25
+ } from './authentication/models.js';
26
+ import type {
27
+ CloudGitSelfManagedHostIntegrationIds,
28
+ IntegrationIds,
29
+ SupportedCloudIntegrationIds,
30
+ } from './constants.js';
31
+ import {
32
+ GitCloudHostIntegrationId,
33
+ GitSelfManagedHostIntegrationId,
34
+ IssuesCloudHostIntegrationId,
35
+ } from './constants.js';
36
+ import type { AuthenticationSessionsChangeEvent, IntegrationServiceContext } from './context.js';
37
+ import type { GitHostIntegration } from './models/gitHostIntegration.js';
38
+ import type {
39
+ Integration,
40
+ IntegrationBase,
41
+ IntegrationById,
42
+ IntegrationKey,
43
+ IntegrationResult,
44
+ } from './models/integration.js';
45
+ import type { IssuesIntegration } from './models/issuesIntegration.js';
46
+ import type { ApiClients } from './providers/apiClients.js';
47
+ import { createApiClients } from './providers/apiClients.js';
48
+ import type { GitHubApi } from './providers/github/github.js';
49
+ import type { ProvidersApi } from './providers/providersApi.js';
50
+ import type { Source } from './telemetry.js';
51
+ import {
52
+ convertRemoteProviderIdToIntegrationId,
53
+ getIntegrationIdForRemote,
54
+ isCloudGitSelfManagedHostIntegrationId,
55
+ isGitCloudHostIntegrationId,
56
+ isGitSelfManagedHostIntegrationId,
57
+ } from './utils/integration.utils.js';
58
+
59
+ export interface ConnectionStateChangeEvent {
60
+ key: string;
61
+ reason: 'connected' | 'disconnected';
62
+ }
63
+
64
+ /** @internal Event emitted when an integration connection state changes */
65
+ export interface IntegrationConnectionChangeEvent extends ConnectionStateChangeEvent {
66
+ integration: IntegrationBase;
67
+ }
68
+
69
+ export class IntegrationService implements Disposable {
70
+ get onDidChange(): Event<ConfiguredIntegrationsChangeEvent> {
71
+ return this.configuredIntegrationService.onDidChange;
72
+ }
73
+
74
+ private readonly _onDidChangeConnectionState = new Emitter<ConnectionStateChangeEvent>();
75
+ get onDidChangeConnectionState(): Event<ConnectionStateChangeEvent> {
76
+ return this._onDidChangeConnectionState.event;
77
+ }
78
+
79
+ private readonly _connectedCache = new Set<string>();
80
+ private readonly _disposable: Disposable;
81
+ private _integrations = new Map<IntegrationKey, Integration>();
82
+ private readonly _onDidChangeIntegrationConnection = new Emitter<IntegrationConnectionChangeEvent>();
83
+ private readonly _apiDisposables: Disposable[] = [];
84
+ private _apis: ApiClients | undefined;
85
+
86
+ /** The package-built, memoized per-provider API clients (was injected via the now-removed `ctx.apis`). */
87
+ get apis(): ApiClients {
88
+ return (this._apis ??= createApiClients(this.ctx, this._apiDisposables));
89
+ }
90
+
91
+ /** The shared `GitHubApi` instance — also consumed by the host's GitHub git provider (one instance). */
92
+ get github(): Promise<GitHubApi | undefined> {
93
+ return this.apis.github;
94
+ }
95
+
96
+ constructor(
97
+ private readonly authenticationService: IntegrationAuthenticationService,
98
+ private readonly configuredIntegrationService: ConfiguredIntegrationService,
99
+ private readonly ctx: IntegrationServiceContext,
100
+ ) {
101
+ this._disposable = fromDisposables(
102
+ ctx.config.onDidChange(e => {
103
+ if (e.remotes) {
104
+ this._ignoreSSLErrors.clear();
105
+ }
106
+ }),
107
+ ctx.account.onDidChangeSessions(this.onAuthenticationSessionsChanged, this),
108
+ ctx.account.onDidCheckIn(this.onUserCheckedIn, this),
109
+ ctx.account.onDidChange(this.onSubscriptionChanged, this),
110
+ this._onDidChangeIntegrationConnection.event(this.onIntegrationConnectionChanged, this),
111
+ );
112
+ }
113
+
114
+ dispose(): void {
115
+ this._integrations.forEach(i => i.dispose());
116
+ this._integrations.clear();
117
+ this._disposable?.dispose();
118
+ // The facade (`createIntegrationManager`) hands us sole ownership of these, so disposing the manager
119
+ // must tear them down too — otherwise cached auth providers (+ their host session listeners) and the
120
+ // configured-integrations emitter outlive the manager.
121
+ this.authenticationService.dispose();
122
+ this.configuredIntegrationService.dispose();
123
+ this._onDidChangeConnectionState.dispose();
124
+ this._onDidChangeIntegrationConnection.dispose();
125
+ this._apiDisposables.forEach(d => d.dispose());
126
+ }
127
+
128
+ @debug()
129
+ async connectCloudIntegrations(
130
+ connect?: { integrationIds: SupportedCloudIntegrationIds[]; skipIfConnected?: boolean; skipPreSync?: boolean },
131
+ source?: Source,
132
+ ): Promise<boolean> {
133
+ const scope = getScopedLogger();
134
+ const integrationIds = connect?.integrationIds;
135
+ this.ctx.hooks?.connection?.onStarted?.({ integrationIds: integrationIds }, source);
136
+
137
+ const connectedIntegrations = new Set<string>();
138
+ if (integrationIds?.length) {
139
+ if (connect?.skipIfConnected && !connect?.skipPreSync) {
140
+ await this.syncCloudIntegrations(true);
141
+ }
142
+
143
+ for (const integrationId of integrationIds) {
144
+ try {
145
+ const integration = await this.get(integrationId);
146
+ if (integration == null) continue;
147
+
148
+ if (integration.maybeConnected ?? (await integration.isConnected())) {
149
+ connectedIntegrations.add(integrationId);
150
+ }
151
+ } catch (ex) {
152
+ scope?.warn(
153
+ `Failed to get integration ${integrationId} by its ID. Consider it as not-connected and ignore. Error message: ${ex.message}`,
154
+ scope,
155
+ );
156
+ }
157
+ }
158
+
159
+ if (connect?.skipIfConnected && connectedIntegrations.size === integrationIds.length) {
160
+ return true;
161
+ }
162
+ }
163
+
164
+ // The host owns the GK-Dev connect round-trip end-to-end (URL/query, exchange-token-vs-redirect,
165
+ // open, await its OAuth callback, sign-in + redeem). The package only orchestrates state/sync/hooks.
166
+ if (!(await this.ctx.account.connect({ integrationIds: integrationIds, source: source }))) {
167
+ return false;
168
+ }
169
+
170
+ const connected = await this.syncCloudIntegrations(true);
171
+ this.ctx.hooks?.connection?.onCompleted?.(
172
+ {
173
+ integrationIds: integrationIds,
174
+ connectedIntegrationIds: connected != null ? [...connected.values()] : undefined,
175
+ },
176
+ source,
177
+ );
178
+
179
+ if (integrationIds != null) {
180
+ for (const integrationId of integrationIds) {
181
+ const integration = await this.get(integrationId);
182
+ if (integration == null) continue;
183
+
184
+ const connected = integration.maybeConnected ?? (await integration.isConnected());
185
+ if (connected && !connectedIntegrations.has(integrationId)) {
186
+ return true;
187
+ }
188
+ }
189
+
190
+ return false;
191
+ }
192
+
193
+ return true;
194
+ }
195
+
196
+ get(id: GitCloudHostIntegrationId): Promise<GitHostIntegration>;
197
+ get(id: IssuesCloudHostIntegrationId): Promise<IssuesIntegration>;
198
+ get(
199
+ id: GitCloudHostIntegrationId | CloudGitSelfManagedHostIntegrationIds,
200
+ domain?: string,
201
+ ): Promise<GitHostIntegration | undefined>;
202
+ get(id: GitSelfManagedHostIntegrationId, domain: string): Promise<GitHostIntegration | undefined>;
203
+ get<T extends IntegrationIds>(id: T, domain?: string): Promise<IntegrationById<T> | undefined>;
204
+ async get<T extends IntegrationIds>(id: T, domain?: string): Promise<IntegrationById<T> | undefined> {
205
+ let integration = this.getCached(id, domain);
206
+ if (integration == null) {
207
+ switch (id) {
208
+ case GitCloudHostIntegrationId.GitHub:
209
+ integration = new (
210
+ await import(/* webpackChunkName: "integrations" */ './providers/github.js')
211
+ ).GitHubIntegration(
212
+ this.ctx,
213
+ this.authenticationService,
214
+ this.getProvidersApi.bind(this),
215
+ this._onDidChangeIntegrationConnection,
216
+ ) as GitHostIntegration as IntegrationById<T>;
217
+ break;
218
+
219
+ case GitSelfManagedHostIntegrationId.CloudGitHubEnterprise:
220
+ if (domain == null) {
221
+ integration = this.findCachedById(id);
222
+ // return immediately in order to not to cache it after the "switch" block:
223
+ if (integration != null) return integration;
224
+
225
+ const configured = this.getConfigured(GitSelfManagedHostIntegrationId.CloudGitHubEnterprise);
226
+ if (configured.length) {
227
+ const { domain: configuredDomain } = configured[0];
228
+ if (configuredDomain == null) throw new Error(`Domain is required for '${id}' integration`);
229
+
230
+ integration = new (
231
+ await import(/* webpackChunkName: "integrations" */ './providers/github.js')
232
+ ).GitHubEnterpriseIntegration(
233
+ this.ctx,
234
+ this.authenticationService,
235
+ this.getProvidersApi.bind(this),
236
+ this._onDidChangeIntegrationConnection,
237
+ configuredDomain,
238
+ ) as GitHostIntegration as IntegrationById<T>;
239
+
240
+ // assign domain because it's part of caching key:
241
+ domain = configuredDomain;
242
+ break;
243
+ }
244
+
245
+ return undefined;
246
+ }
247
+
248
+ integration = new (
249
+ await import(/* webpackChunkName: "integrations" */ './providers/github.js')
250
+ ).GitHubEnterpriseIntegration(
251
+ this.ctx,
252
+ this.authenticationService,
253
+ this.getProvidersApi.bind(this),
254
+ this._onDidChangeIntegrationConnection,
255
+ domain,
256
+ ) as GitHostIntegration as IntegrationById<T>;
257
+ break;
258
+
259
+ case GitCloudHostIntegrationId.GitLab:
260
+ integration = new (
261
+ await import(/* webpackChunkName: "integrations" */ './providers/gitlab.js')
262
+ ).GitLabIntegration(
263
+ this.ctx,
264
+ this.authenticationService,
265
+ this.getProvidersApi.bind(this),
266
+ this._onDidChangeIntegrationConnection,
267
+ ) as GitHostIntegration as IntegrationById<T>;
268
+ break;
269
+
270
+ case GitSelfManagedHostIntegrationId.CloudGitLabSelfHosted:
271
+ if (domain == null) {
272
+ integration = this.findCachedById(id);
273
+ // return immediately in order to not to cache it after the "switch" block:
274
+ if (integration != null) return integration;
275
+
276
+ const configured = this.getConfigured(GitSelfManagedHostIntegrationId.CloudGitLabSelfHosted);
277
+ if (configured.length) {
278
+ const { domain: configuredDomain } = configured[0];
279
+ if (configuredDomain == null) throw new Error(`Domain is required for '${id}' integration`);
280
+
281
+ integration = new (
282
+ await import(/* webpackChunkName: "integrations" */ './providers/gitlab.js')
283
+ ).GitLabSelfHostedIntegration(
284
+ this.ctx,
285
+ this.authenticationService,
286
+ this.getProvidersApi.bind(this),
287
+ this._onDidChangeIntegrationConnection,
288
+ configuredDomain,
289
+ ) as GitHostIntegration as IntegrationById<T>;
290
+
291
+ // assign domain because it's part of caching key:
292
+ domain = configuredDomain;
293
+ break;
294
+ }
295
+
296
+ return undefined;
297
+ }
298
+
299
+ integration = new (
300
+ await import(/* webpackChunkName: "integrations" */ './providers/gitlab.js')
301
+ ).GitLabSelfHostedIntegration(
302
+ this.ctx,
303
+ this.authenticationService,
304
+ this.getProvidersApi.bind(this),
305
+ this._onDidChangeIntegrationConnection,
306
+ domain,
307
+ ) as GitHostIntegration as IntegrationById<T>;
308
+ break;
309
+
310
+ case GitCloudHostIntegrationId.Bitbucket:
311
+ integration = new (
312
+ await import(/* webpackChunkName: "integrations" */ './providers/bitbucket.js')
313
+ ).BitbucketIntegration(
314
+ this.ctx,
315
+ this.authenticationService,
316
+ this.getProvidersApi.bind(this),
317
+ this._onDidChangeIntegrationConnection,
318
+ ) as GitHostIntegration as IntegrationById<T>;
319
+ break;
320
+
321
+ case GitSelfManagedHostIntegrationId.BitbucketServer:
322
+ if (domain == null) {
323
+ integration = this.findCachedById(id);
324
+ // return immediately in order to not to cache it after the "switch" block:
325
+ if (integration != null) return integration;
326
+
327
+ const configured = this.getConfigured(GitSelfManagedHostIntegrationId.BitbucketServer);
328
+ if (configured.length) {
329
+ const { domain: configuredDomain } = configured[0];
330
+ if (configuredDomain == null) throw new Error(`Domain is required for '${id}' integration`);
331
+
332
+ integration = new (
333
+ await import(/* webpackChunkName: "integrations" */ './providers/bitbucket-server.js')
334
+ ).BitbucketServerIntegration(
335
+ this.ctx,
336
+ this.authenticationService,
337
+ this.getProvidersApi.bind(this),
338
+ this._onDidChangeIntegrationConnection,
339
+ configuredDomain,
340
+ ) as GitHostIntegration as IntegrationById<T>;
341
+
342
+ // assign domain because it's part of caching key:
343
+ domain = configuredDomain;
344
+ break;
345
+ }
346
+
347
+ return undefined;
348
+ }
349
+
350
+ integration = new (
351
+ await import(/* webpackChunkName: "integrations" */ './providers/bitbucket-server.js')
352
+ ).BitbucketServerIntegration(
353
+ this.ctx,
354
+ this.authenticationService,
355
+ this.getProvidersApi.bind(this),
356
+ this._onDidChangeIntegrationConnection,
357
+ domain,
358
+ ) as GitHostIntegration as IntegrationById<T>;
359
+ break;
360
+
361
+ case GitCloudHostIntegrationId.AzureDevOps:
362
+ integration = new (
363
+ await import(/* webpackChunkName: "integrations" */ './providers/azureDevOps.js')
364
+ ).AzureDevOpsIntegration(
365
+ this.ctx,
366
+ this.authenticationService,
367
+ this.getProvidersApi.bind(this),
368
+ this._onDidChangeIntegrationConnection,
369
+ ) as GitHostIntegration as IntegrationById<T>;
370
+ break;
371
+
372
+ case GitSelfManagedHostIntegrationId.AzureDevOpsServer:
373
+ if (domain == null) {
374
+ integration = this.findCachedById(id);
375
+ // return immediately in order to not to cache it after the "switch" block:
376
+ if (integration != null) return integration;
377
+
378
+ const configured = this.getConfigured(GitSelfManagedHostIntegrationId.AzureDevOpsServer);
379
+ if (configured.length) {
380
+ const { domain: configuredDomain } = configured[0];
381
+ if (configuredDomain == null) throw new Error(`Domain is required for '${id}' integration`);
382
+
383
+ integration = new (
384
+ await import(/* webpackChunkName: "integrations" */ './providers/azureDevOps.js')
385
+ ).AzureDevOpsServerIntegration(
386
+ this.ctx,
387
+ this.authenticationService,
388
+ this.getProvidersApi.bind(this),
389
+ this._onDidChangeIntegrationConnection,
390
+ configuredDomain,
391
+ ) as GitHostIntegration as IntegrationById<T>;
392
+
393
+ // assign domain because it's part of caching key:
394
+ domain = configuredDomain;
395
+ break;
396
+ }
397
+
398
+ return undefined;
399
+ }
400
+
401
+ integration = new (
402
+ await import(/* webpackChunkName: "integrations" */ './providers/azureDevOps.js')
403
+ ).AzureDevOpsServerIntegration(
404
+ this.ctx,
405
+ this.authenticationService,
406
+ this.getProvidersApi.bind(this),
407
+ this._onDidChangeIntegrationConnection,
408
+ domain,
409
+ ) as GitHostIntegration as IntegrationById<T>;
410
+ break;
411
+
412
+ case IssuesCloudHostIntegrationId.Jira:
413
+ integration = new (
414
+ await import(/* webpackChunkName: "integrations" */ './providers/jira.js')
415
+ ).JiraIntegration(
416
+ this.ctx,
417
+ this.authenticationService,
418
+ this.getProvidersApi.bind(this),
419
+ this._onDidChangeIntegrationConnection,
420
+ ) as IssuesIntegration as IntegrationById<T>;
421
+ break;
422
+
423
+ case IssuesCloudHostIntegrationId.Linear:
424
+ integration = new (
425
+ await import(/* webpackChunkName: "integrations" */ './providers/linear.js')
426
+ ).LinearIntegration(
427
+ this.ctx,
428
+ this.authenticationService,
429
+ this.getProvidersApi.bind(this),
430
+ this._onDidChangeIntegrationConnection,
431
+ ) as IssuesIntegration as IntegrationById<T>;
432
+ break;
433
+ default:
434
+ throw new Error(`Integration with '${id}' is not supported`);
435
+ }
436
+
437
+ this._integrations.set(this.getCacheKey(id, domain), integration);
438
+ }
439
+
440
+ return integration;
441
+ }
442
+
443
+ getConfigured(
444
+ id?: IntegrationIds,
445
+ options?: { cloud?: boolean; domain?: string },
446
+ ): ConfiguredIntegrationDescriptor[] {
447
+ return this.configuredIntegrationService.getConfigured(id, options);
448
+ }
449
+
450
+ /**
451
+ * Returns the connected integration for a `GitRemote`, if any.
452
+ * Internal counterpart to the host's `getRemoteIntegration(remote)` helper.
453
+ */
454
+ async getByRemote(remote: GitRemote): Promise<GitHostIntegration | undefined> {
455
+ if (remote?.provider == null) return undefined;
456
+
457
+ const integrationId = getIntegrationIdForRemote(remote.provider);
458
+ if (integrationId == null) return undefined;
459
+
460
+ const integration = await this.get(integrationId, remote.provider?.domain);
461
+ return integration?.type === 'git' ? integration : undefined;
462
+ }
463
+
464
+ @debug({
465
+ args: integrationIds => ({ integrationIds: integrationIds?.length ? integrationIds.join(',') : '<undefined>' }),
466
+ })
467
+ async getMyIssues(
468
+ integrationIds?: (GitCloudHostIntegrationId | IssuesCloudHostIntegrationId | GitSelfManagedHostIntegrationId)[],
469
+ options?: { openRepositoriesOnly?: boolean; cancellation?: AbortSignal },
470
+ ): Promise<IssueShape[] | undefined> {
471
+ const integrations: Map<Integration, ResourceDescriptor[] | undefined> = new Map();
472
+ const hostingIntegrationIds = integrationIds?.filter(
473
+ id => id in GitCloudHostIntegrationId || id in GitSelfManagedHostIntegrationId,
474
+ ) as GitCloudHostIntegrationId[];
475
+ const openRemotesByIntegrationId = new Map<IntegrationIds, ResourceDescriptor[]>();
476
+ let hasOpenAzureRepository = false;
477
+ for (const remote of await this.ctx.repositories.getOpenRemotes()) {
478
+ const remoteIntegration = await this.getByRemote(remote);
479
+ if (remoteIntegration == null) continue;
480
+
481
+ if (remoteIntegration.id === GitCloudHostIntegrationId.AzureDevOps) {
482
+ hasOpenAzureRepository = true;
483
+ }
484
+ for (const integrationId of hostingIntegrationIds?.length
485
+ ? hostingIntegrationIds
486
+ : [...Object.values(GitCloudHostIntegrationId), ...Object.values(GitSelfManagedHostIntegrationId)]) {
487
+ if (
488
+ remoteIntegration.id === integrationId &&
489
+ remote.provider?.owner != null &&
490
+ remote.provider?.repoName != null
491
+ ) {
492
+ const descriptor = {
493
+ key: `${remote.provider.owner}/${remote.provider.repoName}`,
494
+ owner: remote.provider.owner,
495
+ name: remote.provider.repoName,
496
+ };
497
+ if (openRemotesByIntegrationId.has(integrationId)) {
498
+ openRemotesByIntegrationId.get(integrationId)?.push(descriptor);
499
+ } else {
500
+ openRemotesByIntegrationId.set(integrationId, [descriptor]);
501
+ }
502
+ }
503
+ }
504
+ }
505
+ for (const integrationId of integrationIds?.length
506
+ ? integrationIds
507
+ : [
508
+ ...Object.values(GitCloudHostIntegrationId),
509
+ ...Object.values(IssuesCloudHostIntegrationId),
510
+ ...Object.values(GitSelfManagedHostIntegrationId),
511
+ ]) {
512
+ const integration = await this.get(integrationId);
513
+ const isInvalidIntegration =
514
+ (options?.openRepositoriesOnly &&
515
+ integrationId !== GitCloudHostIntegrationId.AzureDevOps &&
516
+ (isGitCloudHostIntegrationId(integrationId) || isGitSelfManagedHostIntegrationId(integrationId)) &&
517
+ !openRemotesByIntegrationId.has(integrationId)) ||
518
+ (integrationId === GitCloudHostIntegrationId.AzureDevOps && !hasOpenAzureRepository);
519
+ if (integration == null || isInvalidIntegration) {
520
+ continue;
521
+ }
522
+
523
+ integrations.set(
524
+ integration,
525
+ options?.openRepositoriesOnly && !isInvalidIntegration
526
+ ? openRemotesByIntegrationId.get(integrationId)
527
+ : undefined,
528
+ );
529
+ }
530
+ if (integrations.size === 0) return undefined;
531
+
532
+ return this.getMyIssuesCore(integrations, options?.cancellation);
533
+ }
534
+
535
+ private async getMyIssuesCore(
536
+ integrations: Map<Integration, ResourceDescriptor[] | undefined>,
537
+ cancellation?: AbortSignal,
538
+ ): Promise<IssueShape[] | undefined> {
539
+ const promises: Promise<IssueShape[] | undefined>[] = [];
540
+ for (const [integration, repos] of integrations) {
541
+ if (integration == null) continue;
542
+
543
+ promises.push(integration.searchMyIssues(repos, cancellation));
544
+ }
545
+
546
+ const results = await Promise.allSettled(promises);
547
+ return [...flatten(filterMap(results, r => (r.status === 'fulfilled' ? r.value : undefined)))];
548
+ }
549
+
550
+ async getMyIssuesForRemotes(remote: GitRemote): Promise<IssueShape[] | undefined>;
551
+ async getMyIssuesForRemotes(remotes: GitRemote[]): Promise<IssueShape[] | undefined>;
552
+ @trace({
553
+ args: (remoteOrRemotes: GitRemote | GitRemote[]) => ({
554
+ remoteOrRemotes: Array.isArray(remoteOrRemotes) ? remoteOrRemotes.map(rp => rp.name) : remoteOrRemotes.name,
555
+ }),
556
+ })
557
+ async getMyIssuesForRemotes(remoteOrRemotes: GitRemote | GitRemote[]): Promise<IssueShape[] | undefined> {
558
+ if (!Array.isArray(remoteOrRemotes)) {
559
+ remoteOrRemotes = [remoteOrRemotes];
560
+ }
561
+
562
+ if (!remoteOrRemotes.length) return undefined;
563
+ if (remoteOrRemotes.length === 1) {
564
+ const [remote] = remoteOrRemotes;
565
+ if (remote?.provider == null) return undefined;
566
+
567
+ const integration = await this.getByRemote(remote);
568
+ return integration?.searchMyIssues(remote.provider.repoDesc);
569
+ }
570
+
571
+ const integrations = new Map<GitHostIntegration, ResourceDescriptor[]>();
572
+
573
+ for (const remote of remoteOrRemotes) {
574
+ if (remote?.provider == null) continue;
575
+
576
+ const integration = await this.getByRemote(remote);
577
+ if (integration == null) continue;
578
+
579
+ let repos = integrations.get(integration);
580
+ if (repos == null) {
581
+ repos = [];
582
+ integrations.set(integration, repos);
583
+ }
584
+ repos.push(remote.provider.repoDesc);
585
+ }
586
+
587
+ return this.getMyIssuesCore(integrations);
588
+ }
589
+
590
+ @debug({
591
+ args: integrationIds => ({ integrationIds: integrationIds?.length ? integrationIds.join(',') : '<undefined>' }),
592
+ })
593
+ async getMyCurrentAccounts(
594
+ integrationIds: (GitCloudHostIntegrationId | CloudGitSelfManagedHostIntegrationIds)[],
595
+ ): Promise<Map<GitCloudHostIntegrationId | CloudGitSelfManagedHostIntegrationIds, Account>> {
596
+ const accounts = new Map<GitCloudHostIntegrationId | CloudGitSelfManagedHostIntegrationIds, Account>();
597
+ await Promise.allSettled(
598
+ integrationIds.map(async integrationId => {
599
+ const integration = await this.get(integrationId);
600
+ if (integration == null) return;
601
+
602
+ const account = await integration.getCurrentAccount();
603
+ if (account) {
604
+ accounts.set(integrationId, account);
605
+ }
606
+ }),
607
+ );
608
+ return accounts;
609
+ }
610
+
611
+ @debug({
612
+ args: integrationIds => ({ integrationIds: integrationIds?.length ? integrationIds.join(',') : '<undefined>' }),
613
+ })
614
+ async getMyPullRequests(
615
+ integrationIds?: (GitCloudHostIntegrationId | CloudGitSelfManagedHostIntegrationIds)[],
616
+ cancellation?: AbortSignal,
617
+ silent?: boolean,
618
+ ): Promise<IntegrationResult<PullRequest[] | undefined>> {
619
+ const integrations: Map<GitHostIntegration, ResourceDescriptor[] | undefined> = new Map();
620
+ for (const integrationId of integrationIds?.length
621
+ ? integrationIds
622
+ : Object.values(GitCloudHostIntegrationId)) {
623
+ let integration;
624
+ try {
625
+ integration = await this.get(integrationId);
626
+ } catch {}
627
+ if (integration == null) continue;
628
+
629
+ integrations.set(integration, undefined);
630
+ }
631
+ if (integrations.size === 0) return undefined;
632
+
633
+ return this.getMyPullRequestsCore(integrations, cancellation, silent);
634
+ }
635
+
636
+ private async getMyPullRequestsCore(
637
+ integrations: Map<GitHostIntegration, ResourceDescriptor[] | undefined>,
638
+ cancellation?: AbortSignal,
639
+ silent?: boolean,
640
+ ): Promise<IntegrationResult<PullRequest[] | undefined>> {
641
+ const start = performance.now();
642
+
643
+ const promises: Promise<IntegrationResult<PullRequest[] | undefined>>[] = [];
644
+ for (const [integration, repos] of integrations) {
645
+ if (integration == null) continue;
646
+
647
+ promises.push(integration.searchMyPullRequests(repos, cancellation, silent));
648
+ }
649
+
650
+ const results = await Promise.allSettled(promises);
651
+ const successfulResults = [
652
+ ...flatten(
653
+ filterMap(results, r =>
654
+ r.status === 'fulfilled' && r.value?.value != null ? r.value.value : undefined,
655
+ ),
656
+ ),
657
+ ];
658
+ const errors = [
659
+ ...filterMap(results, r =>
660
+ r.status === 'fulfilled' && r.value?.error != null ? r.value.error : undefined,
661
+ ),
662
+ ];
663
+
664
+ const error =
665
+ errors.length === 0
666
+ ? undefined
667
+ : errors.length === 1
668
+ ? errors[0]
669
+ : new AggregateError(errors, 'Failed to get some pull requests');
670
+
671
+ return {
672
+ value: successfulResults,
673
+ error: error,
674
+ duration: performance.now() - start,
675
+ };
676
+ }
677
+
678
+ async getMyPullRequestsForRemotes(remote: GitRemote): Promise<IntegrationResult<PullRequest[] | undefined>>;
679
+ async getMyPullRequestsForRemotes(remotes: GitRemote[]): Promise<IntegrationResult<PullRequest[] | undefined>>;
680
+ @trace({
681
+ args: (remoteOrRemotes: GitRemote | GitRemote[]) => ({
682
+ remoteOrRemotes: Array.isArray(remoteOrRemotes) ? remoteOrRemotes.map(rp => rp.name) : remoteOrRemotes.name,
683
+ }),
684
+ })
685
+ async getMyPullRequestsForRemotes(
686
+ remoteOrRemotes: GitRemote | GitRemote[],
687
+ ): Promise<IntegrationResult<PullRequest[] | undefined>> {
688
+ if (!Array.isArray(remoteOrRemotes)) {
689
+ remoteOrRemotes = [remoteOrRemotes];
690
+ }
691
+
692
+ if (!remoteOrRemotes.length) return undefined;
693
+ if (remoteOrRemotes.length === 1) {
694
+ const [remote] = remoteOrRemotes;
695
+ if (remote?.provider == null) return undefined;
696
+
697
+ const integration = await this.getByRemote(remote);
698
+ return integration?.searchMyPullRequests(remote.provider.repoDesc);
699
+ }
700
+
701
+ const integrations = new Map<GitHostIntegration, ResourceDescriptor[]>();
702
+
703
+ for (const remote of remoteOrRemotes) {
704
+ if (remote?.provider == null) continue;
705
+
706
+ const integration = await this.getByRemote(remote);
707
+ if (integration == null) continue;
708
+
709
+ let repos = integrations.get(integration);
710
+ if (repos == null) {
711
+ repos = [];
712
+ integrations.set(integration, repos);
713
+ }
714
+ repos.push(remote.provider.repoDesc);
715
+ }
716
+
717
+ return this.getMyPullRequestsCore(integrations);
718
+ }
719
+
720
+ private _ignoreSSLErrors = new Map<string, boolean | 'force'>();
721
+ ignoreSSLErrors(integration: GitHostIntegration | { id: IntegrationIds; domain?: string }): boolean | 'force' {
722
+ if (this.ctx.http.isWeb) return false;
723
+
724
+ let ignoreSSLErrors = this._ignoreSSLErrors.get(integration.id);
725
+ if (ignoreSSLErrors === undefined) {
726
+ const cfg = this.ctx.config
727
+ .getRemoteConfigs()
728
+ .find(remote => remote.type.toLowerCase() === integration.id && remote.domain === integration.domain);
729
+ ignoreSSLErrors = cfg?.ignoreSSLErrors ?? false;
730
+ this._ignoreSSLErrors.set(integration.id, ignoreSSLErrors);
731
+ }
732
+
733
+ return ignoreSSLErrors;
734
+ }
735
+
736
+ @debug()
737
+ async manageCloudIntegrations(source: Source | undefined): Promise<void> {
738
+ this.ctx.hooks?.connection?.onManaged?.(source);
739
+
740
+ // The host owns the GK-Dev manage interaction (sign-in if needed, open settings, wait for the user
741
+ // to return); it resolves `true` once they're back, then we re-sync + report.
742
+ if (!(await this.ctx.account.openManagement(source))) return;
743
+
744
+ const connected = await this.syncCloudIntegrations(true);
745
+ this.ctx.hooks?.connection?.onCompleted?.(
746
+ {
747
+ integrationIds: undefined,
748
+ connectedIntegrationIds: connected != null ? [...connected.values()] : undefined,
749
+ },
750
+ source,
751
+ );
752
+ }
753
+
754
+ @debug()
755
+ async reset(): Promise<void> {
756
+ for (const integration of this._integrations.values()) {
757
+ await integration.reset();
758
+ }
759
+
760
+ await this.authenticationService.reset();
761
+ await this.ctx.storage.deleteWithPrefix('provider:authentication:skip');
762
+ queueMicrotask(() => void this.syncCloudIntegrations(true));
763
+ }
764
+
765
+ supports(remoteProviderId: RemoteProviderId): boolean {
766
+ return convertRemoteProviderIdToIntegrationId(remoteProviderId) != null;
767
+ }
768
+
769
+ private onAuthenticationSessionsChanged(e: AuthenticationSessionsChangeEvent) {
770
+ for (const integration of this._integrations.values()) {
771
+ if (e.provider.id === integration.authProvider.id) {
772
+ integration.refresh();
773
+ }
774
+ }
775
+ }
776
+
777
+ private onIntegrationConnectionChanged(e: {
778
+ integration: IntegrationBase;
779
+ key: string;
780
+ reason: 'connected' | 'disconnected';
781
+ }): void {
782
+ const { integration, key, reason } = e;
783
+
784
+ if (reason === 'connected') {
785
+ // Only fire events if the key is being connected for the first time
786
+ if (this._connectedCache.has(key)) return;
787
+
788
+ this._connectedCache.add(key);
789
+ } else {
790
+ // Probably shouldn't bother to fire the event if we don't already think we are connected, but better to be safe
791
+ // if (!_connectedCache.has(key)) return;
792
+
793
+ this._connectedCache.delete(key);
794
+ }
795
+
796
+ const connected = reason === 'connected';
797
+ if (integration.type === 'git') {
798
+ this.ctx.hooks?.connection?.onStateChanged?.({
799
+ id: integration.id,
800
+ key: key,
801
+ connected: connected,
802
+ kind: isSupportedCloudIntegrationId(integration.id) ? 'hosting' : 'remote',
803
+ });
804
+ } else {
805
+ this.ctx.hooks?.connection?.onStateChanged?.({
806
+ id: integration.id,
807
+ key: key,
808
+ connected: connected,
809
+ kind: 'issue',
810
+ });
811
+ }
812
+
813
+ setTimeout(() => this._onDidChangeConnectionState.fire({ key: key, reason: reason }), 250);
814
+ }
815
+
816
+ private async onSubscriptionChanged() {
817
+ // When the account goes away, disconnect all connected cloud integrations. Mirrors the host's
818
+ // historical `account == null` check (the host forwards `onDidChange` without a payload).
819
+ if ((await this.ctx.account.getAccount()) == null) {
820
+ void this.syncCloudIntegrations(false);
821
+ }
822
+ }
823
+
824
+ private onUserCheckedIn(e?: { force?: boolean }) {
825
+ void this.syncCloudIntegrations(Boolean(e?.force));
826
+ }
827
+
828
+ private _providersApi: Promise<ProvidersApi> | undefined;
829
+ private async getProvidersApi() {
830
+ if (this._providersApi == null) {
831
+ const authenticationService = this.authenticationService;
832
+ async function load() {
833
+ return new (
834
+ await import(/* webpackChunkName: "integrations" */ './providers/providersApi.js')
835
+ ).ProvidersApi(authenticationService);
836
+ }
837
+
838
+ this._providersApi = load();
839
+ }
840
+
841
+ return this._providersApi;
842
+ }
843
+
844
+ private getCached<T extends IntegrationIds>(id: T, domain?: string): IntegrationById<T> | undefined {
845
+ return this._integrations.get(this.getCacheKey(id, domain)) as IntegrationById<T> | undefined;
846
+ }
847
+
848
+ private getCacheKey(
849
+ id: GitCloudHostIntegrationId | IssuesCloudHostIntegrationId | GitSelfManagedHostIntegrationId,
850
+ domain?: string,
851
+ ): IntegrationKey {
852
+ return isGitSelfManagedHostIntegrationId(id) ? (`${id}:${domain}` as const) : id;
853
+ }
854
+
855
+ private async *getSupportedCloudIntegrations(domainsById: Map<IntegrationIds, string>): AsyncIterable<Integration> {
856
+ for (const id of getSupportedCloudIntegrationIds()) {
857
+ if (isCloudGitSelfManagedHostIntegrationId(id) && !domainsById.has(id)) {
858
+ // Try getting whatever we have now because we will need to disconnect
859
+ const integration = await this.get(id, undefined);
860
+ if (integration != null) {
861
+ yield integration;
862
+ }
863
+ } else {
864
+ const integration = await this.get(id, domainsById.get(id));
865
+ if (integration != null) {
866
+ yield integration;
867
+ }
868
+ }
869
+ }
870
+ }
871
+
872
+ private findCachedById<T extends IntegrationIds>(id: T): IntegrationById<T> | undefined {
873
+ const key = this.getCacheKey(id, '');
874
+ for (const [k, integration] of this._integrations) {
875
+ if (k.startsWith(key)) {
876
+ return integration as IntegrationById<T>;
877
+ }
878
+ }
879
+ return undefined;
880
+ }
881
+
882
+ @gate()
883
+ @trace()
884
+ private async syncCloudIntegrations(forceConnect: boolean) {
885
+ const scope = getScopedLogger();
886
+ const connectedIntegrations = new Set<IntegrationIds>();
887
+ const domainsById = new Map<IntegrationIds, string>();
888
+
889
+ const loggedIn = (await this.ctx.account.getAccount()) != null;
890
+ if (loggedIn) {
891
+ const connections = await this.authenticationService.cloudIntegrations.getConnections();
892
+ if (connections == null) return;
893
+
894
+ connections.map(p => {
895
+ const integrationId = toIntegrationId[p.provider];
896
+ // GKDev includes some integrations like "google" that we don't support
897
+ if (integrationId == null) return;
898
+
899
+ connectedIntegrations.add(toIntegrationId[p.provider]);
900
+ if (p.domain?.length > 0) {
901
+ try {
902
+ const host = new URL(p.domain).host;
903
+ domainsById.set(integrationId, host);
904
+ } catch {
905
+ scope?.warn(`Invalid domain for ${integrationId} integration: ${p.domain}. Ignoring.`);
906
+ }
907
+ }
908
+ });
909
+ }
910
+
911
+ for await (const integration of this.getSupportedCloudIntegrations(domainsById)) {
912
+ await integration.syncCloudConnection(
913
+ connectedIntegrations.has(integration.id) ? 'connected' : 'disconnected',
914
+ forceConnect,
915
+ );
916
+ }
917
+
918
+ this.ctx.hooks?.connection?.onConnectedChanged?.({
919
+ integrationIds: [...connectedIntegrations.values()],
920
+ });
921
+
922
+ return connectedIntegrations;
923
+ }
924
+ }