|
| 1 | +# CLAUDE.md |
1 | 2 |
|
2 | | -Default to using Bun instead of Node.js. |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
3 | 4 |
|
4 | | -- Use `bun <file>` instead of `node <file>` or `ts-node <file>` |
5 | | -- Use `bun test` instead of `jest` or `vitest` |
6 | | -- Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild` |
7 | | -- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install` |
8 | | -- Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>` |
9 | | -- Use `bunx <package> <command>` instead of `npx <package> <command>` |
10 | | -- Bun automatically loads .env, so don't use dotenv. |
| 5 | +## What This Is |
11 | 6 |
|
12 | | -## APIs |
| 7 | +Skiphooks is a GitHub webhook server that forwards repository events (PRs, issues, pushes, releases, comments) to Slashwork streams via GraphQL. Built with Bun — no Node.js, no Express, no runtime dependencies. |
13 | 8 |
|
14 | | -- `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`. |
15 | | -- `bun:sqlite` for SQLite. Don't use `better-sqlite3`. |
16 | | -- `Bun.redis` for Redis. Don't use `ioredis`. |
17 | | -- `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`. |
18 | | -- `WebSocket` is built-in. Don't use `ws`. |
19 | | -- Prefer `Bun.file` over `node:fs`'s readFile/writeFile |
20 | | -- Bun.$`ls` instead of execa. |
| 9 | +## Commands |
21 | 10 |
|
22 | | -## Testing |
| 11 | +```sh |
| 12 | +bun install # Install dependencies |
| 13 | +bun run dev # Dev server with --watch |
| 14 | +bun run start # Production server |
| 15 | +bun test # Run all tests |
| 16 | +bun test --filter "handler" # Run specific tests |
| 17 | +bunx tsc --noEmit # Type-check without emitting |
| 18 | +./test-webhook.sh skipper # Send test webhook to a route |
| 19 | +./test-webhook.sh skjs http://localhost:3000 # Custom base URL |
| 20 | +``` |
23 | 21 |
|
24 | | -Use `bun test` to run tests. |
| 22 | +## Bun Rules |
25 | 23 |
|
26 | | -```ts#index.test.ts |
27 | | -import { test, expect } from "bun:test"; |
| 24 | +Default to Bun for everything. Bun auto-loads `.env` — don't use dotenv. |
28 | 25 |
|
29 | | -test("hello world", () => { |
30 | | - expect(1).toBe(1); |
31 | | -}); |
32 | | -``` |
| 26 | +- `Bun.serve()` for HTTP — don't use Express |
| 27 | +- `bun:test` for testing — don't use Jest/Vitest |
| 28 | +- `Bun.file` over `node:fs` readFile/writeFile |
| 29 | +- `bun install` / `bun run` / `bunx` — not npm/yarn/npx |
33 | 30 |
|
34 | | -## Frontend |
35 | | - |
36 | | -Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind. |
37 | | - |
38 | | -Server: |
39 | | - |
40 | | -```ts#index.ts |
41 | | -import index from "./index.html" |
42 | | - |
43 | | -Bun.serve({ |
44 | | - routes: { |
45 | | - "/": index, |
46 | | - "/api/users/:id": { |
47 | | - GET: (req) => { |
48 | | - return new Response(JSON.stringify({ id: req.params.id })); |
49 | | - }, |
50 | | - }, |
51 | | - }, |
52 | | - // optional websocket support |
53 | | - websocket: { |
54 | | - open: (ws) => { |
55 | | - ws.send("Hello, world!"); |
56 | | - }, |
57 | | - message: (ws, message) => { |
58 | | - ws.send(message); |
59 | | - }, |
60 | | - close: (ws) => { |
61 | | - // handle close |
62 | | - } |
63 | | - }, |
64 | | - development: { |
65 | | - hmr: true, |
66 | | - console: true, |
67 | | - } |
68 | | -}) |
69 | | -``` |
| 31 | +## Architecture |
70 | 32 |
|
71 | | -HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle. |
| 33 | +**Request flow:** `Bun.serve() → route match → signature verify → handler dispatch → format markdown → GraphQL post` |
72 | 34 |
|
73 | | -```html#index.html |
74 | | -<html> |
75 | | - <body> |
76 | | - <h1>Hello, world!</h1> |
77 | | - <script type="module" src="./frontend.tsx"></script> |
78 | | - </body> |
79 | | -</html> |
80 | | -``` |
| 35 | +### Entry point: `src/index.ts` |
81 | 36 |
|
82 | | -With the following `frontend.tsx`: |
| 37 | +`Bun.serve()` matches URLs via regex `/github/<route-name>`. Routes are defined in root `config.ts`. On each request: verify HMAC-SHA256 signature, look up handler by `x-github-event` header, check action relevance, format to markdown, POST to Slashwork GraphQL. |
83 | 38 |
|
84 | | -```tsx#frontend.tsx |
85 | | -import React from "react"; |
86 | | -import { createRoot } from "react-dom/client"; |
| 39 | +### Config: root `config.ts` + `src/config.ts` |
87 | 40 |
|
88 | | -// import .css files directly and it works |
89 | | -import './index.css'; |
| 41 | +Two-level config system: |
| 42 | +- **`src/config.ts`** — types (`SkiphooksConfig`, `GroupConfig`, `RouteConfig`) and `loadConfig()` with validation |
| 43 | +- **Root `config.ts`** — runtime config instance with actual values |
90 | 44 |
|
91 | | -const root = createRoot(document.body); |
| 45 | +Routes support two modes: |
| 46 | +- **Group-based:** `{ group: "skipper" }` — references a named group in `config.groups` that provides `id` and `authToken` |
| 47 | +- **Direct stream:** `{ streamId: "...", authToken: "..." }` — standalone stream config |
92 | 48 |
|
93 | | -export default function Frontend() { |
94 | | - return <h1>Hello, world!</h1>; |
95 | | -} |
| 49 | +### Handlers: `src/handlers/` |
96 | 50 |
|
97 | | -root.render(<Frontend />); |
98 | | -``` |
| 51 | +Each handler implements `EventHandler` interface (`types.ts`): |
| 52 | +- `isRelevantAction(action?)` — filters which GitHub actions to process |
| 53 | +- `format(payload)` — returns `{ markdown: string }` for Slashwork |
99 | 54 |
|
100 | | -Then, run index.ts |
| 55 | +Handlers: `pull-request.ts`, `issues.ts`, `issue-comment.ts`, `push.ts`, `release.ts` |
101 | 56 |
|
102 | | -```sh |
103 | | -bun --hot ./index.ts |
104 | | -``` |
| 57 | +### Slashwork client: `src/slashwork.ts` |
| 58 | + |
| 59 | +GraphQL client with Bearer token auth. `postToSlashwork()` sends formatted markdown to a target stream/group. `validateConnection()` checks auth on startup. |
| 60 | + |
| 61 | +### Webhook verification: `src/webhook.ts` |
| 62 | + |
| 63 | +HMAC-SHA256 signature verification using `node:crypto`. Compares `x-hub-signature-256` header against computed hash. |
| 64 | + |
| 65 | +## Environment Variables |
| 66 | + |
| 67 | +See `.env.example`. Key vars: |
| 68 | +- `GITHUB_WEBHOOK_SECRET` — shared secret for webhook signature verification |
| 69 | +- `SLASHWORK_GRAPHQL_URL` — GraphQL endpoint |
| 70 | +- `SLASHWORK_AUTH_TOKEN_SKIPPER` / `SLASHWORK_AUTH_TOKEN_SKJS` — per-group auth tokens |
| 71 | +- `PORT` — server port (default 3000) |
| 72 | + |
| 73 | +## Deployment |
105 | 74 |
|
106 | | -For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`. |
| 75 | +Clever Cloud via GitHub Actions. Push to `main` → typecheck + tests → auto-deploy. |
0 commit comments