@paklo/core 0.8.0 → 0.10.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.
@@ -1,130 +1,586 @@
1
- import { r as isErrorTemporaryFailure, t as HttpRequestError } from "../http-D2wwAKQ-.mjs";
2
- import { n as logger } from "../logger-BqvUa-Ue.mjs";
3
- import { F as parseDependabotConfig, M as POSSIBLE_CONFIG_FILE_PATHS, c as DependabotExistingGroupPRSchema, l as DependabotExistingPRSchema } from "../job-DanO84YW.mjs";
4
- import "../dependabot-E0nM5Xbb.mjs";
1
+ import { n as logger } from "../logger-3Qfh9NUj.mjs";
2
+ import { T as CONFIG_FILE_PATHS_AZURE, _ as DependabotPersistedPrSchema, z as parseDependabotConfig } from "../job-COuliaYg.mjs";
3
+ import { n as getDependencyNames, o as normalizeBranchName, s as normalizeFilePath, t as areEqual } from "../dependabot-DVf7lAEG.mjs";
4
+ import { z } from "zod";
5
+ import ky, { isHTTPError } from "ky";
5
6
  import * as path from "node:path";
6
7
  import { existsSync } from "node:fs";
7
8
  import { readFile } from "node:fs/promises";
8
9
 
9
- //#region src/azure/types.ts
10
- let VersionControlChangeType = /* @__PURE__ */ function(VersionControlChangeType$1) {
11
- VersionControlChangeType$1[VersionControlChangeType$1["None"] = 0] = "None";
12
- VersionControlChangeType$1[VersionControlChangeType$1["Add"] = 1] = "Add";
13
- VersionControlChangeType$1[VersionControlChangeType$1["Edit"] = 2] = "Edit";
14
- VersionControlChangeType$1[VersionControlChangeType$1["Encoding"] = 4] = "Encoding";
15
- VersionControlChangeType$1[VersionControlChangeType$1["Rename"] = 8] = "Rename";
16
- VersionControlChangeType$1[VersionControlChangeType$1["Delete"] = 16] = "Delete";
17
- VersionControlChangeType$1[VersionControlChangeType$1["Undelete"] = 32] = "Undelete";
18
- VersionControlChangeType$1[VersionControlChangeType$1["Branch"] = 64] = "Branch";
19
- VersionControlChangeType$1[VersionControlChangeType$1["Merge"] = 128] = "Merge";
20
- VersionControlChangeType$1[VersionControlChangeType$1["Lock"] = 256] = "Lock";
21
- VersionControlChangeType$1[VersionControlChangeType$1["Rollback"] = 512] = "Rollback";
22
- VersionControlChangeType$1[VersionControlChangeType$1["SourceRename"] = 1024] = "SourceRename";
23
- VersionControlChangeType$1[VersionControlChangeType$1["TargetRename"] = 2048] = "TargetRename";
24
- VersionControlChangeType$1[VersionControlChangeType$1["Property"] = 4096] = "Property";
25
- VersionControlChangeType$1[VersionControlChangeType$1["All"] = 8191] = "All";
26
- return VersionControlChangeType$1;
27
- }({});
28
- let GitPullRequestMergeStrategy = /* @__PURE__ */ function(GitPullRequestMergeStrategy$1) {
29
- GitPullRequestMergeStrategy$1[GitPullRequestMergeStrategy$1["NoFastForward"] = 1] = "NoFastForward";
30
- GitPullRequestMergeStrategy$1[GitPullRequestMergeStrategy$1["Squash"] = 2] = "Squash";
31
- GitPullRequestMergeStrategy$1[GitPullRequestMergeStrategy$1["Rebase"] = 3] = "Rebase";
32
- GitPullRequestMergeStrategy$1[GitPullRequestMergeStrategy$1["RebaseMerge"] = 4] = "RebaseMerge";
33
- return GitPullRequestMergeStrategy$1;
34
- }({});
35
- let CommentThreadStatus = /* @__PURE__ */ function(CommentThreadStatus$1) {
36
- CommentThreadStatus$1[CommentThreadStatus$1["Unknown"] = 0] = "Unknown";
37
- CommentThreadStatus$1[CommentThreadStatus$1["Active"] = 1] = "Active";
38
- CommentThreadStatus$1[CommentThreadStatus$1["Fixed"] = 2] = "Fixed";
39
- CommentThreadStatus$1[CommentThreadStatus$1["WontFix"] = 3] = "WontFix";
40
- CommentThreadStatus$1[CommentThreadStatus$1["Closed"] = 4] = "Closed";
41
- CommentThreadStatus$1[CommentThreadStatus$1["ByDesign"] = 5] = "ByDesign";
42
- CommentThreadStatus$1[CommentThreadStatus$1["Pending"] = 6] = "Pending";
43
- return CommentThreadStatus$1;
44
- }({});
45
- let CommentType = /* @__PURE__ */ function(CommentType$1) {
46
- CommentType$1[CommentType$1["Unknown"] = 0] = "Unknown";
47
- CommentType$1[CommentType$1["Text"] = 1] = "Text";
48
- CommentType$1[CommentType$1["CodeChange"] = 2] = "CodeChange";
49
- CommentType$1[CommentType$1["System"] = 3] = "System";
50
- return CommentType$1;
51
- }({});
52
- let ItemContentType = /* @__PURE__ */ function(ItemContentType$1) {
53
- ItemContentType$1[ItemContentType$1["RawText"] = 0] = "RawText";
54
- ItemContentType$1[ItemContentType$1["Base64Encoded"] = 1] = "Base64Encoded";
55
- return ItemContentType$1;
56
- }({});
57
- let PullRequestAsyncStatus = /* @__PURE__ */ function(PullRequestAsyncStatus$1) {
58
- PullRequestAsyncStatus$1[PullRequestAsyncStatus$1["NotSet"] = 0] = "NotSet";
59
- PullRequestAsyncStatus$1[PullRequestAsyncStatus$1["Queued"] = 1] = "Queued";
60
- PullRequestAsyncStatus$1[PullRequestAsyncStatus$1["Conflicts"] = 2] = "Conflicts";
61
- PullRequestAsyncStatus$1[PullRequestAsyncStatus$1["Succeeded"] = 3] = "Succeeded";
62
- PullRequestAsyncStatus$1[PullRequestAsyncStatus$1["RejectedByPolicy"] = 4] = "RejectedByPolicy";
63
- PullRequestAsyncStatus$1[PullRequestAsyncStatus$1["Failure"] = 5] = "Failure";
64
- return PullRequestAsyncStatus$1;
65
- }({});
66
- let PullRequestStatus = /* @__PURE__ */ function(PullRequestStatus$1) {
67
- PullRequestStatus$1[PullRequestStatus$1["NotSet"] = 0] = "NotSet";
68
- PullRequestStatus$1[PullRequestStatus$1["Active"] = 1] = "Active";
69
- PullRequestStatus$1[PullRequestStatus$1["Abandoned"] = 2] = "Abandoned";
70
- PullRequestStatus$1[PullRequestStatus$1["Completed"] = 3] = "Completed";
71
- PullRequestStatus$1[PullRequestStatus$1["All"] = 4] = "All";
72
- return PullRequestStatus$1;
73
- }({});
74
-
75
- //#endregion
76
- //#region src/azure/models.ts
10
+ //#region src/azure/client/constants.ts
11
+ const API_VERSION = "5.0";
12
+ const API_VERSION_PREVIEW = "5.0-preview";
13
+ /** Returned when no user is authenticated */
14
+ const ANONYMOUS_USER_ID = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa";
77
15
  /**
78
16
  * Pull request property names used to store metadata about the pull request.
79
17
  * https://learn.microsoft.com/en-us/rest/api/azure/devops/git/pull-request-properties
80
18
  */
81
- const DEVOPS_PR_PROPERTY_MICROSOFT_GIT_SOURCE_REF_NAME = "Microsoft.Git.PullRequest.SourceRefName";
82
- const DEVOPS_PR_PROPERTY_DEPENDABOT_PACKAGE_MANAGER = "Dependabot.PackageManager";
83
- const DEVOPS_PR_PROPERTY_DEPENDABOT_DEPENDENCIES = "Dependabot.Dependencies";
19
+ const PR_PROPERTY_MICROSOFT_GIT_SOURCE_REF_NAME = "Microsoft.Git.PullRequest.SourceRefName";
20
+ const PR_PROPERTY_DEPENDABOT_PACKAGE_MANAGER = "Dependabot.PackageManager";
21
+ const PR_PROPERTY_DEPENDABOT_DEPENDENCIES = "Dependabot.Dependencies";
22
+ const PR_DESCRIPTION_MAX_LENGTH = 4e3;
84
23
 
85
24
  //#endregion
