@gitkraken/core-gitlens 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/CHANGELOG.md +28 -2
  2. package/dist/git/remotes/azure-devops.js +3 -3
  3. package/dist/git/remotes/azure-devops.js.map +1 -1
  4. package/dist/plus/integrations/authentication/configuredIntegrationService.js +9 -2
  5. package/dist/plus/integrations/authentication/configuredIntegrationService.js.map +1 -1
  6. package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js +1 -1
  7. package/dist/plus/integrations/collectionMetadata.d.ts +48 -3
  8. package/dist/plus/integrations/collectionMetadata.js +138 -8
  9. package/dist/plus/integrations/collectionMetadata.js.map +1 -1
  10. package/dist/plus/integrations/errors.d.ts +3 -1
  11. package/dist/plus/integrations/errors.js +7 -3
  12. package/dist/plus/integrations/errors.js.map +1 -1
  13. package/dist/plus/integrations/index.d.ts +3 -3
  14. package/dist/plus/integrations/index.js.map +1 -1
  15. package/dist/plus/integrations/integrationService.d.ts +35 -1
  16. package/dist/plus/integrations/integrationService.js +90 -43
  17. package/dist/plus/integrations/integrationService.js.map +1 -1
  18. package/dist/plus/integrations/lite.js +5 -4
  19. package/dist/plus/integrations/lite.js.map +1 -1
  20. package/dist/plus/integrations/manager.d.ts +68 -23
  21. package/dist/plus/integrations/models/gitHostIntegration.d.ts +31 -15
  22. package/dist/plus/integrations/models/gitHostIntegration.js +71 -22
  23. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
  24. package/dist/plus/integrations/models/integration.d.ts +70 -12
  25. package/dist/plus/integrations/models/integration.js +56 -1
  26. package/dist/plus/integrations/models/integration.js.map +1 -1
  27. package/dist/plus/integrations/models/issueReads.d.ts +17 -0
  28. package/dist/plus/integrations/models/issuesIntegration.js +2 -0
  29. package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
  30. package/dist/plus/integrations/models/pullRequestReads.d.ts +26 -0
  31. package/dist/plus/integrations/providers/azure/azure.d.ts +66 -1
  32. package/dist/plus/integrations/providers/azure/azure.js +192 -12
  33. package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
  34. package/dist/plus/integrations/providers/azure/search.d.ts +144 -0
  35. package/dist/plus/integrations/providers/azure/search.js +250 -0
  36. package/dist/plus/integrations/providers/azure/search.js.map +1 -0
  37. package/dist/plus/integrations/providers/azureDevOps.d.ts +73 -36
  38. package/dist/plus/integrations/providers/azureDevOps.js +191 -122
  39. package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
  40. package/dist/plus/integrations/providers/azureDevOpsServer.d.ts +176 -0
  41. package/dist/plus/integrations/providers/azureDevOpsServer.js +560 -0
  42. package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -0
  43. package/dist/plus/integrations/providers/bitbucket-server/discovery.d.ts +12 -0
  44. package/dist/plus/integrations/providers/bitbucket-server/discovery.js +128 -0
  45. package/dist/plus/integrations/providers/bitbucket-server/discovery.js.map +1 -0
  46. package/dist/plus/integrations/providers/bitbucket-server/models.d.ts +10 -1
  47. package/dist/plus/integrations/providers/bitbucket-server/models.js +3 -2
  48. package/dist/plus/integrations/providers/bitbucket-server/models.js.map +1 -1
  49. package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.d.ts +58 -0
  50. package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js +485 -0
  51. package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js.map +1 -0
  52. package/dist/plus/integrations/providers/bitbucket-server.d.ts +61 -3
  53. package/dist/plus/integrations/providers/bitbucket-server.js +114 -20
  54. package/dist/plus/integrations/providers/bitbucket-server.js.map +1 -1
  55. package/dist/plus/integrations/providers/bitbucket.d.ts +9 -1
  56. package/dist/plus/integrations/providers/bitbucket.js +13 -2
  57. package/dist/plus/integrations/providers/bitbucket.js.map +1 -1
  58. package/dist/plus/integrations/providers/github.d.ts +6 -6
  59. package/dist/plus/integrations/providers/github.js +35 -34
  60. package/dist/plus/integrations/providers/github.js.map +1 -1
  61. package/dist/plus/integrations/providers/gitlab.d.ts +6 -4
  62. package/dist/plus/integrations/providers/gitlab.js +32 -28
  63. package/dist/plus/integrations/providers/gitlab.js.map +1 -1
  64. package/dist/plus/integrations/providers/issueSorts.d.ts +1 -0
  65. package/dist/plus/integrations/providers/jira-server.d.ts +9 -0
  66. package/dist/plus/integrations/providers/jira-server.js +15 -7
  67. package/dist/plus/integrations/providers/jira-server.js.map +1 -1
  68. package/dist/plus/integrations/providers/jira.d.ts +9 -0
  69. package/dist/plus/integrations/providers/jira.js +14 -2
  70. package/dist/plus/integrations/providers/jira.js.map +1 -1
  71. package/dist/plus/integrations/providers/linear.js +2 -2
  72. package/dist/plus/integrations/providers/models.d.ts +10 -1
  73. package/dist/plus/integrations/providers/models.js +86 -0
  74. package/dist/plus/integrations/providers/models.js.map +1 -1
  75. package/dist/plus/integrations/providers/providersApi.d.ts +10 -0
  76. package/dist/plus/integrations/providers/providersApi.js +18 -3
  77. package/dist/plus/integrations/providers/providersApi.js.map +1 -1
  78. package/dist/plus/integrations/providers/utils.d.ts +12 -1
  79. package/dist/plus/integrations/providers/utils.js +36 -6
  80. package/dist/plus/integrations/providers/utils.js.map +1 -1
  81. package/dist/plus/integrations/reads/broaden.js +6 -6
  82. package/dist/plus/integrations/reads/broaden.js.map +1 -1
  83. package/dist/plus/integrations/reads/context.d.ts +1 -1
  84. package/dist/plus/integrations/reads/counts.d.ts +18 -4
  85. package/dist/plus/integrations/reads/counts.js +66 -15
  86. package/dist/plus/integrations/reads/counts.js.map +1 -1
  87. package/dist/plus/integrations/reads/cursors.js +1 -1
  88. package/dist/plus/integrations/reads/drains.d.ts +2 -1
  89. package/dist/plus/integrations/reads/drains.js +1 -1
  90. package/dist/plus/integrations/reads/filters.d.ts +1 -1
  91. package/dist/plus/integrations/reads/filters.js +17 -12
  92. package/dist/plus/integrations/reads/filters.js.map +1 -1
  93. package/dist/plus/integrations/reads/hierarchy.js +2 -2
  94. package/dist/plus/integrations/reads/hierarchy.js.map +1 -1
  95. package/dist/plus/integrations/reads/hierarchy.utils.js +1 -1
  96. package/dist/plus/integrations/reads/issueBatch.js +1 -1
  97. package/dist/plus/integrations/reads/issueTracker.js +2 -2
  98. package/dist/plus/integrations/reads/issues.js +2 -2
  99. package/dist/plus/integrations/reads/paging.js +1 -1
  100. package/dist/plus/integrations/reads/resolveRepository.d.ts +12 -11
  101. package/dist/plus/integrations/reads/resolveRepository.js +110 -19
  102. package/dist/plus/integrations/reads/resolveRepository.js.map +1 -1
  103. package/dist/plus/integrations/reads/searchIssues.js +4 -2
  104. package/dist/plus/integrations/reads/searchIssues.js.map +1 -1
  105. package/dist/plus/integrations/reads/searchPullRequests.js +9 -3
  106. package/dist/plus/integrations/reads/searchPullRequests.js.map +1 -1
  107. package/dist/plus/integrations/reads/sweeps.js +1 -1
  108. package/dist/plus/integrations/reads/trackerIssue.d.ts +9 -5
  109. package/dist/plus/integrations/reads/trackerIssue.js +15 -9
  110. package/dist/plus/integrations/reads/trackerIssue.js.map +1 -1
  111. package/dist/plus/integrations/reads/warnings.d.ts +6 -2
  112. package/dist/plus/integrations/reads/warnings.js +15 -19
  113. package/dist/plus/integrations/reads/warnings.js.map +1 -1
  114. package/dist/plus/integrations/results.d.ts +84 -6
  115. package/dist/plus/integrations/results.js +9 -2
  116. package/dist/plus/integrations/results.js.map +1 -1
  117. package/dist/plus/integrations/utils/domain.utils.d.ts +19 -0
  118. package/dist/plus/integrations/utils/domain.utils.js +36 -1
  119. package/dist/plus/integrations/utils/domain.utils.js.map +1 -1
  120. package/dist/plus/integrations/utils/integration.utils.d.ts +1 -0
  121. package/dist/plus/integrations/utils/integration.utils.js +12 -1
  122. package/dist/plus/integrations/utils/integration.utils.js.map +1 -1
  123. package/docs/integrations.md +263 -74
  124. package/docs/kepler-read-api-parity.md +19 -6
  125. package/package.json +1 -1
