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,980 @@
|
|
|
1
|
+
import { E as summariseChecks, Nt as soleMergeMethod, O as toDate, Pt as toMergeError, S as phased, T as resolveToken, _t as ForgeError, c as checkRunState, d as forgeIterable, dt as fromBase64, f as getManyConcurrently, ft as toFileContent, g as iteratePages, j as toWarning, jt as UnsupportedOperationError, k as toFileStatus, n as perKind, r as verb, rt as createFetcher, t as defineForgeProvider, v as memo, w as requireThread } from "./define-D3yEXtKK.mjs";
|
|
2
|
+
import { a as sha256Hex, n as headerValue, o as signEdDsaJwt, t as bodyText, u as verifyEd25519 } from "./crypto-CQNEyYHD.mjs";
|
|
3
|
+
import { i as refEvent } from "./webhooks-rn9-8lxT.mjs";
|
|
4
|
+
//#region src/cursor-origin/auth.ts
|
|
5
|
+
/** Origin caps installation tokens at the lifetime of the JWT that minted them. */
|
|
6
|
+
const JWT_LIFETIME_SECONDS = 300;
|
|
7
|
+
const REFRESH_MARGIN_MS = 6e4;
|
|
8
|
+
function createOriginAppCredentials(auth, createFetcher) {
|
|
9
|
+
let jwt;
|
|
10
|
+
const tokens = /* @__PURE__ */ new Map();
|
|
11
|
+
const appId = String(auth.appId);
|
|
12
|
+
async function appJwt() {
|
|
13
|
+
if (jwt && jwt.expiresAt > Date.now() + REFRESH_MARGIN_MS) return jwt.value;
|
|
14
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
15
|
+
const value = await signEdDsaJwt(auth.privateKey, {
|
|
16
|
+
iss: appId,
|
|
17
|
+
aud: "origin-apps",
|
|
18
|
+
iat: now,
|
|
19
|
+
exp: now + JWT_LIFETIME_SECONDS
|
|
20
|
+
}, appId);
|
|
21
|
+
jwt = {
|
|
22
|
+
value,
|
|
23
|
+
expiresAt: (now + JWT_LIFETIME_SECONDS) * 1e3
|
|
24
|
+
};
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
const fetcher = createFetcher(async () => ({ authorization: `Bearer ${await appJwt()}` }));
|
|
28
|
+
async function installationTokenDetails(installationId, options) {
|
|
29
|
+
const cached = tokens.get(installationId);
|
|
30
|
+
if (cached && cached.expiresAt.getTime() > Date.now() + REFRESH_MARGIN_MS) return cached;
|
|
31
|
+
const { data } = await fetcher.json(`/app/installations/${installationId}/access_tokens`, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
json: {},
|
|
34
|
+
signal: options?.signal
|
|
35
|
+
});
|
|
36
|
+
const token = {
|
|
37
|
+
token: data.token,
|
|
38
|
+
expiresAt: new Date(data.expiresAt),
|
|
39
|
+
permissions: {}
|
|
40
|
+
};
|
|
41
|
+
tokens.set(installationId, token);
|
|
42
|
+
return token;
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
appJwt,
|
|
46
|
+
installationTokenDetails,
|
|
47
|
+
installationToken: async (installationId, options) => (await installationTokenDetails(installationId, options)).token
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/cursor-origin/normalise.ts
|
|
52
|
+
const FORGE = "cursor-origin";
|
|
53
|
+
function toActor(instance, actor) {
|
|
54
|
+
if (actor?.user) {
|
|
55
|
+
const user = actor.user;
|
|
56
|
+
return {
|
|
57
|
+
forge: FORGE,
|
|
58
|
+
instance,
|
|
59
|
+
login: user.handle ?? user.email ?? user.id,
|
|
60
|
+
id: user.id,
|
|
61
|
+
name: user.displayName,
|
|
62
|
+
typeRaw: user.performedVia?.app ? "user_via_app" : "user",
|
|
63
|
+
isBotHint: Boolean(user.performedVia?.app)
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (actor?.app) return {
|
|
67
|
+
forge: FORGE,
|
|
68
|
+
instance,
|
|
69
|
+
login: actor.app.displayName ?? actor.app.id,
|
|
70
|
+
id: actor.app.id,
|
|
71
|
+
name: actor.app.displayName,
|
|
72
|
+
typeRaw: "app",
|
|
73
|
+
isBotHint: true
|
|
74
|
+
};
|
|
75
|
+
if (actor?.serviceAccount) return {
|
|
76
|
+
forge: FORGE,
|
|
77
|
+
instance,
|
|
78
|
+
login: actor.serviceAccount.id,
|
|
79
|
+
id: actor.serviceAccount.id,
|
|
80
|
+
typeRaw: "service_account",
|
|
81
|
+
isBotHint: true
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function toRepoRef(instance, repo) {
|
|
85
|
+
return {
|
|
86
|
+
forge: FORGE,
|
|
87
|
+
instance,
|
|
88
|
+
owner: repo.owner.slug,
|
|
89
|
+
name: repo.name,
|
|
90
|
+
externalId: repo.id
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function toRepo(instance, raw) {
|
|
94
|
+
return {
|
|
95
|
+
ref: toRepoRef(instance, raw),
|
|
96
|
+
defaultBranch: raw.defaultBranch,
|
|
97
|
+
visibility: raw.visibility ?? "private",
|
|
98
|
+
visibilityRaw: raw.visibility,
|
|
99
|
+
isFork: false,
|
|
100
|
+
isArchived: false,
|
|
101
|
+
topics: [],
|
|
102
|
+
cloneUrls: raw.cloneUrl ? { https: raw.cloneUrl } : void 0,
|
|
103
|
+
createdAt: toDate(raw.createdAt),
|
|
104
|
+
updatedAt: toDate(raw.updatedAt),
|
|
105
|
+
pushedAt: toDate(raw.pushedAt),
|
|
106
|
+
raw
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function threadState(raw) {
|
|
110
|
+
return raw.merged ? "merged" : raw.state === "open" ? "open" : raw.state === "closed" ? "closed" : "unknown";
|
|
111
|
+
}
|
|
112
|
+
function toThread(ref, raw) {
|
|
113
|
+
const parent = raw.stack?.parentPullRequest;
|
|
114
|
+
return {
|
|
115
|
+
ref: {
|
|
116
|
+
...ref,
|
|
117
|
+
kind: "pull_request",
|
|
118
|
+
number: raw.number,
|
|
119
|
+
externalId: raw.id
|
|
120
|
+
},
|
|
121
|
+
kind: "pull_request",
|
|
122
|
+
title: raw.title,
|
|
123
|
+
body: raw.body || void 0,
|
|
124
|
+
state: threadState(raw),
|
|
125
|
+
stateRaw: raw.merged ? "merged" : raw.state,
|
|
126
|
+
isDraft: raw.draft ?? false,
|
|
127
|
+
author: toActor(ref.instance, raw.author),
|
|
128
|
+
assignees: [],
|
|
129
|
+
reviewers: [],
|
|
130
|
+
labels: (raw.labels ?? []).map((label) => ({
|
|
131
|
+
name: label.name,
|
|
132
|
+
colour: label.color,
|
|
133
|
+
description: label.description || void 0
|
|
134
|
+
})),
|
|
135
|
+
createdAt: toDate(raw.createdAt),
|
|
136
|
+
updatedAt: toDate(raw.updatedAt),
|
|
137
|
+
closedAt: toDate(raw.closedAt),
|
|
138
|
+
lastActivityAt: toDate(raw.updatedAt),
|
|
139
|
+
branches: raw.head && raw.base ? {
|
|
140
|
+
head: {
|
|
141
|
+
ref: raw.head.ref,
|
|
142
|
+
sha: raw.head.sha
|
|
143
|
+
},
|
|
144
|
+
base: {
|
|
145
|
+
ref: raw.base.ref,
|
|
146
|
+
sha: raw.base.sha
|
|
147
|
+
},
|
|
148
|
+
mergeCommitSha: raw.mergeCommitSha
|
|
149
|
+
} : void 0,
|
|
150
|
+
...raw.stack ? { stack: {
|
|
151
|
+
id: raw.stack.id,
|
|
152
|
+
...parent ? { parent: {
|
|
153
|
+
forge: FORGE,
|
|
154
|
+
instance: ref.instance,
|
|
155
|
+
repo: ref.repo,
|
|
156
|
+
kind: "pull_request",
|
|
157
|
+
number: parent.number,
|
|
158
|
+
externalId: parent.id
|
|
159
|
+
} } : {}
|
|
160
|
+
} } : {},
|
|
161
|
+
raw
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/** General-discussion comments have no diff anchor; anchored ones are review comments. */
|
|
165
|
+
function isConversationComment(raw) {
|
|
166
|
+
return !raw.thread?.path;
|
|
167
|
+
}
|
|
168
|
+
function toComment(thread, raw) {
|
|
169
|
+
return {
|
|
170
|
+
ref: {
|
|
171
|
+
forge: FORGE,
|
|
172
|
+
instance: thread.instance,
|
|
173
|
+
thread,
|
|
174
|
+
id: raw.id
|
|
175
|
+
},
|
|
176
|
+
body: raw.body,
|
|
177
|
+
author: toActor(thread.instance, raw.author),
|
|
178
|
+
createdAt: toDate(raw.createdAt),
|
|
179
|
+
updatedAt: toDate(raw.updatedAt),
|
|
180
|
+
raw
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function toCommentEvent(thread, raw) {
|
|
184
|
+
const anchored = !isConversationComment(raw);
|
|
185
|
+
const actor = toActor(thread.instance, raw.author);
|
|
186
|
+
return {
|
|
187
|
+
forge: FORGE,
|
|
188
|
+
instance: thread.instance,
|
|
189
|
+
id: raw.id,
|
|
190
|
+
kind: anchored ? "review_comment" : "comment",
|
|
191
|
+
kindRaw: anchored ? "pull_request.review_comment" : "pull_request.comment",
|
|
192
|
+
summary: `${actor?.login ?? "someone"} ${anchored ? `commented on ${raw.thread.path}` : "commented"}`,
|
|
193
|
+
occurredAt: toDate(raw.createdAt) ?? /* @__PURE__ */ new Date(0),
|
|
194
|
+
actor,
|
|
195
|
+
repo: thread.repo,
|
|
196
|
+
thread,
|
|
197
|
+
source: "poll",
|
|
198
|
+
payload: raw
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
const VERDICTS = {
|
|
202
|
+
approve: "approved",
|
|
203
|
+
request_changes: "changes_requested",
|
|
204
|
+
comment: "commented"
|
|
205
|
+
};
|
|
206
|
+
function toReviewEvent(thread, raw, source = "poll") {
|
|
207
|
+
const actor = toActor(thread.instance, raw.author);
|
|
208
|
+
return {
|
|
209
|
+
forge: FORGE,
|
|
210
|
+
instance: thread.instance,
|
|
211
|
+
id: raw.id,
|
|
212
|
+
kind: "review",
|
|
213
|
+
kindRaw: raw.dismissal ? "pull_request.review.dismissed" : `pull_request.review.${raw.verdict}`,
|
|
214
|
+
summary: `${actor?.login ?? "someone"} ${raw.dismissal ? "dismissed a review" : `reviewed (${VERDICTS[raw.verdict] ?? raw.verdict})`}`,
|
|
215
|
+
occurredAt: toDate(raw.submittedAt) ?? /* @__PURE__ */ new Date(0),
|
|
216
|
+
actor,
|
|
217
|
+
repo: thread.repo,
|
|
218
|
+
thread,
|
|
219
|
+
source,
|
|
220
|
+
payload: raw
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function toCheck(repo, raw) {
|
|
224
|
+
return {
|
|
225
|
+
ref: {
|
|
226
|
+
forge: FORGE,
|
|
227
|
+
instance: repo.instance,
|
|
228
|
+
repo,
|
|
229
|
+
id: raw.id,
|
|
230
|
+
type: "check_run"
|
|
231
|
+
},
|
|
232
|
+
name: raw.name,
|
|
233
|
+
state: checkRunState(raw.status, raw.conclusion),
|
|
234
|
+
stateRaw: raw.status,
|
|
235
|
+
conclusionRaw: raw.conclusion,
|
|
236
|
+
url: raw.detailsUrl,
|
|
237
|
+
startedAt: toDate(raw.startedAt),
|
|
238
|
+
completedAt: toDate(raw.completedAt),
|
|
239
|
+
raw
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function toReview(thread, raw) {
|
|
243
|
+
return {
|
|
244
|
+
ref: {
|
|
245
|
+
forge: FORGE,
|
|
246
|
+
instance: thread.instance,
|
|
247
|
+
thread,
|
|
248
|
+
id: raw.id
|
|
249
|
+
},
|
|
250
|
+
author: toActor(thread.instance, raw.author),
|
|
251
|
+
state: raw.dismissal ? "dismissed" : VERDICTS[raw.verdict] ?? "unknown",
|
|
252
|
+
stateRaw: raw.verdict,
|
|
253
|
+
body: raw.body || void 0,
|
|
254
|
+
submittedAt: toDate(raw.submittedAt),
|
|
255
|
+
comments: false,
|
|
256
|
+
raw
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function toChangedFile(raw) {
|
|
260
|
+
return {
|
|
261
|
+
path: raw.filename,
|
|
262
|
+
previousPath: raw.previousFilename,
|
|
263
|
+
status: toFileStatus(raw.status),
|
|
264
|
+
statusRaw: raw.status,
|
|
265
|
+
additions: raw.additions,
|
|
266
|
+
deletions: raw.deletions,
|
|
267
|
+
patch: raw.patch
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function toCommit(repo, raw, files) {
|
|
271
|
+
return {
|
|
272
|
+
ref: {
|
|
273
|
+
forge: FORGE,
|
|
274
|
+
instance: repo.instance,
|
|
275
|
+
repo,
|
|
276
|
+
sha: raw.sha
|
|
277
|
+
},
|
|
278
|
+
sha: raw.sha,
|
|
279
|
+
message: raw.commit?.message ?? "",
|
|
280
|
+
author: {
|
|
281
|
+
name: raw.commit?.author?.name,
|
|
282
|
+
email: raw.commit?.author?.email,
|
|
283
|
+
date: toDate(raw.commit?.author?.date)
|
|
284
|
+
},
|
|
285
|
+
committer: {
|
|
286
|
+
name: raw.commit?.committer?.name,
|
|
287
|
+
email: raw.commit?.committer?.email,
|
|
288
|
+
date: toDate(raw.commit?.committer?.date)
|
|
289
|
+
},
|
|
290
|
+
parents: raw.parents?.flatMap((parent) => parent.sha ? [parent.sha] : []) ?? [],
|
|
291
|
+
...raw.stats ? { stats: {
|
|
292
|
+
additions: raw.stats.additions ?? 0,
|
|
293
|
+
deletions: raw.stats.deletions ?? 0,
|
|
294
|
+
total: raw.stats.total
|
|
295
|
+
} } : {},
|
|
296
|
+
...files ? { files } : {},
|
|
297
|
+
raw
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
function toBranch(raw) {
|
|
301
|
+
return {
|
|
302
|
+
name: raw.name,
|
|
303
|
+
sha: raw.commit?.sha ?? "",
|
|
304
|
+
raw
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
/** Origin lists tags as refs, so the name is the ref minus its `refs/tags/` prefix. */
|
|
308
|
+
function toTag(raw) {
|
|
309
|
+
return {
|
|
310
|
+
name: raw.ref.replace(/^refs\/tags\//, ""),
|
|
311
|
+
sha: raw.object?.sha ?? "",
|
|
312
|
+
raw
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
function toTreeEntry(raw) {
|
|
316
|
+
return {
|
|
317
|
+
path: raw.path,
|
|
318
|
+
type: raw.mode === "120000" ? "symlink" : raw.type === "tree" ? "directory" : raw.type === "commit" ? "submodule" : "file",
|
|
319
|
+
sha: raw.sha,
|
|
320
|
+
size: raw.size,
|
|
321
|
+
mode: raw.mode
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
//#endregion
|
|
325
|
+
//#region src/cursor-origin/webhooks.ts
|
|
326
|
+
/** Deliveries older or newer than this are refused, as Origin documents. */
|
|
327
|
+
const TOLERANCE_SECONDS = 300;
|
|
328
|
+
const KEYS_TTL_MS = 6e5;
|
|
329
|
+
/**
|
|
330
|
+
* Verifies a delivery: an Ed25519 signature (`v1ed,<base64>`) over the UTF-8
|
|
331
|
+
* hex SHA-256 of `<webhook-id>.<webhook-timestamp>.<body>`, against any
|
|
332
|
+
* active key. Origin signatures carry no key id.
|
|
333
|
+
*/
|
|
334
|
+
async function verifyOriginSignature(delivery, keys, now = Date.now()) {
|
|
335
|
+
const id = headerValue(delivery.headers, "webhook-id");
|
|
336
|
+
const timestamp = Number(headerValue(delivery.headers, "webhook-timestamp"));
|
|
337
|
+
const signature = headerValue(delivery.headers, "webhook-signature")?.split(/\s+/).find((value) => value.startsWith("v1ed,"));
|
|
338
|
+
if (!id || !signature || !Number.isInteger(timestamp) || Math.abs(Math.floor(now / 1e3) - timestamp) > TOLERANCE_SECONDS) return false;
|
|
339
|
+
const digest = await sha256Hex(`${id}.${timestamp}.${bodyText(delivery.body)}`);
|
|
340
|
+
for (const key of await keys()) if (await verifyEd25519(key, digest, signature.slice(5))) return true;
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
const PULL_KINDS = {
|
|
344
|
+
"pull_request.created": "state_change",
|
|
345
|
+
"pull_request.published": "state_change",
|
|
346
|
+
"pull_request.reopened": "state_change",
|
|
347
|
+
"pull_request.closed": "state_change",
|
|
348
|
+
"pull_request.merged": "state_change",
|
|
349
|
+
"pull_request.comment.created": "comment",
|
|
350
|
+
"pull_request.comment.reaction.added": "reaction",
|
|
351
|
+
"pull_request.comment.reaction.removed": "reaction",
|
|
352
|
+
"pull_request.label.added": "label",
|
|
353
|
+
"pull_request.label.removed": "label",
|
|
354
|
+
"pull_request.reviewer.added": "assignment",
|
|
355
|
+
"pull_request.reviewer.removed": "assignment",
|
|
356
|
+
"pull_request.reviewer.rerequested": "assignment",
|
|
357
|
+
"pull_request.head_ref.pushed": "commit"
|
|
358
|
+
};
|
|
359
|
+
function translateOriginWebhook(instance, delivery) {
|
|
360
|
+
const envelope = JSON.parse(bodyText(delivery.body));
|
|
361
|
+
const { event } = envelope;
|
|
362
|
+
const payload = event.payload ?? {};
|
|
363
|
+
const repo = payload.repository ? toRepoRef(instance, payload.repository) : void 0;
|
|
364
|
+
const pull = payload.pullRequest;
|
|
365
|
+
const thread = pull && repo ? {
|
|
366
|
+
forge: FORGE,
|
|
367
|
+
instance,
|
|
368
|
+
repo,
|
|
369
|
+
kind: "pull_request",
|
|
370
|
+
number: pull.number,
|
|
371
|
+
externalId: pull.id
|
|
372
|
+
} : void 0;
|
|
373
|
+
const base = {
|
|
374
|
+
forge: FORGE,
|
|
375
|
+
instance,
|
|
376
|
+
id: envelope.deliveryId ?? event.id,
|
|
377
|
+
kindRaw: event.type,
|
|
378
|
+
occurredAt: toDate(event.eventTime) ?? /* @__PURE__ */ new Date(),
|
|
379
|
+
repo,
|
|
380
|
+
thread,
|
|
381
|
+
installationId: envelope.installationId,
|
|
382
|
+
source: "webhook",
|
|
383
|
+
payload: envelope
|
|
384
|
+
};
|
|
385
|
+
if (event.type.startsWith("pull_request.review.") && payload.review && thread) return [{
|
|
386
|
+
...toReviewEvent(thread, payload.review, "webhook"),
|
|
387
|
+
...base
|
|
388
|
+
}];
|
|
389
|
+
if (thread) {
|
|
390
|
+
const actor = toActor(instance, payload.comment?.author ?? payload.actor ?? payload.createdBy ?? pull?.author);
|
|
391
|
+
const kind = payload.comment?.thread?.path ? "review_comment" : PULL_KINDS[event.type] ?? "other";
|
|
392
|
+
return [{
|
|
393
|
+
...base,
|
|
394
|
+
kind,
|
|
395
|
+
actor,
|
|
396
|
+
summary: `${actor?.login ?? "someone"}: ${event.type.replace(/^pull_request\./, "").replaceAll(".", " ")} on #${thread.number}`
|
|
397
|
+
}];
|
|
398
|
+
}
|
|
399
|
+
if (event.type === "repository.pushed") {
|
|
400
|
+
const pusher = toActor(instance, payload.pusher);
|
|
401
|
+
const who = pusher?.login ?? "someone";
|
|
402
|
+
return (payload.refUpdates ?? []).map((update, index) => {
|
|
403
|
+
const event = refEvent(who, {
|
|
404
|
+
ref: update.ref,
|
|
405
|
+
before: update.before,
|
|
406
|
+
after: update.after,
|
|
407
|
+
created: update.created,
|
|
408
|
+
deleted: update.deleted,
|
|
409
|
+
forced: update.forced,
|
|
410
|
+
commits: update.headCommit?.sha ? [{
|
|
411
|
+
sha: update.headCommit.sha,
|
|
412
|
+
message: update.headCommit.message ?? ""
|
|
413
|
+
}] : []
|
|
414
|
+
});
|
|
415
|
+
return {
|
|
416
|
+
...base,
|
|
417
|
+
id: `${base.id}:${index}`,
|
|
418
|
+
actor: pusher,
|
|
419
|
+
...event,
|
|
420
|
+
...event.kind === "push" ? { summary: `${who} pushed to ${update.ref.replace(/^refs\/(?:heads|tags)\//, "")}` } : {}
|
|
421
|
+
};
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
if (event.type.startsWith("installation.")) return [{
|
|
425
|
+
...base,
|
|
426
|
+
kind: "installation",
|
|
427
|
+
installationId: payload.installation?.id ?? envelope.installationId,
|
|
428
|
+
detail: {
|
|
429
|
+
type: "installation",
|
|
430
|
+
actionRaw: event.type,
|
|
431
|
+
installationId: payload.installation?.id ?? envelope.installationId,
|
|
432
|
+
added: (payload.installation?.repositories ?? []).map((reference) => toRepoRef(instance, reference)),
|
|
433
|
+
removed: []
|
|
434
|
+
},
|
|
435
|
+
summary: `Installation ${event.type.slice(13)}`
|
|
436
|
+
}];
|
|
437
|
+
return [{
|
|
438
|
+
...base,
|
|
439
|
+
kind: "other",
|
|
440
|
+
summary: event.type
|
|
441
|
+
}];
|
|
442
|
+
}
|
|
443
|
+
const ORIGIN_WEBHOOK_EVENTS = [
|
|
444
|
+
{
|
|
445
|
+
kind: "comment",
|
|
446
|
+
action: "created"
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
kind: "review",
|
|
450
|
+
action: "submitted"
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
kind: "state_change",
|
|
454
|
+
action: "opened"
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
kind: "state_change",
|
|
458
|
+
action: "closed"
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
kind: "state_change",
|
|
462
|
+
action: "merged"
|
|
463
|
+
},
|
|
464
|
+
{ kind: "installation" }
|
|
465
|
+
];
|
|
466
|
+
const cursorOriginWebhooks = ({ instance, baseUrl, createFetcher }) => {
|
|
467
|
+
const keysFetcher = createFetcher({ baseUrl });
|
|
468
|
+
const signingKeys = memo(async () => (await keysFetcher.json("/keys")).data.keys ?? [], KEYS_TTL_MS);
|
|
469
|
+
return {
|
|
470
|
+
events: ORIGIN_WEBHOOK_EVENTS,
|
|
471
|
+
verify: (delivery) => verifyOriginSignature(delivery, signingKeys),
|
|
472
|
+
translate: (delivery) => translateOriginWebhook(instance, delivery)
|
|
473
|
+
};
|
|
474
|
+
};
|
|
475
|
+
//#endregion
|
|
476
|
+
//#region src/cursor-origin/index.ts
|
|
477
|
+
/** The Origin API version this provider was built against. See `docs/content/4.providers/cursor-origin.md`. */
|
|
478
|
+
const ORIGIN_API_VERSION = "v1alpha1";
|
|
479
|
+
const PULL = { pull_request: "experimental" };
|
|
480
|
+
const UNVERIFIED = { pull_request: "unverified" };
|
|
481
|
+
const PAGE_SIZE = 50;
|
|
482
|
+
function installationId(installation) {
|
|
483
|
+
return typeof installation === "string" ? installation : installation.id;
|
|
484
|
+
}
|
|
485
|
+
function setupOrigin({ options, baseUrl, instance, origin: context, fetcher, createFetcher, derive, state: credentials }) {
|
|
486
|
+
const repoPath = (repo) => `/repos/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.name)}`;
|
|
487
|
+
const pullPath = (ref) => `${repoPath(ref.repo)}/pulls/${ref.number}`;
|
|
488
|
+
async function tokenPage(path, field, listOptions, from = fetcher) {
|
|
489
|
+
const { data } = await from.json(path, {
|
|
490
|
+
query: {
|
|
491
|
+
...listOptions.query,
|
|
492
|
+
pageSize: Math.min(listOptions.perPage ?? PAGE_SIZE, 100),
|
|
493
|
+
pageToken: listOptions.cursor?.token
|
|
494
|
+
},
|
|
495
|
+
signal: listOptions.signal
|
|
496
|
+
});
|
|
497
|
+
const next = data.nextPageToken;
|
|
498
|
+
return {
|
|
499
|
+
items: data[field] ?? [],
|
|
500
|
+
cursor: next ? { token: next } : void 0
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
function all(path, field, options = {}) {
|
|
504
|
+
return Array.fromAsync(iteratePages((page) => tokenPage(path, field, {
|
|
505
|
+
...page,
|
|
506
|
+
signal: options.signal
|
|
507
|
+
})));
|
|
508
|
+
}
|
|
509
|
+
async function headChecks(repo, sha, signal) {
|
|
510
|
+
return (await all(`${repoPath(repo)}/commits/${sha}/check-runs`, "checkRuns", { signal })).map((raw) => toCheck(repo, raw));
|
|
511
|
+
}
|
|
512
|
+
async function get(thread, options) {
|
|
513
|
+
const ref = requireThread(thread, context);
|
|
514
|
+
const { data } = await fetcher.json(pullPath(ref), { signal: options?.signal });
|
|
515
|
+
const result = toThread(ref, data);
|
|
516
|
+
const sha = data.version?.headSha ?? data.head?.sha;
|
|
517
|
+
if (sha) try {
|
|
518
|
+
result.checks = summariseChecks((await headChecks(ref.repo, sha, options?.signal)).map((check) => check.state));
|
|
519
|
+
} catch (error) {
|
|
520
|
+
if (!(error instanceof ForgeError)) throw error;
|
|
521
|
+
result.warnings = [...result.warnings ?? [], toWarning("checks_unreadable", error, ref.number)];
|
|
522
|
+
}
|
|
523
|
+
return result;
|
|
524
|
+
}
|
|
525
|
+
const VERDICTS = {
|
|
526
|
+
approve: "approve",
|
|
527
|
+
request_changes: "request_changes",
|
|
528
|
+
comment: "comment"
|
|
529
|
+
};
|
|
530
|
+
async function createReview(thread, input, options) {
|
|
531
|
+
const ref = requireThread(thread, context);
|
|
532
|
+
if (!input.event) throw new UnsupportedOperationError("Cursor Origin has no pending reviews; pass an event", context);
|
|
533
|
+
if (input.comments?.length) throw new UnsupportedOperationError("Cursor Origin takes inline comments as their own requests, not with a review", context);
|
|
534
|
+
const { data } = await fetcher.json(`${pullPath(ref)}/reviews`, {
|
|
535
|
+
method: "POST",
|
|
536
|
+
json: {
|
|
537
|
+
verdict: VERDICTS[input.event],
|
|
538
|
+
body: input.body ?? ""
|
|
539
|
+
},
|
|
540
|
+
signal: options?.signal
|
|
541
|
+
});
|
|
542
|
+
return toReview(ref, data);
|
|
543
|
+
}
|
|
544
|
+
async function setThreadResolved(id, resolved, options) {
|
|
545
|
+
await fetcher.raw(`/pulls/threads/${encodeURIComponent(id)}`, {
|
|
546
|
+
method: "PATCH",
|
|
547
|
+
json: { resolved },
|
|
548
|
+
signal: options?.signal
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
async function listPage(repo, query = {}) {
|
|
552
|
+
if (query.kind === "issue" || query.kind === "discussion") return {
|
|
553
|
+
items: [],
|
|
554
|
+
warnings: [{
|
|
555
|
+
code: "kind_unsupported",
|
|
556
|
+
message: `Cursor Origin has pull requests only, no ${query.kind}s`
|
|
557
|
+
}]
|
|
558
|
+
};
|
|
559
|
+
const warnings = [];
|
|
560
|
+
if (!query.cursor) {
|
|
561
|
+
if (query.labels?.length || query.assignee) warnings.push({
|
|
562
|
+
code: "filter_unsupported",
|
|
563
|
+
message: "Cursor Origin cannot filter pull requests by label or assignee; those filters were ignored"
|
|
564
|
+
});
|
|
565
|
+
if (query.sort === "comments") warnings.push({
|
|
566
|
+
code: "sort_unsupported",
|
|
567
|
+
message: "Cursor Origin cannot sort by comment count; sorted by creation time"
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
const page = await tokenPage(`${repoPath(repo)}/pulls`, "pullRequests", {
|
|
571
|
+
...query,
|
|
572
|
+
query: {
|
|
573
|
+
state: query.state === "merged" ? "closed" : query.state ?? "open",
|
|
574
|
+
author: query.author,
|
|
575
|
+
sortBy: query.sort === "updated" ? "updated" : "created",
|
|
576
|
+
direction: query.direction ?? "desc",
|
|
577
|
+
since: query.createdAfter?.toISOString()
|
|
578
|
+
}
|
|
579
|
+
});
|
|
580
|
+
const since = query.since?.getTime();
|
|
581
|
+
return {
|
|
582
|
+
items: page.items.map((raw) => toThread({
|
|
583
|
+
forge: FORGE,
|
|
584
|
+
instance,
|
|
585
|
+
repo,
|
|
586
|
+
kind: "pull_request",
|
|
587
|
+
number: raw.number
|
|
588
|
+
}, raw)).filter((thread) => (since === void 0 || (thread.updatedAt?.getTime() ?? 0) >= since) && (query.state !== "merged" || thread.state === "merged")),
|
|
589
|
+
cursor: page.cursor,
|
|
590
|
+
...warnings.length ? { warnings } : {}
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
async function commentsPage(thread, listOptions = {}) {
|
|
594
|
+
const ref = requireThread(thread, context);
|
|
595
|
+
const page = await tokenPage(`${pullPath(ref)}/comments`, "comments", listOptions);
|
|
596
|
+
return {
|
|
597
|
+
items: page.items.filter(isConversationComment).map((raw) => toComment(ref, raw)),
|
|
598
|
+
cursor: page.cursor
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
async function eventsPage(thread, listOptions = {}) {
|
|
602
|
+
const ref = requireThread(thread, context);
|
|
603
|
+
const [comments, reviews] = await Promise.all([all(`${pullPath(ref)}/comments`, "comments", listOptions), all(`${pullPath(ref)}/reviews`, "reviews", listOptions)]);
|
|
604
|
+
return { items: [...comments.map((raw) => toCommentEvent(ref, raw)), ...reviews.filter((review) => review.submittedAt).map((raw) => toReviewEvent(ref, raw))].sort((a, b) => a.occurredAt.getTime() - b.occurredAt.getTime()) };
|
|
605
|
+
}
|
|
606
|
+
async function reposPage(listOptions = {}, from = fetcher) {
|
|
607
|
+
const page = await tokenPage("/installation/repos", "repositories", listOptions, from);
|
|
608
|
+
return {
|
|
609
|
+
items: page.items.map((raw) => toRepo(instance, raw)),
|
|
610
|
+
cursor: page.cursor
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
/** A user token has no single repository listing, so each namespace the user belongs to is listed in turn. */
|
|
614
|
+
async function userReposPage(listOptions = {}) {
|
|
615
|
+
const namespaces = await all("/namespaces", "namespaces", listOptions);
|
|
616
|
+
if (!namespaces.length) return { items: [] };
|
|
617
|
+
return phased(namespaces.map((namespace) => async (cursor) => {
|
|
618
|
+
const page = await tokenPage(`/repos/${encodeURIComponent(namespace.slug)}`, "repositories", {
|
|
619
|
+
...listOptions,
|
|
620
|
+
cursor
|
|
621
|
+
});
|
|
622
|
+
return {
|
|
623
|
+
items: page.items.map((raw) => toRepo(instance, raw)),
|
|
624
|
+
cursor: page.cursor
|
|
625
|
+
};
|
|
626
|
+
}), listOptions.cursor);
|
|
627
|
+
}
|
|
628
|
+
function createInstallationsApi(app) {
|
|
629
|
+
const appFetcher = createFetcher({
|
|
630
|
+
baseUrl,
|
|
631
|
+
authHeaders: async () => ({ authorization: `Bearer ${await app.appJwt()}` })
|
|
632
|
+
});
|
|
633
|
+
const auth = options.auth;
|
|
634
|
+
const toInstallation = (raw) => ({
|
|
635
|
+
forge: FORGE,
|
|
636
|
+
instance,
|
|
637
|
+
id: raw.id,
|
|
638
|
+
targetTypeRaw: raw.target?.type,
|
|
639
|
+
repositorySelectionRaw: raw.repoSelectionMode,
|
|
640
|
+
...raw.target ? { account: {
|
|
641
|
+
forge: FORGE,
|
|
642
|
+
instance,
|
|
643
|
+
login: raw.target.slug,
|
|
644
|
+
id: raw.target.id ?? raw.target.slug,
|
|
645
|
+
typeRaw: raw.target.type,
|
|
646
|
+
isBotHint: false
|
|
647
|
+
} } : {},
|
|
648
|
+
raw
|
|
649
|
+
});
|
|
650
|
+
const provider = (installation) => derive({
|
|
651
|
+
...options,
|
|
652
|
+
auth: {
|
|
653
|
+
...auth,
|
|
654
|
+
installationId: installationId(installation)
|
|
655
|
+
}
|
|
656
|
+
}, app);
|
|
657
|
+
const listPage = async (listOptions = {}) => {
|
|
658
|
+
const page = await tokenPage("/app/installations", "installations", listOptions, appFetcher);
|
|
659
|
+
return {
|
|
660
|
+
items: page.items.map(toInstallation),
|
|
661
|
+
cursor: page.cursor
|
|
662
|
+
};
|
|
663
|
+
};
|
|
664
|
+
return {
|
|
665
|
+
listPage,
|
|
666
|
+
get: async (installation, options) => toInstallation((await appFetcher.json(`/app/installations/${installationId(installation)}`, { signal: options?.signal })).data),
|
|
667
|
+
token: (installation, options) => app.installationTokenDetails(installationId(installation), options),
|
|
668
|
+
reposPage(installation, listOptions) {
|
|
669
|
+
const id = installationId(installation);
|
|
670
|
+
return reposPage(listOptions, createFetcher({
|
|
671
|
+
baseUrl,
|
|
672
|
+
authHeaders: async () => ({ authorization: `Bearer ${await app.installationToken(id)}` })
|
|
673
|
+
}));
|
|
674
|
+
},
|
|
675
|
+
provider,
|
|
676
|
+
providers: () => forgeIterable(async function* () {
|
|
677
|
+
for await (const installation of iteratePages(listPage)) yield {
|
|
678
|
+
installation,
|
|
679
|
+
provider: provider(installation)
|
|
680
|
+
};
|
|
681
|
+
})
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
const auth = options.auth;
|
|
685
|
+
const repoAccess = auth?.type === "token" || auth?.type === "app" && auth.installationId !== void 0;
|
|
686
|
+
return {
|
|
687
|
+
checks: { list: verb("unverified", async (repo, sha, options) => ({ items: await headChecks(repo, sha, options?.signal) })) },
|
|
688
|
+
contents: {
|
|
689
|
+
file: verb("unverified", async (repo, path, fileOptions = {}) => {
|
|
690
|
+
const { data } = await fetcher.json(`${repoPath(repo)}/contents`, {
|
|
691
|
+
query: {
|
|
692
|
+
path,
|
|
693
|
+
ref: fileOptions.ref
|
|
694
|
+
},
|
|
695
|
+
signal: fileOptions.signal
|
|
696
|
+
});
|
|
697
|
+
const file = {
|
|
698
|
+
path: data.path,
|
|
699
|
+
sha: data.sha,
|
|
700
|
+
size: data.size === void 0 ? void 0 : Number(data.size)
|
|
701
|
+
};
|
|
702
|
+
const content = data.content ?? (data.sha ? (await fetcher.json(`${repoPath(repo)}/git/blobs/${data.sha}`, { signal: fileOptions.signal })).data.content : "");
|
|
703
|
+
return toFileContent(fromBase64(content), file, fileOptions, context);
|
|
704
|
+
}),
|
|
705
|
+
treePage: verb("unverified", async (repo, treeOptions = {}) => {
|
|
706
|
+
const ref = treeOptions.ref ?? "HEAD";
|
|
707
|
+
const { data } = await fetcher.json(`${repoPath(repo)}/git/trees/${encodeURIComponent(ref)}`, {
|
|
708
|
+
query: { recursive: treeOptions.recursive ? "true" : void 0 },
|
|
709
|
+
signal: treeOptions.signal
|
|
710
|
+
});
|
|
711
|
+
const prefix = treeOptions.path?.replace(/^\/|\/$/g, "");
|
|
712
|
+
return {
|
|
713
|
+
items: (data.tree ?? []).filter((entry) => !prefix || entry.path.startsWith(`${prefix}/`)).map(toTreeEntry),
|
|
714
|
+
...data.truncated ? { warnings: [{
|
|
715
|
+
code: "tree_truncated",
|
|
716
|
+
message: "Origin truncated the tree; read subtrees non-recursively instead",
|
|
717
|
+
subject: ref
|
|
718
|
+
}] } : {}
|
|
719
|
+
};
|
|
720
|
+
}),
|
|
721
|
+
branchesPage: verb("unverified", async (repo, listOptions = {}) => {
|
|
722
|
+
const page = await tokenPage(`${repoPath(repo)}/branches`, "branches", listOptions);
|
|
723
|
+
return {
|
|
724
|
+
items: page.items.map(toBranch),
|
|
725
|
+
cursor: page.cursor
|
|
726
|
+
};
|
|
727
|
+
}),
|
|
728
|
+
tagsPage: verb("unverified", async (repo, listOptions = {}) => {
|
|
729
|
+
const { data } = await fetcher.json(`${repoPath(repo)}/git/matching-refs`, {
|
|
730
|
+
query: { ref: "tags/" },
|
|
731
|
+
signal: listOptions.signal
|
|
732
|
+
});
|
|
733
|
+
return { items: (data ?? []).map(toTag) };
|
|
734
|
+
}),
|
|
735
|
+
resolveRef: verb("unverified", async (repo, ref, options) => (await fetcher.json(`${repoPath(repo)}/commits/${encodeURIComponent(ref)}`, { signal: options?.signal })).data.sha),
|
|
736
|
+
commitsPage: verb("unverified", async (repo, query = {}) => {
|
|
737
|
+
const page = await tokenPage(`${repoPath(repo)}/commits`, "commits", {
|
|
738
|
+
...query,
|
|
739
|
+
query: {
|
|
740
|
+
sha: query.ref,
|
|
741
|
+
authorEmails: query.author,
|
|
742
|
+
since: query.since?.toISOString()
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
return {
|
|
746
|
+
items: page.items.map((raw) => toCommit(repo, raw)),
|
|
747
|
+
cursor: page.cursor
|
|
748
|
+
};
|
|
749
|
+
}),
|
|
750
|
+
commit: verb("unverified", async (repo, sha, options) => {
|
|
751
|
+
const [commit, files] = await Promise.all([fetcher.json(`${repoPath(repo)}/commits/${sha}`, { signal: options?.signal }), all(`${repoPath(repo)}/commits/${sha}/files`, "files", options)]);
|
|
752
|
+
return toCommit(repo, commit.data, files.map(toChangedFile));
|
|
753
|
+
}),
|
|
754
|
+
compare: verb("unverified", async (repo, base, head, options) => {
|
|
755
|
+
const basehead = encodeURIComponent(`${base}...${head}`);
|
|
756
|
+
const [comparison, files] = await Promise.all([fetcher.json(`${repoPath(repo)}/compare/${basehead}`, { signal: options?.signal }), all(`${repoPath(repo)}/compare/${basehead}/files`, "files", options)]);
|
|
757
|
+
return {
|
|
758
|
+
base,
|
|
759
|
+
head,
|
|
760
|
+
aheadBy: comparison.data.aheadBy,
|
|
761
|
+
behindBy: comparison.data.behindBy,
|
|
762
|
+
mergeBaseSha: comparison.data.mergeBaseCommit?.sha,
|
|
763
|
+
commits: [],
|
|
764
|
+
files: files.map(toChangedFile),
|
|
765
|
+
raw: comparison.data
|
|
766
|
+
};
|
|
767
|
+
})
|
|
768
|
+
},
|
|
769
|
+
traits: {
|
|
770
|
+
eventKinds: "native",
|
|
771
|
+
authKinds: [
|
|
772
|
+
"token",
|
|
773
|
+
"app",
|
|
774
|
+
"anonymous"
|
|
775
|
+
]
|
|
776
|
+
},
|
|
777
|
+
repos: {
|
|
778
|
+
get: verb("experimental", async (ref, options) => toRepo(instance, (await fetcher.json(repoPath(ref), { signal: options?.signal })).data)),
|
|
779
|
+
listPage: verb(repoAccess && "unverified", async (listOptions = {}) => {
|
|
780
|
+
if (auth?.type === "app") {
|
|
781
|
+
if (auth.installationId === void 0) throw new UnsupportedOperationError("An app without an installation has no repositories; use installations.repos()", context);
|
|
782
|
+
return reposPage(listOptions);
|
|
783
|
+
}
|
|
784
|
+
return userReposPage(listOptions);
|
|
785
|
+
})
|
|
786
|
+
},
|
|
787
|
+
installations: credentials && auth?.type === "app" && auth.installationId === void 0 ? verb("unverified", createInstallationsApi(credentials)) : void 0,
|
|
788
|
+
threads: {
|
|
789
|
+
get: perKind(PULL, get),
|
|
790
|
+
getMany: verb("experimental", (refs, options) => getManyConcurrently(refs, get, options)),
|
|
791
|
+
listPage: perKind(PULL, listPage),
|
|
792
|
+
eventsPage: verb("experimental", eventsPage),
|
|
793
|
+
filesPage: verb("unverified", async (thread, listOptions = {}) => {
|
|
794
|
+
const page = await tokenPage(`${pullPath(requireThread(thread, context))}/files`, "files", listOptions);
|
|
795
|
+
return {
|
|
796
|
+
items: page.items.map(toChangedFile),
|
|
797
|
+
cursor: page.cursor
|
|
798
|
+
};
|
|
799
|
+
}),
|
|
800
|
+
commitsPage: verb("unverified", async (thread, listOptions = {}) => {
|
|
801
|
+
const page = await tokenPage(`${pullPath(requireThread(thread, context))}/commits`, "commits", listOptions);
|
|
802
|
+
return {
|
|
803
|
+
items: page.items.map((raw) => toCommit(thread.repo, raw)),
|
|
804
|
+
cursor: page.cursor
|
|
805
|
+
};
|
|
806
|
+
}),
|
|
807
|
+
commentsPage: perKind(PULL, commentsPage),
|
|
808
|
+
comment: perKind(PULL, async (thread, body, options) => {
|
|
809
|
+
const ref = requireThread(thread, context);
|
|
810
|
+
return toComment(ref, (await fetcher.json(`${pullPath(ref)}/comments`, {
|
|
811
|
+
method: "POST",
|
|
812
|
+
json: { body },
|
|
813
|
+
signal: options?.signal
|
|
814
|
+
})).data);
|
|
815
|
+
}),
|
|
816
|
+
editComment: perKind(UNVERIFIED, async (ref, body, options) => toComment(ref.thread, (await fetcher.json(`${repoPath(ref.thread.repo)}/pulls/comments/${ref.id}`, {
|
|
817
|
+
method: "PATCH",
|
|
818
|
+
json: { body },
|
|
819
|
+
signal: options?.signal
|
|
820
|
+
})).data)),
|
|
821
|
+
deleteComment: perKind(UNVERIFIED, async (ref, options) => {
|
|
822
|
+
await fetcher.raw(`${repoPath(ref.thread.repo)}/pulls/comments/${ref.id}`, {
|
|
823
|
+
method: "DELETE",
|
|
824
|
+
signal: options?.signal
|
|
825
|
+
});
|
|
826
|
+
}),
|
|
827
|
+
create: perKind(UNVERIFIED, async (repo, input, options) => {
|
|
828
|
+
if (input.assignees?.length) throw new UnsupportedOperationError("Cursor Origin pull requests have no assignees", context);
|
|
829
|
+
if (!input.head || !input.base) throw new UnsupportedOperationError("Creating a pull request needs head and base branches", context);
|
|
830
|
+
const { data } = await fetcher.json(`${repoPath(repo)}/pulls`, {
|
|
831
|
+
method: "POST",
|
|
832
|
+
json: {
|
|
833
|
+
title: input.title,
|
|
834
|
+
body: input.body ?? "",
|
|
835
|
+
head: input.head,
|
|
836
|
+
base: input.base,
|
|
837
|
+
draft: input.draft ?? false
|
|
838
|
+
},
|
|
839
|
+
signal: options?.signal
|
|
840
|
+
});
|
|
841
|
+
const ref = {
|
|
842
|
+
forge: FORGE,
|
|
843
|
+
instance,
|
|
844
|
+
repo,
|
|
845
|
+
kind: "pull_request",
|
|
846
|
+
number: data.number
|
|
847
|
+
};
|
|
848
|
+
if (input.labels?.length) await fetcher.raw(`${pullPath(ref)}/labels`, {
|
|
849
|
+
method: "PUT",
|
|
850
|
+
json: { labels: input.labels },
|
|
851
|
+
signal: options?.signal
|
|
852
|
+
});
|
|
853
|
+
return toThread(ref, data);
|
|
854
|
+
}),
|
|
855
|
+
update: perKind(UNVERIFIED, async (thread, input, options) => {
|
|
856
|
+
const ref = requireThread(thread, context);
|
|
857
|
+
return toThread(ref, (await fetcher.json(pullPath(ref), {
|
|
858
|
+
method: "PATCH",
|
|
859
|
+
json: input,
|
|
860
|
+
signal: options?.signal
|
|
861
|
+
})).data);
|
|
862
|
+
}),
|
|
863
|
+
close: perKind(PULL, async (thread, options) => {
|
|
864
|
+
await fetcher.raw(pullPath(requireThread(thread, context)), {
|
|
865
|
+
method: "PATCH",
|
|
866
|
+
json: { state: "closed" },
|
|
867
|
+
signal: options?.signal
|
|
868
|
+
});
|
|
869
|
+
}),
|
|
870
|
+
reopen: perKind(PULL, async (thread, options) => {
|
|
871
|
+
await fetcher.raw(pullPath(requireThread(thread, context)), {
|
|
872
|
+
method: "PATCH",
|
|
873
|
+
json: { state: "open" },
|
|
874
|
+
signal: options?.signal
|
|
875
|
+
});
|
|
876
|
+
}),
|
|
877
|
+
setLabels: perKind(UNVERIFIED, async (thread, labels, options) => {
|
|
878
|
+
await fetcher.raw(`${pullPath(requireThread(thread, context))}/labels`, {
|
|
879
|
+
method: "PUT",
|
|
880
|
+
json: { labels },
|
|
881
|
+
signal: options?.signal
|
|
882
|
+
});
|
|
883
|
+
}),
|
|
884
|
+
requestReview: perKind(UNVERIFIED, async (thread, reviewers, options) => {
|
|
885
|
+
const users = reviewers.map((reviewer) => typeof reviewer === "string" ? reviewer : reviewer.id);
|
|
886
|
+
await fetcher.raw(`${pullPath(requireThread(thread, context))}/requested_reviewers`, {
|
|
887
|
+
method: "POST",
|
|
888
|
+
json: { users },
|
|
889
|
+
signal: options?.signal
|
|
890
|
+
});
|
|
891
|
+
}),
|
|
892
|
+
merge: verb("experimental", async (thread, mergeOptions = {}, hooks = {}) => {
|
|
893
|
+
const ref = requireThread(thread, context);
|
|
894
|
+
if (mergeOptions.whenChecksPass) throw new UnsupportedOperationError("Cursor Origin has no merge queue or auto-merge", context);
|
|
895
|
+
if (mergeOptions.title) throw new UnsupportedOperationError("Cursor Origin does not take a merge commit title", context);
|
|
896
|
+
if (mergeOptions.message) throw new UnsupportedOperationError("Cursor Origin does not take a merge commit message", context);
|
|
897
|
+
if (mergeOptions.method && mergeOptions.method !== "merge" && mergeOptions.method !== "squash") throw new UnsupportedOperationError(`Cursor Origin does not support the ${mergeOptions.method} merge method`, context);
|
|
898
|
+
let method = mergeOptions.method;
|
|
899
|
+
if (!method) {
|
|
900
|
+
const { data: repo } = await fetcher.json(repoPath(ref.repo), { signal: mergeOptions.signal });
|
|
901
|
+
method = soleMergeMethod({
|
|
902
|
+
merge: repo.allowMergeCommit,
|
|
903
|
+
squash: repo.allowSquashMerge
|
|
904
|
+
}, context);
|
|
905
|
+
}
|
|
906
|
+
await hooks.beforeMerge?.();
|
|
907
|
+
await fetcher.raw(`${pullPath(ref)}/merge`, {
|
|
908
|
+
method: "POST",
|
|
909
|
+
json: {
|
|
910
|
+
mergeMethod: method,
|
|
911
|
+
expectedHeadSha: mergeOptions.sha
|
|
912
|
+
},
|
|
913
|
+
mapError: toMergeError,
|
|
914
|
+
signal: mergeOptions.signal
|
|
915
|
+
});
|
|
916
|
+
}),
|
|
917
|
+
reviewsPage: verb("experimental", async (thread, listOptions = {}) => {
|
|
918
|
+
const ref = requireThread(thread, context);
|
|
919
|
+
const page = await tokenPage(`${pullPath(ref)}/reviews`, "reviews", listOptions);
|
|
920
|
+
return {
|
|
921
|
+
items: page.items.map((raw) => toReview(ref, raw)),
|
|
922
|
+
cursor: page.cursor
|
|
923
|
+
};
|
|
924
|
+
}),
|
|
925
|
+
createReview: verb("unverified", createReview),
|
|
926
|
+
reviewThreads: verb("unverified", {
|
|
927
|
+
resolveReviewThread: (_thread, id, options) => setThreadResolved(id, true, options),
|
|
928
|
+
unresolveReviewThread: (_thread, id, options) => setThreadResolved(id, false, options)
|
|
929
|
+
}),
|
|
930
|
+
checks: perKind(PULL, async (thread, options) => {
|
|
931
|
+
const ref = requireThread(thread, context);
|
|
932
|
+
const { data } = await fetcher.json(pullPath(ref), { signal: options?.signal });
|
|
933
|
+
const sha = data.version?.headSha ?? data.head?.sha;
|
|
934
|
+
return { items: sha ? await headChecks(ref.repo, sha, options?.signal) : [] };
|
|
935
|
+
})
|
|
936
|
+
},
|
|
937
|
+
webhooks: {},
|
|
938
|
+
scopes: () => ({ note: "An Origin app installation token; Origin has no per-verb scopes" })
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
const ORIGIN = {
|
|
942
|
+
forge: FORGE,
|
|
943
|
+
experimental: true,
|
|
944
|
+
anonymous: "webhooks",
|
|
945
|
+
baseUrl: "https://api.cursor.com",
|
|
946
|
+
apiPath: "/v1/origin",
|
|
947
|
+
instance: (host) => host === "api.cursor.com" ? "origin.cursor.com" : host,
|
|
948
|
+
headers: { accept: "application/json" },
|
|
949
|
+
prepare: ({ options, state, baseUrl, headers }) => options.auth?.type === "app" ? state ?? createOriginAppCredentials(options.auth, (authHeaders) => createFetcherFor(baseUrl, headers, options, authHeaders)) : void 0,
|
|
950
|
+
authHeaders: ({ options, state }) => {
|
|
951
|
+
const auth = options.auth;
|
|
952
|
+
if (!auth || auth.type === "anonymous") return;
|
|
953
|
+
if (auth.type === "token") return async () => ({ authorization: `Bearer ${await resolveToken(auth)}` });
|
|
954
|
+
const app = state;
|
|
955
|
+
return auth.installationId === void 0 ? async () => ({ authorization: `Bearer ${await app.appJwt()}` }) : async () => ({ authorization: `Bearer ${await app.installationToken(String(auth.installationId))}` });
|
|
956
|
+
},
|
|
957
|
+
setup: setupOrigin
|
|
958
|
+
};
|
|
959
|
+
/**
|
|
960
|
+
* Creates a Cursor Origin provider. Experimental: built against the
|
|
961
|
+
* `v1alpha1` API, which Cursor publishes as Early Beta.
|
|
962
|
+
*/
|
|
963
|
+
const cursorOrigin = /* @__PURE__ */ defineForgeProvider({
|
|
964
|
+
...ORIGIN,
|
|
965
|
+
webhooks: cursorOriginWebhooks
|
|
966
|
+
});
|
|
967
|
+
/** `cursorOrigin()` without webhook ingestion, for bundles that never receive a delivery. */
|
|
968
|
+
const cursorOriginLite = /* @__PURE__ */ defineForgeProvider(ORIGIN);
|
|
969
|
+
function createFetcherFor(baseUrl, headers, options, authHeaders) {
|
|
970
|
+
return createFetcher({
|
|
971
|
+
baseUrl,
|
|
972
|
+
headers,
|
|
973
|
+
fetch: options.fetch,
|
|
974
|
+
timeout: options.timeout,
|
|
975
|
+
authHeaders,
|
|
976
|
+
context: { forge: FORGE }
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
//#endregion
|
|
980
|
+
export { cursorOrigin as n, cursorOriginLite as r, ORIGIN_API_VERSION as t };
|