@sundaysf/cli-v3 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +302 -0
  2. package/dist/cli.js +1327 -0
  3. package/package.json +55 -0
  4. package/templates/api/.claude/agents/knex-table-implementer.md +36 -0
  5. package/templates/api/.claude/agents/sundays-backend-builder.md +32 -0
  6. package/templates/api/.env.example +25 -0
  7. package/templates/api/.github/workflows/ci.yaml +45 -0
  8. package/templates/api/.github/workflows/deploy.yaml +72 -0
  9. package/templates/api/.prettierignore +5 -0
  10. package/templates/api/.prettierrc +9 -0
  11. package/templates/api/.sundaysrc +8 -0
  12. package/templates/api/CLAUDE.md +81 -0
  13. package/templates/api/Dockerfile +17 -0
  14. package/templates/api/README.md +164 -0
  15. package/templates/api/_dockerignore +8 -0
  16. package/templates/api/_gitignore +23 -0
  17. package/templates/api/_package.json +58 -0
  18. package/templates/api/docker-compose.yml +21 -0
  19. package/templates/api/eslint.config.js +27 -0
  20. package/templates/api/jest.config.js +33 -0
  21. package/templates/api/jest.setup.js +25 -0
  22. package/templates/api/knexfile.ts +5 -0
  23. package/templates/api/src/app.ts +48 -0
  24. package/templates/api/src/common/__tests__/common.test.ts +116 -0
  25. package/templates/api/src/common/config/env.ts +53 -0
  26. package/templates/api/src/common/errors/http.error.ts +30 -0
  27. package/templates/api/src/common/logger/index.ts +25 -0
  28. package/templates/api/src/common/utils/environment.resolver.ts +7 -0
  29. package/templates/api/src/common/utils/pagination.ts +25 -0
  30. package/templates/api/src/common/utils/version.resolver.ts +24 -0
  31. package/templates/api/src/common/validation/parse-dto.ts +20 -0
  32. package/templates/api/src/controllers/health/__tests__/health.controller.test.ts +52 -0
  33. package/templates/api/src/controllers/health/health.controller.ts +26 -0
  34. package/templates/api/src/db/BaseDAO.ts +92 -0
  35. package/templates/api/src/db/KnexConnection.ts +59 -0
  36. package/templates/api/src/db/__tests__/base-dao.test.ts +73 -0
  37. package/templates/api/src/db/__tests__/index.barrel.test.ts +10 -0
  38. package/templates/api/src/db/__tests__/knex-connection.test.ts +90 -0
  39. package/templates/api/src/db/d.types.ts +42 -0
  40. package/templates/api/src/db/dao/sundays-package-version/sundays-package-version.dao.ts +12 -0
  41. package/templates/api/src/db/index.ts +17 -0
  42. package/templates/api/src/db/interfaces/sundays-package-version/sundays-package-version.interfaces.ts +5 -0
  43. package/templates/api/src/db/knex.config.ts +46 -0
  44. package/templates/api/src/dto/input/.gitkeep +0 -0
  45. package/templates/api/src/jobs/.gitkeep +0 -0
  46. package/templates/api/src/middlewares/error/__tests__/error.middleware.test.ts +117 -0
  47. package/templates/api/src/middlewares/error/error.middleware.ts +70 -0
  48. package/templates/api/src/middlewares/not-found/__tests__/not-found.middleware.test.ts +54 -0
  49. package/templates/api/src/middlewares/not-found/not-found.middleware.ts +51 -0
  50. package/templates/api/src/middlewares/request-id/__tests__/request-id.middleware.test.ts +31 -0
  51. package/templates/api/src/middlewares/request-id/request-id.middleware.ts +20 -0
  52. package/templates/api/src/migrations/20240101000000_create_sundays_package_version.ts +15 -0
  53. package/templates/api/src/routes/__tests__/index-router.test.ts +61 -0
  54. package/templates/api/src/routes/health/__tests__/health.routes.test.ts +22 -0
  55. package/templates/api/src/routes/health/health.router.ts +18 -0
  56. package/templates/api/src/routes/index.ts +77 -0
  57. package/templates/api/src/seeds/001_sundays_package_version.ts +14 -0
  58. package/templates/api/src/server.ts +56 -0
  59. package/templates/api/src/services/.gitkeep +0 -0
  60. package/templates/api/tsconfig.json +20 -0
  61. package/templates/api/tsconfig.spec.json +10 -0
  62. package/templates/api-auth/overlay.json +95 -0
  63. package/templates/api-auth/src/controllers/auth/__tests__/auth.controller.test.ts +194 -0
  64. package/templates/api-auth/src/controllers/auth/auth.controller.ts +109 -0
  65. package/templates/api-auth/src/db/dao/auth/auth.dao.ts +21 -0
  66. package/templates/api-auth/src/db/dao/user/user.dao.ts +24 -0
  67. package/templates/api-auth/src/db/interfaces/auth/auth.interfaces.ts +8 -0
  68. package/templates/api-auth/src/db/interfaces/user/user.interfaces.ts +11 -0
  69. package/templates/api-auth/src/dto/input/auth/auth.login.dto.ts +14 -0
  70. package/templates/api-auth/src/dto/input/auth/auth.register.dto.ts +19 -0
  71. package/templates/api-auth/src/middlewares/auth/__tests__/auth.middleware.test.ts +52 -0
  72. package/templates/api-auth/src/middlewares/auth/auth.middleware.ts +56 -0
  73. package/templates/api-auth/src/migrations/20240101000001_create_user.ts +18 -0
  74. package/templates/api-auth/src/migrations/20240101000002_create_auth.ts +24 -0
  75. package/templates/api-auth/src/routes/auth/__tests__/auth.routes.test.ts +83 -0
  76. package/templates/api-auth/src/routes/auth/auth.router.ts +28 -0
  77. package/templates/api-auth/src/services/jwt/__tests__/jwt.service.test.ts +32 -0
  78. package/templates/api-auth/src/services/jwt/jwt.service.ts +36 -0
  79. package/templates/api-auth/src/services/password/__tests__/password.service.test.ts +13 -0
  80. package/templates/api-auth/src/services/password/password.service.ts +14 -0
