domain-driver 0.1.0 â 0.2.0
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 +138 -146
- package/dist/commands/action.js +118 -0
- package/dist/commands/component.js +18 -19
- package/dist/commands/container.js +10 -31
- package/dist/commands/controller.js +85 -0
- package/dist/commands/feature.js +59 -51
- package/dist/commands/hints.js +28 -0
- package/dist/commands/hook.js +10 -111
- package/dist/commands/repository.js +17 -115
- package/dist/commands/resolve.js +61 -0
- package/dist/commands/schema.js +22 -69
- package/dist/commands/service.js +15 -113
- package/dist/commands/sides.js +34 -0
- package/dist/commands/target.js +32 -0
- package/dist/commands/types.js +9 -17
- package/dist/commands/write.js +66 -0
- package/dist/index.js +109 -94
- package/dist/init/content.js +73 -0
- package/dist/init/init.js +60 -0
- package/dist/init/markers.js +21 -0
- package/dist/postinstall.js +80 -0
- package/dist/stack/detect.js +125 -0
- package/dist/stack/profiles/nest.js +18 -0
- package/dist/stack/profiles/next-frontend.js +28 -0
- package/dist/stack/profiles/next-fullstack.js +44 -0
- package/dist/stack/profiles/node.js +17 -0
- package/dist/stack/profiles/react.js +19 -0
- package/dist/stack/registry.js +63 -0
- package/dist/stack/types.js +24 -0
- package/dist/templates/actions.js +68 -0
- package/dist/templates/backend/server-repository.js +22 -0
- package/dist/templates/context.js +52 -0
- package/dist/templates/controllers/express.js +60 -0
- package/dist/templates/controllers/fastify.js +54 -0
- package/dist/templates/controllers/generic.js +30 -0
- package/dist/templates/controllers/hono.js +52 -0
- package/dist/templates/controllers/nest.js +63 -0
- package/dist/templates/controllers/next-action-route.js +40 -0
- package/dist/templates/controllers/next-route.js +62 -0
- package/dist/templates/controllers/node.js +46 -0
- package/dist/templates/controllers/shape.js +28 -0
- package/dist/templates/frontend/client-repository.js +38 -0
- package/dist/templates/frontend/component.js +18 -0
- package/dist/templates/frontend/container.js +28 -0
- package/{src/commands/hook.ts â dist/templates/frontend/hook.js} +28 -41
- package/dist/templates/frontend/page.js +26 -0
- package/dist/templates/nest/dto.js +12 -0
- package/dist/templates/nest/injectable.js +4 -0
- package/dist/templates/nest/module.js +41 -0
- package/dist/templates/service.js +40 -0
- package/dist/templates/shared/schema.js +13 -0
- package/dist/templates/shared/types.js +12 -0
- package/dist/templates/signatures.js +15 -0
- package/dist/utils/alias.js +80 -0
- package/dist/utils/fs.js +70 -0
- package/dist/utils/imports.js +61 -0
- package/dist/utils/naming.js +31 -0
- package/dist/utils/paths.js +44 -0
- package/package.json +27 -6
- package/scripts/postinstall.js +7 -0
- package/.github/workflows/publish.yml +0 -28
- package/dist/utils.js +0 -132
- package/src/__tests__/utils.test.ts +0 -128
- package/src/commands/__tests__/feature.test.ts +0 -85
- package/src/commands/__tests__/imports.test.ts +0 -115
- package/src/commands/__tests__/individual.test.ts +0 -137
- package/src/commands/component.ts +0 -33
- package/src/commands/container.ts +0 -42
- package/src/commands/feature.ts +0 -74
- package/src/commands/repository.ts +0 -95
- package/src/commands/schema.ts +0 -47
- package/src/commands/service.ts +0 -93
- package/src/commands/types.ts +0 -25
- package/src/index.ts +0 -126
- package/src/utils.ts +0 -115
- package/tsconfig.json +0 -13
- package/vitest.config.ts +0 -7
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# domain-driver đ
|
|
2
2
|
|
|
3
|
-
A CLI scaffolding tool for domain-driven
|
|
3
|
+
A CLI scaffolding tool for domain-driven feature folders. Like Laravel's `php artisan make`, but for Next.js, React, Node, and NestJS projects. It detects your stack and generates only the layers that stack needs, one file per action.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -13,249 +13,241 @@ npm install -g domain-driver
|
|
|
13
13
|
Or use without installing:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
npx domain-driver make:feature <
|
|
16
|
+
npx domain-driver make:feature <feature>[/<Entity>]
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
21
|
-
##
|
|
22
|
-
|
|
23
|
-
### `make:feature`
|
|
21
|
+
## Stack detection
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
Every command reads your `package.json` once and prints the stack it found before writing anything:
|
|
26
24
|
|
|
27
|
-
```bash
|
|
28
|
-
domain-driver make:feature <n>
|
|
29
|
-
domain-driver make:feature <n> -a
|
|
30
25
|
```
|
|
26
|
+
Stack: next-fullstack (detected)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
| Stack | Detected when | Features live in |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| `nest` | `@nestjs/core` is a dependency | `src/<feature>` |
|
|
32
|
+
| `next-fullstack` | `next` is a dependency and `app/api`, `src/app/api`, `pages/api`, or `src/pages/api` exists | `app/<feature>` or `src/app/<feature>` |
|
|
33
|
+
| `next-frontend` | `next` is a dependency, no api directory | `app/<feature>` or `src/app/<feature>` |
|
|
34
|
+
| `react` | `react` is a dependency, `next` is not | `src/features/<feature>` or `features/<feature>` |
|
|
35
|
+
| `node` | none of the above | `src/features/<feature>` or `features/<feature>` |
|
|
36
|
+
|
|
37
|
+
For `node`, the tool also picks up Express, Fastify, or Hono and shapes the controllers accordingly. With none of them present it generates framework-agnostic controller classes.
|
|
31
38
|
|
|
32
|
-
|
|
39
|
+
Override detection with `--stack`:
|
|
33
40
|
|
|
34
41
|
```bash
|
|
35
|
-
domain-driver make:feature
|
|
36
|
-
domain-driver make:feature coffee-type -a # folders + all files
|
|
42
|
+
domain-driver --stack nest make:feature cat -a
|
|
37
43
|
```
|
|
38
44
|
|
|
39
45
|
---
|
|
40
46
|
|
|
41
|
-
|
|
47
|
+
## What each stack generates
|
|
42
48
|
|
|
43
|
-
|
|
49
|
+
| Layer | next-fullstack | next-frontend | react | node | nest |
|
|
50
|
+
|---|---|---|---|---|---|
|
|
51
|
+
| `page.tsx` | yes | yes | | | |
|
|
52
|
+
| components | client + server | client + server | flat | | |
|
|
53
|
+
| containers, hooks | yes | yes | yes | | |
|
|
54
|
+
| client services + repositories (fetch) | `client/` | top level | top level | | |
|
|
55
|
+
| server services + repositories (database stubs) | `server/` | | | yes | yes |
|
|
56
|
+
| controllers | `app/api/<feature>/` route handlers | | | five files + routes file | five files |
|
|
57
|
+
| module | | | | | yes |
|
|
58
|
+
| DTOs (`nestjs-zod`) | | | | | yes |
|
|
59
|
+
| schemas (Zod), types | yes | yes | yes | yes | yes |
|
|
44
60
|
|
|
45
|
-
|
|
46
|
-
domain-driver make:component <feature> <n>
|
|
47
|
-
domain-driver make:component <feature> <n> client
|
|
48
|
-
domain-driver make:component <feature> <n> server
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
domain-driver make:component coffee-type CoffeeTypeList
|
|
53
|
-
domain-driver make:component coffee-type CoffeeTypeForm client
|
|
54
|
-
domain-driver make:component coffee-type CoffeeTypeCard server
|
|
55
|
-
```
|
|
61
|
+
Every layer that has actions gets one file per action: `List`, `Show`, `Create`, `Update`, `Delete`. Saving a cat means `CreateCat.controller.ts`, `CreateCat.service.ts`, `CreateCat.repository.ts`, and `CreateCat.schema.ts`.
|
|
56
62
|
|
|
57
63
|
---
|
|
58
64
|
|
|
59
|
-
|
|
65
|
+
## Commands
|
|
60
66
|
|
|
61
|
-
|
|
67
|
+
Every layer command takes one target, `<feature>/<Name>`: the feature folder on the left, the name used inside the files on the right. `users/User` reads as "User inside users".
|
|
62
68
|
|
|
63
|
-
|
|
64
|
-
domain-driver make:container <feature> <n>
|
|
65
|
-
```
|
|
69
|
+
### `make:feature`
|
|
66
70
|
|
|
67
71
|
```bash
|
|
68
|
-
domain-driver make:
|
|
72
|
+
domain-driver make:feature users # folders + .gitkeep, plus page.tsx (Next) or the module (Nest)
|
|
73
|
+
domain-driver make:feature users/User -a # every layer for the detected stack, entity User
|
|
69
74
|
```
|
|
70
75
|
|
|
71
|
-
|
|
76
|
+
Feature names must be kebab-case. Without `/Entity`, the entity is the PascalCase feature name (`users` becomes `Users`).
|
|
72
77
|
|
|
73
|
-
### `make:
|
|
74
|
-
|
|
75
|
-
Scaffold a custom hook inside an existing feature.
|
|
78
|
+
### `make:action`
|
|
76
79
|
|
|
77
80
|
```bash
|
|
78
|
-
domain-driver make:
|
|
81
|
+
domain-driver make:action users/User findActiveUsers
|
|
82
|
+
domain-driver make:action users/User archiveUser --with-input --returns one
|
|
79
83
|
```
|
|
80
84
|
|
|
81
|
-
|
|
82
|
-
domain-driver make:hook coffee-type useCoffeeType
|
|
83
|
-
```
|
|
85
|
+
Scaffolds a bespoke operation as its own files, so it never lands inside `ShowUser` or `ListUser`. The action name is used as-is for file and class names: `FindActiveUsers.service.ts`, `FindActiveUsersService`, handler `findActiveUsersController`, route `/find-active-users`. Include the noun in the name (`archiveUser`, not `archive`).
|
|
84
86
|
|
|
85
|
-
|
|
87
|
+
| Option | Effect |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `--with-input` | writes `ArchiveUser.schema.ts` (and the Nest DTO), the service takes `data`, the controller is a `POST` with body validation |
|
|
90
|
+
| `--returns list` | `Promise<User[]>` (default) |
|
|
91
|
+
| `--returns one` | `Promise<User>` |
|
|
92
|
+
| `--returns void` | `Promise<void>`, controller responds 204 |
|
|
86
93
|
|
|
87
|
-
|
|
94
|
+
| Stack | Files written |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `node` | service, repository, controller for the detected framework, plus the line to add above the `/:id` routes in `<feature>.routes.ts` printed |
|
|
97
|
+
| `nest` | injectable service and repository, `@Controller` class, DTO with input, plus the classes to register printed, with the controller listed before `Show<Entity>Controller` |
|
|
98
|
+
| `next-fullstack` | server service and repository, client service and repository, `app/api/<feature>/<slug>/route.ts` |
|
|
99
|
+
| `next-frontend`, `react` | client service and repository calling `/api/<feature>/<slug>` |
|
|
88
100
|
|
|
89
|
-
|
|
101
|
+
### `make:controller`
|
|
90
102
|
|
|
91
103
|
```bash
|
|
92
|
-
domain-driver make:
|
|
104
|
+
domain-driver make:controller users/User
|
|
93
105
|
```
|
|
94
106
|
|
|
95
|
-
|
|
96
|
-
domain-driver make:service coffee-type CoffeeType
|
|
97
|
-
```
|
|
107
|
+
Node: five controllers plus `<feature>.routes.ts` for Express, Fastify, or Hono. Nest: five single-action controllers. Next.js fullstack: `app/api/<feature>/route.ts` and `app/api/<feature>/[id]/route.ts`. Not available on frontend-only stacks.
|
|
98
108
|
|
|
99
|
-
|
|
109
|
+
### `make:service` and `make:repository`
|
|
100
110
|
|
|
111
|
+
```bash
|
|
112
|
+
domain-driver make:service users/User [--side client|server|both]
|
|
113
|
+
domain-driver make:repository users/User [--side client|server|both]
|
|
101
114
|
```
|
|
102
|
-
app/coffee-type/services/
|
|
103
|
-
âââ ListCoffeeType.service.ts
|
|
104
|
-
âââ ShowCoffeeType.service.ts
|
|
105
|
-
âââ CreateCoffeeType.service.ts
|
|
106
|
-
âââ UpdateCoffeeType.service.ts
|
|
107
|
-
âââ DeleteCoffeeType.service.ts
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
---
|
|
111
115
|
|
|
112
|
-
|
|
116
|
+
`--side` matters on `next-fullstack`, where both sides exist. Default is `both`.
|
|
113
117
|
|
|
114
|
-
|
|
118
|
+
### `make:schema`
|
|
115
119
|
|
|
116
120
|
```bash
|
|
117
|
-
domain-driver make:
|
|
121
|
+
domain-driver make:schema users/User
|
|
118
122
|
```
|
|
119
123
|
|
|
124
|
+
Writes `CreateUser.schema.ts` and `UpdateUser.schema.ts`. On Nest it also writes the matching DTO classes derived with `createZodDto` from `nestjs-zod`.
|
|
125
|
+
|
|
126
|
+
### `make:types`, `make:component`, `make:container`, `make:hook`
|
|
127
|
+
|
|
120
128
|
```bash
|
|
121
|
-
domain-driver make:
|
|
129
|
+
domain-driver make:types users/User
|
|
130
|
+
domain-driver make:component users/UserCard [client|server]
|
|
131
|
+
domain-driver make:container users/UserContainer
|
|
132
|
+
domain-driver make:hook users/useUser
|
|
122
133
|
```
|
|
123
134
|
|
|
124
|
-
|
|
135
|
+
Component, container, and hook commands fail with a clear message on backend stacks, and `server` components are rejected on React.
|
|
125
136
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
âââ CreateCoffeeType.repository.ts
|
|
131
|
-
âââ UpdateCoffeeType.repository.ts
|
|
132
|
-
âââ DeleteCoffeeType.repository.ts
|
|
137
|
+
### `init`
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
domain-driver init
|
|
133
141
|
```
|
|
134
142
|
|
|
135
|
-
|
|
143
|
+
Writes agent guidance into the current project so coding agents scaffold with domain-driver instead of hand-writing layers:
|
|
136
144
|
|
|
137
|
-
|
|
145
|
+
- `AGENTS.md` and `CLAUDE.md` get a section between `<!-- domain-driver:start -->` and `<!-- domain-driver:end -->`. Existing content outside the markers is never touched; the section is created, refreshed in place, or left alone.
|
|
146
|
+
- `.claude/skills/domain-driver/SKILL.md` is a Claude Code skill owned by the tool.
|
|
138
147
|
|
|
139
|
-
|
|
148
|
+
Running `init` twice reports `unchanged`. Re-run it after upgrading domain-driver.
|
|
140
149
|
|
|
141
|
-
|
|
142
|
-
domain-driver make:schema <feature> <n>
|
|
143
|
-
```
|
|
150
|
+
**On install.** A local `npm install domain-driver` in a project runs `init` automatically. It does nothing when `CI` is set, for global installs, when there is no `package.json` in the installing project, or when domain-driver installs itself. Files are written to the directory you ran npm install from, which in a workspace is the repository root. Opt out with `npm install --ignore-scripts`, or delete the marked section afterwards.
|
|
144
151
|
|
|
145
|
-
|
|
146
|
-
domain-driver make:schema coffee-type CoffeeType
|
|
147
|
-
```
|
|
152
|
+
---
|
|
148
153
|
|
|
149
|
-
|
|
154
|
+
## Example: `make:feature coffee-type -a` on Node with Express
|
|
150
155
|
|
|
151
156
|
```
|
|
152
|
-
|
|
153
|
-
âââ
|
|
154
|
-
|
|
157
|
+
src/features/coffee-type/
|
|
158
|
+
âââ coffee-type.routes.ts
|
|
159
|
+
âââ controllers/
|
|
160
|
+
â âââ ListCoffeeType.controller.ts
|
|
161
|
+
â âââ ShowCoffeeType.controller.ts
|
|
162
|
+
â âââ CreateCoffeeType.controller.ts
|
|
163
|
+
â âââ UpdateCoffeeType.controller.ts
|
|
164
|
+
â âââ DeleteCoffeeType.controller.ts
|
|
165
|
+
âââ services/ (five files)
|
|
166
|
+
âââ repositories/ (five files, database-agnostic stubs)
|
|
167
|
+
âââ schemas/
|
|
168
|
+
â âââ CreateCoffeeType.schema.ts
|
|
169
|
+
â âââ UpdateCoffeeType.schema.ts
|
|
170
|
+
âââ types/
|
|
171
|
+
âââ CoffeeType.types.ts
|
|
155
172
|
```
|
|
156
173
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
## What `make:feature -a` generates
|
|
174
|
+
Mount the routes with `app.use('/coffee-type', coffeeTypeRoutes)`. Then `make:action coffee-type/CoffeeType findActive` adds `FindActive.controller.ts`, `FindActive.service.ts`, `FindActive.repository.ts`, and prints `router.get('/find-active', findActiveController);` for the routes file.
|
|
160
175
|
|
|
161
|
-
|
|
176
|
+
## Example: `make:feature coffee-type -a` on NestJS
|
|
162
177
|
|
|
163
178
|
```
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
âââ services/
|
|
175
|
-
â âââ ListCoffeeType.service.ts
|
|
176
|
-
â âââ ShowCoffeeType.service.ts
|
|
177
|
-
â âââ CreateCoffeeType.service.ts
|
|
178
|
-
â âââ UpdateCoffeeType.service.ts
|
|
179
|
-
â âââ DeleteCoffeeType.service.ts
|
|
180
|
-
âââ repositories/
|
|
181
|
-
â âââ ListCoffeeType.repository.ts
|
|
182
|
-
â âââ ShowCoffeeType.repository.ts
|
|
183
|
-
â âââ CreateCoffeeType.repository.ts
|
|
184
|
-
â âââ UpdateCoffeeType.repository.ts
|
|
185
|
-
â âââ DeleteCoffeeType.repository.ts
|
|
186
|
-
âââ schemas/
|
|
187
|
-
â âââ CreateCoffeeType.schema.ts
|
|
188
|
-
â âââ UpdateCoffeeType.schema.ts
|
|
189
|
-
âââ page.tsx
|
|
179
|
+
src/coffee-type/
|
|
180
|
+
âââ coffee-type.module.ts registers 5 controllers and 10 providers
|
|
181
|
+
âââ controllers/ five @Controller('coffee-type') classes
|
|
182
|
+
âââ services/ five @Injectable() services
|
|
183
|
+
âââ repositories/ five @Injectable() repositories
|
|
184
|
+
âââ dto/
|
|
185
|
+
â âââ CreateCoffeeType.dto.ts
|
|
186
|
+
â âââ UpdateCoffeeType.dto.ts
|
|
187
|
+
âââ schemas/
|
|
188
|
+
âââ types/
|
|
190
189
|
```
|
|
191
190
|
|
|
191
|
+
Install `nestjs-zod` and register `ZodValidationPipe` as `APP_PIPE` once in your `AppModule`. The tool prints this hint when the package is missing.
|
|
192
|
+
|
|
192
193
|
---
|
|
193
194
|
|
|
194
195
|
## Philosophy
|
|
195
196
|
|
|
196
|
-
|
|
197
|
+
Everything for a feature lives in one folder, and every file does one thing.
|
|
197
198
|
|
|
198
|
-
|
|
199
|
+
- **repositories** â data access only. Client-side repositories call your API; server-side repositories call your database.
|
|
200
|
+
- **services** â business logic, one class per action.
|
|
201
|
+
- **controllers** â HTTP in, service call, HTTP out, one file per action.
|
|
202
|
+
- **hooks** â React state and side effects, calls services.
|
|
203
|
+
- **containers** â wire hooks into UI.
|
|
204
|
+
- **components** â presentational UI.
|
|
205
|
+
- **schemas** â Zod validation for create and update; the update body never carries the id, it comes from the path.
|
|
206
|
+
- **types** â the entity interface.
|
|
199
207
|
|
|
200
|
-
-
|
|
201
|
-
- **services** â business logic, calls repositories
|
|
202
|
-
- **hooks** â React state and side effects, calls services
|
|
203
|
-
- **containers** â wire hooks into UI, no direct data fetching
|
|
204
|
-
- **components** â pure presentational UI, no data dependencies
|
|
205
|
-
- **schemas** â Zod validation for create and update operations
|
|
208
|
+
Server-side repositories throw a clear not-implemented error until you wire your ORM. Compiling code that fails loudly beats a fake store that looks like it works.
|
|
206
209
|
|
|
207
210
|
---
|
|
208
211
|
|
|
209
|
-
##
|
|
212
|
+
## Upgrading from 0.1.0
|
|
210
213
|
|
|
211
|
-
-
|
|
214
|
+
Every layer command now takes a single `<feature>/<Name>` target instead of separate feature and name arguments, for example `make:schema users User` becomes `make:schema users/User`. `make:feature users -a` still works and names the entity `Users`; write `users/User` if you want a different entity name.
|
|
212
215
|
|
|
213
216
|
---
|
|
214
217
|
|
|
215
|
-
##
|
|
218
|
+
## Requirements
|
|
216
219
|
|
|
217
|
-
|
|
220
|
+
- Node.js 18+
|
|
218
221
|
|
|
219
|
-
|
|
222
|
+
---
|
|
220
223
|
|
|
221
224
|
## Local Development
|
|
222
225
|
|
|
223
226
|
```bash
|
|
224
|
-
# Clone the repo
|
|
225
227
|
git clone https://github.com/IsaacHatilima/domain-driver
|
|
226
228
|
cd domain-driver
|
|
227
|
-
|
|
228
|
-
# Install dependencies
|
|
229
229
|
npm install
|
|
230
|
-
|
|
231
|
-
# Build
|
|
232
230
|
npm run build
|
|
233
|
-
|
|
234
|
-
# Link globally for local testing
|
|
235
231
|
npm link
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
domain-driver make:feature test-feature
|
|
239
|
-
domain-driver make:feature test-feature -a
|
|
232
|
+
npm test
|
|
233
|
+
npm run test:coverage
|
|
240
234
|
```
|
|
241
235
|
|
|
242
236
|
---
|
|
243
237
|
|
|
244
238
|
## Roadmap
|
|
245
239
|
|
|
246
|
-
- [x]
|
|
247
|
-
- [x] `make:
|
|
248
|
-
- [x]
|
|
249
|
-
- [x]
|
|
250
|
-
- [x]
|
|
251
|
-
- [
|
|
252
|
-
- [
|
|
253
|
-
- [
|
|
254
|
-
- [ ] Interactive mode â prompt for name if not provided
|
|
255
|
-
- [ ] Config file â customize folder structure per project
|
|
240
|
+
- [x] Stack detection for Next.js, React, Node, and NestJS
|
|
241
|
+
- [x] `make:controller` with Express, Fastify, Hono, Nest, and Next route handlers
|
|
242
|
+
- [x] Per-action files in every layer
|
|
243
|
+
- [x] Bespoke actions with make:action
|
|
244
|
+
- [x] Agent guidance with init and a guarded postinstall
|
|
245
|
+
- [ ] Config file â override stack and feature root per project
|
|
246
|
+
- [ ] Configurable API base URL for client repositories
|
|
247
|
+
- [ ] ORM-aware server repositories
|
|
256
248
|
|
|
257
249
|
---
|
|
258
250
|
|
|
259
251
|
## License
|
|
260
252
|
|
|
261
|
-
MIT
|
|
253
|
+
MIT
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.parseReturns = parseReturns;
|
|
37
|
+
exports.makeAction = makeAction;
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const registry_1 = require("../stack/registry");
|
|
40
|
+
const actions_1 = require("../templates/actions");
|
|
41
|
+
const server_repository_1 = require("../templates/backend/server-repository");
|
|
42
|
+
const nest_1 = require("../templates/controllers/nest");
|
|
43
|
+
const next_action_route_1 = require("../templates/controllers/next-action-route");
|
|
44
|
+
const node_1 = require("../templates/controllers/node");
|
|
45
|
+
const client_repository_1 = require("../templates/frontend/client-repository");
|
|
46
|
+
const dto_1 = require("../templates/nest/dto");
|
|
47
|
+
const service_1 = require("../templates/service");
|
|
48
|
+
const schema_1 = require("../templates/shared/schema");
|
|
49
|
+
const fs_1 = require("../utils/fs");
|
|
50
|
+
const naming_1 = require("../utils/naming");
|
|
51
|
+
const paths_1 = require("../utils/paths");
|
|
52
|
+
const hints_1 = require("./hints");
|
|
53
|
+
const resolve_1 = require("./resolve");
|
|
54
|
+
const write_1 = require("./write");
|
|
55
|
+
function parseReturns(value) {
|
|
56
|
+
if (actions_1.RETURN_KINDS.includes(value))
|
|
57
|
+
return value;
|
|
58
|
+
throw new Error(`Invalid --returns "${value}". Use list, one, or void.`);
|
|
59
|
+
}
|
|
60
|
+
function makeAction(feature, entity, actionName, options) {
|
|
61
|
+
const ctx = (0, resolve_1.requireFeature)(feature);
|
|
62
|
+
const spec = (0, actions_1.customAction)(entity, actionName, options);
|
|
63
|
+
hintMissingTypes(ctx, entity);
|
|
64
|
+
let wroteAny = false;
|
|
65
|
+
if (spec.schema !== null)
|
|
66
|
+
wroteAny = writeInput(ctx, spec) || wroteAny;
|
|
67
|
+
if ((0, registry_1.hasLayer)(ctx.profile, 'serverRepository'))
|
|
68
|
+
wroteAny = writeSide(ctx, spec, entity, 'server') || wroteAny;
|
|
69
|
+
if ((0, registry_1.hasLayer)(ctx.profile, 'controller'))
|
|
70
|
+
wroteAny = writeController(ctx, spec, entity) || wroteAny;
|
|
71
|
+
if ((0, registry_1.hasLayer)(ctx.profile, 'clientRepository'))
|
|
72
|
+
wroteAny = writeSide(ctx, spec, entity, 'client') || wroteAny;
|
|
73
|
+
if (wroteAny)
|
|
74
|
+
console.log(`â
Action "${spec.name}" scaffolded in "${feature}"`);
|
|
75
|
+
return wroteAny;
|
|
76
|
+
}
|
|
77
|
+
function hintMissingTypes(ctx, entity) {
|
|
78
|
+
const typesFile = path.join(ctx.featureDir, (0, registry_1.layerDir)(ctx.profile, 'types'), `${entity}.types.ts`);
|
|
79
|
+
if ((0, fs_1.fileExists)(typesFile))
|
|
80
|
+
return;
|
|
81
|
+
console.log(`âšī¸ types/${entity}.types.ts not found. Run: domain-driver make:types ${ctx.feature}/${entity}`);
|
|
82
|
+
}
|
|
83
|
+
function writeInput(ctx, spec) {
|
|
84
|
+
const schemaFile = path.join((0, resolve_1.ensureLayerDir)(ctx, 'schema'), `${spec.name}.schema.ts`);
|
|
85
|
+
const wroteSchema = (0, write_1.writeIfAbsent)(schemaFile, () => (0, schema_1.renderSchema)(spec.name, (0, naming_1.lowerFirst)(spec.name)));
|
|
86
|
+
if (!(0, registry_1.hasLayer)(ctx.profile, 'dto'))
|
|
87
|
+
return wroteSchema;
|
|
88
|
+
const dtoFile = path.join((0, resolve_1.ensureLayerDir)(ctx, 'dto'), `${spec.name}.dto.ts`);
|
|
89
|
+
const wroteDto = (0, write_1.writeIfAbsent)(dtoFile, () => (0, dto_1.renderDto)(ctx, spec.name, dtoFile));
|
|
90
|
+
(0, hints_1.hintNestjsZod)(ctx.stack);
|
|
91
|
+
return wroteSchema || wroteDto;
|
|
92
|
+
}
|
|
93
|
+
function writeSide(ctx, spec, entity, side) {
|
|
94
|
+
const repositoryLayer = side === 'client' ? 'clientRepository' : 'serverRepository';
|
|
95
|
+
const serviceLayer = side === 'client' ? 'clientService' : 'serverService';
|
|
96
|
+
const renderRepository = side === 'client' ? client_repository_1.renderClientRepository : server_repository_1.renderServerRepository;
|
|
97
|
+
const repositoryFile = path.join((0, resolve_1.ensureLayerDir)(ctx, repositoryLayer), `${spec.name}.repository.ts`);
|
|
98
|
+
const wroteRepository = (0, write_1.writeIfAbsent)(repositoryFile, () => renderRepository(ctx, spec, entity, repositoryFile));
|
|
99
|
+
const serviceFile = path.join((0, resolve_1.ensureLayerDir)(ctx, serviceLayer), `${spec.name}.service.ts`);
|
|
100
|
+
const wroteService = (0, write_1.writeIfAbsent)(serviceFile, () => (0, service_1.renderService)(ctx, spec, entity, serviceFile, side));
|
|
101
|
+
return wroteRepository || wroteService;
|
|
102
|
+
}
|
|
103
|
+
function writeController(ctx, spec, entity) {
|
|
104
|
+
if (ctx.profile.name === 'next-fullstack') {
|
|
105
|
+
const routeDir = path.join((0, paths_1.apiRouteDir)(ctx.stack, ctx.feature), spec.path.slice(1));
|
|
106
|
+
(0, fs_1.mkdirSafe)(routeDir);
|
|
107
|
+
const routeFile = path.join(routeDir, 'route.ts');
|
|
108
|
+
return (0, write_1.writeIfAbsent)(routeFile, () => (0, next_action_route_1.renderActionRoute)(ctx, spec, routeFile));
|
|
109
|
+
}
|
|
110
|
+
const controllerFile = path.join((0, resolve_1.ensureLayerDir)(ctx, 'controller'), `${spec.name}.controller.ts`);
|
|
111
|
+
const render = ctx.profile.name === 'nest' ? nest_1.renderNestController : node_1.renderNodeController;
|
|
112
|
+
const wroteController = (0, write_1.writeIfAbsent)(controllerFile, () => render(ctx, spec, entity, controllerFile));
|
|
113
|
+
const line = ctx.profile.name === 'node' ? (0, node_1.renderNodeRouteLine)(ctx, spec, entity) : null;
|
|
114
|
+
if (wroteController && line !== null) {
|
|
115
|
+
console.log(`âšī¸ Add to ${ctx.feature}.routes.ts above the '/:id' routes: ${line}`);
|
|
116
|
+
}
|
|
117
|
+
return wroteController;
|
|
118
|
+
}
|
|
@@ -33,30 +33,29 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.parseComponentType = parseComponentType;
|
|
36
37
|
exports.makeComponent = makeComponent;
|
|
37
38
|
const path = __importStar(require("path"));
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
export default function ${name}({ id }: ${name}Props) {
|
|
46
|
-
return (
|
|
47
|
-
<div>
|
|
48
|
-
<h1>${name}</h1>
|
|
49
|
-
</div>
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
`;
|
|
39
|
+
const registry_1 = require("../stack/registry");
|
|
40
|
+
const component_1 = require("../templates/frontend/component");
|
|
41
|
+
const fs_1 = require("../utils/fs");
|
|
42
|
+
const resolve_1 = require("./resolve");
|
|
43
|
+
function parseComponentType(value) {
|
|
44
|
+
return value === 'server' ? 'server' : 'client';
|
|
53
45
|
}
|
|
54
46
|
function makeComponent(feature, name, type = 'client') {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
if (
|
|
47
|
+
const ctx = (0, resolve_1.requireFeature)(feature);
|
|
48
|
+
(0, registry_1.assertLayer)(ctx.profile, 'component', 'make:component');
|
|
49
|
+
if (type === 'server' && !ctx.profile.serverComponents) {
|
|
50
|
+
throw new Error(`The ${ctx.profile.name} stack has no server components.`);
|
|
51
|
+
}
|
|
52
|
+
const dir = path.join(ctx.featureDir, (0, registry_1.componentDir)(ctx.profile, type));
|
|
53
|
+
(0, fs_1.mkdirSafe)(dir);
|
|
54
|
+
const filePath = path.join(dir, `${name}.tsx`);
|
|
55
|
+
if ((0, fs_1.fileExists)(filePath)) {
|
|
58
56
|
throw new Error(`Component "${name}" already exists at ${filePath}`);
|
|
59
57
|
}
|
|
60
|
-
|
|
58
|
+
const directive = type === 'client' && ctx.profile.clientDirective;
|
|
59
|
+
(0, fs_1.writeFileSafe)(filePath, (0, component_1.renderComponent)(name, directive));
|
|
61
60
|
console.log(`â
Component "${name}" created at ${filePath}`);
|
|
62
61
|
}
|
|
@@ -35,39 +35,18 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.makeContainer = makeContainer;
|
|
37
37
|
const path = __importStar(require("path"));
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const componentPath = (0, utils_1.resolveImport)(feature, `components/client/${name}`);
|
|
43
|
-
return `'use client';
|
|
44
|
-
|
|
45
|
-
import { ${hookName} } from '${hookPath}';
|
|
46
|
-
import ${name} from '${componentPath}';
|
|
47
|
-
|
|
48
|
-
export default function ${containerName}() {
|
|
49
|
-
const { items, loading, error } = ${hookName}();
|
|
50
|
-
|
|
51
|
-
if (loading) return <div>Loading...</div>;
|
|
52
|
-
if (error) return <div>Error: {error}</div>;
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<div>
|
|
56
|
-
{items.map((item) => (
|
|
57
|
-
<${name} key={item.id} {...item} />
|
|
58
|
-
))}
|
|
59
|
-
</div>
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
`;
|
|
63
|
-
}
|
|
38
|
+
const registry_1 = require("../stack/registry");
|
|
39
|
+
const container_1 = require("../templates/frontend/container");
|
|
40
|
+
const fs_1 = require("../utils/fs");
|
|
41
|
+
const resolve_1 = require("./resolve");
|
|
64
42
|
function makeContainer(feature, name, pascalName) {
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
43
|
+
const ctx = (0, resolve_1.requireFeature)(feature);
|
|
44
|
+
(0, registry_1.assertLayer)(ctx.profile, 'container', 'make:container');
|
|
45
|
+
const filePath = path.join((0, resolve_1.ensureLayerDir)(ctx, 'container'), `${name}.tsx`);
|
|
46
|
+
if ((0, fs_1.fileExists)(filePath)) {
|
|
68
47
|
throw new Error(`Container "${name}" already exists at ${filePath}`);
|
|
69
48
|
}
|
|
70
|
-
const
|
|
71
|
-
(0,
|
|
49
|
+
const entity = pascalName ?? name.replace(/Container$/, '');
|
|
50
|
+
(0, fs_1.writeFileSafe)(filePath, (0, container_1.renderContainer)(ctx, name, entity, filePath));
|
|
72
51
|
console.log(`â
Container "${name}" created at ${filePath}`);
|
|
73
52
|
}
|