@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.
- package/README.md +302 -0
- package/dist/cli.js +1327 -0
- package/package.json +55 -0
- package/templates/api/.claude/agents/knex-table-implementer.md +36 -0
- package/templates/api/.claude/agents/sundays-backend-builder.md +32 -0
- package/templates/api/.env.example +25 -0
- package/templates/api/.github/workflows/ci.yaml +45 -0
- package/templates/api/.github/workflows/deploy.yaml +72 -0
- package/templates/api/.prettierignore +5 -0
- package/templates/api/.prettierrc +9 -0
- package/templates/api/.sundaysrc +8 -0
- package/templates/api/CLAUDE.md +81 -0
- package/templates/api/Dockerfile +17 -0
- package/templates/api/README.md +164 -0
- package/templates/api/_dockerignore +8 -0
- package/templates/api/_gitignore +23 -0
- package/templates/api/_package.json +58 -0
- package/templates/api/docker-compose.yml +21 -0
- package/templates/api/eslint.config.js +27 -0
- package/templates/api/jest.config.js +33 -0
- package/templates/api/jest.setup.js +25 -0
- package/templates/api/knexfile.ts +5 -0
- package/templates/api/src/app.ts +48 -0
- package/templates/api/src/common/__tests__/common.test.ts +116 -0
- package/templates/api/src/common/config/env.ts +53 -0
- package/templates/api/src/common/errors/http.error.ts +30 -0
- package/templates/api/src/common/logger/index.ts +25 -0
- package/templates/api/src/common/utils/environment.resolver.ts +7 -0
- package/templates/api/src/common/utils/pagination.ts +25 -0
- package/templates/api/src/common/utils/version.resolver.ts +24 -0
- package/templates/api/src/common/validation/parse-dto.ts +20 -0
- package/templates/api/src/controllers/health/__tests__/health.controller.test.ts +52 -0
- package/templates/api/src/controllers/health/health.controller.ts +26 -0
- package/templates/api/src/db/BaseDAO.ts +92 -0
- package/templates/api/src/db/KnexConnection.ts +59 -0
- package/templates/api/src/db/__tests__/base-dao.test.ts +73 -0
- package/templates/api/src/db/__tests__/index.barrel.test.ts +10 -0
- package/templates/api/src/db/__tests__/knex-connection.test.ts +90 -0
- package/templates/api/src/db/d.types.ts +42 -0
- package/templates/api/src/db/dao/sundays-package-version/sundays-package-version.dao.ts +12 -0
- package/templates/api/src/db/index.ts +17 -0
- package/templates/api/src/db/interfaces/sundays-package-version/sundays-package-version.interfaces.ts +5 -0
- package/templates/api/src/db/knex.config.ts +46 -0
- package/templates/api/src/dto/input/.gitkeep +0 -0
- package/templates/api/src/jobs/.gitkeep +0 -0
- package/templates/api/src/middlewares/error/__tests__/error.middleware.test.ts +117 -0
- package/templates/api/src/middlewares/error/error.middleware.ts +70 -0
- package/templates/api/src/middlewares/not-found/__tests__/not-found.middleware.test.ts +54 -0
- package/templates/api/src/middlewares/not-found/not-found.middleware.ts +51 -0
- package/templates/api/src/middlewares/request-id/__tests__/request-id.middleware.test.ts +31 -0
- package/templates/api/src/middlewares/request-id/request-id.middleware.ts +20 -0
- package/templates/api/src/migrations/20240101000000_create_sundays_package_version.ts +15 -0
- package/templates/api/src/routes/__tests__/index-router.test.ts +61 -0
- package/templates/api/src/routes/health/__tests__/health.routes.test.ts +22 -0
- package/templates/api/src/routes/health/health.router.ts +18 -0
- package/templates/api/src/routes/index.ts +77 -0
- package/templates/api/src/seeds/001_sundays_package_version.ts +14 -0
- package/templates/api/src/server.ts +56 -0
- package/templates/api/src/services/.gitkeep +0 -0
- package/templates/api/tsconfig.json +20 -0
- package/templates/api/tsconfig.spec.json +10 -0
- package/templates/api-auth/overlay.json +95 -0
- package/templates/api-auth/src/controllers/auth/__tests__/auth.controller.test.ts +194 -0
- package/templates/api-auth/src/controllers/auth/auth.controller.ts +109 -0
- package/templates/api-auth/src/db/dao/auth/auth.dao.ts +21 -0
- package/templates/api-auth/src/db/dao/user/user.dao.ts +24 -0
- package/templates/api-auth/src/db/interfaces/auth/auth.interfaces.ts +8 -0
- package/templates/api-auth/src/db/interfaces/user/user.interfaces.ts +11 -0
- package/templates/api-auth/src/dto/input/auth/auth.login.dto.ts +14 -0
- package/templates/api-auth/src/dto/input/auth/auth.register.dto.ts +19 -0
- package/templates/api-auth/src/middlewares/auth/__tests__/auth.middleware.test.ts +52 -0
- package/templates/api-auth/src/middlewares/auth/auth.middleware.ts +56 -0
- package/templates/api-auth/src/migrations/20240101000001_create_user.ts +18 -0
- package/templates/api-auth/src/migrations/20240101000002_create_auth.ts +24 -0
- package/templates/api-auth/src/routes/auth/__tests__/auth.routes.test.ts +83 -0
- package/templates/api-auth/src/routes/auth/auth.router.ts +28 -0
- package/templates/api-auth/src/services/jwt/__tests__/jwt.service.test.ts +32 -0
- package/templates/api-auth/src/services/jwt/jwt.service.ts +36 -0
- package/templates/api-auth/src/services/password/__tests__/password.service.test.ts +13 -0
- 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
|