@@ -1,12 +1,13 @@
1
1
  import { CancellationError } from "../../../utils/cancellation.js";
2
2
  import { mapSettledBounded } from "../../../utils/promise.js";
3
3
  import "../../../git/models/pullRequest.js";
4
- import { toCollectionScopeFailure } from "../collectionMetadata.js";
5
4
  import "../constants.js";
5
+ import { toCollectionScopeFailure } from "../collectionMetadata.js";
6
6
  import { toTokenWithInfo } from "../authentication/models.js";
7
7
  import "../providerFilters.js";
8
- import { fromProviderIssue, fromProviderPullRequest, getProviderPullRequestIdentity, providerPullRequestMatchesSearch, providersMetadata, toProviderPullRequestStates } from "./models.js";
9
8
  import { collectProviderPagedResult, flatSettledResultsOrThrow, mergeCollectionMetadata } from "./utils/providerPaging.js";
9
+ import { decodePathSegment } from "../utils/domain.utils.js";
10
+ import { fromProviderIssue, fromProviderPullRequest, getProviderPullRequestIdentity, providerPullRequestMatchesSearch, providersMetadata, toProviderPullRequestStates } from "./models.js";
10
11
  import { GitHostIntegration } from "../models/gitHostIntegration.js";
11
12
  //#region ../plus/integrations/src/providers/azureDevOps.ts
12
13
  function getAzureRepositoryIdentity(repo) {
@@ -18,10 +19,33 @@ function getAzureRepositoryIdentity(repo) {
18
19
  };
19
20
  }
