@paklo/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/dist/browser/defineProperty-ie4tC-F5.js +43 -0
  3. package/dist/browser/environment-DinhzwQn.js +139 -0
  4. package/dist/browser/environment-DinhzwQn.js.map +1 -0
  5. package/dist/browser/environment.d.ts +33 -0
  6. package/dist/browser/environment.js +3 -0
  7. package/dist/browser/github.d.ts +151 -0
  8. package/dist/browser/github.js +199 -0
  9. package/dist/browser/github.js.map +1 -0
  10. package/dist/browser/http.d.ts +121 -0
  11. package/dist/browser/http.js +248 -0
  12. package/dist/browser/http.js.map +1 -0
  13. package/dist/browser/logger-B7HLv660.js +31 -0
  14. package/dist/browser/logger-B7HLv660.js.map +1 -0
  15. package/dist/browser/logger.d.ts +23 -0
  16. package/dist/browser/logger.js +4 -0
  17. package/dist/browser/shared-data.d.ts +22 -0
  18. package/dist/browser/shared-data.js +23 -0
  19. package/dist/browser/shared-data.js.map +1 -0
  20. package/dist/browser/usage.d.ts +99 -0
  21. package/dist/browser/usage.js +383 -0
  22. package/dist/browser/usage.js.map +1 -0
  23. package/dist/node/azure.d.ts +338 -0
  24. package/dist/node/azure.js +735 -0
  25. package/dist/node/azure.js.map +1 -0
  26. package/dist/node/dependabot-BteoKZVy.js +547 -0
  27. package/dist/node/dependabot-BteoKZVy.js.map +1 -0
  28. package/dist/node/dependabot.d.ts +3 -0
  29. package/dist/node/dependabot.js +6 -0
  30. package/dist/node/environment-DX5CD-dD.js +138 -0
  31. package/dist/node/environment-DX5CD-dD.js.map +1 -0
  32. package/dist/node/environment.d.ts +33 -0
  33. package/dist/node/environment.js +3 -0
  34. package/dist/node/github.d.ts +2 -0
  35. package/dist/node/github.js +198 -0
  36. package/dist/node/github.js.map +1 -0
  37. package/dist/node/http-BG_-s47I.js +245 -0
  38. package/dist/node/http-BG_-s47I.js.map +1 -0
  39. package/dist/node/http.d.ts +121 -0
  40. package/dist/node/http.js +4 -0
  41. package/dist/node/index-3wZw74Ah.d.ts +151 -0
  42. package/dist/node/index-DP9JfUPG.d.ts +1761 -0
  43. package/dist/node/job-Crr4kh3e.js +451 -0
  44. package/dist/node/job-Crr4kh3e.js.map +1 -0
  45. package/dist/node/logger-bWnHxtAf.js +31 -0
  46. package/dist/node/logger-bWnHxtAf.js.map +1 -0
  47. package/dist/node/logger.d.ts +23 -0
  48. package/dist/node/logger.js +4 -0
  49. package/dist/node/shared-data.d.ts +22 -0
  50. package/dist/node/shared-data.js +23 -0
  51. package/dist/node/shared-data.js.map +1 -0
  52. package/dist/node/usage.d.ts +99 -0
  53. package/dist/node/usage.js +48 -0
  54. package/dist/node/usage.js.map +1 -0
  55. package/package.json +93 -0
