|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Commands |
| 6 | + |
| 7 | +```bash |
| 8 | +yarn dev # Start both server (port 3001) and Vite dev server (port 5173) concurrently |
| 9 | +yarn dev:server # Server only: node --watch server/src/index.js |
| 10 | +yarn dev:client # Client only: Vite dev server with /api proxy to port 3001 |
| 11 | + |
| 12 | +yarn test # All tests (server then client) |
| 13 | +yarn test:server # Server tests via vitest.config.js |
| 14 | +yarn test:client # Client tests via vite.config.js |
| 15 | + |
| 16 | +yarn build # Vite build → client/dist/ |
| 17 | +yarn start # yarn build + node server/src/index.js |
| 18 | + |
| 19 | +yarn lint # ESLint across entire repo |
| 20 | +yarn lint:fix |
| 21 | +yarn format # Prettier |
| 22 | +``` |
| 23 | + |
| 24 | +**Run a single test file:** |
| 25 | +```bash |
| 26 | +yarn test:server --reporter=verbose server/tests/jobs.test.js |
| 27 | +yarn test:client --reporter=verbose client/tests/JobForm.test.jsx |
| 28 | +``` |
| 29 | + |
| 30 | +## Architecture |
| 31 | + |
| 32 | +### Server (`server/src/`) |
| 33 | + |
| 34 | +Express app wired together in `index.js` → `app.js`. The server serves the built React client from `client/dist/` in production; in dev, the Vite proxy handles `/api` routing. |
| 35 | + |
| 36 | +**Request flow:** `gatewayTokenMiddleware` → route handlers → `executor` / DB |
| 37 | + |
| 38 | +- **`db/`** – Single module-level `db` singleton (`getDb()` / `initDb()`). SQLite in WAL mode with foreign keys. Schema and migrations are in `migrations.js` — all tables are created with `IF NOT EXISTS`. |
| 39 | +- **`scheduler/index.js`** – `node-cron` tasks re-fetch the job from the DB on each tick (so config changes take effect immediately without rescheduling). Jobs are stored raw (integers for booleans) at this layer. |
| 40 | +- **`scheduler/executor.js`** – Spawns child processes, caps output at 512 KB, enforces `EXEC_TIMEOUT_MS` (default 30 min). After completion, calls `ntfySend` and emits SSE events via `eventBus`. |
| 41 | +- **`routes/jobs.js`** – `formatJob()` converts raw SQLite rows (integers) to typed JS objects (booleans) before sending to the client. All boolean DB columns are `0`/`1` integers — only `formatJob()` and the route write handlers deal with the conversion. |
| 42 | +- **`services/eventBus.js`** – In-process EventEmitter. The SSE endpoint in `app.js` subscribes to `run:started` and `run:finished` events. |
| 43 | +- **`services/ntfy.js`** – Fire-and-forget HTTP POST to the configured ntfy server. Errors are logged but never re-thrown. |
| 44 | +- **`services/cronUtils.js`** – Uses `cron-parser` v5 (`CronExpressionParser.parse()`, not the old `parseExpression()`) and `cronstrue` for human-readable descriptions. |
| 45 | +- **`env.js`** – Loads `.env` via dotenv (skipped in `NODE_ENV=test`). Exports `PROJECT_ROOT` (two directories up from `server/src/`). The version shown in the UI is read from root `package.json` by `app.js` at startup. |
| 46 | + |
| 47 | +### Client (`client/src/`) |
| 48 | + |
| 49 | +Single-page React app. State lives in hooks; components are presentational. |
| 50 | + |
| 51 | +- **`api/client.js`** – Thin fetch wrapper; sends `X-Gateway-Token` header on every request (reads from URL `?token=`). Throws typed errors with `.fields` for validation responses. |
| 52 | +- **`hooks/useJobs.js`** – Owns the jobs list state and all CRUD operations. |
| 53 | +- **`hooks/useRunHistory.js`** – Paginated run history for a selected job. |
| 54 | +- **`hooks/useJobEvents.js`** – SSE subscription (`/api/events`). Delivers `run:started` / `run:finished` to `App.jsx` which fans them out to `useJobs` and `useRunHistory`. |
| 55 | +- **`hooks/useCronValidation.js`** – Debounced cron expression validation against `/api/jobs/validate`. |
| 56 | +- **`components/`** – Organized by domain: `jobs/`, `runs/`, `cron/`, `ui/`, `layout/`, `auth/`. |
| 57 | + |
| 58 | +### Build / Config |
| 59 | + |
| 60 | +Single `package.json` at root. `vite.config.js` (root) sets `root: 'client'` so Vite treats `client/` as the project root; build output goes to `client/dist/`. `vitest.config.js` (root) scopes server tests to `server/tests/**`. |
| 61 | + |
| 62 | +Client tests run in **happy-dom** (not jsdom). Server tests run in the node environment; `server/tests/setup.js` exposes `makeDb()`, `makeApp()`, and `makeScheduler()` helpers. |
| 63 | + |
| 64 | +### ntfy Notification Logic |
| 65 | + |
| 66 | +`ntfy_on_run` fires on **every** execution (success and error). `ntfy_on_error` fires only on failures, even when `ntfy_on_run` is off. Condition in `executor.js`: |
| 67 | +```js |
| 68 | +if (job.ntfy_enabled && ((isError && job.ntfy_on_error) || job.ntfy_on_run)) |
| 69 | +``` |
| 70 | + |
| 71 | +### Security |
| 72 | + |
| 73 | +`GATEWAY_TOKEN` in `.env` enables the gateway. The token is passed as `?token=` in the URL for browser access (SSE cannot set custom headers). The middleware uses `timingSafeEqual` to prevent timing attacks. |
| 74 | + |
| 75 | +### Coding |
| 76 | +- You must not commit anything or create new branches unless I told you so |
| 77 | +- Use ESM |
| 78 | +- Document everything you add with proper JSDoc |
0 commit comments