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