@@ -0,0 +1,735 @@
1
+ import "./environment-DX5CD-dD.js";
2
+ import { n as logger } from "./logger-bWnHxtAf.js";
3
+ import { r as isErrorTemporaryFailure, t as HttpRequestError } from "./http-BG_-s47I.js";
4
+ import { P as parseDependabotConfig, c as DependabotExistingGroupPRSchema, j as POSSIBLE_CONFIG_FILE_PATHS, l as DependabotExistingPRSchema } from "./job-Crr4kh3e.js";
5
+ import "./dependabot-BteoKZVy.js";
6
+ import * as path from "node:path";
7
+ import { existsSync } from "node:fs";
8
+ import { readFile } from "node:fs/promises";
9
+
10
+ //#region src/azure/types.ts
11
+ let VersionControlChangeType = /* @__PURE__ */ function(VersionControlChangeType$1) {
12
+ VersionControlChangeType$1[VersionControlChangeType$1["None"] = 0] = "None";
13
+ VersionControlChangeType$1[VersionControlChangeType$1["Add"] = 1] = "Add";
14
+ VersionControlChangeType$1[VersionControlChangeType$1["Edit"] = 2] = "Edit";
15
+ VersionControlChangeType$1[VersionControlChangeType$1["Encoding"] = 4] = "Encoding";
16
+ VersionControlChangeType$1[VersionControlChangeType$1["Rename"] = 8] = "Rename";
17
+ VersionControlChangeType$1[VersionControlChangeType$1["Delete"] = 16] = "Delete";
18
+ VersionControlChangeType$1[VersionControlChangeType$1["Undelete"] = 32] = "Undelete";
19
+ VersionControlChangeType$1[VersionControlChangeType$1["Branch"] = 64] = "Branch";
20
+ VersionControlChangeType$1[VersionControlChangeType$1["Merge"] = 128] = "Merge";
21
+ VersionControlChangeType$1[VersionControlChangeType$1["Lock"] = 256] = "Lock";
22
+ VersionControlChangeType$1[VersionControlChangeType$1["Rollback"] = 512] = "Rollback";
23
+ VersionControlChangeType$1[VersionControlChangeType$1["SourceRename"] = 1024] = "SourceRename";
24
+ VersionControlChangeType$1[VersionControlChangeType$1["TargetRename"] = 2048] = "TargetRename";
25
+ VersionControlChangeType$1[VersionControlChangeType$1["Property"] = 4096] = "Property";
26
+ VersionControlChangeType$1[VersionControlChangeType$1["All"] = 8191] = "All";
27
+ return VersionControlChangeType$1;
28
+ }({});
29
+ let GitPullRequestMergeStrategy = /* @__PURE__ */ function(GitPullRequestMergeStrategy$1) {
30
+ GitPullRequestMergeStrategy$1[GitPullRequestMergeStrategy$1["NoFastForward"] = 1] = "NoFastForward";
31
+ GitPullRequestMergeStrategy$1[GitPullRequestMergeStrategy$1["Squash"] = 2] = "Squash";
32
+ GitPullRequestMergeStrategy$1[GitPullRequestMergeStrategy$1["Rebase"] = 3] = "Rebase";
33
+ GitPullRequestMergeStrategy$1[GitPullRequestMergeStrategy$1["RebaseMerge"] = 4] = "RebaseMerge";
34
+ return GitPullRequestMergeStrategy$1;
35
+ }({});
36
+ let CommentThreadStatus = /* @__PURE__ */ function(CommentThreadStatus$1) {
37
+ CommentThreadStatus$1[CommentThreadStatus$1["Unknown"] = 0] = "Unknown";
38
+ CommentThreadStatus$1[CommentThreadStatus$1["Active"] = 1] = "Active";
39
+ CommentThreadStatus$1[CommentThreadStatus$1["Fixed"] = 2] = "Fixed";
40
+ CommentThreadStatus$1[CommentThreadStatus$1["WontFix"] = 3] = "WontFix";
41
+ CommentThreadStatus$1[CommentThreadStatus$1["Closed"] = 4] = "Closed";
42
+ CommentThreadStatus$1[CommentThreadStatus$1["ByDesign"] = 5] = "ByDesign";
43
+ CommentThreadStatus$1[CommentThreadStatus$1["Pending"] = 6] = "Pending";
44
+ return CommentThreadStatus$1;
45
+ }({});
46
+ let CommentType = /* @__PURE__ */ function(CommentType$1) {
47
+ CommentType$1[CommentType$1["Unknown"] = 0] = "Unknown";
48
+ CommentType$1[CommentType$1["Text"] = 1] = "Text";
49
+ CommentType$1[CommentType$1["CodeChange"] = 2] = "CodeChange";
50
+ CommentType$1[CommentType$1["System"] = 3] = "System";
51
+ return CommentType$1;
52
+ }({});
53
+ let ItemContentType = /* @__PURE__ */ function(ItemContentType$1) {
54
+ ItemContentType$1[ItemContentType$1["RawText"] = 0] = "RawText";
55
+ ItemContentType$1[ItemContentType$1["Base64Encoded"] = 1] = "Base64Encoded";
56
+ return ItemContentType$1;
57
+ }({});
58
+ let PullRequestAsyncStatus = /* @__PURE__ */ function(PullRequestAsyncStatus$1) {
59
+ PullRequestAsyncStatus$1[PullRequestAsyncStatus$1["NotSet"] = 0] = "NotSet";
60
+ PullRequestAsyncStatus$1[PullRequestAsyncStatus$1["Queued"] = 1] = "Queued";
61
+ PullRequestAsyncStatus$1[PullRequestAsyncStatus$1["Conflicts"] = 2] = "Conflicts";
62
+ PullRequestAsyncStatus$1[PullRequestAsyncStatus$1["Succeeded"] = 3] = "Succeeded";
63
+ PullRequestAsyncStatus$1[PullRequestAsyncStatus$1["RejectedByPolicy"] = 4] = "RejectedByPolicy";
64
+ PullRequestAsyncStatus$1[PullRequestAsyncStatus$1["Failure"] = 5] = "Failure";
65
+ return PullRequestAsyncStatus$1;
66
+ }({});
67
+ let PullRequestStatus = /* @__PURE__ */ function(PullRequestStatus$1) {
68
+ PullRequestStatus$1[PullRequestStatus$1["NotSet"] = 0] = "NotSet";
69
+ PullRequestStatus$1[PullRequestStatus$1["Active"] = 1] = "Active";
70
+ PullRequestStatus$1[PullRequestStatus$1["Abandoned"] = 2] = "Abandoned";
71
+ PullRequestStatus$1[PullRequestStatus$1["Completed"] = 3] = "Completed";
72
+ PullRequestStatus$1[PullRequestStatus$1["All"] = 4] = "All";
73
+ return PullRequestStatus$1;
74
+ }({});
75
+
76
+ //#endregion
77
+ //#region src/azure/models.ts
78
+ /**
79
+ * Pull request property names used to store metadata about the pull request.
80
+ * https://learn.microsoft.com/en-us/rest/api/azure/devops/git/pull-request-properties
81
+ */
82
+ const DEVOPS_PR_PROPERTY_MICROSOFT_GIT_SOURCE_REF_NAME = "Microsoft.Git.PullRequest.SourceRefName";
83
+ const DEVOPS_PR_PROPERTY_DEPENDABOT_PACKAGE_MANAGER = "Dependabot.PackageManager";
84
+ const DEVOPS_PR_PROPERTY_DEPENDABOT_DEPENDENCIES = "Dependabot.Dependencies";
85
+
86
+ //#endregion
87
+ //#region src/azure/utils.ts
88
+ function normalizeFilePath(path$1) {
89
+ return path$1?.replace(/\\/g, "/")?.replace(/^\.\//, "/")?.replace(/^([^/])/, "/$1");
90
+ }
91
+ function normalizeBranchName(branch) {
92
+ return branch?.replace(/^refs\/heads\//i, "");
93
+ }
94
+ const DependenciesPrPropertySchema = DependabotExistingPRSchema.array().or(DependabotExistingGroupPRSchema);
95
+ function buildPullRequestProperties(packageManager, dependencies) {
96
+ return [{
97
+ name: DEVOPS_PR_PROPERTY_DEPENDABOT_PACKAGE_MANAGER,
98
+ value: packageManager
99
+ }, {
100
+ name: DEVOPS_PR_PROPERTY_DEPENDABOT_DEPENDENCIES,
101
+ value: JSON.stringify(dependencies)
102
+ }];
103
+ }
104
+ function parsePullRequestProperties(pullRequests, packageManager) {
105
+ return Object.fromEntries(pullRequests.filter((pr) => {
106
+ return pr.properties?.find((p) => p.name === DEVOPS_PR_PROPERTY_DEPENDABOT_PACKAGE_MANAGER && (packageManager === null || p.value === packageManager));
107
+ }).map((pr) => {
108
+ return [pr.id, DependenciesPrPropertySchema.parse(JSON.parse(pr.properties.find((p) => p.name === DEVOPS_PR_PROPERTY_DEPENDABOT_DEPENDENCIES).value))];
109
+ }));
110
+ }
111
+ function getPullRequestForDependencyNames(existingPullRequests, packageManager, dependencyNames) {
112
+ return existingPullRequests.find((pr) => {
113
+ 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));
114
+ });
115
+ }
116
+ function getDependencyNames(dependencies) {
117
+ return (Array.isArray(dependencies) ? dependencies : dependencies.dependencies).map((dep) => dep["dependency-name"]?.toString());
118
+ }
119
+ function areEqual(a, b) {
120
+ if (a.length !== b.length) return false;
121
+ return a.every((name) => b.includes(name));
122
+ }
123
+ function getPullRequestChangedFilesForOutputData(data) {
124
+ return data["updated-dependency-files"].filter((file) => file.type === "file").map((file) => {
125
+ let changeType = VersionControlChangeType.None;
126
+ if (file.deleted === true) changeType = VersionControlChangeType.Delete;
127
+ else if (file.operation === "update") changeType = VersionControlChangeType.Edit;
128
+ else changeType = VersionControlChangeType.Add;
129
+ return {
130
+ changeType,
131
+ path: path.join(file.directory, file.name),
132
+ content: file.content,
133
+ encoding: file.content_encoding ?? "utf8"
134
+ };
135
+ });
136
+ }
137
+ function getPullRequestCloseReasonForOutputData(data) {
138
+ const leadDependencyName = data["dependency-names"][0];
139
+ let reason;
140
+ switch (data.reason) {
141
+ case "dependencies_changed":
142
+ reason = `Looks like the dependencies have changed`;
143
+ break;
144
+ case "dependency_group_empty":
145
+ reason = `Looks like the dependencies in this group are now empty`;
146
+ break;
147
+ case "dependency_removed":
148
+ reason = `Looks like ${leadDependencyName} is no longer a dependency`;
149
+ break;
150
+ case "up_to_date":
151
+ reason = `Looks like ${leadDependencyName} is up-to-date now`;
152
+ break;
153
+ case "update_no_longer_possible":
154
+ reason = `Looks like ${leadDependencyName} can no longer be updated`;
155
+ break;
156
+ }
157
+ if (reason && reason.length > 0) reason += ", so this is no longer needed.";
158
+ return reason;
159
+ }
160
+ function getPullRequestDependenciesPropertyValueForOutputData(data) {
161
+ const dependencies = data.dependencies?.map((dep) => {
162
+ return {
163
+ "dependency-name": dep.name,
164
+ "dependency-version": dep.version,
165
+ directory: dep.directory
166
+ };
167
+ });
168
+ const dependencyGroupName = data["dependency-group"]?.name;
169
+ if (!dependencyGroupName) return dependencies;
170
+ return {
171
+ "dependency-group-name": dependencyGroupName,
172
+ dependencies
173
+ };
174
+ }
175
+ function getPullRequestDescription(packageManager, body, dependencies) {
176
+ let header = "";
177
+ const footer = "";
178
+ const description = (body || "").replace(new RegExp(decodeURIComponent("%EF%BF%BD%EF%BF%BD%EF%BF%BD"), "g"), "");
179
+ if (dependencies.length === 1) {
180
+ const compatibilityScoreBadges = dependencies.map((dep) => {
181
+ 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})`;
182
+ });
183
+ header += `${compatibilityScoreBadges.join(" ")}\n\n`;
184
+ }
185
+ const maxDescriptionLengthAfterHeaderAndFooter = 4e3 - header.length - 0;
186
+ return `${header}${description.substring(0, maxDescriptionLengthAfterHeaderAndFooter)}${footer}`;
187
+ }
188
+
189
+ //#endregion
190
+ //#region src/azure/client.ts
191
+ /** Azure DevOps REST API client. */
192
+ var AzureDevOpsWebApiClient = class AzureDevOpsWebApiClient {
193
+ organisationApiUrl;
194
+ identityApiUrl;
195
+ accessToken;
196
+ debug;
197
+ authenticatedUserId;
198
+ resolvedUserIds;
199
+ static API_VERSION = "5.0";
200
+ static API_VERSION_PREVIEW = "5.0-preview";
201
+ constructor(url, accessToken, debug = false) {
202
+ const organisationApiUrl = url.url.toString();
203
+ this.organisationApiUrl = organisationApiUrl.replace(/\/$/, "");
204
+ this.identityApiUrl = getIdentityApiUrl(organisationApiUrl).replace(/\/$/, "");
205
+ this.accessToken = accessToken;
206
+ this.debug = debug;
207
+ this.resolvedUserIds = {};
208
+ }
209
+ /**
210
+ * Get the identity of the authenticated user.
211
+ * @returns
212
+ */
213
+ async getUserId() {
214
+ if (!this.authenticatedUserId) {
215
+ this.authenticatedUserId = (await this.restApiGet(`${this.organisationApiUrl}/_apis/connectiondata`, void 0, AzureDevOpsWebApiClient.API_VERSION_PREVIEW))?.authenticatedUser?.id;
216
+ if (!this.authenticatedUserId) throw new Error("Failed to get authenticated user ID");
217
+ }
218
+ return this.authenticatedUserId;
219
+ }
220
+ /**
221
+ * Get the identity id from a user name, email, or group name.
222
+ * Requires scope "Identity (Read)" (vso.identity).
223
+ * @param userNameEmailOrGroupName
224
+ * @returns
225
+ */
226
+ async resolveIdentityId(userNameEmailOrGroupName) {
227
+ if (this.resolvedUserIds[userNameEmailOrGroupName]) return this.resolvedUserIds[userNameEmailOrGroupName];
228
+ try {
229
+ const identities = await this.restApiGet(`${this.identityApiUrl}/_apis/identities`, {
230
+ searchFilter: "General",
231
+ filterValue: userNameEmailOrGroupName,
232
+ queryMembership: "None"
233
+ });
234
+ if (!identities?.value || identities.value.length === 0) return;
235
+ this.resolvedUserIds[userNameEmailOrGroupName] = identities.value[0]?.id;
236
+ return this.resolvedUserIds[userNameEmailOrGroupName];
237
+ } catch (e) {
238
+ logger.error(`Failed to resolve user id: ${e}`);
239
+ logger.debug(e);
240
+ return;
241
+ }
242
+ }
243
+ /**
244
+ * Get the default branch for a repository.
245
+ * Requires scope "Code (Read)" (vso.code).
246
+ * @param project
247
+ * @param repository
248
+ * @returns
249
+ */
250
+ async getDefaultBranch(project, repository) {
251
+ try {
252
+ const repo = await this.restApiGet(`${this.organisationApiUrl}/${project}/_apis/git/repositories/${repository}`);
253
+ if (!repo) throw new Error(`Repository '${project}/${repository}' not found`);
254
+ return normalizeBranchName(repo.defaultBranch);
255
+ } catch (e) {
256
+ logger.error(`Failed to get default branch for '${project}/${repository}': ${e}`);
257
+ logger.debug(e);
258
+ return;
259
+ }
260
+ }
261
+ /**
262
+ * Get the list of branch names for a repository.
263
+ * Requires scope "Code (Read)" (vso.code).
264
+ * @param project
265
+ * @param repository
266
+ * @returns
267
+ */
268
+ async getBranchNames(project, repository) {
269
+ try {
270
+ const refs = await this.restApiGet(`${this.organisationApiUrl}/${project}/_apis/git/repositories/${repository}/refs`);
271
+ if (!refs) throw new Error(`Repository '${project}/${repository}' not found`);
272
+ return refs.value?.map((r) => normalizeBranchName(r.name)) || [];
273
+ } catch (e) {
274
+ logger.error(`Failed to list branch names for '${project}/${repository}': ${e}`);
275
+ logger.debug(e);
276
+ return;
277
+ }
278
+ }
279
+ /**
280
+ * Get the properties for all active pull request created by the supplied user.
281
+ * Requires scope "Code (Read)" (vso.code).
282
+ * @param project
283
+ * @param repository
284
+ * @param creator
285
+ * @returns
286
+ */
287
+ async getActivePullRequestProperties(project, repository, creator) {
288
+ try {
289
+ const pullRequests = await this.restApiGet(`${this.organisationApiUrl}/${project}/_apis/git/repositories/${repository}/pullrequests`, {
290
+ "searchCriteria.creatorId": isGuid(creator) ? creator : await this.getUserId(),
291
+ "searchCriteria.status": "Active"
292
+ });
293
+ if (!pullRequests?.value || pullRequests.value.length === 0) return [];
294
+ return await Promise.all(pullRequests.value.map(async (pr) => {
295
+ const properties = await this.restApiGet(`${this.organisationApiUrl}/${project}/_apis/git/repositories/${repository}/pullrequests/${pr.pullRequestId}/properties`);
296
+ return {
297
+ id: pr.pullRequestId,
298
+ properties: Object.keys(properties?.value || {}).map((key) => {
299
+ return {
300
+ name: key,
301
+ value: properties.value[key]?.$value
302
+ };
303
+ }) || []
304
+ };
305
+ }));
306
+ } catch (e) {
307
+ logger.error(`Failed to list active pull request properties: ${e}`);
308
+ logger.debug(e);
309
+ return [];
310
+ }
311
+ }
312
+ /**
313
+ * Create a new pull request.
314
+ * Requires scope "Code (Write)" (vso.code_write).
315
+ * Requires scope "Identity (Read)" (vso.identity), if assignees are specified.
316
+ * @param pr
317
+ * @returns
318
+ */
319
+ async createPullRequest(pr) {
320
+ logger.info(`Creating pull request '${pr.title}'...`);
321
+ try {
322
+ const userId = await this.getUserId();
323
+ const reviewers = [];
324
+ if (pr.assignees && pr.assignees.length > 0) for (const assignee of pr.assignees) {
325
+ const identityId = isGuid(assignee) ? assignee : await this.resolveIdentityId(assignee);
326
+ if (identityId && !reviewers.some((r) => r.id === identityId)) reviewers.push({ id: identityId });
327
+ else logger.warn(`Unable to resolve assignee identity '${assignee}'`);
328
+ }
329
+ logger.info(` - Pushing ${pr.changes.length} file change(s) to branch '${pr.source.branch}'...`);
330
+ const push = await this.restApiPost(`${this.organisationApiUrl}/${pr.project}/_apis/git/repositories/${pr.repository}/pushes`, {
331
+ refUpdates: [{
332
+ name: `refs/heads/${pr.source.branch}`,
333
+ oldObjectId: pr.source.commit
334
+ }],
335
+ commits: [{
336
+ comment: pr.commitMessage,
337
+ author: pr.author,
338
+ changes: pr.changes.map((change) => {
339
+ return {
340
+ changeType: change.changeType,
341
+ item: { path: normalizeFilePath(change.path) },
342
+ newContent: {
343
+ content: Buffer.from(change.content, change.encoding).toString("base64"),
344
+ contentType: ItemContentType.Base64Encoded
345
+ }
346
+ };
347
+ })
348
+ }]
349
+ });
350
+ if (!push?.commits?.length) throw new Error("Failed to push changes to source branch, no commits were created");
351
+ logger.info(` - Pushed commit: ${push.commits.map((c) => c.commitId).join(", ")}.`);
352
+ logger.info(` - Creating pull request to merge '${pr.source.branch}' into '${pr.target.branch}'...`);
353
+ const pullRequest = await this.restApiPost(`${this.organisationApiUrl}/${pr.project}/_apis/git/repositories/${pr.repository}/pullrequests`, {
354
+ sourceRefName: `refs/heads/${pr.source.branch}`,
355
+ targetRefName: `refs/heads/${pr.target.branch}`,
356
+ title: pr.title,
357
+ description: pr.description,
358
+ reviewers,
359
+ workItemRefs: pr.workItems?.map((id) => ({ id })),
360
+ labels: pr.labels?.map((label) => ({ name: label }))
361
+ });
362
+ if (!pullRequest?.pullRequestId) throw new Error("Failed to create pull request, no pull request id was returned");
363
+ logger.info(` - Created pull request: #${pullRequest.pullRequestId}.`);
364
+ if (pr.properties && pr.properties.length > 0) {
365
+ logger.info(` - Adding dependency metadata to pull request properties...`);
366
+ if (!(await this.restApiPatch(`${this.organisationApiUrl}/${pr.project}/_apis/git/repositories/${pr.repository}/pullrequests/${pullRequest.pullRequestId}/properties`, pr.properties.map((property) => {
367
+ return {
368
+ op: "add",
369
+ path: `/${property.name}`,
370
+ value: property.value
371
+ };
372
+ }), "application/json-patch+json"))?.count) throw new Error("Failed to add dependency metadata properties to pull request");
373
+ }
374
+ if (pr.autoComplete) {
375
+ logger.info(` - Updating auto-complete options...`);
376
+ const updatedPullRequest = await this.restApiPatch(`${this.organisationApiUrl}/${pr.project}/_apis/git/repositories/${pr.repository}/pullrequests/${pullRequest.pullRequestId}`, {
377
+ autoCompleteSetBy: { id: userId },
378
+ completionOptions: {
379
+ autoCompleteIgnoreConfigIds: pr.autoComplete.ignorePolicyConfigIds,
380
+ deleteSourceBranch: true,
381
+ mergeCommitMessage: mergeCommitMessage(pullRequest.pullRequestId, pr.title, pr.description),
382
+ mergeStrategy: pr.autoComplete.mergeStrategy,
383
+ transitionWorkItems: false
384
+ }
385
+ });
386
+ if (!updatedPullRequest || updatedPullRequest.autoCompleteSetBy?.id !== userId) throw new Error("Failed to set auto-complete on pull request");
387
+ }
388
+ logger.info(` - Pull request was created successfully.`);
389
+ return pullRequest.pullRequestId;
390
+ } catch (e) {
391
+ logger.error(`Failed to create pull request: ${e}`);
392
+ logger.debug(e);
393
+ return null;
394
+ }
395
+ }
396
+ /**
397
+ * Update a pull request.
398
+ * Requires scope "Code (Read & Write)" (vso.code, vso.code_write).
399
+ * @param pr
400
+ * @returns
401
+ */
402
+ async updatePullRequest(pr) {
403
+ logger.info(`Updating pull request #${pr.pullRequestId}...`);
404
+ try {
405
+ const pullRequest = await this.restApiGet(`${this.organisationApiUrl}/${pr.project}/_apis/git/repositories/${pr.repository}/pullrequests/${pr.pullRequestId}`);
406
+ if (!pullRequest) throw new Error(`Pull request #${pr.pullRequestId} not found`);
407
+ if (pr.skipIfDraft && pullRequest.isDraft) {
408
+ logger.info(` - Skipping update as pull request is currently marked as a draft.`);
409
+ return true;
410
+ }
411
+ if (pr.skipIfCommitsFromAuthorsOtherThan) {
412
+ 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)) {
413
+ logger.info(` - Skipping update as pull request has been modified by another user.`);
414
+ return true;
415
+ }
416
+ }
417
+ const stats = await this.restApiGet(`${this.organisationApiUrl}/${pr.project}/_apis/git/repositories/${pr.repository}/stats/branches`, { name: normalizeBranchName(pullRequest.sourceRefName) });
418
+ if (stats?.behindCount === void 0) throw new Error(`Failed to get branch stats for '${pullRequest.sourceRefName}'`);
419
+ if (pr.skipIfNotBehindTargetBranch && stats.behindCount === 0) {
420
+ logger.info(` - Skipping update as source branch is not behind target branch.`);
421
+ return true;
422
+ }
423
+ const sourceBranchName = normalizeBranchName(pullRequest.sourceRefName);
424
+ const targetBranchName = normalizeBranchName(pullRequest.targetRefName);
425
+ if (stats.behindCount > 0) {
426
+ logger.info(` - Rebasing '${targetBranchName}' into '${sourceBranchName}' (${stats.behindCount} commit(s) behind)...`);
427
+ if ((await this.restApiPost(`${this.organisationApiUrl}/${pr.project}/_apis/git/repositories/${pr.repository}/refs`, [{
428
+ name: pullRequest.sourceRefName,
429
+ oldObjectId: pullRequest.lastMergeSourceCommit.commitId,
430
+ newObjectId: pr.commit
431
+ }]))?.value?.[0]?.success !== true) throw new Error("Failed to rebase the target branch into the source branch");
432
+ }
433
+ logger.info(` - Pushing ${pr.changes.length} file change(s) to branch '${pullRequest.sourceRefName}'...`);
434
+ const push = await this.restApiPost(`${this.organisationApiUrl}/${pr.project}/_apis/git/repositories/${pr.repository}/pushes`, {
435
+ refUpdates: [{
436
+ name: pullRequest.sourceRefName,
437
+ oldObjectId: pr.commit
438
+ }],
439
+ commits: [{
440
+ comment: pullRequest.mergeStatus === PullRequestAsyncStatus.Conflicts ? "Resolve merge conflicts" : `Rebase '${sourceBranchName}' onto '${targetBranchName}'`,
441
+ author: pr.author,
442
+ changes: pr.changes.map((change) => {
443
+ return {
444
+ changeType: change.changeType,
445
+ item: { path: normalizeFilePath(change.path) },
446
+ newContent: {
447
+ content: Buffer.from(change.content, change.encoding).toString("base64"),
448
+ contentType: ItemContentType.Base64Encoded
449
+ }
450
+ };
451
+ })
452
+ }]
453
+ });
454
+ if (!push?.commits?.length) throw new Error("Failed to push changes to source branch, no commits were created");
455
+ logger.info(` - Pushed commit: ${push.commits.map((c) => c.commitId).join(", ")}.`);
456
+ logger.info(` - Pull request was updated successfully.`);
457
+ return true;
458
+ } catch (e) {
459
+ logger.error(`Failed to update pull request: ${e}`);
460
+ logger.debug(e);
461
+ return false;
462
+ }
463
+ }
464
+ /**
465
+ * Approve a pull request.
466
+ * Requires scope "Code (Write)" (vso.code_write).
467
+ * @param pr
468
+ * @returns
469
+ */
470
+ async approvePullRequest(pr) {
471
+ logger.info(`Approving pull request #${pr.pullRequestId}...`);
472
+ try {
473
+ logger.info(` - Updating reviewer vote on pull request...`);
474
+ const userId = await this.getUserId();
475
+ if ((await this.restApiPut(`${this.organisationApiUrl}/${pr.project}/_apis/git/repositories/${pr.repository}/pullrequests/${pr.pullRequestId}/reviewers/${userId}`, {
476
+ vote: 10,
477
+ isReapprove: true
478
+ }, "7.1"))?.vote !== 10) throw new Error("Failed to approve pull request, vote was not recorded");
479
+ logger.info(` - Pull request was approved successfully.`);
480
+ return true;
481
+ } catch (e) {
482
+ logger.error(`Failed to approve pull request: ${e}`);
483
+ logger.debug(e);
484
+ return false;
485
+ }
486
+ }
487
+ /**
488
+ * Abandon a pull request.
489
+ * Requires scope "Code (Write)" (vso.code_write).
490
+ * @param pr
491
+ * @returns
492
+ */
493
+ async abandonPullRequest(pr) {
494
+ logger.info(`Abandoning pull request #${pr.pullRequestId}...`);
495
+ try {
496
+ const userId = await this.getUserId();
497
+ if (pr.comment) {
498
+ logger.info(` - Adding abandonment reason comment to pull request...`);
499
+ if (!(await this.restApiPost(`${this.organisationApiUrl}/${pr.project}/_apis/git/repositories/${pr.repository}/pullrequests/${pr.pullRequestId}/threads`, {
500
+ status: CommentThreadStatus.Closed,
501
+ comments: [{
502
+ author: { id: userId },
503
+ content: pr.comment,
504
+ commentType: CommentType.System
505
+ }]
506
+ }))?.id) throw new Error("Failed to add comment to pull request, thread was not created");
507
+ }
508
+ logger.info(` - Abandoning pull request...`);
509
+ const abandonedPullRequest = await this.restApiPatch(`${this.organisationApiUrl}/${pr.project}/_apis/git/repositories/${pr.repository}/pullrequests/${pr.pullRequestId}`, {
510
+ status: PullRequestStatus.Abandoned,
511
+ closedBy: { id: userId }
512
+ });
513
+ if (abandonedPullRequest?.status?.toLowerCase() !== "abandoned") throw new Error("Failed to abandon pull request, status was not updated");
514
+ if (pr.deleteSourceBranch) {
515
+ logger.info(` - Deleting source branch...`);
516
+ if ((await this.restApiPost(`${this.organisationApiUrl}/${pr.project}/_apis/git/repositories/${pr.repository}/refs`, [{
517
+ name: abandonedPullRequest.sourceRefName,
518
+ oldObjectId: abandonedPullRequest.lastMergeSourceCommit.commitId,
519
+ newObjectId: "0000000000000000000000000000000000000000",
520
+ isLocked: false
521
+ }]))?.value?.[0]?.success !== true) throw new Error("Failed to delete the source branch");
522
+ }
523
+ logger.info(` - Pull request was abandoned successfully.`);
524
+ return true;
525
+ } catch (e) {
526
+ logger.error(`Failed to abandon pull request: ${e}`);
527
+ logger.debug(e);
528
+ return false;
529
+ }
530
+ }
531
+ async restApiGet(url, params, apiVersion = AzureDevOpsWebApiClient.API_VERSION) {
532
+ params ??= {};
533
+ const queryString = Object.keys(params).map((key) => `${key}=${params[key]}`).join("&");
534
+ const fullUrl = `${url}?api-version=${apiVersion}${queryString ? `&${queryString}` : ""}`;
535
+ return await sendRestApiRequestWithRetry("GET", fullUrl, void 0, async () => {
536
+ return await fetch(fullUrl, {
537
+ method: "GET",
538
+ headers: {
539
+ Accept: "application/json",
540
+ Authorization: `Basic ${Buffer.from(`:${this.accessToken}`).toString("base64")}`
541
+ }
542
+ });
543
+ }, this.debug);
544
+ }
545
+ async restApiPost(url, data, apiVersion = AzureDevOpsWebApiClient.API_VERSION) {
546
+ const fullUrl = `${url}?api-version=${apiVersion}`;
547
+ return await sendRestApiRequestWithRetry("POST", fullUrl, data, async () => {
548
+ return await fetch(fullUrl, {
549
+ method: "POST",
550
+ headers: {
551
+ "Content-Type": "application/json",
552
+ Authorization: `Basic ${Buffer.from(`:${this.accessToken}`).toString("base64")}`
553
+ },
554
+ body: JSON.stringify(data)
555
+ });
556
+ }, this.debug);
557
+ }
558
+ async restApiPut(url, data, apiVersion = AzureDevOpsWebApiClient.API_VERSION) {
559
+ const fullUrl = `${url}?api-version=${apiVersion}`;
560
+ return await sendRestApiRequestWithRetry("PUT", fullUrl, data, async () => {
561
+ return await fetch(fullUrl, {
562
+ method: "PUT",
563
+ headers: {
564
+ "Content-Type": "application/json",
565
+ Authorization: `Basic ${Buffer.from(`:${this.accessToken}`).toString("base64")}`
566
+ },
567
+ body: JSON.stringify(data)
568
+ });
569
+ }, this.debug);
570
+ }
571
+ async restApiPatch(url, data, contentType, apiVersion = AzureDevOpsWebApiClient.API_VERSION) {
572
+ const fullUrl = `${url}?api-version=${apiVersion}`;
573
+ return await sendRestApiRequestWithRetry("PATCH", fullUrl, data, async () => {
574
+ return await fetch(fullUrl, {
575
+ method: "PATCH",
576
+ headers: {
577
+ "Content-Type": contentType || "application/json",
578
+ Authorization: `Basic ${Buffer.from(`:${this.accessToken}`).toString("base64")}`
579
+ },
580
+ body: JSON.stringify(data)
581
+ });
582
+ }, this.debug);
583
+ }
584
+ };
585
+ function mergeCommitMessage(id, title, description) {
586
+ return `Merged PR ${id}: ${title}\n\n${description}`.slice(0, 3500);
587
+ }
588
+ function isGuid(guid) {
589
+ 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);
590
+ }
591
+ function getIdentityApiUrl(organisationApiUrl) {
592
+ const uri = new URL(organisationApiUrl);
593
+ const hostname = uri.hostname.toLowerCase();
594
+ if (hostname === "dev.azure.com" || hostname.endsWith(".visualstudio.com")) uri.host = "vssps.dev.azure.com";
595
+ return uri.toString();
596
+ }
597
+ async function sendRestApiRequestWithRetry(method, url, payload, requestAsync, isDebug = false, retryCount = 3, retryDelay = 3e3) {
598
+ let body;
599
+ try {
600
+ if (isDebug) logger.debug(`🌎 🠊 [${method}] ${url}`);
601
+ const response = await requestAsync();
602
+ body = await response.text();
603
+ const { status: statusCode, statusText: statusMessage } = response;
604
+ if (isDebug) logger.debug(`🌎 🠈 [${statusCode}] ${statusMessage}`);
605
+ if (statusCode < 200 || statusCode > 299) throw new HttpRequestError(`HTTP ${method} '${url}' failed: ${statusCode} ${statusMessage}`, statusCode);
606
+ return JSON.parse(body);
607
+ } catch (e) {
608
+ const err = e;
609
+ if (retryCount > 1 && isErrorTemporaryFailure(err)) {
610
+ logger.warn(err.message);
611
+ if (isDebug) logger.debug(`⏳ Retrying request in ${retryDelay}ms...`);
612
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
613
+ return sendRestApiRequestWithRetry(method, url, payload, requestAsync, isDebug, retryCount - 1, retryDelay);
614
+ }
615
+ if (isDebug) {
616
+ if (payload) logger.debug(`REQUEST: ${JSON.stringify(payload)}`);
617
+ if (body) logger.debug(`RESPONSE: ${body}`);
618
+ }
619
+ logger.trace(`THROW${e}`);
620
+ throw e;
621
+ }
622
+ }
623
+
624
+ //#endregion
625
+ //#region src/azure/config.ts
626
+ /**
627
+ * Parse the dependabot config YAML file to specify update configuration.
628
+ * The file should be located at any of `POSSIBLE_CONFIG_FILE_PATHS`.
629
+ *
630
+ * To view YAML file format, visit
631
+ * https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates#allow
632
+ *
633
+ * @returns {DependabotConfig} config - the dependabot configuration
634
+ */
635
+ async function getDependabotConfig({ url, token, remote, rootDir = process.cwd(), variableFinder }) {
636
+ let configPath;
637
+ let configContents;
638
+ if (remote) {
639
+ logger.debug(`Attempting to fetch configuration file via REST API ...`);
640
+ for (const fp of POSSIBLE_CONFIG_FILE_PATHS) {
641
+ const requestUrl = `${url.url}${url.project}/_apis/git/repositories/${url.repository}/items?path=/${fp}`;
642
+ logger.debug(`GET ${requestUrl}`);
643
+ try {
644
+ const authHeader = `Basic ${Buffer.from(`x-access-token:${token}`).toString("base64")}`;
645
+ const response = await fetch(requestUrl, { headers: {
646
+ Authorization: authHeader,
647
+ Accept: "*/*"
648
+ } });
649
+ if (response.ok) {
650
+ logger.debug(`Found configuration file at '${requestUrl}'`);
651
+ configContents = await response.text();
652
+ configPath = fp;
653
+ break;
654
+ } else if (response.status === 404) {
655
+ logger.trace(`No configuration file at '${requestUrl}'`);
656
+ continue;
657
+ } else if (response.status === 401) throw new Error(`No or invalid access token has been provided to access '${requestUrl}'`);
658
+ else if (response.status === 403) throw new Error(`The access token provided does not have permissions to access '${requestUrl}'`);
659
+ } catch (error) {
660
+ if (error instanceof Error && error.message.includes("access token")) throw error;
661
+ else throw error;
662
+ }
663
+ }
664
+ } else for (const fp of POSSIBLE_CONFIG_FILE_PATHS) {
665
+ const filePath = path.join(rootDir, fp);
666
+ if (existsSync(filePath)) {
667
+ logger.debug(`Found configuration file cloned at ${filePath}`);
668
+ configContents = await readFile(filePath, "utf-8");
669
+ configPath = filePath;
670
+ break;
671
+ } else logger.trace(`No configuration file cloned at ${filePath}`);
672
+ }
673
+ if (!configContents || !configPath || typeof configContents !== "string") throw new Error(`Configuration file not found at possible locations: ${POSSIBLE_CONFIG_FILE_PATHS.join(", ")}`);
674
+ else logger.trace("Configuration file contents read.");
675
+ return await parseDependabotConfig({
676
+ configContents,
677
+ configPath,
678
+ variableFinder
679
+ });
680
+ }
681
+
682
+ //#endregion
683
+ //#region src/azure/url-parts.ts
684
+ function extractUrlParts({ organisationUrl, project, repository }) {
685
+ const url = new URL(organisationUrl);
686
+ const protocol = url.protocol.slice(0, -1);
687
+ let { hostname } = url;
688
+ if (/^(?<prefix>\S+)\.visualstudio\.com$/iu.test(hostname)) hostname = "dev.azure.com";
689
+ const organisation = extractOrganisation(organisationUrl);
690
+ const virtualDirectory = extractVirtualDirectory(url);
691
+ const apiEndpoint = `${protocol}://${hostname}${url.port ? `:${url.port}` : ""}/${virtualDirectory ? `${virtualDirectory}/` : ""}`;
692
+ const escapedProject = encodeURI(project);
693
+ const escapedRepository = encodeURI(repository);
694
+ const repoSlug = `${virtualDirectory ? `${virtualDirectory}/` : ""}${organisation}/${escapedProject}/_git/${escapedRepository}`;
695
+ return {
696
+ url,
697
+ hostname,
698
+ "api-endpoint": apiEndpoint,
699
+ project: escapedProject,
700
+ repository: escapedRepository,
701
+ "repository-slug": repoSlug
702
+ };
703
+ }
704
+ /**
705
+ * Extract organisation name from organisation URL
706
+ *
707
+ * @param organisationUrl
708
+ *
709
+ * @returns organisation name
710
+ */
711
+ function extractOrganisation(organisationUrl) {
712
+ const parts = organisationUrl.split("/");
713
+ if (parts.length === 6) return parts[4];
714
+ if (parts.length === 5) return parts[3];
715
+ if (parts.length === 4) return parts[2].split(".")[0];
716
+ throw new Error(`Error parsing organisation from organisation url: '${organisationUrl}'.`);
717
+ }
718
+ /**
719
+ * Extract virtual directory from organisation URL
720
+ *
721
+ * Virtual Directories are sometimes used in on-premises
722
+ * @param organisationUrl
723
+ *
724
+ * @returns virtual directory
725
+ *
726
+ * @example URLs typically are like this:`https://server.domain.com/tfs/x/` and `tfs` is the virtual directory
727
+ */
728
+ function extractVirtualDirectory(organisationUrl) {
729
+ const path$1 = organisationUrl.pathname.split("/");
730
+ return path$1.length === 4 ? path$1[1] : void 0;
731
+ }
732
+
733
+ //#endregion
734
+ export { 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, extractUrlParts, getDependabotConfig, getPullRequestChangedFilesForOutputData, getPullRequestCloseReasonForOutputData, getPullRequestDependenciesPropertyValueForOutputData, getPullRequestDescription, getPullRequestForDependencyNames, normalizeBranchName, normalizeFilePath, parsePullRequestProperties, sendRestApiRequestWithRetry };
735
+ //# sourceMappingURL=azure.js.map