forges 0.0.0 → 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 (152) hide show
  1. package/LICENCE +21 -0
  2. package/README.md +72 -0
  3. package/dist/azure-devops/index.d.mts +2 -0
  4. package/dist/azure-devops/index.mjs +1 -0
  5. package/dist/azure-devops-DwQvL4TV.mjs +1355 -0
  6. package/dist/bitbucket/index.d.mts +2 -0
  7. package/dist/bitbucket/index.mjs +1 -0
  8. package/dist/bitbucket-CpcfJlYi.mjs +1171 -0
  9. package/dist/crypto-CQNEyYHD.mjs +156 -0
  10. package/dist/cursor-origin/index.d.mts +2 -0
  11. package/dist/cursor-origin/index.mjs +1 -0
  12. package/dist/cursor-origin-BhqYx2oW.mjs +980 -0
  13. package/dist/define-B2_4ob2i.d.mts +307 -0
  14. package/dist/define-D3yEXtKK.mjs +2276 -0
  15. package/dist/env.d.mts +46 -0
  16. package/dist/env.mjs +283 -0
  17. package/dist/fake/index.d.mts +227 -0
  18. package/dist/fake/index.mjs +1001 -0
  19. package/dist/fetch-D4pSRvvA.d.mts +1723 -0
  20. package/dist/forgejo/index.d.mts +2 -0
  21. package/dist/forgejo/index.mjs +1 -0
  22. package/dist/forgejo-BCefbHUN.mjs +2005 -0
  23. package/dist/gitea/index.d.mts +2 -0
  24. package/dist/gitea/index.mjs +22 -0
  25. package/dist/gitee/index.d.mts +2 -0
  26. package/dist/gitee/index.mjs +1 -0
  27. package/dist/gitee-CA-f7Rs8.mjs +1272 -0
  28. package/dist/github/index.d.mts +2 -0
  29. package/dist/github/index.mjs +1 -0
  30. package/dist/github-B0umL8_f.mjs +3259 -0
  31. package/dist/gitlab/index.d.mts +2 -0
  32. package/dist/gitlab/index.mjs +1 -0
  33. package/dist/gitlab-Bsr__Z0C.mjs +2378 -0
  34. package/dist/graphql-D7uGzZTf.mjs +366 -0
  35. package/dist/index-BQ6VS31s.d.mts +32 -0
  36. package/dist/index-C-jz9Mo3.d.mts +16 -0
  37. package/dist/index-C4fYzmK3.d.mts +102 -0
  38. package/dist/index-CQiIX4SH.d.mts +27 -0
  39. package/dist/index-CTj5gQzl.d.mts +35 -0
  40. package/dist/index-CliBWzOF.d.mts +23 -0
  41. package/dist/index-Cz_y-t3d.d.mts +36 -0
  42. package/dist/index-DEt25E1w.d.mts +77 -0
  43. package/dist/index-DIuS0EOy.d.mts +43 -0
  44. package/dist/index-uSpsMCrC.d.mts +26 -0
  45. package/dist/index.d.mts +41 -0
  46. package/dist/index.mjs +13 -0
  47. package/dist/kit.d.mts +183 -0
  48. package/dist/kit.mjs +4 -0
  49. package/dist/provider-CoZ7cCxp.d.mts +1439 -0
  50. package/dist/provider-Ed1YhkQN.mjs +169 -0
  51. package/dist/pushin/index.d.mts +2 -0
  52. package/dist/pushin/index.mjs +1 -0
  53. package/dist/pushin-CkxgJ2cU.mjs +343 -0
  54. package/dist/schema/Actor.json +96 -0
  55. package/dist/schema/AlertSupport.json +49 -0
  56. package/dist/schema/AuthKind.json +15 -0
  57. package/dist/schema/Branch.json +31 -0
  58. package/dist/schema/ChangedFile.json +58 -0
  59. package/dist/schema/Check.json +169 -0
  60. package/dist/schema/CheckRef.json +114 -0
  61. package/dist/schema/CheckState.json +14 -0
  62. package/dist/schema/ChecksSummary.json +34 -0
  63. package/dist/schema/CiJob.json +186 -0
  64. package/dist/schema/CiJobRef.json +132 -0
  65. package/dist/schema/CiRun.json +234 -0
  66. package/dist/schema/CiRunRef.json +103 -0
  67. package/dist/schema/CodeFragment.json +19 -0
  68. package/dist/schema/CodeMatch.json +128 -0
  69. package/dist/schema/Collaborator.json +169 -0
  70. package/dist/schema/Comment.json +336 -0
  71. package/dist/schema/CommentRef.json +158 -0
  72. package/dist/schema/Commit.json +307 -0
  73. package/dist/schema/CommitRef.json +104 -0
  74. package/dist/schema/CommitSignature.json +118 -0
  75. package/dist/schema/Comparison.json +355 -0
  76. package/dist/schema/Cursor.json +21 -0
  77. package/dist/schema/EventAction.json +33 -0
  78. package/dist/schema/EventDetail.json +649 -0
  79. package/dist/schema/EventKind.json +26 -0
  80. package/dist/schema/FileContent.json +73 -0
  81. package/dist/schema/FileStatus.json +15 -0
  82. package/dist/schema/ForgeCapabilities.json +737 -0
  83. package/dist/schema/ForgeEvent.json +789 -0
  84. package/dist/schema/ForgeInstance.json +7 -0
  85. package/dist/schema/ForgeKind.json +32 -0
  86. package/dist/schema/ForgeWarning.json +49 -0
  87. package/dist/schema/ForgeWarningCode.json +7 -0
  88. package/dist/schema/GetManyResult.json +699 -0
  89. package/dist/schema/Installation.json +131 -0
  90. package/dist/schema/InstallationToken.json +31 -0
  91. package/dist/schema/KnownForgeKind.json +19 -0
  92. package/dist/schema/Label.json +22 -0
  93. package/dist/schema/MergeMethod.json +14 -0
  94. package/dist/schema/Milestone.json +40 -0
  95. package/dist/schema/Notification.json +498 -0
  96. package/dist/schema/NotificationReason.json +28 -0
  97. package/dist/schema/NotificationRef.json +60 -0
  98. package/dist/schema/NotificationSubject.json +376 -0
  99. package/dist/schema/PerKind.json +49 -0
  100. package/dist/schema/PullBranches.json +125 -0
  101. package/dist/schema/PushCommit.json +24 -0
  102. package/dist/schema/Reaction.json +148 -0
  103. package/dist/schema/ReactionContent.json +17 -0
  104. package/dist/schema/ReactionSummary.json +70 -0
  105. package/dist/schema/Release.json +286 -0
  106. package/dist/schema/ReleaseAsset.json +169 -0
  107. package/dist/schema/ReleaseAssetRef.json +138 -0
  108. package/dist/schema/ReleaseRef.json +107 -0
  109. package/dist/schema/Repo.json +354 -0
  110. package/dist/schema/RepoFeatures.json +35 -0
  111. package/dist/schema/RepoPermissions.json +35 -0
  112. package/dist/schema/RepoRef.json +77 -0
  113. package/dist/schema/RepoRole.json +15 -0
  114. package/dist/schema/RepoVisibility.json +12 -0
  115. package/dist/schema/Review.json +371 -0
  116. package/dist/schema/ReviewComment.json +280 -0
  117. package/dist/schema/ReviewCommentInput.json +34 -0
  118. package/dist/schema/ReviewRef.json +158 -0
  119. package/dist/schema/ReviewState.json +15 -0
  120. package/dist/schema/Reviewer.json +133 -0
  121. package/dist/schema/SecurityAlert.json +219 -0
  122. package/dist/schema/SecurityAlertKind.json +14 -0
  123. package/dist/schema/SecurityAlertRef.json +122 -0
  124. package/dist/schema/SecuritySeverity.json +14 -0
  125. package/dist/schema/SourceKind.json +12 -0
  126. package/dist/schema/SubscriptionState.json +12 -0
  127. package/dist/schema/Support.json +23 -0
  128. package/dist/schema/Tag.json +25 -0
  129. package/dist/schema/TextLimits.json +18 -0
  130. package/dist/schema/Thread.json +653 -0
  131. package/dist/schema/ThreadKind.json +14 -0
  132. package/dist/schema/ThreadRef.json +131 -0
  133. package/dist/schema/ThreadStack.json +146 -0
  134. package/dist/schema/ThreadState.json +13 -0
  135. package/dist/schema/TreeEntry.json +46 -0
  136. package/dist/schema/TreeEntryType.json +13 -0
  137. package/dist/schema/UpsertCommentResult.json +355 -0
  138. package/dist/schema/User.json +123 -0
  139. package/dist/schema/Webhook.json +188 -0
  140. package/dist/schema/WebhookDeliveryRecord.json +173 -0
  141. package/dist/schema/WebhookDeliveryRef.json +135 -0
  142. package/dist/schema/WebhookRef.json +105 -0
  143. package/dist/schema/index.d.mts +4 -0
  144. package/dist/schema/index.mjs +3219 -0
  145. package/dist/tangled/index.d.mts +2 -0
  146. package/dist/tangled/index.mjs +1 -0
  147. package/dist/tangled-ChdPmiv-.mjs +1570 -0
  148. package/dist/testing/index.d.mts +123 -0
  149. package/dist/testing/index.mjs +242 -0
  150. package/dist/webhooks-rn9-8lxT.mjs +70 -0
  151. package/package.json +115 -3
  152. package/dist/.gitkeep +0 -0
