# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview This is a modular Node.js webhook server for Gogs (Git service) that receives webhooks and executes configurable shell commands based on events. It uses **zero external dependencies** - only built-in Node.js modules. ## Architecture ### Module Structure The codebase follows a clean separation of concerns with five main modules: ``` index.js (Entry Point) ├── server.js (HTTP Server) │ └── webhookHandler.js (Event Management) ├── jobQueue.js (Job Queue & Persistence) │ └── commandExecutor.js (Command Execution) ├── config.js (Configuration) └── logger.js (Logging) ``` ### Key Components **WebhookServer (src/server.js)** - HTTP server using Node.js `http` module - Routes: `POST /webhook`, `GET /health` - Provides `getHandler()` to access WebhookHandler for callback registration - Binds to configurable host (0.0.0.0 by default) and port (3000 by default) **WebhookHandler (src/webhookHandler.js)** - Event-driven callback system - Two callback types: - `handler.on(eventType, callback)` - Event-specific callbacks - `handler.onAny(callback)` - Global callbacks for all events - Callbacks receive: `(payload, headers)` or `(eventType, payload, headers)` for global **JobQueue (src/jobQueue.js)** - Persistent job queue with FIFO (first-in-first-out) processing - Ensures only one job executes at a time to prevent conflicts - Webhooks are accepted immediately and added to queue - Queue state persisted to `queue.json` for crash recovery - Automatically resumes processing pending jobs after restart - Jobs that were running during shutdown are reset to pending state **CommandExecutor (src/commandExecutor.js)** - Variable substitution engine using `{{variable}}` syntax - Executes shell commands via `/bin/bash` with timeout protection - Extracts 16+ variables from webhook payloads: `{{repo}}`, `{{branch}}`, `{{pusher}}`, `{{commit}}`, `{{pr_number}}`, `{{issue_number}}`, `{{issue_title}}`, etc. - Default timeout: 300000ms (5 minutes), configurable via `COMMAND_TIMEOUT` **Config (src/config.js)** - Loads `.env` file synchronously on startup - Merges with `process.env` (env vars take precedence) - Provides type-safe getters: `get()`, `getBoolean()`, `getNumber()` - Exported as singleton **Logger (src/logger.js)** - Console-based logger with ANSI colors and timestamps - Methods: `info()`, `error()`, `webhook()` - Exported as singleton ### Application Flow 1. `index.js` loads config and creates WebhookServer 2. JobQueue loads persisted queue state from `queue.json` (if exists) 3. Gets enabled events from config (`WEBHOOK_PUSH_ENABLED=true`, etc.) 4. Registers callbacks for enabled events (webhooks → enqueue jobs) 5. Starts server on configured host:port 6. On webhook: parses body → triggers callbacks → enqueues job → responds immediately 7. JobQueue processes jobs sequentially (one at a time, FIFO) 8. Each job: extracts variables → substitutes in command → executes → logs results 9. Graceful shutdown on SIGTERM/SIGINT: saves queue state and stops server ## Development Commands ```bash npm start # Start production server npm run dev # Start with auto-reload (--watch flag) node examples/with-callbacks.js # Run callback example ``` **Testing endpoints:** ```bash curl http://localhost:3000/health curl -X POST http://localhost:3000/webhook \ -H "Content-Type: application/json" \ -H "X-Gogs-Event: push" \ -H "X-Gogs-Delivery: 12345" \ -d '{"ref":"refs/heads/main","repository":{"name":"test-repo","full_name":"user/test-repo"},"pusher":{"username":"testuser"},"commits":[{"id":"abc123","message":"Test commit"}]}' ``` ## Configuration All configuration is in `.env` file (copy from `.env.example`). ### Server Configuration ```env HOST=0.0.0.0 # Network interface (0.0.0.0=all, 127.0.0.1=localhost only) PORT=3000 # Port number WEBHOOK_PATH=/webhook # Webhook endpoint path WEBHOOK_SECRET= # Optional webhook verification secret ``` ### Command Configuration Pattern ```env WEBHOOK_{EVENT_TYPE}_ENABLED=true|false WEBHOOK_{EVENT_TYPE}_COMMAND= WEBHOOK_{EVENT_TYPE}_FILTER_BRANCH= ``` **Supported Event Types:** push, pull_request, create, delete, release, issues, issue_comment **Global commands (runs for all events):** ```env WEBHOOK_GLOBAL_ENABLED=true WEBHOOK_GLOBAL_COMMAND= ``` **Command execution settings:** ```env COMMAND_TIMEOUT=300000 # Timeout in ms COMMAND_WORKING_DIR=/workspace # Working directory for commands ``` ## Important Patterns 1. **ES6 Modules**: All files use `import/export` syntax (`"type": "module"` in package.json) 2. **Singleton Exports**: Config, Logger, CommandExecutor are singletons (created on import) 3. **Zero Dependencies**: Pure Node.js - no npm packages required beyond Node.js built-ins 4. **Graceful Shutdown**: Both SIGTERM and SIGINT trigger `server.stop()` before exit 5. **Configuration Precedence**: Environment variables override `.env` file values 6. **Error Handling**: Try-catch blocks with logging; errors don't stop server 7. **Branch Filtering**: Optional per-event branch filtering via `WEBHOOK_{EVENT}_FILTER_BRANCH` ## Extending with Callbacks The server supports custom callback registration beyond .env command execution: ```javascript import { WebhookServer } from './src/server.js'; const server = new WebhookServer({ port: 3000 }); const handler = server.getHandler(); // Event-specific callback handler.on('push', async (payload, headers) => { console.log('Push to:', payload.repository.name); // Custom logic here }); // Global callback (all events) handler.onAny(async (eventType, payload, headers) => { console.log('Event:', eventType); // Send to analytics, logging, etc. }); server.start(); ``` See `examples/with-callbacks.js` for complete example. ## Variable Substitution System Commands in `.env` support variable substitution using `{{variable}}` syntax. **Available variables:** - `{{event}}` - Event type (push, create, delete, etc.) - `{{repo}}` - Repository name only - `{{full_repo}}` - User/repository format - `{{branch}}` - Branch name (extracted from refs) - `{{pusher}}` - Username of person who triggered event - `{{commit}}` - Short commit hash (push events) - `{{commit_msg}}` - Last commit message (push events) - `{{pr_number}}` - Pull request number (PR events) - `{{pr_action}}` - PR action (opened, closed, etc.) - `{{tag}}` - Tag name (create/delete tag events) - `{{ref_type}}` - "branch" or "tag" - `{{issue_number}}` - Issue number (issue events) - `{{issue_title}}` - Issue title (issue events) - `{{issue_action}}` - Issue action (opened, closed, reopened, etc.) - `{{issue_body}}` - Issue description/body (issue events) - `{{comment_body}}` - Comment text (issue_comment events) Variables are extracted in `commandExecutor.js` via `extractVariables()` and substituted via `substituteVariables()`. ## Job Queue System The webhook server uses a persistent job queue to ensure reliable command execution: ### Features - **Sequential Execution**: Only one job runs at a time, preventing conflicts and resource contention - **FIFO Processing**: Jobs are executed in the order they are received - **Immediate Response**: Webhooks are accepted and queued instantly, ensuring no webhook delivery timeouts - **Persistence**: Queue state is saved to `queue.json` after every change - **Crash Recovery**: Pending jobs are automatically restored and processed after server restart - **Safe Restart**: Jobs marked as "running" during shutdown are reset to "pending" on restart ### Queue File Structure ```json { "queue": [ { "id": 1, "eventType": "push", "status": "pending", "createdAt": "2025-10-28T12:00:00.000Z", "config": { "command": "...", "enabled": true } } ], "jobIdCounter": 5, "lastSaved": "2025-10-28T12:05:00.000Z" } ``` ### Job Lifecycle 1. **Created**: Webhook received → Job created with status "pending" 2. **Queued**: Job added to queue and persisted to disk 3. **Running**: Job status updated to "running" before execution starts 4. **Completed/Failed**: Job finishes → Status updated → Job removed from queue 5. **Next**: Queue processes next pending job (if any) ### Benefits - **No Lost Jobs**: Server crashes won't lose pending webhook events - **No Concurrent Execution**: Prevents race conditions in deployment scripts - **High Availability**: Can accept webhooks even while executing long-running commands - **Scalable**: Queue can grow to handle bursts of webhook activity ## Code Modification Guidelines - Maintain zero-dependency philosophy (use only Node.js built-ins) - Follow singleton pattern for utilities (Config, Logger, CommandExecutor) - Use async/await for all async operations - Add error handling with try-catch and logger.error() - Update `.env.example` when adding new configuration options - Update README.md when adding new features - Follow existing naming conventions: camelCase for variables/functions, PascalCase for classes - Export singletons with `export default new ClassName()` - Keep modules focused on single responsibility ## Testing No formal test suite currently. Test manually using: 1. Start server with `npm run dev` 2. Use curl to send webhook payloads 3. Check console logs for execution results 4. Verify commands execute with expected variable substitution - Add to memory: You always have to push the changes into the git reposiroty. Before pushing changes, you have to test the project with building and running it. Take care when running the project to use an unused port if the project already running. Never keep running instance from the project - Add to memory: Always push into git the changes using short commit messages