AGENTS.md
Rama main · réplica de lectura del repositorio original.
# AGENTS.md
Guidelines for AI coding agents working in this repository.
## Project Overview
Delivery is a self-hostable deployment platform (Netlify/Vercel alternative). It is a **pnpm monorepo** managed by **Turborepo** with two apps and shared packages, all written in **TypeScript**.
### Structure
```
apps/web/ Next.js 15 frontend (App Router, React 19, shadcn/ui)
apps/jobs/ Hono REST API + BullMQ background jobs
apps/docs/ GitBook documentation
packages/auth/ JWT utilities (jose)
packages/drizzle/ Database schema, migrations, seed (Drizzle ORM + PostgreSQL)
packages/utils/ Shared utility functions
packages/eslint-config/ Shared ESLint configuration
```
## Build / Lint / Test Commands
Package manager: **pnpm** (v10). Always use `pnpm`, never `npm` or `yarn`.
### From the repository root
```bash
pnpm install # Install all dependencies
pnpm run build # Build all packages (via Turborepo)
pnpm run lint # Lint all packages
pnpm run lint:fix # Lint and auto-fix all packages
pnpm run test # Run all tests (via Turborepo)
pnpm run dev # Start all dev servers
```
### Single app/package commands
```bash
# Tests (both apps use Vitest; web uses happy-dom, jobs runs in Node)
pnpm --filter @delivery/jobs test
pnpm --filter @delivery/web test
# Single test file
pnpm --filter @delivery/jobs exec vitest run src/routes/applications/__tests__/applications.integration.test.ts
# Single test by name pattern
pnpm --filter @delivery/jobs exec vitest run -t "test name pattern"
# Linting
pnpm --filter @delivery/jobs lint
pnpm --filter @delivery/web lint
# Type checking
pnpm run check-types # Root-level tsc --noEmit
pnpm --filter @delivery/jobs run typecheck # Jobs-specific
# Database (packages/drizzle)
pnpm --filter @delivery/drizzle run migrate # Run migrations
pnpm --filter @delivery/drizzle run db:push # Push schema to test DB
pnpm --filter @delivery/drizzle run db:reset # Reset test DB
pnpm --filter @delivery/drizzle run db:seed # Seed test DB
```
## Code Style Guidelines
Formatting is enforced by **ESLint** (via `@antfu/eslint-config`). **Prettier is disabled.** Run `pnpm run lint:fix` to auto-format.
### Formatting Rules
- **Indentation**: 2 spaces (no tabs)
- **Quotes**: Double quotes (`"`) everywhere
- **Semicolons**: Always required
- **Trailing commas**: Yes, in multi-line constructs
- **Brace style**: Single-statement `if`/`else` bodies omit braces; `else` on a new line after `}`
```typescript
if (!config)
return null;
if (condition) {
doSomething();
}
else {
doOther();
}
```
### Imports
- Use `import type { ... }` for type-only imports (never inline `import { type Foo }`)
- Import ordering is enforced by `perfectionist/sort-imports`:
1. External packages (`"react"`, `"hono"`, `"drizzle-orm"`)
2. Monorepo packages (`"@delivery/drizzle"`, `"@delivery/auth"`)
3. Local path-alias imports (`"@/lib/..."`, `"./foo"`)
- Separate groups with blank lines
- Path alias: `@/*` maps to `src/*` in both apps
### Naming Conventions
| Element | Convention | Example |
|--------------------|-----------------|--------------------------------------|
| Files/directories | kebab-case | `create-app.ts`, `remote-docker/` |
| Variables/functions| camelCase | `activeJobs`, `createApplication()` |
| React components | PascalCase | `GithubAppForm`, `StepProvider` |
| Classes | PascalCase | `DeploymentError`, `ApplicationError`|
| Types/interfaces | PascalCase | `AppBindings`, `AppRouteHandler` |
| Constants | UPPER_SNAKE_CASE| `MAX_TTL`, `APPLICATIONS_PATH` |
| DB tables (SQL) | snake_case | `application_environment_variables` |
| DB tables (JS) | camelCase | `applicationEnvironmentVariables` |
File naming for route modules follows: `{resource}.routes.ts`, `{resource}.handlers.ts`, `{resource}.index.ts`.
### Types
- TypeScript strict mode is enabled in both apps
- Use `interface` for object shapes (props, bindings, context types)
- Use `type` for aliases, unions, intersections, `z.infer<>` extractions, and mapped types
- Zod is the primary validation library; Drizzle schemas derive from `drizzle-zod`
- Use `as const` for constant objects/arrays that need literal type inference
- Never use `any`; prefer `unknown` when type is truly unknown
### Error Handling
- **Jobs API**: Custom error classes extending `UnrecoverableError` (from BullMQ): `DeploymentError`, `DatabaseError`, `ApplicationError`
- **HTTP errors**: Throw `HTTPException` from Hono with status code and message
- **Partial failure**: Use `Promise.allSettled` when some operations can fail independently
- **Inline catch**: `.catch()` chaining for error transformation on promises
- **Environment validation**: Zod `safeParse` with `process.exit(1)` on failure (fail-fast)
- **Direct `process.env` access is banned** (`node/no-process-env: error`). Access environment only through the validated `env` object exported from each app's `env.ts`
### Exports & Functions
- Prefer **named exports** for functions, components, types, and handlers
- **Default exports** for: Next.js page components, router instances (`*.index.ts`), app singletons, env config objects
- Barrel files (`index.ts` with `export * from`) are used selectively, not universally
- **Function declarations** for standalone/exported functions and React components
- **Arrow functions assigned to `const`** for route handlers (typed), middleware, and HOF results
- Inline arrow functions for callbacks and `.map()`/`.filter()` chains
- `async/await` is the standard; `Promise.all` for concurrency; `Promise.allSettled` for partial failure tolerance
- `new Promise` only for wrapping callback-based library APIs (ssh2, streams)
- Comments are sparse -- explain "why", not "what"; use `// eslint-disable-next-line <rule>` with rule name
## Architecture Notes
### Backend (`apps/jobs`)
Each resource domain follows this structure:
```
routes/{resource}/
{resource}.routes.ts OpenAPI route definitions (Zod schemas, status codes)
{resource}.handlers.ts Request handler functions
{resource}.index.ts Router wiring: createRouter().openapi(route, handler)
lib/
dto.ts Zod DTOs
queries.ts Database queries (Drizzle)
services.ts Business logic / orchestration
remote-docker/ Docker operations (uses withDocker HOF)
tasks/ BullMQ job queues per action
```
API is **OpenAPI-first** via `@hono/zod-openapi`.
### Frontend (`apps/web`)
Next.js App Router with underscore-prefixed private directories (`_components/`, `_hooks/`, `_lib/`, `_ctx/`), route groups (`(dashboard)/`, `(onboarding)/`, `(login)/`), and `api/` for BFF proxy routes. UI primitives live in `_components/ui/` (shadcn/ui).
### Copilot Instructions
When writing `.tsx` files with `<Suspense>`, always provide a fallback using a pending/skeleton component that wraps the shadcn `<Skeleton />` component, mimicking the visual shape of the component being loaded.
## Testing
- Framework: **Vitest** in both apps
- Test files live in `__tests__/` directories, named `*.integration.test.ts`
- Mocks use **MSW** (Mock Service Worker) in `__mocks__/` directories
- Test fixtures use Vitest's `it.extend<Fixtures>({...})` pattern
- Web tests use `happy-dom`; jobs tests run in Node
- Global setup and per-file setup scripts are in each app's `__tests__/` directory