package/README.md ADDED
@@ -0,0 +1,302 @@
1
+ # Sundays Framework v3 — `@sundaysf/cli-v3`
2
+
3
+ CLI that scaffolds a production-ready REST API (Express 5 · TypeScript · Knex · PostgreSQL ·
4
+ Zod · pino · Jest) and generates complete entity verticals inside it.
5
+
6
+ ```bash
7
+ npx @sundaysf/cli-v3 new my-api --with-auth
8
+ cd my-api && docker compose up -d && npm run db:migrate && npm run start:dev
9
+ sundaysf generate entity product name:string:unique price:decimal isActive:boolean=true
10
+ ```
11
+
12
+ - [What changed since v2](#what-changed-since-v2)
13
+ - [Install](#install)
14
+ - [`sundaysf new`](#sundaysf-new)
15
+ - [`sundaysf generate entity`](#sundaysf-generate-entity)
16
+ - [The generated API](#the-generated-api)
17
+ - [Conventions](#conventions)
18
+ - [Testing the generated API](#testing-the-generated-api)
19
+ - [Deploying the generated API](#deploying-the-generated-api)
20
+ - [Developing the CLI](#developing-the-cli)
21
+ - [Troubleshooting](#troubleshooting)
22
+
23
+ ## What changed since v2
24
+
25
+ | | v2 (`@sundaysf/cli-v2`) | v3 (`@sundaysf/cli-v3`) |
26
+ | ----------- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
27
+ | Templates | 6 (`--backend`, `--db-sql`, `--backend-embedded-db-sql`...) | 1: API with Knex embedded, plus an optional auth overlay |
28
+ | Commands | `--init --<template>`, `--create-controller` | `new`, `generate entity` |
29
+ | Generators | controller + router stubs; dead `manifest.js`; `scripts/import-manifest.js` copied into projects | one generator: migration, interface, DAO, DTOs, controller, router, unit + route tests, barrel export |
30
+ | Express | 4 | 5 |
31
+ | Validation | hand-written DTO classes, phantom `@sundaysf/utils` | Zod schemas in the same `dto/input/<e>/` layout |
32
+ | Data access | every DAO re-implements 6 CRUD methods | `BaseDAO<T>` with pagination and optional transaction |
33
+ | Config | duplicated in `knexfile.ts` and `KnexConnection.ts`, `process.env` everywhere | one `knex.config.ts`; env validated once with Zod |
34
+ | Dev tooling | nodemon + ts-node | `tsx` for the dev server and the knex CLI |
35
+ | Logging | morgan + `console.*` | pino + pino-http with request ids |
36
+ | Security | none | helmet, CORS allowlist, graceful shutdown |
37
+ | Tests | none shipped | Jest + supertest suite included, ~97% coverage, CI workflow |
38
+ | Local DB | bring your own | `docker-compose.yml` with PostgreSQL 16 |
39
+ | Docs | stale README/CLAUDE.md | README, CLAUDE.md and Claude agents rewritten for the real code |
40
+ | Removed | `ownLibs/`, `postman.json`, `.npmrc` in projects, root `migrations/`, lodash, rimraf, `expo lint` | |
41
+
42
+ ## Install
43
+
44
+ ```bash
45
+ npm install -g @sundaysf/cli-v3 # global: `sundaysf ...`
46
+ npx @sundaysf/cli-v3 new my-api # or one-off
47
+ ```
48
+
49
+ Requires Node.js 20+ (the generated project targets Node 22+), npm or pnpm, git, and Docker for
50
+ the local database (optional).
51
+
52
+ ## `sundaysf new`
53
+
54
+ ```
55
+ sundaysf new [name] [options]
56
+
57
+ --with-auth include auth (user/auth tables, register/login/me, JWT, bcrypt, middleware, tests)
58
+ --no-auth skip auth without asking
59
+ --port <port> HTTP port (default 3005)
60
+ --pm <npm|pnpm> package manager (auto-detected)
61
+ --no-install skip dependency installation
62
+ --no-git skip git init and the initial commit
63
+ -y, --yes accept defaults, never prompt
64
+ ```
65
+
66
+ Interactive runs ask for anything not given as a flag. With `--yes` or without a TTY the defaults
67
+ are: no auth, port 3005, install, git.
68
+
69
+ What it does:
70
+
71
+ 1. Validates the name (`^[a-z0-9][a-z0-9-]{0,63}$`) and that the target folder is empty.
72
+ 2. Copies the `api` template, applies the `api-auth` overlay when requested, replaces the
73
+ `__SF_*__` tokens (project name, port, database name, CLI version), renames `_gitignore` and
74
+ `_package.json`, and refuses to finish if any token is left or a `.npmrc` sneaked in.
75
+ 3. Writes `.env` from `.env.example`. With auth, `.env` gets a random 64-char `JWT_SECRET`
76
+ (`.env.example` keeps a placeholder).
77
+ 4. `git init -b main`, `npm install`, `npm run format`, and an initial commit
78
+ (`chore: scaffold <name> with @sundaysf/cli-v3 <version>`).
79
+ 5. Prints the next steps. A failing install or commit is reported but never deletes the files.
80
+
81
+ Generated tree (auth files marked `*`):
82
+
83
+ ```
84
+ my-api/
85
+ ├── .env .env.example .sundaysrc .gitignore .dockerignore .prettierrc eslint.config.js
86
+ ├── package.json tsconfig.json tsconfig.spec.json jest.config.js jest.setup.js knexfile.ts
87
+ ├── docker-compose.yml Dockerfile README.md CLAUDE.md
88
+ ├── .claude/agents/{sundays-backend-builder,knex-table-implementer}.md
89
+ ├── .github/workflows/{ci,deploy}.yaml
90
+ └── src/
91
+ ├── server.ts boot, migrations on demand, graceful shutdown
92
+ ├── app.ts helmet, cors, request id, pino-http, parsers, /api, 404, errors
93
+ ├── common/{config/env,logger,errors/http.error,validation/parse-dto,utils/*}.ts
94
+ ├── db/{index,BaseDAO,KnexConnection,knex.config,d.types}.ts
95
+ ├── db/dao/<entity>/<entity>.dao.ts db/interfaces/<entity>/<entity>.interfaces.ts
96
+ ├── migrations/ seeds/
97
+ ├── routes/index.ts (auto-discovery) routes/health/ routes/auth/*
98
+ ├── controllers/health/ controllers/auth/*
99
+ ├── dto/input/auth/* services/jwt/* services/password/* middlewares/auth/*
100
+ ├── middlewares/{error,not-found,request-id}/
101
+ └── jobs/ (cron jobs: export run() + schedule(), register schedule() in server.ts)
102
+ ```
103
+
104
+ ## `sundaysf generate entity`
105
+
106
+ ```
107
+ sundaysf generate entity <name> [fields...] [options] (alias: sundaysf g entity)
108
+
109
+ --fields "<spec>" fields as one string instead of positional arguments
110
+ --no-tests skip the unit and route tests
111
+ --no-migration skip the migration
112
+ --dry-run print everything, write nothing
113
+ --force overwrite existing code files (migrations are never overwritten)
114
+ ```
115
+
116
+ Must run inside a project created by v3 (it looks for `.sundaysrc` with a `cli` field and the
117
+ `@sundays` markers in `src/db/index.ts`). Without fields on a TTY it asks for them one by one.
118
+
119
+ ### Field syntax
120
+
121
+ ```
122
+ name:type[?][:unique][=default]
123
+ ```
124
+
125
+ | Type | Column (knex) | TypeScript | Zod (create) |
126
+ | ------------- | -------------------------------------------------------------------------------- | ------------------------- | ----------------------------------- |
127
+ | `string` | `string(name, 255)` | `string` | `z.string().trim().min(1).max(255)` |
128
+ | `text` | `text(name)` | `string` | `z.string()` |
129
+ | `integer` | `integer(name)` | `number` | `z.number().int()` |
130
+ | `decimal` | `decimal(name, 15, 2)` | `number` | `z.number()` |
131
+ | `boolean` | `boolean(name)` | `boolean` | `z.boolean()` |
132
+ | `date` | `date(name)` | `string` (`YYYY-MM-DD`) | `z.iso.date()` |
133
+ | `datetime` | `timestamp(name)` | `Date \| string` | `z.coerce.date()` |
134
+ | `json` | `jsonb(name)` | `Record<string, unknown>` | `z.record(z.string(), z.unknown())` |
135
+ | `uuid` | `uuid(name)` | `string` | `z.uuid()` |
136
+ | `<entity>.id` | `integer(name).references('id').inTable('<entity>').onDelete('CASCADE').index()` | `number` | `z.number().int().positive()` |
137
+
138
+ | Modifier | Column | TypeScript | Zod |
139
+ | --------- | ------------------------------------------------ | ------------------- | ------------------------ |
140
+ | (none) | `.notNullable()` | required | required |
141
+ | `?` | nullable | `field?: T \| null` | `.nullable().optional()` |
142
+ | `:unique` | `.unique()` + `getBy<Field>()` finder in the DAO | | |
143
+ | `=value` | `.defaultTo(value)` | `field?: T` | `.default(value)` |
144
+
145
+ `id`, `uuid`, `createdAt` and `updatedAt` are always added and cannot be declared. Field names
146
+ are camelCase; the entity name can be written in any case (`productCategory`, `product-category`,
147
+ `ProductCategory`) and is derived into kebab (files, `/api/product-category`), Pascal
148
+ (`ProductCategoryDAO`, `IProductCategory`), camel (`_productCategoryDAO`) and snake
149
+ (`product_category` table).
150
+
151
+ ### Files written
152
+
153
+ ```
154
+ src/migrations/<timestamp>_create_<table>.ts
155
+ src/db/interfaces/<kebab>/<kebab>.interfaces.ts interface I<Pascal> extends IEntity
156
+ src/db/dao/<kebab>/<kebab>.dao.ts class <Pascal>DAO extends BaseDAO<I<Pascal>>
157
+ src/dto/input/<kebab>/<kebab>.create.dto.ts <Pascal>CreateSchema + validate<Pascal>Create()
158
+ src/dto/input/<kebab>/<kebab>.update.dto.ts <Pascal>UpdateSchema (all optional, no defaults)
159
+ src/controllers/<kebab>/<kebab>.controller.ts getAll, getByUuid, create, update, delete
160
+ src/routes/<kebab>/<kebab>.router.ts GET /, GET /:uuid, POST /, PUT /:uuid, DELETE /:uuid
161
+ src/controllers/<kebab>/__tests__/<kebab>.controller.test.ts unit test, DAO mocked
162
+ src/routes/<kebab>/__tests__/<kebab>.routes.test.ts supertest lifecycle against Postgres
163
+ src/db/index.ts two export lines added above the markers
164
+ ```
165
+
166
+ The route test is written as `describe.skip` when the entity has foreign keys: the header comment
167
+ explains which parent rows to create in `beforeAll` before enabling it. Generated files are
168
+ formatted with the project's prettier.
169
+
170
+ ### Markers
171
+
172
+ Generators never parse TypeScript; they insert lines above marker comments, skipping lines that
173
+ already exist. The base project ships these markers, keep them:
174
+
175
+ | File | Marker |
176
+ | --------------------------- | -------------------------------------------------------------------- |
177
+ | `src/db/index.ts` | `// @sundays:interfaces`, `// @sundays:daos` |
178
+ | `src/common/config/env.ts` | `// @sundays:env-schema` |
179
+ | `.env.example` | `# @sundays:env` |
180
+ | `.github/workflows/ci.yaml` | `# @sundays:ci-env` |
181
+ | `README.md` | `<!-- @sundays:readme-env -->`, `<!-- @sundays:readme-endpoints -->` |
182
+ | `CLAUDE.md` | `<!-- @sundays:features -->` |
183
+
184
+ ## The generated API
185
+
186
+ Request flow: `routes/<x>/<x>.router.ts` (auto-mounted at `/api/<x>`) → `controllers/<x>` →
187
+ `dto/input/<x>` for validation → `db/dao/<x>` (`BaseDAO`) → PostgreSQL. Cross-cutting logic goes in
188
+ `services/<x>`, scheduled work in `jobs/`.
189
+
190
+ - **`src/server.ts`** validates the environment, connects `KnexManager`, runs migrations when
191
+ `RUN_MIGRATIONS=true`, imports `app.ts` and listens. `SIGTERM`/`SIGINT` close the HTTP server and
192
+ the pool (10 s timeout).
193
+ - **`src/app.ts`** wires `helmet`, `cors` (from `CORS_ORIGINS`), the request id middleware,
194
+ `pino-http` (skips `/api/health`), body parsers, `/api`, the 404 handler and the error handler.
195
+ A commented hook shows where to mount raw-body webhooks (Stripe) before the JSON parser.
196
+ - **`src/common/config/env.ts`** is a Zod schema; `env` is typed and a bad deploy fails at boot.
197
+ - **`src/common/errors/http.error.ts`** exports `HttpError` and `badRequest()`, `unauthorized()`,
198
+ `forbidden()`, `notFound()`, `conflict()`. The error middleware renders them as
199
+ `{ success: false, message, errors? }` and hides 500 messages in production.
200
+ - **`src/common/validation/parse-dto.ts`** turns a Zod failure into `HttpError(400)` with
201
+ `{ field: [messages] }`.
202
+ - **`src/db/BaseDAO.ts`** gives every DAO `create`, `getById`, `getByUuid`, `update`, `delete`
203
+ and `getAll(page, limit)` (returns `IDataPaginator`), each with an optional `trx`. Subclasses
204
+ declare `protected readonly table` and add finders with `this.q(trx)`.
205
+ - **`src/db/knex.config.ts`** is the only knex configuration; `knexfile.ts` re-exports it for the
206
+ CLI. Migrations and seeds live in `src/` and compile with the app (`loadExtensions` follows the
207
+ running extension: `.ts` under tsx/jest, `.js` from `dist/`).
208
+ - **Auth overlay** adds `user` + `auth` tables, `UserDAO`/`AuthDAO`, `JwtService`,
209
+ `PasswordService`, `authMiddleware`/`optionalAuthMiddleware` (sets `req.auth`), the
210
+ `/api/auth/register|login|me` endpoints, and their tests.
211
+
212
+ ## Conventions
213
+
214
+ - Envelope: `{ success: true, data }` / `{ success: false, message, errors? }`; lists return
215
+ `{ success, data, page, limit, count, totalCount, totalPages }`.
216
+ - Public identifier `uuid`, internal numeric `id`. Routes take `/:uuid`.
217
+ - Tables snake_case, columns camelCase, every table has `id`, `uuid`, `createdAt`, `updatedAt`.
218
+ - Classes: `XRouter` (`public router: Router`, handlers bound with `.bind()`), `XController`
219
+ (`private _xDAO = new XDAO()`, `try { } catch (err) { next(err) }`), `XDAO extends BaseDAO<IX>`.
220
+ - Express 5: no bare `*` (use `/*splat`), optional params `{/:id}`, `req.query` is read-only,
221
+ `req.body` is `undefined` without a matching parser.
222
+ - Logging through `req.log` / `logger`, never `console`.
223
+
224
+ ## Testing the generated API
225
+
226
+ | Command | Needs Postgres | What runs |
227
+ | ------------------- | -------------- | ---------------------------------------------------------------------- |
228
+ | `npm test` | yes | everything, with coverage (`jest.config.js` threshold, 80% by default) |
229
+ | `npm run test:unit` | no | everything except `src/routes/**` and `src/db/**` |
230
+ | `npm run typecheck` | no | `tsc --noEmit` on sources and tests |
231
+
232
+ `docker compose up -d` starts PostgreSQL 16 with the credentials in `.env.example`. `jest.setup.js`
233
+ aborts if `SQL_HOST` is not local unless `ALLOW_REMOTE_DB_TESTS=1`. Controller tests mock the
234
+ `src/db` barrel; route tests use supertest against the real app and clean up after themselves.
235
+
236
+ ## Deploying the generated API
237
+
238
+ - `Dockerfile`: two-stage `node:24-alpine` build, `npm ci --omit=dev` runtime, `node dist/server.js`.
239
+ - `.github/workflows/ci.yaml`: typecheck, lint, migrate, test and build on every PR/push with a
240
+ Postgres service.
241
+ - `.github/workflows/deploy.yaml`: manual (`workflow_dispatch`) build → ECR push → ECS task
242
+ definition render → service deploy. Needs `secrets.AWS_ROLE_ARN` (OIDC) and `vars.AWS_REGION`;
243
+ ECR/ECS resource names default to the project slug.
244
+ - `.sundaysrc` carries `runtime`, `install`, `build`, `start`, `port` and the generating `cli`
245
+ version for the Sundays platform.
246
+
247
+ ## Developing the CLI
248
+
249
+ ```
250
+ src/
251
+ cli.ts commander program
252
+ commands/{new,generate-entity}.ts
253
+ core/ naming, fields DSL, template engine, overlay, inject, project lookup, exec, prompts
254
+ generators/entity/ context + render/* (one renderer per file) + index (writes + barrel)
255
+ templates/api/ the base project (tokens: __SF_PROJECT_NAME__, __SF_PROJECT_SLUG__,
256
+ __SF_DB_NAME__, __SF_PORT__, __SF_CLI_VERSION__, __SF_YEAR__, __SF_JWT_SECRET__)
257
+ templates/api-auth/ overlay: new files + overlay.json (package.json merge + marker injections)
258
+ test/unit DSL, naming, engine, overlay, inject
259
+ test/snapshot every rendered entity file (update with `npx vitest run -u`)
260
+ test/e2e scaffold + typecheck + generate (+ migrate + jest with SUNDAYS_E2E_DB=1)
261
+ ```
262
+
263
+ ```bash
264
+ npm install
265
+ npm run dev -- new demo --yes # run from source (tsx)
266
+ npm run build && npm link # real `sundaysf` binary from dist/cli.js
267
+ npm test # unit + snapshot (fast)
268
+ SUNDAYS_E2E=1 npm run test:e2e # scaffolds into a temp dir, needs network for npm install
269
+ SUNDAYS_E2E=1 SUNDAYS_E2E_DB=1 SQL_HOST=localhost SQL_PORT=5432 SQL_USER=postgres \
270
+ SQL_PASSWORD=postgres SQL_DB_NAME=e2e_api npm run test:e2e # + migrate + jest
271
+ npm pack --dry-run # check templates/ and _gitignore ship, .npmrc does not
272
+ ```
273
+
274
+ To change the base project edit `templates/api` directly (it is a real project: copy it somewhere,
275
+ `npm install`, run it). Files npm would strip from a tarball are named `_gitignore`,
276
+ `_dockerignore`, `_package.json` and renamed on copy (`RENAME_MAP` in `core/template-engine.ts`).
277
+ To add a field type, extend `FIELD_TYPES` and the four mappings in `core/fields.ts`, then update
278
+ the snapshot. To add an overlay, create `templates/<name>/` with an `overlay.json`.
279
+
280
+ Publishing: `npm publish` runs `prepublishOnly` (build + tests). The registry token belongs in
281
+ the developer's `~/.npmrc`, never in the repo or the templates.
282
+
283
+ ## Troubleshooting
284
+
285
+ - **`Marker "// @sundays:daos" not found`** — the barrel lost its markers; add the two comment
286
+ lines back to `src/db/index.ts` (see [Markers](#markers)).
287
+ - **`ts-jest` errors about TypeScript version** — the project pins `typescript ~5.9`; TypeScript 7
288
+ is not supported by ts-jest yet. Do not bump it.
289
+ - **`SQL_HOST="..." is not local`** — the test guard refused a remote database. Point `.env` at
290
+ localhost or export `ALLOW_REMOTE_DB_TESTS=1` on purpose.
291
+ - **`PathError` / routes not matching after copying Express 4 code** — Express 5 changed the
292
+ path syntax: `*` → `/*splat`, `/:id?` → `{/:id}`, no regex in strings.
293
+ - **`Invalid environment configuration`** — a required variable is missing from `.env`; the
294
+ message lists the fields. Add it to `.env` (and to the schema if it is new).
295
+ - **`npm run start:dev` runs code with type errors** — `tsx` does not type-check; run
296
+ `npm run typecheck` (CI does).
297
+ - **Port 5432 already in use** — another Postgres is running; change the host port in
298
+ `docker-compose.yml` and `SQL_PORT` in `.env`.
299
+
300
+ ## License
301
+
302
+ MIT