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.
- package/LICENCE +21 -0
- package/README.md +72 -0
- package/dist/azure-devops/index.d.mts +2 -0
- package/dist/azure-devops/index.mjs +1 -0
- package/dist/azure-devops-DwQvL4TV.mjs +1355 -0
- package/dist/bitbucket/index.d.mts +2 -0
- package/dist/bitbucket/index.mjs +1 -0
- package/dist/bitbucket-CpcfJlYi.mjs +1171 -0
- package/dist/crypto-CQNEyYHD.mjs +156 -0
- package/dist/cursor-origin/index.d.mts +2 -0
- package/dist/cursor-origin/index.mjs +1 -0
- package/dist/cursor-origin-BhqYx2oW.mjs +980 -0
- package/dist/define-B2_4ob2i.d.mts +307 -0
- package/dist/define-D3yEXtKK.mjs +2276 -0
- package/dist/env.d.mts +46 -0
- package/dist/env.mjs +283 -0
- package/dist/fake/index.d.mts +227 -0
- package/dist/fake/index.mjs +1001 -0
- package/dist/fetch-D4pSRvvA.d.mts +1723 -0
- package/dist/forgejo/index.d.mts +2 -0
- package/dist/forgejo/index.mjs +1 -0
- package/dist/forgejo-BCefbHUN.mjs +2005 -0
- package/dist/gitea/index.d.mts +2 -0
- package/dist/gitea/index.mjs +22 -0
- package/dist/gitee/index.d.mts +2 -0
- package/dist/gitee/index.mjs +1 -0
- package/dist/gitee-CA-f7Rs8.mjs +1272 -0
- package/dist/github/index.d.mts +2 -0
- package/dist/github/index.mjs +1 -0
- package/dist/github-B0umL8_f.mjs +3259 -0
- package/dist/gitlab/index.d.mts +2 -0
- package/dist/gitlab/index.mjs +1 -0
- package/dist/gitlab-Bsr__Z0C.mjs +2378 -0
- package/dist/graphql-D7uGzZTf.mjs +366 -0
- package/dist/index-BQ6VS31s.d.mts +32 -0
- package/dist/index-C-jz9Mo3.d.mts +16 -0
- package/dist/index-C4fYzmK3.d.mts +102 -0
- package/dist/index-CQiIX4SH.d.mts +27 -0
- package/dist/index-CTj5gQzl.d.mts +35 -0
- package/dist/index-CliBWzOF.d.mts +23 -0
- package/dist/index-Cz_y-t3d.d.mts +36 -0
- package/dist/index-DEt25E1w.d.mts +77 -0
- package/dist/index-DIuS0EOy.d.mts +43 -0
- package/dist/index-uSpsMCrC.d.mts +26 -0
- package/dist/index.d.mts +41 -0
- package/dist/index.mjs +13 -0
- package/dist/kit.d.mts +183 -0
- package/dist/kit.mjs +4 -0
- package/dist/provider-CoZ7cCxp.d.mts +1439 -0
- package/dist/provider-Ed1YhkQN.mjs +169 -0
- package/dist/pushin/index.d.mts +2 -0
- package/dist/pushin/index.mjs +1 -0
- package/dist/pushin-CkxgJ2cU.mjs +343 -0
- package/dist/schema/Actor.json +96 -0
- package/dist/schema/AlertSupport.json +49 -0
- package/dist/schema/AuthKind.json +15 -0
- package/dist/schema/Branch.json +31 -0
- package/dist/schema/ChangedFile.json +58 -0
- package/dist/schema/Check.json +169 -0
- package/dist/schema/CheckRef.json +114 -0
- package/dist/schema/CheckState.json +14 -0
- package/dist/schema/ChecksSummary.json +34 -0
- package/dist/schema/CiJob.json +186 -0
- package/dist/schema/CiJobRef.json +132 -0
- package/dist/schema/CiRun.json +234 -0
- package/dist/schema/CiRunRef.json +103 -0
- package/dist/schema/CodeFragment.json +19 -0
- package/dist/schema/CodeMatch.json +128 -0
- package/dist/schema/Collaborator.json +169 -0
- package/dist/schema/Comment.json +336 -0
- package/dist/schema/CommentRef.json +158 -0
- package/dist/schema/Commit.json +307 -0
- package/dist/schema/CommitRef.json +104 -0
- package/dist/schema/CommitSignature.json +118 -0
- package/dist/schema/Comparison.json +355 -0
- package/dist/schema/Cursor.json +21 -0
- package/dist/schema/EventAction.json +33 -0
- package/dist/schema/EventDetail.json +649 -0
- package/dist/schema/EventKind.json +26 -0
- package/dist/schema/FileContent.json +73 -0
- package/dist/schema/FileStatus.json +15 -0
- package/dist/schema/ForgeCapabilities.json +737 -0
- package/dist/schema/ForgeEvent.json +789 -0
- package/dist/schema/ForgeInstance.json +7 -0
- package/dist/schema/ForgeKind.json +32 -0
- package/dist/schema/ForgeWarning.json +49 -0
- package/dist/schema/ForgeWarningCode.json +7 -0
- package/dist/schema/GetManyResult.json +699 -0
- package/dist/schema/Installation.json +131 -0
- package/dist/schema/InstallationToken.json +31 -0
- package/dist/schema/KnownForgeKind.json +19 -0
- package/dist/schema/Label.json +22 -0
- package/dist/schema/MergeMethod.json +14 -0
- package/dist/schema/Milestone.json +40 -0
- package/dist/schema/Notification.json +498 -0
- package/dist/schema/NotificationReason.json +28 -0
- package/dist/schema/NotificationRef.json +60 -0
- package/dist/schema/NotificationSubject.json +376 -0
- package/dist/schema/PerKind.json +49 -0
- package/dist/schema/PullBranches.json +125 -0
- package/dist/schema/PushCommit.json +24 -0
- package/dist/schema/Reaction.json +148 -0
- package/dist/schema/ReactionContent.json +17 -0
- package/dist/schema/ReactionSummary.json +70 -0
- package/dist/schema/Release.json +286 -0
- package/dist/schema/ReleaseAsset.json +169 -0
- package/dist/schema/ReleaseAssetRef.json +138 -0
- package/dist/schema/ReleaseRef.json +107 -0
- package/dist/schema/Repo.json +354 -0
- package/dist/schema/RepoFeatures.json +35 -0
- package/dist/schema/RepoPermissions.json +35 -0
- package/dist/schema/RepoRef.json +77 -0
- package/dist/schema/RepoRole.json +15 -0
- package/dist/schema/RepoVisibility.json +12 -0
- package/dist/schema/Review.json +371 -0
- package/dist/schema/ReviewComment.json +280 -0
- package/dist/schema/ReviewCommentInput.json +34 -0
- package/dist/schema/ReviewRef.json +158 -0
- package/dist/schema/ReviewState.json +15 -0
- package/dist/schema/Reviewer.json +133 -0
- package/dist/schema/SecurityAlert.json +219 -0
- package/dist/schema/SecurityAlertKind.json +14 -0
- package/dist/schema/SecurityAlertRef.json +122 -0
- package/dist/schema/SecuritySeverity.json +14 -0
- package/dist/schema/SourceKind.json +12 -0
- package/dist/schema/SubscriptionState.json +12 -0
- package/dist/schema/Support.json +23 -0
- package/dist/schema/Tag.json +25 -0
- package/dist/schema/TextLimits.json +18 -0
- package/dist/schema/Thread.json +653 -0
- package/dist/schema/ThreadKind.json +14 -0
- package/dist/schema/ThreadRef.json +131 -0
- package/dist/schema/ThreadStack.json +146 -0
- package/dist/schema/ThreadState.json +13 -0
- package/dist/schema/TreeEntry.json +46 -0
- package/dist/schema/TreeEntryType.json +13 -0
- package/dist/schema/UpsertCommentResult.json +355 -0
- package/dist/schema/User.json +123 -0
- package/dist/schema/Webhook.json +188 -0
- package/dist/schema/WebhookDeliveryRecord.json +173 -0
- package/dist/schema/WebhookDeliveryRef.json +135 -0
- package/dist/schema/WebhookRef.json +105 -0
- package/dist/schema/index.d.mts +4 -0
- package/dist/schema/index.mjs +3219 -0
- package/dist/tangled/index.d.mts +2 -0
- package/dist/tangled/index.mjs +1 -0
- package/dist/tangled-ChdPmiv-.mjs +1570 -0
- package/dist/testing/index.d.mts +123 -0
- package/dist/testing/index.mjs +242 -0
- package/dist/webhooks-rn9-8lxT.mjs +70 -0
- package/package.json +115 -3
- 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 };
|