20
21
  function getAzureRepositoryApiBaseUrl(baseUrl, repo) {
21
- if (repo.virtualDirectory == null) return baseUrl;
22
- const segments = repo.virtualDirectory.split("/");
23
- if (segments.some((segment) => !segment || segment === "." || segment === "..")) throw new Error(`Invalid Azure virtual directory '${repo.virtualDirectory}'.`);
24
- return `${baseUrl.replace(/\/+$/, "")}/${segments.map(encodeURIComponent).join("/")}`;
22
+ const directory = repo.virtualDirectory?.split("/");
23
+ if (directory?.some((segment) => !segment || segment === "." || segment === "..")) throw new Error(`Invalid Azure virtual directory '${repo.virtualDirectory}'.`);
24
+ const url = new URL(baseUrl);
25
+ const toUrl = (segments) => `${url.protocol}//${url.host}${segments.map((s) => `/${s}`).join("")}`;
26
+ const configured = url.pathname.split("/").filter(Boolean);
27
+ if (!configured.length) return toUrl(directory?.map(encodeURIComponent) ?? []);
28
+ const namesCollection = sameAzurePathSegment(configured.at(-1), repo.owner);
29
+ const installation = namesCollection ? configured.slice(0, -1) : configured;
30
+ if (directory == null) return toUrl(installation);
31
+ for (const candidate of namesCollection ? [configured, installation] : [configured]) if (candidate.length === directory.length && candidate.every((s, i) => sameAzurePathSegment(s, directory[i]))) return toUrl(candidate);
32
+ throw new Error("Azure repository virtual directory does not match the configured installation");
33
+ }
34
+ function sameAzurePathSegment(encoded, value) {
35
+ return decodePathSegment(encoded).toLowerCase() === value.toLowerCase();
36
+ }
37
+ /**
38
+ * Whether two collection or project names name the same scope. Azure DevOps treats them case-insensitively, so a
39
+ * name typed by hand must still select the collection or project discovery reported.
40
+ */
41
+ function sameAzureName(a, b) {
42
+ return a.toLowerCase() === b.toLowerCase();
43
+ }
44
+ /** The distinct names in `names`, compared as Azure compares them, keeping the first spelling of each. */
45
+ function uniqueAzureNames(names) {
46
+ const unique = [];
47
+ for (const name of names) if (!unique.some((n) => sameAzureName(n, name))) unique.push(name);
48
+ return unique;
25
49
  }
26
50
  /**
27
51
  * Matches an org/project descriptor against a caller-supplied name, mirroring the facade's own
@@ -39,15 +63,51 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
39
63
  options: { isPAT: usePat }
40
64
  };
41
65
  }
66
+ /**
67
+ * The options for a request addressed below one collection (an Azure DevOps Services organization).
68
+ *
69
+ * The same as {@link getApiOptions} unless a self-managed address already names the collection, which requests
70
+ * append themselves: see the server override, which keeps it from being applied twice.
71
+ */
72
+ getCollectionApiOptions(session, _collection) {
73
+ return this.getApiOptions(session).options;
74
+ }
75
+ /** The base a request or link below `collection` appends the collection to; see {@link getCollectionApiOptions}. */
76
+ collectionApiBaseUrl(session, _collection) {
77
+ return this.apiBaseUrlFor(session);
78
+ }
79
+ /** Reads the organizations (Azure DevOps Server: the collections) the account can see. */
80
+ async requestResourcesForUser(session, userId) {
81
+ const api = await this.getProvidersApi();
82
+ const { tokenWithInfo, options } = this.getApiOptions(session);
83
+ return api.getAzureResourcesForUser(tokenWithInfo, userId, options);
84
+ }
85
+ /**
86
+ * The identity id a pull-request creator/reviewer filter must carry for `collection`.
87
+ *
88
+ * On Azure DevOps Services that is the account id, one per user. The server override resolves it per collection,
89
+ * since Azure DevOps Server gives one person a different id in each collection than at the server level.
90
+ */
91
+ async getFilterUserId(session, _collection) {
92
+ return (await this.getProviderCurrentAccount(session))?.id;
93
+ }
94
+ /**
95
+ * What the discovery caches (account, organizations, projects, and their stored copies) are keyed by. Azure DevOps
96
+ * Services has a single address, so the credential alone identifies what it discovers; the server override adds
97
+ * the installation address.
98
+ */
99
+ discoveryKey(session) {
100
+ return session.accessToken;
101
+ }
42
102
  _accounts;
43
103
  async getProviderCurrentAccount(session) {
44
- const { accessToken } = session;
104
+ const key = this.discoveryKey(session);
45
105
  this._accounts ??= /* @__PURE__ */ new Map();
46
- if (this._accounts.get(accessToken) == null) {
106
+ if (this._accounts.get(key) == null) {
47
107
  const user = await this._requestForCurrentUser(session);
48
- this._accounts.set(accessToken, user);
108
+ this._accounts.set(key, user);
49
109
  }
50
- return this._accounts.get(accessToken);
110
+ return this._accounts.get(key);
51
111
  }
52
112
  async _requestForCurrentUser(session) {
53
113
  const api = await this.getProvidersApi();
@@ -62,22 +122,56 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
62
122
  username: user.username ?? void 0
63
123
  } : void 0;
64
124
  }