86
- //#region src/azure/utils.ts
87
- function normalizeFilePath(path$1) {
88
- return path$1?.replace(/\\/g, "/")?.replace(/^\.\//, "/")?.replace(/^([^/])/, "/$1");
89
- }
90
- function normalizeBranchName(branch) {
91
- return branch?.replace(/^refs\/heads\//i, "");
92
- }
93
- const DependenciesPrPropertySchema = DependabotExistingPRSchema.array().or(DependabotExistingGroupPRSchema);
25
+ //#region src/azure/client/client-base.ts
26
+ var BaseAzureDevOpsClient = class {
27
+ constructor(client) {
28
+ this.client = client;
29
+ }
30
+ makeUrl(path$1, params, apiVersion = API_VERSION) {
31
+ if (typeof params === "string") {
32
+ apiVersion = params;
33
+ params = {};
34
+ }
35
+ return `${path$1}?${Object.entries({
36
+ "api-version": apiVersion,
37
+ ...params
38
+ }).filter(([, value]) => value).map(([key, value]) => `${key}=${value}`).join("&")}`;
39
+ }
40
+ };
41
+
42
+ //#endregion
43
+ //#region src/azure/client/client-connection.ts
44
+ var ConnectionClient = class extends BaseAzureDevOpsClient {
45
+ /**
46
+ * Get the connection data for the current user.
47
+ */
48
+ async get() {
49
+ return await this.client.get(this.makeUrl("_apis/connectiondata", API_VERSION_PREVIEW)).json();
50
+ }
51
+ };
52
+
53
+ //#endregion
54
+ //#region src/azure/client/client-git.ts
55
+ var GitClient = class extends BaseAzureDevOpsClient {
56
+ async getItem(projectIdOrName, repositoryIdOrName, path$1, includeContent = true, latestProcessedChange = true) {
57
+ try {
58
+ return await this.client.get(this.makeUrl(`${encodeURIComponent(projectIdOrName)}/_apis/git/repositories/${encodeURIComponent(repositoryIdOrName)}/items`, {
59
+ path: path$1,
60
+ includeContent,
61
+ latestProcessedChange
62
+ })).json();
63
+ } catch (e) {
64
+ if (isHTTPError(e) && e.response.status === 404) return;
65
+ throw e;
66
+ }
67
+ }
68
+ async getPush(projectIdOrName, repositoryIdOrName, pushId, includeCommits, includeRefUpdates) {
69
+ return await this.client.get(this.makeUrl(`${encodeURIComponent(projectIdOrName)}/_apis/git/repositories/${encodeURIComponent(repositoryIdOrName)}/pushes/${pushId}`, {
70
+ includeCommits,
71
+ includeRefUpdates
72
+ })).json();
73
+ }
74
+ async createPush(projectIdOrName, repositoryIdOrName, push) {
75
+ return await this.client.post(this.makeUrl(`${encodeURIComponent(projectIdOrName)}/_apis/git/repositories/${encodeURIComponent(repositoryIdOrName)}/pushes`), { json: push }).json();
76
+ }
77
+ async getDiffCommits(projectIdOrName, repositoryIdOrName, baseVersion, targetVersion) {
78
+ return await this.client.get(this.makeUrl(`${encodeURIComponent(projectIdOrName)}/_apis/git/repositories/${encodeURIComponent(repositoryIdOrName)}/diffs/commits`, {
79
+ baseVersion,
80
+ baseVersionType: "commit",
81
+ targetVersion,
82
+ targetVersionType: "commit"
83
+ })).json();
84
+ }
85
+ async updateRef(projectIdOrName, repositoryIdOrName, ref) {
86
+ return (await this.client.post(this.makeUrl(`${encodeURIComponent(projectIdOrName)}/_apis/git/repositories/${encodeURIComponent(repositoryIdOrName)}/refs`), { json: ref }).json()).value;
87
+ }
88
+ };
89
+
90
+ //#endregion
91
+ //#region src/azure/client/client-identity.ts
92
+ var IdentityClient = class extends BaseAzureDevOpsClient {
93
+ /**
94
+ * Get the identities that match the given user name, email, or group name.
95
+ * Requires scope "Identity (Read)" (vso.identity).
96
+ * @param filterValue username, email, or group name
97
+ * @returns
98
+ */
99
+ async get(filterValue) {
100
+ return (await this.client.get(this.makeUrl("_apis/identities", {
101
+ searchFilter: "General",
102
+ filterValue,
103
+ queryMembership: "None"
104
+ })).json())?.value;
105
+ }
106
+ };
107
+
108
+ //#endregion
109
+ //#region src/azure/client/client-projects.ts
110
+ var ProjectsClient = class extends BaseAzureDevOpsClient {
111
+ async list() {
112
+ return (await this.client.get(this.makeUrl("_apis/projects")).json())?.value;
113
+ }
114
+ async get(idOrName) {
115
+ return await this.client.get(this.makeUrl(`_apis/projects/${encodeURIComponent(idOrName)}`)).json();
116
+ }
117
+ };
118
+
119
+ //#endregion
120
+ //#region src/azure/client/client-pull-requests.ts
121
+ var PullRequestsClient = class extends BaseAzureDevOpsClient {
122
+ /**
123
+ * List pull requests
124
+ * Requires scope "Code (Read)" (vso.code).
125
+ * @param projectIdOrName
126
+ * @param repositoryIdOrName
127
+ * @param creatorId ID of the user who created the pull requests
128
+ * @param status The status of the pull requests to filter by
129
+ */
130
+ async list(projectIdOrName, repositoryIdOrName, creatorId, status) {
131
+ return (await this.client.get(this.makeUrl(`${projectIdOrName}/_apis/git/repositories/${repositoryIdOrName}/pullrequests`, {
132
+ "searchCriteria.creatorId": creatorId,
133
+ "searchCriteria.status": status
134
+ })).json())?.value;
135
+ }
136
+ async get(projectIdOrName, repositoryIdOrName, pullRequestId) {
137
+ return await this.client.get(this.makeUrl(`${projectIdOrName}/_apis/git/repositories/${repositoryIdOrName}/pullrequests/${pullRequestId}`)).json();
138
+ }
139
+ async create(projectIdOrName, repositoryIdOrName, pr) {
140
+ return await this.client.post(this.makeUrl(`${projectIdOrName}/_apis/git/repositories/${repositoryIdOrName}/pullrequests`), { json: pr }).json();
141
+ }
142
+ async update(projectIdOrName, repositoryIdOrName, pullRequestId, pr) {
143
+ return await this.client.patch(this.makeUrl(`${projectIdOrName}/_apis/git/repositories/${repositoryIdOrName}/pullrequests/${pullRequestId}`), { json: pr }).json();
144
+ }
145
+ async getProperties(projectIdOrName, repositoryIdOrName, pullRequestId) {
146
+ const response = await this.client.get(this.makeUrl(`${projectIdOrName}/_apis/git/repositories/${repositoryIdOrName}/pullrequests/${pullRequestId}/properties`)).json();
147
+ return Object.entries(response?.value || {}).filter(([, val]) => val?.$value).map(([key, val]) => ({
148
+ name: key,
149
+ value: val.$value
150
+ }));
151
+ }
152
+ async setProperties(projectIdOrName, repositoryIdOrName, pullRequestId, properties) {
153
+ return await this.client.patch(this.makeUrl(`${projectIdOrName}/_apis/git/repositories/${repositoryIdOrName}/pullrequests/${pullRequestId}/properties`), {
154
+ headers: { "Content-Type": "application/json-patch+json" },
155
+ json: properties.map((property) => {
156
+ return {
157
+ op: "add",
158
+ path: `/${property.name}`,
159
+ value: property.value
160
+ };
161
+ })
162
+ }).json();
163
+ }
164
+ /**
165
+ * Approve a pull request.
166
+ * Requires scope "Code (Write)" (vso.code_write).
167
+ */
168
+ async approve(projectIdOrName, repositoryIdOrName, pullRequestId, userId) {
169
+ return await this.client.put(this.makeUrl(`${projectIdOrName}/_apis/git/repositories/${repositoryIdOrName}/pullrequests/${pullRequestId}/reviewers/${userId}`, "7.1"), { json: {
170
+ vote: 10,
171
+ isReapprove: true
172
+ } }).json();
173
+ }
174
+ /**
175
+ * Abandon a pull request.
176
+ * Requires scope "Code (Write)" (vso.code_write).
177
+ */
178
+ async abandon(projectIdOrName, repositoryIdOrName, pullRequestId, userId) {
179
+ return await this.client.patch(this.makeUrl(`${projectIdOrName}/_apis/git/repositories/${repositoryIdOrName}/pullrequests/${pullRequestId}`), { json: {
180
+ status: "abandoned",
181
+ closedBy: { id: userId }
182
+ } }).json();
183
+ }
184
+ /**
185
+ * Get commits of a pull request.
186
+ * Requires scope "Code (Read)" (vso.code_read).
187
+ */
188
+ async getCommits(projectIdOrName, repositoryIdOrName, pullRequestId) {
189
+ return (await this.client.get(this.makeUrl(`${projectIdOrName}/_apis/git/repositories/${repositoryIdOrName}/pullrequests/${pullRequestId}/commits`)).json())?.value;
190
+ }
191
+ /**
192
+ * Create a comment thread on a pull request.
193
+ * Requires scope "Code (Write)" (vso.code_write).
194
+ */
195
+ async createCommentThread(projectIdOrName, repositoryIdOrName, pullRequestId, thread) {
196
+ return await this.client.post(this.makeUrl(`${projectIdOrName}/_apis/git/repositories/${repositoryIdOrName}/pullrequests/${pullRequestId}/threads`), { json: thread }).json();
197
+ }
198
+ };
199
+
200
+ //#endregion
201
+ //#region src/azure/client/client-repositories.ts
202
+ var RepositoriesClient = class extends BaseAzureDevOpsClient {
203
+ async list(projectIdOrName) {
204
+ return (await this.client.get(this.makeUrl(`${encodeURIComponent(projectIdOrName)}/_apis/git/repositories`)).json())?.value;
205
+ }
206
+ async get(projectIdOrName, repositoryIdOrName) {
207
+ try {
208
+ return await this.client.get(this.makeUrl(`${encodeURIComponent(projectIdOrName)}/_apis/git/repositories/${encodeURIComponent(repositoryIdOrName)}`)).json();
209
+ } catch (e) {
210
+ if (isHTTPError(e) && e.response.status === 404) return;
211
+ throw e;
212
+ }
213
+ }
214
+ /**
215
+ * Get the list of refs (a.k.a branch names) for a repository.
216
+ * Requires scope "Code (Read)" (vso.code).
217
+ * @param projectIdOrName
218
+ * @param repositoryIdOrName
219
+ * @returns
220
+ */
221
+ async getRefs(projectIdOrName, repositoryIdOrName) {
222
+ return (await this.client.get(this.makeUrl(`${projectIdOrName}/_apis/git/repositories/${repositoryIdOrName}/refs`)).json())?.value;
223
+ }
224
+ async getBranchStats(projectIdOrName, repositoryIdOrName, branchName) {
225
+ return await this.client.get(this.makeUrl(`${projectIdOrName}/_apis/git/repositories/${repositoryIdOrName}/stats/branches`, { name: branchName })).json();
226
+ }
227
+ };
228
+
229
+ //#endregion
230
+ //#region src/azure/client/client-subscriptions.ts
231
+ var HookSubscriptionsClient = class extends BaseAzureDevOpsClient {
232
+ async query(query) {
233
+ return (await this.client.post(this.makeUrl("_apis/hooks/subscriptionsquery"), { json: query }).json())?.results;
234
+ }
235
+ async create(subscription) {
236
+ return await this.client.post(this.makeUrl("_apis/hooks/subscriptions"), { json: subscription }).json();
237
+ }
238
+ async replace(subscriptionId, subscription) {
239
+ return await this.client.put(this.makeUrl(`_apis/hooks/subscriptions/${subscriptionId}`), { json: subscription }).json();
240
+ }
241
+ async delete(subscriptionId) {
242
+ await this.client.delete(this.makeUrl(`_apis/hooks/subscriptions/${subscriptionId}`)).json();
243
+ }
244
+ };
245
+
246
+ //#endregion
247
+ //#region src/azure/client/client.ts
248
+ var AzureDevOpsClient = class AzureDevOpsClient {
249
+ organizationSlug;
250
+ organizationUrl;
251
+ connection;
252
+ identity;
253
+ projects;
254
+ repositories;
255
+ git;
256
+ pullRequests;
257
+ subscriptions;
258
+ constructor(url, accessToken, debug = false) {
259
+ this.organizationSlug = url.organisation;
260
+ const organizationUrl = url.value.toString().replace(/\/$/, "");
261
+ this.organizationUrl = organizationUrl;
262
+ const mainClientOptions = AzureDevOpsClient.createClientOptions(accessToken, debug, { prefixUrl: organizationUrl });
263
+ const mainClient = ky.create(mainClientOptions);
264
+ this.connection = new ConnectionClient(mainClient);
265
+ this.projects = new ProjectsClient(mainClient);
266
+ this.repositories = new RepositoriesClient(mainClient);
267
+ this.git = new GitClient(mainClient);
268
+ this.pullRequests = new PullRequestsClient(mainClient);
269
+ this.subscriptions = new HookSubscriptionsClient(mainClient);
270
+ const identityApiUrl = url["identity-api-url"].toString().replace(/\/$/, "");
271
+ this.identity = new IdentityClient(ky.create({
272
+ ...mainClientOptions,
273
+ prefixUrl: identityApiUrl
274
+ }));
275
+ }
276
+ static createClientOptions(accessToken, debug, options) {
277
+ return {
278
+ headers: {
279
+ Authorization: `Basic ${Buffer.from(`:${accessToken}`).toString("base64")}`,
280
+ Accept: "application/json"
281
+ },
282
+ hooks: {
283
+ beforeRequest: [async (request, options$1) => {
284
+ if (debug) logger.debug(`🌎 🠊 [${request.method}] ${request.url}`);
285
+ }],
286
+ afterResponse: [async (request, options$1, response) => {
287
+ if (debug) {
288
+ logger.debug(`🌎 🠈 [${response.status}] ${response.statusText}`);
289
+ if (request.body) logger.debug(`REQUEST: ${JSON.stringify(request.body)}`);
290
+ }
291
+ }],
292
+ beforeRetry: [async ({ request, options: options$1, error, retryCount }) => {
293
+ if (debug && isHTTPError(error)) logger.debug(`⏳ Retrying failed request with status code: ${error.response.status}`);
294
+ }]
295
+ },
296
+ retry: {
297
+ limit: 3,
298
+ delay: (attempt) => 3e3
299
+ },
300
+ ...options
301
+ };
302
+ }
303
+ };
304
+
305
+ //#endregion
306
+ //#region src/azure/client/types.ts
307
+ const AzdoVersionControlChangeTypeSchema = z.enum([
308
+ "none",
309
+ "add",
310
+ "edit",
311
+ "encoding",
312
+ "rename",
313
+ "delete",
314
+ "undelete",
315
+ "branch",
316
+ "merge",
317
+ "lock",
318
+ "rollback",
319
+ "sourceRename",
320
+ "targetRename",
321
+ "property",
322
+ "all"
323
+ ]);
324
+ const AZDO_PULL_REQUEST_MERGE_STRATEGIES = [
325
+ "noFastForward",
326
+ "squash",
327
+ "rebase",
328
+ "rebaseMerge"
329
+ ];
330
+ const AzdoPullRequestMergeStrategySchema = z.enum(AZDO_PULL_REQUEST_MERGE_STRATEGIES);
331
+ const AzdoCommentThreadStatusSchema = z.enum([
332
+ "unknown",
333
+ "active",
334
+ "fixed",
335
+ "wontFix",
336
+ "closed",
337
+ "byDesign",
338
+ "pending"
339
+ ]);
340
+ const AzdoCommentTypeSchema = z.enum([
341
+ "unknown",
342
+ "text",
343
+ "codeChange",
344
+ "system"
345
+ ]);
346
+ const AzdoPullRequestAsyncStatusSchema = z.enum([
347
+ "notSet",
348
+ "queued",
349
+ "conflicts",
350
+ "succeeded",
351
+ "rejectedByPolicy",
352
+ "failure"
353
+ ]);
354
+ const AzdoPullRequestStatusSchema = z.enum([
355
+ "notSet",
356
+ "active",
357
+ "abandoned",
358
+ "completed",
359
+ "all"
360
+ ]);
361
+ const AzdoProjectSchema = z.object({
362
+ id: z.string(),
363
+ name: z.string(),
364
+ description: z.string().optional(),
365
+ url: z.string(),
366
+ state: z.enum([
367
+ "deleting",
368
+ "new",
369
+ "wellFormed",
370
+ "createPending",
371
+ "all",
372
+ "unchanged",
373
+ "deleted"
374
+ ]),
375
+ _links: z.object({
376
+ self: z.object({ href: z.string() }),
377
+ collection: z.object({ href: z.string() }),
378
+ web: z.object({ href: z.string() })
379
+ }).optional()
380
+ });
381
+ const AzdoRepositorySchema = z.object({
382
+ id: z.string(),
383
+ name: z.string(),
384
+ defaultBranch: z.string().optional(),
385
+ project: AzdoProjectSchema,
386
+ isDisabled: z.boolean().optional(),
387
+ isFork: z.boolean().optional(),
388
+ url: z.string(),
389
+ remoteUrl: z.string(),
390
+ webUrl: z.string()
391
+ });
392
+ const AzdoIdentitySchema = z.object({
393
+ id: z.string(),
394
+ displayName: z.string(),
395
+ url: z.string()
396
+ });
397
+ const AzdoIdentityRefSchema = z.object({
398
+ id: z.string().optional(),
399
+ displayName: z.string().optional(),
400
+ uniqueName: z.string().optional(),
401
+ url: z.string().optional()
402
+ });
403
+ const AzdoConnectionDataSchema = z.object({
404
+ authenticatedUser: AzdoIdentitySchema,
405
+ authorizedUser: AzdoIdentitySchema
406
+ });
407
+ const AzdoGitUserDateSchema = z.object({
408
+ name: z.string(),
409
+ email: z.string(),
410
+ date: z.string().optional()
411
+ });
412
+ const AzdoGitRefSchema = z.object({
413
+ name: z.string(),
414
+ objectId: z.string(),
415
+ isLocked: z.boolean().optional()
416
+ });
417
+ const AzdoGitRefUpdateResultSchema = AzdoGitRefSchema.extend({
418
+ oldObjectId: z.string(),
419
+ newObjectId: z.string(),
420
+ success: z.boolean(),
421
+ customMessage: z.string().optional()
422
+ });
423
+ const AzdoGitChangeSchema = z.object({
424
+ changeType: AzdoVersionControlChangeTypeSchema,
425
+ item: z.object({ path: z.string() }).optional(),
426
+ newContent: z.object({
427
+ content: z.string(),
428
+ contentType: z.enum(["rawtext", "base64encoded"])
429
+ }).optional(),
430
+ originalPath: z.string().optional()
431
+ });
432
+ const AzdoGitCommitRefSchema = z.object({
433
+ commitId: z.string(),
434
+ author: AzdoGitUserDateSchema.optional(),
435
+ committer: AzdoGitUserDateSchema.optional(),
436
+ changes: AzdoGitChangeSchema.array().optional()
437
+ });
438
+ const AzdoGitPushSchema = z.object({
439
+ commits: AzdoGitCommitRefSchema.array(),
440
+ refUpdates: AzdoGitRefSchema.array()
441
+ });
442
+ const AzdoGitRefUpdateSchema = z.object({
443
+ name: z.string(),
444
+ oldObjectId: z.string(),
445
+ newObjectId: z.string().optional(),
446
+ isLocked: z.boolean().optional()
447
+ });
448
+ const AzdoGitPushCreateSchema = z.object({
449
+ refUpdates: AzdoGitRefUpdateSchema.array(),
450
+ commits: z.object({
451
+ comment: z.string(),
452
+ author: AzdoGitUserDateSchema.optional(),
453
+ changes: AzdoGitChangeSchema.array()
454
+ }).array()
455
+ });
456
+ const AzdoGitBranchStatsSchema = z.object({
457
+ aheadCount: z.number(),
458
+ behindCount: z.number()
459
+ });
460
+ const AzdoGitCommitDiffsSchema = z.object({
461
+ allChangesIncluded: z.boolean(),
462
+ baseCommit: z.string(),
463
+ changes: AzdoGitChangeSchema.array(),
464
+ targetCommit: z.string()
465
+ });
466
+ const AzdoRepositoryItemSchema = z.object({
467
+ latestProcessedChange: AzdoGitCommitRefSchema.optional(),
468
+ content: z.string().optional()
469
+ });
470
+ const AzdoIdentityRefWithVoteSchema = z.object({
471
+ id: z.string().optional(),
472
+ displayName: z.string().optional(),
473
+ vote: z.number().optional(),
474
+ hasDeclined: z.boolean().optional(),
475
+ isFlagged: z.boolean().optional(),
476
+ isRequired: z.boolean().optional()
477
+ });
478
+ const AzdoPullRequestSchema = z.object({
479
+ pullRequestId: z.number(),
480
+ status: AzdoPullRequestStatusSchema,
481
+ isDraft: z.boolean(),
482
+ sourceRefName: z.string(),
483
+ targetRefName: z.string(),
484
+ title: z.string(),
485
+ description: z.string().optional(),
486
+ lastMergeCommit: AzdoGitCommitRefSchema,
487
+ lastMergeSourceCommit: AzdoGitCommitRefSchema,
488
+ mergeStatus: AzdoPullRequestAsyncStatusSchema,
489
+ reviewers: AzdoIdentityRefWithVoteSchema.array().optional(),
490
+ workItemRefs: z.object({ id: z.string() }).array().optional(),
491
+ labels: z.object({ name: z.string() }).array().optional(),
492
+ autoCompleteSetBy: AzdoIdentityRefSchema.optional(),
493
+ completionOptions: z.object({
494
+ autoCompleteIgnoreConfigIds: z.number().array().optional(),
495
+ deleteSourceBranch: z.boolean().optional(),
496
+ mergeCommitMessage: z.string().optional(),
497
+ mergeStrategy: AzdoPullRequestMergeStrategySchema.optional(),
498
+ transitionWorkItems: z.boolean().optional()
499
+ }).optional(),
500
+ closedBy: AzdoIdentityRefSchema.optional()
501
+ });
502
+ const AzdoPropertiesSchema = z.record(z.string(), z.object({
503
+ $type: z.string(),
504
+ $value: z.string()
505
+ }));
506
+ const AzdoPullRequestCommentSchema = z.object({
507
+ id: z.number().optional(),
508
+ parentCommentId: z.number().optional(),
509
+ content: z.string(),
510
+ commentType: AzdoCommentTypeSchema,
511
+ publishedDate: z.string().optional(),
512
+ author: AzdoIdentityRefSchema
513
+ });
514
+ const AzdoPullRequestCommentThreadSchema = z.object({
515
+ id: z.number(),
516
+ comments: AzdoPullRequestCommentSchema.array(),
517
+ status: AzdoCommentThreadStatusSchema
518
+ });
519
+ const AzdoSubscriptionSchema = z.object({
520
+ id: z.string(),
521
+ status: z.enum([
522
+ "enabled",
523
+ "onProbation",
524
+ "disabledByUser",
525
+ "disabledBySystem",
526
+ "disabledByInactiveIdentity"
527
+ ]),
528
+ publisherId: z.string(),
529
+ publisherInputs: z.record(z.string(), z.string()),
530
+ consumerId: z.string().optional(),
531
+ consumerActionId: z.string().optional(),
532
+ consumerInputs: z.record(z.string(), z.string()),
533
+ eventType: z.string(),
534
+ resourceVersion: z.string(),
535
+ eventDescription: z.string().optional(),
536
+ actionDescription: z.string().optional()
537
+ });
538
+ const AzdoSubscriptionsQueryResponseSchema = z.object({ results: AzdoSubscriptionSchema.array() });
539
+ const AzdoSubscriptionsQueryInputFilterSchema = z.object({ conditions: z.object({
540
+ caseSensitive: z.boolean().optional(),
541
+ inputId: z.string().optional(),
542
+ inputValue: z.string().optional(),
543
+ operator: z.enum(["equals", "notEquals"])
544
+ }).array().optional() });
545
+ const AzdoSubscriptionsQuerySchema = z.object({
546
+ consumerActionId: z.string().optional(),
547
+ consumerId: z.string().optional(),
548
+ consumerInputFilters: AzdoSubscriptionsQueryInputFilterSchema.array().optional(),
549
+ eventType: z.string().optional(),
550
+ publisherId: z.string().optional(),
551
+ publisherInputFilters: AzdoSubscriptionsQueryInputFilterSchema.array().optional(),
552
+ subscriberId: z.string().optional()
553
+ });
554
+
555
+ //#endregion
556
+ //#region src/azure/client/utils.ts
94
557
  function buildPullRequestProperties(packageManager, dependencies) {
95
558
  return [{
96
- name: DEVOPS_PR_PROPERTY_DEPENDABOT_PACKAGE_MANAGER,
559
+ name: PR_PROPERTY_DEPENDABOT_PACKAGE_MANAGER,
97
560
  value: packageManager
98
561
  }, {
99
- name: DEVOPS_PR_PROPERTY_DEPENDABOT_DEPENDENCIES,
562
+ name: PR_PROPERTY_DEPENDABOT_DEPENDENCIES,
100
563
  value: JSON.stringify(dependencies)
101
564
  }];
102
565
  }
103
566
  function parsePullRequestProperties(pullRequests, packageManager) {
104
567
  return Object.fromEntries(pullRequests.filter((pr) => {
105
- return pr.properties?.find((p) => p.name === DEVOPS_PR_PROPERTY_DEPENDABOT_PACKAGE_MANAGER && (packageManager === null || p.value === packageManager));
568
+ return pr.properties?.find((p) => p.name === PR_PROPERTY_DEPENDABOT_PACKAGE_MANAGER && (packageManager === null || p.value === packageManager));
106
569
  }).map((pr) => {
107
- return [pr.id, DependenciesPrPropertySchema.parse(JSON.parse(pr.properties.find((p) => p.name === DEVOPS_PR_PROPERTY_DEPENDABOT_DEPENDENCIES).value))];
570
+ return [pr.pullRequestId, DependabotPersistedPrSchema.parse(JSON.parse(pr.properties.find((p) => p.name === PR_PROPERTY_DEPENDABOT_DEPENDENCIES).value))];
108
571
  }));
109
572
  }
110
573
  function getPullRequestForDependencyNames(existingPullRequests, packageManager, dependencyNames) {
111
574
  return existingPullRequests.find((pr) => {
112
- return pr.properties?.find((p) => p.name === DEVOPS_PR_PROPERTY_DEPENDABOT_PACKAGE_MANAGER && p.value === packageManager) && pr.properties?.find((p) => p.name === DEVOPS_PR_PROPERTY_DEPENDABOT_DEPENDENCIES && areEqual(getDependencyNames(DependenciesPrPropertySchema.parse(JSON.parse(p.value))), dependencyNames));
575
+ return pr.properties?.find((p) => p.name === PR_PROPERTY_DEPENDABOT_PACKAGE_MANAGER && p.value === packageManager) && pr.properties?.find((p) => p.name === PR_PROPERTY_DEPENDABOT_DEPENDENCIES && areEqual(getDependencyNames(DependabotPersistedPrSchema.parse(JSON.parse(p.value))), dependencyNames));
113
576
  });
114
577
  }
115
- function getDependencyNames(dependencies) {
116
- return (Array.isArray(dependencies) ? dependencies : dependencies.dependencies).map((dep) => dep["dependency-name"]?.toString());
117
- }
118
- function areEqual(a, b) {
119
- if (a.length !== b.length) return false;
120
- return a.every((name) => b.includes(name));
121
- }
122
- function getPullRequestChangedFilesForOutputData(data) {
578
+ function getPullRequestChangedFiles(data) {
123
579
  return data["updated-dependency-files"].filter((file) => file.type === "file").map((file) => {
124
- let changeType = VersionControlChangeType.None;
125
- if (file.deleted === true || file.operation === "delete") changeType = VersionControlChangeType.Delete;
126
- else if (file.operation === "update") changeType = VersionControlChangeType.Edit;
127
- else changeType = VersionControlChangeType.Add;
580
+ let changeType = "none";
581
+ if (file.deleted === true || file.operation === "delete") changeType = "delete";
582
+ else if (file.operation === "update") changeType = "edit";
583
+ else changeType = "add";
128
584
  return {
129
585
  changeType,
130
586
  path: path.join(file.directory, file.name),
@@ -133,230 +589,91 @@ function getPullRequestChangedFilesForOutputData(data) {
133
589
  };
134
590
  });
135
591
  }
136
- function getPullRequestCloseReasonForOutputData(data) {
137
- const leadDependencyName = data["dependency-names"][0];
138
- let reason;
139
- switch (data.reason) {
140
- case "dependencies_changed":
141
- reason = `Looks like the dependencies have changed`;
142
- break;
143
- case "dependency_group_empty":
144
- reason = `Looks like the dependencies in this group are now empty`;
145
- break;
146
- case "dependency_removed":
147
- reason = `Looks like ${leadDependencyName} is no longer a dependency`;
148
- break;
149
- case "up_to_date":
150
- reason = `Looks like ${leadDependencyName} is up-to-date now`;
151
- break;
152
- case "update_no_longer_possible":
153
- reason = `Looks like ${leadDependencyName} can no longer be updated`;
154
- break;
155
- }
156
- if (reason && reason.length > 0) reason += ", so this is no longer needed.";
157
- return reason;
158
- }
159
- function getPullRequestDependenciesPropertyValueForOutputData(data) {
160
- const dependencies = data.dependencies?.map((dep) => {
161
- return {
162
- "dependency-name": dep.name,
163
- "dependency-version": dep.version,
164
- directory: dep.directory
165
- };
166
- });
167
- const dependencyGroupName = data["dependency-group"]?.name;
168
- if (!dependencyGroupName) return dependencies;
169
- return {
170
- "dependency-group-name": dependencyGroupName,
171
- dependencies
172
- };
173
- }
174
- function getPullRequestDescription(packageManager, body, dependencies) {
175
- let header = "";
176
- const footer = "";
177
- const description = (body || "").replace(new RegExp(decodeURIComponent("%EF%BF%BD%EF%BF%BD%EF%BF%BD"), "g"), "");
178
- if (dependencies.length === 1) {
179
- const compatibilityScoreBadges = dependencies.map((dep) => {
180
- return `![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=${dep.name}&package-manager=${packageManager}&previous-version=${dep["previous-version"]}&new-version=${dep.version})`;
181
- });
182
- header += `${compatibilityScoreBadges.join(" ")}\n\n`;
183
- }
184
- const maxDescriptionLengthAfterHeaderAndFooter = 4e3 - header.length - 0;
185
- return `${header}${description.substring(0, maxDescriptionLengthAfterHeaderAndFooter)}${footer}`;
186
- }
187
592
 
188
593
  //#endregion
189
- //#region src/azure/client.ts
190
- /** Returned from AzureDevOpsWebApiClient.getUserId() when no user is authenticated */
191
- const ANONYMOUS_USER_ID = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa";
192
- /** Azure DevOps REST API client. */
193
- var AzureDevOpsWebApiClient = class AzureDevOpsWebApiClient {
194
- organisationApiUrl;
195
- identityApiUrl;
196
- accessToken;
197
- debug;
594
+ //#region src/azure/client/wrapper.ts
595
+ var AzureDevOpsClientWrapper = class {
596
+ inner;
198
597
  authenticatedUserId;
199
- resolvedUserIds;
200
- static API_VERSION = "5.0";
201
- static API_VERSION_PREVIEW = "5.0-preview";
598
+ resolvedUserIds = {};
599
+ /**
600
+ * Create a new Azure DevOps client wrapper.
601
+ * @param url The Azure DevOps organization URL
602
+ * @param accessToken The personal access token for authentication
603
+ * @param debug Enable debug logging for API requests (default: false)
604
+ */
202
605
  constructor(url, accessToken, debug = false) {
203
- const organisationApiUrl = url.value.toString();
204
- this.organisationApiUrl = organisationApiUrl.replace(/\/$/, "");
205
- this.identityApiUrl = getIdentityApiUrl(organisationApiUrl).replace(/\/$/, "");
206
- this.accessToken = accessToken;
207
- this.debug = debug;
208
- this.resolvedUserIds = {};
606
+ this.inner = new AzureDevOpsClient(url, accessToken, debug);
209
607
  }
210
608
  /**
211
609
  * Get the identity of the authenticated user.
212
- * @returns
610
+ * The result is cached after the first call to avoid repeated API requests.
213
611
  */
214
612
  async getUserId() {
215
613
  if (!this.authenticatedUserId) {
216
- this.authenticatedUserId = (await this.restApiGet(`${this.organisationApiUrl}/_apis/connectiondata`, void 0, AzureDevOpsWebApiClient.API_VERSION_PREVIEW))?.authenticatedUser?.id;
614
+ this.authenticatedUserId = (await this.inner.connection.get())?.authenticatedUser?.id;
217
615
  if (!this.authenticatedUserId) throw new Error("Failed to get authenticated user ID");
218
616
  }
219
617
  return this.authenticatedUserId;
220
618
  }
221
619
  /**
222
620
  * Get the identity id from a user name, email, or group name.
621
+ * Results are cached to avoid repeated API requests for the same identifier.
622
+ *
223
623
  * Requires scope "Identity (Read)" (vso.identity).
224
- * @param userNameEmailOrGroupName
225
- * @returns
624
+ * @param identifier Username, email, or group name to resolve
625
+ * @returns The resolved identity ID, or undefined if not found or on error
226
626
  */
227
- async resolveIdentityId(userNameEmailOrGroupName) {
228
- if (this.resolvedUserIds[userNameEmailOrGroupName]) return this.resolvedUserIds[userNameEmailOrGroupName];
627
+ async resolveIdentityId(identifier) {
628
+ if (this.resolvedUserIds[identifier]) return this.resolvedUserIds[identifier];
229
629
  try {
230
- const identities = await this.restApiGet(`${this.identityApiUrl}/_apis/identities`, {
231
- searchFilter: "General",
232
- filterValue: userNameEmailOrGroupName,
233
- queryMembership: "None"
234
- });
235
- if (!identities?.value || identities.value.length === 0) return;
236
- this.resolvedUserIds[userNameEmailOrGroupName] = identities.value[0]?.id;
237
- return this.resolvedUserIds[userNameEmailOrGroupName];
630
+ const identities = await this.inner.identity.get(identifier);
631
+ if (!identities || identities.length === 0) return;
632
+ this.resolvedUserIds[identifier] = identities[0].id;
633
+ return this.resolvedUserIds[identifier];
238
634
  } catch (e) {
239
635
  logger.error(`Failed to resolve user id: ${e}`);
240
636
  logger.debug(e);
241
637
  return;
242
638
  }
243
639
  }
244
- async getProjects() {
245
- try {
246
- return await this.restApiGet(`${this.organisationApiUrl}/_apis/projects`);
247
- } catch (e) {
248
- logger.error(`Failed to get projects: ${e}`);
249
- logger.debug(e);
250
- return;
251
- }
252
- }
253
- async getProject(idOrName) {
254
- try {
255
- return await this.restApiGet(`${this.organisationApiUrl}/_apis/projects/${encodeURIComponent(idOrName)}`);
256
- } catch (e) {
257
- logger.error(`Failed to get project: ${e}`);
258
- logger.debug(e);
259
- return;
260
- }
261
- }
262
- async getRepositories(projectIdOrName) {
263
- try {
264
- return await this.restApiGet(`${this.organisationApiUrl}/${encodeURIComponent(projectIdOrName)}/_apis/git/repositories`);
265
- } catch (e) {
266
- logger.error(`Failed to get repositories: ${e}`);
267
- logger.debug(e);
268
- return;
269
- }
270
- }
271
- async getRepository(projectIdOrName, repositoryIdOrName) {
272
- try {
273
- return await this.restApiGet(`${this.organisationApiUrl}/${encodeURIComponent(projectIdOrName)}/_apis/git/repositories/${encodeURIComponent(repositoryIdOrName)}`);
274
- } catch (e) {
275
- if (e instanceof HttpRequestError && e.code === 404) return;
276
- else {
277
- logger.error(`Failed to get repository: ${e}`);
278
- logger.debug(e);
279
- }
280
- return;
281
- }
282
- }
283
- async getRepositoryItem(projectIdOrName, repositoryIdOrName, path$1, includeContent = true, latestProcessedChange = true) {
284
- try {
285
- return await this.restApiGet(`${this.organisationApiUrl}/${encodeURIComponent(projectIdOrName)}/_apis/git/repositories/${encodeURIComponent(repositoryIdOrName)}/items`, {
286
- path: path$1,
287
- includeContent,
288
- latestProcessedChange
289
- });
290
- } catch (e) {
291
- if (e instanceof HttpRequestError && e.code === 404) return;
292
- else {
293
- logger.error(`Failed to get repository item: ${e}`);
294
- logger.debug(e);
295
- }
296
- return;
297
- }
298
- }
299
640
  /**
300
641
  * Get the default branch for a repository.
642
+ *
301
643
  * Requires scope "Code (Read)" (vso.code).
302
- * @param project
303
- * @param repository
304
- * @returns
644
+ * @param options Repository identification options (project and repository)
645
+ * @returns The normalized default branch name (e.g., "main"), or undefined if not found
305
646
  */
306
- async getDefaultBranch(project, repository) {
307
- try {
308
- const repo = await this.restApiGet(`${this.organisationApiUrl}/${project}/_apis/git/repositories/${repository}`);
309
- if (!repo) throw new Error(`Repository '${project}/${repository}' not found`);
310
- return normalizeBranchName(repo.defaultBranch);
311
- } catch (e) {
312
- logger.error(`Failed to get default branch for '${project}/${repository}': ${e}`);
313
- logger.debug(e);
314
- return;
315
- }
647
+ async getDefaultBranch(options) {
648
+ return normalizeBranchName((await this.inner.repositories.get(options.project, options.repository))?.defaultBranch);
316
649
  }
317
650
  /**
318
651
  * Get the list of branch names for a repository.
652
+ *
319
653
  * Requires scope "Code (Read)" (vso.code).
320
- * @param project
321
- * @param repository
322
- * @returns
654
+ * @param options Repository identification options (project and repository)
655
+ * @returns Array of normalized branch names, or undefined if not found
323
656
  */
324
- async getBranchNames(project, repository) {
325
- try {
326
- const refs = await this.restApiGet(`${this.organisationApiUrl}/${project}/_apis/git/repositories/${repository}/refs`);
327
- if (!refs) throw new Error(`Repository '${project}/${repository}' not found`);
328
- return refs.value?.map((r) => normalizeBranchName(r.name)) || [];
329
- } catch (e) {
330
- logger.error(`Failed to list branch names for '${project}/${repository}': ${e}`);
331
- logger.debug(e);
332
- return;
333
- }
657
+ async getBranchNames(options) {
658
+ return (await this.inner.repositories.getRefs(options.project, options.repository))?.map((r) => normalizeBranchName(r.name));
334
659
  }
335
660
  /**
336
- * Get the properties for all active pull request created by the supplied user.
661
+ * Get the properties for all active pull requests created by the specified user.
662
+ * This retrieves both the pull request IDs and their associated properties.
663
+ *
337
664
  * Requires scope "Code (Read)" (vso.code).
338
- * @param project
339
- * @param repository
340
- * @param creator
341
- * @returns
665
+ * @param options Repository identification options including the creator user ID
666
+ * @returns Array of pull request IDs with their properties, empty array on error
342
667
  */
343
- async getActivePullRequestProperties(project, repository, creator) {
668
+ async getActivePullRequestProperties({ project, repository, creatorId }) {
344
669
  try {
345
- const pullRequests = await this.restApiGet(`${this.organisationApiUrl}/${project}/_apis/git/repositories/${repository}/pullrequests`, {
346
- "searchCriteria.creatorId": isGuid(creator) ? creator : await this.getUserId(),
347
- "searchCriteria.status": "Active"
348
- });
349
- if (!pullRequests?.value || pullRequests.value.length === 0) return [];
350
- return await Promise.all(pullRequests.value.map(async (pr) => {
351
- const properties = await this.restApiGet(`${this.organisationApiUrl}/${project}/_apis/git/repositories/${repository}/pullrequests/${pr.pullRequestId}/properties`);
670
+ const pullRequests = await this.inner.pullRequests.list(project, repository, creatorId, "active");
671
+ if (!pullRequests || pullRequests.length === 0) return [];
672
+ return await Promise.all(pullRequests.map(async (pr) => {
673
+ const properties = await this.inner.pullRequests.getProperties(project, repository, pr.pullRequestId);
352
674
  return {
353
- id: pr.pullRequestId,
354
- properties: Object.keys(properties?.value || {}).map((key) => {
355
- return {
356
- name: key,
357
- value: properties.value[key]?.$value
358
- };
359
- }) || []
675
+ pullRequestId: pr.pullRequestId,
676
+ properties
360
677
  };
361
678
  }));
362
679
  } catch (e) {
@@ -366,38 +683,45 @@ var AzureDevOpsWebApiClient = class AzureDevOpsWebApiClient {
366
683
  }
367
684
  }
368
685
  /**
369
- * Create a new pull request.
686
+ * Create a new pull request with the specified changes.
687
+ * This method performs the following operations:
688
+ * 1. Resolves assignee identities for assignees (if specified)
689
+ * 2. Creates a new branch and pushes changes
690
+ * 3. Creates the pull request with assignees (optional reviewers), labels, and work items
691
+ * 4. Sets pull request properties for dependency metadata
692
+ * 5. Configures auto-complete options (if specified)
693
+ *
370
694
  * Requires scope "Code (Write)" (vso.code_write).
371
695
  * Requires scope "Identity (Read)" (vso.identity), if assignees are specified.
372
- * @param pr
373
- * @returns
696
+ * @param options Pull request creation options including changes, assignees, and auto-complete settings
697
+ * @returns The created pull request ID, or null on error
374
698
  */
375
- async createPullRequest(pr) {
376
- logger.info(`Creating pull request '${pr.title}'...`);
699
+ async createPullRequest(options) {
700
+ logger.info(`Creating pull request '${options.title}'...`);
377
701
  try {
378
702
  const userId = await this.getUserId();
379
703
  const reviewers = [];
380
- if (pr.assignees && pr.assignees.length > 0) for (const assignee of pr.assignees) {
381
- const identityId = isGuid(assignee) ? assignee : await this.resolveIdentityId(assignee);
704
+ if (options.assignees && options.assignees.length > 0) for (const assignee of options.assignees) {
705
+ const identityId = this.isGuid(assignee) ? assignee : await this.resolveIdentityId(assignee);
382
706
  if (identityId && !reviewers.some((r) => r.id === identityId)) reviewers.push({ id: identityId });
383
707
  else logger.warn(`Unable to resolve assignee identity '${assignee}'`);
384
708
  }
385
- logger.info(` - Pushing ${pr.changes.length} file change(s) to branch '${pr.source.branch}'...`);
386
- const push = await this.restApiPost(`${this.organisationApiUrl}/${pr.project}/_apis/git/repositories/${pr.repository}/pushes`, {
709
+ logger.info(` - Pushing ${options.changes.length} file change(s) to branch '${options.source.branch}'...`);
710
+ const push = await this.inner.git.createPush(options.project, options.repository, {
387
711
  refUpdates: [{
388
- name: `refs/heads/${pr.source.branch}`,
389
- oldObjectId: pr.source.commit
712
+ name: `refs/heads/${options.source.branch}`,
713
+ oldObjectId: options.source.commit
390
714
  }],
391
715
  commits: [{
392
- comment: pr.commitMessage,
393
- author: pr.author,
394
- changes: pr.changes.filter((change) => change.changeType !== VersionControlChangeType.None).map(({ changeType, ...change }) => {
716
+ comment: options.commitMessage,
717
+ author: options.author,
718
+ changes: options.changes.filter((change) => change.changeType !== "none").map(({ changeType, ...change }) => {
395
719
  return {
396
720
  changeType,
397
721
  item: { path: normalizeFilePath(change.path) },
398
- newContent: changeType !== VersionControlChangeType.Delete ? {
722
+ newContent: changeType !== "delete" ? {
399
723
  content: Buffer.from(change.content, change.encoding).toString("base64"),
400
- contentType: ItemContentType.Base64Encoded
724
+ contentType: "base64encoded"
401
725
  } : void 0
402
726
  };
403
727
  })
@@ -405,37 +729,31 @@ var AzureDevOpsWebApiClient = class AzureDevOpsWebApiClient {
405
729
  });
406
730
  if (!push?.commits?.length) throw new Error("Failed to push changes to source branch, no commits were created");
407
731
  logger.info(` - Pushed commit: ${push.commits.map((c) => c.commitId).join(", ")}.`);
408
- logger.info(` - Creating pull request to merge '${pr.source.branch}' into '${pr.target.branch}'...`);
409
- const pullRequest = await this.restApiPost(`${this.organisationApiUrl}/${pr.project}/_apis/git/repositories/${pr.repository}/pullrequests`, {
410
- sourceRefName: `refs/heads/${pr.source.branch}`,
411
- targetRefName: `refs/heads/${pr.target.branch}`,
412
- title: pr.title,
413
- description: pr.description,
732
+ logger.info(` - Creating pull request to merge '${options.source.branch}' into '${options.target.branch}'...`);
733
+ const pullRequest = await this.inner.pullRequests.create(options.project, options.repository, {
734
+ sourceRefName: `refs/heads/${options.source.branch}`,
735
+ targetRefName: `refs/heads/${options.target.branch}`,
736
+ title: options.title,
737
+ description: options.description,
414
738
  reviewers,
415
- workItemRefs: pr.workItems?.map((id) => ({ id })),
416
- labels: pr.labels?.map((label) => ({ name: label }))
739
+ workItemRefs: options.workItems?.map((id) => ({ id })),
740
+ labels: options.labels?.map((label) => ({ name: label }))
417
741
  });
418
742
  if (!pullRequest?.pullRequestId) throw new Error("Failed to create pull request, no pull request id was returned");
419
743
  logger.info(` - Created pull request: #${pullRequest.pullRequestId}.`);
420
- if (pr.properties && pr.properties.length > 0) {
744
+ if (options.properties && options.properties.length > 0) {
421
745
  logger.info(` - Adding dependency metadata to pull request properties...`);
422
- if (!(await this.restApiPatch(`${this.organisationApiUrl}/${pr.project}/_apis/git/repositories/${pr.repository}/pullrequests/${pullRequest.pullRequestId}/properties`, pr.properties.map((property) => {
423
- return {
424
- op: "add",
425
- path: `/${property.name}`,
426
- value: property.value
427
- };
428
- }), "application/json-patch+json"))?.count) throw new Error("Failed to add dependency metadata properties to pull request");
746
+ if (!(await this.inner.pullRequests.setProperties(options.project, options.repository, pullRequest.pullRequestId, options.properties))?.count) throw new Error("Failed to add dependency metadata properties to pull request");
429
747
  }
430
- if (pr.autoComplete) {
748
+ if (options.autoComplete) {
431
749
  logger.info(` - Updating auto-complete options...`);
432
- const updatedPullRequest = await this.restApiPatch(`${this.organisationApiUrl}/${pr.project}/_apis/git/repositories/${pr.repository}/pullrequests/${pullRequest.pullRequestId}`, {
750
+ const updatedPullRequest = await this.inner.pullRequests.update(options.project, options.repository, pullRequest.pullRequestId, {
433
751
  autoCompleteSetBy: { id: userId },
434
752
  completionOptions: {
435
- autoCompleteIgnoreConfigIds: pr.autoComplete.ignorePolicyConfigIds,
753
+ autoCompleteIgnoreConfigIds: options.autoComplete.ignorePolicyConfigIds,
436
754
  deleteSourceBranch: true,
437
- mergeCommitMessage: mergeCommitMessage(pullRequest.pullRequestId, pr.title, pr.description),
438
- mergeStrategy: pr.autoComplete.mergeStrategy,
755
+ mergeCommitMessage: this.mergeCommitMessage(pullRequest.pullRequestId, options.title, options.description),
756
+ mergeStrategy: options.autoComplete.mergeStrategy,
439
757
  transitionWorkItems: false
440
758
  }
441
759
  });
@@ -450,29 +768,29 @@ var AzureDevOpsWebApiClient = class AzureDevOpsWebApiClient {
450
768
  }
451
769
  }
452
770
  /**
453
- * Update a pull request.
771
+ * Update an existing pull request with new changes.
772
+ * This method performs the following operations:
773
+ * 1. Validates the pull request hasn't been modified by another author
774
+ * 2. Checks if the source branch is behind the target branch
775
+ * 3. Rebases the target branch into the source branch if needed
776
+ * 4. Pushes the new changes to the source branch
777
+ *
454
778
  * Requires scope "Code (Read & Write)" (vso.code, vso.code_write).
455
- * @param pr
456
- * @returns
779
+ * @param options Pull request update options including the commit and changes
780
+ * @returns True if successful, false on error
457
781
  */
458
- async updatePullRequest(pr) {
459
- logger.info(`Updating pull request #${pr.pullRequestId}...`);
782
+ async updatePullRequest(options) {
783
+ logger.info(`Updating pull request #${options.pullRequestId}...`);
460
784
  try {
461
- const pullRequest = await this.restApiGet(`${this.organisationApiUrl}/${pr.project}/_apis/git/repositories/${pr.repository}/pullrequests/${pr.pullRequestId}`);
462
- if (!pullRequest) throw new Error(`Pull request #${pr.pullRequestId} not found`);
463
- if (pr.skipIfDraft && pullRequest.isDraft) {
464
- logger.info(` - Skipping update as pull request is currently marked as a draft.`);
785
+ const pullRequest = await this.inner.pullRequests.get(options.project, options.repository, options.pullRequestId);
786
+ if (!pullRequest) throw new Error(`Pull request #${options.pullRequestId} not found`);
787
+ if ((await this.inner.pullRequests.getCommits(options.project, options.repository, options.pullRequestId))?.some((c) => c.author?.email !== options.author.email)) {
788
+ logger.info(` - Skipping update as pull request has been modified by another user.`);
465
789
  return true;
466
790
  }
467
- if (pr.skipIfCommitsFromAuthorsOtherThan) {
468
- if ((await this.restApiGet(`${this.organisationApiUrl}/${pr.project}/_apis/git/repositories/${pr.repository}/pullrequests/${pr.pullRequestId}/commits`))?.value?.some((c) => c.author?.email !== pr.skipIfCommitsFromAuthorsOtherThan)) {
469
- logger.info(` - Skipping update as pull request has been modified by another user.`);
470
- return true;
471
- }
472
- }
473
- const stats = await this.restApiGet(`${this.organisationApiUrl}/${pr.project}/_apis/git/repositories/${pr.repository}/stats/branches`, { name: normalizeBranchName(pullRequest.sourceRefName) });
791
+ const stats = await this.inner.repositories.getBranchStats(options.project, options.repository, normalizeBranchName(pullRequest.sourceRefName));
474
792
  if (stats?.behindCount === void 0) throw new Error(`Failed to get branch stats for '${pullRequest.sourceRefName}'`);
475
- if (pr.skipIfNotBehindTargetBranch && stats.behindCount === 0) {
793
+ if (stats.behindCount === 0) {
476
794
  logger.info(` - Skipping update as source branch is not behind target branch.`);
477
795
  return true;
478
796
  }
@@ -480,28 +798,28 @@ var AzureDevOpsWebApiClient = class AzureDevOpsWebApiClient {
480
798
  const targetBranchName = normalizeBranchName(pullRequest.targetRefName);
481
799
  if (stats.behindCount > 0) {
482
800
  logger.info(` - Rebasing '${targetBranchName}' into '${sourceBranchName}' (${stats.behindCount} commit(s) behind)...`);
483
- if ((await this.restApiPost(`${this.organisationApiUrl}/${pr.project}/_apis/git/repositories/${pr.repository}/refs`, [{
801
+ if ((await this.inner.git.updateRef(options.project, options.repository, [{
484
802
  name: pullRequest.sourceRefName,
485
803
  oldObjectId: pullRequest.lastMergeSourceCommit.commitId,
486
- newObjectId: pr.commit
487
- }]))?.value?.[0]?.success !== true) throw new Error("Failed to rebase the target branch into the source branch");
804
+ newObjectId: options.commit
805
+ }]))?.[0]?.success !== true) throw new Error("Failed to rebase the target branch into the source branch");
488
806
  }
489
- logger.info(` - Pushing ${pr.changes.length} file change(s) to branch '${pullRequest.sourceRefName}'...`);
490
- const push = await this.restApiPost(`${this.organisationApiUrl}/${pr.project}/_apis/git/repositories/${pr.repository}/pushes`, {
807
+ logger.info(` - Pushing ${options.changes.length} file change(s) to branch '${pullRequest.sourceRefName}'...`);
808
+ const push = await this.inner.git.createPush(options.project, options.repository, {
491
809
  refUpdates: [{
492
810
  name: pullRequest.sourceRefName,
493
- oldObjectId: pr.commit
811
+ oldObjectId: options.commit
494
812
  }],
495
813
  commits: [{
496
- comment: pullRequest.mergeStatus === PullRequestAsyncStatus.Conflicts ? "Resolve merge conflicts" : `Rebase '${sourceBranchName}' onto '${targetBranchName}'`,
497
- author: pr.author,
498
- changes: pr.changes.filter((change) => change.changeType !== VersionControlChangeType.None).map(({ changeType, ...change }) => {
814
+ comment: pullRequest.mergeStatus === "conflicts" ? "Resolve merge conflicts" : `Rebase '${sourceBranchName}' onto '${targetBranchName}'`,
815
+ author: options.author,
816
+ changes: options.changes.filter((change) => change.changeType !== "none").map(({ changeType, ...change }) => {
499
817
  return {
500
818
  changeType,
501
819
  item: { path: normalizeFilePath(change.path) },
502
- newContent: changeType !== VersionControlChangeType.Delete ? {
820
+ newContent: changeType !== "delete" ? {
503
821
  content: Buffer.from(change.content, change.encoding).toString("base64"),
504
- contentType: ItemContentType.Base64Encoded
822
+ contentType: "base64encoded"
505
823
  } : void 0
506
824
  };
507
825
  })
@@ -518,20 +836,19 @@ var AzureDevOpsWebApiClient = class AzureDevOpsWebApiClient {
518
836
  }
519
837
  }
520
838
  /**
521
- * Approve a pull request.
839
+ * Approve a pull request as the authenticated user.
840
+ * Sets the reviewer vote to 10 (approved).
841
+ *
522
842
  * Requires scope "Code (Write)" (vso.code_write).
523
- * @param pr
524
- * @returns
843
+ * @param options Pull request identification options
844
+ * @returns True if successful, false on error
525
845
  */
526
- async approvePullRequest(pr) {
527
- logger.info(`Approving pull request #${pr.pullRequestId}...`);
846
+ async approvePullRequest(options) {
847
+ logger.info(`Approving pull request #${options.pullRequestId}...`);
528
848
  try {
529
849
  logger.info(` - Updating reviewer vote on pull request...`);
530
850
  const userId = await this.getUserId();
531
- if ((await this.restApiPut(`${this.organisationApiUrl}/${pr.project}/_apis/git/repositories/${pr.repository}/pullrequests/${pr.pullRequestId}/reviewers/${userId}`, {
532
- vote: 10,
533
- isReapprove: true
534
- }, "7.1"))?.vote !== 10) throw new Error("Failed to approve pull request, vote was not recorded");
851
+ if ((await this.inner.pullRequests.approve(options.project, options.repository, options.pullRequestId, userId))?.vote !== 10) throw new Error("Failed to approve pull request, vote was not recorded");
535
852
  logger.info(` - Pull request was approved successfully.`);
536
853
  return true;
537
854
  } catch (e) {
@@ -541,37 +858,39 @@ var AzureDevOpsWebApiClient = class AzureDevOpsWebApiClient {
541
858
  }
542
859
  }
543
860
  /**
544
- * Abandon a pull request.
861
+ * Abandon a pull request and optionally delete its source branch.
862
+ * This method performs the following operations:
863
+ * 1. Adds an optional comment explaining the abandonment reason
864
+ * 2. Sets the pull request status to abandoned
865
+ * 3. Deletes the source branch if requested
866
+ *
545
867
  * Requires scope "Code (Write)" (vso.code_write).
546
- * @param pr
547
- * @returns
868
+ * @param options Pull request abandonment options including optional comment and branch deletion flag
869
+ * @returns True if successful, false on error
548
870
  */
549
- async abandonPullRequest(pr) {
550
- logger.info(`Abandoning pull request #${pr.pullRequestId}...`);
871
+ async abandonPullRequest(options) {
872
+ logger.info(`Abandoning pull request #${options.pullRequestId}...`);
551
873
  try {
552
874
  const userId = await this.getUserId();
553
- if (pr.comment) {
875
+ if (options.comment) {
554
876
  logger.info(` - Adding abandonment reason comment to pull request...`);
555
877
  if (!await this.addCommentThread({
556
- ...pr,
557
- content: pr.comment,
878
+ ...options,
879
+ content: options.comment,
558
880
  userId
559
881
  })) throw new Error("Failed to add comment to pull request, thread was not created");
560
882
  }
561
883
  logger.info(` - Abandoning pull request...`);
562
- const abandonedPullRequest = await this.restApiPatch(`${this.organisationApiUrl}/${pr.project}/_apis/git/repositories/${pr.repository}/pullrequests/${pr.pullRequestId}`, {
563
- status: PullRequestStatus.Abandoned,
564
- closedBy: { id: userId }
565
- });
566
- if (abandonedPullRequest?.status?.toLowerCase() !== "abandoned") throw new Error("Failed to abandon pull request, status was not updated");
567
- if (pr.deleteSourceBranch) {
884
+ const abandonedPullRequest = await this.inner.pullRequests.abandon(options.project, options.repository, options.pullRequestId, userId);
885
+ if (abandonedPullRequest?.status !== "abandoned") throw new Error("Failed to abandon pull request, status was not updated");
886
+ if (options.deleteSourceBranch) {
568
887
  logger.info(` - Deleting source branch...`);
569
- if ((await this.restApiPost(`${this.organisationApiUrl}/${pr.project}/_apis/git/repositories/${pr.repository}/refs`, [{
888
+ if ((await this.inner.git.updateRef(options.project, options.repository, [{
570
889
  name: abandonedPullRequest.sourceRefName,
571
890
  oldObjectId: abandonedPullRequest.lastMergeSourceCommit.commitId,
572
891
  newObjectId: "0000000000000000000000000000000000000000",
573
892
  isLocked: false
574
- }]))?.value?.[0]?.success !== true) throw new Error("Failed to delete the source branch");
893
+ }]))?.[0]?.success !== true) throw new Error("Failed to delete the source branch");
575
894
  }
576
895
  logger.info(` - Pull request was abandoned successfully.`);
577
896
  return true;
@@ -583,117 +902,149 @@ var AzureDevOpsWebApiClient = class AzureDevOpsWebApiClient {
583
902
  }
584
903
  /**
585
904
  * Add a comment thread on a pull request.
905
+ * The comment thread is created with a closed status and system comment type.
906
+ *
586
907
  * Requires scope "Code (Write)" (vso.code_write).
908
+ * @param options Comment creation options including content and optional user ID
909
+ * @returns The created thread ID, or undefined on error
587
910
  */
588
- async addCommentThread(comment) {
589
- const userId = comment.userId ?? await this.getUserId();
590
- return (await this.restApiPost(`${this.organisationApiUrl}/${comment.project}/_apis/git/repositories/${comment.repository}/pullrequests/${comment.pullRequestId}/threads`, {
591
- status: CommentThreadStatus.Closed,
911
+ async addCommentThread(options) {
912
+ const userId = options.userId ?? await this.getUserId();
913
+ return (await this.inner.pullRequests.createCommentThread(options.project, options.repository, options.pullRequestId, {
914
+ status: "closed",
592
915
  comments: [{
593
916
  author: { id: userId },
594
- content: comment.content,
595
- commentType: CommentType.System
917
+ content: options.content,
918
+ commentType: "system"
596
919
  }]
597
920
  }))?.id;
598
921
  }
599
- async restApiGet(url, params, apiVersion = AzureDevOpsWebApiClient.API_VERSION) {
600
- params ??= {};
601
- const queryString = Object.keys(params).map((key) => `${key}=${params[key]}`).join("&");
602
- const fullUrl = `${url}?api-version=${apiVersion}${queryString ? `&${queryString}` : ""}`;
603
- return await sendRestApiRequestWithRetry("GET", fullUrl, void 0, async () => {
604
- return await fetch(fullUrl, {
605
- method: "GET",
606
- headers: {
607
- Accept: "application/json",
608
- Authorization: `Basic ${Buffer.from(`:${this.accessToken}`).toString("base64")}`
609
- }
610
- });
611
- }, this.debug);
612
- }
613
- async restApiPost(url, data, apiVersion = AzureDevOpsWebApiClient.API_VERSION) {
614
- const fullUrl = `${url}?api-version=${apiVersion}`;
615
- return await sendRestApiRequestWithRetry("POST", fullUrl, data, async () => {
616
- return await fetch(fullUrl, {
617
- method: "POST",
618
- headers: {
619
- "Content-Type": "application/json",
620
- Authorization: `Basic ${Buffer.from(`:${this.accessToken}`).toString("base64")}`
621
- },
622
- body: JSON.stringify(data)
623
- });
624
- }, this.debug);
625
- }
626
- async restApiPut(url, data, apiVersion = AzureDevOpsWebApiClient.API_VERSION) {
627
- const fullUrl = `${url}?api-version=${apiVersion}`;
628
- return await sendRestApiRequestWithRetry("PUT", fullUrl, data, async () => {
629
- return await fetch(fullUrl, {
630
- method: "PUT",
631
- headers: {
632
- "Content-Type": "application/json",
633
- Authorization: `Basic ${Buffer.from(`:${this.accessToken}`).toString("base64")}`
634
- },
635
- body: JSON.stringify(data)
922
+ /**
923
+ * Create or update webhook subscriptions for Azure DevOps events.
924
+ * This sets up subscriptions for various git events (push, pull request updates, repository changes, etc.)
925
+ * and ensures they are configured to send webhooks to the specified URL.
926
+ * Existing subscriptions matching the URL will be updated, otherwise new subscriptions are created.
927
+ *
928
+ * Requires scope "Service Hooks (Read & Write)" (vso.hooks_write).
929
+ * @returns Array of subscription IDs that were created or updated
930
+ */
931
+ async createOrUpdateHookSubscriptions({ url, headers, project }) {
932
+ const subscriptionTypes = new Map([
933
+ ["git.push", "1.0"],
934
+ ["git.pullrequest.updated", "1.0"],
935
+ ["git.pullrequest.merged", "1.0"],
936
+ ["git.repo.created", "1.0-preview.1"],
937
+ ["git.repo.deleted", "1.0-preview.1"],
938
+ ["git.repo.renamed", "1.0-preview.1"],
939
+ ["git.repo.statuschanged", "1.0-preview.1"],
940
+ ["ms.vss-code.git-pullrequest-comment-event", "2.0"]
941
+ ]);
942
+ const query = this.buildSubscriptionsQuery({
943
+ url,
944
+ project
945
+ });
946
+ const subscriptions = await this.inner.subscriptions.query(query);
947
+ const ids = [];
948
+ for (const [eventType, resourceVersion] of subscriptionTypes) {
949
+ const existing = subscriptions.find((sub) => {
950
+ return sub.eventType === eventType && sub.resourceVersion === resourceVersion;
636
951
  });
637
- }, this.debug);
638
- }
639
- async restApiPatch(url, data, contentType, apiVersion = AzureDevOpsWebApiClient.API_VERSION) {
640
- const fullUrl = `${url}?api-version=${apiVersion}`;
641
- return await sendRestApiRequestWithRetry("PATCH", fullUrl, data, async () => {
642
- return await fetch(fullUrl, {
643
- method: "PATCH",
644
- headers: {
645
- "Content-Type": contentType || "application/json",
646
- Authorization: `Basic ${Buffer.from(`:${this.accessToken}`).toString("base64")}`
647
- },
648
- body: JSON.stringify(data)
952
+ let subscription;
953
+ if (existing) {
954
+ existing.status = "enabled";
955
+ existing.eventType = eventType;
956
+ existing.resourceVersion = resourceVersion;
957
+ existing.publisherInputs = this.makeTfsPublisherInputs({
958
+ eventType,
959
+ project
960
+ });
961
+ existing.consumerInputs = this.makeWebhookConsumerInputs({
962
+ url,
963
+ headers
964
+ });
965
+ subscription = await this.inner.subscriptions.replace(existing.id, existing);
966
+ } else subscription = await this.inner.subscriptions.create({
967
+ status: "enabled",
968
+ eventType,
969
+ resourceVersion,
970
+ publisherId: "tfs",
971
+ publisherInputs: this.makeTfsPublisherInputs({
972
+ eventType,
973
+ project
974
+ }),
975
+ consumerId: "webHooks",
976
+ consumerActionId: "httpRequest",
977
+ consumerInputs: this.makeWebhookConsumerInputs({
978
+ url,
979
+ headers
980
+ })
649
981
  });
650
- }, this.debug);
651
- }
652
- };
653
- function mergeCommitMessage(id, title, description) {
654
- return `Merged PR ${id}: ${title}\n\n${description}`.slice(0, 3500);
655
- }
656
- function isGuid(guid) {
657
- return /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(guid);
658
- }
659
- function getIdentityApiUrl(organisationApiUrl) {
660
- const uri = new URL(organisationApiUrl);
661
- const hostname = uri.hostname.toLowerCase();
662
- if (hostname === "dev.azure.com" || hostname.endsWith(".visualstudio.com")) uri.host = "vssps.dev.azure.com";
663
- return uri.toString();
664
- }
665
- async function sendRestApiRequestWithRetry(method, url, payload, requestAsync, isDebug = false, retryCount = 3, retryDelay = 3e3) {
666
- let body;
667
- try {
668
- if (isDebug) logger.debug(`🌎 🠊 [${method}] ${url}`);
669
- const response = await requestAsync();
670
- body = await response.text();
671
- const { status: statusCode, statusText: statusMessage } = response;
672
- if (isDebug) logger.debug(`🌎 🠈 [${statusCode}] ${statusMessage}`);
673
- if (statusCode < 200 || statusCode > 299) throw new HttpRequestError(`HTTP ${method} '${url}' failed: ${statusCode} ${statusMessage}`, statusCode);
674
- return JSON.parse(body);
675
- } catch (e) {
676
- const err = e;
677
- if (retryCount > 1 && isErrorTemporaryFailure(err)) {
678
- logger.warn(err.message);
679
- if (isDebug) logger.debug(`⏳ Retrying request in ${retryDelay}ms...`);
680
- await new Promise((resolve) => setTimeout(resolve, retryDelay));
681
- return sendRestApiRequestWithRetry(method, url, payload, requestAsync, isDebug, retryCount - 1, retryDelay);
982
+ ids.push(subscription.id);
682
983
  }
683
- if (isDebug) {
684
- if (payload) logger.debug(`REQUEST: ${JSON.stringify(payload)}`);
685
- if (body) logger.debug(`RESPONSE: ${body}`);
686
- }
687
- logger.trace(`THROW${e}`);
688
- throw e;
984
+ for (const sub of subscriptions) if (!ids.includes(sub.id)) await this.inner.subscriptions.delete(sub.id);
689
985
  }
690
- }
986
+ /**
987
+ * Remove all webhook subscriptions for a specific URL.
988
+ * This finds all subscriptions matching the provided URL and deletes them.
989
+ *
990
+ * Requires scope "Service Hooks (Read & Write)" (vso.hooks_write).
991
+ */
992
+ async deleteHookSubscriptions({ url, project }) {
993
+ const query = this.buildSubscriptionsQuery({
994
+ url,
995
+ project
996
+ });
997
+ const subscriptions = await this.inner.subscriptions.query(query);
998
+ for (const sub of subscriptions) await this.inner.subscriptions.delete(sub.id);
999
+ }
1000
+ mergeCommitMessage(id, title, description) {
1001
+ return `Merged PR ${id}: ${title}\n\n${description}`.slice(0, 3500);
1002
+ }
1003
+ isGuid(guid) {
1004
+ return /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(guid);
1005
+ }
1006
+ buildSubscriptionsQuery({ url, project }) {
1007
+ return {
1008
+ publisherId: "tfs",
1009
+ publisherInputFilters: [{ conditions: [{
1010
+ operator: "equals",
1011
+ inputId: "projectId",
1012
+ caseSensitive: false,
1013
+ inputValue: project
1014
+ }] }],
1015
+ consumerId: "webHooks",
1016
+ consumerActionId: "httpRequest",
1017
+ consumerInputFilters: [{ conditions: [{
1018
+ operator: "equals",
1019
+ inputId: "url",
1020
+ caseSensitive: false,
1021
+ inputValue: url
1022
+ }] }]
1023
+ };
1024
+ }
1025
+ makeTfsPublisherInputs({ eventType, project }) {
1026
+ return {
1027
+ projectId: project,
1028
+ ...eventType === "git.pullrequest.updated" && { notificationType: "StatusUpdateNotification" },
1029
+ ...eventType === "git.pullrequest.merged" && { mergeResult: "Conflicts" }
1030
+ };
1031
+ }
1032
+ makeWebhookConsumerInputs({ url, headers }) {
1033
+ return {
1034
+ url,
1035
+ acceptUntrustedCerts: "false",
1036
+ httpHeaders: Object.entries(headers).map(([key, value]) => `${key}:${value}`).join("\n"),
1037
+ messagesToSend: "none",
1038
+ detailedMessagesToSend: "none"
1039
+ };
1040
+ }
1041
+ };
691
1042
 
692
1043
  //#endregion
693
1044
  //#region src/azure/config.ts
694
1045
  /**
695
1046
  * Parse the dependabot config YAML file to specify update configuration.
696
- * The file should be located at any of `POSSIBLE_CONFIG_FILE_PATHS`.
1047
+ * The file should be located at any of `CONFIG_FILE_PATHS_AZURE`.
697
1048
  *
698
1049
  * To view YAML file format, visit
699
1050
  * https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates#allow
@@ -705,15 +1056,22 @@ async function getDependabotConfig({ url, token, remote, rootDir = process.cwd()
705
1056
  let configContents;
706
1057
  if (remote) {
707
1058
  logger.debug(`Attempting to fetch configuration file via REST API ...`);
708
- for (const fp of POSSIBLE_CONFIG_FILE_PATHS) {
1059
+ for (const fp of CONFIG_FILE_PATHS_AZURE) {
709
1060
  const requestUrl = `${url.value}${url.project}/_apis/git/repositories/${url.repository}/items?path=/${fp}`;
710
1061
  logger.debug(`GET ${requestUrl}`);
711
1062
  try {
712
1063
  const authHeader = `Basic ${Buffer.from(`x-access-token:${token}`).toString("base64")}`;
713
- const response = await fetch(requestUrl, { headers: {
714
- Authorization: authHeader,
715
- Accept: "*/*"
716
- } });
1064
+ const response = await ky.get(requestUrl, {
1065
+ headers: {
1066
+ Authorization: authHeader,
1067
+ Accept: "*/*"
1068
+ },
1069
+ throwHttpErrors: (status) => ![
1070
+ 401,
1071
+ 403,
1072
+ 404
1073
+ ].includes(status)
1074
+ });
717
1075
  if (response.ok) {
718
1076
  logger.debug(`Found configuration file at '${requestUrl}'`);
719
1077
  configContents = await response.text();
@@ -729,7 +1087,7 @@ async function getDependabotConfig({ url, token, remote, rootDir = process.cwd()
729
1087
  else throw error;
730
1088
  }
731
1089
  }
732
- } else for (const fp of POSSIBLE_CONFIG_FILE_PATHS) {
1090
+ } else for (const fp of CONFIG_FILE_PATHS_AZURE) {
733
1091
  const filePath = path.join(rootDir, fp);
734
1092
  if (existsSync(filePath)) {
735
1093
  logger.debug(`Found configuration file cloned at ${filePath}`);
@@ -738,7 +1096,7 @@ async function getDependabotConfig({ url, token, remote, rootDir = process.cwd()
738
1096
  break;
739
1097
  } else logger.trace(`No configuration file cloned at ${filePath}`);
740
1098
  }
741
- if (!configContents || !configPath || typeof configContents !== "string") throw new Error(`Configuration file not found at possible locations: ${POSSIBLE_CONFIG_FILE_PATHS.join(", ")}`);
1099
+ if (!configContents || !configPath || typeof configContents !== "string") throw new Error(`Configuration file not found at possible locations: ${CONFIG_FILE_PATHS_AZURE.join(", ")}`);
742
1100
  else logger.trace("Configuration file contents read.");
743
1101
  return await parseDependabotConfig({
744
1102
  configContents,
@@ -747,6 +1105,119 @@ async function getDependabotConfig({ url, token, remote, rootDir = process.cwd()
747
1105
  });
748
1106
  }
749
1107
 
1108
+ //#endregion
1109
+ //#region src/azure/events.ts
1110
+ const AzdoEventTypeSchema = z.enum([
1111
+ "git.push",
1112
+ "git.pullrequest.updated",
1113
+ "git.pullrequest.merged",
1114
+ "git.repo.created",
1115
+ "git.repo.deleted",
1116
+ "git.repo.renamed",
1117
+ "git.repo.statuschanged",
1118
+ "ms.vss-code.git-pullrequest-comment-event"
1119
+ ]);
1120
+ const AzdoEventProjectSchema = z.object({
1121
+ id: z.string(),
1122
+ name: z.string(),
1123
+ url: z.string()
1124
+ });
1125
+ const AzdoEventRepositorySchema = z.object({
1126
+ id: z.string(),
1127
+ name: z.string(),
1128
+ project: AzdoEventProjectSchema,
1129
+ defaultBranch: z.string().optional(),
1130
+ remoteUrl: z.string()
1131
+ });
1132
+ const AzdoEventCodePushResourceSchema = z.object({
1133
+ repository: AzdoEventRepositorySchema,
1134
+ commits: AzdoGitCommitRefSchema.array(),
1135
+ refUpdates: z.object({
1136
+ name: z.string(),
1137
+ oldObjectId: z.string().nullish(),
1138
+ newObjectId: z.string().nullish()
1139
+ }).array(),
1140
+ pushId: z.number(),
1141
+ url: z.string()
1142
+ });
1143
+ const AzdoEventPullRequestResourceSchema = z.object({
1144
+ repository: AzdoEventRepositorySchema,
1145
+ pullRequestId: z.number(),
1146
+ status: AzdoPullRequestStatusSchema,
1147
+ createdBy: AzdoIdentityRefSchema,
1148
+ title: z.string(),
1149
+ sourceRefName: z.string(),
1150
+ targetRefName: z.string(),
1151
+ mergeStatus: AzdoPullRequestAsyncStatusSchema,
1152
+ mergeId: z.string(),
1153
+ url: z.string()
1154
+ });
1155
+ const AzdoEventRepositoryCreatedResourceSchema = z.object({ repository: AzdoEventRepositorySchema });
1156
+ const AzdoEventRepositoryDeletedResourceSchema = z.object({
1157
+ project: AzdoEventProjectSchema,
1158
+ repositoryId: z.string(),
1159
+ repositoryName: z.string(),
1160
+ isHardDelete: z.boolean()
1161
+ });
1162
+ const AzdoEventRepositoryRenamedResourceSchema = z.object({
1163
+ oldName: z.string(),
1164
+ newName: z.string(),
1165
+ repository: AzdoEventRepositorySchema
1166
+ });
1167
+ const AzdoEventRepositoryStatusChangedResourceSchema = z.object({
1168
+ disabled: z.boolean(),
1169
+ repository: AzdoEventRepositorySchema
1170
+ });
1171
+ const AzdoEventPullRequestCommentEventResourceSchema = z.object({
1172
+ pullRequest: AzdoEventPullRequestResourceSchema,
1173
+ comment: AzdoPullRequestCommentSchema
1174
+ });
1175
+ const AzdoEventSchema = z.object({
1176
+ subscriptionId: z.string(),
1177
+ notificationId: z.number(),
1178
+ id: z.string(),
1179
+ publisherId: z.string(),
1180
+ resourceVersion: z.enum([
1181
+ "1.0",
1182
+ "1.0-preview.1",
1183
+ "2.0"
1184
+ ]),
1185
+ createdDate: z.coerce.date()
1186
+ }).and(z.discriminatedUnion("eventType", [
1187
+ z.object({
1188
+ eventType: z.literal("git.push"),
1189
+ resource: AzdoEventCodePushResourceSchema
1190
+ }),
1191
+ z.object({
1192
+ eventType: z.literal("git.pullrequest.updated"),
1193
+ resource: AzdoEventPullRequestResourceSchema
1194
+ }),
1195
+ z.object({
1196
+ eventType: z.literal("git.pullrequest.merged"),
1197
+ resource: AzdoEventPullRequestResourceSchema
1198
+ }),
1199
+ z.object({
1200
+ eventType: z.literal("git.repo.created"),
1201
+ resource: AzdoEventRepositoryCreatedResourceSchema
1202
+ }),
1203
+ z.object({
1204
+ eventType: z.literal("git.repo.deleted"),
1205
+ resource: AzdoEventRepositoryDeletedResourceSchema
1206
+ }),
1207
+ z.object({
1208
+ eventType: z.literal("git.repo.renamed"),
1209
+ resource: AzdoEventRepositoryRenamedResourceSchema
1210
+ }),
1211
+ z.object({
1212
+ eventType: z.literal("git.repo.statuschanged"),
1213
+ resource: AzdoEventRepositoryStatusChangedResourceSchema
1214
+ }),
1215
+ z.object({
1216
+ eventType: z.literal("ms.vss-code.git-pullrequest-comment-event"),
1217
+ resource: AzdoEventPullRequestCommentEventResourceSchema
1218
+ })
1219
+ ]));
1220
+
750
1221
  //#endregion
751
1222
  //#region src/azure/url-parts.ts
752
1223
  function extractOrganizationUrl({ organisationUrl }) {
@@ -757,12 +1228,14 @@ function extractOrganizationUrl({ organisationUrl }) {
757
1228
  const organisation = extractOrganisation(organisationUrl);
758
1229
  const virtualDirectory = extractVirtualDirectory(value);
759
1230
  const apiEndpoint = `${protocol}://${hostname}${value.port ? `:${value.port}` : ""}/${virtualDirectory ? `${virtualDirectory}/` : ""}`;
1231
+ const identityApiUrl = hostname === "dev.azure.com" || hostname.endsWith(".visualstudio.com") ? new URL(`https://vssps.dev.azure.com/${organisation}/`) : value;
760
1232
  return {
761
1233
  value,
762
1234
  hostname,
763
1235
  "api-endpoint": apiEndpoint,
764
1236
  organisation,
765
- "virtual-directory": virtualDirectory
1237
+ "virtual-directory": virtualDirectory,
1238
+ "identity-api-url": identityApiUrl
766
1239
  };
767
1240
  }
768
1241
  function extractProjectUrl({ organisationUrl, project }) {
@@ -818,5 +1291,5 @@ function extractVirtualDirectory(organisationUrl) {
818
1291
  }
819
1292
 
820
1293
  //#endregion
821
- export { ANONYMOUS_USER_ID, AzureDevOpsWebApiClient, CommentThreadStatus, CommentType, DEVOPS_PR_PROPERTY_DEPENDABOT_DEPENDENCIES, DEVOPS_PR_PROPERTY_DEPENDABOT_PACKAGE_MANAGER, DEVOPS_PR_PROPERTY_MICROSOFT_GIT_SOURCE_REF_NAME, DependenciesPrPropertySchema, GitPullRequestMergeStrategy, ItemContentType, PullRequestAsyncStatus, PullRequestStatus, VersionControlChangeType, buildPullRequestProperties, extractOrganizationUrl, extractProjectUrl, extractRepositoryUrl, getDependabotConfig, getPullRequestChangedFilesForOutputData, getPullRequestCloseReasonForOutputData, getPullRequestDependenciesPropertyValueForOutputData, getPullRequestDescription, getPullRequestForDependencyNames, normalizeBranchName, normalizeFilePath, parsePullRequestProperties, sendRestApiRequestWithRetry };
1294
+ export { ANONYMOUS_USER_ID, API_VERSION, API_VERSION_PREVIEW, AZDO_PULL_REQUEST_MERGE_STRATEGIES, AzdoCommentThreadStatusSchema, AzdoCommentTypeSchema, AzdoConnectionDataSchema, AzdoEventCodePushResourceSchema, AzdoEventPullRequestCommentEventResourceSchema, AzdoEventPullRequestResourceSchema, AzdoEventRepositoryCreatedResourceSchema, AzdoEventRepositoryDeletedResourceSchema, AzdoEventRepositoryRenamedResourceSchema, AzdoEventRepositorySchema, AzdoEventRepositoryStatusChangedResourceSchema, AzdoEventSchema, AzdoEventTypeSchema, AzdoGitBranchStatsSchema, AzdoGitChangeSchema, AzdoGitCommitDiffsSchema, AzdoGitCommitRefSchema, AzdoGitPushCreateSchema, AzdoGitPushSchema, AzdoGitRefSchema, AzdoGitRefUpdateResultSchema, AzdoGitRefUpdateSchema, AzdoGitUserDateSchema, AzdoIdentityRefSchema, AzdoIdentityRefWithVoteSchema, AzdoIdentitySchema, AzdoProjectSchema, AzdoPropertiesSchema, AzdoPullRequestAsyncStatusSchema, AzdoPullRequestCommentSchema, AzdoPullRequestCommentThreadSchema, AzdoPullRequestMergeStrategySchema, AzdoPullRequestSchema, AzdoPullRequestStatusSchema, AzdoRepositoryItemSchema, AzdoRepositorySchema, AzdoSubscriptionSchema, AzdoSubscriptionsQueryInputFilterSchema, AzdoSubscriptionsQueryResponseSchema, AzdoSubscriptionsQuerySchema, AzdoVersionControlChangeTypeSchema, AzureDevOpsClient, AzureDevOpsClientWrapper, PR_DESCRIPTION_MAX_LENGTH, PR_PROPERTY_DEPENDABOT_DEPENDENCIES, PR_PROPERTY_DEPENDABOT_PACKAGE_MANAGER, PR_PROPERTY_MICROSOFT_GIT_SOURCE_REF_NAME, buildPullRequestProperties, extractOrganizationUrl, extractProjectUrl, extractRepositoryUrl, getDependabotConfig, getPullRequestChangedFiles, getPullRequestForDependencyNames, parsePullRequestProperties };
822
1295
  //# sourceMappingURL=index.mjs.map