@sentry/junior-github 0.181.0 → 0.182.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/dist/chunk-JROTRF27.js +1731 -0
- package/dist/egress-policy.d.ts +11 -0
- package/dist/index.js +114 -763
- package/dist/plugin.d.ts +1 -1
- package/dist/resource-events/pull-request.d.ts +7 -1
- package/dist/testing.js +1 -1
- package/dist/webhooks/check-suite.d.ts +69 -0
- package/dist/webhooks/handler.d.ts +3 -2
- package/dist/webhooks/resource-events.d.ts +5 -37
- package/package.json +2 -2
- package/skills/github-code/SKILL.md +2 -2
- package/skills/github-code/SPEC.md +1 -1
- package/dist/chunk-GXZVNEIB.js +0 -825
- package/dist/webhooks/check-suite-enrichment.d.ts +0 -11
|
@@ -0,0 +1,1731 @@
|
|
|
1
|
+
// src/webhooks/resource-events.ts
|
|
2
|
+
import { z as z2 } from "zod";
|
|
3
|
+
|
|
4
|
+
// src/resource-events/deployment.ts
|
|
5
|
+
var GITHUB_DEPLOYMENT_EVENTS = [
|
|
6
|
+
"deployment.created",
|
|
7
|
+
"deployment.queued",
|
|
8
|
+
"deployment.pending",
|
|
9
|
+
"deployment.in_progress",
|
|
10
|
+
"deployment.succeeded",
|
|
11
|
+
"deployment.failed",
|
|
12
|
+
"deployment.error"
|
|
13
|
+
];
|
|
14
|
+
var GITHUB_DEPLOYMENT_SUGGESTED_EVENTS = [
|
|
15
|
+
"deployment.succeeded",
|
|
16
|
+
"deployment.failed",
|
|
17
|
+
"deployment.error"
|
|
18
|
+
];
|
|
19
|
+
function gitHubDeploymentSourceResource(input) {
|
|
20
|
+
const commitSha = input.commitSha.toLowerCase();
|
|
21
|
+
const environment = input.environment?.trim();
|
|
22
|
+
const repo = input.repo.toLowerCase();
|
|
23
|
+
return {
|
|
24
|
+
label: `GitHub deployment for ${repo} at ${commitSha.slice(0, 12)}`,
|
|
25
|
+
namespace: "github",
|
|
26
|
+
identifier: environment ? `deployment-source:${repo}:${encodeURIComponent(environment.toLowerCase())}:${commitSha}` : `deployment-source:${repo}:${commitSha}`
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function gitHubDeploymentSourceSubscribable(input) {
|
|
30
|
+
if (!process.env.GITHUB_WEBHOOK_SECRET?.trim()) return void 0;
|
|
31
|
+
return {
|
|
32
|
+
...gitHubDeploymentSourceResource(input),
|
|
33
|
+
suggestedEvents: GITHUB_DEPLOYMENT_SUGGESTED_EVENTS,
|
|
34
|
+
supportedEvents: [...GITHUB_DEPLOYMENT_EVENTS],
|
|
35
|
+
type: "deployment_source"
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/resource-events/issue.ts
|
|
40
|
+
var GITHUB_ISSUE_EVENTS = [
|
|
41
|
+
"issue.comment.created",
|
|
42
|
+
"issue.opened",
|
|
43
|
+
"issue.closed",
|
|
44
|
+
"issue.reopened"
|
|
45
|
+
];
|
|
46
|
+
var GITHUB_ISSUE_SUGGESTED_EVENTS = [
|
|
47
|
+
"issue.comment.created",
|
|
48
|
+
"issue.closed",
|
|
49
|
+
"issue.reopened"
|
|
50
|
+
];
|
|
51
|
+
function gitHubIssueResource(input) {
|
|
52
|
+
return {
|
|
53
|
+
label: `GitHub issue ${input.repo}#${input.number}`,
|
|
54
|
+
namespace: "github",
|
|
55
|
+
identifier: `${input.repo.toLowerCase()}#${input.number}`
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function gitHubIssueSubscribable(input) {
|
|
59
|
+
if (!process.env.GITHUB_WEBHOOK_SECRET?.trim()) return void 0;
|
|
60
|
+
return {
|
|
61
|
+
...gitHubIssueResource(input),
|
|
62
|
+
suggestedEvents: GITHUB_ISSUE_SUGGESTED_EVENTS,
|
|
63
|
+
supportedEvents: GITHUB_ISSUE_EVENTS,
|
|
64
|
+
type: "issue"
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/resource-events/pull-request.ts
|
|
69
|
+
import {
|
|
70
|
+
resourceEventMatchFieldsSchema
|
|
71
|
+
} from "@sentry/junior-plugin-api";
|
|
72
|
+
var GITHUB_PULL_REQUEST_EVENTS = [
|
|
73
|
+
"pull_request.checks.failed",
|
|
74
|
+
"pull_request.checks.recovered",
|
|
75
|
+
"pull_request.comment.created",
|
|
76
|
+
"pull_request.opened",
|
|
77
|
+
"pull_request.ready_for_review",
|
|
78
|
+
"pull_request.review.approved",
|
|
79
|
+
"pull_request.review.changes_requested",
|
|
80
|
+
"pull_request.review.commented",
|
|
81
|
+
"pull_request.review_comment.created",
|
|
82
|
+
"pull_request.merged",
|
|
83
|
+
"pull_request.closed_unmerged"
|
|
84
|
+
];
|
|
85
|
+
var GITHUB_PULL_REQUEST_SUGGESTED_EVENTS = [
|
|
86
|
+
"pull_request.checks.failed",
|
|
87
|
+
"pull_request.comment.created",
|
|
88
|
+
"pull_request.ready_for_review",
|
|
89
|
+
"pull_request.review.changes_requested",
|
|
90
|
+
"pull_request.review.commented",
|
|
91
|
+
"pull_request.review_comment.created",
|
|
92
|
+
"pull_request.merged",
|
|
93
|
+
"pull_request.closed_unmerged"
|
|
94
|
+
];
|
|
95
|
+
var GITHUB_PULL_REQUEST_MATCH_FIELDS = resourceEventMatchFieldsSchema.parse({
|
|
96
|
+
authorEmail: {
|
|
97
|
+
kind: "string",
|
|
98
|
+
description: "pull request author email when GitHub sends it"
|
|
99
|
+
},
|
|
100
|
+
authorUsername: {
|
|
101
|
+
kind: "string",
|
|
102
|
+
description: "pull request author login"
|
|
103
|
+
},
|
|
104
|
+
isDraft: {
|
|
105
|
+
kind: "boolean",
|
|
106
|
+
description: "true when the pull request is a draft"
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
function gitHubPullRequestResource(input) {
|
|
110
|
+
return {
|
|
111
|
+
label: `GitHub PR ${input.repo}#${input.number}`,
|
|
112
|
+
namespace: "github",
|
|
113
|
+
identifier: `${input.repo.toLowerCase()}#${input.number}`
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function gitHubPullRequestSubscribable(input) {
|
|
117
|
+
if (!process.env.GITHUB_WEBHOOK_SECRET?.trim()) return void 0;
|
|
118
|
+
const omitted = new Set(input.omitSuggestedEvents ?? []);
|
|
119
|
+
const suggestedEvents = GITHUB_PULL_REQUEST_SUGGESTED_EVENTS.filter(
|
|
120
|
+
(eventType) => !omitted.has(eventType)
|
|
121
|
+
);
|
|
122
|
+
return {
|
|
123
|
+
...gitHubPullRequestResource(input),
|
|
124
|
+
...suggestedEvents.length > 0 ? { suggestedEvents: [...suggestedEvents] } : void 0,
|
|
125
|
+
supportedEvents: [...GITHUB_PULL_REQUEST_EVENTS],
|
|
126
|
+
type: "pull_request"
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/resource-events/release.ts
|
|
131
|
+
var GITHUB_RELEASE_EVENTS = ["release.published"];
|
|
132
|
+
var GITHUB_RELEASE_SUGGESTED_EVENTS = ["release.published"];
|
|
133
|
+
function gitHubReleaseSourceResource(input) {
|
|
134
|
+
const repo = input.repo.toLowerCase();
|
|
135
|
+
const tag = input.tag?.trim();
|
|
136
|
+
return {
|
|
137
|
+
label: `GitHub release for ${repo}`,
|
|
138
|
+
namespace: "github",
|
|
139
|
+
identifier: tag ? `release-source:${repo}:${encodeURIComponent(tag)}` : `release-source:${repo}`
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function gitHubReleaseSourceSubscribable(input) {
|
|
143
|
+
if (!process.env.GITHUB_WEBHOOK_SECRET?.trim()) return void 0;
|
|
144
|
+
return {
|
|
145
|
+
...gitHubReleaseSourceResource(input),
|
|
146
|
+
suggestedEvents: GITHUB_RELEASE_SUGGESTED_EVENTS,
|
|
147
|
+
supportedEvents: [...GITHUB_RELEASE_EVENTS],
|
|
148
|
+
type: "release_source"
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/resource-events/repository.ts
|
|
153
|
+
function gitHubRepositoryResource(input) {
|
|
154
|
+
return {
|
|
155
|
+
label: `GitHub repository ${input.repo}`,
|
|
156
|
+
namespace: "github",
|
|
157
|
+
identifier: input.repo.toLowerCase()
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function gitHubRepositorySubscribable(input) {
|
|
161
|
+
if (!process.env.GITHUB_WEBHOOK_SECRET?.trim()) return void 0;
|
|
162
|
+
return {
|
|
163
|
+
...gitHubRepositoryResource(input),
|
|
164
|
+
suggestedEvents: [
|
|
165
|
+
"issue.opened",
|
|
166
|
+
"pull_request.opened",
|
|
167
|
+
...GITHUB_ISSUE_SUGGESTED_EVENTS,
|
|
168
|
+
...GITHUB_PULL_REQUEST_SUGGESTED_EVENTS
|
|
169
|
+
],
|
|
170
|
+
supportedEvents: [...GITHUB_ISSUE_EVENTS, ...GITHUB_PULL_REQUEST_EVENTS],
|
|
171
|
+
type: "repository"
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/webhooks/check-suite.ts
|
|
176
|
+
import { z } from "zod";
|
|
177
|
+
|
|
178
|
+
// src/credential-support.ts
|
|
179
|
+
import { createPrivateKey, createSign } from "crypto";
|
|
180
|
+
|
|
181
|
+
// src/permissions.ts
|
|
182
|
+
var LEVELS = /* @__PURE__ */ new Set(["read", "write", "admin"]);
|
|
183
|
+
var WRITE_ONLY_PERMISSIONS = /* @__PURE__ */ new Set(["profile", "workflows"]);
|
|
184
|
+
function isLevel(value) {
|
|
185
|
+
return LEVELS.has(value);
|
|
186
|
+
}
|
|
187
|
+
function normalizeScope(rawScope) {
|
|
188
|
+
return String(rawScope).trim().replace(/-/g, "_");
|
|
189
|
+
}
|
|
190
|
+
function normalizePermissions(permissions) {
|
|
191
|
+
if (permissions === void 0) {
|
|
192
|
+
return void 0;
|
|
193
|
+
}
|
|
194
|
+
const entries = Object.entries(permissions);
|
|
195
|
+
if (entries.length === 0) {
|
|
196
|
+
throw new Error(
|
|
197
|
+
"githubPlugin appPermissions must contain at least one permission when provided."
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
const request = {};
|
|
201
|
+
for (const [rawScope, rawLevel] of entries) {
|
|
202
|
+
const normalizedScope = normalizeScope(rawScope);
|
|
203
|
+
if (!normalizedScope) {
|
|
204
|
+
throw new Error(
|
|
205
|
+
"githubPlugin appPermissions contains an empty permission name."
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
if (!/^[a-z][a-z0-9_]*$/.test(normalizedScope)) {
|
|
209
|
+
throw new Error(
|
|
210
|
+
`githubPlugin appPermissions contains invalid permission "${rawScope}".`
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
if (!isLevel(rawLevel)) {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`githubPlugin appPermissions.${rawScope} must be "read", "write", or "admin".`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
request[normalizedScope] = rawLevel;
|
|
219
|
+
}
|
|
220
|
+
return request;
|
|
221
|
+
}
|
|
222
|
+
function readGrantPermissions(permissions) {
|
|
223
|
+
const readOnly = { metadata: "read" };
|
|
224
|
+
for (const [scope, level] of Object.entries(permissions ?? {})) {
|
|
225
|
+
if (!isLevel(level)) {
|
|
226
|
+
throw new Error(
|
|
227
|
+
`GitHub permission "${scope}" returned invalid level "${String(level)}".`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
if (!WRITE_ONLY_PERMISSIONS.has(scope)) {
|
|
231
|
+
readOnly[scope] = "read";
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return readOnly;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/credential-support.ts
|
|
238
|
+
var GITHUB_APP_ID_ENV = "GITHUB_APP_ID";
|
|
239
|
+
var GITHUB_APP_PRIVATE_KEY_ENV = "GITHUB_APP_PRIVATE_KEY";
|
|
240
|
+
var GITHUB_INSTALLATION_ID_ENV = "GITHUB_INSTALLATION_ID";
|
|
241
|
+
var GITHUB_AUTH_TOKEN_ENV = "GITHUB_TOKEN";
|
|
242
|
+
var GITHUB_AUTH_TOKEN_PLACEHOLDER = "ghp_host_managed_credential";
|
|
243
|
+
var MAX_LEASE_MS = 60 * 60 * 1e3;
|
|
244
|
+
var REFRESH_BUFFER_MS = 5 * 60 * 1e3;
|
|
245
|
+
var USER_REFRESH_TIMEOUT_MS = 2e4;
|
|
246
|
+
var GITHUB_GRAPHQL_RESPONSE_BODY_LIMIT_BYTES = 64 * 1024;
|
|
247
|
+
var HTTP_READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
248
|
+
var USER_TOKEN_GRANTS = /* @__PURE__ */ new Set(["user-read", "user-write"]);
|
|
249
|
+
var CREATE_TOOL_ROUTING_GUIDANCE = "This is a Junior tool-routing denial, not a GitHub permission failure. Do not ask the user for GitHub permissions; retry with the required Junior tool.";
|
|
250
|
+
var USER_WRITE_REQUIREMENTS = [
|
|
251
|
+
"requesting GitHub user permission to perform this operation"
|
|
252
|
+
];
|
|
253
|
+
var GITHUB_CREDENTIAL_DOMAINS = ["api.github.com", "github.com"];
|
|
254
|
+
var GITHUB_ASSET_UPLOAD_CREDENTIAL_DOMAINS = [
|
|
255
|
+
...GITHUB_CREDENTIAL_DOMAINS,
|
|
256
|
+
"uploads.github.com"
|
|
257
|
+
];
|
|
258
|
+
var GitHubUserRefreshRejectedError = class extends Error {
|
|
259
|
+
constructor(message) {
|
|
260
|
+
super(message);
|
|
261
|
+
this.name = "GitHubUserRefreshRejectedError";
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
var GitHubRequestError = class extends Error {
|
|
265
|
+
status;
|
|
266
|
+
constructor(message, status) {
|
|
267
|
+
super(message);
|
|
268
|
+
this.name = "GitHubRequestError";
|
|
269
|
+
this.status = status;
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
var GitHubPluginSetupError = class extends Error {
|
|
273
|
+
constructor(message) {
|
|
274
|
+
super(message);
|
|
275
|
+
this.name = "GitHubPluginSetupError";
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
function isRecord(value) {
|
|
279
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
280
|
+
}
|
|
281
|
+
function readEnv(name) {
|
|
282
|
+
const value = process.env[name];
|
|
283
|
+
if (typeof value !== "string") {
|
|
284
|
+
return void 0;
|
|
285
|
+
}
|
|
286
|
+
const trimmed = value.trim();
|
|
287
|
+
return trimmed ? trimmed : void 0;
|
|
288
|
+
}
|
|
289
|
+
function requireEnv(name) {
|
|
290
|
+
const value = readEnv(name);
|
|
291
|
+
if (!value) {
|
|
292
|
+
throw new GitHubPluginSetupError(`Missing ${name}`);
|
|
293
|
+
}
|
|
294
|
+
return value;
|
|
295
|
+
}
|
|
296
|
+
function normalizeScopeList(scopes) {
|
|
297
|
+
return [
|
|
298
|
+
...new Set(
|
|
299
|
+
(scopes ?? []).flatMap((scope) => String(scope).split(/\s+/)).map((scope) => scope.trim()).filter(Boolean)
|
|
300
|
+
)
|
|
301
|
+
].sort();
|
|
302
|
+
}
|
|
303
|
+
function normalizeOAuthScope(scope) {
|
|
304
|
+
const normalized = normalizeScopeList(scope ? [scope] : []);
|
|
305
|
+
return normalized.length ? normalized.join(" ") : void 0;
|
|
306
|
+
}
|
|
307
|
+
function hasRequiredOAuthScope(storedScope, requiredScope) {
|
|
308
|
+
const required = normalizeScopeList(requiredScope ? [requiredScope] : []);
|
|
309
|
+
if (required.length === 0) {
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
const stored = new Set(normalizeScopeList(storedScope ? [storedScope] : []));
|
|
313
|
+
if (stored.size === 0) {
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
return required.every((scope) => stored.has(scope));
|
|
317
|
+
}
|
|
318
|
+
function isGitHubApiUrl(upstreamUrl) {
|
|
319
|
+
return upstreamUrl.hostname.toLowerCase() === "api.github.com";
|
|
320
|
+
}
|
|
321
|
+
function base64Url(input) {
|
|
322
|
+
return Buffer.from(input).toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
323
|
+
}
|
|
324
|
+
function getPrivateKey(envName) {
|
|
325
|
+
const raw = requireEnv(envName);
|
|
326
|
+
let key;
|
|
327
|
+
try {
|
|
328
|
+
key = createPrivateKey({ key: raw, format: "pem" });
|
|
329
|
+
} catch {
|
|
330
|
+
throw new GitHubPluginSetupError(
|
|
331
|
+
`Invalid ${envName}: expected a PEM-encoded RSA private key`
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
if (key.asymmetricKeyType !== "rsa") {
|
|
335
|
+
throw new GitHubPluginSetupError(
|
|
336
|
+
`Invalid ${envName}: GitHub App signing requires an RSA private key`
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
return key;
|
|
340
|
+
}
|
|
341
|
+
function createAppJwt(appId, privateKeyEnv) {
|
|
342
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
343
|
+
const header = { alg: "RS256", typ: "JWT" };
|
|
344
|
+
const payload = { iat: now - 60, exp: now + 9 * 60, iss: appId };
|
|
345
|
+
const encodedHeader = base64Url(JSON.stringify(header));
|
|
346
|
+
const encodedPayload = base64Url(JSON.stringify(payload));
|
|
347
|
+
const signingInput = `${encodedHeader}.${encodedPayload}`;
|
|
348
|
+
const signer = createSign("RSA-SHA256");
|
|
349
|
+
signer.update(signingInput);
|
|
350
|
+
signer.end();
|
|
351
|
+
const signature = signer.sign(getPrivateKey(privateKeyEnv)).toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
352
|
+
return `${signingInput}.${signature}`;
|
|
353
|
+
}
|
|
354
|
+
async function githubRequest(apiBase, path, params) {
|
|
355
|
+
const response = await fetch(`${apiBase}${path}`, {
|
|
356
|
+
method: params.method ?? "GET",
|
|
357
|
+
headers: {
|
|
358
|
+
Accept: "application/vnd.github+json",
|
|
359
|
+
Authorization: `Bearer ${params.token}`,
|
|
360
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
361
|
+
...params.body ? { "Content-Type": "application/json" } : void 0
|
|
362
|
+
},
|
|
363
|
+
...params.body ? { body: JSON.stringify(params.body) } : void 0
|
|
364
|
+
});
|
|
365
|
+
const text = await response.text();
|
|
366
|
+
let parsed;
|
|
367
|
+
if (text) {
|
|
368
|
+
try {
|
|
369
|
+
parsed = JSON.parse(text);
|
|
370
|
+
} catch {
|
|
371
|
+
parsed = void 0;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (!response.ok) {
|
|
375
|
+
const message = isRecord(parsed) && typeof parsed.message === "string" ? parsed.message : `GitHub API error ${response.status}`;
|
|
376
|
+
throw new GitHubRequestError(message, response.status);
|
|
377
|
+
}
|
|
378
|
+
return parsed;
|
|
379
|
+
}
|
|
380
|
+
function buildOAuthTokenRequest(input) {
|
|
381
|
+
const payload = {
|
|
382
|
+
...input.payload,
|
|
383
|
+
client_id: input.clientId,
|
|
384
|
+
client_secret: input.clientSecret
|
|
385
|
+
};
|
|
386
|
+
return {
|
|
387
|
+
headers: {
|
|
388
|
+
Accept: "application/json",
|
|
389
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
390
|
+
},
|
|
391
|
+
body: new URLSearchParams(payload)
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
function parseOAuthResponseJson(responseText) {
|
|
395
|
+
if (!responseText.trim()) {
|
|
396
|
+
return void 0;
|
|
397
|
+
}
|
|
398
|
+
try {
|
|
399
|
+
return JSON.parse(responseText);
|
|
400
|
+
} catch {
|
|
401
|
+
return void 0;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function oauthErrorCode(data) {
|
|
405
|
+
return isRecord(data) && typeof data.error === "string" ? data.error : void 0;
|
|
406
|
+
}
|
|
407
|
+
function isRejectedRefreshError(errorCode) {
|
|
408
|
+
return errorCode === "bad_refresh_token" || errorCode === "invalid_grant";
|
|
409
|
+
}
|
|
410
|
+
function parseOAuthTokenResponse(data, requestedScope) {
|
|
411
|
+
if (!isRecord(data)) {
|
|
412
|
+
throw new Error("OAuth token response is invalid");
|
|
413
|
+
}
|
|
414
|
+
if (typeof data.access_token !== "string" || !data.access_token.trim()) {
|
|
415
|
+
throw new Error("OAuth token response missing access_token");
|
|
416
|
+
}
|
|
417
|
+
if (typeof data.refresh_token !== "string" || !data.refresh_token.trim()) {
|
|
418
|
+
throw new Error("OAuth token response missing refresh_token");
|
|
419
|
+
}
|
|
420
|
+
let scope = normalizeOAuthScope(requestedScope);
|
|
421
|
+
if (data.scope !== void 0) {
|
|
422
|
+
if (typeof data.scope !== "string") {
|
|
423
|
+
throw new Error("OAuth token response returned invalid scope");
|
|
424
|
+
}
|
|
425
|
+
scope = normalizeOAuthScope(data.scope) ?? scope;
|
|
426
|
+
}
|
|
427
|
+
const result = {
|
|
428
|
+
accessToken: data.access_token,
|
|
429
|
+
refreshToken: data.refresh_token,
|
|
430
|
+
...scope ? { scope } : void 0
|
|
431
|
+
};
|
|
432
|
+
if (data.expires_in !== void 0) {
|
|
433
|
+
if (typeof data.expires_in !== "number" || !Number.isFinite(data.expires_in) || data.expires_in <= 0) {
|
|
434
|
+
throw new Error("OAuth token response returned invalid expires_in");
|
|
435
|
+
}
|
|
436
|
+
result.expiresAt = Date.now() + data.expires_in * 1e3;
|
|
437
|
+
}
|
|
438
|
+
if (data.refresh_token_expires_in !== void 0) {
|
|
439
|
+
if (typeof data.refresh_token_expires_in !== "number" || !Number.isFinite(data.refresh_token_expires_in) || data.refresh_token_expires_in <= 0) {
|
|
440
|
+
throw new Error(
|
|
441
|
+
"OAuth token response returned invalid refresh_token_expires_in"
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
result.refreshTokenExpiresAt = Date.now() + data.refresh_token_expires_in * 1e3;
|
|
445
|
+
}
|
|
446
|
+
return result;
|
|
447
|
+
}
|
|
448
|
+
async function refreshUserAccessToken(input) {
|
|
449
|
+
const clientId = requireEnv(input.clientIdEnv);
|
|
450
|
+
const clientSecret = requireEnv(input.clientSecretEnv);
|
|
451
|
+
const request = buildOAuthTokenRequest({
|
|
452
|
+
clientId,
|
|
453
|
+
clientSecret,
|
|
454
|
+
payload: {
|
|
455
|
+
grant_type: "refresh_token",
|
|
456
|
+
refresh_token: input.refreshToken
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
const response = await fetch("https://github.com/login/oauth/access_token", {
|
|
460
|
+
method: "POST",
|
|
461
|
+
headers: request.headers,
|
|
462
|
+
body: request.body,
|
|
463
|
+
signal: AbortSignal.timeout(USER_REFRESH_TIMEOUT_MS)
|
|
464
|
+
});
|
|
465
|
+
const responseText = await response.text();
|
|
466
|
+
const responseData = parseOAuthResponseJson(responseText);
|
|
467
|
+
const errorCode = oauthErrorCode(responseData);
|
|
468
|
+
if (isRejectedRefreshError(errorCode)) {
|
|
469
|
+
throw new GitHubUserRefreshRejectedError(
|
|
470
|
+
`GitHub user token refresh rejected: ${errorCode}`
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
if (!response.ok || errorCode) {
|
|
474
|
+
throw new Error(
|
|
475
|
+
`GitHub user token refresh failed: ${response.status}${errorCode ? ` ${errorCode}` : ""}`
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
try {
|
|
479
|
+
return parseOAuthTokenResponse(responseData, input.requestedScope);
|
|
480
|
+
} catch (error) {
|
|
481
|
+
if (error instanceof Error && error.message === "OAuth token response missing access_token") {
|
|
482
|
+
throw new GitHubUserRefreshRejectedError(error.message);
|
|
483
|
+
}
|
|
484
|
+
throw error;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function leaseExpiry(expiresAt) {
|
|
488
|
+
return expiresAt ? Math.min(expiresAt, Date.now() + MAX_LEASE_MS) : Date.now() + MAX_LEASE_MS;
|
|
489
|
+
}
|
|
490
|
+
function isGitSmartHttpDomain(domain) {
|
|
491
|
+
return domain.toLowerCase() === "github.com";
|
|
492
|
+
}
|
|
493
|
+
function authorizationFor(domain, token) {
|
|
494
|
+
if (isGitSmartHttpDomain(domain)) {
|
|
495
|
+
return `Basic ${Buffer.from(`x-access-token:${token}`).toString("base64")}`;
|
|
496
|
+
}
|
|
497
|
+
return `Bearer ${token}`;
|
|
498
|
+
}
|
|
499
|
+
function createCredentialLease(input) {
|
|
500
|
+
return {
|
|
501
|
+
type: "lease",
|
|
502
|
+
lease: {
|
|
503
|
+
...input.account ? { account: input.account } : void 0,
|
|
504
|
+
...input.authorization ? { authorization: input.authorization } : void 0,
|
|
505
|
+
expiresAt: new Date(input.expiresAtMs).toISOString(),
|
|
506
|
+
headerTransforms: (input.domains ?? (input.authorization ? GITHUB_ASSET_UPLOAD_CREDENTIAL_DOMAINS : GITHUB_CREDENTIAL_DOMAINS)).map((domain) => ({
|
|
507
|
+
domain,
|
|
508
|
+
headers: {
|
|
509
|
+
Authorization: authorizationFor(domain, input.token)
|
|
510
|
+
}
|
|
511
|
+
}))
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
function githubUserAuthorization(scope) {
|
|
516
|
+
return {
|
|
517
|
+
type: "oauth",
|
|
518
|
+
provider: "github",
|
|
519
|
+
...scope ? { scope } : void 0
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
function credentialNeeded(message, scope, allowAuthorization = true) {
|
|
523
|
+
return {
|
|
524
|
+
type: "needed",
|
|
525
|
+
message,
|
|
526
|
+
...allowAuthorization ? { authorization: githubUserAuthorization(scope) } : void 0
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
function credentialUnavailable(message) {
|
|
530
|
+
return {
|
|
531
|
+
type: "unavailable",
|
|
532
|
+
message
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
function parseInstallationTokenResponse(data) {
|
|
536
|
+
if (!isRecord(data)) {
|
|
537
|
+
throw new Error("GitHub installation token response is invalid");
|
|
538
|
+
}
|
|
539
|
+
const token = data.token;
|
|
540
|
+
if (typeof token !== "string" || !token.trim()) {
|
|
541
|
+
throw new Error("GitHub installation token response missing token");
|
|
542
|
+
}
|
|
543
|
+
const expiresAt = data.expires_at;
|
|
544
|
+
const expiresAtMs = typeof expiresAt === "string" ? Date.parse(expiresAt) : Number.NaN;
|
|
545
|
+
if (!Number.isFinite(expiresAtMs) || expiresAtMs <= Date.now()) {
|
|
546
|
+
throw new Error(
|
|
547
|
+
"GitHub installation token response returned invalid expires_at"
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
return { token, expiresAtMs };
|
|
551
|
+
}
|
|
552
|
+
function readInstallationPermissions(installation) {
|
|
553
|
+
if (!isRecord(installation) || !isRecord(installation.permissions)) {
|
|
554
|
+
throw new Error("GitHub installation response missing permissions");
|
|
555
|
+
}
|
|
556
|
+
return readGrantPermissions(installation.permissions);
|
|
557
|
+
}
|
|
558
|
+
function decodeGitHubPathSegment(value) {
|
|
559
|
+
try {
|
|
560
|
+
const decoded = decodeURIComponent(value).trim();
|
|
561
|
+
return decoded && !decoded.includes("/") ? decoded : void 0;
|
|
562
|
+
} catch {
|
|
563
|
+
return void 0;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
function githubRepositoryFromUrl(upstreamUrl) {
|
|
567
|
+
const segments = upstreamUrl.pathname.split("/").filter(Boolean);
|
|
568
|
+
if (isGitHubApiUrl(upstreamUrl) && segments[0]?.toLowerCase() === "repos") {
|
|
569
|
+
const owner2 = segments[1] ? decodeGitHubPathSegment(segments[1]) : void 0;
|
|
570
|
+
const name2 = segments[2] ? decodeGitHubPathSegment(segments[2]) : void 0;
|
|
571
|
+
return owner2 && name2 ? { owner: owner2, name: name2 } : void 0;
|
|
572
|
+
}
|
|
573
|
+
if (upstreamUrl.hostname.toLowerCase() !== "github.com") {
|
|
574
|
+
return void 0;
|
|
575
|
+
}
|
|
576
|
+
const owner = segments[0] ? decodeGitHubPathSegment(segments[0]) : void 0;
|
|
577
|
+
const rawName = segments[1]?.replace(/\.git$/i, "");
|
|
578
|
+
const name = rawName ? decodeGitHubPathSegment(rawName) : void 0;
|
|
579
|
+
return owner && name ? { owner, name } : void 0;
|
|
580
|
+
}
|
|
581
|
+
function githubRepositoryLeaseScope(repository) {
|
|
582
|
+
return `repository:${repository.owner.toLowerCase()}/${repository.name.toLowerCase()}`;
|
|
583
|
+
}
|
|
584
|
+
function githubRepositoryFromLeaseScope(leaseScope) {
|
|
585
|
+
const match = /^repository:([^/]+)\/([^/]+)$/.exec(leaseScope ?? "");
|
|
586
|
+
if (!match?.[1] || !match[2]) {
|
|
587
|
+
throw new GitHubPluginSetupError(
|
|
588
|
+
"GitHub installation write grant is missing a repository lease scope."
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
return { owner: match[1], name: match[2] };
|
|
592
|
+
}
|
|
593
|
+
async function resolveUserAccount(tokens) {
|
|
594
|
+
const account = await githubRequest("https://api.github.com", "/user", {
|
|
595
|
+
token: tokens.accessToken
|
|
596
|
+
});
|
|
597
|
+
if (!isRecord(account)) {
|
|
598
|
+
throw new Error("GitHub user response is invalid");
|
|
599
|
+
}
|
|
600
|
+
const id = account.id;
|
|
601
|
+
const login = account.login;
|
|
602
|
+
if (typeof id !== "number" && typeof id !== "string" || typeof login !== "string" || !login.trim()) {
|
|
603
|
+
throw new Error("GitHub user response missing id or login");
|
|
604
|
+
}
|
|
605
|
+
const url = typeof account.html_url === "string" ? account.html_url : void 0;
|
|
606
|
+
return {
|
|
607
|
+
handle: login.trim(),
|
|
608
|
+
id: String(id),
|
|
609
|
+
label: login.trim(),
|
|
610
|
+
...url ? { url } : void 0
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
async function tokensWithAccount(tokenSlot, stored, scope) {
|
|
614
|
+
if (stored.account) {
|
|
615
|
+
return { ok: true, tokens: stored };
|
|
616
|
+
}
|
|
617
|
+
let account;
|
|
618
|
+
try {
|
|
619
|
+
account = await resolveUserAccount(stored);
|
|
620
|
+
} catch (error) {
|
|
621
|
+
if (error instanceof GitHubRequestError && (error.status === 401 || error.status === 403)) {
|
|
622
|
+
return {
|
|
623
|
+
ok: false,
|
|
624
|
+
result: credentialNeeded(
|
|
625
|
+
"Your GitHub authorization needs to be refreshed.",
|
|
626
|
+
scope
|
|
627
|
+
)
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
throw error;
|
|
631
|
+
}
|
|
632
|
+
const updated = { ...stored, account };
|
|
633
|
+
await tokenSlot.set(updated);
|
|
634
|
+
return { ok: true, tokens: updated };
|
|
635
|
+
}
|
|
636
|
+
function shouldRefreshUserToken(stored, now = Date.now()) {
|
|
637
|
+
return stored.expiresAt !== void 0 && stored.expiresAt - now < REFRESH_BUFFER_MS;
|
|
638
|
+
}
|
|
639
|
+
function canUseStoredUserToken(stored) {
|
|
640
|
+
return stored.expiresAt === void 0 || stored.expiresAt > Date.now() && !shouldRefreshUserToken(stored);
|
|
641
|
+
}
|
|
642
|
+
async function refreshUserTokensWithLock(tokenSlot, scope, options) {
|
|
643
|
+
return await tokenSlot.withRefresh(async () => {
|
|
644
|
+
const latest = await tokenSlot.get();
|
|
645
|
+
if (!latest) {
|
|
646
|
+
return {
|
|
647
|
+
ok: false,
|
|
648
|
+
result: credentialNeeded("Connect your GitHub account.", scope)
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
if (!hasRequiredOAuthScope(latest.scope, scope)) {
|
|
652
|
+
return {
|
|
653
|
+
ok: false,
|
|
654
|
+
result: credentialNeeded(
|
|
655
|
+
"Your GitHub authorization needs to be refreshed.",
|
|
656
|
+
scope
|
|
657
|
+
)
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
if (canUseStoredUserToken(latest)) {
|
|
661
|
+
return { ok: true, tokens: latest };
|
|
662
|
+
}
|
|
663
|
+
let refreshed;
|
|
664
|
+
try {
|
|
665
|
+
refreshed = await refreshUserAccessToken({
|
|
666
|
+
clientIdEnv: options.clientIdEnv,
|
|
667
|
+
clientSecretEnv: options.clientSecretEnv,
|
|
668
|
+
refreshToken: latest.refreshToken,
|
|
669
|
+
requestedScope: latest.scope ?? scope
|
|
670
|
+
});
|
|
671
|
+
} catch (error) {
|
|
672
|
+
if (!(error instanceof GitHubUserRefreshRejectedError)) {
|
|
673
|
+
throw error;
|
|
674
|
+
}
|
|
675
|
+
return {
|
|
676
|
+
ok: false,
|
|
677
|
+
result: credentialNeeded(
|
|
678
|
+
"Your GitHub authorization has expired.",
|
|
679
|
+
scope
|
|
680
|
+
)
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
if (!hasRequiredOAuthScope(refreshed.scope, scope)) {
|
|
684
|
+
return {
|
|
685
|
+
ok: false,
|
|
686
|
+
result: credentialNeeded(
|
|
687
|
+
"Your GitHub authorization needs to be refreshed.",
|
|
688
|
+
scope
|
|
689
|
+
)
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
const refreshedTokens = {
|
|
693
|
+
...latest.refreshTokenExpiresAt ? { refreshTokenExpiresAt: latest.refreshTokenExpiresAt } : void 0,
|
|
694
|
+
...refreshed,
|
|
695
|
+
...latest.account ? { account: latest.account } : void 0
|
|
696
|
+
};
|
|
697
|
+
await tokenSlot.set(refreshedTokens);
|
|
698
|
+
return { ok: true, tokens: refreshedTokens };
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
async function issueUserCredential(ctx, options) {
|
|
702
|
+
const scope = options.userScope;
|
|
703
|
+
const tokenSlot = ctx.tokens.currentUser ?? ctx.tokens.credentialSubject;
|
|
704
|
+
if (!tokenSlot) {
|
|
705
|
+
return credentialNeeded(
|
|
706
|
+
"GitHub write access requires a current user or delegated user credential subject.",
|
|
707
|
+
scope,
|
|
708
|
+
false
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
const stored = await tokenSlot.get();
|
|
712
|
+
if (!stored) {
|
|
713
|
+
return credentialNeeded(
|
|
714
|
+
"GitHub write access requires user authorization.",
|
|
715
|
+
scope
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
if (!hasRequiredOAuthScope(stored.scope, scope)) {
|
|
719
|
+
return credentialNeeded(
|
|
720
|
+
"Your GitHub authorization needs to be refreshed.",
|
|
721
|
+
scope
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
const now = Date.now();
|
|
725
|
+
if (stored.expiresAt !== void 0 && stored.expiresAt - now < REFRESH_BUFFER_MS) {
|
|
726
|
+
const refreshResult = await refreshUserTokensWithLock(
|
|
727
|
+
tokenSlot,
|
|
728
|
+
scope,
|
|
729
|
+
options
|
|
730
|
+
);
|
|
731
|
+
if (!refreshResult.ok) {
|
|
732
|
+
return refreshResult.result;
|
|
733
|
+
}
|
|
734
|
+
const withAccount = await tokensWithAccount(
|
|
735
|
+
tokenSlot,
|
|
736
|
+
refreshResult.tokens,
|
|
737
|
+
scope
|
|
738
|
+
);
|
|
739
|
+
if (!withAccount.ok) {
|
|
740
|
+
return withAccount.result;
|
|
741
|
+
}
|
|
742
|
+
return createCredentialLease({
|
|
743
|
+
account: withAccount.tokens.account,
|
|
744
|
+
token: withAccount.tokens.accessToken,
|
|
745
|
+
expiresAtMs: leaseExpiry(withAccount.tokens.expiresAt),
|
|
746
|
+
authorization: githubUserAuthorization(scope)
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
if (stored.expiresAt === void 0 || stored.expiresAt > Date.now()) {
|
|
750
|
+
const withAccount = await tokensWithAccount(tokenSlot, stored, scope);
|
|
751
|
+
if (!withAccount.ok) {
|
|
752
|
+
return withAccount.result;
|
|
753
|
+
}
|
|
754
|
+
return createCredentialLease({
|
|
755
|
+
account: withAccount.tokens.account,
|
|
756
|
+
token: withAccount.tokens.accessToken,
|
|
757
|
+
expiresAtMs: leaseExpiry(withAccount.tokens.expiresAt),
|
|
758
|
+
authorization: githubUserAuthorization(scope)
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
return credentialNeeded("Your GitHub authorization has expired.", scope);
|
|
762
|
+
}
|
|
763
|
+
async function issueInstallationToken(options) {
|
|
764
|
+
const appId = requireEnv(options.appIdEnv);
|
|
765
|
+
const installationIdRaw = requireEnv(options.installationIdEnv);
|
|
766
|
+
const installationId = Number(installationIdRaw);
|
|
767
|
+
if (!Number.isSafeInteger(installationId) || installationId <= 0) {
|
|
768
|
+
throw new GitHubPluginSetupError(`Invalid ${options.installationIdEnv}`);
|
|
769
|
+
}
|
|
770
|
+
const appJwt = createAppJwt(appId, options.privateKeyEnv);
|
|
771
|
+
const permissions = "permissions" in options ? options.permissions : typeof options.loadPermissions === "function" ? await options.loadPermissions({ appJwt, installationId }) : void 0;
|
|
772
|
+
const body = {
|
|
773
|
+
...permissions ? { permissions } : void 0,
|
|
774
|
+
..."repositories" in options ? { repositories: options.repositories } : void 0
|
|
775
|
+
};
|
|
776
|
+
const accessTokenResponse = await githubRequest(
|
|
777
|
+
"https://api.github.com",
|
|
778
|
+
`/app/installations/${installationId}/access_tokens`,
|
|
779
|
+
{
|
|
780
|
+
method: "POST",
|
|
781
|
+
token: appJwt,
|
|
782
|
+
body
|
|
783
|
+
}
|
|
784
|
+
);
|
|
785
|
+
const parsedToken = parseInstallationTokenResponse(accessTokenResponse);
|
|
786
|
+
return {
|
|
787
|
+
expiresAtMs: Math.min(parsedToken.expiresAtMs, Date.now() + MAX_LEASE_MS),
|
|
788
|
+
token: parsedToken.token
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
async function issueInstallationCredential(options) {
|
|
792
|
+
const token = await issueInstallationToken(options);
|
|
793
|
+
return createCredentialLease({
|
|
794
|
+
token: token.token,
|
|
795
|
+
expiresAtMs: token.expiresAtMs
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
function createPermissionCache() {
|
|
799
|
+
let cached;
|
|
800
|
+
let pending;
|
|
801
|
+
return async ({ appJwt, installationId }) => {
|
|
802
|
+
if (cached && cached.expiresAtMs > Date.now()) {
|
|
803
|
+
return cached.permissions;
|
|
804
|
+
}
|
|
805
|
+
pending ??= githubRequest(
|
|
806
|
+
"https://api.github.com",
|
|
807
|
+
`/app/installations/${installationId}`,
|
|
808
|
+
{ token: appJwt }
|
|
809
|
+
).then((installation) => {
|
|
810
|
+
const permissions = readInstallationPermissions(installation);
|
|
811
|
+
cached = {
|
|
812
|
+
expiresAtMs: Date.now() + MAX_LEASE_MS,
|
|
813
|
+
permissions
|
|
814
|
+
};
|
|
815
|
+
return permissions;
|
|
816
|
+
}).finally(() => {
|
|
817
|
+
pending = void 0;
|
|
818
|
+
});
|
|
819
|
+
return await pending;
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// src/webhooks/check-suite.ts
|
|
824
|
+
function gitHubEventKey(deliveryId, eventType) {
|
|
825
|
+
return `github:${deliveryId}:${eventType}`;
|
|
826
|
+
}
|
|
827
|
+
function pullRequestTargets(event, repo) {
|
|
828
|
+
const { terminal: _terminal, ...repositoryEvent } = event;
|
|
829
|
+
return [
|
|
830
|
+
event,
|
|
831
|
+
{
|
|
832
|
+
...repositoryEvent,
|
|
833
|
+
identifier: gitHubRepositoryResource({ repo }).identifier
|
|
834
|
+
}
|
|
835
|
+
];
|
|
836
|
+
}
|
|
837
|
+
var repositorySchema = z.object({ full_name: z.string().min(1) }).passthrough();
|
|
838
|
+
var checkSuiteWebhookSchema = z.object({
|
|
839
|
+
action: z.string(),
|
|
840
|
+
check_suite: z.object({
|
|
841
|
+
app: z.object({
|
|
842
|
+
name: z.string().optional().nullable(),
|
|
843
|
+
slug: z.string().optional().nullable()
|
|
844
|
+
}).optional().nullable(),
|
|
845
|
+
conclusion: z.string().optional().nullable(),
|
|
846
|
+
head_sha: z.string().optional(),
|
|
847
|
+
id: z.number().optional(),
|
|
848
|
+
latest_check_runs_count: z.number().optional().nullable(),
|
|
849
|
+
pull_requests: z.array(
|
|
850
|
+
z.object({ draft: z.boolean().optional(), number: z.number() })
|
|
851
|
+
)
|
|
852
|
+
}),
|
|
853
|
+
repository: repositorySchema
|
|
854
|
+
});
|
|
855
|
+
var FAILING_CHECK_CONCLUSIONS = /* @__PURE__ */ new Set([
|
|
856
|
+
"failure",
|
|
857
|
+
"timed_out",
|
|
858
|
+
"cancelled",
|
|
859
|
+
"action_required",
|
|
860
|
+
"startup_failure"
|
|
861
|
+
]);
|
|
862
|
+
function buildCheckSuiteUrl(args) {
|
|
863
|
+
return `https://github.com/${args.repo}/commit/${args.headSha}/checks?check_suite_id=${args.checkSuiteId}`;
|
|
864
|
+
}
|
|
865
|
+
function oneLineLabel(value, maxLength = 80) {
|
|
866
|
+
return value.replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim().slice(0, maxLength);
|
|
867
|
+
}
|
|
868
|
+
function buildCheckSuiteResourceEvent(args) {
|
|
869
|
+
const resource = gitHubPullRequestResource({
|
|
870
|
+
number: args.pullRequestNumber,
|
|
871
|
+
repo: args.repo
|
|
872
|
+
});
|
|
873
|
+
const shortSha = args.headSha?.slice(0, 12);
|
|
874
|
+
const failingChecks = (args.failingChecks ?? []).slice(0, 12);
|
|
875
|
+
const failingCount = failingChecks.length;
|
|
876
|
+
const trustedSummary = args.eventType === "pull_request.checks.failed" ? `${resource.label} checks failed${failingCount > 0 ? ` (${failingCount})` : ""}${shortSha ? ` for ${shortSha}` : ""}.` : `${resource.label} check suite recovered${shortSha ? ` for ${shortSha}` : ""}.`;
|
|
877
|
+
const data = {
|
|
878
|
+
repo: args.repo,
|
|
879
|
+
pullRequest: args.pullRequestNumber,
|
|
880
|
+
scope: "check_suite",
|
|
881
|
+
suiteConclusion: args.suiteConclusion
|
|
882
|
+
};
|
|
883
|
+
if (typeof args.isDraft === "boolean") data.isDraft = args.isDraft;
|
|
884
|
+
if (args.authorUsername) data.authorUsername = args.authorUsername;
|
|
885
|
+
if (args.authorEmail) data.authorEmail = args.authorEmail;
|
|
886
|
+
if (args.headSha) data.headSha = args.headSha;
|
|
887
|
+
if (args.checkSuiteId !== void 0) data.checkSuiteId = args.checkSuiteId;
|
|
888
|
+
if (args.checkSuiteId !== void 0 && args.headSha) {
|
|
889
|
+
data.checkSuiteUrl = buildCheckSuiteUrl({
|
|
890
|
+
checkSuiteId: args.checkSuiteId,
|
|
891
|
+
headSha: args.headSha,
|
|
892
|
+
repo: args.repo
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
if (args.appName) data.appName = oneLineLabel(args.appName, 120);
|
|
896
|
+
if (args.latestCheckRunsCount !== void 0) {
|
|
897
|
+
data.latestCheckRunsCount = args.latestCheckRunsCount;
|
|
898
|
+
}
|
|
899
|
+
if (args.eventType === "pull_request.checks.failed" && failingCount > 0) {
|
|
900
|
+
data.failingChecks = failingChecks.map((check) => ({
|
|
901
|
+
conclusion: check.conclusion,
|
|
902
|
+
...check.htmlUrl ? { htmlUrl: check.htmlUrl } : void 0,
|
|
903
|
+
checkRunId: check.checkRunId
|
|
904
|
+
}));
|
|
905
|
+
}
|
|
906
|
+
const untrustedParts = args.eventType === "pull_request.checks.failed" ? failingChecks.map((check) => {
|
|
907
|
+
const name = oneLineLabel(check.name);
|
|
908
|
+
if (!name) return void 0;
|
|
909
|
+
return check.htmlUrl ? `${name}: ${check.htmlUrl}` : name;
|
|
910
|
+
}).filter((part) => part !== void 0) : [];
|
|
911
|
+
const untrustedText = untrustedParts.length > 0 ? [`Failed checks:`, ...untrustedParts.map((part) => `- ${part}`)].join(
|
|
912
|
+
"\n"
|
|
913
|
+
) : void 0;
|
|
914
|
+
return {
|
|
915
|
+
eventKey: gitHubEventKey(
|
|
916
|
+
args.deliveryId,
|
|
917
|
+
`${args.eventType}:${args.pullRequestNumber}`
|
|
918
|
+
),
|
|
919
|
+
eventType: args.eventType,
|
|
920
|
+
occurredAtMs: Date.now(),
|
|
921
|
+
identifier: resource.identifier,
|
|
922
|
+
trustedSummary,
|
|
923
|
+
data,
|
|
924
|
+
...untrustedText ? { untrustedText } : void 0
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
function selectFailingChecks(checkRuns, options) {
|
|
928
|
+
if (!Array.isArray(checkRuns)) return [];
|
|
929
|
+
const failing = [];
|
|
930
|
+
for (const run of checkRuns) {
|
|
931
|
+
if (!run || typeof run !== "object" || Array.isArray(run)) continue;
|
|
932
|
+
const record = run;
|
|
933
|
+
if (options?.checkSuiteId !== void 0) {
|
|
934
|
+
const suite = record.check_suite;
|
|
935
|
+
const suiteId = suite && typeof suite === "object" && !Array.isArray(suite) && typeof suite.id === "number" ? suite.id : void 0;
|
|
936
|
+
if (suiteId !== void 0 && suiteId !== options.checkSuiteId) continue;
|
|
937
|
+
}
|
|
938
|
+
const conclusion = typeof record.conclusion === "string" ? record.conclusion : void 0;
|
|
939
|
+
const name = typeof record.name === "string" ? record.name.trim() : "";
|
|
940
|
+
const checkRunId = typeof record.id === "number" && Number.isSafeInteger(record.id) ? record.id : void 0;
|
|
941
|
+
if (!conclusion || !FAILING_CHECK_CONCLUSIONS.has(conclusion)) continue;
|
|
942
|
+
if (!name || checkRunId === void 0) continue;
|
|
943
|
+
const htmlUrl = typeof record.html_url === "string" && record.html_url.length > 0 ? record.html_url : void 0;
|
|
944
|
+
failing.push({
|
|
945
|
+
checkRunId,
|
|
946
|
+
conclusion,
|
|
947
|
+
...htmlUrl ? { htmlUrl } : void 0,
|
|
948
|
+
name
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
return failing.slice(0, 12);
|
|
952
|
+
}
|
|
953
|
+
function normalizeCheckSuiteEvents(deliveryId, body, options) {
|
|
954
|
+
const parsed = checkSuiteWebhookSchema.safeParse(body);
|
|
955
|
+
if (!parsed.success || parsed.data.action !== "completed") return [];
|
|
956
|
+
const conclusion = parsed.data.check_suite.conclusion;
|
|
957
|
+
if (!conclusion) return [];
|
|
958
|
+
const eventType = conclusion === "failure" || conclusion === "timed_out" ? "pull_request.checks.failed" : conclusion === "success" ? "pull_request.checks.recovered" : void 0;
|
|
959
|
+
if (!eventType) return [];
|
|
960
|
+
const suite = parsed.data.check_suite;
|
|
961
|
+
const appName = suite.app?.name?.trim() || suite.app?.slug?.trim() || void 0;
|
|
962
|
+
const headSha = typeof suite.head_sha === "string" && /^[0-9a-f]{7,40}$/i.test(suite.head_sha) ? suite.head_sha : void 0;
|
|
963
|
+
return suite.pull_requests.flatMap((pullRequest) => {
|
|
964
|
+
const repo = parsed.data.repository.full_name;
|
|
965
|
+
const facts = options?.pullRequestFactsByNumber?.[pullRequest.number];
|
|
966
|
+
const draft = typeof pullRequest.draft === "boolean" ? pullRequest.draft : typeof facts?.isDraft === "boolean" ? facts.isDraft : void 0;
|
|
967
|
+
return pullRequestTargets(
|
|
968
|
+
buildCheckSuiteResourceEvent({
|
|
969
|
+
appName,
|
|
970
|
+
...facts?.authorEmail ? { authorEmail: facts.authorEmail } : void 0,
|
|
971
|
+
...facts?.authorUsername ? { authorUsername: facts.authorUsername } : void 0,
|
|
972
|
+
checkSuiteId: suite.id,
|
|
973
|
+
deliveryId,
|
|
974
|
+
eventType,
|
|
975
|
+
failingChecks: eventType === "pull_request.checks.failed" ? options?.failingChecks : void 0,
|
|
976
|
+
headSha,
|
|
977
|
+
...typeof draft === "boolean" ? { isDraft: draft } : void 0,
|
|
978
|
+
latestCheckRunsCount: typeof suite.latest_check_runs_count === "number" ? suite.latest_check_runs_count : void 0,
|
|
979
|
+
pullRequestNumber: pullRequest.number,
|
|
980
|
+
repo,
|
|
981
|
+
suiteConclusion: conclusion
|
|
982
|
+
}),
|
|
983
|
+
repo
|
|
984
|
+
);
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
function parseCheckSuiteFactsTarget(body) {
|
|
988
|
+
const parsed = checkSuiteWebhookSchema.safeParse(body);
|
|
989
|
+
if (!parsed.success || parsed.data.action !== "completed") return void 0;
|
|
990
|
+
const conclusion = parsed.data.check_suite.conclusion;
|
|
991
|
+
if (conclusion !== "failure" && conclusion !== "timed_out" && conclusion !== "success") {
|
|
992
|
+
return void 0;
|
|
993
|
+
}
|
|
994
|
+
const headSha = parsed.data.check_suite.head_sha;
|
|
995
|
+
const checkSuiteId = parsed.data.check_suite.id;
|
|
996
|
+
if (typeof headSha !== "string" || !/^[0-9a-f]{7,40}$/i.test(headSha) || typeof checkSuiteId !== "number") {
|
|
997
|
+
return void 0;
|
|
998
|
+
}
|
|
999
|
+
const [owner, repoName, ...extra] = parsed.data.repository.full_name.split("/");
|
|
1000
|
+
if (!owner || !repoName || extra.length > 0) return void 0;
|
|
1001
|
+
const pullRequestNumbers = [
|
|
1002
|
+
...new Set(
|
|
1003
|
+
parsed.data.check_suite.pull_requests.map(
|
|
1004
|
+
(pullRequest) => pullRequest.number
|
|
1005
|
+
)
|
|
1006
|
+
)
|
|
1007
|
+
];
|
|
1008
|
+
const loadFailingChecks = conclusion === "failure" || conclusion === "timed_out";
|
|
1009
|
+
if (!loadFailingChecks && pullRequestNumbers.length === 0) {
|
|
1010
|
+
return void 0;
|
|
1011
|
+
}
|
|
1012
|
+
return {
|
|
1013
|
+
checkSuiteId,
|
|
1014
|
+
headSha,
|
|
1015
|
+
loadFailingChecks,
|
|
1016
|
+
owner,
|
|
1017
|
+
pullRequestNumbers,
|
|
1018
|
+
repoName
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
function checkRunsFromResponse(value) {
|
|
1022
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1023
|
+
return [];
|
|
1024
|
+
}
|
|
1025
|
+
const checkRuns = value.check_runs;
|
|
1026
|
+
return Array.isArray(checkRuns) ? checkRuns : [];
|
|
1027
|
+
}
|
|
1028
|
+
function pullRequestFactsFromResponse(value) {
|
|
1029
|
+
if (!isRecord(value)) return void 0;
|
|
1030
|
+
const facts = {};
|
|
1031
|
+
if (typeof value.draft === "boolean") facts.isDraft = value.draft;
|
|
1032
|
+
const user = value.user;
|
|
1033
|
+
if (isRecord(user)) {
|
|
1034
|
+
const username = typeof user.login === "string" ? user.login.trim() : void 0;
|
|
1035
|
+
if (username) facts.authorUsername = username;
|
|
1036
|
+
const email = typeof user.email === "string" ? user.email.trim() : void 0;
|
|
1037
|
+
if (email) facts.authorEmail = email;
|
|
1038
|
+
}
|
|
1039
|
+
return Object.keys(facts).length > 0 ? facts : void 0;
|
|
1040
|
+
}
|
|
1041
|
+
async function loadCheckSuiteFacts(args) {
|
|
1042
|
+
const target = parseCheckSuiteFactsTarget(args.body);
|
|
1043
|
+
if (!target) return void 0;
|
|
1044
|
+
const facts = {};
|
|
1045
|
+
if (target.loadFailingChecks) {
|
|
1046
|
+
try {
|
|
1047
|
+
const token = await issueInstallationToken({
|
|
1048
|
+
appIdEnv: args.appIdEnv,
|
|
1049
|
+
installationIdEnv: args.installationIdEnv,
|
|
1050
|
+
permissions: { checks: "read" },
|
|
1051
|
+
privateKeyEnv: args.privateKeyEnv,
|
|
1052
|
+
repositories: [target.repoName]
|
|
1053
|
+
});
|
|
1054
|
+
const response = await githubRequest(
|
|
1055
|
+
"https://api.github.com",
|
|
1056
|
+
`/repos/${encodeURIComponent(target.owner)}/${encodeURIComponent(target.repoName)}/check-suites/${target.checkSuiteId}/check-runs?filter=latest&per_page=100`,
|
|
1057
|
+
{ token: token.token }
|
|
1058
|
+
);
|
|
1059
|
+
const failing = selectFailingChecks(checkRunsFromResponse(response), {
|
|
1060
|
+
checkSuiteId: target.checkSuiteId
|
|
1061
|
+
});
|
|
1062
|
+
if (failing.length > 0) facts.failingChecks = failing;
|
|
1063
|
+
} catch (error) {
|
|
1064
|
+
args.log?.error("GitHub check suite load failed", {
|
|
1065
|
+
checkSuiteId: target.checkSuiteId,
|
|
1066
|
+
errorType: error instanceof Error ? error.name : "UnknownError",
|
|
1067
|
+
repository: `${target.owner}/${target.repoName}`
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
if (target.pullRequestNumbers.length > 0) {
|
|
1072
|
+
try {
|
|
1073
|
+
const token = await issueInstallationToken({
|
|
1074
|
+
appIdEnv: args.appIdEnv,
|
|
1075
|
+
installationIdEnv: args.installationIdEnv,
|
|
1076
|
+
permissions: { pull_requests: "read" },
|
|
1077
|
+
privateKeyEnv: args.privateKeyEnv,
|
|
1078
|
+
repositories: [target.repoName]
|
|
1079
|
+
});
|
|
1080
|
+
const loaded = await Promise.all(
|
|
1081
|
+
target.pullRequestNumbers.map(async (number) => {
|
|
1082
|
+
try {
|
|
1083
|
+
const response = await githubRequest(
|
|
1084
|
+
"https://api.github.com",
|
|
1085
|
+
`/repos/${encodeURIComponent(target.owner)}/${encodeURIComponent(target.repoName)}/pulls/${number}`,
|
|
1086
|
+
{ token: token.token }
|
|
1087
|
+
);
|
|
1088
|
+
const facts2 = pullRequestFactsFromResponse(response);
|
|
1089
|
+
return facts2 ? [number, facts2] : void 0;
|
|
1090
|
+
} catch (error) {
|
|
1091
|
+
args.log?.error("GitHub pull request data load failed", {
|
|
1092
|
+
errorType: error instanceof Error ? error.name : "UnknownError",
|
|
1093
|
+
pullRequest: number,
|
|
1094
|
+
repository: `${target.owner}/${target.repoName}`
|
|
1095
|
+
});
|
|
1096
|
+
return void 0;
|
|
1097
|
+
}
|
|
1098
|
+
})
|
|
1099
|
+
);
|
|
1100
|
+
const pullRequestFactsByNumber = {};
|
|
1101
|
+
for (const entry of loaded) {
|
|
1102
|
+
if (!entry) continue;
|
|
1103
|
+
pullRequestFactsByNumber[entry[0]] = entry[1];
|
|
1104
|
+
}
|
|
1105
|
+
if (Object.keys(pullRequestFactsByNumber).length > 0) {
|
|
1106
|
+
facts.pullRequestFactsByNumber = pullRequestFactsByNumber;
|
|
1107
|
+
}
|
|
1108
|
+
} catch (error) {
|
|
1109
|
+
args.log?.error("GitHub check suite pull request data load failed", {
|
|
1110
|
+
checkSuiteId: target.checkSuiteId,
|
|
1111
|
+
errorType: error instanceof Error ? error.name : "UnknownError",
|
|
1112
|
+
repository: `${target.owner}/${target.repoName}`
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
return facts.failingChecks || facts.pullRequestFactsByNumber ? facts : void 0;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
// src/webhooks/resource-events.ts
|
|
1120
|
+
function gitHubEventKey2(deliveryId, eventType) {
|
|
1121
|
+
return `github:${deliveryId}:${eventType}`;
|
|
1122
|
+
}
|
|
1123
|
+
function pullRequestDraftData(draft) {
|
|
1124
|
+
return typeof draft === "boolean" ? { isDraft: draft } : void 0;
|
|
1125
|
+
}
|
|
1126
|
+
function pullRequestAuthorData(user) {
|
|
1127
|
+
const data = {};
|
|
1128
|
+
const username = user?.login?.trim();
|
|
1129
|
+
if (username) data.authorUsername = username;
|
|
1130
|
+
const email = user?.email?.trim();
|
|
1131
|
+
if (email) data.authorEmail = email;
|
|
1132
|
+
return data;
|
|
1133
|
+
}
|
|
1134
|
+
function pullRequestMatchData(parts) {
|
|
1135
|
+
const data = {};
|
|
1136
|
+
for (const part of parts) {
|
|
1137
|
+
if (!part) continue;
|
|
1138
|
+
Object.assign(data, part);
|
|
1139
|
+
}
|
|
1140
|
+
return Object.keys(data).length > 0 ? data : void 0;
|
|
1141
|
+
}
|
|
1142
|
+
function pullRequestTargets2(event, repo) {
|
|
1143
|
+
const { terminal: _terminal, ...repositoryEvent } = event;
|
|
1144
|
+
return [
|
|
1145
|
+
event,
|
|
1146
|
+
{
|
|
1147
|
+
...repositoryEvent,
|
|
1148
|
+
identifier: gitHubRepositoryResource({ repo }).identifier
|
|
1149
|
+
}
|
|
1150
|
+
];
|
|
1151
|
+
}
|
|
1152
|
+
var repositorySchema2 = z2.object({ full_name: z2.string().min(1) }).passthrough();
|
|
1153
|
+
var deploymentSchema = z2.object({
|
|
1154
|
+
created_at: z2.string().optional(),
|
|
1155
|
+
environment: z2.string().min(1),
|
|
1156
|
+
id: z2.number(),
|
|
1157
|
+
sha: z2.string().regex(/^[0-9a-f]{40}$/i)
|
|
1158
|
+
}).passthrough();
|
|
1159
|
+
var deploymentWebhookSchema = z2.object({
|
|
1160
|
+
action: z2.string(),
|
|
1161
|
+
deployment: deploymentSchema,
|
|
1162
|
+
repository: repositorySchema2
|
|
1163
|
+
}).passthrough();
|
|
1164
|
+
var canonicalDeploymentEventSchema = z2.object({
|
|
1165
|
+
action: z2.string(),
|
|
1166
|
+
commitSha: z2.string().regex(/^[0-9a-f]{40}$/i),
|
|
1167
|
+
createdAt: z2.string().optional(),
|
|
1168
|
+
deploymentId: z2.number(),
|
|
1169
|
+
environment: z2.string().min(1),
|
|
1170
|
+
repo: z2.string().min(1)
|
|
1171
|
+
}).strict();
|
|
1172
|
+
function parseDeploymentEvent(body) {
|
|
1173
|
+
const parsed = deploymentWebhookSchema.safeParse(body);
|
|
1174
|
+
if (!parsed.success) return void 0;
|
|
1175
|
+
return canonicalDeploymentEventSchema.parse({
|
|
1176
|
+
action: parsed.data.action,
|
|
1177
|
+
commitSha: parsed.data.deployment.sha,
|
|
1178
|
+
createdAt: parsed.data.deployment.created_at,
|
|
1179
|
+
deploymentId: parsed.data.deployment.id,
|
|
1180
|
+
environment: parsed.data.deployment.environment,
|
|
1181
|
+
repo: parsed.data.repository.full_name
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
function normalizeDeploymentEvent(deliveryId, body) {
|
|
1185
|
+
const parsed = parseDeploymentEvent(body);
|
|
1186
|
+
if (!parsed || parsed.action !== "created") return [];
|
|
1187
|
+
const eventType = "deployment.created";
|
|
1188
|
+
return deploymentSourceTargets(parsed).map(({ resource }) => ({
|
|
1189
|
+
eventKey: gitHubEventKey2(deliveryId, eventType),
|
|
1190
|
+
eventType,
|
|
1191
|
+
occurredAtMs: providerTime(parsed.createdAt) ?? Date.now(),
|
|
1192
|
+
identifier: resource.identifier,
|
|
1193
|
+
trustedSummary: `${resource.label} was created (deployment ${parsed.deploymentId}).`
|
|
1194
|
+
}));
|
|
1195
|
+
}
|
|
1196
|
+
var deploymentStatusWebhookSchema = z2.object({
|
|
1197
|
+
action: z2.string(),
|
|
1198
|
+
deployment: deploymentSchema,
|
|
1199
|
+
deployment_status: z2.object({
|
|
1200
|
+
created_at: z2.string().optional(),
|
|
1201
|
+
description: z2.string().optional().nullable(),
|
|
1202
|
+
state: z2.string()
|
|
1203
|
+
}).passthrough(),
|
|
1204
|
+
repository: repositorySchema2
|
|
1205
|
+
}).passthrough();
|
|
1206
|
+
var canonicalDeploymentStatusEventSchema = canonicalDeploymentEventSchema.extend({
|
|
1207
|
+
description: z2.string().optional().nullable(),
|
|
1208
|
+
state: z2.string(),
|
|
1209
|
+
statusCreatedAt: z2.string().optional()
|
|
1210
|
+
}).strict();
|
|
1211
|
+
function parseDeploymentStatusEvent(body) {
|
|
1212
|
+
const parsed = deploymentStatusWebhookSchema.safeParse(body);
|
|
1213
|
+
if (!parsed.success) return void 0;
|
|
1214
|
+
return canonicalDeploymentStatusEventSchema.parse({
|
|
1215
|
+
action: parsed.data.action,
|
|
1216
|
+
commitSha: parsed.data.deployment.sha,
|
|
1217
|
+
createdAt: parsed.data.deployment.created_at,
|
|
1218
|
+
deploymentId: parsed.data.deployment.id,
|
|
1219
|
+
description: parsed.data.deployment_status.description,
|
|
1220
|
+
environment: parsed.data.deployment.environment,
|
|
1221
|
+
repo: parsed.data.repository.full_name,
|
|
1222
|
+
state: parsed.data.deployment_status.state,
|
|
1223
|
+
statusCreatedAt: parsed.data.deployment_status.created_at
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
function deploymentStatusEventType(state) {
|
|
1227
|
+
switch (state) {
|
|
1228
|
+
case "queued":
|
|
1229
|
+
case "pending":
|
|
1230
|
+
case "in_progress":
|
|
1231
|
+
return `deployment.${state}`;
|
|
1232
|
+
case "success":
|
|
1233
|
+
return "deployment.succeeded";
|
|
1234
|
+
case "failure":
|
|
1235
|
+
return "deployment.failed";
|
|
1236
|
+
case "error":
|
|
1237
|
+
return "deployment.error";
|
|
1238
|
+
default:
|
|
1239
|
+
return void 0;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
function normalizeDeploymentStatusEvent(deliveryId, body) {
|
|
1243
|
+
const parsed = parseDeploymentStatusEvent(body);
|
|
1244
|
+
if (!parsed || parsed.action !== "created") return [];
|
|
1245
|
+
const eventType = deploymentStatusEventType(parsed.state);
|
|
1246
|
+
if (!eventType) return [];
|
|
1247
|
+
const outcome = eventType === "deployment.succeeded" ? "succeeded" : eventType === "deployment.failed" ? "failed" : eventType === "deployment.error" ? "reported an error" : eventType === "deployment.in_progress" ? "started" : `is ${parsed.state}`;
|
|
1248
|
+
const terminal = eventType === "deployment.succeeded" || eventType === "deployment.failed" || eventType === "deployment.error";
|
|
1249
|
+
return deploymentSourceTargets(parsed).map(
|
|
1250
|
+
({ completeOnTerminalEvent, resource }) => ({
|
|
1251
|
+
eventKey: gitHubEventKey2(deliveryId, eventType),
|
|
1252
|
+
eventType,
|
|
1253
|
+
occurredAtMs: providerTime(parsed.statusCreatedAt) ?? Date.now(),
|
|
1254
|
+
identifier: resource.identifier,
|
|
1255
|
+
...terminal && completeOnTerminalEvent ? { terminal: true } : void 0,
|
|
1256
|
+
trustedSummary: `${resource.label} ${outcome} (deployment ${parsed.deploymentId}).`,
|
|
1257
|
+
untrustedText: parsed.description ?? void 0
|
|
1258
|
+
})
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
function deploymentSourceTargets(input) {
|
|
1262
|
+
return [
|
|
1263
|
+
{
|
|
1264
|
+
completeOnTerminalEvent: true,
|
|
1265
|
+
resource: gitHubDeploymentSourceResource(input)
|
|
1266
|
+
},
|
|
1267
|
+
{
|
|
1268
|
+
completeOnTerminalEvent: false,
|
|
1269
|
+
resource: gitHubDeploymentSourceResource({
|
|
1270
|
+
commitSha: input.commitSha,
|
|
1271
|
+
repo: input.repo
|
|
1272
|
+
})
|
|
1273
|
+
}
|
|
1274
|
+
];
|
|
1275
|
+
}
|
|
1276
|
+
var issueCommentWebhookSchema = z2.object({
|
|
1277
|
+
action: z2.string(),
|
|
1278
|
+
comment: z2.object({
|
|
1279
|
+
body: z2.string(),
|
|
1280
|
+
user: z2.object({ login: z2.string().optional() }).optional()
|
|
1281
|
+
}),
|
|
1282
|
+
issue: z2.object({
|
|
1283
|
+
draft: z2.boolean().optional(),
|
|
1284
|
+
number: z2.number(),
|
|
1285
|
+
pull_request: z2.object({ url: z2.string().min(1) }).optional(),
|
|
1286
|
+
user: z2.object({
|
|
1287
|
+
email: z2.string().optional().nullable(),
|
|
1288
|
+
login: z2.string().optional().nullable()
|
|
1289
|
+
}).optional().nullable()
|
|
1290
|
+
}),
|
|
1291
|
+
repository: repositorySchema2
|
|
1292
|
+
});
|
|
1293
|
+
function normalizeIssueCommentEvents(deliveryId, body) {
|
|
1294
|
+
const parsed = issueCommentWebhookSchema.safeParse(body);
|
|
1295
|
+
if (!parsed.success || parsed.data.action !== "created") return [];
|
|
1296
|
+
const input = {
|
|
1297
|
+
number: parsed.data.issue.number,
|
|
1298
|
+
repo: parsed.data.repository.full_name
|
|
1299
|
+
};
|
|
1300
|
+
const author = parsed.data.comment.user?.login;
|
|
1301
|
+
if (parsed.data.issue.pull_request) {
|
|
1302
|
+
const eventType = "pull_request.comment.created";
|
|
1303
|
+
const resource = gitHubPullRequestResource(input);
|
|
1304
|
+
const data = pullRequestMatchData([
|
|
1305
|
+
pullRequestDraftData(parsed.data.issue.draft),
|
|
1306
|
+
pullRequestAuthorData(parsed.data.issue.user)
|
|
1307
|
+
]);
|
|
1308
|
+
return pullRequestTargets2(
|
|
1309
|
+
{
|
|
1310
|
+
eventKey: gitHubEventKey2(deliveryId, eventType),
|
|
1311
|
+
eventType,
|
|
1312
|
+
occurredAtMs: Date.now(),
|
|
1313
|
+
identifier: resource.identifier,
|
|
1314
|
+
trustedSummary: `${resource.label} received a comment${author ? ` from ${author}` : ""}.`,
|
|
1315
|
+
...data ? { data } : void 0,
|
|
1316
|
+
untrustedText: parsed.data.comment.body
|
|
1317
|
+
},
|
|
1318
|
+
input.repo
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
1321
|
+
const issue = gitHubIssueResource(input);
|
|
1322
|
+
const repository = gitHubRepositoryResource(input);
|
|
1323
|
+
return [
|
|
1324
|
+
{
|
|
1325
|
+
eventKey: gitHubEventKey2(deliveryId, "issue.comment.created"),
|
|
1326
|
+
eventType: "issue.comment.created",
|
|
1327
|
+
occurredAtMs: Date.now(),
|
|
1328
|
+
identifier: issue.identifier,
|
|
1329
|
+
trustedSummary: `${issue.label} received a comment${author ? ` from ${author}` : ""}.`,
|
|
1330
|
+
untrustedText: parsed.data.comment.body
|
|
1331
|
+
},
|
|
1332
|
+
{
|
|
1333
|
+
eventKey: gitHubEventKey2(deliveryId, "issue.comment.created"),
|
|
1334
|
+
eventType: "issue.comment.created",
|
|
1335
|
+
occurredAtMs: Date.now(),
|
|
1336
|
+
identifier: repository.identifier,
|
|
1337
|
+
trustedSummary: `${issue.label} received a comment${author ? ` from ${author}` : ""}.`,
|
|
1338
|
+
untrustedText: parsed.data.comment.body
|
|
1339
|
+
}
|
|
1340
|
+
];
|
|
1341
|
+
}
|
|
1342
|
+
var issueWebhookSchema = z2.object({
|
|
1343
|
+
action: z2.string(),
|
|
1344
|
+
issue: z2.object({
|
|
1345
|
+
body: z2.string().optional().nullable(),
|
|
1346
|
+
closed_at: z2.string().optional().nullable(),
|
|
1347
|
+
created_at: z2.string().optional(),
|
|
1348
|
+
number: z2.number(),
|
|
1349
|
+
title: z2.string().optional(),
|
|
1350
|
+
updated_at: z2.string().optional()
|
|
1351
|
+
}),
|
|
1352
|
+
repository: repositorySchema2
|
|
1353
|
+
});
|
|
1354
|
+
function issueEventText(issue) {
|
|
1355
|
+
const parts = [
|
|
1356
|
+
issue.title ? `Title: ${issue.title}` : void 0,
|
|
1357
|
+
issue.body?.trim() || void 0
|
|
1358
|
+
].filter((part) => part !== void 0);
|
|
1359
|
+
return parts.length > 0 ? parts.join("\n\n") : void 0;
|
|
1360
|
+
}
|
|
1361
|
+
function normalizeIssueEvents(deliveryId, body) {
|
|
1362
|
+
const parsed = issueWebhookSchema.safeParse(body);
|
|
1363
|
+
if (!parsed.success) return [];
|
|
1364
|
+
const state = parsed.data.action === "opened" ? "opened" : parsed.data.action === "closed" ? "closed" : parsed.data.action === "reopened" ? "reopened" : void 0;
|
|
1365
|
+
if (!state) return [];
|
|
1366
|
+
const input = {
|
|
1367
|
+
number: parsed.data.issue.number,
|
|
1368
|
+
repo: parsed.data.repository.full_name
|
|
1369
|
+
};
|
|
1370
|
+
const issue = gitHubIssueResource(input);
|
|
1371
|
+
const repository = gitHubRepositoryResource(input);
|
|
1372
|
+
const occurredAtMs = providerTime(
|
|
1373
|
+
state === "opened" ? parsed.data.issue.created_at : state === "closed" ? parsed.data.issue.closed_at : parsed.data.issue.updated_at
|
|
1374
|
+
) ?? Date.now();
|
|
1375
|
+
const untrustedText = issueEventText(parsed.data.issue);
|
|
1376
|
+
return [
|
|
1377
|
+
{
|
|
1378
|
+
eventKey: gitHubEventKey2(deliveryId, `issue.${state}`),
|
|
1379
|
+
eventType: `issue.${state}`,
|
|
1380
|
+
occurredAtMs,
|
|
1381
|
+
identifier: issue.identifier,
|
|
1382
|
+
trustedSummary: `${issue.label} was ${state}.`,
|
|
1383
|
+
...untrustedText ? { untrustedText } : void 0
|
|
1384
|
+
},
|
|
1385
|
+
{
|
|
1386
|
+
eventKey: gitHubEventKey2(deliveryId, `issue.${state}`),
|
|
1387
|
+
eventType: `issue.${state}`,
|
|
1388
|
+
occurredAtMs,
|
|
1389
|
+
identifier: repository.identifier,
|
|
1390
|
+
trustedSummary: `${issue.label} was ${state}.`,
|
|
1391
|
+
...untrustedText ? { untrustedText } : void 0
|
|
1392
|
+
}
|
|
1393
|
+
];
|
|
1394
|
+
}
|
|
1395
|
+
var pullRequestReviewCommentWebhookSchema = z2.object({
|
|
1396
|
+
action: z2.string(),
|
|
1397
|
+
comment: z2.object({
|
|
1398
|
+
body: z2.string(),
|
|
1399
|
+
user: z2.object({ login: z2.string().optional() }).optional()
|
|
1400
|
+
}),
|
|
1401
|
+
pull_request: z2.object({
|
|
1402
|
+
draft: z2.boolean().optional(),
|
|
1403
|
+
number: z2.number(),
|
|
1404
|
+
user: z2.object({
|
|
1405
|
+
email: z2.string().optional().nullable(),
|
|
1406
|
+
login: z2.string().optional().nullable()
|
|
1407
|
+
}).optional().nullable()
|
|
1408
|
+
}),
|
|
1409
|
+
repository: repositorySchema2
|
|
1410
|
+
});
|
|
1411
|
+
function normalizePullRequestReviewCommentEvent(deliveryId, body) {
|
|
1412
|
+
const parsed = pullRequestReviewCommentWebhookSchema.safeParse(body);
|
|
1413
|
+
if (!parsed.success || parsed.data.action !== "created") return [];
|
|
1414
|
+
const eventType = "pull_request.review_comment.created";
|
|
1415
|
+
const repo = parsed.data.repository.full_name;
|
|
1416
|
+
const resource = gitHubPullRequestResource({
|
|
1417
|
+
number: parsed.data.pull_request.number,
|
|
1418
|
+
repo
|
|
1419
|
+
});
|
|
1420
|
+
const author = parsed.data.comment.user?.login;
|
|
1421
|
+
const data = pullRequestMatchData([
|
|
1422
|
+
pullRequestDraftData(parsed.data.pull_request.draft),
|
|
1423
|
+
pullRequestAuthorData(parsed.data.pull_request.user)
|
|
1424
|
+
]);
|
|
1425
|
+
return pullRequestTargets2(
|
|
1426
|
+
{
|
|
1427
|
+
eventKey: gitHubEventKey2(deliveryId, eventType),
|
|
1428
|
+
eventType,
|
|
1429
|
+
occurredAtMs: Date.now(),
|
|
1430
|
+
identifier: resource.identifier,
|
|
1431
|
+
trustedSummary: `${resource.label} received an inline review comment${author ? ` from ${author}` : ""}.`,
|
|
1432
|
+
...data ? { data } : void 0,
|
|
1433
|
+
untrustedText: parsed.data.comment.body
|
|
1434
|
+
},
|
|
1435
|
+
repo
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1438
|
+
var pullRequestReviewWebhookSchema = z2.object({
|
|
1439
|
+
action: z2.string(),
|
|
1440
|
+
pull_request: z2.object({
|
|
1441
|
+
draft: z2.boolean().optional(),
|
|
1442
|
+
number: z2.number(),
|
|
1443
|
+
user: z2.object({
|
|
1444
|
+
email: z2.string().optional().nullable(),
|
|
1445
|
+
login: z2.string().optional().nullable()
|
|
1446
|
+
}).optional().nullable()
|
|
1447
|
+
}),
|
|
1448
|
+
repository: repositorySchema2,
|
|
1449
|
+
review: z2.object({
|
|
1450
|
+
body: z2.string().optional().nullable(),
|
|
1451
|
+
state: z2.string(),
|
|
1452
|
+
user: z2.object({ login: z2.string().optional() }).optional()
|
|
1453
|
+
})
|
|
1454
|
+
});
|
|
1455
|
+
function normalizePullRequestReviewEvent(deliveryId, body) {
|
|
1456
|
+
const parsed = pullRequestReviewWebhookSchema.safeParse(body);
|
|
1457
|
+
if (!parsed.success || parsed.data.action !== "submitted") return [];
|
|
1458
|
+
const reviewState = parsed.data.review.state.toUpperCase();
|
|
1459
|
+
const eventType = reviewState === "APPROVED" ? "pull_request.review.approved" : reviewState === "CHANGES_REQUESTED" ? "pull_request.review.changes_requested" : reviewState === "COMMENTED" ? "pull_request.review.commented" : void 0;
|
|
1460
|
+
if (!eventType) return [];
|
|
1461
|
+
const repo = parsed.data.repository.full_name;
|
|
1462
|
+
const resource = gitHubPullRequestResource({
|
|
1463
|
+
number: parsed.data.pull_request.number,
|
|
1464
|
+
repo
|
|
1465
|
+
});
|
|
1466
|
+
const reviewer = parsed.data.review.user?.login;
|
|
1467
|
+
const data = pullRequestMatchData([
|
|
1468
|
+
pullRequestDraftData(parsed.data.pull_request.draft),
|
|
1469
|
+
pullRequestAuthorData(parsed.data.pull_request.user)
|
|
1470
|
+
]);
|
|
1471
|
+
return pullRequestTargets2(
|
|
1472
|
+
{
|
|
1473
|
+
eventKey: gitHubEventKey2(deliveryId, eventType),
|
|
1474
|
+
eventType,
|
|
1475
|
+
occurredAtMs: Date.now(),
|
|
1476
|
+
identifier: resource.identifier,
|
|
1477
|
+
trustedSummary: eventType === "pull_request.review.approved" ? `${resource.label} was approved${reviewer ? ` by ${reviewer}` : ""}.` : eventType === "pull_request.review.changes_requested" ? `${resource.label} received requested changes${reviewer ? ` from ${reviewer}` : ""}.` : `${resource.label} received a review comment${reviewer ? ` from ${reviewer}` : ""}.`,
|
|
1478
|
+
...data ? { data } : void 0,
|
|
1479
|
+
untrustedText: parsed.data.review.body ?? void 0
|
|
1480
|
+
},
|
|
1481
|
+
repo
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1484
|
+
var pullRequestWebhookSchema = z2.object({
|
|
1485
|
+
action: z2.string(),
|
|
1486
|
+
pull_request: z2.object({
|
|
1487
|
+
body: z2.string().optional().nullable(),
|
|
1488
|
+
closed_at: z2.string().optional().nullable(),
|
|
1489
|
+
created_at: z2.string().optional(),
|
|
1490
|
+
draft: z2.boolean().optional(),
|
|
1491
|
+
merged: z2.boolean().optional(),
|
|
1492
|
+
merged_at: z2.string().optional().nullable(),
|
|
1493
|
+
number: z2.number(),
|
|
1494
|
+
title: z2.string().optional(),
|
|
1495
|
+
updated_at: z2.string().optional(),
|
|
1496
|
+
user: z2.object({
|
|
1497
|
+
email: z2.string().optional().nullable(),
|
|
1498
|
+
login: z2.string().optional().nullable()
|
|
1499
|
+
}).optional().nullable()
|
|
1500
|
+
}),
|
|
1501
|
+
repository: repositorySchema2
|
|
1502
|
+
});
|
|
1503
|
+
function providerTime(value) {
|
|
1504
|
+
if (!value) return void 0;
|
|
1505
|
+
const parsed = Date.parse(value);
|
|
1506
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
1507
|
+
}
|
|
1508
|
+
function pullRequestEventText(pullRequest) {
|
|
1509
|
+
const parts = [
|
|
1510
|
+
pullRequest.title ? `Title: ${pullRequest.title}` : void 0,
|
|
1511
|
+
pullRequest.body?.trim() || void 0
|
|
1512
|
+
].filter((part) => part !== void 0);
|
|
1513
|
+
return parts.length > 0 ? parts.join("\n\n") : void 0;
|
|
1514
|
+
}
|
|
1515
|
+
function pullRequestLifecycleEvents(input) {
|
|
1516
|
+
const data = pullRequestMatchData([
|
|
1517
|
+
pullRequestDraftData(input.isDraft),
|
|
1518
|
+
pullRequestAuthorData({
|
|
1519
|
+
email: input.authorEmail,
|
|
1520
|
+
login: input.authorUsername
|
|
1521
|
+
})
|
|
1522
|
+
]);
|
|
1523
|
+
return pullRequestTargets2(
|
|
1524
|
+
{
|
|
1525
|
+
eventKey: gitHubEventKey2(input.deliveryId, input.eventType),
|
|
1526
|
+
eventType: input.eventType,
|
|
1527
|
+
occurredAtMs: input.occurredAtMs,
|
|
1528
|
+
identifier: input.resource.identifier,
|
|
1529
|
+
...input.terminal ? { terminal: true } : void 0,
|
|
1530
|
+
trustedSummary: input.trustedSummary,
|
|
1531
|
+
...data ? { data } : void 0,
|
|
1532
|
+
...input.untrustedText ? { untrustedText: input.untrustedText } : void 0
|
|
1533
|
+
},
|
|
1534
|
+
input.repo
|
|
1535
|
+
);
|
|
1536
|
+
}
|
|
1537
|
+
function normalizePullRequestEvent(deliveryId, body) {
|
|
1538
|
+
const parsed = pullRequestWebhookSchema.safeParse(body);
|
|
1539
|
+
if (!parsed.success) return [];
|
|
1540
|
+
const repo = parsed.data.repository.full_name;
|
|
1541
|
+
const resource = gitHubPullRequestResource({
|
|
1542
|
+
number: parsed.data.pull_request.number,
|
|
1543
|
+
repo
|
|
1544
|
+
});
|
|
1545
|
+
const draft = parsed.data.pull_request.draft;
|
|
1546
|
+
const isDraft = typeof draft === "boolean" ? draft : void 0;
|
|
1547
|
+
const author = pullRequestAuthorData(parsed.data.pull_request.user);
|
|
1548
|
+
const untrustedText = pullRequestEventText(parsed.data.pull_request);
|
|
1549
|
+
if (parsed.data.action === "opened") {
|
|
1550
|
+
const openedAtMs = providerTime(parsed.data.pull_request.created_at) ?? Date.now();
|
|
1551
|
+
const events = pullRequestLifecycleEvents({
|
|
1552
|
+
...author,
|
|
1553
|
+
deliveryId,
|
|
1554
|
+
eventType: "pull_request.opened",
|
|
1555
|
+
...isDraft !== void 0 ? { isDraft } : void 0,
|
|
1556
|
+
occurredAtMs: openedAtMs,
|
|
1557
|
+
repo,
|
|
1558
|
+
resource,
|
|
1559
|
+
trustedSummary: `${resource.label} was opened.`,
|
|
1560
|
+
untrustedText
|
|
1561
|
+
});
|
|
1562
|
+
if (draft !== true) {
|
|
1563
|
+
events.push(
|
|
1564
|
+
...pullRequestLifecycleEvents({
|
|
1565
|
+
...author,
|
|
1566
|
+
deliveryId,
|
|
1567
|
+
eventType: "pull_request.ready_for_review",
|
|
1568
|
+
isDraft: false,
|
|
1569
|
+
occurredAtMs: openedAtMs,
|
|
1570
|
+
repo,
|
|
1571
|
+
resource,
|
|
1572
|
+
trustedSummary: `${resource.label} is ready for review.`,
|
|
1573
|
+
untrustedText
|
|
1574
|
+
})
|
|
1575
|
+
);
|
|
1576
|
+
}
|
|
1577
|
+
return events;
|
|
1578
|
+
}
|
|
1579
|
+
if (parsed.data.action === "ready_for_review") {
|
|
1580
|
+
return pullRequestLifecycleEvents({
|
|
1581
|
+
...author,
|
|
1582
|
+
deliveryId,
|
|
1583
|
+
eventType: "pull_request.ready_for_review",
|
|
1584
|
+
isDraft: false,
|
|
1585
|
+
occurredAtMs: providerTime(parsed.data.pull_request.updated_at) ?? Date.now(),
|
|
1586
|
+
repo,
|
|
1587
|
+
resource,
|
|
1588
|
+
trustedSummary: `${resource.label} is ready for review.`,
|
|
1589
|
+
untrustedText
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
if (parsed.data.action !== "closed") return [];
|
|
1593
|
+
const eventType = parsed.data.pull_request.merged ? "pull_request.merged" : "pull_request.closed_unmerged";
|
|
1594
|
+
return pullRequestLifecycleEvents({
|
|
1595
|
+
...author,
|
|
1596
|
+
deliveryId,
|
|
1597
|
+
eventType,
|
|
1598
|
+
...isDraft !== void 0 ? { isDraft } : void 0,
|
|
1599
|
+
occurredAtMs: providerTime(
|
|
1600
|
+
parsed.data.pull_request.merged ? parsed.data.pull_request.merged_at : parsed.data.pull_request.closed_at
|
|
1601
|
+
) ?? Date.now(),
|
|
1602
|
+
repo,
|
|
1603
|
+
resource,
|
|
1604
|
+
terminal: true,
|
|
1605
|
+
trustedSummary: eventType === "pull_request.merged" ? `${resource.label} was merged.` : `${resource.label} was closed without being merged.`
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1608
|
+
var releaseWebhookSchema = z2.object({
|
|
1609
|
+
action: z2.string(),
|
|
1610
|
+
release: z2.object({
|
|
1611
|
+
body: z2.string().optional().nullable(),
|
|
1612
|
+
draft: z2.boolean().optional(),
|
|
1613
|
+
id: z2.number(),
|
|
1614
|
+
name: z2.string().optional().nullable(),
|
|
1615
|
+
prerelease: z2.boolean().optional(),
|
|
1616
|
+
published_at: z2.string().optional().nullable(),
|
|
1617
|
+
tag_name: z2.string().min(1)
|
|
1618
|
+
}).passthrough(),
|
|
1619
|
+
repository: repositorySchema2
|
|
1620
|
+
}).passthrough();
|
|
1621
|
+
function releaseSourceTargets(input) {
|
|
1622
|
+
return [
|
|
1623
|
+
{
|
|
1624
|
+
completeOnTerminalEvent: true,
|
|
1625
|
+
resource: gitHubReleaseSourceResource(input)
|
|
1626
|
+
},
|
|
1627
|
+
{
|
|
1628
|
+
completeOnTerminalEvent: false,
|
|
1629
|
+
resource: gitHubReleaseSourceResource({ repo: input.repo })
|
|
1630
|
+
}
|
|
1631
|
+
];
|
|
1632
|
+
}
|
|
1633
|
+
function normalizeReleaseEvent(deliveryId, body) {
|
|
1634
|
+
const parsed = releaseWebhookSchema.safeParse(body);
|
|
1635
|
+
if (!parsed.success || parsed.data.action !== "published") return [];
|
|
1636
|
+
if (parsed.data.release.draft) return [];
|
|
1637
|
+
const eventType = "release.published";
|
|
1638
|
+
const repo = parsed.data.repository.full_name;
|
|
1639
|
+
const tag = parsed.data.release.tag_name;
|
|
1640
|
+
const untrustedParts = [
|
|
1641
|
+
tag ? `Tag: ${tag}` : void 0,
|
|
1642
|
+
parsed.data.release.name?.trim() ? `Name: ${parsed.data.release.name.trim()}` : void 0,
|
|
1643
|
+
parsed.data.release.body?.trim() || void 0
|
|
1644
|
+
].filter((part) => part !== void 0);
|
|
1645
|
+
const untrustedText = untrustedParts.length > 0 ? untrustedParts.join("\n\n") : void 0;
|
|
1646
|
+
return releaseSourceTargets({ repo, tag }).map(
|
|
1647
|
+
({ completeOnTerminalEvent, resource }) => ({
|
|
1648
|
+
eventKey: gitHubEventKey2(deliveryId, eventType),
|
|
1649
|
+
eventType,
|
|
1650
|
+
occurredAtMs: providerTime(parsed.data.release.published_at) ?? Date.now(),
|
|
1651
|
+
identifier: resource.identifier,
|
|
1652
|
+
...completeOnTerminalEvent ? { terminal: true } : void 0,
|
|
1653
|
+
trustedSummary: `${resource.label} was published (release ${parsed.data.release.id}).`,
|
|
1654
|
+
...untrustedText ? { untrustedText } : void 0
|
|
1655
|
+
})
|
|
1656
|
+
);
|
|
1657
|
+
}
|
|
1658
|
+
function normalizeGitHubResourceEvents(args) {
|
|
1659
|
+
switch (args.eventName) {
|
|
1660
|
+
case "deployment":
|
|
1661
|
+
return normalizeDeploymentEvent(args.deliveryId, args.body);
|
|
1662
|
+
case "deployment_status":
|
|
1663
|
+
return normalizeDeploymentStatusEvent(args.deliveryId, args.body);
|
|
1664
|
+
case "pull_request":
|
|
1665
|
+
return normalizePullRequestEvent(args.deliveryId, args.body);
|
|
1666
|
+
case "issues":
|
|
1667
|
+
return normalizeIssueEvents(args.deliveryId, args.body);
|
|
1668
|
+
case "pull_request_review":
|
|
1669
|
+
return normalizePullRequestReviewEvent(args.deliveryId, args.body);
|
|
1670
|
+
case "issue_comment":
|
|
1671
|
+
return normalizeIssueCommentEvents(args.deliveryId, args.body);
|
|
1672
|
+
case "pull_request_review_comment":
|
|
1673
|
+
return normalizePullRequestReviewCommentEvent(args.deliveryId, args.body);
|
|
1674
|
+
case "check_suite":
|
|
1675
|
+
return normalizeCheckSuiteEvents(
|
|
1676
|
+
args.deliveryId,
|
|
1677
|
+
args.body,
|
|
1678
|
+
args.checkSuiteFacts
|
|
1679
|
+
);
|
|
1680
|
+
case "release":
|
|
1681
|
+
return normalizeReleaseEvent(args.deliveryId, args.body);
|
|
1682
|
+
default:
|
|
1683
|
+
return [];
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
export {
|
|
1688
|
+
normalizePermissions,
|
|
1689
|
+
readGrantPermissions,
|
|
1690
|
+
GITHUB_ISSUE_EVENTS,
|
|
1691
|
+
GITHUB_ISSUE_SUGGESTED_EVENTS,
|
|
1692
|
+
gitHubIssueSubscribable,
|
|
1693
|
+
GITHUB_DEPLOYMENT_EVENTS,
|
|
1694
|
+
GITHUB_DEPLOYMENT_SUGGESTED_EVENTS,
|
|
1695
|
+
gitHubDeploymentSourceSubscribable,
|
|
1696
|
+
GITHUB_PULL_REQUEST_EVENTS,
|
|
1697
|
+
GITHUB_PULL_REQUEST_SUGGESTED_EVENTS,
|
|
1698
|
+
GITHUB_PULL_REQUEST_MATCH_FIELDS,
|
|
1699
|
+
gitHubPullRequestSubscribable,
|
|
1700
|
+
GITHUB_RELEASE_EVENTS,
|
|
1701
|
+
GITHUB_RELEASE_SUGGESTED_EVENTS,
|
|
1702
|
+
gitHubReleaseSourceSubscribable,
|
|
1703
|
+
gitHubRepositorySubscribable,
|
|
1704
|
+
GITHUB_APP_ID_ENV,
|
|
1705
|
+
GITHUB_APP_PRIVATE_KEY_ENV,
|
|
1706
|
+
GITHUB_INSTALLATION_ID_ENV,
|
|
1707
|
+
GITHUB_AUTH_TOKEN_ENV,
|
|
1708
|
+
GITHUB_AUTH_TOKEN_PLACEHOLDER,
|
|
1709
|
+
GITHUB_GRAPHQL_RESPONSE_BODY_LIMIT_BYTES,
|
|
1710
|
+
HTTP_READ_METHODS,
|
|
1711
|
+
USER_TOKEN_GRANTS,
|
|
1712
|
+
CREATE_TOOL_ROUTING_GUIDANCE,
|
|
1713
|
+
USER_WRITE_REQUIREMENTS,
|
|
1714
|
+
GitHubPluginSetupError,
|
|
1715
|
+
isRecord,
|
|
1716
|
+
readEnv,
|
|
1717
|
+
requireEnv,
|
|
1718
|
+
normalizeScopeList,
|
|
1719
|
+
githubRequest,
|
|
1720
|
+
credentialUnavailable,
|
|
1721
|
+
githubRepositoryFromUrl,
|
|
1722
|
+
githubRepositoryLeaseScope,
|
|
1723
|
+
githubRepositoryFromLeaseScope,
|
|
1724
|
+
resolveUserAccount,
|
|
1725
|
+
issueUserCredential,
|
|
1726
|
+
issueInstallationToken,
|
|
1727
|
+
issueInstallationCredential,
|
|
1728
|
+
createPermissionCache,
|
|
1729
|
+
loadCheckSuiteFacts,
|
|
1730
|
+
normalizeGitHubResourceEvents
|
|
1731
|
+
};
|