125
+ /**
126
+ * The profile request the account cache would otherwise answer; see `IntegrationBase.validateCredential`. No
127
+ * account proves nothing, since Azure DevOps Server's request answers every failure but a refusal that way.
128
+ */
129
+ async validateCredential(session) {
130
+ if (await this._requestForCurrentUser(session) == null) throw new Error("Azure DevOps did not confirm the credential");
131
+ }
132
+ /**
133
+ * Names a confirmed credential's refusal from the answers Azure DevOps gives, captured live (#5890):
134
+ * - `VS403463` in the explanation is a Conditional Access policy;
135
+ * - a typed `UnauthorizedRequestException` body is an organization or project the account has no access to;
136
+ * - a `401` to an OAuth token that explains nothing (sent as Basic), or only `TF400813` (sent as Bearer), is an
137
+ * organization whose "Third-party application access via OAuth" policy is off. It is the same answer an
138
+ * expired token gets, which is why this is only asked once the credential passed. The policy does not govern
139
+ * personal access tokens, so a PAT is never given it.
140
+ *
141
+ * Any other explanation is left unnamed, so the warning keeps Azure's own words rather than a guess.
142
+ */
143
+ describeRefusal(session, refusal, scope) {
144
+ const code = /\b(?:TF|VS)\d{6}\b/.exec(refusal.detail ?? "")?.[0];
145
+ if (code === "VS403463") return {
146
+ reason: "conditional-access",
147
+ code
148
+ };
149
+ if (refusal.typeKey === "UnauthorizedRequestException") return {
150
+ reason: "access-denied",
151
+ ...code != null ? { code } : {}
152
+ };
153
+ if (this.id !== "azureDevOps" || session.type !== "oauth" || refusal.status !== 401 || refusal.typeKey != null || refusal.detail != null && code !== "TF400813") return;
154
+ const org = scope.resourceId != null ? this._organizations?.get(this.discoveryKey(session))?.find((o) => o.id === scope.resourceId || o.name === scope.resourceId)?.name : void 0;
155
+ return {
156
+ reason: "oauth-app-not-allowed",
157
+ ...code != null ? { code } : {},
158
+ ...org != null ? { remedyUrl: `${this.apiBaseUrlFor(session)}/${encodeURIComponent(org)}/_settings/organizationPolicy` } : {}
159
+ };
160
+ }
65
161
  _organizations;
