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,1272 @@
1
+ import { C as requireIssueOrPull, Ct as MergeMethodRequiredError, E as summariseChecks, O as toDate, Pt as toMergeError, S as phased, T as resolveToken, _t as ForgeError, c as checkRunState, dt as fromBase64, f as getManyConcurrently, ft as toFileContent, g as iteratePages, i as githubShapedWeb, j as toWarning, jt as UnsupportedOperationError, k as toFileStatus, m as hexColour, n as perKind, p as hasEveryLabel, r as verb, s as actorLogin, t as defineForgeProvider, w as requireThread, wt as NotFoundError } from "./define-D3yEXtKK.mjs";
2
+ import { c as timingSafeEqual, n as headerValue, r as hmacSha256Base64, t as bodyText } from "./crypto-CQNEyYHD.mjs";
3
+ import { i as refEvent, o as verifySharedToken, r as nativeEventsFor, t as eventKindsOf } from "./webhooks-rn9-8lxT.mjs";
4
+ //#region src/gitee/webhook-events.ts
5
+ /** Gitee subscribes per event with a boolean flag, so the flag names are the native events. */
6
+ const GITEE_NATIVE_EVENTS = {
7
+ comment: ["note_events"],
8
+ state_change: ["issues_events", "merge_requests_events"],
9
+ label: ["issues_events", "merge_requests_events"],
10
+ assignment: ["issues_events", "merge_requests_events"],
11
+ review: ["merge_requests_events"],
12
+ push: ["push_events"],
13
+ ref: ["tag_push_events"]
14
+ };
15
+ /** Normalised kinds and actions Gitee deliveries translate into. */
16
+ const GITEE_WEBHOOK_EVENTS = [
17
+ {
18
+ kind: "comment",
19
+ action: "created"
20
+ },
21
+ {
22
+ kind: "state_change",
23
+ action: "opened"
24
+ },
25
+ {
26
+ kind: "state_change",
27
+ action: "closed"
28
+ },
29
+ {
30
+ kind: "state_change",
31
+ action: "reopened"
32
+ },
33
+ {
34
+ kind: "state_change",
35
+ action: "merged"
36
+ },
37
+ { kind: "push" },
38
+ {
39
+ kind: "ref",
40
+ action: "created"
41
+ },
42
+ {
43
+ kind: "ref",
44
+ action: "deleted"
45
+ }
46
+ ];
47
+ //#endregion
48
+ //#region src/gitee/normalise.ts
49
+ const FORGE = "gitee";
50
+ function toActor(instance, user) {
51
+ if (!user) return;
52
+ return {
53
+ forge: FORGE,
54
+ instance,
55
+ login: user.login,
56
+ id: String(user.id),
57
+ name: user.name,
58
+ avatarUrl: user.avatar_url,
59
+ url: user.html_url,
60
+ typeRaw: user.type,
61
+ isBotHint: user.type === "Bot" || /\[bot\]$|-bot$/i.test(user.login)
62
+ };
63
+ }
64
+ function toRepoRef(instance, repo) {
65
+ const [owner = repo.namespace?.path ?? "", name = repo.path] = repo.full_name.split("/");
66
+ return {
67
+ forge: FORGE,
68
+ instance,
69
+ owner,
70
+ name,
71
+ externalId: String(repo.id)
72
+ };
73
+ }
74
+ function toRepo(instance, raw) {
75
+ return {
76
+ ref: toRepoRef(instance, raw),
77
+ description: raw.description ?? void 0,
78
+ defaultBranch: raw.default_branch,
79
+ visibility: raw.private ? "private" : raw.internal ? "internal" : "public",
80
+ isFork: raw.fork ?? false,
81
+ isArchived: false,
82
+ parent: raw.parent ? toRepoRef(instance, raw.parent) : void 0,
83
+ topics: [],
84
+ url: raw.html_url,
85
+ cloneUrls: {
86
+ https: raw.html_url ? `${raw.html_url}.git` : void 0,
87
+ ssh: raw.ssh_url
88
+ },
89
+ createdAt: toDate(raw.created_at),
90
+ updatedAt: toDate(raw.updated_at),
91
+ pushedAt: toDate(raw.pushed_at),
92
+ openIssueCount: raw.open_issues_count,
93
+ owner: toActor(instance, raw.owner),
94
+ language: raw.language ?? void 0,
95
+ homepage: raw.homepage || void 0,
96
+ licence: raw.license ?? void 0,
97
+ stars: raw.stargazers_count,
98
+ forks: raw.forks_count,
99
+ watchers: raw.watchers_count,
100
+ raw
101
+ };
102
+ }
103
+ /** Gitee issues are `open`, `progressing`, `closed` or `rejected`; progressing work is still open. */
104
+ function issueState(state) {
105
+ return state === "open" || state === "progressing" ? "open" : state === "closed" || state === "rejected" ? "closed" : "unknown";
106
+ }
107
+ function toIssueThread(ref, raw) {
108
+ const instance = ref.instance;
109
+ return {
110
+ ref: {
111
+ ...ref,
112
+ kind: "issue",
113
+ number: raw.number,
114
+ externalId: String(raw.id)
115
+ },
116
+ kind: "issue",
117
+ title: raw.title,
118
+ body: raw.body ?? void 0,
119
+ state: issueState(raw.state),
120
+ stateRaw: raw.state,
121
+ stateReason: raw.state === "rejected" ? "not_planned" : void 0,
122
+ isDraft: false,
123
+ author: toActor(instance, raw.user),
124
+ assignees: raw.assignee ? [toActor(instance, raw.assignee)] : [],
125
+ reviewers: [],
126
+ labels: (raw.labels ?? []).map((label) => ({
127
+ name: label.name,
128
+ colour: label.color
129
+ })),
130
+ url: raw.html_url,
131
+ createdAt: toDate(raw.created_at),
132
+ updatedAt: toDate(raw.updated_at),
133
+ closedAt: toDate(raw.finished_at),
134
+ lastActivityAt: toDate(raw.updated_at),
135
+ commentCount: raw.comments,
136
+ raw
137
+ };
138
+ }
139
+ function toPullThread(ref, raw) {
140
+ const instance = ref.instance;
141
+ const state = raw.merged_at || raw.state === "merged" ? "merged" : raw.state === "open" ? "open" : raw.state === "closed" ? "closed" : "unknown";
142
+ return {
143
+ ref: {
144
+ ...ref,
145
+ kind: "pull_request",
146
+ number: String(raw.number),
147
+ externalId: String(raw.id)
148
+ },
149
+ kind: "pull_request",
150
+ title: raw.title,
151
+ body: raw.body ?? void 0,
152
+ state,
153
+ stateRaw: raw.state,
154
+ isDraft: raw.draft ?? false,
155
+ author: toActor(instance, raw.user),
156
+ assignees: [],
157
+ reviewers: (raw.assignees ?? []).map((reviewer) => ({
158
+ actor: toActor(instance, reviewer),
159
+ state: reviewer.accept ? "approved" : "pending",
160
+ stateRaw: reviewer.accept ? "accepted" : "requested"
161
+ })),
162
+ labels: (raw.labels ?? []).map((label) => ({
163
+ name: label.name,
164
+ colour: label.color
165
+ })),
166
+ url: raw.html_url,
167
+ createdAt: toDate(raw.created_at),
168
+ updatedAt: toDate(raw.updated_at),
169
+ closedAt: toDate(raw.closed_at),
170
+ lastActivityAt: toDate(raw.updated_at),
171
+ locked: raw.locked,
172
+ branches: raw.head && raw.base ? {
173
+ head: {
174
+ ref: raw.head.ref,
175
+ sha: raw.head.sha,
176
+ repo: raw.head.repo ? toRepoRef(instance, raw.head.repo) : void 0
177
+ },
178
+ base: {
179
+ ref: raw.base.ref,
180
+ sha: raw.base.sha
181
+ }
182
+ } : void 0,
183
+ raw
184
+ };
185
+ }
186
+ /** Inline review comments are `diff_comment`s; everything else is conversation. */
187
+ function isConversationComment(raw) {
188
+ return raw.comment_type !== "diff_comment" && !raw.path;
189
+ }
190
+ function toComment(thread, raw) {
191
+ return {
192
+ ref: {
193
+ forge: FORGE,
194
+ instance: thread.instance,
195
+ thread,
196
+ id: String(raw.id)
197
+ },
198
+ body: raw.body,
199
+ author: toActor(thread.instance, raw.user),
200
+ createdAt: toDate(raw.created_at),
201
+ updatedAt: toDate(raw.updated_at),
202
+ url: raw.html_url,
203
+ raw
204
+ };
205
+ }
206
+ function toCommentEvent(thread, raw) {
207
+ const actor = toActor(thread.instance, raw.user);
208
+ const inline = !isConversationComment(raw);
209
+ return {
210
+ forge: FORGE,
211
+ instance: thread.instance,
212
+ id: `comment:${raw.id}`,
213
+ kind: inline ? "review_comment" : "comment",
214
+ kindRaw: raw.comment_type ?? "comment",
215
+ summary: `${actor?.login ?? "someone"} ${inline ? "commented on the diff" : "commented"}`,
216
+ occurredAt: toDate(raw.created_at) ?? /* @__PURE__ */ new Date(0),
217
+ actor,
218
+ repo: thread.repo,
219
+ thread,
220
+ source: "poll",
221
+ payload: raw
222
+ };
223
+ }
224
+ const LOG_KINDS = {
225
+ label: "label",
226
+ add_label: "label",
227
+ remove_label: "label",
228
+ assign: "assignment",
229
+ set_assignee: "assignment",
230
+ change_issue_state: "state_change",
231
+ closed: "state_change",
232
+ reopened: "state_change",
233
+ merged: "state_change",
234
+ review: "review",
235
+ approve: "review"
236
+ };
237
+ function toLogEvent(thread, raw) {
238
+ const actor = toActor(thread.instance, raw.user);
239
+ return {
240
+ forge: FORGE,
241
+ instance: thread.instance,
242
+ id: `log:${raw.id}`,
243
+ kind: LOG_KINDS[raw.action_type ?? ""] ?? "other",
244
+ kindRaw: raw.action_type,
245
+ summary: `${actor?.login ?? "someone"} ${raw.content ?? raw.action_type ?? "changed the thread"}`.trim(),
246
+ occurredAt: toDate(raw.created_at) ?? /* @__PURE__ */ new Date(0),
247
+ actor,
248
+ repo: thread.repo,
249
+ thread,
250
+ source: "poll",
251
+ payload: raw
252
+ };
253
+ }
254
+ function threadFromUrl(instance, repo, url) {
255
+ const match = /\/(issues|pulls)\/([^/?#]+)/.exec(url ?? "");
256
+ return repo && match ? {
257
+ forge: FORGE,
258
+ instance,
259
+ repo,
260
+ kind: match[1] === "pulls" ? "pull_request" : "issue",
261
+ number: match[2]
262
+ } : void 0;
263
+ }
264
+ function toNotification(instance, raw) {
265
+ const repo = raw.repository ? toRepoRef(instance, raw.repository) : void 0;
266
+ const type = raw.subject?.type ?? raw.type ?? "unknown";
267
+ const thread = threadFromUrl(instance, repo, raw.subject?.url ?? raw.html_url);
268
+ return {
269
+ ref: {
270
+ forge: FORGE,
271
+ instance,
272
+ id: String(raw.id)
273
+ },
274
+ subject: thread ? {
275
+ type: "thread",
276
+ thread: {
277
+ ...thread,
278
+ typeRaw: type
279
+ }
280
+ } : repo ? {
281
+ type: "repo",
282
+ repo
283
+ } : {
284
+ type: "other",
285
+ typeRaw: type,
286
+ id: String(raw.id),
287
+ url: raw.html_url
288
+ },
289
+ subjectTypeRaw: type,
290
+ reason: raw.type === "referer" ? "mention" : "unknown",
291
+ reasonRaw: raw.type,
292
+ unread: raw.unread,
293
+ title: raw.subject?.title ?? raw.content ?? type,
294
+ subjectState: "unknown",
295
+ updatedAt: toDate(raw.updated_at) ?? /* @__PURE__ */ new Date(0),
296
+ raw
297
+ };
298
+ }
299
+ function toCheck(repo, raw) {
300
+ return {
301
+ ref: {
302
+ forge: FORGE,
303
+ instance: repo.instance,
304
+ repo,
305
+ id: String(raw.id),
306
+ type: "check_run"
307
+ },
308
+ name: raw.name,
309
+ state: checkRunState(raw.status, raw.conclusion),
310
+ stateRaw: raw.status,
311
+ conclusionRaw: raw.conclusion ?? void 0,
312
+ url: raw.html_url ?? raw.details_url,
313
+ startedAt: toDate(raw.started_at),
314
+ completedAt: toDate(raw.completed_at),
315
+ raw
316
+ };
317
+ }
318
+ /** Gitee releases have no draft state or separate publish time. */
319
+ function toRelease(repo, raw) {
320
+ return {
321
+ ref: {
322
+ forge: FORGE,
323
+ instance: repo.instance,
324
+ repo,
325
+ id: String(raw.id),
326
+ tag: raw.tag_name
327
+ },
328
+ name: raw.name || void 0,
329
+ tag: raw.tag_name,
330
+ body: raw.body || void 0,
331
+ isDraft: false,
332
+ isPrerelease: raw.prerelease,
333
+ author: toActor(repo.instance, raw.author),
334
+ publishedAt: toDate(raw.created_at),
335
+ createdAt: toDate(raw.created_at),
336
+ assets: raw.assets?.map((asset) => ({
337
+ name: asset.name ?? asset.browser_download_url.split("/").pop() ?? "",
338
+ url: asset.browser_download_url
339
+ })),
340
+ raw
341
+ };
342
+ }
343
+ function toChangedFile(raw) {
344
+ return {
345
+ path: raw.filename,
346
+ status: toFileStatus(raw.status),
347
+ statusRaw: raw.status,
348
+ additions: raw.additions,
349
+ deletions: raw.deletions,
350
+ patch: typeof raw.patch === "string" ? raw.patch : raw.patch?.diff,
351
+ sha: raw.sha
352
+ };
353
+ }
354
+ function parentShas(parents) {
355
+ if (Array.isArray(parents)) return parents.flatMap((parent) => typeof parent === "string" ? [parent] : parent.sha ? [parent.sha] : []);
356
+ return parents?.shas ?? (parents?.sha ? [parents.sha] : []);
357
+ }
358
+ function toCommit(repo, raw) {
359
+ return {
360
+ ref: {
361
+ forge: FORGE,
362
+ instance: repo.instance,
363
+ repo,
364
+ sha: raw.sha
365
+ },
366
+ sha: raw.sha,
367
+ message: raw.commit?.message ?? "",
368
+ author: {
369
+ actor: toActor(repo.instance, raw.author),
370
+ name: raw.commit?.author?.name,
371
+ email: raw.commit?.author?.email,
372
+ date: toDate(raw.commit?.author?.date)
373
+ },
374
+ committer: {
375
+ actor: toActor(repo.instance, raw.committer),
376
+ name: raw.commit?.committer?.name,
377
+ email: raw.commit?.committer?.email,
378
+ date: toDate(raw.commit?.committer?.date)
379
+ },
380
+ parents: parentShas(raw.parents),
381
+ url: raw.html_url,
382
+ ...raw.stats ? { stats: {
383
+ additions: raw.stats.additions ?? 0,
384
+ deletions: raw.stats.deletions ?? 0,
385
+ total: raw.stats.total
386
+ } } : {},
387
+ ...raw.files ? { files: raw.files.map(toChangedFile) } : {},
388
+ raw
389
+ };
390
+ }
391
+ function toBranch(raw) {
392
+ return {
393
+ name: raw.name,
394
+ sha: raw.commit?.sha ?? "",
395
+ isProtected: raw.protected,
396
+ raw
397
+ };
398
+ }
399
+ function toTag(raw) {
400
+ return {
401
+ name: raw.name,
402
+ sha: raw.commit?.sha ?? "",
403
+ raw
404
+ };
405
+ }
406
+ function toTreeEntry(raw) {
407
+ return {
408
+ path: raw.path,
409
+ type: raw.mode === "120000" ? "symlink" : raw.type === "tree" ? "directory" : raw.type === "commit" ? "submodule" : "file",
410
+ sha: raw.sha,
411
+ size: raw.size,
412
+ mode: raw.mode
413
+ };
414
+ }
415
+ function toWebhook(target, raw) {
416
+ const nativeEvents = Object.entries(raw).filter(([key, value]) => key.endsWith("_events") && value === true).map(([key]) => key);
417
+ return {
418
+ ref: {
419
+ forge: FORGE,
420
+ instance: target.instance,
421
+ target,
422
+ id: String(raw.id)
423
+ },
424
+ url: raw.url,
425
+ events: eventKindsOf(GITEE_NATIVE_EVENTS, nativeEvents),
426
+ nativeEvents,
427
+ active: true,
428
+ createdAt: toDate(raw.created_at),
429
+ raw
430
+ };
431
+ }
432
+ //#endregion
433
+ //#region src/gitee/webhooks.ts
434
+ /** Gitee rejects signatures more than an hour from the request time. */
435
+ const TOLERANCE_MS = 36e5;
436
+ /**
437
+ * Gitee sends either the hook password, or a signature, in `X-Gitee-Token`.
438
+ * The signature is the URL-encoded base64 HMAC-SHA256 of
439
+ * `<X-Gitee-Timestamp>\n<secret>` under the secret, with the timestamp in
440
+ * milliseconds; it signs the timestamp, not the body.
441
+ */
442
+ async function verifyGiteeToken(delivery, secret, now = Date.now()) {
443
+ const token = headerValue(delivery.headers, "x-gitee-token");
444
+ const key = delivery.secret ?? secret;
445
+ if (!key || !token) return false;
446
+ if (verifySharedToken(delivery, secret, "x-gitee-token")) return true;
447
+ const timestamp = headerValue(delivery.headers, "x-gitee-timestamp");
448
+ if (!timestamp || !(Math.abs(now - Number(timestamp)) <= TOLERANCE_MS)) return false;
449
+ const expected = await hmacSha256Base64(key, `${timestamp}\n${key}`);
450
+ let received = token;
451
+ try {
452
+ received = decodeURIComponent(token);
453
+ } catch {}
454
+ return timingSafeEqual(received, expected);
455
+ }
456
+ const KINDS = {
457
+ open: "state_change",
458
+ close: "state_change",
459
+ reopen: "state_change",
460
+ merge: "state_change",
461
+ state_change: "state_change",
462
+ approved: "review",
463
+ tested: "review",
464
+ assign: "assignment",
465
+ label: "label"
466
+ };
467
+ function translateGiteeWebhook(instance, delivery) {
468
+ const payload = JSON.parse(bodyText(delivery.body));
469
+ const hook = payload.hook_name ?? headerValue(delivery.headers, "x-gitee-event") ?? "";
470
+ const rawRepo = payload.repository ?? payload.project;
471
+ const repo = rawRepo ? toRepoRef(instance, rawRepo) : void 0;
472
+ const actor = toActor(instance, payload.sender ?? payload.user ?? payload.comment?.user);
473
+ const who = actor?.login ?? "someone";
474
+ const base = {
475
+ forge: FORGE,
476
+ instance,
477
+ id: headerValue(delivery.headers, "x-gitee-delivery") ?? `${hook}:${payload.timestamp ?? Date.now()}`,
478
+ kindRaw: payload.action ? `${hook}.${payload.action}` : hook,
479
+ occurredAt: toDate(payload.comment?.created_at) ?? (payload.timestamp ? new Date(Number(payload.timestamp)) : /* @__PURE__ */ new Date()),
480
+ actor,
481
+ repo,
482
+ source: "webhook",
483
+ payload
484
+ };
485
+ if (hook === "push_hooks" || hook === "tag_push_hooks" || hook === "Push Hook" || hook === "Tag Push Hook") return [{
486
+ ...base,
487
+ ...refEvent(who, {
488
+ ref: payload.ref ?? "",
489
+ before: payload.before,
490
+ after: payload.after,
491
+ created: payload.created,
492
+ deleted: payload.deleted,
493
+ commits: (payload.commits ?? []).map((commit) => ({
494
+ sha: commit.id,
495
+ message: commit.message,
496
+ author: commit.author?.username ?? commit.author?.name,
497
+ url: commit.url
498
+ }))
499
+ })
500
+ }];
501
+ const thread = repo && payload.pull_request ? {
502
+ forge: FORGE,
503
+ instance,
504
+ repo,
505
+ kind: "pull_request",
506
+ number: String(payload.pull_request.number)
507
+ } : repo && payload.issue ? {
508
+ forge: FORGE,
509
+ instance,
510
+ repo,
511
+ kind: "issue",
512
+ number: payload.issue.number
513
+ } : void 0;
514
+ if (hook === "note_hooks" || hook === "Note Hook") return [{
515
+ ...base,
516
+ kind: "comment",
517
+ thread,
518
+ summary: `${who} commented${thread ? ` on ${thread.kind === "issue" ? "" : "!"}${thread.number}` : ""}`
519
+ }];
520
+ return [{
521
+ ...base,
522
+ kind: KINDS[payload.action ?? ""] ?? "other",
523
+ thread,
524
+ summary: `${who} ${payload.action ?? "changed"} ${thread ? `${thread.kind === "issue" ? "" : "!"}${thread.number}` : hook}`
525
+ }];
526
+ }
527
+ const giteeWebhooks = ({ options, instance }) => ({
528
+ events: GITEE_WEBHOOK_EVENTS,
529
+ verify: (delivery) => verifyGiteeToken(delivery, options.webhookSecret),
530
+ translate: (delivery) => translateGiteeWebhook(instance, delivery)
531
+ });
532
+ //#endregion
533
+ //#region src/gitee/index.ts
534
+ const PULL = { pull_request: true };
535
+ const PER_PAGE = 50;
536
+ function setupGitee({ instance, origin: context, fetcher, baseUrl }) {
537
+ const repoPath = (repo) => `/repos/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.name)}`;
538
+ async function readUser(path, signal) {
539
+ const { data } = await fetcher.json(path, { signal });
540
+ return {
541
+ ...toActor(instance, data),
542
+ bio: data.bio ?? void 0,
543
+ company: data.company ?? void 0,
544
+ websiteUrl: data.blog || void 0,
545
+ createdAt: toDate(data.created_at),
546
+ followers: data.followers,
547
+ following: data.following,
548
+ publicRepos: data.public_repos,
549
+ raw: data
550
+ };
551
+ }
552
+ function requirePull(thread, action) {
553
+ const ref = requireThread(thread, context);
554
+ if (ref.kind !== "pull_request") throw new UnsupportedOperationError(`Only pull requests can be ${action}`, context);
555
+ return ref;
556
+ }
557
+ const threadPath = (ref) => `${repoPath(ref.repo)}/${ref.kind === "pull_request" ? "pulls" : "issues"}/${encodeURIComponent(ref.number)}`;
558
+ /**
559
+ * Gitee pages by number and reports the page count in `total_page`. The
560
+ * cursor carries the next page's URL, with the same filters.
561
+ */
562
+ async function numberedPage(path, query, listOptions) {
563
+ const perPage = Math.min(listOptions.perPage ?? PER_PAGE, 100);
564
+ const url = listOptions.cursor?.nextUrl ?? fetcher.resolve(path, {
565
+ ...query,
566
+ page: 1,
567
+ per_page: perPage
568
+ });
569
+ const page = Number(new URL(url).searchParams.get("page") ?? 1);
570
+ const { data, response } = await fetcher.json(url, { signal: listOptions.signal });
571
+ const items = data ?? [];
572
+ const total = Number(response.headers.get("total_page"));
573
+ return {
574
+ items,
575
+ cursor: (Number.isFinite(total) && total > 0 ? page < total : items.length === perPage) ? { nextUrl: nextPage(url, page) } : void 0
576
+ };
577
+ }
578
+ function nextPage(url, page) {
579
+ const next = new URL(url);
580
+ next.searchParams.set("page", String(page + 1));
581
+ return next.toString();
582
+ }
583
+ function all(path, options = {}) {
584
+ return Array.fromAsync(iteratePages((page) => numberedPage(path, {}, page), { signal: options.signal }));
585
+ }
586
+ /** Gitee records an approval, with no review object to read back. */
587
+ async function createReview(thread, input, options) {
588
+ const ref = requireThread(thread, context);
589
+ if (ref.kind !== "pull_request") throw new UnsupportedOperationError("Only pull requests can be reviewed", context);
590
+ if (input.event !== "approve") throw new UnsupportedOperationError("Gitee records approvals only; post anything else as a comment", context);
591
+ if (input.comments?.length) throw new UnsupportedOperationError("Gitee approvals carry no inline comments", context);
592
+ const { data } = await fetcher.json(`${threadPath(ref)}/review`, {
593
+ method: "POST",
594
+ json: input.body ? { body: input.body } : {},
595
+ signal: options?.signal
596
+ });
597
+ return {
598
+ ref: {
599
+ forge: FORGE,
600
+ instance,
601
+ thread: ref,
602
+ id: String(data?.id ?? "approval")
603
+ },
604
+ state: "approved",
605
+ stateRaw: "approved",
606
+ body: input.body,
607
+ comments: false,
608
+ raw: data
609
+ };
610
+ }
611
+ async function headChecks(repo, sha, signal) {
612
+ const { data } = await fetcher.json(`${repoPath(repo)}/commits/${sha}/check-runs`, { signal });
613
+ return (Array.isArray(data) ? data : data.check_runs ?? []).map((raw) => toCheck(repo, raw));
614
+ }
615
+ async function get(thread, options) {
616
+ const ref = requireIssueOrPull(thread, context, "read");
617
+ if (ref.kind === "issue") return toIssueThread(ref, (await fetcher.json(threadPath(ref), { signal: options?.signal })).data);
618
+ const { data } = await fetcher.json(threadPath(ref), { signal: options?.signal });
619
+ const result = toPullThread(ref, data);
620
+ if (data.head?.sha) try {
621
+ result.checks = summariseChecks((await headChecks(ref.repo, data.head.sha, options?.signal)).map((check) => check.state));
622
+ } catch (error) {
623
+ if (!(error instanceof ForgeError)) throw error;
624
+ result.warnings = [...result.warnings ?? [], toWarning("checks_unreadable", error, ref.number)];
625
+ }
626
+ return result;
627
+ }
628
+ async function listPage(repo, query = {}) {
629
+ if (query.kind === "discussion") return {
630
+ items: [],
631
+ warnings: [{
632
+ code: "kind_unsupported",
633
+ message: "Gitee has no discussions"
634
+ }]
635
+ };
636
+ const warnings = [];
637
+ if (!query.cursor) {
638
+ if (query.sort === "comments") warnings.push({
639
+ code: "sort_unsupported",
640
+ message: "Gitee cannot sort by comment count; sorted by creation time"
641
+ });
642
+ if (query.assignee && query.kind !== "issue") warnings.push({
643
+ code: "filter_unsupported",
644
+ message: "Gitee pull requests have reviewers, not assignees; the assignee filter applies to issues only"
645
+ });
646
+ }
647
+ const state = query.state ?? "open";
648
+ const createdAfter = query.createdAfter?.getTime();
649
+ const phases = [];
650
+ for (const kind of query.kind ? [query.kind] : ["issue", "pull_request"]) {
651
+ const states = kind === "issue" ? state === "open" ? ["open", "progressing"] : state === "closed" ? ["closed", "rejected"] : ["all"] : state === "closed" ? ["closed", "merged"] : [state];
652
+ phases.push(...states.map((value) => ({
653
+ kind,
654
+ state: value
655
+ })));
656
+ }
657
+ const page = await phased(phases.map((phase) => async (cursor) => {
658
+ const pulls = phase.kind === "pull_request";
659
+ const result = await numberedPage(`${repoPath(repo)}/${pulls ? "pulls" : "issues"}`, {
660
+ state: phase.state,
661
+ labels: query.labels?.join(","),
662
+ sort: query.sort === "updated" ? "updated" : "created",
663
+ direction: query.direction ?? "desc",
664
+ since: query.since?.toISOString(),
665
+ ...pulls ? { author: query.author } : {
666
+ creator: query.author,
667
+ assignee: query.assignee
668
+ }
669
+ }, {
670
+ perPage: query.perPage,
671
+ cursor,
672
+ signal: query.signal
673
+ });
674
+ return {
675
+ items: result.items.filter((raw) => (createdAfter === void 0 || (Date.parse(raw.created_at ?? "") || 0) >= createdAfter) && hasEveryLabel(raw.labels, query.labels)).map((raw) => {
676
+ const ref = {
677
+ forge: FORGE,
678
+ instance,
679
+ repo,
680
+ kind: phase.kind,
681
+ number: String(raw.number)
682
+ };
683
+ return pulls ? toPullThread(ref, raw) : toIssueThread(ref, raw);
684
+ }),
685
+ cursor: result.cursor
686
+ };
687
+ }), query.cursor);
688
+ return warnings.length ? {
689
+ ...page,
690
+ warnings: [...warnings, ...page.warnings ?? []]
691
+ } : page;
692
+ }
693
+ /** Gitee subscribes with one boolean per event group rather than a list of names. */
694
+ function hookFlags(input) {
695
+ return Object.fromEntries(nativeEventsFor(GITEE_NATIVE_EVENTS, input.events, input.nativeEvents).map((flag) => [flag, true]));
696
+ }
697
+ /** Gitee searches issues only; its pull requests have no search endpoint. */
698
+ async function searchThreadsPage(query) {
699
+ if (query.kind === "pull_request") return {
700
+ items: [],
701
+ warnings: [{
702
+ code: "kind_unsupported",
703
+ message: "Gitee has no pull request search"
704
+ }]
705
+ };
706
+ const warnings = [];
707
+ if (!query.cursor && query.involves) warnings.push({
708
+ code: "filter_unsupported",
709
+ message: "Gitee has no involves filter; it was ignored"
710
+ });
711
+ if (!query.cursor && query.since) warnings.push({
712
+ code: "filter_unsupported",
713
+ message: "Gitee issue search has no update-time filter; it was ignored"
714
+ });
715
+ const page = await numberedPage("/search/issues", {
716
+ q: query.text ?? "",
717
+ repo: query.repo ? `${query.repo.owner}/${query.repo.name}` : void 0,
718
+ state: query.state && query.state !== "all" ? query.state : void 0,
719
+ label: query.labels?.join(","),
720
+ author: query.author,
721
+ assignee: query.assignee,
722
+ sort: query.sort === "updated" ? "updated_at" : "created_at",
723
+ order: query.direction ?? "desc"
724
+ }, {
725
+ perPage: query.perPage,
726
+ cursor: query.cursor,
727
+ signal: query.signal
728
+ });
729
+ return {
730
+ items: page.items.flatMap((raw) => {
731
+ const repo = query.repo ?? (raw.repository && toRepoRef(instance, raw.repository));
732
+ return repo && hasEveryLabel(raw.labels, query.labels) ? [toIssueThread({
733
+ forge: FORGE,
734
+ instance,
735
+ repo,
736
+ kind: "issue",
737
+ number: raw.number
738
+ }, raw)] : [];
739
+ }),
740
+ cursor: page.cursor,
741
+ ...warnings.length ? { warnings } : {}
742
+ };
743
+ }
744
+ async function searchReposPage(query) {
745
+ const page = await numberedPage("/search/repositories", {
746
+ q: query.text ?? "",
747
+ owner: query.owner,
748
+ language: query.language,
749
+ sort: query.sort === "stars" ? "stars_count" : query.sort === "updated" ? "last_push_at" : void 0,
750
+ order: query.direction ?? "desc"
751
+ }, {
752
+ perPage: query.perPage,
753
+ cursor: query.cursor,
754
+ signal: query.signal
755
+ });
756
+ const warnings = query.sort === "created" && !query.cursor ? [{
757
+ code: "sort_unsupported",
758
+ message: "Gitee has no repository search by creation time; sorted by best match"
759
+ }] : void 0;
760
+ return {
761
+ items: page.items.map((raw) => toRepo(instance, raw)),
762
+ cursor: page.cursor,
763
+ warnings
764
+ };
765
+ }
766
+ async function commentsPage(thread, listOptions = {}) {
767
+ const ref = requireIssueOrPull(thread, context, "list comments on");
768
+ const page = await numberedPage(`${threadPath(ref)}/comments`, {}, listOptions);
769
+ return {
770
+ items: page.items.filter(isConversationComment).map((raw) => toComment(ref, raw)),
771
+ cursor: page.cursor
772
+ };
773
+ }
774
+ /** Comments and the operation log, merged and ordered by time, so one complete page. */
775
+ async function eventsPage(thread, listOptions = {}) {
776
+ const ref = requireIssueOrPull(thread, context, "list events on");
777
+ const logsPath = ref.kind === "pull_request" ? `${threadPath(ref)}/operate_logs` : `/repos/${encodeURIComponent(ref.repo.owner)}/issues/${encodeURIComponent(ref.number)}/operate_logs`;
778
+ const [comments, { data: logs }] = await Promise.all([all(`${threadPath(ref)}/comments`, listOptions), fetcher.json(logsPath, {
779
+ query: ref.kind === "issue" ? { repo: ref.repo.name } : { sort: "asc" },
780
+ signal: listOptions.signal
781
+ })]);
782
+ return { items: [...comments.map((raw) => toCommentEvent(ref, raw)), ...(logs ?? []).map((raw) => toLogEvent(ref, raw))].sort((a, b) => a.occurredAt.getTime() - b.occurredAt.getTime()) };
783
+ }
784
+ /** Issue writes are addressed by owner, with the repository in the body. */
785
+ function issueWrite(ref, json, signal) {
786
+ return fetcher.json(`/repos/${encodeURIComponent(ref.repo.owner)}/issues/${encodeURIComponent(ref.number)}`, {
787
+ method: "PATCH",
788
+ json: {
789
+ repo: ref.repo.name,
790
+ ...json
791
+ },
792
+ signal
793
+ });
794
+ }
795
+ async function setState(thread, state, options) {
796
+ const ref = requireIssueOrPull(thread, context, state === "open" ? "reopen" : "close");
797
+ if (ref.kind === "issue") {
798
+ await issueWrite(ref, { state }, options?.signal);
799
+ return;
800
+ }
801
+ await fetcher.raw(threadPath(ref), {
802
+ method: "PATCH",
803
+ json: { state },
804
+ signal: options?.signal
805
+ });
806
+ }
807
+ async function notificationPage(listOptions = {}) {
808
+ const perPage = Math.min(listOptions.perPage ?? PER_PAGE, 100);
809
+ const url = listOptions.cursor?.nextUrl ?? fetcher.resolve("/notifications/threads", {
810
+ unread: listOptions.all ? void 0 : true,
811
+ since: listOptions.since?.toISOString(),
812
+ page: 1,
813
+ per_page: perPage
814
+ });
815
+ const page = Number(new URL(url).searchParams.get("page") ?? 1);
816
+ const { data, response } = await fetcher.json(url, { signal: listOptions.signal });
817
+ const items = data.list ?? [];
818
+ const totalPages = Number(response.headers.get("total_page"));
819
+ const more = Number.isFinite(totalPages) && totalPages > 0 ? page < totalPages : data.total_count !== void 0 ? page * perPage < data.total_count : items.length === perPage;
820
+ return {
821
+ items: items.map((raw) => toNotification(instance, raw)),
822
+ cursor: more ? { nextUrl: nextPage(url, page) } : void 0
823
+ };
824
+ }
825
+ async function releasesPage(repo, listOptions = {}) {
826
+ const page = await numberedPage(`${repoPath(repo)}/releases`, {}, listOptions);
827
+ return {
828
+ items: page.items.map((raw) => toRelease(repo, raw)),
829
+ cursor: page.cursor
830
+ };
831
+ }
832
+ return {
833
+ checks: { list: verb(true, async (repo, sha, options) => ({ items: await headChecks(repo, sha, options?.signal) })) },
834
+ traits: {
835
+ eventKinds: "native",
836
+ authKinds: ["token", "anonymous"]
837
+ },
838
+ users: {
839
+ get: verb(true, (login, options) => readUser(`/users/${encodeURIComponent(login)}`, options?.signal)),
840
+ me: verb(true, (options) => readUser("/user", options?.signal))
841
+ },
842
+ repos: {
843
+ get: verb(true, async (ref, options) => toRepo(instance, (await fetcher.json(repoPath(ref), { signal: options?.signal })).data)),
844
+ listPage: verb(true, async (listOptions = {}) => {
845
+ const page = await numberedPage("/user/repos", {}, listOptions);
846
+ return {
847
+ items: page.items.map((raw) => toRepo(instance, raw)),
848
+ cursor: page.cursor
849
+ };
850
+ }),
851
+ labelsPage: verb(true, async (repo, listOptions = {}) => {
852
+ const page = await numberedPage(`${repoPath(repo)}/labels`, {}, listOptions);
853
+ return {
854
+ items: page.items.map((raw) => ({
855
+ name: raw.name,
856
+ colour: raw.color ?? void 0
857
+ })),
858
+ cursor: page.cursor
859
+ };
860
+ }),
861
+ createLabel: verb("unverified", async (repo, label, options) => {
862
+ const { data } = await fetcher.json(`${repoPath(repo)}/labels`, {
863
+ method: "POST",
864
+ json: {
865
+ name: label.name,
866
+ color: hexColour(label.colour)
867
+ },
868
+ signal: options?.signal
869
+ });
870
+ return {
871
+ name: data.name,
872
+ colour: data.color ?? void 0
873
+ };
874
+ }),
875
+ collaboratorsPage: verb(true, async (repo, listOptions = {}) => {
876
+ const page = await numberedPage(`${repoPath(repo)}/collaborators`, {}, listOptions);
877
+ return {
878
+ items: page.items.map((raw) => ({
879
+ actor: toActor(instance, raw),
880
+ role: raw.permissions?.admin ? "admin" : raw.permissions?.push ? "write" : raw.permissions?.pull ? "read" : "none",
881
+ raw
882
+ })),
883
+ cursor: page.cursor
884
+ };
885
+ })
886
+ },
887
+ notifications: {
888
+ listPage: verb(true, notificationPage),
889
+ markRead: verb(true, async (ref, options) => {
890
+ await fetcher.raw(`/notifications/threads/${ref.id}`, {
891
+ method: "PATCH",
892
+ signal: options?.signal
893
+ });
894
+ }),
895
+ markAllRead: verb("unverified", async (bulk = {}) => {
896
+ if (bulk.before) throw new UnsupportedOperationError("Gitee marks notifications read without a time bound", context);
897
+ await fetcher.raw(bulk.repo ? `${repoPath(bulk.repo)}/notifications` : "/notifications/threads", {
898
+ method: "PUT",
899
+ signal: bulk.signal
900
+ });
901
+ }),
902
+ unreadCount: verb(true, async (options) => (await fetcher.json("/notifications/count", {
903
+ query: { unread: true },
904
+ signal: options?.signal
905
+ })).data.notification_count ?? 0)
906
+ },
907
+ contents: {
908
+ file: verb(true, async (repo, path, fileOptions = {}) => {
909
+ const { data } = await fetcher.json(`${repoPath(repo)}/contents/${path.split("/").map(encodeURIComponent).join("/")}`, {
910
+ query: { ref: fileOptions.ref },
911
+ signal: fileOptions.signal
912
+ });
913
+ const file = {
914
+ path: data.path,
915
+ sha: data.sha,
916
+ size: data.size,
917
+ url: data._links?.html
918
+ };
919
+ return toFileContent(fromBase64(data.content ?? ""), file, fileOptions, context);
920
+ }),
921
+ treePage: verb(true, async (repo, treeOptions = {}) => {
922
+ const ref = treeOptions.ref ?? (await fetcher.json(repoPath(repo), { signal: treeOptions.signal })).data.default_branch ?? "master";
923
+ const { data } = await fetcher.json(`${repoPath(repo)}/git/trees/${encodeURIComponent(ref)}`, {
924
+ query: { recursive: treeOptions.recursive ? 1 : void 0 },
925
+ signal: treeOptions.signal
926
+ });
927
+ const prefix = treeOptions.path?.replace(/^\/|\/$/g, "");
928
+ return {
929
+ items: (data.tree ?? []).filter((entry) => !prefix || entry.path.startsWith(`${prefix}/`)).map(toTreeEntry),
930
+ ...data.truncated ? { warnings: [{
931
+ code: "tree_truncated",
932
+ message: "Gitee truncated the tree; read it directory by directory instead"
933
+ }] } : {}
934
+ };
935
+ }),
936
+ branchesPage: verb(true, async (repo, listOptions = {}) => {
937
+ const page = await numberedPage(`${repoPath(repo)}/branches`, {}, listOptions);
938
+ return {
939
+ items: page.items.map(toBranch),
940
+ cursor: page.cursor
941
+ };
942
+ }),
943
+ tagsPage: verb(true, async (repo, listOptions = {}) => {
944
+ const { data } = await fetcher.json(`${repoPath(repo)}/tags`, { signal: listOptions.signal });
945
+ return { items: (data ?? []).map(toTag) };
946
+ }),
947
+ resolveRef: verb(true, async (repo, ref, options) => (await fetcher.json(`${repoPath(repo)}/commits/${encodeURIComponent(ref)}`, { signal: options?.signal })).data.sha),
948
+ commitsPage: verb(true, async (repo, query = {}) => {
949
+ const page = await numberedPage(`${repoPath(repo)}/commits`, {
950
+ sha: query.ref,
951
+ path: query.path,
952
+ author: query.author,
953
+ since: query.since?.toISOString(),
954
+ until: query.until?.toISOString()
955
+ }, query);
956
+ return {
957
+ items: page.items.map((raw) => toCommit(repo, raw)),
958
+ cursor: page.cursor
959
+ };
960
+ }),
961
+ commit: verb(true, async (repo, sha, options) => toCommit(repo, (await fetcher.json(`${repoPath(repo)}/commits/${sha}`, { signal: options?.signal })).data)),
962
+ compare: verb(true, async (repo, base, head, options) => {
963
+ const { data } = await fetcher.json(`${repoPath(repo)}/compare/${encodeURIComponent(base)}...${encodeURIComponent(head)}`, { signal: options?.signal });
964
+ return {
965
+ base,
966
+ head,
967
+ aheadBy: data.commits?.length,
968
+ commits: (data.commits ?? []).map((raw) => toCommit(repo, raw)),
969
+ files: (data.files ?? []).map(toChangedFile),
970
+ raw: data
971
+ };
972
+ })
973
+ },
974
+ search: {
975
+ threadsPage: verb(true, searchThreadsPage),
976
+ reposPage: verb(true, searchReposPage)
977
+ },
978
+ releases: {
979
+ listPage: verb(true, releasesPage),
980
+ get: verb(true, async (ref, options) => toRelease(ref.repo, (await fetcher.json(`${repoPath(ref.repo)}/releases/${ref.id}`, { signal: options?.signal })).data)),
981
+ getByTag: verb(true, async (repo, tag, options) => toRelease(repo, (await fetcher.json(`${repoPath(repo)}/releases/tags/${encodeURIComponent(tag)}`, { signal: options?.signal })).data)),
982
+ latest: verb(true, async (repo, options) => {
983
+ try {
984
+ const latest = toRelease(repo, (await fetcher.json(`${repoPath(repo)}/releases/latest`, { signal: options?.signal })).data);
985
+ if (!latest.isPrerelease) return latest;
986
+ } catch (error) {
987
+ if (!(error instanceof NotFoundError)) throw error;
988
+ return;
989
+ }
990
+ return (await releasesPage(repo, {
991
+ perPage: 20,
992
+ signal: options?.signal
993
+ })).items.find((release) => !release.isPrerelease);
994
+ })
995
+ },
996
+ threads: {
997
+ get: perKind({
998
+ issue: true,
999
+ pull_request: true
1000
+ }, get),
1001
+ getMany: verb(true, (refs, options) => getManyConcurrently(refs, get, options)),
1002
+ listPage: perKind({
1003
+ issue: true,
1004
+ pull_request: true
1005
+ }, listPage),
1006
+ eventsPage: verb(true, eventsPage),
1007
+ filesPage: verb(true, async (thread, listOptions = {}) => {
1008
+ const ref = requirePull(thread, "read for changed files");
1009
+ const { data } = await fetcher.json(`${threadPath(ref)}/files`, { signal: listOptions.signal });
1010
+ return { items: (data ?? []).map(toChangedFile) };
1011
+ }),
1012
+ commitsPage: verb(true, async (thread, listOptions = {}) => {
1013
+ const ref = requirePull(thread, "read for commits");
1014
+ const { data } = await fetcher.json(`${threadPath(ref)}/commits`, { signal: listOptions.signal });
1015
+ return { items: (data ?? []).map((raw) => toCommit(thread.repo, raw)) };
1016
+ }),
1017
+ commentsPage: perKind({
1018
+ issue: true,
1019
+ pull_request: true
1020
+ }, commentsPage),
1021
+ comment: perKind({
1022
+ issue: "unverified",
1023
+ pull_request: true
1024
+ }, async (thread, body, options) => {
1025
+ const ref = requireIssueOrPull(thread, context, "comment on");
1026
+ return toComment(ref, (await fetcher.json(`${threadPath(ref)}/comments`, {
1027
+ method: "POST",
1028
+ json: { body },
1029
+ signal: options?.signal
1030
+ })).data);
1031
+ }),
1032
+ editComment: perKind({
1033
+ issue: "unverified",
1034
+ pull_request: "unverified"
1035
+ }, async (ref, body, options) => {
1036
+ const kind = ref.thread.kind === "pull_request" ? "pulls" : "issues";
1037
+ return toComment(ref.thread, (await fetcher.json(`${repoPath(ref.thread.repo)}/${kind}/comments/${ref.id}`, {
1038
+ method: "PATCH",
1039
+ json: { body },
1040
+ signal: options?.signal
1041
+ })).data);
1042
+ }),
1043
+ deleteComment: perKind({
1044
+ issue: "unverified",
1045
+ pull_request: "unverified"
1046
+ }, async (ref, options) => {
1047
+ const kind = ref.thread.kind === "pull_request" ? "pulls" : "issues";
1048
+ await fetcher.raw(`${repoPath(ref.thread.repo)}/${kind}/comments/${ref.id}`, {
1049
+ method: "DELETE",
1050
+ signal: options?.signal
1051
+ });
1052
+ }),
1053
+ create: perKind({
1054
+ issue: "unverified",
1055
+ pull_request: "unverified"
1056
+ }, async (repo, input, options) => {
1057
+ const labels = input.labels?.join(",");
1058
+ if (input.kind === "issue") {
1059
+ if ((input.assignees?.length ?? 0) > 1) throw new UnsupportedOperationError("Gitee issues take a single assignee", context);
1060
+ const { data } = await fetcher.json(`/repos/${encodeURIComponent(repo.owner)}/issues`, {
1061
+ method: "POST",
1062
+ json: {
1063
+ repo: repo.name,
1064
+ title: input.title,
1065
+ body: input.body,
1066
+ labels,
1067
+ assignee: input.assignees?.[0] ? actorLogin(input.assignees[0]) : void 0
1068
+ },
1069
+ signal: options?.signal
1070
+ });
1071
+ return toIssueThread({
1072
+ forge: FORGE,
1073
+ instance,
1074
+ repo,
1075
+ kind: "issue",
1076
+ number: data.number
1077
+ }, data);
1078
+ }
1079
+ if (!input.head || !input.base) throw new UnsupportedOperationError("Creating a pull request needs head and base branches", context);
1080
+ const { data } = await fetcher.json(`${repoPath(repo)}/pulls`, {
1081
+ method: "POST",
1082
+ json: {
1083
+ title: input.title,
1084
+ body: input.body,
1085
+ head: input.head,
1086
+ base: input.base,
1087
+ labels,
1088
+ draft: input.draft,
1089
+ assignees: input.assignees?.map(actorLogin).join(",")
1090
+ },
1091
+ signal: options?.signal
1092
+ });
1093
+ return toPullThread({
1094
+ forge: FORGE,
1095
+ instance,
1096
+ repo,
1097
+ kind: "pull_request",
1098
+ number: String(data.number)
1099
+ }, data);
1100
+ }),
1101
+ update: perKind({
1102
+ issue: "unverified",
1103
+ pull_request: "unverified"
1104
+ }, async (thread, input, options) => {
1105
+ const ref = requireIssueOrPull(thread, context, "update");
1106
+ return ref.kind === "issue" ? toIssueThread(ref, (await issueWrite(ref, { ...input }, options?.signal)).data) : toPullThread(ref, (await fetcher.json(threadPath(ref), {
1107
+ method: "PATCH",
1108
+ json: input,
1109
+ signal: options?.signal
1110
+ })).data);
1111
+ }),
1112
+ close: perKind({
1113
+ issue: "unverified",
1114
+ pull_request: true
1115
+ }, (ref, options) => setState(ref, "closed", options)),
1116
+ reopen: perKind({
1117
+ issue: "unverified",
1118
+ pull_request: true
1119
+ }, (ref, options) => setState(ref, "open", options)),
1120
+ addLabels: perKind({
1121
+ issue: "unverified",
1122
+ pull_request: "unverified"
1123
+ }, async (thread, labels, options) => {
1124
+ await fetcher.raw(`${threadPath(requireIssueOrPull(thread, context, "label"))}/labels`, {
1125
+ method: "POST",
1126
+ json: labels,
1127
+ signal: options?.signal
1128
+ });
1129
+ }),
1130
+ removeLabels: perKind({
1131
+ issue: "unverified",
1132
+ pull_request: "unverified"
1133
+ }, async (thread, labels, options) => {
1134
+ const ref = requireIssueOrPull(thread, context, "label");
1135
+ for (const label of labels) await fetcher.raw(`${threadPath(ref)}/labels/${encodeURIComponent(label)}`, {
1136
+ method: "DELETE",
1137
+ signal: options?.signal
1138
+ });
1139
+ }),
1140
+ setLabels: perKind({
1141
+ issue: "unverified",
1142
+ pull_request: "unverified"
1143
+ }, async (thread, labels, options) => {
1144
+ await fetcher.raw(`${threadPath(requireIssueOrPull(thread, context, "label"))}/labels`, {
1145
+ method: "PUT",
1146
+ json: labels,
1147
+ signal: options?.signal
1148
+ });
1149
+ }),
1150
+ setAssignees: perKind({ issue: "unverified" }, async (thread, assignees, options) => {
1151
+ if (assignees.length > 1) throw new UnsupportedOperationError("Gitee issues take a single assignee", context);
1152
+ await issueWrite(requireIssueOrPull(thread, context, "assign"), { assignee: assignees[0] ? actorLogin(assignees[0]) : "" }, options?.signal);
1153
+ }),
1154
+ requestReview: perKind({ pull_request: "unverified" }, async (thread, reviewers, options) => {
1155
+ await fetcher.raw(`${threadPath(requireThread(thread, context))}/assignees`, {
1156
+ method: "POST",
1157
+ json: { assignees: reviewers.map(actorLogin).join(",") },
1158
+ signal: options?.signal
1159
+ });
1160
+ }),
1161
+ merge: verb(true, async (thread, mergeOptions = {}, hooks = {}) => {
1162
+ const ref = requireThread(thread, context);
1163
+ if (ref.kind !== "pull_request") throw new UnsupportedOperationError("Only pull requests can be merged", context);
1164
+ if (mergeOptions.whenChecksPass) throw new UnsupportedOperationError("Gitee has no auto-merge through the API", context);
1165
+ if (!mergeOptions.method) throw new MergeMethodRequiredError([
1166
+ "merge",
1167
+ "squash",
1168
+ "rebase"
1169
+ ], context);
1170
+ if (![
1171
+ "merge",
1172
+ "squash",
1173
+ "rebase"
1174
+ ].includes(mergeOptions.method)) throw new UnsupportedOperationError(`Gitee does not support the ${mergeOptions.method} merge method`, context);
1175
+ await hooks.beforeMerge?.();
1176
+ await fetcher.raw(`${threadPath(ref)}/merge`, {
1177
+ method: "PUT",
1178
+ json: {
1179
+ merge_method: mergeOptions.method,
1180
+ title: mergeOptions.title,
1181
+ description: mergeOptions.message
1182
+ },
1183
+ mapError: toMergeError,
1184
+ signal: mergeOptions.signal
1185
+ });
1186
+ }),
1187
+ checks: perKind(PULL, async (thread, options) => {
1188
+ const ref = requireThread(thread, context);
1189
+ const { data } = await fetcher.json(threadPath(ref), { signal: options?.signal });
1190
+ return { items: data.head?.sha ? await headChecks(ref.repo, data.head.sha, options?.signal) : [] };
1191
+ }),
1192
+ createReview: verb("unverified", createReview)
1193
+ },
1194
+ web: githubShapedWeb(baseUrl.replace(/\/api\/v5$/, ""), {
1195
+ pull: "pulls",
1196
+ commentFragment: "note_",
1197
+ file: (at) => `/blob/${encodeURIComponent(at)}`,
1198
+ lineFragment: (line) => `L${line}`,
1199
+ pullPrefix: "!"
1200
+ }),
1201
+ webhooks: {
1202
+ listPage: verb("unverified", async (target, listOptions = {}) => {
1203
+ const page = await numberedPage(`${repoPath(target)}/hooks`, {}, listOptions);
1204
+ return {
1205
+ items: page.items.map((raw) => toWebhook(target, raw)),
1206
+ cursor: page.cursor
1207
+ };
1208
+ }),
1209
+ create: verb("unverified", async (target, input, options) => toWebhook(target, (await fetcher.json(`${repoPath(target)}/hooks`, {
1210
+ method: "POST",
1211
+ json: {
1212
+ url: input.url,
1213
+ password: input.secret,
1214
+ ...hookFlags(input)
1215
+ },
1216
+ signal: options?.signal
1217
+ })).data)),
1218
+ update: verb("unverified", async (ref, update, options) => toWebhook(ref.target, (await fetcher.json(`${repoPath(ref.target)}/hooks/${ref.id}`, {
1219
+ method: "PATCH",
1220
+ json: {
1221
+ ...update.url ? { url: update.url } : {},
1222
+ ...update.secret ? { password: update.secret } : {},
1223
+ ...hookFlags(update)
1224
+ },
1225
+ signal: options?.signal
1226
+ })).data)),
1227
+ delete: verb("unverified", async (ref, options) => {
1228
+ await fetcher.raw(`${repoPath(ref.target)}/hooks/${ref.id}`, {
1229
+ method: "DELETE",
1230
+ signal: options?.signal
1231
+ });
1232
+ }),
1233
+ rotateSecret: verb("unverified", async (ref, secret, options) => toWebhook(ref.target, (await fetcher.json(`${repoPath(ref.target)}/hooks/${ref.id}`, {
1234
+ method: "PATCH",
1235
+ json: { password: secret },
1236
+ signal: options?.signal
1237
+ })).data))
1238
+ },
1239
+ scopes: giteeScopesFor
1240
+ };
1241
+ }
1242
+ const GITEE = {
1243
+ forge: FORGE,
1244
+ baseUrl: "https://gitee.com",
1245
+ anonymous: true,
1246
+ apiPath: "/api/v5",
1247
+ headers: { accept: "application/json" },
1248
+ authHeaders: ({ options: { auth } }) => auth?.type === "token" ? async () => ({ authorization: `token ${await resolveToken(auth)}` }) : void 0,
1249
+ setup: setupGitee
1250
+ };
1251
+ /** Creates a Gitee provider for gitee.com. The enterprise edition (`e.gitee.com`) is not covered. */
1252
+ const gitee = /* @__PURE__ */ defineForgeProvider({
1253
+ ...GITEE,
1254
+ webhooks: giteeWebhooks
1255
+ });
1256
+ /** `gitee()` without webhook ingestion, for bundles that never receive a delivery. */
1257
+ const giteeLite = /* @__PURE__ */ defineForgeProvider(GITEE);
1258
+ /** Gitee OAuth scopes, one per resource group. */
1259
+ function giteeScopesFor(verb) {
1260
+ const [group = "", name = ""] = verb.split(".");
1261
+ if (group === "webhooks") return name === "verify" || name === "ingest" ? {} : { token: ["hook"] };
1262
+ if (group === "notifications") return { token: ["notes", "user_info"] };
1263
+ if (group === "users" || group === "search") return { token: ["user_info"] };
1264
+ if (group === "threads") return { token: [
1265
+ "issues",
1266
+ "pull_requests",
1267
+ "notes"
1268
+ ] };
1269
+ return { token: ["projects"] };
1270
+ }
1271
+ //#endregion
1272
+ export { giteeLite as n, giteeScopesFor as r, gitee as t };