Contributing
This page is for working on webhook-it itself. To just use it, start at Installation.
The stack
| Layer | Choice | Version |
|---|---|---|
| Runtime / package manager / bundler | Bun | >=1.3.0 |
| Language | TypeScript | ~5.7 |
| Terminal UI | OpenTUI + Solid | @opentui/* 0.2, solid-js 1.9 |
| Validation | zod | ^3.24 |
| Persistence | bun:sqlite | built into Bun |
| Tunnel | ngrok binary | installed by the user |
Bun is the whole toolchain — it runs TypeScript directly, installs dependencies, and compiles the standalone binary. No external service other than the tunnel.
Monorepo structure
webhook-it/
├── apps/
│ └── cli/ @carlos3g/webhook-it — the interactive dashboard
│ ├── src/
│ │ ├── index.ts entry: args + `wi apply`, lazy-loads the UI
│ │ ├── app.tsx the dashboard (state, daemon, keyboard, layout)
│ │ └── theme.ts colour palette
│ ├── build.ts Bun.build → standalone binary
│ ├── bunfig.toml preloads the OpenTUI Solid JSX runtime
│ └── tsconfig.json
├── packages/
│ ├── core/ @webhook-it/core — the daemon and the core
│ │ └── src/
│ │ ├── index.ts the package's public API
│ │ ├── daemon.ts HTTP ingest + forward server
│ │ ├── storage.ts bun:sqlite persistence
│ │ ├── forwarder.ts POST to the local target
│ │ ├── config.ts ~/.webhook-it/config.json
│ │ ├── paths.ts paths under ~/.webhook-it/
│ │ ├── project.ts .webhook-it.json + `wi apply` reconcile
│ │ ├── ids.ts short id generator
│ │ └── tunnel/ngrok.ts ngrok tunnel adapter
│ ├── shared/ @webhook-it/shared — types + zod schemas
│ │ └── src/
│ │ ├── endpoint.ts Endpoint, endpointNameSchema
│ │ └── event.ts WebhookEvent, httpMethodSchema
│ └── tsconfig/ @webhook-it/tsconfig — base tsconfigs
├── docs/ project documentation (Markdown)
├── website/ this documentation site (Docusaurus)
└── package.json Bun workspaces declared here
The layering is strict — see Architecture:
cli → core → shared. shared has no runtime APIs; core has no UI
dependency; cli is presentation only.
Development commands
bun install
bun run typecheck # tsc --noEmit across all packages
bun run dev # run the dashboard from source, with hot reload
bun run build # compile apps/cli/dist/wi (standalone binary)
bun run lint # eslint
bun run lint:fix # eslint --fix
Each is wired through Bun workspace filters in the root package.json.
For iterating on the dashboard, bun run dev is the fastest loop — it runs
straight from source with hot reload, no build step.
Code conventions
- Strict TypeScript —
strict,noUncheckedIndexedAccess,verbatimModuleSyntax(seepackages/tsconfig/base.json). - ESM — relative imports carry the
.jsextension (even when they point at.ts/.tsx); type-only imports useimport type. - English everywhere — documentation, comments, UI text, errors and identifiers.
- Synchronous storage — the
Storageclass is synchronous on purpose; a single-user local database has no real concurrency to manage.
How to test
There is no automated suite yet — an early backlog item (see the Roadmap). Current validation is manual:
- Typecheck + build:
bun run typecheck && bun run build. - Run the dashboard:
bun run dev, or the compiledapps/cli/dist/wi. Use an isolatedHOMEso you do not touch real state:HOME=/tmp/wi-test ./apps/cli/dist/wi - End to end: start the daemon with u (local mode), then send
webhooks with
curltohttp://127.0.0.1:4505/w/<name>and watch them in the Events pane. wi apply: drop a.webhook-it.jsonin a scratch directory and runHOME=/tmp/wi-test ./apps/cli/dist/wi applyfrom it — re-run it to confirm it is idempotent.
An automated suite (bun test covering storage and forwarder) is the
natural next step.
The build
apps/cli/build.ts calls Bun.build with @opentui/solid/bun-plugin (which
compiles the Solid JSX) and compile (which embeds the Bun runtime). The result
is a self-contained executable — apps/cli/dist/wi. apps/cli/bunfig.toml
preloads the same Solid runtime for bun run dev.
Working on this documentation site
The site you are reading lives in website/ and is built with
Docusaurus:
cd website
npm install
npm start # dev server with hot reload
npm run build # production build into website/build
The terminal screenshots are generated, not captured — website/scripts/gen-assets.mjs
renders them as SVG. Re-run it after changing the script:
node website/scripts/gen-assets.mjs
Next
- Architecture — the deep technical view.
- Roadmap — what needs doing.