@theholocron/github-client 1.17.0 → 1.19.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/README.md +39 -0
- package/dist/index.d.mts +98 -1
- package/dist/index.mjs +57 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -51,3 +51,42 @@ const blob = await client.git.createBlob("owner/name", "file contents");
|
|
|
51
51
|
| `topics` | `setTopics` |
|
|
52
52
|
| `properties` | `setProperties` |
|
|
53
53
|
| `git` | `getRef`, `getCommit`, `getTree`, `getContents`, `createBlob`, `createTree`, `createCommit`, `createRef`, `updateRef`, `createPull` |
|
|
54
|
+
| `checks` | `createCheckRun` |
|
|
55
|
+
|
|
56
|
+
## Webhooks
|
|
57
|
+
|
|
58
|
+
GitHub's own inbound-webhook mechanics — signature verification and
|
|
59
|
+
header/payload shapes — as standalone functions, not part of
|
|
60
|
+
`createGitHubClient()`: these verify a _delivery this package's consumer
|
|
61
|
+
received_, not an outbound REST call, so they need a webhook secret
|
|
62
|
+
instead of an API token.
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import {
|
|
66
|
+
parseGitHubWebhookHeaders,
|
|
67
|
+
verifyGitHubWebhookSignature,
|
|
68
|
+
type GitHubPushWebhookPayload,
|
|
69
|
+
} from "@theholocron/github-client";
|
|
70
|
+
|
|
71
|
+
const { event, delivery, signature } = parseGitHubWebhookHeaders(req.headers);
|
|
72
|
+
const ok = verifyGitHubWebhookSignature({ body: rawBody, signature, secret: webhookSecret });
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`verifyGitHubWebhookSignature({ body, signature, secret })` — `X-Hub-
|
|
76
|
+
Signature-256` verification (HMAC-SHA256 over the raw body,
|
|
77
|
+
`timingSafeEqual`-compared). Returns `false` for any failure to verify
|
|
78
|
+
(empty secret, missing/malformed signature, mismatch) — never throws;
|
|
79
|
+
the caller decides how to surface that.
|
|
80
|
+
|
|
81
|
+
`parseGitHubWebhookHeaders(headers)` — extracts `event`, `delivery`, and
|
|
82
|
+
`signature` from GitHub's three webhook headers, case-insensitively.
|
|
83
|
+
|
|
84
|
+
`GitHubInstallationWebhookPayload`, `GitHubPushWebhookPayload`,
|
|
85
|
+
`GitHubPullRequestWebhookPayload` — the delivery body shapes, scoped to
|
|
86
|
+
the fields a consumer reads today (not a full re-typing of every field
|
|
87
|
+
GitHub sends).
|
|
88
|
+
|
|
89
|
+
A consumer owns what to _do_ with a verified delivery — which event
|
|
90
|
+
categories matter, how to normalize them into its own domain shape.
|
|
91
|
+
`@theholocron/sentinel`'s `parseWebhookEvent()` is the reference
|
|
92
|
+
consumer.
|
package/dist/index.d.mts
CHANGED
|
@@ -11,6 +11,33 @@ interface GitHubClientOptions {
|
|
|
11
11
|
errors?: ErrorSink;
|
|
12
12
|
}
|
|
13
13
|
//#endregion
|
|
14
|
+
//#region src/checks/checks.d.ts
|
|
15
|
+
type CheckRunStatus = "queued" | "in_progress" | "completed";
|
|
16
|
+
type CheckRunConclusion = "success" | "failure" | "neutral" | "cancelled" | "timed_out" | "action_required" | "skipped";
|
|
17
|
+
interface CheckRunOutput {
|
|
18
|
+
title: string;
|
|
19
|
+
summary: string;
|
|
20
|
+
text?: string;
|
|
21
|
+
}
|
|
22
|
+
interface CreateCheckRunInput {
|
|
23
|
+
name: string;
|
|
24
|
+
/** The commit SHA the check run attaches to. */
|
|
25
|
+
head_sha: string;
|
|
26
|
+
/** Defaults to `"queued"` (GitHub's own default) when omitted. */
|
|
27
|
+
status?: CheckRunStatus;
|
|
28
|
+
/** Required when `status` is `"completed"`. */
|
|
29
|
+
conclusion?: CheckRunConclusion;
|
|
30
|
+
output?: CheckRunOutput;
|
|
31
|
+
}
|
|
32
|
+
interface GitHubCheckRun {
|
|
33
|
+
id: number;
|
|
34
|
+
name: string;
|
|
35
|
+
head_sha: string;
|
|
36
|
+
status: CheckRunStatus;
|
|
37
|
+
conclusion: CheckRunConclusion | null;
|
|
38
|
+
html_url: string;
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
14
41
|
//#region src/environments/environments.d.ts
|
|
15
42
|
interface GitHubEnvironment {
|
|
16
43
|
name: string;
|
|
@@ -228,11 +255,81 @@ interface WorkflowRunFilter {
|
|
|
228
255
|
status?: string;
|
|
229
256
|
}
|
|
230
257
|
//#endregion
|
|
258
|
+
//#region src/webhooks/webhooks.d.ts
|
|
259
|
+
/**
|
|
260
|
+
* GitHub's own webhook mechanics — signature verification and header/
|
|
261
|
+
* payload shapes. Pure functions, no REST call, no auth token: these
|
|
262
|
+
* operate on an *inbound* delivery, the mirror of everything else this
|
|
263
|
+
* package does (outbound REST calls), so they're standalone exports
|
|
264
|
+
* rather than part of `createGitHubClient()`'s returned object.
|
|
265
|
+
*
|
|
266
|
+
* A consumer (a GitHub App receiving webhooks) owns what to *do* with a
|
|
267
|
+
* verified delivery — which event categories matter, how to normalize
|
|
268
|
+
* them — the same split `holocron-plugin-clerk`'s `parseWebhook` draws
|
|
269
|
+
* between Svix verification and Clerk-specific `AuthEvent` normalization,
|
|
270
|
+
* just with the verification half living here instead of inline, since
|
|
271
|
+
* "how GitHub signs and shapes a webhook" is vendor knowledge this
|
|
272
|
+
* package already owns for every other GitHub API surface.
|
|
273
|
+
*/
|
|
274
|
+
/**
|
|
275
|
+
* Verifies a GitHub webhook delivery's `X-Hub-Signature-256` header —
|
|
276
|
+
* HMAC-SHA256 over the raw body, keyed by the webhook's configured
|
|
277
|
+
* secret, `timingSafeEqual`-compared.
|
|
278
|
+
* https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries
|
|
279
|
+
*
|
|
280
|
+
* Returns `false` (never throws) for any failure to verify — a missing/
|
|
281
|
+
* empty secret, a missing/malformed signature header, or a mismatch —
|
|
282
|
+
* so the caller decides how to surface that (e.g. as its own error type).
|
|
283
|
+
*/
|
|
284
|
+
declare function verifyGitHubWebhookSignature(input: {
|
|
285
|
+
body: string | Buffer;
|
|
286
|
+
signature: string | undefined;
|
|
287
|
+
secret: string;
|
|
288
|
+
}): boolean;
|
|
289
|
+
interface GitHubWebhookHeaders {
|
|
290
|
+
/** `X-GitHub-Event` — the event category, e.g. `"push"`, `"pull_request"`. */
|
|
291
|
+
event: string | undefined;
|
|
292
|
+
/** `X-GitHub-Delivery` — GitHub's per-delivery id, useful for idempotency/logging. */
|
|
293
|
+
delivery: string | undefined;
|
|
294
|
+
/** `X-Hub-Signature-256` — pass straight to `verifyGitHubWebhookSignature`. */
|
|
295
|
+
signature: string | undefined;
|
|
296
|
+
}
|
|
297
|
+
/** Extracts GitHub's three webhook headers, case-insensitively (Node's raw headers may arrive lower-cased, or as an array when a header repeats). */
|
|
298
|
+
declare function parseGitHubWebhookHeaders(headers: Record<string, string | string[] | undefined>): GitHubWebhookHeaders;
|
|
299
|
+
interface GitHubInstallationWebhookPayload {
|
|
300
|
+
action: string;
|
|
301
|
+
installation: {
|
|
302
|
+
id: number;
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
interface GitHubPushWebhookPayload {
|
|
306
|
+
ref: string;
|
|
307
|
+
installation?: {
|
|
308
|
+
id: number;
|
|
309
|
+
};
|
|
310
|
+
repository: {
|
|
311
|
+
full_name: string;
|
|
312
|
+
default_branch: string;
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
interface GitHubPullRequestWebhookPayload {
|
|
316
|
+
action: string;
|
|
317
|
+
installation?: {
|
|
318
|
+
id: number;
|
|
319
|
+
};
|
|
320
|
+
repository: {
|
|
321
|
+
full_name: string;
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
//#endregion
|
|
231
325
|
//#region src/index.d.ts
|
|
232
326
|
declare function createGitHubClient(opts: GitHubClientOptions): {
|
|
233
327
|
branches: {
|
|
234
328
|
protectBranch: (repo: string, branch: string, payload: Record<string, unknown>) => Promise<void>;
|
|
235
329
|
};
|
|
330
|
+
checks: {
|
|
331
|
+
createCheckRun: (repo: string, input: CreateCheckRunInput) => Promise<GitHubCheckRun>;
|
|
332
|
+
};
|
|
236
333
|
environments: {
|
|
237
334
|
listEnvironments: (repo: string) => Promise<GitHubEnvironment[]>;
|
|
238
335
|
upsertEnvironment: (repo: string, name: string, body: Record<string, unknown>) => Promise<void>;
|
|
@@ -332,4 +429,4 @@ declare function createGitHubClient(opts: GitHubClientOptions): {
|
|
|
332
429
|
};
|
|
333
430
|
type GitHubClient = ReturnType<typeof createGitHubClient>;
|
|
334
431
|
//#endregion
|
|
335
|
-
export { type CodeScanningSetupResult, type CreatePagesPayload, type CreatePullInput, type GitBlob, type GitCommit, type GitContents, GitHubClient, type GitHubClientOptions, type GitHubContents, type GitHubEnvironment, type GitHubIssue, type GitHubLabel, type GitHubMilestone, type GitHubPages, type GitHubPublicKey, type GitHubPullRequest, type GitHubRepo, type GitHubRuleset, type GitHubUser, type GitHubWorkflowRun, type GitPull, type GitRef, type GitTree, type GitTreeItem, type IssueSearchParams, type PagesBuildStatus, type PagesBuildType, type SecretScope, type TeamPermission, type UpdatePagesPayload, type WorkflowRunFilter, createGitHubClient };
|
|
432
|
+
export { type CheckRunConclusion, type CheckRunOutput, type CheckRunStatus, type CodeScanningSetupResult, type CreateCheckRunInput, type CreatePagesPayload, type CreatePullInput, type GitBlob, type GitCommit, type GitContents, type GitHubCheckRun, GitHubClient, type GitHubClientOptions, type GitHubContents, type GitHubEnvironment, type GitHubInstallationWebhookPayload, type GitHubIssue, type GitHubLabel, type GitHubMilestone, type GitHubPages, type GitHubPublicKey, type GitHubPullRequest, type GitHubPullRequestWebhookPayload, type GitHubPushWebhookPayload, type GitHubRepo, type GitHubRuleset, type GitHubUser, type GitHubWebhookHeaders, type GitHubWorkflowRun, type GitPull, type GitRef, type GitTree, type GitTreeItem, type IssueSearchParams, type PagesBuildStatus, type PagesBuildType, type SecretScope, type TeamPermission, type UpdatePagesPayload, type WorkflowRunFilter, createGitHubClient, parseGitHubWebhookHeaders, verifyGitHubWebhookSignature };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ProviderApiError, createRestClient } from "@theholocron/http-client";
|
|
2
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
2
3
|
//#region src/utils.ts
|
|
3
4
|
function createGitHubRestClient(opts) {
|
|
4
5
|
return createRestClient({
|
|
@@ -28,6 +29,14 @@ function branches(rest) {
|
|
|
28
29
|
}) };
|
|
29
30
|
}
|
|
30
31
|
//#endregion
|
|
32
|
+
//#region src/checks/checks.ts
|
|
33
|
+
function checks(rest) {
|
|
34
|
+
return { createCheckRun: (repo, input) => rest.request(`${repoBase(repo)}/check-runs`, {
|
|
35
|
+
method: "POST",
|
|
36
|
+
body: input
|
|
37
|
+
}) };
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
31
40
|
//#region src/environments/environments.ts
|
|
32
41
|
function environments(rest) {
|
|
33
42
|
return {
|
|
@@ -344,11 +353,58 @@ function workflows(rest) {
|
|
|
344
353
|
};
|
|
345
354
|
}
|
|
346
355
|
//#endregion
|
|
356
|
+
//#region src/webhooks/webhooks.ts
|
|
357
|
+
/**
|
|
358
|
+
* GitHub's own webhook mechanics — signature verification and header/
|
|
359
|
+
* payload shapes. Pure functions, no REST call, no auth token: these
|
|
360
|
+
* operate on an *inbound* delivery, the mirror of everything else this
|
|
361
|
+
* package does (outbound REST calls), so they're standalone exports
|
|
362
|
+
* rather than part of `createGitHubClient()`'s returned object.
|
|
363
|
+
*
|
|
364
|
+
* A consumer (a GitHub App receiving webhooks) owns what to *do* with a
|
|
365
|
+
* verified delivery — which event categories matter, how to normalize
|
|
366
|
+
* them — the same split `holocron-plugin-clerk`'s `parseWebhook` draws
|
|
367
|
+
* between Svix verification and Clerk-specific `AuthEvent` normalization,
|
|
368
|
+
* just with the verification half living here instead of inline, since
|
|
369
|
+
* "how GitHub signs and shapes a webhook" is vendor knowledge this
|
|
370
|
+
* package already owns for every other GitHub API surface.
|
|
371
|
+
*/
|
|
372
|
+
/**
|
|
373
|
+
* Verifies a GitHub webhook delivery's `X-Hub-Signature-256` header —
|
|
374
|
+
* HMAC-SHA256 over the raw body, keyed by the webhook's configured
|
|
375
|
+
* secret, `timingSafeEqual`-compared.
|
|
376
|
+
* https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries
|
|
377
|
+
*
|
|
378
|
+
* Returns `false` (never throws) for any failure to verify — a missing/
|
|
379
|
+
* empty secret, a missing/malformed signature header, or a mismatch —
|
|
380
|
+
* so the caller decides how to surface that (e.g. as its own error type).
|
|
381
|
+
*/
|
|
382
|
+
function verifyGitHubWebhookSignature(input) {
|
|
383
|
+
if (!input.secret || !input.signature || !input.signature.startsWith("sha256=")) return false;
|
|
384
|
+
const bodyStr = typeof input.body === "string" ? input.body : input.body.toString("utf8");
|
|
385
|
+
const provided = Buffer.from(input.signature.slice(7), "hex");
|
|
386
|
+
const computed = createHmac("sha256", input.secret).update(bodyStr).digest();
|
|
387
|
+
return provided.length === computed.length && timingSafeEqual(provided, computed);
|
|
388
|
+
}
|
|
389
|
+
/** Extracts GitHub's three webhook headers, case-insensitively (Node's raw headers may arrive lower-cased, or as an array when a header repeats). */
|
|
390
|
+
function parseGitHubWebhookHeaders(headers) {
|
|
391
|
+
const find = (name) => {
|
|
392
|
+
const target = name.toLowerCase();
|
|
393
|
+
for (const [k, v] of Object.entries(headers)) if (k.toLowerCase() === target) return Array.isArray(v) ? v[0] : v;
|
|
394
|
+
};
|
|
395
|
+
return {
|
|
396
|
+
event: find("x-github-event"),
|
|
397
|
+
delivery: find("x-github-delivery"),
|
|
398
|
+
signature: find("x-hub-signature-256")
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
//#endregion
|
|
347
402
|
//#region src/index.ts
|
|
348
403
|
function createGitHubClient(opts) {
|
|
349
404
|
const rest = createGitHubRestClient(opts);
|
|
350
405
|
return {
|
|
351
406
|
branches: branches(rest),
|
|
407
|
+
checks: checks(rest),
|
|
352
408
|
environments: environments(rest),
|
|
353
409
|
git: git(rest),
|
|
354
410
|
issues: issues(rest),
|
|
@@ -367,4 +423,4 @@ function createGitHubClient(opts) {
|
|
|
367
423
|
};
|
|
368
424
|
}
|
|
369
425
|
//#endregion
|
|
370
|
-
export { createGitHubClient };
|
|
426
|
+
export { createGitHubClient, parseGitHubWebhookHeaders, verifyGitHubWebhookSignature };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theholocron/github-client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.0",
|
|
4
4
|
"description": "A TypeScript client for the GitHub REST API",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"github",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
],
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@theholocron/observability": "^0.3.0",
|
|
36
|
-
"@theholocron/http-client": "^1.
|
|
36
|
+
"@theholocron/http-client": "^1.19.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@theholocron/cli": "5.0.0-alpha.3",
|