66
162
  async getProviderResourcesForUser(session, force = false) {
67
163
  this._organizations ??= /* @__PURE__ */ new Map();
68
- const { accessToken } = session;
69
- if (this._organizations.get(accessToken) == null || force) {
70
- const api = await this.getProvidersApi();
164
+ const key = this.discoveryKey(session);
165
+ if (this._organizations.get(key) == null || force) {
71
166
  const account = await this.getProviderCurrentAccount(session);
72
167
  if (account?.id == null) return void 0;
73
- const { tokenWithInfo, options } = this.getApiOptions(session);
74
- const resources = await api.getAzureResourcesForUser(tokenWithInfo, account.id, options);
75
- this._organizations.set(accessToken, resources != null ? resources.map((r) => ({
168
+ const resources = await this.requestResourcesForUser(session, account.id);
169
+ this._organizations.set(key, resources != null ? resources.map((r) => ({
76
170
  ...r,
77
171
  key: r.id
78
172
  })) : void 0);
79
173
  }
80
- return this._organizations.get(accessToken);
174
+ return this._organizations.get(key);
81
175
  }
82
176
  _projects;
83
177
  /**
@@ -89,22 +183,22 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
89
183
  */
90
184
  async getProviderProjectsForResources(session, resources, force = false, failures) {
91
185
  this._projects ??= /* @__PURE__ */ new Map();
92
- const { accessToken } = session;
186
+ const discoveryKey = this.discoveryKey(session);
93
187
  let resourcesWithoutProjects = [];
94
188
  if (force) resourcesWithoutProjects = resources;
95
189
  else for (const resource of resources) {
96
- const resourceKey = `${accessToken}:${resource.id}`;
190
+ const resourceKey = `${discoveryKey}:${resource.id}`;
97
191
  if (this._projects.get(resourceKey) == null) resourcesWithoutProjects.push(resource);
98
192
  }
99
193
  const allProjects = [];
100
194
  let resultMetadata;
101
195
  if (resourcesWithoutProjects.length > 0) {
102
196
  const api = await this.getProvidersApi();
103
- const { tokenWithInfo, options } = this.getApiOptions(session);
197
+ const { tokenWithInfo } = this.getApiOptions(session);
104
198
  (await Promise.allSettled(resourcesWithoutProjects.map(async (resource) => ({
105
199
  resource,
106
200
  result: await collectProviderPagedResult((cursor) => api.getAzureProjectsForResource(tokenWithInfo, resource.name, {
107
- ...options,
201
+ ...this.getCollectionApiOptions(session, resource.name),
108
202
  cursor
109
203
  }), 20, {
110
204
  providerId: this.id,
@@ -149,11 +243,11 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
149
243
  allProjects.push(...projects);
150
244
  return;
151
245
  }
152
- this._projects.set(`${accessToken}:${resource.id}`, projects);
246
+ this._projects.set(`${discoveryKey}:${resource.id}`, projects);
153
247
  });
154
248
  }
155
249
  const cachedProjects = resources.reduce((projects, resource) => {
156
- const resourceProjects = this._projects.get(`${accessToken}:${resource.id}`);
250
+ const resourceProjects = this._projects.get(`${discoveryKey}:${resource.id}`);
157
251
  if (resourceProjects != null) projects.push(...resourceProjects);
158
252
  return projects;
159
253
  }, []);
@@ -173,9 +267,9 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
173
267
  const descriptors = /* @__PURE__ */ new Map();
174
268
  if (projects.length === 0) return descriptors;
175
269
  const api = await this.getProvidersApi();
176
- const { tokenWithInfo, options } = this.getApiOptions(session);
270
+ const { tokenWithInfo } = this.getApiOptions(session);
177
271
  await Promise.all(projects.map(async (project) => {
178
- const repos = (await api.getReposForAzureProject(tokenWithInfo, project.resourceName, project.name, options))?.values;
272
+ const repos = (await api.getReposForAzureProject(tokenWithInfo, project.resourceName, project.name, this.getCollectionApiOptions(session, project.resourceName)))?.values;
179
273
  if (repos != null && repos.length > 0) descriptors.set(project.id, repos.map((r) => ({
180
274
  id: r.id,
181
275
  nodeId: r.graphQLId ?? void 0,
@@ -197,7 +291,7 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
197
291
  id: o.id,
198
292
  providerId: this.id,
199
293
  name: o.name,
200
- url: `${this.apiBaseUrl}/${o.name}`
294
+ url: `${this.collectionApiBaseUrl(session, o.name)}/${o.name}`
201
295
  })) };
202
296
  }
203
297
  async getProviderProjectsForOrg(session, org) {
@@ -220,7 +314,7 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
220
314
  providerId: this.id,
221
315
  name: p.name,
222
316
  org: p.resourceName,
223
- url: `${this.apiBaseUrl}/${p.resourceName}/${p.name}`
317
+ url: `${this.collectionApiBaseUrl(session, p.resourceName)}/${p.resourceName}/${p.name}`
224
318
  })),
225
319
  ...projects.metadata != null ? { metadata: projects.metadata } : {}
226
320
  };
@@ -234,7 +328,8 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
234
328
  */
235
329
  async getProviderRepositoriesForOrg(session, org, options) {
236
330
  const api = await this.getProvidersApi();
237
- const { tokenWithInfo, options: apiOptions } = this.getApiOptions(session);
331
+ const { tokenWithInfo } = this.getApiOptions(session);
332
+ const apiOptions = this.getCollectionApiOptions(session, org);
238
333
  if (options?.project) return api.getReposForAzureProject(tokenWithInfo, org, options.project, {
239
334
  ...apiOptions,
240
335
  cursor: options.cursor
@@ -276,7 +371,8 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
276
371
  async mergeProviderPullRequest(session, pr, options) {
277
372
  const api = await this.getProvidersApi();
278
373
  if (pr.refs == null || pr.project == null) return false;
279
- const { tokenWithInfo, options: apiOptions } = this.getApiOptions(session);
374
+ const { tokenWithInfo } = this.getApiOptions(session);
375
+ const apiOptions = this.getCollectionApiOptions(session, pr.repository.owner);
280
376
  try {
281
377
  return await api.mergePullRequest(tokenWithInfo, pr, {
282
378
  ...options,
@@ -291,17 +387,17 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
291
387
  this.ctx.hooks?.ui?.onError?.(`${ex.message}. Check branch policies, and ensure you have the necessary permissions to merge the pull request.`);
292
388
  }
293
389
  async getProviderAccountForCommit(session, repo, rev, options) {
294
- return (await this.authenticationService.apis.azure)?.getAccountForCommit(this, toTokenWithInfo(this.id, session), repo.owner, repo.name, rev, getAzureRepositoryApiBaseUrl(this.apiBaseUrl, repo), options);
390
+ return (await this.authenticationService.apis.azure)?.getAccountForCommit(this, toTokenWithInfo(this.id, session), repo.owner, repo.name, rev, getAzureRepositoryApiBaseUrl(this.apiBaseUrlFor(session), repo), options);
295
391
  }
296
392
  async getProviderAccountForEmail(_session, _repo, _email, _options) {
297
393
  return Promise.resolve(void 0);
298
394
  }
299
395
  async getProviderDefaultBranch(session, repo, cancellation) {
300
- return (await this.authenticationService.apis.azure)?.getDefaultBranch(this, toTokenWithInfo(this.id, session), repo.owner, repo.name, { baseUrl: getAzureRepositoryApiBaseUrl(this.apiBaseUrl, repo) }, cancellation);
396
+ return (await this.authenticationService.apis.azure)?.getDefaultBranch(this, toTokenWithInfo(this.id, session), repo.owner, repo.name, { baseUrl: getAzureRepositoryApiBaseUrl(this.apiBaseUrlFor(session), repo) }, cancellation);
301
397
  }
302
398
  async getProviderLinkedIssueOrPullRequest(session, repo, { id }, type) {
303
399
  return (await this.authenticationService.apis.azure)?.getIssueOrPullRequest(this, toTokenWithInfo(this.id, session), repo.owner, repo.name, id, {
304
- baseUrl: getAzureRepositoryApiBaseUrl(this.apiBaseUrl, repo),
400
+ baseUrl: getAzureRepositoryApiBaseUrl(this.apiBaseUrlFor(session), repo),
305
401
  type
306
402
  });
307
403
  }
@@ -313,13 +409,13 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
313
409
  if (projects.values.length === 0) return void 0;
314
410
  const matchingProject = projects.values.find((p) => p.resourceName === project.owner && p.name === project.name);
315
411
  if (matchingProject == null) return void 0;
316
- return (await this.authenticationService.apis.azure)?.getIssue(this, toTokenWithInfo(this.id, session), matchingProject, id, { baseUrl: this.apiBaseUrl });
412
+ return (await this.authenticationService.apis.azure)?.getIssue(this, toTokenWithInfo(this.id, session), matchingProject, id, { baseUrl: this.collectionApiBaseUrl(session, matchingProject.resourceName) });
317
413
  }
318
414
  async getProviderPullRequestForBranch(session, repo, branch, _options) {
319
- return (await this.authenticationService.apis.azure)?.getPullRequestForBranch(this, toTokenWithInfo(this.id, session), repo.owner, repo.name, branch, { baseUrl: getAzureRepositoryApiBaseUrl(this.apiBaseUrl, repo) });
415
+ return (await this.authenticationService.apis.azure)?.getPullRequestForBranch(this, toTokenWithInfo(this.id, session), repo.owner, repo.name, branch, { baseUrl: getAzureRepositoryApiBaseUrl(this.apiBaseUrlFor(session), repo) });
320
416
  }
321
417
  async getProviderPullRequestForCommit(session, repo, rev) {
322
- return (await this.authenticationService.apis.azure)?.getPullRequestForCommit(this, toTokenWithInfo(this.id, session), repo.owner, repo.name, rev, getAzureRepositoryApiBaseUrl(this.apiBaseUrl, repo));
418
+ return (await this.authenticationService.apis.azure)?.getPullRequestForCommit(this, toTokenWithInfo(this.id, session), repo.owner, repo.name, rev, getAzureRepositoryApiBaseUrl(this.apiBaseUrlFor(session), repo));
323
419
  }
324
420
  async getRepoInfo(repo) {
325
421
  const identity = getAzureRepositoryIdentity(repo);
@@ -330,47 +426,53 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
330
426
  const { tokenWithInfo, options } = this.getApiOptions(session);
331
427
  return api.getRepo(tokenWithInfo, identity.resourceName, identity.repositoryName, identity.projectName, {
332
428
  ...options,
333
- baseUrl: getAzureRepositoryApiBaseUrl(this.apiBaseUrl, repo)
429
+ baseUrl: getAzureRepositoryApiBaseUrl(this.apiBaseUrlFor(session), repo)
334
430
  });
335
431
  }
336
432
  async getProviderRepositoryMetadata(session, repo, cancellation) {
337
- return (await this.authenticationService.apis.azure)?.getRepositoryMetadata(this, toTokenWithInfo(this.id, session), repo.owner, repo.name, { baseUrl: getAzureRepositoryApiBaseUrl(this.apiBaseUrl, repo) }, cancellation);
433
+ return (await this.authenticationService.apis.azure)?.getRepositoryMetadata(this, toTokenWithInfo(this.id, session), repo.owner, repo.name, { baseUrl: getAzureRepositoryApiBaseUrl(this.apiBaseUrlFor(session), repo) }, cancellation);
338
434
  }
339
435
  async searchProviderMyPullRequests(session, repos, _cancellation, options) {
340
436
  const api = await this.getProvidersApi();
341
437
  if (repos != null) return;
342
438
  const states = toProviderPullRequestStates(options?.state);
343
- const user = await this.getProviderCurrentAccount(session);
344
- if (user?.id == null) return void 0;
439
+ if ((await this.getProviderCurrentAccount(session))?.id == null) return void 0;
345
440
  const orgs = await this.getProviderResourcesForUser(session);
346
441
  if (orgs == null || orgs.length === 0) return void 0;
347
442
  const projects = await this.getProviderProjectsForResources(session, orgs);
348
443
  if (projects.values.length === 0) return void 0;
349
444
  const repoDescriptors = [...(await this.getRepoDescriptorsForProjects(session, projects.values) ?? /* @__PURE__ */ new Map()).values()].filter((r) => r != null).flat();
350
- const { tokenWithInfo, options: apiOptions } = this.getApiOptions(session);
351
- const projectInputs = projects.values.map((p) => ({
352
- namespace: p.resourceName,
353
- project: p.name
354
- }));
355
- const assignedPrs = (await api.getPullRequestsForAzureProjects(tokenWithInfo, projectInputs, {
356
- ...apiOptions,
357
- assigneeLogins: [user.id],
358
- states
359
- })).values.map((pr) => this.fromAzureProviderPullRequest(pr, repoDescriptors, projects.values));
360
- const authoredPrs = (await api.getPullRequestsForAzureProjects(tokenWithInfo, projectInputs, {
361
- ...apiOptions,
362
- authorLogin: user.id,
363
- states
364
- })).values.map((pr) => this.fromAzureProviderPullRequest(pr, repoDescriptors, projects.values));
365
- const prsById = /* @__PURE__ */ new Map();
366
- for (const pr of authoredPrs) prsById.set(pr.id, pr);
367
- for (const pr of assignedPrs) if (prsById.get(pr.id) == null) prsById.set(pr.id, pr);
368
- return [...prsById.values()];
445
+ const { tokenWithInfo } = this.getApiOptions(session);
446
+ const assignedPrs = [];
447
+ const authoredPrs = [];
448
+ for (const org of uniqueAzureNames(projects.values.map((p) => p.resourceName))) {
449
+ const userId = await this.getFilterUserId(session, org);
450
+ if (userId == null) continue;
451
+ const projectInputs = projects.values.filter((p) => sameAzureName(p.resourceName, org)).map((p) => ({
452
+ namespace: p.resourceName,
453
+ project: p.name
454
+ }));
455
+ const orgOptions = this.getCollectionApiOptions(session, org);
456
+ const reads = [[{ assigneeLogins: [userId] }, assignedPrs], [{ authorLogin: userId }, authoredPrs]];
457
+ for (const [filter, into] of reads) {
458
+ const result = await api.getPullRequestsForAzureProjects(tokenWithInfo, projectInputs, {
459
+ ...orgOptions,
460
+ ...filter,
461
+ states
462
+ });
463
+ into.push(...result.values.map((pr) => this.fromAzureProviderPullRequest(pr, repoDescriptors, projects.values)));
464
+ }
465
+ }
466
+ const prsByIdentity = /* @__PURE__ */ new Map();
467
+ for (const pr of [...authoredPrs, ...assignedPrs]) {
468
+ const identity = `${pr.repository.owner}/${pr.repository.id || pr.repository.repo}#${pr.id}`;
469
+ if (!prsByIdentity.has(identity)) prsByIdentity.set(identity, pr);
470
+ }
471
+ return [...prsByIdentity.values()];
369
472
  }
370
473
  async getProviderMyPullRequestsForUser(session, options) {
371
474
  const api = await this.getProvidersApi();
372
- const user = await this.getProviderCurrentAccount(session);
373
- if (user?.id == null) return void 0;
475
+ if ((await this.getProviderCurrentAccount(session))?.id == null) return void 0;
374
476
  const orgs = await this.getProviderResourcesForUser(session);
375
477
  if (orgs == null || orgs.length === 0) return void 0;
376
478
  const failures = [];
@@ -384,7 +486,7 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
384
486
  },
385
487
  ...projects.metadata != null ? { metadata: projects.metadata } : {}
386
488
  };
387
- const { tokenWithInfo, options: apiOptions } = this.getApiOptions(session);
489
+ const { tokenWithInfo } = this.getApiOptions(session);
388
490
  const states = toProviderPullRequestStates(options?.state);
389
491
  const maxPagesPerProject = 20;
390
492
  let truncated = projects.metadata != null && projects.metadata.completeness !== "complete";
@@ -394,7 +496,7 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
394
496
  let page;
395
497
  for (let i = 0; i < maxPagesPerProject; i++) try {
396
498
  const result = await api.getPullRequestsForAzureProject(tokenWithInfo, project, {
397
- ...apiOptions,
499
+ ...this.getCollectionApiOptions(session, project.namespace),
398
500
  ...filter,
399
501
  states,
400
502
  page
@@ -436,6 +538,8 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
436
538
  const requested = options?.filters?.length ? new Set(options.filters) : void 0;
437
539
  const wantAuthored = requested == null || requested.has("author");
438
540
  const wantReviewed = requested == null || requested.has("assignee") || requested.has("review-requested");
541
+ const userIds = /* @__PURE__ */ new Map();
542
+ for (const org of uniqueAzureNames(projects.values.map((p) => p.resourceName))) userIds.set(org.toLowerCase(), await this.getFilterUserId(session, org));
439
543
  const outcomes = await Promise.all(projects.values.flatMap((p) => {
440
544
  const project = {
441
545
  namespace: p.resourceName,
@@ -446,9 +550,15 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
446
550
  resourceId: p.resourceId,
447
551
  projectId: p.name
448
552
  };
553
+ const userId = userIds.get(p.resourceName.toLowerCase());
554
+ if (userId == null) {
555
+ failures.push(toCollectionScopeFailure(scope, /* @__PURE__ */ new Error("The current user could not be resolved here")));
556
+ truncated = true;
557
+ return [];
558
+ }
449
559
  const drains = [];
450
- if (wantAuthored) drains.push(drainProject(project, scope, { authorLogin: user.id }));
451
- if (wantReviewed) drains.push(drainProject(project, scope, { reviewerId: user.id }));
560
+ if (wantAuthored) drains.push(drainProject(project, scope, { authorLogin: userId }));
561
+ if (wantReviewed) drains.push(drainProject(project, scope, { reviewerId: userId }));
452
562
  return drains;
453
563
  }));
454
564
  const prsByIdentity = /* @__PURE__ */ new Map();
@@ -488,7 +598,7 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
488
598
  const repoInputs = requestedRepos == null ? void 0 : repoDescriptors.filter((r) => requestedRepos.some((repo) => repo.resourceName === r.resourceName && repo.repositoryName === r.name && (repo.projectName == null || repo.projectName === r.projectName)));
489
599
  if (repoInputs?.length === 0) return [];
490
600
  const api = await this.getProvidersApi();
491
- const { tokenWithInfo, options: apiOptions } = this.getApiOptions(session);
601
+ const { tokenWithInfo } = this.getApiOptions(session);
492
602
  const states = toProviderPullRequestStates(options?.include);
493
603
  const searchScopes = repoInputs != null ? repoInputs.flatMap((repo) => repo.projectName == null ? [] : [{
494
604
  project: {
@@ -511,7 +621,7 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
511
621
  for (let i = 0; i < 20; i++) {
512
622
  if (cancellation?.aborted) throw new CancellationError();
513
623
  const result = await api.getPullRequestsForAzureProject(tokenWithInfo, scope.project, {
514
- ...apiOptions,
624
+ ...this.getCollectionApiOptions(session, scope.project.namespace),
515
625
  page,
516
626
  repo: scope.repo,
517
627
  states
@@ -571,10 +681,10 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
571
681
  values: [],
572
682
  truncated: false
573
683
  };
574
- const { tokenWithInfo, options } = this.getApiOptions(session);
684
+ const { tokenWithInfo } = this.getApiOptions(session);
575
685
  const drain = async (p, filter) => {
576
686
  const result = await collectProviderPagedResult((cursor) => api.getIssuesForAzureProject(tokenWithInfo, p.resourceName, p.name, {
577
- ...options,
687
+ ...this.getCollectionApiOptions(session, p.resourceName),
578
688
  ...filter,
579
689
  cursor,
580
690
  sort: searchOptions?.sort
@@ -623,10 +733,11 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
623
733
  }
624
734
  async providerOnConnect() {
625
735
  if (this._session == null) return;
736
+ const discoveryKey = this.discoveryKey(this._session);
626
737
  const canHydrateStoredProjects = (metadata) => metadata == null || metadata.completeness === "complete";
627
- const storedAccount = this.ctx.storage.get(`azure:${this._session.accessToken}:account`);
628
- const storedOrganizations = this.ctx.storage.get(`azure:${this._session.accessToken}:organizations`);
629
- const storedProjects = this.ctx.storage.get(`azure:${this._session.accessToken}:projects`);
738
+ const storedAccount = this.ctx.storage.get(`azure:${discoveryKey}:account`);
739
+ const storedOrganizations = this.ctx.storage.get(`azure:${discoveryKey}:organizations`);
740
+ const storedProjects = this.ctx.storage.get(`azure:${discoveryKey}:projects`);
630
741
  let account = storedAccount?.data ? {
631
742
  ...storedAccount.data,
632
743
  provider: this
@@ -645,7 +756,7 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
645
756
  account = await this.getProviderCurrentAccount(this._session);
646
757
  if (account != null) {
647
758
  await this.ctx.storage.deleteWithPrefix("azure");
648
- await this.ctx.storage.store(`azure:${this._session.accessToken}:account`, {
759
+ await this.ctx.storage.store(`azure:${discoveryKey}:account`, {
649
760
  v: 1,
650
761
  timestamp: Date.now(),
651
762
  data: {
@@ -659,29 +770,29 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
659
770
  }
660
771
  }
661
772
  this._accounts ??= /* @__PURE__ */ new Map();
662
- this._accounts.set(this._session.accessToken, account);
773
+ this._accounts.set(discoveryKey, account);
663
774
  if (storedOrganizations == null) {
664
775
  organizations = await this.getProviderResourcesForUser(this._session, true);
665
- await this.ctx.storage.store(`azure:${this._session.accessToken}:organizations`, {
776
+ await this.ctx.storage.store(`azure:${discoveryKey}:organizations`, {
666
777
  v: 1,
667
778
  timestamp: Date.now(),
668
779
  data: organizations
669
780
  });
670
781
  }
671
782
  this._organizations ??= /* @__PURE__ */ new Map();
672
- this._organizations.set(this._session.accessToken, organizations);
783
+ this._organizations.set(discoveryKey, organizations);
673
784
  if (projects == null && organizations?.length) {
674
785
  projects = await this.getProviderProjectsForResources(this._session, organizations);
675
- if (projects != null && canHydrateStoredProjects(projects.metadata)) await this.ctx.storage.store(`azure:${this._session.accessToken}:projects`, {
786
+ if (projects != null && canHydrateStoredProjects(projects.metadata)) await this.ctx.storage.store(`azure:${discoveryKey}:projects`, {
676
787
  v: 2,
677
788
  timestamp: Date.now(),
678
789
  data: projects
679
790
  });
680
- else await this.ctx.storage.delete(`azure:${this._session.accessToken}:projects`);
791
+ else await this.ctx.storage.delete(`azure:${discoveryKey}:projects`);
681
792
  }
682
793
  this._projects ??= /* @__PURE__ */ new Map();
683
794
  if (projects != null && canHydrateStoredProjects(projects.metadata)) for (const project of projects.values) {
684
- const projectKey = `${this._session.accessToken}:${project.resourceId}`;
795
+ const projectKey = `${discoveryKey}:${project.resourceId}`;
685
796
  const projects = this._projects.get(projectKey);
686
797
  if (projects == null) this._projects.set(projectKey, [project]);
687
798
  else if (!projects.some((p) => p.id === project.id)) projects.push(project);
@@ -729,53 +840,11 @@ var AzureDevOpsIntegration = class extends AzureDevOpsIntegrationBase {
729
840
  get domain() {
730
841
  return cloudMetadata.domain;
731
842
  }
732
- get apiBaseUrl() {
843
+ apiBaseUrlFor(_session) {
733
844
  return "https://dev.azure.com";
734
845
  }
735
846
  };
736
- const serverMetadata = providersMetadata["azure-devops-server"];
737
- const serverAuthProvider = Object.freeze({
738
- id: serverMetadata.id,
739
- scopes: serverMetadata.scopes
740
- });
741
- var AzureDevOpsServerIntegration = class extends AzureDevOpsIntegrationBase {
742
- domain;
743
- authProvider = serverAuthProvider;
744
- id = "azure-devops-server";
745
- key;
746
- name = "Azure DevOps Server";
747
- constructor(ctx, authenticationService, getProvidersApi, didChangeConnection, domain) {
748
- super(ctx, authenticationService, getProvidersApi, didChangeConnection);
749
- this.domain = domain;
750
- this.key = `${this.id}:${this.domain}`;
751
- }
752
- get apiBaseUrl() {
753
- return `${this._session?.protocol ?? "https:"}//${this.domain}`;
754
- }
755
- getApiOptions(session, doNotConvertToPat = false) {
756
- const { options, ...rest } = super.getApiOptions(session, doNotConvertToPat);
757
- return {
758
- ...rest,
759
- options: {
760
- ...options,
761
- baseUrl: this.apiBaseUrl
762
- }
763
- };
764
- }
765
- async _requestForCurrentUser(session) {
766
- const azure = await this.authenticationService.apis.azure;
767
- const user = azure ? await azure.getCurrentUserOnServer(this, toTokenWithInfo(this.id, session), this.apiBaseUrl) : void 0;
768
- return user ? {
769
- provider: this,
770
- id: user.id,
771
- name: user.name ?? void 0,
772
- email: user.email ?? void 0,
773
- avatarUrl: user.avatarUrl ?? void 0,
774
- username: user.username ?? void 0
775
- } : void 0;
776
- }
777
- };
778
847
  //#endregion
779
- export { AzureDevOpsIntegration, AzureDevOpsIntegrationBase, AzureDevOpsServerIntegration };
848
+ export { AzureDevOpsIntegration, AzureDevOpsIntegrationBase, getAzureRepositoryApiBaseUrl, getAzureRepositoryIdentity, sameAzureName, uniqueAzureNames };
780
849
 
781
850
  //# sourceMappingURL=azureDevOps.js.map