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,2005 @@
1
+ import { A as toPage, B as isNamespaceRef, C as requireIssueOrPull, E as summariseChecks, M as versionAtLeast, Mt as WebhookVerificationError, Nt as soleMergeMethod, O as toDate, Pt as toMergeError, T as resolveToken, Y as reactionContent, _t as ForgeError, ct as eventAction, dt as fromBase64, f as getManyConcurrently, ft as toFileContent, h as hostOf, i as githubShapedWeb, j as toWarning, jt as UnsupportedOperationError, k as toFileStatus, l as createListing, lt as reviewState, m as hexColour, n as perKind, p as hasEveryLabel, r as verb, s as actorLogin, t as defineForgeProvider, v as memo, w as requireThread, wt as NotFoundError, x as milestoneId, y as memoBy } from "./define-D3yEXtKK.mjs";
2
+ import { t as bodyText } from "./crypto-CQNEyYHD.mjs";
3
+ import { a as verifyHmacSignature, i as refEvent, n as firstHeader, r as nativeEventsFor, t as eventKindsOf } from "./webhooks-rn9-8lxT.mjs";
4
+ //#region src/forgejo/webhook-events.ts
5
+ const FORGEJO_HEADERS = {
6
+ signature: ["x-forgejo-signature", "x-gitea-signature"],
7
+ event: [
8
+ "x-forgejo-event",
9
+ "x-gitea-event",
10
+ "x-github-event"
11
+ ],
12
+ delivery: ["x-forgejo-delivery", "x-gitea-delivery"]
13
+ };
14
+ const GITEA_HEADERS = {
15
+ signature: ["x-gitea-signature"],
16
+ event: ["x-gitea-event", "x-github-event"],
17
+ delivery: ["x-gitea-delivery"]
18
+ };
19
+ /** Native Forgejo and Gitea event names per normalised kind, for hook subscriptions. */
20
+ const FORGEJO_NATIVE_EVENTS = {
21
+ comment: ["issue_comment", "pull_request_comment"],
22
+ review: [
23
+ "pull_request_review_approved",
24
+ "pull_request_review_rejected",
25
+ "pull_request_review_comment"
26
+ ],
27
+ review_comment: ["pull_request_review_comment"],
28
+ state_change: [
29
+ "issues",
30
+ "pull_request",
31
+ "pull_request_sync"
32
+ ],
33
+ label: ["issue_label", "pull_request_label"],
34
+ assignment: ["issue_assign", "pull_request_assign"],
35
+ push: ["push"],
36
+ ref: ["create", "delete"],
37
+ release: ["release"]
38
+ };
39
+ /** Normalised kinds and actions Forgejo and Gitea deliveries translate into. */
40
+ const FORGEJO_WEBHOOK_EVENTS = [
41
+ {
42
+ kind: "comment",
43
+ action: "created"
44
+ },
45
+ {
46
+ kind: "comment",
47
+ action: "edited"
48
+ },
49
+ {
50
+ kind: "comment",
51
+ action: "deleted"
52
+ },
53
+ {
54
+ kind: "review",
55
+ action: "submitted"
56
+ },
57
+ {
58
+ kind: "review_comment",
59
+ action: "created"
60
+ },
61
+ {
62
+ kind: "state_change",
63
+ action: "opened"
64
+ },
65
+ {
66
+ kind: "state_change",
67
+ action: "closed"
68
+ },
69
+ {
70
+ kind: "state_change",
71
+ action: "reopened"
72
+ },
73
+ {
74
+ kind: "state_change",
75
+ action: "merged"
76
+ },
77
+ {
78
+ kind: "label",
79
+ action: "edited"
80
+ },
81
+ {
82
+ kind: "label",
83
+ action: "unlabelled"
84
+ },
85
+ {
86
+ kind: "assignment",
87
+ action: "assigned"
88
+ },
89
+ {
90
+ kind: "assignment",
91
+ action: "unassigned"
92
+ },
93
+ { kind: "push" },
94
+ {
95
+ kind: "ref",
96
+ action: "created"
97
+ },
98
+ {
99
+ kind: "ref",
100
+ action: "deleted"
101
+ },
102
+ {
103
+ kind: "release",
104
+ action: "published"
105
+ }
106
+ ];
107
+ //#endregion
108
+ //#region src/forgejo/normalise.ts
109
+ /** The id of the built-in user that Forgejo and Gitea Actions act as. */
110
+ const ACTIONS_USER_ID = -2;
111
+ /** Normalisers take the origin because they serve both Forgejo and Gitea. */
112
+ function toActor(origin, user) {
113
+ if (!user) return;
114
+ return {
115
+ ...origin,
116
+ login: user.login,
117
+ id: String(user.id ?? user.login),
118
+ name: user.full_name || void 0,
119
+ avatarUrl: user.avatar_url,
120
+ url: user.html_url,
121
+ typeRaw: user.is_bot === void 0 ? void 0 : user.is_bot ? "bot" : "user",
122
+ isBotHint: user.is_bot === true || user.id === ACTIONS_USER_ID || user.login.endsWith("[bot]")
123
+ };
124
+ }
125
+ function toRepoRef(origin, repo) {
126
+ const [owner, name] = repo.full_name.split("/");
127
+ return {
128
+ ...origin,
129
+ owner: repo.owner?.login ?? owner ?? "",
130
+ name: repo.name ?? name ?? "",
131
+ externalId: repo.id === void 0 ? void 0 : String(repo.id)
132
+ };
133
+ }
134
+ function toRepo(origin, raw) {
135
+ return {
136
+ ref: toRepoRef(origin, raw),
137
+ description: raw.description || void 0,
138
+ defaultBranch: raw.default_branch,
139
+ visibility: raw.internal ? "internal" : raw.private ? "private" : "public",
140
+ visibilityRaw: raw.internal ? "internal" : raw.private ? "private" : "public",
141
+ isFork: raw.fork ?? false,
142
+ isArchived: raw.archived ?? false,
143
+ parent: raw.parent ? toRepoRef(origin, raw.parent) : void 0,
144
+ topics: raw.topics ?? [],
145
+ url: raw.html_url,
146
+ cloneUrls: raw.clone_url || raw.ssh_url ? {
147
+ https: raw.clone_url,
148
+ ssh: raw.ssh_url
149
+ } : void 0,
150
+ createdAt: toDate(raw.created_at),
151
+ updatedAt: toDate(raw.updated_at),
152
+ openIssueCount: raw.open_issues_count,
153
+ openPullCount: raw.open_pr_counter,
154
+ owner: toActor(origin, raw.owner),
155
+ language: raw.language || void 0,
156
+ homepage: raw.website || void 0,
157
+ licence: raw.licenses?.length === 1 ? raw.licenses[0] : void 0,
158
+ stars: raw.stars_count,
159
+ forks: raw.forks_count,
160
+ watchers: raw.watchers_count,
161
+ mergeMethods: raw.allow_merge_commits === void 0 ? void 0 : [
162
+ ...raw.allow_merge_commits ? ["merge"] : [],
163
+ ...raw.allow_squash_merge ? ["squash"] : [],
164
+ ...raw.allow_rebase ? ["rebase"] : [],
165
+ ...raw.allow_rebase_explicit ? ["rebase_merge"] : [],
166
+ ...raw.allow_fast_forward_only_merge ? ["fast_forward_only"] : []
167
+ ],
168
+ features: raw.has_issues === void 0 ? void 0 : {
169
+ issues: raw.has_issues,
170
+ pullRequests: raw.has_pull_requests ?? true,
171
+ discussions: false,
172
+ wiki: raw.has_wiki ?? false,
173
+ projects: raw.has_projects ?? false,
174
+ releases: raw.has_releases ?? true
175
+ },
176
+ permissions: raw.permissions ? {
177
+ admin: raw.permissions.admin,
178
+ maintain: raw.permissions.admin,
179
+ push: raw.permissions.push,
180
+ triage: raw.permissions.push,
181
+ pull: raw.permissions.pull
182
+ } : void 0,
183
+ raw
184
+ };
185
+ }
186
+ /** Forgejo and Gitea report `owner`, `admin`, `write`, `read` or `none`; there is no triage or maintain role. */
187
+ function toRole(raw) {
188
+ switch (raw) {
189
+ case "owner":
190
+ case "admin": return "admin";
191
+ case "write": return "write";
192
+ case "read": return "read";
193
+ default: return "none";
194
+ }
195
+ }
196
+ function toLabel(raw) {
197
+ return {
198
+ name: raw.name,
199
+ colour: raw.color || void 0,
200
+ description: raw.description || void 0
201
+ };
202
+ }
203
+ function toMilestone(raw) {
204
+ return raw ? {
205
+ id: String(raw.id),
206
+ title: raw.title,
207
+ state: raw.state === "closed" ? "closed" : "open",
208
+ description: raw.description || void 0,
209
+ dueOn: toDate(raw.due_on),
210
+ raw
211
+ } : void 0;
212
+ }
213
+ function toThreadKind(subjectType) {
214
+ switch (subjectType) {
215
+ case "Pull":
216
+ case "PullRequest": return "pull_request";
217
+ case "Commit": return "commit";
218
+ case "Issue": return "issue";
219
+ default: return "other";
220
+ }
221
+ }
222
+ function toSubjectState(state) {
223
+ switch (state) {
224
+ case "open":
225
+ case "closed":
226
+ case "merged": return state;
227
+ default: return "unknown";
228
+ }
229
+ }
230
+ function numberFromUrl(url) {
231
+ return /\/(?:issues|pulls|commits|git\/commits)\/([^/?#]+)/.exec(url ?? "")?.[1];
232
+ }
233
+ function toSubject(origin, raw) {
234
+ const repo = toRepoRef(origin, raw.repository);
235
+ const kind = toThreadKind(raw.subject.type);
236
+ if (kind !== "other") return {
237
+ type: "thread",
238
+ thread: {
239
+ ...origin,
240
+ repo,
241
+ kind,
242
+ typeRaw: raw.subject.type,
243
+ number: numberFromUrl(raw.subject.url)
244
+ }
245
+ };
246
+ if (raw.subject.type === "Repository") return {
247
+ type: "repo",
248
+ repo
249
+ };
250
+ return {
251
+ type: "other",
252
+ typeRaw: raw.subject.type,
253
+ repo,
254
+ url: raw.subject.url ?? void 0
255
+ };
256
+ }
257
+ function toNotification(origin, raw) {
258
+ return {
259
+ ref: {
260
+ ...origin,
261
+ id: String(raw.id)
262
+ },
263
+ subject: toSubject(origin, raw),
264
+ subjectTypeRaw: raw.subject.type,
265
+ reason: "unknown",
266
+ unread: raw.unread,
267
+ title: raw.subject.title,
268
+ subjectState: toSubjectState(raw.subject.state),
269
+ subjectStateRaw: raw.subject.state,
270
+ updatedAt: toDate(raw.updated_at) ?? /* @__PURE__ */ new Date(0),
271
+ url: raw.subject.url ?? raw.url,
272
+ raw
273
+ };
274
+ }
275
+ function toThread(ref, raw) {
276
+ const origin = {
277
+ forge: ref.forge,
278
+ instance: ref.instance
279
+ };
280
+ const kind = (Boolean(raw.pull_request) || Boolean(raw.head)) && ref.kind === "issue" ? "pull_request" : ref.kind;
281
+ const state = raw.merged || raw.pull_request?.merged ? "merged" : raw.state === "open" || raw.state === "closed" ? raw.state : "unknown";
282
+ return {
283
+ ref: {
284
+ ...ref,
285
+ kind,
286
+ externalId: raw.id === void 0 ? ref.externalId : String(raw.id)
287
+ },
288
+ kind,
289
+ title: raw.title,
290
+ body: raw.body ?? void 0,
291
+ state,
292
+ stateRaw: raw.state,
293
+ isDraft: raw.draft ?? raw.pull_request?.draft ?? false,
294
+ author: toActor(origin, raw.user),
295
+ assignees: (raw.assignees ?? []).flatMap((user) => toActor(origin, user) ?? []),
296
+ reviewers: (raw.requested_reviewers ?? []).flatMap((user) => {
297
+ const actor = toActor(origin, user);
298
+ return actor ? [{
299
+ actor,
300
+ state: "pending",
301
+ stateRaw: "requested"
302
+ }] : [];
303
+ }),
304
+ labels: (raw.labels ?? []).map((label) => ({
305
+ name: label.name,
306
+ colour: label.color,
307
+ description: label.description || void 0
308
+ })),
309
+ url: raw.html_url,
310
+ createdAt: toDate(raw.created_at),
311
+ updatedAt: toDate(raw.updated_at),
312
+ closedAt: toDate(raw.closed_at),
313
+ lastActivityAt: toDate(raw.updated_at),
314
+ locked: raw.is_locked,
315
+ commentCount: raw.comments,
316
+ milestone: toMilestone(raw.milestone),
317
+ branches: raw.head && raw.base ? {
318
+ head: {
319
+ ref: raw.head.ref,
320
+ sha: raw.head.sha,
321
+ repo: raw.head.repo ? toRepoRef(origin, raw.head.repo) : void 0
322
+ },
323
+ base: {
324
+ ref: raw.base.ref,
325
+ sha: raw.base.sha
326
+ },
327
+ mergeCommitSha: raw.merge_commit_sha ?? void 0
328
+ } : void 0,
329
+ raw
330
+ };
331
+ }
332
+ function toComment(thread, raw) {
333
+ return {
334
+ ref: {
335
+ forge: thread.forge,
336
+ instance: thread.instance,
337
+ thread,
338
+ id: String(raw.id)
339
+ },
340
+ body: raw.body ?? "",
341
+ author: toActor({
342
+ forge: thread.forge,
343
+ instance: thread.instance
344
+ }, raw.user),
345
+ createdAt: toDate(raw.created_at),
346
+ updatedAt: toDate(raw.updated_at),
347
+ url: raw.html_url,
348
+ raw
349
+ };
350
+ }
351
+ const EVENT_KINDS$1 = {
352
+ comment: "comment",
353
+ review: "review",
354
+ code_comment: "review_comment",
355
+ commit_ref: "commit",
356
+ issue_ref: "referenced",
357
+ pull_ref: "referenced",
358
+ change_issue_ref: "referenced",
359
+ pull_push: "commit",
360
+ label: "label",
361
+ close: "state_change",
362
+ reopen: "state_change",
363
+ merge_pull: "state_change",
364
+ pull_request_ready_for_review: "state_change",
365
+ assignees: "assignment"
366
+ };
367
+ function toEventKind(type) {
368
+ return EVENT_KINDS$1[type ?? ""] ?? "other";
369
+ }
370
+ function summarise(entry, kind, actor) {
371
+ const who = actor?.login ?? "someone";
372
+ switch (kind) {
373
+ case "comment": return `${who} commented`;
374
+ case "review": return `${who} reviewed`;
375
+ case "review_comment": return `${who} commented on a line`;
376
+ case "commit": return `${who} referenced ${entry.ref_commit_sha?.slice(0, 7) || "a commit"}`;
377
+ case "referenced": return `${who} referenced this${entry.ref_issue ? ` from #${entry.ref_issue.number}` : ""}`;
378
+ case "label": return `${who} changed label ${entry.label?.name ?? ""}`.trim();
379
+ case "state_change": return `${who} ${entry.type ?? "changed state"}`;
380
+ case "assignment": return `${who} assigned ${entry.assignee?.login ?? ""}`.trim();
381
+ default: return `${who} ${entry.type ?? "acted"}`;
382
+ }
383
+ }
384
+ /** `issue_ref`, `pull_ref` and `change_issue_ref` name the thread that mentioned this one. */
385
+ function crossReference(thread, entry) {
386
+ const source = entry.ref_issue;
387
+ if (!source) return { type: "referenced" };
388
+ const [owner = "", name = ""] = source.repository?.full_name.split("/") ?? [];
389
+ const repo = name ? {
390
+ forge: thread.forge,
391
+ instance: thread.instance,
392
+ owner,
393
+ name,
394
+ externalId: source.repository?.id === void 0 ? void 0 : String(source.repository.id)
395
+ } : thread.repo;
396
+ return {
397
+ type: "referenced",
398
+ from: {
399
+ forge: thread.forge,
400
+ instance: thread.instance,
401
+ repo,
402
+ kind: source.pull_request ? "pull_request" : "issue",
403
+ number: String(source.number)
404
+ },
405
+ fromRepo: repo
406
+ };
407
+ }
408
+ function toEvent(thread, entry, source = "poll") {
409
+ const origin = {
410
+ forge: thread.forge,
411
+ instance: thread.instance
412
+ };
413
+ const kind = toEventKind(entry.type);
414
+ const actor = toActor(origin, entry.user);
415
+ return {
416
+ ...origin,
417
+ id: String(entry.id ?? `${thread.number ?? ""}-${entry.type}-${entry.created_at}`),
418
+ kind,
419
+ kindRaw: entry.type,
420
+ summary: summarise(entry, kind, actor),
421
+ occurredAt: toDate(entry.created_at) ?? /* @__PURE__ */ new Date(0),
422
+ actor,
423
+ repo: thread.repo,
424
+ thread,
425
+ ...kind === "referenced" ? { detail: crossReference(thread, entry) } : {},
426
+ source,
427
+ payload: entry
428
+ };
429
+ }
430
+ /** Commit status values. `warning` is informational and neither passes nor fails. */
431
+ function statusState(status) {
432
+ switch (status) {
433
+ case "success": return "success";
434
+ case "failure":
435
+ case "error": return "failure";
436
+ case "pending": return "pending";
437
+ case "warning": return "neutral";
438
+ default: return "unknown";
439
+ }
440
+ }
441
+ function toStatusCheck(repo, raw) {
442
+ const state = statusState(raw.status);
443
+ return {
444
+ ref: {
445
+ forge: repo.forge,
446
+ instance: repo.instance,
447
+ repo,
448
+ id: String(raw.id),
449
+ type: "status"
450
+ },
451
+ name: raw.context,
452
+ state,
453
+ stateRaw: raw.status,
454
+ url: raw.target_url || void 0,
455
+ startedAt: toDate(raw.created_at),
456
+ completedAt: state === "pending" ? void 0 : toDate(raw.updated_at),
457
+ raw
458
+ };
459
+ }
460
+ function toStatusChecks(repo, raw) {
461
+ return (raw.statuses ?? []).map((status) => toStatusCheck(repo, status));
462
+ }
463
+ /** Every Actions run and job status behind each normalised state, as the repeated `status` filter takes them. */
464
+ const ACTION_STATES = {
465
+ pending: [
466
+ "waiting",
467
+ "running",
468
+ "blocked"
469
+ ],
470
+ success: ["success"],
471
+ failure: ["failure", "cancelled"],
472
+ neutral: ["skipped"]
473
+ };
474
+ /** Gitea's statuses and conclusions behind each normalised state, as its repeated `status` filter takes them. */
475
+ const GITEA_ACTION_STATES = {
476
+ pending: [
477
+ "queued",
478
+ "waiting",
479
+ "in_progress"
480
+ ],
481
+ success: ["success"],
482
+ failure: ["failure", "cancelled"],
483
+ neutral: ["skipped"]
484
+ };
485
+ function stateOf(states) {
486
+ return new Map(Object.entries(states).flatMap(([state, raws]) => raws.map((raw) => [raw, state])));
487
+ }
488
+ const ACTION_STATE_OF = /* @__PURE__ */ stateOf(ACTION_STATES);
489
+ const GITEA_ACTION_STATE_OF = /* @__PURE__ */ stateOf(GITEA_ACTION_STATES);
490
+ function actionState(status) {
491
+ return ACTION_STATE_OF.get(status) ?? "unknown";
492
+ }
493
+ function giteaActionState(raw) {
494
+ return GITEA_ACTION_STATE_OF.get(raw.status === "completed" ? raw.conclusion ?? "" : raw.status) ?? "unknown";
495
+ }
496
+ /** Forgejo and Gitea write the zero Unix time for a run or job that never started or stopped. */
497
+ function actionTime(value) {
498
+ const date = toDate(value);
499
+ return date && date.getTime() > 0 ? date : void 0;
500
+ }
501
+ /** `prettyref` is the short ref; pull request runs carry `#<number>` or a bare sha instead of a branch. */
502
+ function actionBranch(raw) {
503
+ const ref = raw.prettyref;
504
+ return ref && !ref.startsWith("#") && ref !== raw.commit_sha ? ref : void 0;
505
+ }
506
+ function toActionRun(repo, raw) {
507
+ const state = actionState(raw.status);
508
+ return {
509
+ ref: {
510
+ forge: repo.forge,
511
+ instance: repo.instance,
512
+ repo,
513
+ id: String(raw.id)
514
+ },
515
+ name: raw.workflow_id || raw.title || String(raw.id),
516
+ state,
517
+ stateRaw: raw.status,
518
+ number: raw.index_in_repo === void 0 ? void 0 : String(raw.index_in_repo),
519
+ eventRaw: raw.trigger_event || raw.event,
520
+ branch: actionBranch(raw),
521
+ sha: raw.commit_sha || void 0,
522
+ url: raw.html_url || void 0,
523
+ actor: toActor({
524
+ forge: repo.forge,
525
+ instance: repo.instance
526
+ }, raw.trigger_user),
527
+ createdAt: toDate(raw.created),
528
+ startedAt: actionTime(raw.started),
529
+ completedAt: state === "pending" ? void 0 : actionTime(raw.stopped),
530
+ raw
531
+ };
532
+ }
533
+ function toActionJob(run, raw) {
534
+ return {
535
+ ref: {
536
+ forge: run.forge,
537
+ instance: run.instance,
538
+ repo: run.repo,
539
+ id: String(raw.id),
540
+ run
541
+ },
542
+ name: raw.name,
543
+ state: actionState(raw.status),
544
+ stateRaw: raw.status,
545
+ url: raw.html_url || void 0,
546
+ raw
547
+ };
548
+ }
549
+ function toGiteaActionRun(repo, raw) {
550
+ const state = giteaActionState(raw);
551
+ return {
552
+ ref: {
553
+ forge: repo.forge,
554
+ instance: repo.instance,
555
+ repo,
556
+ id: String(raw.id)
557
+ },
558
+ name: raw.path?.split("@refs/")[0] || raw.display_title || String(raw.id),
559
+ state,
560
+ stateRaw: raw.status,
561
+ number: raw.run_number === void 0 ? void 0 : String(raw.run_number),
562
+ eventRaw: raw.event,
563
+ branch: raw.head_branch || void 0,
564
+ sha: raw.head_sha || void 0,
565
+ url: raw.html_url || void 0,
566
+ actor: toActor({
567
+ forge: repo.forge,
568
+ instance: repo.instance
569
+ }, raw.actor),
570
+ createdAt: toDate(raw.created_at),
571
+ startedAt: actionTime(raw.started_at),
572
+ completedAt: state === "pending" ? void 0 : actionTime(raw.completed_at),
573
+ raw
574
+ };
575
+ }
576
+ function toGiteaActionJob(run, raw) {
577
+ const state = giteaActionState(raw);
578
+ return {
579
+ ref: {
580
+ forge: run.forge,
581
+ instance: run.instance,
582
+ repo: run.repo,
583
+ id: String(raw.id),
584
+ run
585
+ },
586
+ name: raw.name,
587
+ state,
588
+ stateRaw: raw.status,
589
+ url: raw.html_url || void 0,
590
+ startedAt: actionTime(raw.started_at),
591
+ completedAt: state === "pending" ? void 0 : actionTime(raw.completed_at),
592
+ raw
593
+ };
594
+ }
595
+ function toRelease(repo, raw) {
596
+ const origin = {
597
+ forge: repo.forge,
598
+ instance: repo.instance
599
+ };
600
+ const ref = {
601
+ ...origin,
602
+ repo,
603
+ id: String(raw.id),
604
+ tag: raw.tag_name
605
+ };
606
+ return {
607
+ ref,
608
+ name: raw.name || void 0,
609
+ tag: raw.tag_name,
610
+ body: raw.body || void 0,
611
+ isDraft: raw.draft,
612
+ isPrerelease: raw.prerelease,
613
+ author: raw.author ? toActor(repo, raw.author) : void 0,
614
+ publishedAt: raw.draft ? void 0 : toDate(raw.published_at),
615
+ createdAt: toDate(raw.created_at),
616
+ url: raw.html_url,
617
+ assets: raw.assets?.map((asset) => ({
618
+ ...asset.id === void 0 ? {} : { ref: {
619
+ ...origin,
620
+ repo,
621
+ release: ref,
622
+ id: String(asset.id)
623
+ } },
624
+ name: asset.name,
625
+ url: asset.browser_download_url,
626
+ size: asset.size,
627
+ downloadCount: asset.download_count
628
+ })),
629
+ raw
630
+ };
631
+ }
632
+ function toReviewComment(thread, raw) {
633
+ return {
634
+ ref: {
635
+ forge: thread.forge,
636
+ instance: thread.instance,
637
+ thread,
638
+ id: String(raw.id)
639
+ },
640
+ body: raw.body ?? "",
641
+ author: toActor(thread, raw.user),
642
+ path: raw.path,
643
+ line: raw.position ?? raw.original_position,
644
+ side: raw.position ? "right" : raw.original_position ? "left" : void 0,
645
+ createdAt: toDate(raw.created_at),
646
+ url: raw.html_url,
647
+ raw
648
+ };
649
+ }
650
+ function toReview(thread, raw, comments) {
651
+ return {
652
+ ref: {
653
+ forge: thread.forge,
654
+ instance: thread.instance,
655
+ thread,
656
+ id: String(raw.id)
657
+ },
658
+ author: toActor(thread, raw.user),
659
+ state: reviewState(raw.state?.toLowerCase() === "request_changes" ? "changes_requested" : raw.state),
660
+ stateRaw: raw.state,
661
+ body: raw.body || void 0,
662
+ submittedAt: toDate(raw.submitted_at),
663
+ comments,
664
+ url: raw.html_url,
665
+ raw
666
+ };
667
+ }
668
+ function toChangedFile(raw) {
669
+ return {
670
+ path: raw.filename,
671
+ previousPath: raw.previous_filename,
672
+ status: toFileStatus(raw.status),
673
+ statusRaw: raw.status,
674
+ additions: raw.additions,
675
+ deletions: raw.deletions
676
+ };
677
+ }
678
+ function toCommit(repo, raw) {
679
+ const origin = {
680
+ forge: repo.forge,
681
+ instance: repo.instance
682
+ };
683
+ return {
684
+ ref: {
685
+ ...origin,
686
+ repo,
687
+ sha: raw.sha
688
+ },
689
+ sha: raw.sha,
690
+ message: raw.commit?.message ?? "",
691
+ author: {
692
+ actor: toActor(origin, raw.author),
693
+ name: raw.commit?.author?.name,
694
+ email: raw.commit?.author?.email,
695
+ date: toDate(raw.commit?.author?.date)
696
+ },
697
+ committer: {
698
+ actor: toActor(origin, raw.committer),
699
+ name: raw.commit?.committer?.name,
700
+ email: raw.commit?.committer?.email,
701
+ date: toDate(raw.commit?.committer?.date)
702
+ },
703
+ parents: raw.parents?.flatMap((parent) => parent.sha ? [parent.sha] : []) ?? [],
704
+ url: raw.html_url,
705
+ ...raw.stats ? { stats: {
706
+ additions: raw.stats.additions ?? 0,
707
+ deletions: raw.stats.deletions ?? 0,
708
+ total: raw.stats.total
709
+ } } : {},
710
+ ...raw.files ? { files: raw.files.map(toChangedFile) } : {},
711
+ raw
712
+ };
713
+ }
714
+ function toBranch(raw) {
715
+ return {
716
+ name: raw.name,
717
+ sha: raw.commit?.id ?? "",
718
+ isProtected: raw.protected,
719
+ raw
720
+ };
721
+ }
722
+ function toTag(raw) {
723
+ return {
724
+ name: raw.name,
725
+ sha: raw.commit?.sha ?? raw.id ?? "",
726
+ raw
727
+ };
728
+ }
729
+ const TREE_TYPES = {
730
+ blob: "file",
731
+ tree: "directory",
732
+ commit: "submodule"
733
+ };
734
+ function toTreeEntry(raw) {
735
+ return {
736
+ path: raw.path,
737
+ type: raw.mode === "120000" ? "symlink" : TREE_TYPES[raw.type ?? ""] ?? "file",
738
+ sha: raw.sha,
739
+ size: raw.size,
740
+ mode: raw.mode
741
+ };
742
+ }
743
+ function toWebhook(target, raw) {
744
+ const nativeEvents = raw.events ?? [];
745
+ return {
746
+ ref: {
747
+ forge: target.forge,
748
+ instance: target.instance,
749
+ target,
750
+ id: String(raw.id)
751
+ },
752
+ url: raw.config?.url ?? "",
753
+ events: eventKindsOf(FORGEJO_NATIVE_EVENTS, nativeEvents),
754
+ nativeEvents,
755
+ active: raw.active ?? true,
756
+ contentType: raw.config?.content_type === "form" ? "form" : "json",
757
+ createdAt: toDate(raw.created_at),
758
+ updatedAt: toDate(raw.updated_at),
759
+ raw
760
+ };
761
+ }
762
+ //#endregion
763
+ //#region src/forgejo/pages.ts
764
+ /** A full page is followed even when `x-total-count` disagrees, because timelines report the page's own length. */
765
+ function countedNextUrl(url, count, totalHeader) {
766
+ const next = new URL(url);
767
+ const page = Number(next.searchParams.get("page")) || 1;
768
+ const limit = Number(next.searchParams.get("limit")) || void 0;
769
+ if (count === 0 || limit !== void 0 && count > limit) return;
770
+ const size = limit ?? count;
771
+ const total = totalHeader === null ? NaN : Number(totalHeader);
772
+ if (!(Number.isFinite(total) && (page - 1) * size + count < total) && count !== limit) return;
773
+ next.searchParams.set("page", String(page + 1));
774
+ next.searchParams.set("limit", String(Math.min(count, size)));
775
+ return next.toString();
776
+ }
777
+ /** `fetcher` with `page` and `items` that also continue listings Forgejo and Gitea page without a `Link` header. */
778
+ function countedPages(fetcher) {
779
+ async function page(path, options = {}) {
780
+ const result = await fetcher.page(path, options);
781
+ if (result.cursor?.nextUrl || result.notModified) return result;
782
+ const nextUrl = countedNextUrl(options.cursor?.nextUrl ? fetcher.resolve(options.cursor.nextUrl) : fetcher.resolve(path, options.query), result.data.length, result.response.headers.get("x-total-count"));
783
+ return nextUrl ? {
784
+ ...result,
785
+ cursor: {
786
+ ...result.cursor,
787
+ nextUrl
788
+ }
789
+ } : result;
790
+ }
791
+ async function* items(path, options = {}) {
792
+ let cursor;
793
+ do {
794
+ const result = await page(path, {
795
+ ...options,
796
+ cursor
797
+ });
798
+ yield* result.data;
799
+ cursor = result.notModified ? void 0 : result.cursor;
800
+ } while (cursor?.nextUrl);
801
+ }
802
+ return {
803
+ ...fetcher,
804
+ page,
805
+ items
806
+ };
807
+ }
808
+ //#endregion
809
+ //#region src/forgejo/webhooks.ts
810
+ function verifyForgejoSignature(delivery, secret, names = FORGEJO_HEADERS) {
811
+ return verifyHmacSignature(delivery, secret, { header: names.signature });
812
+ }
813
+ const EVENT_KINDS = {
814
+ issue_comment: "comment",
815
+ pull_request_comment: "comment",
816
+ pull_request_review_comment: "review_comment",
817
+ pull_request_review_approved: "review",
818
+ pull_request_review_rejected: "review",
819
+ pull_request_review_comment_created: "review_comment",
820
+ pull_request_label: "label",
821
+ issue_label: "label",
822
+ issue_assign: "assignment",
823
+ pull_request_assign: "assignment"
824
+ };
825
+ function eventKindFor(event, payload) {
826
+ const mapped = EVENT_KINDS[event];
827
+ if (mapped) return mapped;
828
+ if (event === "issues" || event === "pull_request") switch (payload.action) {
829
+ case "label_updated":
830
+ case "label_cleared": return "label";
831
+ case "assigned":
832
+ case "unassigned": return "assignment";
833
+ case "opened":
834
+ case "closed":
835
+ case "reopened":
836
+ case "merged": return "state_change";
837
+ default: return "other";
838
+ }
839
+ return "other";
840
+ }
841
+ const ZERO_SHA = /^0+$/;
842
+ function repoLevel(origin, event, payload, who) {
843
+ switch (event) {
844
+ case "push": return refEvent(who, {
845
+ ref: payload.ref ?? "",
846
+ before: payload.before,
847
+ after: payload.after,
848
+ deleted: Boolean(payload.after && ZERO_SHA.test(payload.after)),
849
+ commits: (payload.commits ?? []).map((commit) => ({
850
+ sha: commit.id,
851
+ message: commit.message,
852
+ author: commit.author?.username ?? commit.author?.name,
853
+ url: commit.url
854
+ })),
855
+ commitCount: payload.total_commits
856
+ });
857
+ case "create":
858
+ case "delete": return {
859
+ kind: "ref",
860
+ action: event === "create" ? "created" : "deleted",
861
+ detail: {
862
+ type: "ref",
863
+ ref: payload.ref ?? "",
864
+ refType: payload.ref_type ?? "branch"
865
+ },
866
+ summary: `${who} ${event === "create" ? "created" : "deleted"} ${payload.ref_type ?? "ref"} ${payload.ref ?? ""}`.trim()
867
+ };
868
+ case "release":
869
+ if (payload.action !== "published" || !payload.release || !payload.repository) return;
870
+ return {
871
+ kind: "release",
872
+ action: "published",
873
+ detail: {
874
+ type: "release",
875
+ release: {
876
+ ...origin,
877
+ repo: toRepoRef(origin, payload.repository),
878
+ id: String(payload.release.id),
879
+ tag: payload.release.tag_name
880
+ },
881
+ name: payload.release.name
882
+ },
883
+ summary: `${who} published ${payload.release.name || payload.release.tag_name || "a release"}`
884
+ };
885
+ default: return;
886
+ }
887
+ }
888
+ function detailOf(kind, event, thread, origin, payload) {
889
+ const comment = payload.comment;
890
+ const commentRef = comment?.id !== void 0 && thread ? {
891
+ ...origin,
892
+ thread,
893
+ id: String(comment.id)
894
+ } : void 0;
895
+ switch (kind) {
896
+ case "comment": return {
897
+ type: "comment",
898
+ comment: commentRef,
899
+ body: comment?.body
900
+ };
901
+ case "review_comment": return {
902
+ type: "review_comment",
903
+ comment: commentRef,
904
+ body: comment?.body,
905
+ path: comment?.path,
906
+ line: comment?.line
907
+ };
908
+ case "review": {
909
+ const stateRaw = event.replace("pull_request_review_", "");
910
+ return {
911
+ type: "review",
912
+ state: reviewState(stateRaw === "rejected" ? "changes_requested" : stateRaw),
913
+ stateRaw,
914
+ body: payload.review?.content
915
+ };
916
+ }
917
+ case "label": return payload.label?.name ? {
918
+ type: "label",
919
+ label: payload.label.name
920
+ } : void 0;
921
+ case "assignment": return {
922
+ type: "assignment",
923
+ assignee: toActor(origin, payload.assignee)
924
+ };
925
+ case "state_change": return {
926
+ type: "state_change",
927
+ state: payload.pull_request?.merged ? "merged" : payload.action === "closed" ? "closed" : "open",
928
+ draft: payload.pull_request?.draft
929
+ };
930
+ default: return;
931
+ }
932
+ }
933
+ function translateForgejoWebhook(origin, delivery, names = FORGEJO_HEADERS) {
934
+ const event = firstHeader(delivery, names.event);
935
+ if (!event) throw new WebhookVerificationError(`Missing ${names.event[0]} header`, origin);
936
+ const deliveryId = firstHeader(delivery, names.delivery) ?? crypto.randomUUID();
937
+ const payload = JSON.parse(bodyText(delivery.body));
938
+ const repo = payload.repository ? toRepoRef(origin, payload.repository) : void 0;
939
+ const actor = toActor(origin, payload.sender);
940
+ const who = actor?.login ?? "someone";
941
+ const level = repoLevel(origin, event, payload, who);
942
+ if (level) return [{
943
+ ...origin,
944
+ id: deliveryId,
945
+ kind: level.kind,
946
+ action: level.action,
947
+ kindRaw: payload.action ? `${event}.${payload.action}` : event,
948
+ summary: level.summary,
949
+ occurredAt: toDate(payload.commits?.at(-1)?.timestamp ?? payload.release?.published_at) ?? /* @__PURE__ */ new Date(),
950
+ actor,
951
+ repo,
952
+ detail: level.detail,
953
+ source: "webhook",
954
+ payload
955
+ }];
956
+ const subject = payload.pull_request ?? payload.issue;
957
+ const isPull = Boolean(payload.pull_request || payload.is_pull || payload.issue?.pull_request);
958
+ const thread = repo && subject ? {
959
+ ...origin,
960
+ repo,
961
+ kind: isPull ? "pull_request" : "issue",
962
+ number: String(subject.number)
963
+ } : void 0;
964
+ const kind = eventKindFor(event, payload);
965
+ return [{
966
+ ...origin,
967
+ id: deliveryId,
968
+ kind,
969
+ kindRaw: payload.action ? `${event}.${payload.action}` : event,
970
+ action: payload.action === "closed" && payload.pull_request?.merged ? "merged" : eventAction(payload.action),
971
+ actionRaw: payload.action,
972
+ detail: detailOf(kind, event, thread, origin, payload),
973
+ summary: `${who} ${payload.action ? `${payload.action} ` : ""}${event}`.trim(),
974
+ occurredAt: toDate(payload.comment?.created_at) ?? /* @__PURE__ */ new Date(),
975
+ actor,
976
+ repo,
977
+ thread,
978
+ source: "webhook",
979
+ payload
980
+ }];
981
+ }
982
+ /** Handlers for one deployment family; Gitea differs from Forgejo only in its header names. */
983
+ function forgejoWebhooks(profile) {
984
+ return ({ options, origin }) => ({
985
+ events: FORGEJO_WEBHOOK_EVENTS,
986
+ verify: (delivery) => verifyForgejoSignature(delivery, options.webhookSecret, profile.headers),
987
+ translate: (delivery) => translateForgejoWebhook(origin, delivery, profile.headers)
988
+ });
989
+ }
990
+ //#endregion
991
+ //#region src/forgejo/index.ts
992
+ const FORGEJO_PROFILE = {
993
+ forge: "forgejo",
994
+ defaultBaseUrl: "https://codeberg.org",
995
+ headers: FORGEJO_HEADERS
996
+ };
997
+ const ISSUE_AND_PULL = {
998
+ issue: true,
999
+ pull_request: true
1000
+ };
1001
+ /** Forgejo versions with the Actions runs API, and with run jobs and job logs. */
1002
+ const FORGEJO_ACTION_RUNS = "12.0";
1003
+ const FORGEJO_ACTION_JOBS = "16.0";
1004
+ /** Gitea versions with runs and their jobs, and with job logs. */
1005
+ const GITEA_ACTION_RUNS = "1.25";
1006
+ const GITEA_ACTION_LOGS = "1.24";
1007
+ /** Timelines send no usable count, so only a full page of an explicit size shows that another follows. */
1008
+ const TIMELINE_PAGE_SIZE = 50;
1009
+ /** Whether `user` has `login`, in any case; `true` when no login is wanted. Forgejo's issue search ignores `created_by` and `assigned_by`. */
1010
+ function sameLogin(user, login) {
1011
+ return !login || user?.login.toLowerCase() === login.toLowerCase();
1012
+ }
1013
+ const FORGEJO_RESERVED_PATHS = [
1014
+ "-",
1015
+ ".well-known",
1016
+ "admin",
1017
+ "api",
1018
+ "assets",
1019
+ "attachments",
1020
+ "avatars",
1021
+ "captcha",
1022
+ "explore",
1023
+ "issues",
1024
+ "login",
1025
+ "milestones",
1026
+ "notifications",
1027
+ "org",
1028
+ "pulls",
1029
+ "repo",
1030
+ "repo-avatars",
1031
+ "search",
1032
+ "user"
1033
+ ];
1034
+ /** The Forgejo implementation for one deployment family. Shared by `forgejo()` and `gitea()`. */
1035
+ function forgejoDefinition(profile) {
1036
+ return {
1037
+ forge: profile.forge,
1038
+ baseUrl: profile.defaultBaseUrl,
1039
+ anonymous: true,
1040
+ apiPath: "/api/v1",
1041
+ headers: { accept: "application/json" },
1042
+ authHeaders: ({ options: { auth } }) => auth?.type === "token" ? async () => ({ authorization: `token ${await resolveToken(auth)}` }) : void 0,
1043
+ setup: (ctx) => setupForgejo(ctx, profile)
1044
+ };
1045
+ }
1046
+ function setupForgejo({ options, origin, fetcher: baseFetcher, baseUrl }, profile) {
1047
+ const context = origin;
1048
+ /** Collaborators, assignees, reviewers and permissions are served only to signed-in users. */
1049
+ const anonymous = options.auth?.type === "anonymous";
1050
+ const fetcher = countedPages(baseFetcher);
1051
+ const list = createListing(fetcher, "limit");
1052
+ async function readUser(path, signal) {
1053
+ const { data } = await fetcher.json(path, { signal });
1054
+ return {
1055
+ ...toActor(origin, data),
1056
+ bio: data.description || void 0,
1057
+ location: data.location || void 0,
1058
+ websiteUrl: data.website || void 0,
1059
+ createdAt: toDate(data.created),
1060
+ followers: data.followers_count,
1061
+ following: data.following_count,
1062
+ raw: data
1063
+ };
1064
+ }
1065
+ function repoPath(repo) {
1066
+ return `/repos/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.name)}`;
1067
+ }
1068
+ function threadPath(ref) {
1069
+ switch (ref.kind) {
1070
+ case "pull_request": return `${repoPath(ref.repo)}/pulls/${encodeURIComponent(ref.number)}`;
1071
+ case "commit": return `${repoPath(ref.repo)}/git/commits/${encodeURIComponent(ref.number)}`;
1072
+ case "discussion": throw new UnsupportedOperationError(`${profile.forge} has no discussions`, context);
1073
+ default: return `${repoPath(ref.repo)}/issues/${encodeURIComponent(ref.number)}`;
1074
+ }
1075
+ }
1076
+ const currentUser = memo(() => fetcher.json("/user").then((result) => result.data));
1077
+ /** Labels are addressed by id in writes, so names are resolved once per repo. */
1078
+ /** Forgejo and Gitea address reactions on issues and on comments with the same body. */
1079
+ function reactionPath(target) {
1080
+ return "thread" in target ? `${repoPath(target.thread.repo)}/issues/comments/${encodeURIComponent(target.id)}/reactions` : `${issuePath(requireIssueOrPull(target, context, "react to"))}/reactions`;
1081
+ }
1082
+ async function setReaction(target, reaction, method, options) {
1083
+ await fetcher.raw(reactionPath(target), {
1084
+ method,
1085
+ json: { content: reaction },
1086
+ signal: options?.signal
1087
+ });
1088
+ }
1089
+ const labelIdsOf = memoBy(async (path) => {
1090
+ const map = /* @__PURE__ */ new Map();
1091
+ for await (const label of fetcher.items(path, { query: { limit: 50 } })) map.set(label.name, label.id);
1092
+ return map;
1093
+ });
1094
+ /** Labels an organisation shares with its repositories; none for a user, whose `/orgs` route is a 404. */
1095
+ const orgLabelIdsOf = memoBy(async (owner) => {
1096
+ try {
1097
+ return await labelIdsOf(`/orgs/${encodeURIComponent(owner)}/labels`);
1098
+ } catch (error) {
1099
+ if (error instanceof NotFoundError) return /* @__PURE__ */ new Map();
1100
+ throw error;
1101
+ }
1102
+ });
1103
+ async function resolveLabels(repo, names) {
1104
+ const map = await labelIdsOf(`${repoPath(repo)}/labels`);
1105
+ const shared = names.some((name) => !map.has(name)) ? await orgLabelIdsOf(repo.owner) : void 0;
1106
+ return names.map((name) => {
1107
+ const id = map.get(name) ?? shared?.get(name);
1108
+ if (id === void 0) throw new UnsupportedOperationError(`No label named ${name} in ${repo.owner}/${repo.name}`, context);
1109
+ return id;
1110
+ });
1111
+ }
1112
+ async function notificationPage(listOptions = {}) {
1113
+ const result = await fetcher.json(listOptions.cursor?.nextUrl ?? "/notifications", {
1114
+ query: listOptions.cursor?.nextUrl ? void 0 : {
1115
+ all: listOptions.all ?? false,
1116
+ since: listOptions.since?.toISOString(),
1117
+ limit: listOptions.perPage
1118
+ },
1119
+ signal: listOptions.signal
1120
+ });
1121
+ return {
1122
+ items: (result.data ?? []).map((raw) => toNotification(origin, raw)),
1123
+ cursor: result.cursor,
1124
+ notModified: result.notModified
1125
+ };
1126
+ }
1127
+ async function setStatus(ref, status, options) {
1128
+ await fetcher.raw(`/notifications/threads/${encodeURIComponent(ref.id)}`, {
1129
+ method: "PATCH",
1130
+ query: { "to-status": status },
1131
+ signal: options?.signal
1132
+ });
1133
+ }
1134
+ async function issueTarget(ref, { thread, signal }) {
1135
+ if (thread) {
1136
+ const resolved = requireThread(thread, context);
1137
+ if (resolved.kind !== "issue" && resolved.kind !== "pull_request") throw new UnsupportedOperationError("Only issue and pull request threads can be unsubscribed from", context);
1138
+ return {
1139
+ owner: resolved.repo.owner,
1140
+ name: resolved.repo.name,
1141
+ number: resolved.number
1142
+ };
1143
+ }
1144
+ const { data } = await fetcher.json(`/notifications/threads/${encodeURIComponent(ref.id)}`, { signal });
1145
+ const number = numberFromUrl(data.subject.url);
1146
+ if (!number || toThreadKind(data.subject.type) === "commit") throw new UnsupportedOperationError("Only issue and pull request threads can be unsubscribed from", context);
1147
+ const [owner = "", name = ""] = data.repository.full_name.split("/");
1148
+ return {
1149
+ owner,
1150
+ name,
1151
+ number
1152
+ };
1153
+ }
1154
+ async function subscriptionPath(target) {
1155
+ const user = await currentUser();
1156
+ return `${repoPath(target)}/issues/${encodeURIComponent(target.number)}/subscriptions/${encodeURIComponent(user.login)}`;
1157
+ }
1158
+ async function unsubscribe(ref, writeOptions = {}) {
1159
+ await fetcher.raw(await subscriptionPath(await issueTarget(ref, writeOptions)), {
1160
+ method: "DELETE",
1161
+ signal: writeOptions.signal
1162
+ });
1163
+ }
1164
+ function issuePath(ref) {
1165
+ return `${repoPath(ref.repo)}/issues/${encodeURIComponent(ref.number)}`;
1166
+ }
1167
+ async function setState(thread, state, options) {
1168
+ await fetcher.raw(issuePath(requireIssueOrPull(thread, context, state === "open" ? "reopen" : "close")), {
1169
+ method: "PATCH",
1170
+ json: { state },
1171
+ signal: options?.signal
1172
+ });
1173
+ }
1174
+ async function merge(thread, mergeOptions = {}, hooks = {}) {
1175
+ const ref = requireThread(thread, context);
1176
+ if (ref.kind !== "pull_request") throw new UnsupportedOperationError("Only pull requests can be merged", context);
1177
+ const pull = `${repoPath(ref.repo)}/pulls/${encodeURIComponent(ref.number)}`;
1178
+ let method = mergeOptions.method;
1179
+ if (!method) {
1180
+ const { data: repo } = await fetcher.json(repoPath(ref.repo), { signal: mergeOptions.signal });
1181
+ method = soleMergeMethod({
1182
+ merge: repo.allow_merge_commits,
1183
+ rebase: repo.allow_rebase,
1184
+ rebase_merge: repo.allow_rebase_explicit,
1185
+ squash: repo.allow_squash_merge,
1186
+ fast_forward_only: repo.allow_fast_forward_only_merge
1187
+ }, context);
1188
+ }
1189
+ await hooks.beforeMerge?.();
1190
+ await fetcher.raw(`${pull}/merge`, {
1191
+ method: "POST",
1192
+ json: {
1193
+ Do: method.replaceAll("_", "-"),
1194
+ MergeTitleField: mergeOptions.title,
1195
+ MergeMessageField: mergeOptions.message,
1196
+ head_commit_id: mergeOptions.sha,
1197
+ merge_when_checks_succeed: mergeOptions.whenChecksPass
1198
+ },
1199
+ mapError: toMergeError,
1200
+ signal: mergeOptions.signal
1201
+ });
1202
+ }
1203
+ const REVIEW_EVENTS = {
1204
+ approve: "APPROVED",
1205
+ request_changes: "REQUEST_CHANGES",
1206
+ comment: "COMMENT"
1207
+ };
1208
+ function requirePull(thread, action) {
1209
+ const ref = requireThread(thread, context);
1210
+ if (ref.kind !== "pull_request") throw new UnsupportedOperationError(`Only pull requests can be ${action}`, context);
1211
+ return ref;
1212
+ }
1213
+ function pullPath(ref) {
1214
+ return `${repoPath(ref.repo)}/pulls/${encodeURIComponent(ref.number)}`;
1215
+ }
1216
+ function reviewCommentInput(comments) {
1217
+ return comments?.map((comment) => ({
1218
+ path: comment.path,
1219
+ body: comment.body,
1220
+ ...comment.side === "left" ? { old_position: comment.line } : { new_position: comment.line }
1221
+ }));
1222
+ }
1223
+ async function createReview(thread, input, options) {
1224
+ const ref = requirePull(thread, "reviewed");
1225
+ const { data } = await fetcher.json(`${pullPath(ref)}/reviews`, {
1226
+ method: "POST",
1227
+ json: {
1228
+ event: input.event ? REVIEW_EVENTS[input.event] : void 0,
1229
+ body: input.body ?? "",
1230
+ comments: reviewCommentInput(input.comments)
1231
+ },
1232
+ signal: options?.signal
1233
+ });
1234
+ return toReview(ref, data, []);
1235
+ }
1236
+ async function get(thread, options) {
1237
+ const ref = requireThread(thread, context);
1238
+ const { data } = await fetcher.json(threadPath(ref), { signal: options?.signal });
1239
+ const result = toThread(ref, data);
1240
+ if (ref.kind === "pull_request" && data.head?.sha) try {
1241
+ const checks = await statusChecks(ref.repo, data.head.sha, options?.signal);
1242
+ result.checks = summariseChecks(checks.map((check) => check.state));
1243
+ } catch (error) {
1244
+ if (!(error instanceof ForgeError)) throw error;
1245
+ result.warnings = [...result.warnings ?? [], toWarning("checks_unreadable", error, ref.number)];
1246
+ }
1247
+ return result;
1248
+ }
1249
+ const STATUS_STATES = {
1250
+ pending: "pending",
1251
+ success: "success",
1252
+ failure: "failure",
1253
+ neutral: "success",
1254
+ unknown: "pending"
1255
+ };
1256
+ async function statusChecks(repo, sha, signal) {
1257
+ const { data } = await fetcher.json(`${repoPath(repo)}/commits/${sha}/status`, { signal });
1258
+ return toStatusChecks(repo, data);
1259
+ }
1260
+ function milestonesPage(repo, listOptions = {}) {
1261
+ return list(`${repoPath(repo)}/milestones`, listOptions, (raw) => toMilestone(raw), { query: { state: listOptions.state ?? "open" } });
1262
+ }
1263
+ async function releasesPage(repo, listOptions = {}) {
1264
+ return list(`${repoPath(repo)}/releases`, listOptions, (raw) => toRelease(repo, raw));
1265
+ }
1266
+ async function writeRelease(repo, path, method, input, options) {
1267
+ const { data } = await fetcher.json(path, {
1268
+ method,
1269
+ json: {
1270
+ tag_name: input.tag,
1271
+ target_commitish: input.target,
1272
+ name: input.name,
1273
+ body: input.body,
1274
+ draft: input.draft,
1275
+ prerelease: input.prerelease
1276
+ },
1277
+ signal: options?.signal
1278
+ });
1279
+ return toRelease(repo, method === "POST" ? {
1280
+ ...data,
1281
+ assets: []
1282
+ } : data);
1283
+ }
1284
+ async function listPage(repo, query = {}) {
1285
+ const warnings = [];
1286
+ if (query.kind === "discussion") return {
1287
+ items: [],
1288
+ warnings: [{
1289
+ code: "kind_unsupported",
1290
+ message: `${profile.forge} has no discussions`
1291
+ }]
1292
+ };
1293
+ const sort = query.sort ?? "created";
1294
+ const direction = query.direction ?? "desc";
1295
+ if (!query.cursor && (sort !== "created" || direction !== "desc")) warnings.push({
1296
+ code: "sort_unsupported",
1297
+ message: `${profile.forge} lists repository issues newest first only`
1298
+ });
1299
+ const createdAfter = query.createdAfter?.getTime();
1300
+ const result = await fetcher.page(`${repoPath(repo)}/issues`, {
1301
+ query: {
1302
+ state: query.state === "merged" ? "closed" : query.state ?? "open",
1303
+ type: query.kind === "pull_request" ? "pulls" : query.kind === "issue" ? "issues" : void 0,
1304
+ labels: query.labels?.join(","),
1305
+ created_by: query.author,
1306
+ assigned_by: query.assignee,
1307
+ since: query.since?.toISOString(),
1308
+ limit: query.perPage
1309
+ },
1310
+ cursor: query.cursor,
1311
+ signal: query.signal
1312
+ });
1313
+ const older = (raw) => createdAfter !== void 0 && (Date.parse(raw.created_at ?? "") || 0) < createdAfter;
1314
+ const unmerged = (raw) => query.state === "merged" && !raw.pull_request?.merged;
1315
+ const page = toPage(result, (raw) => older(raw) || unmerged(raw) || !hasEveryLabel(raw.labels, query.labels) ? void 0 : toThread({
1316
+ ...origin,
1317
+ repo,
1318
+ kind: raw.pull_request ? "pull_request" : "issue",
1319
+ number: String(raw.number)
1320
+ }, raw), warnings);
1321
+ return (result.data ?? []).some(older) ? {
1322
+ ...page,
1323
+ cursor: void 0
1324
+ } : page;
1325
+ }
1326
+ /** Repository hooks, or organisation hooks for a namespace ref. */
1327
+ function hooksPath(target) {
1328
+ return isNamespaceRef(target) ? `/orgs/${encodeURIComponent(target.owner)}/hooks` : `${repoPath(target)}/hooks`;
1329
+ }
1330
+ async function searchThreadsPage(query) {
1331
+ const warnings = [];
1332
+ if (!query.cursor && query.involves) warnings.push({
1333
+ code: "filter_unsupported",
1334
+ message: `${profile.forge} has no involves filter; it was ignored`
1335
+ });
1336
+ if (!query.cursor && query.sort && query.sort !== "relevance") warnings.push({
1337
+ code: "sort_unsupported",
1338
+ message: `${profile.forge} searches issues newest first only`
1339
+ });
1340
+ const result = await fetcher.page("/repos/issues/search", {
1341
+ query: {
1342
+ q: query.text,
1343
+ type: query.kind === "pull_request" ? "pulls" : query.kind === "issue" ? "issues" : void 0,
1344
+ state: query.state ?? "all",
1345
+ labels: query.labels?.join(","),
1346
+ created_by: query.author,
1347
+ assigned_by: query.assignee,
1348
+ since: query.since?.toISOString(),
1349
+ owner: query.repo?.owner,
1350
+ limit: query.perPage
1351
+ },
1352
+ cursor: query.cursor,
1353
+ signal: query.signal
1354
+ });
1355
+ return toPage(result, (raw) => {
1356
+ const [owner = "", name = ""] = raw.repository?.full_name.split("/") ?? [];
1357
+ const repo = query.repo ?? (name ? {
1358
+ ...origin,
1359
+ owner,
1360
+ name,
1361
+ externalId: raw.repository?.id === void 0 ? void 0 : String(raw.repository.id)
1362
+ } : void 0);
1363
+ if (!repo || !hasEveryLabel(raw.labels, query.labels) || !sameLogin(raw.user, query.author) || query.assignee && !raw.assignees?.some((user) => sameLogin(user, query.assignee)) || query.repo && raw.repository && raw.repository.full_name !== `${query.repo.owner}/${query.repo.name}`) return;
1364
+ return toThread({
1365
+ ...origin,
1366
+ repo,
1367
+ kind: raw.pull_request ? "pull_request" : "issue",
1368
+ number: String(raw.number)
1369
+ }, raw);
1370
+ }, warnings);
1371
+ }
1372
+ const isGitea = profile.forge === "gitea";
1373
+ /** Codeberg and gitea.com run a current release, so only a self-hosted instance waits for its version. */
1374
+ function actions(forgejoMinimum, giteaMinimum) {
1375
+ const minimum = isGitea ? giteaMinimum : forgejoMinimum;
1376
+ return hostOf(baseUrl) === hostOf(profile.defaultBaseUrl) ? true : ({ version }) => versionAtLeast(version, minimum);
1377
+ }
1378
+ /** Gitea lists runs and serves job logs only with a token. */
1379
+ const giteaAnonymous = isGitea && anonymous;
1380
+ /** One page of an Actions listing, read from `key` and continued from `total_count` when no `Link` header is sent. */
1381
+ async function actionsPage(path, key, query, pageOptions) {
1382
+ const url = pageOptions.cursor?.nextUrl ? fetcher.resolve(pageOptions.cursor.nextUrl) : fetcher.resolve(path, query);
1383
+ return fetcher.page(path, {
1384
+ query,
1385
+ cursor: pageOptions.cursor,
1386
+ signal: pageOptions.signal,
1387
+ select: (body, next) => {
1388
+ const listing = body;
1389
+ const items = listing[key] ?? [];
1390
+ return {
1391
+ items,
1392
+ next: next ?? countedNextUrl(url, items.length, listing.total_count === void 0 ? null : String(listing.total_count))
1393
+ };
1394
+ }
1395
+ });
1396
+ }
1397
+ async function runsPage(repo, query = {}) {
1398
+ const states = isGitea ? GITEA_ACTION_STATES : ACTION_STATES;
1399
+ const statuses = query.state ? `?${states[query.state].map((status) => `status=${status}`).join("&")}` : "";
1400
+ const path = `${repoPath(repo)}/actions/runs${statuses}`;
1401
+ if (isGitea) return toPage(await actionsPage(path, "workflow_runs", {
1402
+ branch: query.branch,
1403
+ page: 1,
1404
+ limit: query.perPage ?? 50
1405
+ }, query), (raw) => toGiteaActionRun(repo, raw));
1406
+ const result = await actionsPage(path, "workflow_runs", {
1407
+ ref: query.branch ? `refs/heads/${query.branch}` : void 0,
1408
+ page: 1,
1409
+ limit: query.perPage ?? 50
1410
+ }, query);
1411
+ return toPage(result, (raw) => query.branch && actionBranch(raw) !== query.branch ? void 0 : toActionRun(repo, raw));
1412
+ }
1413
+ async function jobsPage(ref, listOptions = {}) {
1414
+ const path = `${repoPath(ref.repo)}/actions/runs/${encodeURIComponent(ref.id)}/jobs`;
1415
+ if (isGitea) return toPage(await actionsPage(path, "jobs", {
1416
+ page: 1,
1417
+ limit: listOptions.perPage ?? 50
1418
+ }, listOptions), (raw) => toGiteaActionJob(ref, raw));
1419
+ const { data } = await fetcher.json(path, { signal: listOptions.signal });
1420
+ return { items: (data ?? []).map((raw) => toActionJob(ref, raw)) };
1421
+ }
1422
+ async function searchReposPage(query) {
1423
+ const result = await fetcher.page("/repos/search", {
1424
+ query: {
1425
+ q: query.text,
1426
+ owner: query.owner,
1427
+ sort: query.sort === "stars" ? "stars" : query.sort === "created" ? "created" : query.sort === "updated" ? "updated" : void 0,
1428
+ order: query.direction,
1429
+ limit: query.perPage
1430
+ },
1431
+ cursor: query.cursor,
1432
+ signal: query.signal,
1433
+ select: (body, next) => ({
1434
+ items: body.data,
1435
+ next
1436
+ })
1437
+ });
1438
+ return toPage(result, (raw) => toRepo(origin, raw));
1439
+ }
1440
+ return {
1441
+ traits: {
1442
+ eventKinds: "native",
1443
+ authKinds: ["token", "anonymous"]
1444
+ },
1445
+ search: {
1446
+ threadsPage: verb(true, searchThreadsPage),
1447
+ reposPage: verb(true, searchReposPage)
1448
+ },
1449
+ probeVersion: async () => (await fetcher.json("/version")).data.version,
1450
+ users: {
1451
+ get: verb(true, (login, options) => readUser(`/users/${encodeURIComponent(login)}`, options?.signal)),
1452
+ me: verb(true, (options) => readUser("/user", options?.signal))
1453
+ },
1454
+ repos: {
1455
+ get: verb(true, async (ref, options) => toRepo(origin, (await fetcher.json(repoPath(ref), { signal: options?.signal })).data)),
1456
+ listPage: verb(true, (listOptions = {}) => list("/user/repos", listOptions, (raw) => toRepo(origin, raw))),
1457
+ labelsPage: verb(true, (repo, listOptions = {}) => list(`${repoPath(repo)}/labels`, listOptions, toLabel)),
1458
+ createLabel: verb(true, async (repo, label, options) => toLabel((await fetcher.json(`${repoPath(repo)}/labels`, {
1459
+ method: "POST",
1460
+ json: {
1461
+ name: label.name,
1462
+ color: hexColour(label.colour, "#"),
1463
+ description: label.description
1464
+ },
1465
+ signal: options?.signal
1466
+ })).data)),
1467
+ milestonesPage: verb(true, milestonesPage),
1468
+ collaboratorsPage: verb(!anonymous, (repo, listOptions = {}) => list(`${repoPath(repo)}/collaborators`, listOptions, (raw) => ({
1469
+ actor: toActor(origin, raw),
1470
+ role: "read",
1471
+ raw
1472
+ }))),
1473
+ permissionFor: verb(!anonymous, async (repo, actor, options) => {
1474
+ const { data } = await fetcher.json(`${repoPath(repo)}/collaborators/${encodeURIComponent(actorLogin(actor))}/permission`, { signal: options?.signal });
1475
+ return toRole(data.role_name ?? data.permission);
1476
+ }),
1477
+ addCollaborator: verb(true, async (repo, actor, role, options) => {
1478
+ await fetcher.raw(`${repoPath(repo)}/collaborators/${encodeURIComponent(actorLogin(actor))}`, {
1479
+ method: "PUT",
1480
+ json: { permission: role === "admin" ? "admin" : role === "read" || role === "triage" ? "read" : "write" },
1481
+ signal: options?.signal
1482
+ });
1483
+ }),
1484
+ assignableUsersPage: verb(!anonymous, (repo, listOptions = {}) => list(`${repoPath(repo)}/assignees`, listOptions, (raw) => toActor(origin, raw))),
1485
+ reviewerCandidatesPage: verb(!anonymous, (thread, listOptions = {}) => list(`${repoPath(requireThread(thread, context).repo)}/reviewers`, listOptions, (raw) => toActor(origin, raw)))
1486
+ },
1487
+ notifications: {
1488
+ listPage: verb(true, notificationPage),
1489
+ markRead: verb(true, (ref, options) => setStatus(ref, "read", options)),
1490
+ markDone: verb("emulated", async (ref, writeOptions) => {
1491
+ await setStatus(ref, "read", writeOptions);
1492
+ await unsubscribe(ref, writeOptions);
1493
+ }),
1494
+ unsubscribe: verb(true, unsubscribe),
1495
+ markAllRead: verb(true, async (bulk = {}) => {
1496
+ await fetcher.raw(bulk.repo ? `${repoPath(bulk.repo)}/notifications` : "/notifications", {
1497
+ method: "PUT",
1498
+ query: {
1499
+ "last_read_at": bulk.before?.toISOString(),
1500
+ "to-status": "read"
1501
+ },
1502
+ signal: bulk.signal
1503
+ });
1504
+ }),
1505
+ unreadCount: verb(true, async (options) => (await fetcher.json("/notifications/new", { signal: options?.signal })).data.new)
1506
+ },
1507
+ checks: {
1508
+ list: verb(true, async (repo, sha, options) => ({ items: await statusChecks(repo, sha, options?.signal) })),
1509
+ report: verb(true, async (repo, sha, input, options) => toStatusCheck(repo, (await fetcher.json(`${repoPath(repo)}/statuses/${sha}`, {
1510
+ method: "POST",
1511
+ json: {
1512
+ state: STATUS_STATES[input.state],
1513
+ context: input.name,
1514
+ description: input.description,
1515
+ target_url: input.url
1516
+ },
1517
+ signal: options?.signal
1518
+ })).data))
1519
+ },
1520
+ ci: {
1521
+ runsPage: verb(giteaAnonymous ? false : actions(FORGEJO_ACTION_RUNS, GITEA_ACTION_RUNS), runsPage),
1522
+ run: verb(actions(FORGEJO_ACTION_RUNS, GITEA_ACTION_RUNS), async (ref, options) => {
1523
+ const path = `${repoPath(ref.repo)}/actions/runs/${encodeURIComponent(ref.id)}`;
1524
+ return isGitea ? toGiteaActionRun(ref.repo, (await fetcher.json(path, { signal: options?.signal })).data) : toActionRun(ref.repo, (await fetcher.json(path, { signal: options?.signal })).data);
1525
+ }),
1526
+ jobsPage: verb(actions(FORGEJO_ACTION_JOBS, GITEA_ACTION_RUNS), jobsPage),
1527
+ log: verb(giteaAnonymous ? false : actions(FORGEJO_ACTION_JOBS, GITEA_ACTION_LOGS), async (ref, options) => (await fetcher.stream(`${repoPath(ref.repo)}/actions/jobs/${encodeURIComponent(ref.id)}/logs`, { signal: options?.signal })).body)
1528
+ },
1529
+ contents: {
1530
+ file: verb(true, async (repo, path, fileOptions = {}) => {
1531
+ const encoded = path.split("/").map(encodeURIComponent).join("/");
1532
+ const { data } = await fetcher.json(`${repoPath(repo)}/contents/${encoded}`, {
1533
+ query: { ref: fileOptions.ref },
1534
+ signal: fileOptions.signal
1535
+ });
1536
+ const file = {
1537
+ path: data.path,
1538
+ sha: data.sha,
1539
+ size: data.size,
1540
+ url: data.html_url ?? void 0
1541
+ };
1542
+ if (data.encoding === "base64" && data.content) return toFileContent(fromBase64(data.content), file, fileOptions, context);
1543
+ const response = await fetcher.raw(`${repoPath(repo)}/raw/${encoded}`, {
1544
+ query: { ref: fileOptions.ref },
1545
+ signal: fileOptions.signal
1546
+ });
1547
+ return toFileContent(new Uint8Array(await response.arrayBuffer()), file, fileOptions, context);
1548
+ }),
1549
+ treePage: verb(true, async (repo, treeOptions = {}) => {
1550
+ const ref = treeOptions.ref ?? "HEAD";
1551
+ const { data, response } = await fetcher.json(`${repoPath(repo)}/git/trees/${encodeURIComponent(ref)}`, {
1552
+ query: {
1553
+ recursive: treeOptions.recursive ? "true" : void 0,
1554
+ per_page: treeOptions.perPage
1555
+ },
1556
+ signal: treeOptions.signal
1557
+ });
1558
+ const prefix = treeOptions.path?.replace(/^\/|\/$/g, "");
1559
+ const entries = (data.tree ?? []).filter((entry) => !prefix || entry.path.startsWith(`${prefix}/`));
1560
+ const warnings = data.truncated ? [{
1561
+ code: "tree_truncated",
1562
+ message: `${profile.forge} truncated the tree; read it directory by directory instead`,
1563
+ subject: ref
1564
+ }] : [];
1565
+ return toPage({
1566
+ data: entries,
1567
+ response,
1568
+ notModified: false
1569
+ }, toTreeEntry, warnings);
1570
+ }),
1571
+ branchesPage: verb(true, (repo, listOptions = {}) => list(`${repoPath(repo)}/branches`, listOptions, toBranch)),
1572
+ tagsPage: verb(true, (repo, listOptions = {}) => list(`${repoPath(repo)}/tags`, listOptions, toTag)),
1573
+ resolveRef: verb(true, async (repo, ref, options) => {
1574
+ const { data } = await fetcher.json(`${repoPath(repo)}/commits`, {
1575
+ query: {
1576
+ sha: ref,
1577
+ limit: 1,
1578
+ stat: "false"
1579
+ },
1580
+ signal: options?.signal
1581
+ });
1582
+ const sha = data[0]?.sha;
1583
+ if (!sha) throw new NotFoundError(`${profile.forge} has no commit for ${ref}`, 404, "", context);
1584
+ return sha;
1585
+ }),
1586
+ commitsPage: verb(true, (repo, query = {}) => list(`${repoPath(repo)}/commits`, query, (raw) => toCommit(repo, raw), { query: {
1587
+ sha: query.ref,
1588
+ path: query.path,
1589
+ since: query.since?.toISOString(),
1590
+ until: query.until?.toISOString()
1591
+ } })),
1592
+ commit: verb(true, async (repo, sha, options) => toCommit(repo, (await fetcher.json(`${repoPath(repo)}/git/commits/${sha}`, {
1593
+ query: {
1594
+ stat: "true",
1595
+ files: "true"
1596
+ },
1597
+ signal: options?.signal
1598
+ })).data)),
1599
+ compare: verb(true, async (repo, base, head, options) => {
1600
+ const { data } = await fetcher.json(`${repoPath(repo)}/compare/${encodeURIComponent(base)}...${encodeURIComponent(head)}`, { signal: options?.signal });
1601
+ return {
1602
+ base,
1603
+ head,
1604
+ aheadBy: data.total_commits ?? data.commits?.length,
1605
+ commits: (data.commits ?? []).map((raw) => toCommit(repo, raw)),
1606
+ files: (data.files ?? []).map(toChangedFile),
1607
+ raw: data
1608
+ };
1609
+ })
1610
+ },
1611
+ releases: {
1612
+ listPage: verb(true, releasesPage),
1613
+ get: verb(true, async (ref, options) => toRelease(ref.repo, (await fetcher.json(`${repoPath(ref.repo)}/releases/${encodeURIComponent(ref.id)}`, { signal: options?.signal })).data)),
1614
+ getByTag: verb(true, async (repo, tag, options) => toRelease(repo, (await fetcher.json(`${repoPath(repo)}/releases/tags/${encodeURIComponent(tag)}`, { signal: options?.signal })).data)),
1615
+ downloadAsset: verb(true, async (ref, downloadOptions = {}) => {
1616
+ const { data } = await fetcher.json(`${repoPath(ref.repo)}/releases/${encodeURIComponent(ref.release.id)}/assets/${encodeURIComponent(ref.id)}`, { signal: downloadOptions.signal });
1617
+ return (await fetcher.stream(data.browser_download_url, { signal: downloadOptions.signal })).body;
1618
+ }),
1619
+ latest: verb(true, async (repo, options) => {
1620
+ try {
1621
+ return toRelease(repo, (await fetcher.json(`${repoPath(repo)}/releases/latest`, { signal: options?.signal })).data);
1622
+ } catch (error) {
1623
+ if (error instanceof NotFoundError) return;
1624
+ throw error;
1625
+ }
1626
+ }),
1627
+ create: verb(true, (repo, input, options) => writeRelease(repo, `${repoPath(repo)}/releases`, "POST", input, options)),
1628
+ update: verb(true, (ref, update, options) => writeRelease(ref.repo, `${repoPath(ref.repo)}/releases/${encodeURIComponent(ref.id)}`, "PATCH", update, options))
1629
+ },
1630
+ threads: {
1631
+ checks: perKind({ pull_request: true }, async (thread, options) => {
1632
+ const ref = requireThread(thread, context);
1633
+ const { data } = await fetcher.json(threadPath(ref), { signal: options?.signal });
1634
+ return { items: data.head?.sha ? await statusChecks(ref.repo, data.head.sha, options?.signal) : [] };
1635
+ }),
1636
+ reviewsPage: verb(true, async (thread, listOptions = {}) => {
1637
+ const ref = requirePull(thread, "reviewed");
1638
+ const result = await fetcher.page(`${pullPath(ref)}/reviews`, {
1639
+ query: { limit: listOptions.perPage },
1640
+ cursor: listOptions.cursor,
1641
+ signal: listOptions.signal
1642
+ });
1643
+ return {
1644
+ items: await Promise.all((result.data ?? []).map(async (raw) => {
1645
+ const comments = raw.comments_count === 0 ? [] : (await fetcher.json(`${pullPath(ref)}/reviews/${encodeURIComponent(raw.id)}/comments`, { signal: listOptions.signal })).data ?? [];
1646
+ return toReview(ref, raw, comments.map((comment) => toReviewComment(ref, comment)));
1647
+ })),
1648
+ cursor: result.cursor
1649
+ };
1650
+ }),
1651
+ filesPage: verb(true, (ref, listOptions = {}) => list(`${pullPath(requirePull(ref, "read for changed files"))}/files`, listOptions, toChangedFile)),
1652
+ commitsPage: verb(true, (ref, listOptions = {}) => list(`${pullPath(requirePull(ref, "read for commits"))}/commits`, listOptions, (raw) => toCommit(ref.repo, raw))),
1653
+ createReview: verb(true, createReview),
1654
+ submitReview: verb(true, async (ref, event, body, options) => {
1655
+ const pull = requirePull(ref.thread, "reviewed");
1656
+ const { data } = await fetcher.json(`${pullPath(pull)}/reviews/${encodeURIComponent(ref.id)}`, {
1657
+ method: "POST",
1658
+ json: {
1659
+ event: REVIEW_EVENTS[event],
1660
+ body: body ?? ""
1661
+ },
1662
+ signal: options?.signal
1663
+ });
1664
+ return toReview(pull, data, []);
1665
+ }),
1666
+ get: perKind({
1667
+ issue: true,
1668
+ pull_request: true,
1669
+ commit: true
1670
+ }, get),
1671
+ getMany: verb(true, (refs, options) => getManyConcurrently(refs, get, options)),
1672
+ listPage: perKind(ISSUE_AND_PULL, listPage),
1673
+ eventsPage: verb(true, async (thread, listOptions = {}) => {
1674
+ const ref = requireThread(thread, context);
1675
+ if (ref.kind === "commit") return { items: [] };
1676
+ return list(`${issuePath(ref)}/timeline`, {
1677
+ perPage: TIMELINE_PAGE_SIZE,
1678
+ ...listOptions
1679
+ }, (entry) => toEvent(ref, entry));
1680
+ }),
1681
+ commentsPage: perKind(ISSUE_AND_PULL, async (thread, listOptions = {}) => {
1682
+ const ref = requireIssueOrPull(thread, context, "list comments on");
1683
+ return list(`${issuePath(ref)}/comments`, listOptions, (raw) => toComment(ref, raw));
1684
+ }),
1685
+ comment: perKind({
1686
+ issue: true,
1687
+ pull_request: true
1688
+ }, async (thread, body, options) => {
1689
+ const ref = requireIssueOrPull(thread, context, "comment on");
1690
+ const { data } = await fetcher.json(`${issuePath(ref)}/comments`, {
1691
+ method: "POST",
1692
+ json: { body },
1693
+ signal: options?.signal
1694
+ });
1695
+ return toComment(ref, data);
1696
+ }),
1697
+ editComment: perKind({
1698
+ issue: true,
1699
+ pull_request: true
1700
+ }, async (ref, body, options) => {
1701
+ const { data } = await fetcher.json(`${repoPath(ref.thread.repo)}/issues/comments/${encodeURIComponent(ref.id)}`, {
1702
+ method: "PATCH",
1703
+ json: { body },
1704
+ signal: options?.signal
1705
+ });
1706
+ return toComment(ref.thread, data);
1707
+ }),
1708
+ deleteComment: perKind({
1709
+ issue: true,
1710
+ pull_request: true
1711
+ }, async (ref, options) => {
1712
+ await fetcher.raw(`${repoPath(ref.thread.repo)}/issues/comments/${encodeURIComponent(ref.id)}`, {
1713
+ method: "DELETE",
1714
+ signal: options?.signal
1715
+ });
1716
+ }),
1717
+ create: perKind({
1718
+ issue: true,
1719
+ pull_request: true
1720
+ }, async (repo, input, options) => {
1721
+ const labels = input.labels?.length ? await resolveLabels(repo, input.labels) : void 0;
1722
+ const assignees = input.assignees?.map(actorLogin);
1723
+ if (input.kind === "issue") {
1724
+ const { data } = await fetcher.json(`${repoPath(repo)}/issues`, {
1725
+ method: "POST",
1726
+ json: {
1727
+ title: input.title,
1728
+ body: input.body,
1729
+ labels,
1730
+ assignees
1731
+ },
1732
+ signal: options?.signal
1733
+ });
1734
+ return toThread({
1735
+ ...origin,
1736
+ repo,
1737
+ kind: "issue",
1738
+ number: String(data.number)
1739
+ }, data);
1740
+ }
1741
+ if (!input.head || !input.base) throw new UnsupportedOperationError("Creating a pull request needs head and base branches", context);
1742
+ const { data } = await fetcher.json(`${repoPath(repo)}/pulls`, {
1743
+ method: "POST",
1744
+ json: {
1745
+ title: input.draft ? `WIP: ${input.title}` : input.title,
1746
+ body: input.body,
1747
+ head: input.head,
1748
+ base: input.base,
1749
+ labels,
1750
+ assignees
1751
+ },
1752
+ signal: options?.signal
1753
+ });
1754
+ return toThread({
1755
+ ...origin,
1756
+ repo,
1757
+ kind: "pull_request",
1758
+ number: String(data.number)
1759
+ }, data);
1760
+ }),
1761
+ update: perKind({
1762
+ issue: true,
1763
+ pull_request: true
1764
+ }, async (thread, input, options) => {
1765
+ const ref = requireIssueOrPull(thread, context, "update");
1766
+ const { data } = await fetcher.json(ref.kind === "pull_request" ? threadPath(ref) : issuePath(ref), {
1767
+ method: "PATCH",
1768
+ json: input,
1769
+ signal: options?.signal
1770
+ });
1771
+ return toThread(ref, data);
1772
+ }),
1773
+ addLabels: perKind({
1774
+ issue: true,
1775
+ pull_request: true
1776
+ }, async (thread, labels, options) => {
1777
+ const ref = requireIssueOrPull(thread, context, "label");
1778
+ await fetcher.raw(`${issuePath(ref)}/labels`, {
1779
+ method: "POST",
1780
+ json: { labels: await resolveLabels(ref.repo, labels) },
1781
+ signal: options?.signal
1782
+ });
1783
+ }),
1784
+ removeLabels: perKind({
1785
+ issue: true,
1786
+ pull_request: true
1787
+ }, async (thread, labels, options) => {
1788
+ const ref = requireIssueOrPull(thread, context, "label");
1789
+ for (const id of await resolveLabels(ref.repo, labels)) await fetcher.raw(`${issuePath(ref)}/labels/${id}`, {
1790
+ method: "DELETE",
1791
+ signal: options?.signal
1792
+ });
1793
+ }),
1794
+ setMilestone: perKind({
1795
+ issue: true,
1796
+ pull_request: true
1797
+ }, async (thread, milestone, options) => {
1798
+ const ref = requireIssueOrPull(thread, context, "set the milestone of");
1799
+ await fetcher.raw(issuePath(ref), {
1800
+ method: "PATCH",
1801
+ json: { milestone: milestone === void 0 ? 0 : await milestoneId(milestone, (page) => milestonesPage(ref.repo, page), context, options) },
1802
+ signal: options?.signal
1803
+ });
1804
+ }),
1805
+ reactionsPage: perKind(ISSUE_AND_PULL, async (target, listOptions = {}) => {
1806
+ const result = await fetcher.page(reactionPath(target), {
1807
+ query: { limit: listOptions.perPage },
1808
+ cursor: listOptions.cursor,
1809
+ signal: listOptions.signal
1810
+ });
1811
+ return toPage(result, (raw) => {
1812
+ const reactor = toActor(context, raw.user);
1813
+ return reactor && {
1814
+ content: reactionContent(raw.content),
1815
+ contentRaw: raw.content,
1816
+ actor: reactor,
1817
+ createdAt: toDate(raw.created_at),
1818
+ raw
1819
+ };
1820
+ });
1821
+ }),
1822
+ reactions: perKind({
1823
+ issue: true,
1824
+ pull_request: true
1825
+ }, {
1826
+ react: (target, reaction, options) => setReaction(target, reaction, "POST", options),
1827
+ unreact: (target, reaction, options) => setReaction(target, reaction, "DELETE", options)
1828
+ }),
1829
+ setLabels: perKind({
1830
+ issue: true,
1831
+ pull_request: true
1832
+ }, async (thread, labels, options) => {
1833
+ const ref = requireIssueOrPull(thread, context, "label");
1834
+ await fetcher.raw(`${issuePath(ref)}/labels`, {
1835
+ method: "PUT",
1836
+ json: { labels: await resolveLabels(ref.repo, labels) },
1837
+ signal: options?.signal
1838
+ });
1839
+ }),
1840
+ setAssignees: perKind({
1841
+ issue: true,
1842
+ pull_request: true
1843
+ }, async (thread, assignees, options) => {
1844
+ const ref = requireIssueOrPull(thread, context, "assign");
1845
+ await fetcher.raw(issuePath(ref), {
1846
+ method: "PATCH",
1847
+ json: { assignees: assignees.map(actorLogin) },
1848
+ signal: options?.signal
1849
+ });
1850
+ }),
1851
+ requestReview: perKind({ pull_request: true }, async (thread, reviewers, options) => {
1852
+ await fetcher.raw(`${threadPath(requireThread(thread, context))}/requested_reviewers`, {
1853
+ method: "POST",
1854
+ json: { reviewers: reviewers.map(actorLogin) },
1855
+ signal: options?.signal
1856
+ });
1857
+ }),
1858
+ close: perKind({
1859
+ issue: true,
1860
+ pull_request: true
1861
+ }, (ref, options) => setState(ref, "closed", options)),
1862
+ reopen: perKind({
1863
+ issue: true,
1864
+ pull_request: true
1865
+ }, (ref, options) => setState(ref, "open", options)),
1866
+ merge: verb(true, merge),
1867
+ subscriptions: perKind(ISSUE_AND_PULL, {
1868
+ async subscription(thread, options) {
1869
+ const ref = requireIssueOrPull(thread, context, "read the subscription of");
1870
+ const { data } = await fetcher.json(`${issuePath(ref)}/subscriptions/check`, { signal: options?.signal });
1871
+ return data.ignored ? "ignored" : data.subscribed ? "subscribed" : "none";
1872
+ },
1873
+ async subscribe(thread, options) {
1874
+ const ref = requireIssueOrPull(thread, context, "subscribe to");
1875
+ await fetcher.raw(await subscriptionPath({
1876
+ owner: ref.repo.owner,
1877
+ name: ref.repo.name,
1878
+ number: ref.number
1879
+ }), {
1880
+ method: "PUT",
1881
+ signal: options?.signal
1882
+ });
1883
+ },
1884
+ async unsubscribe(thread, options) {
1885
+ const ref = requireIssueOrPull(thread, context, "unsubscribe from");
1886
+ await fetcher.raw(await subscriptionPath({
1887
+ owner: ref.repo.owner,
1888
+ name: ref.repo.name,
1889
+ number: ref.number
1890
+ }), {
1891
+ method: "DELETE",
1892
+ signal: options?.signal
1893
+ });
1894
+ }
1895
+ })
1896
+ },
1897
+ web: githubShapedWeb(baseUrl.replace(/\/api\/v1$/, ""), {
1898
+ pull: "pulls",
1899
+ commentFragment: "issuecomment-",
1900
+ file: (at) => /^[0-9a-f]{40}$/.test(at) ? `/src/commit/${at}` : `/src/branch/${encodeURIComponent(at)}`,
1901
+ lineFragment: (line) => `L${line}`,
1902
+ reserved: FORGEJO_RESERVED_PATHS
1903
+ }),
1904
+ webhooks: {
1905
+ listPage: verb(true, (target, listOptions = {}) => list(hooksPath(target), listOptions, (raw) => toWebhook(target, raw))),
1906
+ create: verb(true, async (target, input, options) => toWebhook(target, (await fetcher.json(hooksPath(target), {
1907
+ method: "POST",
1908
+ json: {
1909
+ type: "gitea",
1910
+ active: input.active ?? true,
1911
+ events: nativeEventsFor(FORGEJO_NATIVE_EVENTS, input.events, input.nativeEvents),
1912
+ config: {
1913
+ url: input.url,
1914
+ content_type: input.contentType ?? "json",
1915
+ ...input.secret ? { secret: input.secret } : {}
1916
+ }
1917
+ },
1918
+ signal: options?.signal
1919
+ })).data)),
1920
+ update: verb(true, async (ref, update, options) => toWebhook(ref.target, (await fetcher.json(`${hooksPath(ref.target)}/${encodeURIComponent(ref.id)}`, {
1921
+ method: "PATCH",
1922
+ json: {
1923
+ ...update.active === void 0 ? {} : { active: update.active },
1924
+ ...update.events || update.nativeEvents ? { events: nativeEventsFor(FORGEJO_NATIVE_EVENTS, update.events, update.nativeEvents) } : {},
1925
+ ...update.url || update.secret || update.contentType ? { config: {
1926
+ ...update.url ? { url: update.url } : {},
1927
+ content_type: update.contentType ?? "json",
1928
+ ...update.secret ? { secret: update.secret } : {}
1929
+ } } : {}
1930
+ },
1931
+ signal: options?.signal
1932
+ })).data)),
1933
+ delete: verb(true, async (ref, options) => {
1934
+ await fetcher.raw(`${hooksPath(ref.target)}/${encodeURIComponent(ref.id)}`, {
1935
+ method: "DELETE",
1936
+ signal: options?.signal
1937
+ });
1938
+ }),
1939
+ rotateSecret: verb(true, async (ref, secret, options) => {
1940
+ const current = toWebhook(ref.target, (await fetcher.json(`${hooksPath(ref.target)}/${encodeURIComponent(ref.id)}`, { signal: options?.signal })).data);
1941
+ const { data } = await fetcher.json(`${hooksPath(ref.target)}/${encodeURIComponent(ref.id)}`, {
1942
+ method: "PATCH",
1943
+ json: { config: {
1944
+ url: current.url,
1945
+ content_type: current.contentType ?? "json",
1946
+ secret
1947
+ } },
1948
+ signal: options?.signal
1949
+ });
1950
+ return toWebhook(ref.target, data);
1951
+ })
1952
+ },
1953
+ scopes: forgejoScopesFor
1954
+ };
1955
+ }
1956
+ const FORGEJO = /* @__PURE__ */ forgejoDefinition(FORGEJO_PROFILE);
1957
+ /** Creates a Forgejo provider for Codeberg or any self-hosted Forgejo instance. */
1958
+ const forgejo = /* @__PURE__ */ defineForgeProvider({
1959
+ ...FORGEJO,
1960
+ webhooks: forgejoWebhooks(FORGEJO_PROFILE)
1961
+ });
1962
+ /** `forgejo()` without webhook ingestion, for bundles that never receive a delivery. */
1963
+ const forgejoLite = /* @__PURE__ */ defineForgeProvider(FORGEJO);
1964
+ /**
1965
+ * Forgejo and Gitea token scopes, which are per resource group with a
1966
+ * `read:` or `write:` prefix.
1967
+ */
1968
+ function forgejoScopesFor(verb) {
1969
+ const [group = "", name = ""] = verb.split(".");
1970
+ const write = (/* @__PURE__ */ new Set([
1971
+ "comment",
1972
+ "upsertComment",
1973
+ "editComment",
1974
+ "deleteComment",
1975
+ "create",
1976
+ "update",
1977
+ "close",
1978
+ "reopen",
1979
+ "setLabels",
1980
+ "addLabels",
1981
+ "removeLabels",
1982
+ "setMilestone",
1983
+ "react",
1984
+ "unreact",
1985
+ "setAssignees",
1986
+ "requestReview",
1987
+ "merge",
1988
+ "approveAndMerge",
1989
+ "createReview",
1990
+ "submitReview",
1991
+ "approve",
1992
+ "createLabel",
1993
+ "addCollaborator",
1994
+ "subscribe",
1995
+ "unsubscribe",
1996
+ "report"
1997
+ ])).has(name);
1998
+ if (group === "webhooks") return name === "verify" || name === "ingest" ? {} : { token: ["write:repository", "write:organization"] };
1999
+ if (group === "notifications") return { token: [name === "list" || name === "page" || name === "unreadCount" ? "read:notification" : "write:notification"] };
2000
+ if (group === "users" || group === "search") return { token: ["read:user"] };
2001
+ if (group === "threads" || group === "repos" || group === "contents" || group === "releases" || group === "checks" || group === "ci") return { token: [write ? "write:issue" : "read:issue", write ? "write:repository" : "read:repository"] };
2002
+ return {};
2003
+ }
2004
+ //#endregion
2005
+ export { forgejoScopesFor as a, forgejoLite as i, forgejo as n, forgejoWebhooks as o, forgejoDefinition as r, GITEA_HEADERS as s, FORGEJO_PROFILE as t };