@sentry/junior-github 0.181.1 → 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.
@@ -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
+ };