@@ -0,0 +1,1171 @@
1
+ import { A as toPage, B as isNamespaceRef, D as syntheticReview, E as summariseChecks, Mt as WebhookVerificationError, Nt as soleMergeMethod, O as toDate, Pt as toMergeError, S as phased, St as MergeConflictError, T as resolveToken, _t as ForgeError, b as mergeCommitMessage, f as getManyConcurrently, ft as toFileContent, h as hostOf, j as toWarning, jt as UnsupportedOperationError, k as toFileStatus, l as createListing, n as perKind, r as verb, t as defineForgeProvider, w as requireThread } from "./define-D3yEXtKK.mjs";
2
+ import { l as toBase64, n as headerValue, t as bodyText } from "./crypto-CQNEyYHD.mjs";
3
+ import { a as verifyHmacSignature, i as refEvent, r as nativeEventsFor, t as eventKindsOf } from "./webhooks-rn9-8lxT.mjs";
4
+ //#region src/bitbucket/webhook-events.ts
5
+ /** Native Bitbucket event names per normalised kind, for hook subscriptions. */
6
+ const BITBUCKET_NATIVE_EVENTS = {
7
+ comment: [
8
+ "pullrequest:comment_created",
9
+ "pullrequest:comment_updated",
10
+ "pullrequest:comment_deleted"
11
+ ],
12
+ review: [
13
+ "pullrequest:approved",
14
+ "pullrequest:unapproved",
15
+ "pullrequest:changes_request_created"
16
+ ],
17
+ state_change: [
18
+ "pullrequest:created",
19
+ "pullrequest:updated",
20
+ "pullrequest:fulfilled",
21
+ "pullrequest:rejected"
22
+ ],
23
+ push: ["repo:push"],
24
+ ref: ["repo:push"],
25
+ repo: ["repo:updated", "repo:transfer"]
26
+ };
27
+ /** Normalised kinds and actions Bitbucket deliveries translate into. */
28
+ const BITBUCKET_WEBHOOK_EVENTS = [
29
+ {
30
+ kind: "comment",
31
+ action: "created"
32
+ },
33
+ {
34
+ kind: "comment",
35
+ action: "edited"
36
+ },
37
+ {
38
+ kind: "comment",
39
+ action: "deleted"
40
+ },
41
+ {
42
+ kind: "review",
43
+ action: "submitted"
44
+ },
45
+ {
46
+ kind: "state_change",
47
+ action: "opened"
48
+ },
49
+ {
50
+ kind: "state_change",
51
+ action: "closed"
52
+ },
53
+ {
54
+ kind: "state_change",
55
+ action: "merged"
56
+ },
57
+ { kind: "push" },
58
+ {
59
+ kind: "ref",
60
+ action: "created"
61
+ },
62
+ {
63
+ kind: "ref",
64
+ action: "deleted"
65
+ },
66
+ {
67
+ kind: "repo",
68
+ action: "renamed"
69
+ },
70
+ {
71
+ kind: "repo",
72
+ action: "transferred"
73
+ }
74
+ ];
75
+ //#endregion
76
+ //#region src/bitbucket/normalise.ts
77
+ const FORGE = "bitbucket";
78
+ function toActor(instance, user) {
79
+ if (!user) return;
80
+ const login = user.nickname ?? user.username ?? user.display_name ?? user.uuid ?? "unknown";
81
+ return {
82
+ forge: FORGE,
83
+ instance,
84
+ login,
85
+ id: user.uuid ?? user.account_id ?? login,
86
+ name: user.display_name,
87
+ avatarUrl: user.links?.avatar?.href,
88
+ url: user.links?.html?.href,
89
+ typeRaw: user.type,
90
+ isBotHint: user.type === "app_user"
91
+ };
92
+ }
93
+ function toRepoRef(instance, repo) {
94
+ const [workspace = "", slug = ""] = repo.full_name.split("/");
95
+ return {
96
+ forge: FORGE,
97
+ instance,
98
+ owner: repo.workspace?.slug ?? workspace,
99
+ name: repo.slug ?? slug,
100
+ externalId: repo.uuid
101
+ };
102
+ }
103
+ function toRepo(instance, raw) {
104
+ const clone = (name) => raw.links?.clone?.find((link) => link.name === name)?.href;
105
+ return {
106
+ ref: toRepoRef(instance, raw),
107
+ description: raw.description || void 0,
108
+ defaultBranch: raw.mainbranch?.name,
109
+ visibility: raw.is_private ? "private" : "public",
110
+ visibilityRaw: raw.is_private === void 0 ? void 0 : raw.is_private ? "private" : "public",
111
+ isFork: Boolean(raw.parent),
112
+ isArchived: false,
113
+ parent: raw.parent ? toRepoRef(instance, raw.parent) : void 0,
114
+ topics: [],
115
+ url: raw.links?.html?.href,
116
+ cloneUrls: clone("https") || clone("ssh") ? {
117
+ https: clone("https"),
118
+ ssh: clone("ssh")
119
+ } : void 0,
120
+ createdAt: toDate(raw.created_on),
121
+ updatedAt: toDate(raw.updated_on),
122
+ owner: toActor(instance, raw.owner),
123
+ language: raw.language || void 0,
124
+ homepage: raw.website || void 0,
125
+ features: raw.has_issues === void 0 ? void 0 : {
126
+ issues: raw.has_issues,
127
+ pullRequests: true,
128
+ discussions: false,
129
+ wiki: raw.has_wiki ?? false,
130
+ projects: false,
131
+ releases: false
132
+ },
133
+ raw
134
+ };
135
+ }
136
+ function toPullState(state) {
137
+ switch (state) {
138
+ case "OPEN": return "open";
139
+ case "MERGED": return "merged";
140
+ case "DECLINED":
141
+ case "SUPERSEDED": return "closed";
142
+ default: return "unknown";
143
+ }
144
+ }
145
+ const STRATEGIES = {
146
+ merge_commit: "merge",
147
+ squash: "squash",
148
+ fast_forward: "fast_forward_only",
149
+ rebase_merge: "rebase_merge",
150
+ rebase_fast_forward: "rebase"
151
+ };
152
+ function toMergeMethod(strategy) {
153
+ return STRATEGIES[strategy];
154
+ }
155
+ function toStrategy(method) {
156
+ return Object.entries(STRATEGIES).find(([, value]) => value === method)[0];
157
+ }
158
+ function toReviewers(instance, raw) {
159
+ const participants = new Map((raw.participants ?? []).map((participant) => [participant.user?.uuid, participant]));
160
+ return (raw.reviewers ?? []).flatMap((user) => {
161
+ const actor = toActor(instance, user);
162
+ if (!actor) return [];
163
+ const participant = participants.get(user.uuid);
164
+ const stateRaw = participant?.state ?? (participant?.approved ? "approved" : void 0);
165
+ return [{
166
+ actor,
167
+ state: stateRaw === "approved" ? "approved" : stateRaw === "changes_requested" ? "changes_requested" : "pending",
168
+ stateRaw: stateRaw ?? void 0
169
+ }];
170
+ });
171
+ }
172
+ function toPullThread(ref, raw) {
173
+ const repo = raw.destination?.repository;
174
+ const threadRepo = repo ? {
175
+ ...ref.repo,
176
+ externalId: repo.uuid ?? ref.repo.externalId
177
+ } : ref.repo;
178
+ return {
179
+ ref: {
180
+ ...ref,
181
+ repo: threadRepo
182
+ },
183
+ kind: "pull_request",
184
+ title: raw.title,
185
+ body: raw.description ?? raw.summary?.raw,
186
+ state: toPullState(raw.state),
187
+ stateRaw: raw.state,
188
+ isDraft: raw.draft ?? false,
189
+ assignees: [],
190
+ reviewers: toReviewers(ref.instance, raw),
191
+ lastActivityAt: toDate(raw.updated_on),
192
+ branches: raw.source?.branch && raw.destination?.branch ? {
193
+ head: {
194
+ ref: raw.source.branch.name,
195
+ sha: raw.source.commit?.hash,
196
+ repo: raw.source.repository ? toRepoRef(ref.instance, raw.source.repository) : void 0
197
+ },
198
+ base: {
199
+ ref: raw.destination.branch.name,
200
+ sha: raw.destination.commit?.hash
201
+ },
202
+ mergeCommitSha: raw.merge_commit?.hash
203
+ } : void 0,
204
+ author: toActor(ref.instance, raw.author),
205
+ url: raw.links?.html?.href,
206
+ createdAt: toDate(raw.created_on),
207
+ updatedAt: toDate(raw.updated_on),
208
+ closedAt: raw.state === "OPEN" ? void 0 : toDate(raw.updated_on),
209
+ labels: [],
210
+ commentCount: raw.comment_count,
211
+ raw
212
+ };
213
+ }
214
+ function toCommitThread(ref, raw) {
215
+ return {
216
+ ref,
217
+ kind: "commit",
218
+ title: raw.message?.split("\n")[0] ?? raw.hash,
219
+ body: raw.message,
220
+ state: "unknown",
221
+ isDraft: false,
222
+ assignees: [],
223
+ reviewers: [],
224
+ lastActivityAt: toDate(raw.date),
225
+ author: toActor(ref.instance, raw.author?.user),
226
+ url: raw.links?.html?.href,
227
+ createdAt: toDate(raw.date),
228
+ labels: [],
229
+ raw
230
+ };
231
+ }
232
+ function event(thread, id, kind, kindRaw, actor, summary, at, payload) {
233
+ return {
234
+ forge: FORGE,
235
+ instance: thread.instance,
236
+ id,
237
+ kind,
238
+ kindRaw,
239
+ summary: `${actor?.login ?? "someone"} ${summary}`,
240
+ occurredAt: toDate(at) ?? /* @__PURE__ */ new Date(0),
241
+ actor,
242
+ repo: thread.repo,
243
+ thread,
244
+ source: "poll",
245
+ payload
246
+ };
247
+ }
248
+ function toCommentEvent(thread, comment) {
249
+ const inline = Boolean(comment.inline);
250
+ return event(thread, `comment:${comment.id}`, inline ? "review_comment" : "comment", inline ? "comment.inline" : "comment", toActor(thread.instance, comment.user), inline ? "commented on a line" : "commented", comment.created_on, comment);
251
+ }
252
+ /** Pull request activity entries are a union keyed by their single property. */
253
+ function toActivityEvent(thread, entry, index) {
254
+ if (entry.comment) return toCommentEvent(thread, entry.comment);
255
+ if (entry.approval) return event(thread, `approval:${entry.approval.user?.uuid ?? index}:${entry.approval.date}`, "review", "approval", toActor(thread.instance, entry.approval.user), "approved", entry.approval.date, entry);
256
+ if (entry.changes_requested) return event(thread, `changes_requested:${entry.changes_requested.user?.uuid ?? index}:${entry.changes_requested.date}`, "review", "changes_requested", toActor(thread.instance, entry.changes_requested.user), "requested changes", entry.changes_requested.date, entry);
257
+ const update = entry.update;
258
+ const terminal = update?.state && update.state !== "OPEN";
259
+ return event(thread, `update:${update?.date ?? index}`, terminal ? "state_change" : "other", terminal ? `update.${update.state}` : "update", toActor(thread.instance, update?.author), terminal ? update.state.toLowerCase() : "updated the pull request", update?.date, entry);
260
+ }
261
+ function toComment(thread, raw) {
262
+ return {
263
+ ref: {
264
+ forge: FORGE,
265
+ instance: thread.instance,
266
+ thread,
267
+ id: String(raw.id)
268
+ },
269
+ body: raw.content?.raw ?? "",
270
+ author: toActor(thread.instance, raw.user),
271
+ createdAt: toDate(raw.created_on),
272
+ updatedAt: toDate(raw.updated_on),
273
+ url: raw.links?.html?.href,
274
+ raw
275
+ };
276
+ }
277
+ /** Bitbucket renders Markdown without HTML, so collapsible blocks become a bold heading and their content. */
278
+ function normaliseMarkdown(body) {
279
+ return body.replace(/<summary>([\s\S]*?)<\/summary>/gi, (_, summary) => `**${summary.trim()}**\n`).replace(/<\/?details[^>]*>/gi, "");
280
+ }
281
+ /** `STOPPED` builds were cancelled, which counts as a failure. */
282
+ function statusState(state) {
283
+ return state === "SUCCESSFUL" ? "success" : state === "FAILED" || state === "STOPPED" ? "failure" : state === "INPROGRESS" ? "pending" : "unknown";
284
+ }
285
+ function toStatusCheck(repo, raw) {
286
+ const state = statusState(raw.state);
287
+ return {
288
+ ref: {
289
+ forge: FORGE,
290
+ instance: repo.instance,
291
+ repo,
292
+ id: raw.uuid ?? raw.key,
293
+ type: "status"
294
+ },
295
+ name: raw.name ?? raw.key,
296
+ state,
297
+ stateRaw: raw.state,
298
+ url: raw.url,
299
+ startedAt: toDate(raw.created_on),
300
+ completedAt: state === "pending" ? void 0 : toDate(raw.updated_on),
301
+ raw
302
+ };
303
+ }
304
+ function toCommit(repo, raw, files) {
305
+ const authored = raw.author?.raw;
306
+ const match = authored ? /^(?<name>[^<]*)<(?<email>[^>]*)>$/.exec(authored) : void 0;
307
+ return {
308
+ ref: {
309
+ forge: FORGE,
310
+ instance: repo.instance,
311
+ repo,
312
+ sha: raw.hash
313
+ },
314
+ sha: raw.hash,
315
+ message: raw.message ?? raw.summary?.raw ?? "",
316
+ author: {
317
+ actor: toActor(repo.instance, raw.author?.user),
318
+ name: match?.groups?.name?.trim() || authored,
319
+ email: match?.groups?.email,
320
+ date: toDate(raw.date)
321
+ },
322
+ parents: raw.parents?.flatMap((parent) => parent.hash ? [parent.hash] : []) ?? [],
323
+ url: raw.links?.html?.href,
324
+ ...files ? { files } : {},
325
+ raw
326
+ };
327
+ }
328
+ /** Bitbucket reports changes as a diffstat, with the old and new paths rather than a rename flag. */
329
+ function toChangedFile(raw) {
330
+ return {
331
+ path: raw.new?.path ?? raw.old?.path ?? "",
332
+ previousPath: raw.status === "renamed" ? raw.old?.path : void 0,
333
+ status: toFileStatus(raw.status),
334
+ statusRaw: raw.status,
335
+ additions: raw.lines_added,
336
+ deletions: raw.lines_removed
337
+ };
338
+ }
339
+ function toBranch(raw, defaultBranch) {
340
+ return {
341
+ name: raw.name,
342
+ sha: raw.target?.hash ?? "",
343
+ isDefault: defaultBranch === void 0 ? void 0 : raw.name === defaultBranch,
344
+ url: raw.links?.html?.href,
345
+ raw
346
+ };
347
+ }
348
+ function toTag(raw) {
349
+ return {
350
+ name: raw.name,
351
+ sha: raw.target?.hash ?? "",
352
+ url: raw.links?.html?.href,
353
+ raw
354
+ };
355
+ }
356
+ function toTreeEntry(raw) {
357
+ return {
358
+ path: raw.path,
359
+ type: raw.type === "commit_directory" ? "directory" : raw.attributes?.includes("link") ? "symlink" : "file",
360
+ size: raw.size,
361
+ sha: raw.commit?.hash
362
+ };
363
+ }
364
+ function toWebhook(target, raw) {
365
+ const nativeEvents = raw.events ?? [];
366
+ return {
367
+ ref: {
368
+ forge: FORGE,
369
+ instance: target.instance,
370
+ target,
371
+ id: raw.uuid
372
+ },
373
+ url: raw.url,
374
+ events: eventKindsOf(BITBUCKET_NATIVE_EVENTS, nativeEvents),
375
+ nativeEvents,
376
+ active: raw.active ?? true,
377
+ createdAt: toDate(raw.created_at),
378
+ raw
379
+ };
380
+ }
381
+ //#endregion
382
+ //#region src/bitbucket/web.ts
383
+ const SECTIONS = {
384
+ issue: "issues",
385
+ pull_request: "pull-requests",
386
+ commit: "commits"
387
+ };
388
+ const KINDS = new Map(Object.entries(SECTIONS).map(([kind, section]) => [section, kind]));
389
+ const repoPath = (repo) => `/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.name)}`;
390
+ function bitbucketWeb(origin) {
391
+ return {
392
+ origin,
393
+ repo: repoPath,
394
+ thread: (ref) => SECTIONS[ref.kind] && `${repoPath(ref.repo)}/${SECTIONS[ref.kind]}/${encodeURIComponent(ref.number)}`,
395
+ comment: (ref) => ref.thread.kind === "commit" || !SECTIONS[ref.thread.kind] ? void 0 : `${repoPath(ref.thread.repo)}/${SECTIONS[ref.thread.kind]}/${encodeURIComponent(ref.thread.number)}#comment-${ref.id}`,
396
+ file: (repo, path, at, line) => `${repoPath(repo)}/src/${encodeURIComponent(at)}/${path.split("/").map(encodeURIComponent).join("/")}${line ? `#lines-${line}` : ""}`,
397
+ compare: (repo, base, head) => `${repoPath(repo)}/branches/compare/${encodeURIComponent(head)}%0D${encodeURIComponent(base)}`,
398
+ parse: (segments, url, from) => {
399
+ const [owner, name, section, id] = segments;
400
+ if (!owner || !name) return;
401
+ const repo = {
402
+ ...from,
403
+ owner,
404
+ name: name.replace(/\.git$/, "")
405
+ };
406
+ const kind = section ? KINDS.get(section) : void 0;
407
+ if (!kind || !id) return { repo };
408
+ const thread = {
409
+ ...from,
410
+ repo,
411
+ kind,
412
+ number: id
413
+ };
414
+ const comment = url.hash.startsWith("#comment-") ? url.hash.slice(9) : void 0;
415
+ return {
416
+ repo,
417
+ thread,
418
+ ...comment ? { comment: {
419
+ ...from,
420
+ thread,
421
+ id: comment
422
+ } } : {}
423
+ };
424
+ },
425
+ reference: (ref, same) => {
426
+ if (!same) return;
427
+ return ref.kind === "issue" ? `#${ref.number}` : ref.kind === "pull_request" ? `pull request #${ref.number}` : ref.kind === "commit" ? ref.number.slice(0, 12) : void 0;
428
+ }
429
+ };
430
+ }
431
+ //#endregion
432
+ //#region src/bitbucket/webhooks.ts
433
+ const SIGNATURE_HEADER = "x-hub-signature";
434
+ const EVENT_HEADER = "x-event-key";
435
+ /** Bitbucket signs with `X-Hub-Signature: sha256=<hex>` only when the webhook has a secret. */
436
+ function verifyBitbucketSignature(delivery, secret) {
437
+ return verifyHmacSignature(delivery, secret, {
438
+ header: SIGNATURE_HEADER,
439
+ prefix: "sha256="
440
+ });
441
+ }
442
+ const EVENT_KINDS = {
443
+ "pullrequest:created": "state_change",
444
+ "pullrequest:fulfilled": "state_change",
445
+ "pullrequest:rejected": "state_change",
446
+ "pullrequest:approved": "review",
447
+ "pullrequest:unapproved": "review",
448
+ "pullrequest:changes_request_created": "review",
449
+ "pullrequest:changes_request_removed": "review",
450
+ "pullrequest:comment_created": "comment"
451
+ };
452
+ function translateBitbucketWebhook(instance, delivery) {
453
+ const event = headerValue(delivery.headers, EVENT_HEADER);
454
+ if (!event) throw new WebhookVerificationError(`Missing ${EVENT_HEADER} header`, {
455
+ forge: FORGE,
456
+ instance
457
+ });
458
+ const deliveryId = headerValue(delivery.headers, "x-request-uuid") ?? crypto.randomUUID();
459
+ const payload = JSON.parse(bodyText(delivery.body));
460
+ const repo = payload.repository ? toRepoRef(instance, payload.repository) : void 0;
461
+ const actor = toActor(instance, payload.actor);
462
+ const who = actor?.login ?? "someone";
463
+ const thread = repo && payload.pullrequest ? {
464
+ forge: FORGE,
465
+ instance,
466
+ repo,
467
+ kind: "pull_request",
468
+ number: String(payload.pullrequest.id)
469
+ } : void 0;
470
+ if (event === "repo:push" && payload.push) return payload.push.changes.map((change, index) => {
471
+ const ref = change.new ?? change.old;
472
+ const refType = ref?.type === "tag" ? "tag" : "branch";
473
+ const name = ref?.name ?? "";
474
+ return {
475
+ forge: FORGE,
476
+ instance,
477
+ id: `${deliveryId}:${index}`,
478
+ kindRaw: event,
479
+ actor,
480
+ repo,
481
+ source: "webhook",
482
+ payload: {
483
+ ...payload,
484
+ push: { changes: [change] }
485
+ },
486
+ ...refEvent(who, {
487
+ ref: name,
488
+ refType,
489
+ before: change.old?.target?.hash,
490
+ after: change.new?.target?.hash,
491
+ deleted: change.closed,
492
+ created: change.created && !change.commits?.length,
493
+ forced: change.forced,
494
+ commits: (change.commits ?? []).map((commit) => ({
495
+ sha: commit.hash,
496
+ message: commit.message,
497
+ author: commit.author?.raw,
498
+ url: commit.links?.html?.href
499
+ }))
500
+ }),
501
+ occurredAt: toDate(change.commits?.[0]?.date) ?? /* @__PURE__ */ new Date()
502
+ };
503
+ });
504
+ if (event === "repo:updated" && payload.changes?.name) return [{
505
+ forge: FORGE,
506
+ instance,
507
+ id: deliveryId,
508
+ kind: "repo",
509
+ action: "renamed",
510
+ kindRaw: event,
511
+ summary: `${who} renamed the repository`,
512
+ occurredAt: /* @__PURE__ */ new Date(),
513
+ actor,
514
+ repo,
515
+ detail: {
516
+ type: "repo_renamed",
517
+ from: payload.changes.name.old,
518
+ to: payload.changes.name.new
519
+ },
520
+ source: "webhook",
521
+ payload
522
+ }];
523
+ if (event === "repo:transfer") return [{
524
+ forge: FORGE,
525
+ instance,
526
+ id: deliveryId,
527
+ kind: "repo",
528
+ action: "transferred",
529
+ kindRaw: event,
530
+ summary: `${who} transferred the repository`,
531
+ occurredAt: /* @__PURE__ */ new Date(),
532
+ actor,
533
+ repo,
534
+ detail: {
535
+ type: "repo_transferred",
536
+ fromOwner: payload.previous_workspace?.slug,
537
+ toOwner: repo?.owner
538
+ },
539
+ source: "webhook",
540
+ payload
541
+ }];
542
+ const kind = EVENT_KINDS[event] ?? "other";
543
+ return [{
544
+ forge: FORGE,
545
+ instance,
546
+ id: deliveryId,
547
+ kind,
548
+ kindRaw: event,
549
+ summary: `${who} ${event.split(":")[1]?.replaceAll("_", " ") ?? event}`,
550
+ occurredAt: toDate(payload.comment?.created_on ?? payload.approval?.date ?? payload.pullrequest?.updated_on) ?? /* @__PURE__ */ new Date(),
551
+ actor,
552
+ repo,
553
+ thread,
554
+ source: "webhook",
555
+ payload
556
+ }];
557
+ }
558
+ const bitbucketWebhooks = ({ options, instance }) => ({
559
+ events: BITBUCKET_WEBHOOK_EVENTS,
560
+ verify: (delivery) => verifyBitbucketSignature(delivery, options.webhookSecret),
561
+ translate: (delivery) => translateBitbucketWebhook(instance, delivery)
562
+ });
563
+ //#endregion
564
+ //#region src/bitbucket/index.ts
565
+ const PULL_STATES = {
566
+ open: ["OPEN"],
567
+ merged: ["MERGED"],
568
+ closed: [
569
+ "MERGED",
570
+ "DECLINED",
571
+ "SUPERSEDED"
572
+ ],
573
+ all: [
574
+ "OPEN",
575
+ "MERGED",
576
+ "DECLINED",
577
+ "SUPERSEDED"
578
+ ]
579
+ };
580
+ /** A BBQL string literal. */
581
+ function bbql(value) {
582
+ return `"${value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}"`;
583
+ }
584
+ function accountRef(actor) {
585
+ return { uuid: typeof actor === "string" ? actor : actor.id };
586
+ }
587
+ function page(body) {
588
+ const { values, next } = body;
589
+ return {
590
+ items: values,
591
+ next
592
+ };
593
+ }
594
+ /** Creates a Bitbucket Cloud provider. Bitbucket Data Center has a different API and is not supported. */
595
+ const BITBUCKET = {
596
+ forge: FORGE,
597
+ baseUrl: "https://api.bitbucket.org/2.0",
598
+ anonymous: true,
599
+ instance: (host) => host === "api.bitbucket.org" ? "bitbucket.org" : host,
600
+ headers: { accept: "application/json" },
601
+ authHeaders({ options: { auth } }) {
602
+ if (!auth || auth.type === "anonymous") return;
603
+ if (auth.type === "token") return async () => ({ authorization: `Bearer ${await resolveToken(auth)}` });
604
+ const authorization = `Basic ${toBase64(new TextEncoder().encode(`${auth.username}:${auth.password}`))}`;
605
+ return () => ({ authorization });
606
+ },
607
+ setup({ options, instance, origin: context, fetcher, baseUrl }) {
608
+ const list = createListing(fetcher, "pagelen", 50);
609
+ const webOrigin = hostOf(baseUrl) === "api.bitbucket.org" ? "https://bitbucket.org" : baseUrl.replace(/\/2\.0$/, "");
610
+ /** Repository search covers the signed-in account's workspaces. */
611
+ const anonymous = options.auth?.type === "anonymous";
612
+ function repoPath(ref) {
613
+ return `/repositories/${encodeURIComponent(ref.repo.owner)}/${encodeURIComponent(ref.repo.name)}`;
614
+ }
615
+ function threadPath(ref) {
616
+ switch (ref.kind) {
617
+ case "pull_request": return `${repoPath(ref)}/pullrequests/${ref.number}`;
618
+ case "commit": return `${repoPath(ref)}/commit/${ref.number}`;
619
+ default: throw new UnsupportedOperationError(`Bitbucket has no ${ref.kind} threads`, context);
620
+ }
621
+ }
622
+ function all(path, listOptions = {}) {
623
+ return fetcher.items(path, {
624
+ query: { pagelen: listOptions.perPage ?? 50 },
625
+ signal: listOptions.signal,
626
+ select: page
627
+ });
628
+ }
629
+ async function resolveMergeMethod(ref, pull, signal) {
630
+ const branch = pull?.destination?.branch?.name ?? (await fetcher.json(threadPath(ref), { signal })).data.destination?.branch?.name;
631
+ const { data } = await fetcher.json(`${repoPath(ref)}/refs/branches/${encodeURIComponent(branch ?? "")}`, { signal });
632
+ return soleMergeMethod(Object.fromEntries((data.merge_strategies ?? []).map(toMergeMethod).filter((method) => method !== void 0).map((method) => [method, true])), context);
633
+ }
634
+ async function merge(thread, mergeOptions = {}, hooks = {}) {
635
+ const ref = requireThread(thread, context);
636
+ if (ref.kind !== "pull_request") throw new UnsupportedOperationError("Only pull requests can be merged", context);
637
+ if (mergeOptions.whenChecksPass) throw new UnsupportedOperationError("Bitbucket Cloud cannot queue a merge until checks pass through the API", context);
638
+ let pull;
639
+ if (mergeOptions.sha) {
640
+ pull = (await fetcher.json(threadPath(ref), { signal: mergeOptions.signal })).data;
641
+ const head = pull.source?.commit?.hash ?? "";
642
+ if (!head || !(mergeOptions.sha.startsWith(head) || head.startsWith(mergeOptions.sha))) throw new MergeConflictError(`Pull request head is ${head || "unknown"}, not ${mergeOptions.sha}`, 409, "", context);
643
+ }
644
+ const method = mergeOptions.method ?? await resolveMergeMethod(ref, pull, mergeOptions.signal);
645
+ await hooks.beforeMerge?.();
646
+ await fetcher.raw(`${threadPath(ref)}/merge`, {
647
+ method: "POST",
648
+ json: {
649
+ merge_strategy: toStrategy(method),
650
+ message: mergeCommitMessage(mergeOptions)
651
+ },
652
+ mapError: toMergeError,
653
+ signal: mergeOptions.signal
654
+ });
655
+ }
656
+ const STATUS_STATES = {
657
+ pending: "INPROGRESS",
658
+ success: "SUCCESSFUL",
659
+ failure: "FAILED",
660
+ neutral: "STOPPED",
661
+ unknown: "INPROGRESS"
662
+ };
663
+ function repoPathOf(repo) {
664
+ return `/repositories/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.name)}`;
665
+ }
666
+ function requirePull(thread, action) {
667
+ const ref = requireThread(thread, context);
668
+ if (ref.kind !== "pull_request") throw new UnsupportedOperationError(`Only pull requests can be ${action}`, context);
669
+ return ref;
670
+ }
671
+ /** Bitbucket's source endpoints need an explicit commit or branch, so the main branch stands in. */
672
+ async function mainBranch(repo, options) {
673
+ const { data } = await fetcher.json(repoPathOf(repo), { signal: options?.signal });
674
+ return data.mainbranch?.name ?? "HEAD";
675
+ }
676
+ async function get(thread, options) {
677
+ const ref = requireThread(thread, context);
678
+ switch (ref.kind) {
679
+ case "pull_request": {
680
+ const result = toPullThread(ref, (await fetcher.json(threadPath(ref), { signal: options?.signal })).data);
681
+ try {
682
+ result.checks = summariseChecks((await pullChecks(ref, options?.signal)).map((check) => check.state));
683
+ } catch (error) {
684
+ if (!(error instanceof ForgeError)) throw error;
685
+ result.warnings = [...result.warnings ?? [], toWarning("checks_unreadable", error, ref.number)];
686
+ }
687
+ return result;
688
+ }
689
+ case "commit": return toCommitThread(ref, (await fetcher.json(threadPath(ref), { signal: options?.signal })).data);
690
+ default: throw new UnsupportedOperationError(`Bitbucket has no ${ref.kind} threads`, context);
691
+ }
692
+ }
693
+ async function listPage(repo, query = {}) {
694
+ if (query.kind && query.kind !== "pull_request") return {
695
+ items: [],
696
+ warnings: [{
697
+ code: "kind_unsupported",
698
+ message: `Bitbucket Cloud has no ${query.kind === "issue" ? "issues" : "discussions"}`
699
+ }]
700
+ };
701
+ const warnings = [];
702
+ if (!query.cursor) {
703
+ if (query.labels?.length) warnings.push({
704
+ code: "filter_unsupported",
705
+ message: "Bitbucket has no labels; the labels filter was ignored"
706
+ });
707
+ if (query.sort === "comments") warnings.push({
708
+ code: "sort_unsupported",
709
+ message: "Bitbucket cannot sort by comment count; sorted by creation time"
710
+ });
711
+ if (query.assignee) warnings.push({
712
+ code: "filter_unsupported",
713
+ message: "Bitbucket pull requests have no assignees; the assignee filter was ignored"
714
+ });
715
+ }
716
+ const field = query.sort === "updated" ? "updated_on" : "created_on";
717
+ const clauses = [];
718
+ if (query.since) clauses.push(`updated_on >= ${query.since.toISOString()}`);
719
+ if (query.createdAfter) clauses.push(`created_on >= ${query.createdAfter.toISOString()}`);
720
+ if (query.author) clauses.push(`author.nickname = ${bbql(query.author)}`);
721
+ const params = new URLSearchParams({
722
+ sort: `${(query.direction ?? "desc") === "desc" ? "-" : ""}${field}`,
723
+ pagelen: String(query.perPage ?? 50)
724
+ });
725
+ for (const value of PULL_STATES[query.state ?? "open"]) params.append("state", value);
726
+ if (clauses.length) params.set("q", clauses.join(" AND "));
727
+ const result = await fetcher.page(`${repoPathOf(repo)}/pullrequests?${params}`, {
728
+ signal: query.signal,
729
+ cursor: query.cursor,
730
+ select: page
731
+ });
732
+ return toPage(result, (raw) => toPullThread({
733
+ forge: FORGE,
734
+ instance,
735
+ repo,
736
+ kind: "pull_request",
737
+ number: String(raw.id)
738
+ }, raw), warnings.length ? warnings : void 0);
739
+ }
740
+ /** Repository hooks, or workspace hooks for a namespace ref. */
741
+ function hooksPath(target) {
742
+ return isNamespaceRef(target) ? `/workspaces/${encodeURIComponent(target.owner)}/hooks` : `${repoPathOf(target)}/hooks`;
743
+ }
744
+ /** Bitbucket replaces a hook whole, so a change is merged into the hook as it is. */
745
+ async function putHook(ref, changes, options) {
746
+ const path = `${hooksPath(ref.target)}/${ref.id}`;
747
+ const { data: current } = await fetcher.json(path, { signal: options?.signal });
748
+ const { data } = await fetcher.json(path, {
749
+ method: "PUT",
750
+ json: {
751
+ url: current.url,
752
+ description: current.description,
753
+ active: current.active,
754
+ events: current.events,
755
+ ...changes
756
+ },
757
+ signal: options?.signal
758
+ });
759
+ return toWebhook(ref.target, data);
760
+ }
761
+ /**
762
+ * Bitbucket indexes issues and pull requests per repository, so search is
763
+ * the same `q=` filter language scoped to one repository.
764
+ */
765
+ async function searchThreadsPage(query) {
766
+ if (!query.repo) throw new UnsupportedOperationError("Bitbucket has no cross-repository issue or pull request search; pass `repo`", context);
767
+ const warnings = [];
768
+ if (!query.cursor) {
769
+ if (query.labels?.length) warnings.push({
770
+ code: "filter_unsupported",
771
+ message: "Bitbucket has no labels; the label filter was ignored"
772
+ });
773
+ if (query.involves) warnings.push({
774
+ code: "filter_unsupported",
775
+ message: "Bitbucket has no involves filter; it was ignored"
776
+ });
777
+ if (query.sort === "comments") warnings.push({
778
+ code: "sort_unsupported",
779
+ message: "Bitbucket cannot sort by comment count; sorted by update time"
780
+ });
781
+ }
782
+ const page = await listPage(query.repo, {
783
+ kind: query.kind,
784
+ state: query.state ?? "all",
785
+ author: query.author,
786
+ assignee: query.assignee,
787
+ since: query.since,
788
+ sort: query.sort === "created" ? "created" : "updated",
789
+ direction: query.direction,
790
+ perPage: query.perPage,
791
+ cursor: query.cursor,
792
+ signal: query.signal
793
+ });
794
+ const text = query.text?.toLowerCase();
795
+ const items = text ? page.items.filter((thread) => thread.title.toLowerCase().includes(text) || (thread.body ?? "").toLowerCase().includes(text)) : page.items;
796
+ const all = [...warnings, ...page.warnings ?? []];
797
+ return {
798
+ ...page,
799
+ items,
800
+ ...all.length ? { warnings: all } : {}
801
+ };
802
+ }
803
+ async function searchReposPage(query) {
804
+ const clauses = [...query.text ? [`name ~ ${bbql(query.text)}`] : [], ...query.language ? [`language = ${bbql(query.language)}`] : []];
805
+ const field = query.sort === "created" ? "created_on" : "updated_on";
806
+ const extras = {
807
+ query: {
808
+ q: clauses.length ? clauses.join(" AND ") : void 0,
809
+ sort: `${(query.direction ?? "desc") === "desc" ? "-" : ""}${field}`
810
+ },
811
+ warnings: query.sort === "stars" ? [{
812
+ code: "sort_unsupported",
813
+ message: "Bitbucket has no stars; sorted by update time"
814
+ }] : void 0
815
+ };
816
+ return query.owner ? list(`/repositories/${encodeURIComponent(query.owner)}`, query, (raw) => toRepo(instance, raw), {
817
+ ...extras,
818
+ select: page
819
+ }) : memberRepos(query, extras);
820
+ }
821
+ /** One phase per workspace the account belongs to; Bitbucket lists repositories by workspace only. */
822
+ async function memberRepos(listOptions, extras = {}) {
823
+ const workspaces = await Array.fromAsync(fetcher.items("/user/workspaces", {
824
+ query: { pagelen: 100 },
825
+ select: page,
826
+ signal: listOptions.signal
827
+ }));
828
+ if (!workspaces.length) return { items: [] };
829
+ return phased(workspaces.map(({ workspace }) => (cursor) => list(`/repositories/${encodeURIComponent(workspace.slug)}`, {
830
+ ...listOptions,
831
+ cursor
832
+ }, (raw) => toRepo(instance, raw), {
833
+ ...extras,
834
+ query: {
835
+ ...extras.query,
836
+ role: "member"
837
+ },
838
+ select: page
839
+ })), listOptions.cursor);
840
+ }
841
+ /** Bitbucket has participants, not reviews: one synthesised review per participant who has voted. */
842
+ function toParticipantReviews(ref, pull) {
843
+ return (pull.participants ?? []).flatMap((participant) => {
844
+ const stateRaw = participant.state ?? (participant.approved ? "approved" : void 0);
845
+ if (!stateRaw) return [];
846
+ const actor = toActor(instance, participant.user);
847
+ const state = stateRaw === "approved" ? "approved" : stateRaw === "changes_requested" ? "changes_requested" : "unknown";
848
+ return [syntheticReview(ref, `participant:${participant.user?.uuid ?? actor?.login ?? ""}`, state, {
849
+ author: actor,
850
+ stateRaw,
851
+ raw: participant
852
+ })];
853
+ });
854
+ }
855
+ async function createReview(thread, input, options) {
856
+ const ref = requireThread(thread, context);
857
+ if (ref.kind !== "pull_request") throw new UnsupportedOperationError("Only pull requests can be reviewed", context);
858
+ if (input.event !== "approve" && input.event !== "request_changes") throw new UnsupportedOperationError("Bitbucket records approve and request-changes votes, not review bodies", context);
859
+ if (input.body || input.comments?.length) throw new UnsupportedOperationError("Bitbucket votes carry no body or inline comments; post them as comments", context);
860
+ const { data } = await fetcher.json(`${threadPath(ref)}/${input.event === "approve" ? "approve" : "request-changes"}`, {
861
+ method: "POST",
862
+ signal: options?.signal
863
+ });
864
+ return toParticipantReviews(ref, { participants: [data] })[0] ?? {
865
+ ref: {
866
+ forge: "bitbucket",
867
+ instance,
868
+ thread: ref,
869
+ id: "participant:"
870
+ },
871
+ state: "unknown",
872
+ comments: false,
873
+ raw: data
874
+ };
875
+ }
876
+ function pullChecks(ref, signal) {
877
+ return Array.fromAsync(all(`${threadPath(ref)}/statuses`, { signal }), (status) => toStatusCheck(ref.repo, status));
878
+ }
879
+ function commentsPath(ref) {
880
+ return `${threadPath(ref)}/comments`;
881
+ }
882
+ function commentPath(ref) {
883
+ return `${commentsPath(requireThread(ref.thread, context))}/${ref.id}`;
884
+ }
885
+ return {
886
+ traits: {
887
+ eventKinds: "native",
888
+ authKinds: [
889
+ "token",
890
+ "basic",
891
+ "anonymous"
892
+ ]
893
+ },
894
+ normaliseMarkdown,
895
+ users: { me: verb(true, async (options) => {
896
+ const { data } = await fetcher.json("/user", { signal: options?.signal });
897
+ return {
898
+ ...toActor(instance, data),
899
+ raw: data
900
+ };
901
+ }) },
902
+ web: bitbucketWeb(webOrigin),
903
+ webhooks: {
904
+ listPage: verb(true, (target, listOptions = {}) => list(hooksPath(target), listOptions, (raw) => toWebhook(target, raw), { select: page })),
905
+ create: verb(true, async (target, input, options) => toWebhook(target, (await fetcher.json(hooksPath(target), {
906
+ method: "POST",
907
+ json: {
908
+ url: input.url,
909
+ description: "forges",
910
+ active: input.active ?? true,
911
+ events: nativeEventsFor(BITBUCKET_NATIVE_EVENTS, input.events, input.nativeEvents),
912
+ ...input.secret ? { secret: input.secret } : {}
913
+ },
914
+ signal: options?.signal
915
+ })).data)),
916
+ update: verb(true, async (ref, update, options) => putHook(ref, {
917
+ ...update.url ? { url: update.url } : {},
918
+ ...update.active === void 0 ? {} : { active: update.active },
919
+ ...update.events || update.nativeEvents ? { events: nativeEventsFor(BITBUCKET_NATIVE_EVENTS, update.events, update.nativeEvents) } : {},
920
+ ...update.secret ? { secret: update.secret } : {}
921
+ }, options)),
922
+ delete: verb(true, async (ref, options) => {
923
+ await fetcher.raw(`${hooksPath(ref.target)}/${ref.id}`, {
924
+ method: "DELETE",
925
+ signal: options?.signal
926
+ });
927
+ }),
928
+ rotateSecret: verb(true, async (ref, secret, options) => putHook(ref, { secret }, options))
929
+ },
930
+ scopes: bitbucketScopesFor,
931
+ repos: {
932
+ get: verb(true, async (ref, options) => {
933
+ return toRepo(instance, (await fetcher.json(repoPathOf(ref), { signal: options?.signal })).data);
934
+ }),
935
+ listPage: verb(true, (listOptions = {}) => memberRepos(listOptions))
936
+ },
937
+ search: {
938
+ threadsPage: verb(true, searchThreadsPage),
939
+ reposPage: verb(!anonymous, searchReposPage)
940
+ },
941
+ contents: {
942
+ file: verb(true, async (repo, path, fileOptions = {}) => {
943
+ const ref = fileOptions.ref ?? await mainBranch(repo, fileOptions);
944
+ const target = `${repoPathOf(repo)}/src/${encodeURIComponent(ref)}/${path.split("/").map(encodeURIComponent).join("/")}`;
945
+ const [meta, raw] = await Promise.all([fetcher.json(target, {
946
+ query: { format: "meta" },
947
+ signal: fileOptions.signal
948
+ }), fetcher.raw(target, { signal: fileOptions.signal })]);
949
+ const file = {
950
+ path: meta.data.path,
951
+ sha: meta.data.commit?.hash,
952
+ size: meta.data.size,
953
+ url: meta.data.links?.html?.href
954
+ };
955
+ return toFileContent(new Uint8Array(await raw.arrayBuffer()), file, fileOptions, context);
956
+ }),
957
+ treePage: verb(true, async (repo, treeOptions = {}) => {
958
+ const ref = treeOptions.ref ?? await mainBranch(repo, treeOptions);
959
+ const path = treeOptions.path?.replace(/^\/|\/$/g, "") ?? "";
960
+ return list(`${repoPathOf(repo)}/src/${encodeURIComponent(ref)}/${path.split("/").map(encodeURIComponent).join("/")}`, treeOptions, toTreeEntry, {
961
+ query: { max_depth: treeOptions.recursive ? 100 : void 0 },
962
+ select: page
963
+ });
964
+ }),
965
+ branchesPage: verb(true, (repo, listOptions = {}) => list(`${repoPathOf(repo)}/refs/branches`, listOptions, (raw) => toBranch(raw), { select: page })),
966
+ tagsPage: verb(true, (repo, listOptions = {}) => list(`${repoPathOf(repo)}/refs/tags`, listOptions, toTag, { select: page })),
967
+ resolveRef: verb(true, async (repo, ref, options) => (await fetcher.json(`${repoPathOf(repo)}/commit/${encodeURIComponent(ref)}`, { signal: options?.signal })).data.hash),
968
+ commitsPage: verb(true, (repo, query = {}) => list(`${repoPathOf(repo)}/commits${query.ref ? `/${encodeURIComponent(query.ref)}` : ""}`, query, (raw) => toCommit(repo, raw), {
969
+ query: { path: query.path },
970
+ select: page
971
+ })),
972
+ commit: verb(true, async (repo, sha, options) => {
973
+ const [commit, files] = await Promise.all([fetcher.json(`${repoPathOf(repo)}/commit/${sha}`, { signal: options?.signal }), Array.fromAsync(all(`${repoPathOf(repo)}/diffstat/${sha}`, { signal: options?.signal }), toChangedFile)]);
974
+ return toCommit(repo, commit.data, files);
975
+ }),
976
+ compare: verb(true, async (repo, base, head, options) => {
977
+ const [files, commits] = await Promise.all([Array.fromAsync(all(`${repoPathOf(repo)}/diffstat/${encodeURIComponent(head)}..${encodeURIComponent(base)}`, { signal: options?.signal }), toChangedFile), Array.fromAsync(fetcher.items(`${repoPathOf(repo)}/commits`, {
978
+ query: {
979
+ include: head,
980
+ exclude: base,
981
+ pagelen: 50
982
+ },
983
+ select: page,
984
+ signal: options?.signal
985
+ }), (raw) => toCommit(repo, raw))]);
986
+ return {
987
+ base,
988
+ head,
989
+ aheadBy: commits.length,
990
+ commits,
991
+ files,
992
+ raw: {
993
+ files,
994
+ commits
995
+ }
996
+ };
997
+ })
998
+ },
999
+ checks: {
1000
+ list: verb(true, async (repo, sha, options) => ({ items: await Array.fromAsync(all(`${repoPathOf(repo)}/commit/${sha}/statuses`, { signal: options?.signal }), (raw) => toStatusCheck(repo, raw)) })),
1001
+ report: verb(true, async (repo, sha, input, options) => toStatusCheck(repo, (await fetcher.json(`${repoPathOf(repo)}/commit/${sha}/statuses/build`, {
1002
+ method: "POST",
1003
+ json: {
1004
+ key: input.externalId ?? input.name,
1005
+ name: input.name,
1006
+ state: STATUS_STATES[input.state],
1007
+ description: input.description,
1008
+ url: input.url ?? `${webOrigin}/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.name)}/commits/${sha}`
1009
+ },
1010
+ signal: options?.signal
1011
+ })).data))
1012
+ },
1013
+ threads: {
1014
+ filesPage: verb(true, (thread, listOptions = {}) => list(`${threadPath(requirePull(thread, "read for changed files"))}/diffstat`, listOptions, toChangedFile, { select: page })),
1015
+ commitsPage: verb(true, (thread, listOptions = {}) => list(`${threadPath(requirePull(thread, "read for commits"))}/commits`, listOptions, (raw) => toCommit(thread.repo, raw), { select: page })),
1016
+ checks: perKind({ pull_request: true }, async (thread, options) => ({ items: await pullChecks(requireThread(thread, context), options?.signal) })),
1017
+ reviewsPage: verb("emulated", async (thread, listOptions) => {
1018
+ const ref = requireThread(thread, context);
1019
+ if (ref.kind !== "pull_request") throw new UnsupportedOperationError("Only pull requests have participants", context);
1020
+ return { items: toParticipantReviews(ref, (await fetcher.json(threadPath(ref), { signal: listOptions?.signal })).data) };
1021
+ }),
1022
+ createReview: verb("emulated", createReview),
1023
+ get: perKind({
1024
+ pull_request: true,
1025
+ commit: true
1026
+ }, get),
1027
+ getMany: verb(true, (refs, options) => getManyConcurrently(refs, get, options)),
1028
+ listPage: perKind({ pull_request: true }, listPage),
1029
+ eventsPage: verb(true, async (thread, listOptions = {}) => {
1030
+ const ref = requireThread(thread, context);
1031
+ const events = [];
1032
+ if (ref.kind === "pull_request") {
1033
+ let index = 0;
1034
+ for await (const entry of all(`${threadPath(ref)}/activity`, listOptions)) events.push(toActivityEvent(ref, entry, index++));
1035
+ } else for await (const comment of all(commentsPath(ref), listOptions)) events.push(toCommentEvent(ref, comment));
1036
+ return { items: events.sort((a, b) => a.occurredAt.getTime() - b.occurredAt.getTime()) };
1037
+ }),
1038
+ commentsPage: perKind({
1039
+ pull_request: true,
1040
+ commit: true
1041
+ }, async (thread, listOptions = {}) => {
1042
+ const ref = requireThread(thread, context);
1043
+ return list(commentsPath(ref), listOptions, (comment) => toComment(ref, comment), { select: page });
1044
+ }),
1045
+ comment: perKind({
1046
+ pull_request: true,
1047
+ commit: true
1048
+ }, async (thread, body, options) => {
1049
+ const ref = requireThread(thread, context);
1050
+ const { data } = await fetcher.json(commentsPath(ref), {
1051
+ method: "POST",
1052
+ json: { content: { raw: body } },
1053
+ signal: options?.signal
1054
+ });
1055
+ return toComment(ref, data);
1056
+ }),
1057
+ editComment: perKind({
1058
+ pull_request: true,
1059
+ commit: true
1060
+ }, async (ref, body, options) => {
1061
+ const { data } = await fetcher.json(commentPath(ref), {
1062
+ method: "PUT",
1063
+ json: { content: { raw: body } },
1064
+ signal: options?.signal
1065
+ });
1066
+ return toComment(ref.thread, data);
1067
+ }),
1068
+ deleteComment: perKind({
1069
+ pull_request: true,
1070
+ commit: true
1071
+ }, async (ref, options) => {
1072
+ await fetcher.raw(commentPath(ref), {
1073
+ method: "DELETE",
1074
+ signal: options?.signal
1075
+ });
1076
+ }),
1077
+ create: perKind({ pull_request: true }, async (repo, input, options) => {
1078
+ if (input.labels?.length) throw new UnsupportedOperationError("Bitbucket has no labels", context);
1079
+ if (!input.head || !input.base) throw new UnsupportedOperationError("Creating a pull request needs head and base branches", context);
1080
+ if (input.assignees?.length) throw new UnsupportedOperationError("Bitbucket pull requests have no assignees", context);
1081
+ const { data } = await fetcher.json(`${repoPathOf(repo)}/pullrequests`, {
1082
+ method: "POST",
1083
+ json: {
1084
+ title: input.title,
1085
+ description: input.body,
1086
+ source: { branch: { name: input.head } },
1087
+ destination: { branch: { name: input.base } },
1088
+ draft: input.draft
1089
+ },
1090
+ signal: options?.signal
1091
+ });
1092
+ return toPullThread({
1093
+ forge: FORGE,
1094
+ instance,
1095
+ repo,
1096
+ kind: "pull_request",
1097
+ number: String(data.id)
1098
+ }, data);
1099
+ }),
1100
+ update: perKind({ pull_request: true }, async (thread, input, options) => {
1101
+ const ref = requirePull(thread, "updated");
1102
+ const { data } = await fetcher.json(threadPath(ref), {
1103
+ method: "PUT",
1104
+ json: {
1105
+ title: input.title,
1106
+ description: input.body
1107
+ },
1108
+ signal: options?.signal
1109
+ });
1110
+ return toPullThread(ref, data);
1111
+ }),
1112
+ requestReview: perKind({ pull_request: true }, async (thread, reviewers, options) => {
1113
+ const ref = requireThread(thread, context);
1114
+ if (ref.kind !== "pull_request") throw new UnsupportedOperationError("Only pull requests have reviewers", context);
1115
+ const { data } = await fetcher.json(threadPath(ref), { signal: options?.signal });
1116
+ const uuids = /* @__PURE__ */ new Set([...(data.reviewers ?? []).flatMap((user) => user.uuid ?? []), ...reviewers.map((reviewer) => accountRef(reviewer).uuid)]);
1117
+ await fetcher.raw(threadPath(ref), {
1118
+ method: "PUT",
1119
+ json: {
1120
+ title: data.title,
1121
+ reviewers: [...uuids].map((uuid) => ({ uuid }))
1122
+ },
1123
+ signal: options?.signal
1124
+ });
1125
+ }),
1126
+ close: perKind({ pull_request: true }, async (thread, options) => {
1127
+ await fetcher.raw(`${threadPath(requirePull(thread, "declined"))}/decline`, {
1128
+ method: "POST",
1129
+ signal: options?.signal
1130
+ });
1131
+ }),
1132
+ merge: verb(true, merge)
1133
+ }
1134
+ };
1135
+ }
1136
+ };
1137
+ /** Creates a Bitbucket Cloud provider. */
1138
+ const bitbucket = /* @__PURE__ */ defineForgeProvider({
1139
+ ...BITBUCKET,
1140
+ webhooks: bitbucketWebhooks
1141
+ });
1142
+ /** `bitbucket()` without webhook ingestion, for bundles that never receive a delivery. */
1143
+ const bitbucketLite = /* @__PURE__ */ defineForgeProvider(BITBUCKET);
1144
+ /** Bitbucket Cloud scopes, as an app password or OAuth consumer grants them. */
1145
+ function bitbucketScopesFor(verb) {
1146
+ const [group = "", name = ""] = verb.split(".");
1147
+ if (group === "webhooks") return name === "verify" || name === "ingest" ? {} : { token: ["webhook"] };
1148
+ const write = (/* @__PURE__ */ new Set([
1149
+ "comment",
1150
+ "upsertComment",
1151
+ "editComment",
1152
+ "deleteComment",
1153
+ "create",
1154
+ "update",
1155
+ "close",
1156
+ "reopen",
1157
+ "merge",
1158
+ "approveAndMerge",
1159
+ "approve",
1160
+ "createReview",
1161
+ "submitReview",
1162
+ "setAssignees",
1163
+ "requestReview",
1164
+ "report"
1165
+ ])).has(name);
1166
+ if (group === "threads") return { token: [write ? "issue:write" : "issue", write ? "pullrequest:write" : "pullrequest"] };
1167
+ if (group === "users" || group === "search") return { token: ["account"] };
1168
+ return { token: [write ? "repository:write" : "repository"] };
1169
+ }
1170
+ //#endregion
1171
+ export { bitbucketLite as n, bitbucketScopesFor as r, bitbucket as t };