@tulipes/cli 0.1.0 → 0.2.0-rc.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 +11 -8
- package/dist/agent.d.ts +12 -0
- package/dist/agent.js +29 -0
- package/dist/agent.js.map +1 -0
- package/dist/app-core.d.ts +8 -6
- package/dist/app-core.js +23 -14
- package/dist/app-core.js.map +1 -1
- package/dist/bin.js +3 -3
- package/dist/bin.js.map +1 -1
- package/dist/config-check.d.ts +8 -0
- package/dist/config-check.js +43 -0
- package/dist/config-check.js.map +1 -0
- package/dist/config-import.d.ts +20 -0
- package/dist/config-import.js +133 -0
- package/dist/config-import.js.map +1 -0
- package/dist/dev.d.ts +4 -4
- package/dist/dev.js +7 -10
- package/dist/dev.js.map +1 -1
- package/dist/init.d.ts +6 -11
- package/dist/init.js +17 -6420
- package/dist/init.js.map +1 -1
- package/dist/inspection.d.ts +9 -8
- package/dist/inspection.js +10 -11
- package/dist/inspection.js.map +1 -1
- package/dist/main.js +82 -11
- package/dist/main.js.map +1 -1
- package/dist/migrate.d.ts +30 -0
- package/dist/migrate.js +772 -0
- package/dist/migrate.js.map +1 -0
- package/dist/new-module.js +87 -46
- package/dist/new-module.js.map +1 -1
- package/dist/routes.d.ts +3 -5
- package/dist/routes.js +4 -6
- package/dist/routes.js.map +1 -1
- package/dist/spec.d.ts +2 -5
- package/dist/spec.js +13 -6
- package/dist/spec.js.map +1 -1
- package/dist/sync.d.ts +3 -11
- package/dist/sync.js +81 -20
- package/dist/sync.js.map +1 -1
- package/dist/template.d.ts +58 -0
- package/dist/template.js +244 -0
- package/dist/template.js.map +1 -0
- package/dist/update.js +4 -4
- package/package.json +17 -16
- package/templates/AGENTS.md +162 -0
- package/templates/CLAUDE.md +11 -118
- package/templates/app/.envs/.env.development +18 -0
- package/templates/app/.envs/.env.production +19 -0
- package/templates/app/.envs/.env.staging +19 -0
- package/templates/app/README.md +327 -0
- package/templates/app/app.ts +3 -0
- package/templates/app/config/app.config.ts +73 -0
- package/templates/app/docs/agent-manifest.json +724 -0
- package/templates/app/ecosystem.config.cjs +60 -0
- package/templates/app/modules/agent/controllers/agent.controllers.ts +31 -0
- package/templates/app/modules/agent/i18n/en.json +5 -0
- package/templates/app/modules/agent/i18n/fr.json +5 -0
- package/templates/app/modules/agent/module.acl.ts +14 -0
- package/templates/app/modules/agent/package.json +21 -0
- package/templates/app/modules/agent/routes/agent.routes.ts +29 -0
- package/templates/app/modules/agent/schemas/agent.schemas.ts +9 -0
- package/templates/app/modules/auth/bootstrap/credentials.bootstrap.ts +26 -0
- package/templates/app/modules/auth/bootstrap/seed-admin.bootstrap.ts +44 -0
- package/templates/app/modules/auth/controllers/auth.controllers.ts +312 -0
- package/templates/app/modules/auth/helpers/access-check.ts +42 -0
- package/templates/app/modules/auth/helpers/auth.ts +48 -0
- package/templates/app/modules/auth/helpers/documents.ts +57 -0
- package/templates/app/modules/auth/helpers/passport.ts +180 -0
- package/templates/app/modules/auth/helpers/passwords.ts +58 -0
- package/templates/app/modules/auth/helpers/session-cache.ts +67 -0
- package/templates/app/modules/auth/helpers/tokens.ts +329 -0
- package/templates/app/modules/auth/i18n/en.json +19 -0
- package/templates/app/modules/auth/i18n/fr.json +19 -0
- package/templates/app/modules/auth/lib/index.ts +12 -0
- package/templates/app/modules/auth/models/credential.model.ts +31 -0
- package/templates/app/modules/auth/models/revoked-token.model.ts +20 -0
- package/templates/app/modules/auth/models/session.model.ts +42 -0
- package/templates/app/modules/auth/module.acl.ts +41 -0
- package/templates/app/modules/auth/module.config.ts +30 -0
- package/templates/app/modules/auth/package.json +61 -0
- package/templates/app/modules/auth/routes/auth.routes.ts +94 -0
- package/templates/app/modules/auth/schemas/auth.schemas.ts +37 -0
- package/templates/app/modules/auth/tests/auth.test.ts +1079 -0
- package/templates/app/modules/config/controllers/config.controllers.ts +89 -0
- package/templates/app/modules/config/helpers/audit.ts +41 -0
- package/templates/app/modules/config/helpers/describe.ts +72 -0
- package/templates/app/modules/config/i18n/en.json +6 -0
- package/templates/app/modules/config/i18n/fr.json +6 -0
- package/templates/app/modules/config/models/config-audit.model.ts +26 -0
- package/templates/app/modules/config/module.acl.ts +12 -0
- package/templates/app/modules/config/package.json +23 -0
- package/templates/app/modules/config/routes/config.routes.ts +37 -0
- package/templates/app/modules/config/schemas/config.schemas.ts +13 -0
- package/templates/app/modules/config/tests/config.test.ts +126 -0
- package/templates/app/modules/core/bootstrap/cache.bootstrap.ts +12 -0
- package/templates/app/modules/core/helpers/cache/index.ts +60 -0
- package/templates/app/modules/core/helpers/cache/memory-cache.ts +53 -0
- package/templates/app/modules/core/helpers/cache/redis-cache.ts +68 -0
- package/templates/app/modules/core/helpers/cache/types.ts +37 -0
- package/templates/app/modules/core/helpers/lazy.ts +13 -0
- package/templates/app/modules/core/helpers/logger/index.ts +115 -0
- package/templates/app/modules/core/helpers/mongoose/index.ts +27 -0
- package/templates/app/modules/core/helpers/mongoose/plugins/base.plugin.ts +14 -0
- package/templates/app/modules/core/helpers/mongoose/plugins/soft-delete.plugin.ts +124 -0
- package/templates/app/modules/core/helpers/mongoose/plugins/timestamps.plugin.ts +13 -0
- package/templates/app/modules/core/helpers/mongoose/plugins/types.ts +45 -0
- package/templates/app/modules/core/helpers/mongoose/register.ts +20 -0
- package/templates/app/modules/core/lib/index.ts +34 -0
- package/templates/app/modules/core/module.config.ts +24 -0
- package/templates/app/modules/core/package.json +80 -0
- package/templates/app/modules/core/routes/core.routes.ts +24 -0
- package/templates/app/modules/core/tests/core.test.ts +19 -0
- package/templates/app/modules/hello/agent/hello.agent.ts +33 -0
- package/templates/app/modules/hello/bootstrap/seed-greetings.bootstrap.ts +27 -0
- package/templates/app/modules/hello/controllers/greetings.controllers.ts +43 -0
- package/templates/app/modules/hello/controllers/hello.controllers.ts +50 -0
- package/templates/app/modules/hello/helpers/greeting.ts +8 -0
- package/templates/app/modules/hello/i18n/en.json +8 -0
- package/templates/app/modules/hello/i18n/fr.json +8 -0
- package/templates/app/modules/hello/models/greeting.model.ts +24 -0
- package/templates/app/modules/hello/module.acl.ts +16 -0
- package/templates/app/modules/hello/module.config.ts +14 -0
- package/templates/app/modules/hello/package.json +24 -0
- package/templates/app/modules/hello/queues/hello.queues.ts +38 -0
- package/templates/app/modules/hello/routes/greetings.routes.ts +25 -0
- package/templates/app/modules/hello/routes/hello.routes.ts +44 -0
- package/templates/app/modules/hello/sockets/hello.sockets.ts +21 -0
- package/templates/app/modules/hello/tests/hello.test.ts +19 -0
- package/templates/app/modules/security/bootstrap/rate-limit.bootstrap.ts +8 -0
- package/templates/app/modules/security/helpers/hardening.ts +65 -0
- package/templates/app/modules/security/helpers/proxy.ts +16 -0
- package/templates/app/modules/security/helpers/rate-limit.ts +150 -0
- package/templates/app/modules/security/module.config.ts +59 -0
- package/templates/app/modules/security/package.json +26 -0
- package/templates/app/modules/security/routes/security.routes.ts +42 -0
- package/templates/app/modules/security/tests/security.test.ts +174 -0
- package/templates/app/modules/settings/bootstrap/log-streams.bootstrap.ts +12 -0
- package/templates/app/modules/settings/controllers/settings.controllers.ts +58 -0
- package/templates/app/modules/settings/helpers/access-lease.ts +99 -0
- package/templates/app/modules/settings/helpers/log-stream.ts +272 -0
- package/templates/app/modules/settings/helpers/log-tail.ts +253 -0
- package/templates/app/modules/settings/i18n/en.json +3 -0
- package/templates/app/modules/settings/i18n/fr.json +3 -0
- package/templates/app/modules/settings/module.acl.ts +12 -0
- package/templates/app/modules/settings/package.json +21 -0
- package/templates/app/modules/settings/routes/settings.routes.ts +33 -0
- package/templates/app/modules/settings/schemas/settings.schemas.ts +8 -0
- package/templates/app/modules/settings/tests/settings.test.ts +409 -0
- package/templates/app/modules/users/controllers/users.controllers.ts +113 -0
- package/templates/app/modules/users/i18n/en.json +14 -0
- package/templates/app/modules/users/i18n/fr.json +14 -0
- package/templates/app/modules/users/models/user.model.ts +18 -0
- package/templates/app/modules/users/module.acl.ts +10 -0
- package/templates/app/modules/users/module.config.ts +11 -0
- package/templates/app/modules/users/package.json +24 -0
- package/templates/app/modules/users/queues/users.queues.ts +31 -0
- package/templates/app/modules/users/routes/users.routes.ts +60 -0
- package/templates/app/modules/users/schemas/users.schemas.ts +30 -0
- package/templates/app/modules/users/sockets/users.sockets.ts +18 -0
- package/templates/app/nginx/development.conf +44 -0
- package/templates/app/nginx/production.conf +75 -0
- package/templates/app/nginx/staging.conf +51 -0
- package/templates/app/package.json +58 -0
- package/templates/app/scripts/count-greetings.ts +29 -0
- package/templates/app/scripts/deploy-nginx.ts +143 -0
- package/templates/app/scripts/list-users.ts +34 -0
- package/templates/app/tests/app.test.ts +28 -0
- package/templates/app/tests/support.ts +36 -0
- package/templates/app/tsconfig.json +28 -0
- package/templates/app/vitest.config.ts +6 -0
- package/templates/app/worker.ts +6 -0
- package/templates/claude/skills/tulipes-agent-tool/SKILL.md +83 -0
- package/templates/claude/skills/tulipes-boot-errors/SKILL.md +11 -5
- package/templates/claude/skills/tulipes-config/SKILL.md +74 -0
- package/templates/claude/skills/tulipes-endpoint/SKILL.md +38 -25
- package/templates/claude/skills/tulipes-env-variable/SKILL.md +54 -26
- package/templates/claude/skills/tulipes-model/SKILL.md +11 -4
- package/templates/claude/skills/tulipes-module/SKILL.md +11 -10
- package/templates/claude/skills/tulipes-queue/SKILL.md +20 -6
- package/templates/claude/skills/tulipes-settings/SKILL.md +49 -100
- package/templates/claude/skills/tulipes-spec/SKILL.md +10 -12
- package/templates/minimal/README.md +21 -0
- package/templates/minimal/app.ts +3 -0
- package/templates/minimal/config/app.config.ts +16 -0
- package/templates/minimal/modules/agent/module.acl.ts +3 -0
- package/templates/minimal/modules/agent/package.json +14 -0
- package/templates/minimal/modules/agent/routes/agent.routes.ts +20 -0
- package/templates/minimal/modules/core/module.acl.ts +4 -0
- package/templates/minimal/modules/core/package.json +21 -0
- package/templates/minimal/modules/hello/i18n/en.json +3 -0
- package/templates/minimal/modules/hello/module.acl.ts +2 -0
- package/templates/minimal/modules/hello/package.json +11 -0
- package/templates/minimal/modules/hello/routes/health.routes.ts +9 -0
- package/templates/minimal/modules/hello/routes/hello.routes.ts +10 -0
- package/templates/minimal/modules/security/package.json +15 -0
- package/templates/minimal/modules/security/routes/security.routes.ts +8 -0
- package/templates/minimal/package.json +38 -0
- package/templates/minimal/tests/app.test.ts +52 -0
- package/templates/minimal/tsconfig.json +22 -0
- package/templates/minimal/vitest.config.ts +5 -0
- package/templates/overlays/no-queues/ecosystem.config.cjs +52 -0
- package/templates/overlays/no-queues/modules/hello/controllers/greetings.controllers.ts +41 -0
- package/templates/overlays/no-queues/modules/users/controllers/users.controllers.ts +107 -0
- package/templates/versions.json +11 -0
- package/dist/minimal.d.ts +0 -2
- package/dist/minimal.js +0 -2200
- package/dist/minimal.js.map +0 -1
- /package/templates/{browser-auth.md → app/docs/browser-auth.md} +0 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Working in this project
|
|
2
|
+
|
|
3
|
+
This is a **Tulipes** application (`@tulipes/core`) — a module-based Express
|
|
4
|
+
framework. It is convention-driven and deliberately fail-closed: almost
|
|
5
|
+
everything that can be validated at boot is, and a violation crashes the
|
|
6
|
+
process with a report rather than degrading quietly.
|
|
7
|
+
|
|
8
|
+
Writing plain Express here will fight the framework. Read the rule that
|
|
9
|
+
applies before you write code.
|
|
10
|
+
|
|
11
|
+
The `→ skill x` pointers below name a task-scoped guide in
|
|
12
|
+
`.claude/skills/<name>/SKILL.md`. Claude Code loads those on demand; any other
|
|
13
|
+
agent can read the file directly — they are plain markdown, and each one covers
|
|
14
|
+
one contract in depth.
|
|
15
|
+
|
|
16
|
+
## Preset scope
|
|
17
|
+
|
|
18
|
+
Read the installed modules and package scripts before applying integration examples.
|
|
19
|
+
The minimal preset has core, security, hello and agent only. Auth, users,
|
|
20
|
+
config, settings, MongoDB, Redis, queues, sockets, shared logger/cache helpers
|
|
21
|
+
and PM2 examples below apply when those modules are installed (the production
|
|
22
|
+
preset). Minimal
|
|
23
|
+
requests use the framework guest identity. Grant permissions to roles the app
|
|
24
|
+
actually defines. Use the generated Vitest suite for changes; production tests
|
|
25
|
+
require dedicated TEST_MONGO_URI and TEST_REDIS_URL services.
|
|
26
|
+
|
|
27
|
+
## Non-negotiable rules
|
|
28
|
+
|
|
29
|
+
1. **Never read `process.env` directly, and keep the environment small.**
|
|
30
|
+
A value is an environment variable only if boot needs it before the
|
|
31
|
+
database answers (connection strings, the port, log destination) or it
|
|
32
|
+
is a secret: declared under `tulipes.env` in the module's `package.json`, read with
|
|
33
|
+
`ctx.Environment.get("NAME")`. Everything else is a *setting*: declared
|
|
34
|
+
with a zod schema in that module's `module.config.ts`, stored in the
|
|
35
|
+
settings store (the `settings` collection here; a JSON file under
|
|
36
|
+
`config/` in an app without the Mongo store), edited through the
|
|
37
|
+
console, read live at `ctx.config.<module>`. → skill `tulipes-env-variable`
|
|
38
|
+
2. **A feature is a module, not a layer.** Everything a feature needs —
|
|
39
|
+
routes, models, config, permissions, jobs — lives in one folder under
|
|
40
|
+
`modules/`. Never add a top-level `services/` or `middlewares/` folder.
|
|
41
|
+
A module's root holds exactly `package.json`, `module.config.ts` and
|
|
42
|
+
`module.acl.ts`; everything else has a folder (`routes/`, `controllers/`,
|
|
43
|
+
`models/`, `schemas/`, `helpers/`, `bootstrap/`, `queues/`, `sockets/`,
|
|
44
|
+
`i18n/`, `agent/`, `lib/`, `tests/`), and the boot refuses a stray root file.
|
|
45
|
+
→ skill `tulipes-module`
|
|
46
|
+
3. **Never call `mongoose.model()` yourself.** Models are declarations
|
|
47
|
+
picked up by the framework and read from `ctx.models`. Persisted and
|
|
48
|
+
wire field names are `snake_case` (`created_at`, never `createdAt`);
|
|
49
|
+
camelCase is for TypeScript-only names. → skill `tulipes-model`
|
|
50
|
+
4. **Global middleware belongs to a sys-tier module**, not to the
|
|
51
|
+
entrypoint. `app.ts` stays three lines. → skill `tulipes-endpoint`
|
|
52
|
+
5. **Run `pnpm sync` after touching a module's `tulipes.env`, a
|
|
53
|
+
settings schema in `module.config.ts`, a model file or a queue file's
|
|
54
|
+
`jobs` export.** It regenerates `types/tulipes.d.ts` and `.env.example`;
|
|
55
|
+
stale generated types are a lie. With it, `ctx.models.get("User")` is
|
|
56
|
+
typed from the model's schema and `queues.add("users.welcome", …)` from
|
|
57
|
+
the queue's job schema, so a wrong name or payload does not compile. `module.config.ts`
|
|
58
|
+
has exactly one export, `defineConfig({ schema })`; initialization and
|
|
59
|
+
cleanup belong to `bootstrap/*.bootstrap.ts`, which may return the
|
|
60
|
+
function that closes what it opened, and long-lived work ends on
|
|
61
|
+
`ctx.signal`.
|
|
62
|
+
6. **Every response is `res.respond({ data, meta })`** — never `res.json`,
|
|
63
|
+
which ships a raw body outside the envelope. Failures are **thrown**:
|
|
64
|
+
`throw new HttpError(404, "notFound")`, where the message is an i18n
|
|
65
|
+
key and the code derives from it. Never hand-roll an error shape.
|
|
66
|
+
→ skill `tulipes-response`
|
|
67
|
+
7. **Every route declares a RAI**: put `rai({ id: "x:read" })` first in
|
|
68
|
+
each endpoint handler chain, including routes on a nested router.
|
|
69
|
+
The id is also the ACL permission, and core refuses to boot if a route
|
|
70
|
+
is missing one. Public is `acl.allow("guest", "x:read")`, never a
|
|
71
|
+
missing check. → skill `tulipes-endpoint`
|
|
72
|
+
8. **Validate input with a zod schema on the RAI** (`body`, `query`,
|
|
73
|
+
`params`, from `zod/v4`) rather than hand-rolled `if` checks. The same
|
|
74
|
+
declaration generates the OpenAPI and Postman documents, so they cannot
|
|
75
|
+
describe a shape the endpoint rejects. → skill `tulipes-spec`
|
|
76
|
+
9. **User-facing strings are keys, never literals.** `req.t("notFound")`
|
|
77
|
+
inside a route reads that module's `i18n/<locale>.json` — the module is
|
|
78
|
+
the namespace and `rai()` binds it. Never concatenate a translated
|
|
79
|
+
string with a variable. → skill `tulipes-i18n`
|
|
80
|
+
10. **`req.auth` is who is calling** — `{ isAuthenticated, user, session }`,
|
|
81
|
+
resolved before any feature route and never undefined inside one. Never
|
|
82
|
+
re-parse the Authorization header or trust a claim from a token.
|
|
83
|
+
→ skill `tulipes-permissions`
|
|
84
|
+
11. **When auth is installed, secrets never go on the User model.** Passwords and keys belong in
|
|
85
|
+
the auth module's `Credential` model, which is `select: false`.
|
|
86
|
+
12. **When auth is installed, a new feature module's namespace goes in BOTH the `admin` and
|
|
87
|
+
`sysadmin` grants** in `modules/auth/module.acl.ts`. Neither role holds
|
|
88
|
+
`*` — a wildcard would reach `config:write` and `settings:*` and make
|
|
89
|
+
the sysadmin boundary a label rather than a rule. → skills
|
|
90
|
+
`tulipes-config`, `tulipes-settings`
|
|
91
|
+
13. **Cross-module imports use the package name** (`@app/users`,
|
|
92
|
+
`@app/core`), never a relative path that climbs out of the module. The
|
|
93
|
+
workspace boundary is what keeps modules independent, and a package
|
|
94
|
+
name makes the dependency visible in package.json — declare it there
|
|
95
|
+
and in `dependsOn`.
|
|
96
|
+
14. **Every endpoint is a tool an agent can call; describe it as one.** The
|
|
97
|
+
`rai()` name, description and schemas are what the manifest shows, so a
|
|
98
|
+
bare `id` is a tool with no description. A module's `agent/*.agent.ts`
|
|
99
|
+
adds a summary, curates the routes and declares actions that are not
|
|
100
|
+
endpoints; `agent: false` on a `rai()` hides one. → skill
|
|
101
|
+
`tulipes-agent-tool`
|
|
102
|
+
15. **A module's tests live in `modules/<name>/tests/` and boot the app
|
|
103
|
+
through `@tulipes/testing`**: `useTestApp({ rootDir }, { beforeAll,
|
|
104
|
+
afterAll })` once per file, `app.get(app.api("/x"))` with the envelope
|
|
105
|
+
decoded, `app.as("admin")` for a signed-in role. Never boot per test, and
|
|
106
|
+
never reach into another module's files from a test.
|
|
107
|
+
|
|
108
|
+
For endpoint changes, read `tulipes-endpoint`: native routers live in
|
|
109
|
+
`routes/*.routes.ts`, plain `(ctx) => Router` functions: build the router from
|
|
110
|
+
the context, return it for the configured prefix, and read models, settings
|
|
111
|
+
and the environment inside handlers, never while building — `tulipes routes`
|
|
112
|
+
and `tulipes spec` build every router with nothing acquired. Global
|
|
113
|
+
middleware that needs the environment is built on its first request with
|
|
114
|
+
`lazy()` from `@app/core`.
|
|
115
|
+
|
|
116
|
+
## Where things go
|
|
117
|
+
|
|
118
|
+
| Decision | Location |
|
|
119
|
+
|---|---|
|
|
120
|
+
| Connection string, secret, or anything boot needs before the database answers | `tulipes.env` in that module's `package.json` |
|
|
121
|
+
| Policy shared by ≥2 modules and the same everywhere (pagination, locales) | `config/app.config.ts` |
|
|
122
|
+
| A value an operator may change at runtime (limits, modes, toggles, lifetimes) | a field in that module's `module.config.ts` schema, read at `ctx.config.<module>` |
|
|
123
|
+
| Opening a client, seeding rows, ensuring indexes, and closing them again | that module's `bootstrap/*.bootstrap.ts`, returning the cleanup |
|
|
124
|
+
| A string a user will read | that module's `i18n/<locale>.json`, reached with `req.t` |
|
|
125
|
+
| The shape of a request body/query | a zod schema on the route's `rai()` |
|
|
126
|
+
| Middleware for every request | a sys-tier module's routes file |
|
|
127
|
+
| Middleware for one feature's routes | that module's own `Router` |
|
|
128
|
+
| Helper used by ≥2 modules | the sys core module, exported from its `lib/index.ts` and imported as `@app/core` |
|
|
129
|
+
| Helper used by one module | that module's `helpers/` |
|
|
130
|
+
| One-off task (backfill, export, admin chore) | `scripts/`, booted with `mode: "script"` |
|
|
131
|
+
| Anything you would `console.log` | `moduleLogger(ctx.Environment, "<module>", ctx.mode)` from `@app/core` |
|
|
132
|
+
|
|
133
|
+
## Boot order (why load order matters)
|
|
134
|
+
|
|
135
|
+
`sys` modules load before `app` modules; within a tier, lower `priority`
|
|
136
|
+
first; then `dependsOn` topological order. Declarations → models →
|
|
137
|
+
settings → translations → ACL → bootstrap → queues → routes. If code needs
|
|
138
|
+
something another module provides, declare `dependsOn` — do not rely on
|
|
139
|
+
luck.
|
|
140
|
+
|
|
141
|
+
## Commands
|
|
142
|
+
|
|
143
|
+
| Command | Use |
|
|
144
|
+
|---|---|
|
|
145
|
+
| `pnpm dev` | run the backend process (regenerates types first) |
|
|
146
|
+
| `pnpm worker` | run the queue-consumer process |
|
|
147
|
+
| `pnpm sync` | regenerate `types/tulipes.d.ts` + `.env.example` |
|
|
148
|
+
| `pnpm tulipes agent manifest [--role r]` | print every tool the app exposes to agents, offline |
|
|
149
|
+
| `pnpm test` | every `tests/` and `modules/*/tests/` file; each boots the app once through `@tulipes/testing` |
|
|
150
|
+
| `pnpm check` | validate the environment without booting |
|
|
151
|
+
| `pnpm typecheck` | typecheck the whole app |
|
|
152
|
+
| `pnpm tulipes new module <name>` | scaffold a module |
|
|
153
|
+
| `tulipes update` | upgrade the global cli and the framework in every manifest |
|
|
154
|
+
| `pnpm script scripts/x.ts` | run a one-off script against the real app |
|
|
155
|
+
|
|
156
|
+
## Verifying your work
|
|
157
|
+
|
|
158
|
+
A change is not done until the app boots. Run `pnpm typecheck`, then
|
|
159
|
+
`pnpm dev` and confirm the startup banner lists what you added (modules,
|
|
160
|
+
queues, sockets, roles). Boot failures print an aggregate report naming
|
|
161
|
+
every problem at once — read all of it before fixing anything. → skill
|
|
162
|
+
`tulipes-boot-errors`
|
package/templates/CLAUDE.md
CHANGED
|
@@ -1,120 +1,13 @@
|
|
|
1
1
|
# Working in this project
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
The minimal preset has core, security and hello only. Auth, users, settings,
|
|
15
|
-
MongoDB, Redis, queues, sockets, shared logger/cache helpers and PM2 examples
|
|
16
|
-
below apply when those modules are installed (the production preset). Minimal
|
|
17
|
-
requests use the framework guest identity. Grant permissions to roles the app
|
|
18
|
-
actually defines. Use the generated Vitest suite for changes; production tests
|
|
19
|
-
require dedicated TEST_MONGO_URI and TEST_REDIS_URL services.
|
|
20
|
-
|
|
21
|
-
## Non-negotiable rules
|
|
22
|
-
|
|
23
|
-
1. **Never read `process.env` directly.** Every configuration value is
|
|
24
|
-
declared in a module's `meta.variables.json` and read with
|
|
25
|
-
`ctx.Environment.get("NAME")`. Reading an undeclared name throws on
|
|
26
|
-
purpose. → skill `tulipes-env-variable`
|
|
27
|
-
2. **A feature is a module, not a layer.** Everything a feature needs —
|
|
28
|
-
routes, models, config, permissions, jobs — lives in one folder under
|
|
29
|
-
`modules/`. Never add a top-level `services/` or `middlewares/` folder.
|
|
30
|
-
→ skill `tulipes-module`
|
|
31
|
-
3. **Never call `mongoose.model()` yourself.** Models are declarations
|
|
32
|
-
picked up by the framework and read from `ctx.models`. Persisted and
|
|
33
|
-
wire field names are `snake_case` (`created_at`, never `createdAt`);
|
|
34
|
-
camelCase is for TypeScript-only names. → skill `tulipes-model`
|
|
35
|
-
4. **Global middleware belongs to a sys-tier module**, not to the
|
|
36
|
-
entrypoint. `app.ts` stays three lines. → skill `tulipes-endpoint`
|
|
37
|
-
5. **Run `yarn sync` after touching any `meta.variables.json` or
|
|
38
|
-
`module.config.ts`.** It regenerates `types/config.d.ts` and
|
|
39
|
-
`.env.example`; stale generated types are a lie.
|
|
40
|
-
6. **Every response is `res.respond({ data, meta })`** — never `res.json`,
|
|
41
|
-
which ships a raw body outside the envelope. Failures are **thrown**:
|
|
42
|
-
`throw new HttpError(404, "notFound")`, where the message is an i18n
|
|
43
|
-
key and the code derives from it. Never hand-roll an error shape.
|
|
44
|
-
→ skill `tulipes-response`
|
|
45
|
-
7. **Every route declares a RAI**: put `rai({ id: "x:read" })` first in
|
|
46
|
-
each endpoint handler chain, including routes inside `defineRoutes()`.
|
|
47
|
-
The id is also the ACL permission, and core refuses to boot if a route
|
|
48
|
-
is missing one. Public is `acl.allow("guest", "x:read")`, never a
|
|
49
|
-
missing check. → skill `tulipes-endpoint`
|
|
50
|
-
8. **Validate input with a zod schema on the RAI** (`body`, `query`,
|
|
51
|
-
`params`, from `zod/v4`) rather than hand-rolled `if` checks. The same
|
|
52
|
-
declaration generates the OpenAPI and Postman documents, so they cannot
|
|
53
|
-
describe a shape the endpoint rejects. → skill `tulipes-spec`
|
|
54
|
-
9. **User-facing strings are keys, never literals.** `req.t("notFound")`
|
|
55
|
-
inside a route reads that module's `i18n/<locale>.json` — the module is
|
|
56
|
-
the namespace and `rai()` binds it. Never concatenate a translated
|
|
57
|
-
string with a variable. → skill `tulipes-i18n`
|
|
58
|
-
10. **`req.auth` is who is calling** — `{ isAuthenticated, user, session }`,
|
|
59
|
-
resolved before any feature route and never undefined inside one. Never
|
|
60
|
-
re-parse the Authorization header or trust a claim from a token.
|
|
61
|
-
→ skill `tulipes-permissions`
|
|
62
|
-
11. **When auth is installed, secrets never go on the User model.** Passwords and keys belong in
|
|
63
|
-
the auth module's `Credential` model, which is `select: false`.
|
|
64
|
-
12. **When auth is installed, a new feature module's namespace goes in BOTH the `admin` and
|
|
65
|
-
`sysadmin` grants** in `modules/auth/module.acl.ts`. Neither role holds
|
|
66
|
-
`*` — a wildcard would reach `settings:*` and make the sysadmin
|
|
67
|
-
boundary a label rather than a rule. → skill `tulipes-settings`
|
|
68
|
-
13. **Cross-module imports use the package name** (`@app/users`,
|
|
69
|
-
`@app/core`), never a relative path that climbs out of the module. The
|
|
70
|
-
workspace boundary is what keeps modules independent, and a package
|
|
71
|
-
name makes the dependency visible in package.json — declare it there
|
|
72
|
-
and in `dependsOn`.
|
|
73
|
-
|
|
74
|
-
For endpoint changes, read `tulipes-endpoint`: native routers live in
|
|
75
|
-
`routes/*.routes.ts`. Use `defineRoutes()` and `runtime()` for offline inspection;
|
|
76
|
-
keep shared middleware and param callbacks beside their endpoints.
|
|
77
|
-
|
|
78
|
-
## Where things go
|
|
79
|
-
|
|
80
|
-
| Decision | Location |
|
|
81
|
-
|---|---|
|
|
82
|
-
| Value differs per deployment (URL, secret, toggle) | that module's `meta.variables.json` |
|
|
83
|
-
| Policy shared by ≥2 modules (pagination, body limit) | `config/app.config.ts` |
|
|
84
|
-
| Config for one feature | that module's `module.config.ts` |
|
|
85
|
-
| A string a user will read | that module's `i18n/<locale>.json`, reached with `req.t` |
|
|
86
|
-
| The shape of a request body/query | a zod schema on the route's `rai()` |
|
|
87
|
-
| Middleware for every request | a sys-tier module's routes file |
|
|
88
|
-
| Middleware for one feature's routes | that module's own `Router` |
|
|
89
|
-
| Helper used by ≥2 modules | the sys core module, exported from its `index.ts` and imported as `@app/core` |
|
|
90
|
-
| Helper used by one module | that module's `helpers/` |
|
|
91
|
-
| One-off task (backfill, export, admin chore) | `scripts/`, booted with `mode: "script"` |
|
|
92
|
-
| Anything you would `console.log` | `moduleLogger(ctx.Environment, "<module>", ctx.mode)` from `@app/core` |
|
|
93
|
-
|
|
94
|
-
## Boot order (why load order matters)
|
|
95
|
-
|
|
96
|
-
`sys` modules load before `app` modules; within a tier, lower `priority`
|
|
97
|
-
first; then `dependsOn` topological order. Config → translations → ACL →
|
|
98
|
-
database → queues → routes. If code needs something another module
|
|
99
|
-
provides, declare `dependsOn` — do not rely on luck.
|
|
100
|
-
|
|
101
|
-
## Commands
|
|
102
|
-
|
|
103
|
-
| Command | Use |
|
|
104
|
-
|---|---|
|
|
105
|
-
| `yarn dev` | run the backend process (regenerates types first) |
|
|
106
|
-
| `yarn worker` | run the queue-consumer process |
|
|
107
|
-
| `yarn sync` | regenerate `types/config.d.ts` + `.env.example` |
|
|
108
|
-
| `yarn check` | validate the environment without booting |
|
|
109
|
-
| `yarn typecheck` | typecheck the whole app |
|
|
110
|
-
| `yarn tulipes new module <name>` | scaffold a module |
|
|
111
|
-
| `tulipes update` | upgrade the global cli and the framework in every manifest |
|
|
112
|
-
| `yarn script scripts/x.ts` | run a one-off script against the real app |
|
|
113
|
-
|
|
114
|
-
## Verifying your work
|
|
115
|
-
|
|
116
|
-
A change is not done until the app boots. Run `yarn typecheck`, then
|
|
117
|
-
`yarn dev` and confirm the startup banner lists what you added (modules,
|
|
118
|
-
queues, sockets, roles). Boot failures print an aggregate report naming
|
|
119
|
-
every problem at once — read all of it before fixing anything. → skill
|
|
120
|
-
`tulipes-boot-errors`
|
|
3
|
+
The project instructions live in **[AGENTS.md](./AGENTS.md)** — the
|
|
4
|
+
vendor-neutral file every coding agent reads. Read it before writing code
|
|
5
|
+
here; it holds the non-negotiable rules, where each kind of code belongs, the
|
|
6
|
+
boot order and the commands.
|
|
7
|
+
|
|
8
|
+
This file exists so Claude Code picks that up automatically. The other
|
|
9
|
+
Claude-specific piece is `.claude/skills/`: task-scoped guides — module,
|
|
10
|
+
environment variable, endpoint, response, model, queue, socket, permissions,
|
|
11
|
+
settings (the config module and the log stream), i18n, spec, agent tools and
|
|
12
|
+
reading boot errors — loaded on demand when a task matches one. AGENTS.md
|
|
13
|
+
points at them by name.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Development. NOT committed — .gitignore excludes .envs/, because a
|
|
2
|
+
# file holding working credentials does not belong in a repository.
|
|
3
|
+
# The committed contract is .env.example at the root: copy it here and
|
|
4
|
+
# fill it in. Real deployments inject variables through the container
|
|
5
|
+
# environment, which always wins over this file.
|
|
6
|
+
|
|
7
|
+
PORT=3000
|
|
8
|
+
LOG_LEVEL=debug
|
|
9
|
+
|
|
10
|
+
MONGO_URI=mongodb://127.0.0.1:27017/tulipes-example
|
|
11
|
+
REDIS_URL=redis://127.0.0.1:6379
|
|
12
|
+
|
|
13
|
+
# Auth. Separate keys, so leaking one cannot mint the other.
|
|
14
|
+
JWT_ACCESS_SECRET=dev-only-access-secret-change-me
|
|
15
|
+
JWT_REFRESH_SECRET=dev-only-refresh-secret-change-me
|
|
16
|
+
AUTH_ADMIN_EMAIL=admin@example.com
|
|
17
|
+
AUTH_ADMIN_PASSWORD=dev-admin-password
|
|
18
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Production. NOT committed — .gitignore excludes .envs/. Recreate it
|
|
2
|
+
# on the deployment host from .env.example, or leave it out entirely
|
|
3
|
+
# and inject everything through the container environment.
|
|
4
|
+
#
|
|
5
|
+
# The blanks below are intentional. Real deployments inject MONGO_URI,
|
|
6
|
+
# REDIS_URL and any secret through the container environment, which
|
|
7
|
+
# always wins over this file. Booting without them fails closed with a
|
|
8
|
+
# report naming every missing variable — run `pnpm check` to see it
|
|
9
|
+
# without starting the app.
|
|
10
|
+
|
|
11
|
+
PORT=3000
|
|
12
|
+
LOG_LEVEL=info
|
|
13
|
+
|
|
14
|
+
# MONGO_URI=
|
|
15
|
+
# REDIS_URL=
|
|
16
|
+
# JWT_ACCESS_SECRET=
|
|
17
|
+
# JWT_REFRESH_SECRET=
|
|
18
|
+
# AUTH_ADMIN_EMAIL=
|
|
19
|
+
# AUTH_ADMIN_PASSWORD=
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Staging. NOT committed — .gitignore excludes .envs/. Recreate it on
|
|
2
|
+
# the deployment host from .env.example, or leave it out entirely and
|
|
3
|
+
# inject everything through the container environment.
|
|
4
|
+
#
|
|
5
|
+
# The blanks below are intentional. Real deployments inject MONGO_URI,
|
|
6
|
+
# REDIS_URL and any secret through the container environment, which
|
|
7
|
+
# always wins over this file. Booting without them fails closed with a
|
|
8
|
+
# report naming every missing variable — run `pnpm check` to see it
|
|
9
|
+
# without starting the app.
|
|
10
|
+
|
|
11
|
+
PORT=3000
|
|
12
|
+
LOG_LEVEL=info
|
|
13
|
+
|
|
14
|
+
# MONGO_URI=
|
|
15
|
+
# REDIS_URL=
|
|
16
|
+
# JWT_ACCESS_SECRET=
|
|
17
|
+
# JWT_REFRESH_SECRET=
|
|
18
|
+
# AUTH_ADMIN_EMAIL=
|
|
19
|
+
# AUTH_ADMIN_PASSWORD=
|