domain-driver 0.1.0 → 0.3.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 +159 -145
- package/dist/cli.js +208 -0
- 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/update.js +148 -0
- package/dist/commands/write.js +66 -0
- package/dist/index.js +8 -122
- package/dist/init/content.js +73 -0
- package/dist/init/init.js +66 -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/update/cache.js +80 -0
- package/dist/update/check.js +47 -0
- package/dist/update/install-mode.js +96 -0
- package/dist/update/package-manager.js +99 -0
- package/dist/update/registry.js +77 -0
- package/dist/update/version.js +70 -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,263 @@ 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>` |
|
|
31
36
|
|
|
32
|
-
|
|
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.
|
|
38
|
+
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
Scaffold a component inside an existing feature. Defaults to `client` if no type is specified.
|
|
47
|
+
## What each stack generates
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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 |
|
|
50
60
|
|
|
51
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
### `make:hook`
|
|
76
|
+
Feature names must be kebab-case. Without `/Entity`, the entity is the PascalCase feature name (`users` becomes `Users`).
|
|
74
77
|
|
|
75
|
-
|
|
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
|
|
|
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.
|
|
108
|
+
|
|
109
|
+
### `make:service` and `make:repository`
|
|
110
|
+
|
|
95
111
|
```bash
|
|
96
|
-
domain-driver make:service
|
|
112
|
+
domain-driver make:service users/User [--side client|server|both]
|
|
113
|
+
domain-driver make:repository users/User [--side client|server|both]
|
|
97
114
|
```
|
|
98
115
|
|
|
99
|
-
|
|
116
|
+
`--side` matters on `next-fullstack`, where both sides exist. Default is `both`.
|
|
100
117
|
|
|
101
|
-
|
|
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
|
-
```
|
|
118
|
+
### `make:schema`
|
|
109
119
|
|
|
110
|
-
|
|
120
|
+
```bash
|
|
121
|
+
domain-driver make:schema users/User
|
|
122
|
+
```
|
|
111
123
|
|
|
112
|
-
|
|
124
|
+
Writes `CreateUser.schema.ts` and `UpdateUser.schema.ts`. On Nest it also writes the matching DTO classes derived with `createZodDto` from `nestjs-zod`.
|
|
113
125
|
|
|
114
|
-
|
|
126
|
+
### `make:types`, `make:component`, `make:container`, `make:hook`
|
|
115
127
|
|
|
116
128
|
```bash
|
|
117
|
-
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
|
|
118
133
|
```
|
|
119
134
|
|
|
135
|
+
Component, container, and hook commands fail with a clear message on backend stacks, and `server` components are rejected on React.
|
|
136
|
+
|
|
137
|
+
### `init`
|
|
138
|
+
|
|
120
139
|
```bash
|
|
121
|
-
domain-driver
|
|
140
|
+
domain-driver init
|
|
122
141
|
```
|
|
123
142
|
|
|
124
|
-
|
|
143
|
+
Writes agent guidance into the current project so coding agents scaffold with domain-driver instead of hand-writing layers:
|
|
125
144
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
├── ListCoffeeType.repository.ts
|
|
129
|
-
├── ShowCoffeeType.repository.ts
|
|
130
|
-
├── CreateCoffeeType.repository.ts
|
|
131
|
-
├── UpdateCoffeeType.repository.ts
|
|
132
|
-
└── DeleteCoffeeType.repository.ts
|
|
133
|
-
```
|
|
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.
|
|
134
147
|
|
|
135
|
-
|
|
148
|
+
Running `init` twice reports `unchanged`. Re-run it after upgrading domain-driver.
|
|
136
149
|
|
|
137
|
-
|
|
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.
|
|
151
|
+
|
|
152
|
+
---
|
|
138
153
|
|
|
139
|
-
|
|
154
|
+
## Example: `make:feature coffee-type -a` on Node with Express
|
|
140
155
|
|
|
141
|
-
```bash
|
|
142
|
-
domain-driver make:schema <feature> <n>
|
|
143
156
|
```
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
147
172
|
```
|
|
148
173
|
|
|
149
|
-
|
|
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.
|
|
175
|
+
|
|
176
|
+
## Example: `make:feature coffee-type -a` on NestJS
|
|
150
177
|
|
|
151
178
|
```
|
|
152
|
-
|
|
153
|
-
├──
|
|
154
|
-
|
|
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/
|
|
155
189
|
```
|
|
156
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
|
+
|
|
157
193
|
---
|
|
158
194
|
|
|
159
|
-
##
|
|
195
|
+
## Philosophy
|
|
160
196
|
|
|
161
|
-
|
|
197
|
+
Everything for a feature lives in one folder, and every file does one thing.
|
|
162
198
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
│ └── useCoffeeType.ts
|
|
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
|
|
190
|
-
```
|
|
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.
|
|
207
|
+
|
|
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.
|
|
191
209
|
|
|
192
210
|
---
|
|
193
211
|
|
|
194
|
-
##
|
|
212
|
+
## Updating
|
|
195
213
|
|
|
196
|
-
|
|
214
|
+
Every command checks the registry at most once a day and, when a newer release exists, prints one line after its output:
|
|
197
215
|
|
|
198
|
-
|
|
216
|
+
```
|
|
217
|
+
ℹ️ domain-driver 0.3.0 is available (you have 0.2.0). Run: domain-driver update
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
domain-driver update # detects how it was installed, runs your package manager, refreshes the guidance
|
|
222
|
+
domain-driver update --dry-run # show the command it would run
|
|
223
|
+
domain-driver update --check # only report whether a newer version exists
|
|
224
|
+
```
|
|
199
225
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
- **schemas** — Zod validation for create and update operations
|
|
226
|
+
Local installs use the package manager the project uses (npm, pnpm, yarn, or bun, from the `packageManager` field or the lockfile). Global installs use `npm install -g`. Running through `npx` needs no update: `npx domain-driver@latest` always fetches the newest.
|
|
227
|
+
|
|
228
|
+
The check is skipped in CI (any `CI` value other than empty, `0`, or `false`), when output is not a terminal, when `DOMAIN_DRIVER_NO_UPDATE_CHECK` is set (same value rule), or when `NO_UPDATE_NOTIFIER` is set to anything.
|
|
229
|
+
|
|
230
|
+
The cache lives in `~/.cache/domain-driver` (or `$XDG_CACHE_HOME/domain-driver`); `DOMAIN_DRIVER_CACHE_DIR` overrides it.
|
|
206
231
|
|
|
207
232
|
---
|
|
208
233
|
|
|
209
|
-
##
|
|
234
|
+
## Upgrading from 0.1.0
|
|
210
235
|
|
|
211
|
-
-
|
|
236
|
+
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
237
|
|
|
213
238
|
---
|
|
214
239
|
|
|
215
|
-
##
|
|
240
|
+
## Requirements
|
|
216
241
|
|
|
217
|
-
|
|
242
|
+
- Node.js 18+
|
|
218
243
|
|
|
219
|
-
|
|
244
|
+
---
|
|
220
245
|
|
|
221
246
|
## Local Development
|
|
222
247
|
|
|
223
248
|
```bash
|
|
224
|
-
# Clone the repo
|
|
225
249
|
git clone https://github.com/IsaacHatilima/domain-driver
|
|
226
250
|
cd domain-driver
|
|
227
|
-
|
|
228
|
-
# Install dependencies
|
|
229
251
|
npm install
|
|
230
|
-
|
|
231
|
-
# Build
|
|
232
252
|
npm run build
|
|
233
|
-
|
|
234
|
-
# Link globally for local testing
|
|
235
253
|
npm link
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
domain-driver make:feature test-feature
|
|
239
|
-
domain-driver make:feature test-feature -a
|
|
254
|
+
npm test
|
|
255
|
+
npm run test:coverage
|
|
240
256
|
```
|
|
241
257
|
|
|
242
258
|
---
|
|
243
259
|
|
|
244
260
|
## Roadmap
|
|
245
261
|
|
|
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
|
|
262
|
+
- [x] Stack detection for Next.js, React, Node, and NestJS
|
|
263
|
+
- [x] `make:controller` with Express, Fastify, Hono, Nest, and Next route handlers
|
|
264
|
+
- [x] Per-action files in every layer
|
|
265
|
+
- [x] Bespoke actions with make:action
|
|
266
|
+
- [x] Agent guidance with init and a guarded postinstall
|
|
267
|
+
- [ ] Config file — override stack and feature root per project
|
|
268
|
+
- [ ] Configurable API base URL for client repositories
|
|
269
|
+
- [ ] ORM-aware server repositories
|
|
256
270
|
|
|
257
271
|
---
|
|
258
272
|
|
|
259
273
|
## License
|
|
260
274
|
|
|
261
|
-
MIT
|
|
275
|
+
MIT
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
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.defaultCliDeps = defaultCliDeps;
|
|
37
|
+
exports.createProgram = createProgram;
|
|
38
|
+
// src/cli.ts
|
|
39
|
+
const commander_1 = require("commander");
|
|
40
|
+
const os = __importStar(require("os"));
|
|
41
|
+
const action_1 = require("./commands/action");
|
|
42
|
+
const component_1 = require("./commands/component");
|
|
43
|
+
const container_1 = require("./commands/container");
|
|
44
|
+
const controller_1 = require("./commands/controller");
|
|
45
|
+
const feature_1 = require("./commands/feature");
|
|
46
|
+
const hints_1 = require("./commands/hints");
|
|
47
|
+
const hook_1 = require("./commands/hook");
|
|
48
|
+
const repository_1 = require("./commands/repository");
|
|
49
|
+
const schema_1 = require("./commands/schema");
|
|
50
|
+
const service_1 = require("./commands/service");
|
|
51
|
+
const sides_1 = require("./commands/sides");
|
|
52
|
+
const target_1 = require("./commands/target");
|
|
53
|
+
const types_1 = require("./commands/types");
|
|
54
|
+
const update_1 = require("./commands/update");
|
|
55
|
+
const init_1 = require("./init/init");
|
|
56
|
+
const detect_1 = require("./stack/detect");
|
|
57
|
+
const types_2 = require("./stack/types");
|
|
58
|
+
const actions_1 = require("./templates/actions");
|
|
59
|
+
const check_1 = require("./update/check");
|
|
60
|
+
const registry_1 = require("./update/registry");
|
|
61
|
+
const version_1 = require("./update/version");
|
|
62
|
+
const SKIP_DETECTION = new Set(['init', 'update']);
|
|
63
|
+
function defaultCliDeps() {
|
|
64
|
+
return {
|
|
65
|
+
env: process.env,
|
|
66
|
+
isTTY: process.stdout.isTTY === true,
|
|
67
|
+
homedir: os.homedir(),
|
|
68
|
+
now: Date.now,
|
|
69
|
+
fetchImpl: registry_1.nodeFetch,
|
|
70
|
+
current: (0, version_1.currentVersion)(),
|
|
71
|
+
updateDeps: update_1.defaultUpdateDeps,
|
|
72
|
+
log: (line) => console.log(line),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function createProgram(deps) {
|
|
76
|
+
const program = new commander_1.Command();
|
|
77
|
+
program
|
|
78
|
+
.name('domain-driver')
|
|
79
|
+
.description('CLI scaffolding tool for domain-driven feature folders in Next.js, React, Node, and NestJS projects')
|
|
80
|
+
.version(deps.current)
|
|
81
|
+
.option('--stack <name>', `Override stack detection (${types_2.STACK_NAMES.join(', ')})`);
|
|
82
|
+
let pendingNotice = Promise.resolve(null);
|
|
83
|
+
program.hook('preAction', (_thisCommand, actionCommand) => {
|
|
84
|
+
const name = actionCommand.name();
|
|
85
|
+
if ((0, check_1.shouldCheck)(deps.env, deps.isTTY, name)) {
|
|
86
|
+
pendingNotice = (0, check_1.checkForUpdate)({
|
|
87
|
+
env: deps.env,
|
|
88
|
+
homedir: deps.homedir,
|
|
89
|
+
now: deps.now,
|
|
90
|
+
fetchImpl: deps.fetchImpl,
|
|
91
|
+
current: deps.current,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
if (SKIP_DETECTION.has(name))
|
|
95
|
+
return;
|
|
96
|
+
const { stack } = program.opts();
|
|
97
|
+
deps.log((0, detect_1.describeStack)((0, detect_1.detectStack)(stack)));
|
|
98
|
+
});
|
|
99
|
+
program.hook('postAction', async () => {
|
|
100
|
+
const notice = await pendingNotice;
|
|
101
|
+
if (notice !== null)
|
|
102
|
+
deps.log(notice);
|
|
103
|
+
});
|
|
104
|
+
program
|
|
105
|
+
.command('make:feature <target>')
|
|
106
|
+
.description('Scaffold a feature folder for the detected stack (<feature> or <feature>/<Entity>)')
|
|
107
|
+
.option('-a, --all', 'Scaffold all files inside each folder')
|
|
108
|
+
.action(async (target, options) => {
|
|
109
|
+
const { feature, entity } = (0, target_1.parseFeatureTarget)(target);
|
|
110
|
+
await (0, feature_1.makeFeature)(feature, options.all ?? false, entity ?? undefined);
|
|
111
|
+
});
|
|
112
|
+
program
|
|
113
|
+
.command('make:component <target>')
|
|
114
|
+
.description('Scaffold a component inside an existing feature (<feature>/<Name>)')
|
|
115
|
+
.argument('[type]', 'Component type: client or server', 'client')
|
|
116
|
+
.action((target, type) => {
|
|
117
|
+
const { feature, name } = (0, target_1.parseTarget)(target);
|
|
118
|
+
(0, component_1.makeComponent)(feature, name, (0, component_1.parseComponentType)(type));
|
|
119
|
+
});
|
|
120
|
+
program
|
|
121
|
+
.command('make:container <target>')
|
|
122
|
+
.description('Scaffold a smart container component inside an existing feature (<feature>/<Name>)')
|
|
123
|
+
.action((target) => {
|
|
124
|
+
const { feature, name } = (0, target_1.parseTarget)(target);
|
|
125
|
+
(0, container_1.makeContainer)(feature, name);
|
|
126
|
+
});
|
|
127
|
+
program
|
|
128
|
+
.command('make:hook <target>')
|
|
129
|
+
.description('Scaffold a custom hook inside an existing feature (<feature>/<useName>)')
|
|
130
|
+
.action((target) => {
|
|
131
|
+
const { feature, name } = (0, target_1.parseTarget)(target);
|
|
132
|
+
(0, hook_1.makeHook)(feature, name);
|
|
133
|
+
});
|
|
134
|
+
program
|
|
135
|
+
.command('make:service <target>')
|
|
136
|
+
.description('Scaffold single-responsibility service files inside an existing feature (<feature>/<Entity>)')
|
|
137
|
+
.option('--side <side>', 'client, server, or both', 'both')
|
|
138
|
+
.action((target, options) => {
|
|
139
|
+
const { feature, name } = (0, target_1.parseTarget)(target);
|
|
140
|
+
const wrote = (0, service_1.makeService)(feature, name, (0, sides_1.parseSide)(options.side));
|
|
141
|
+
if (wrote)
|
|
142
|
+
(0, hints_1.hintRegisterInModule)(feature, (0, hints_1.standardClassNames)(name, 'Service'));
|
|
143
|
+
});
|
|
144
|
+
program
|
|
145
|
+
.command('make:repository <target>')
|
|
146
|
+
.description('Scaffold single-responsibility repository files inside an existing feature (<feature>/<Entity>)')
|
|
147
|
+
.option('--side <side>', 'client, server, or both', 'both')
|
|
148
|
+
.action((target, options) => {
|
|
149
|
+
const { feature, name } = (0, target_1.parseTarget)(target);
|
|
150
|
+
const wrote = (0, repository_1.makeRepository)(feature, name, (0, sides_1.parseSide)(options.side));
|
|
151
|
+
if (wrote)
|
|
152
|
+
(0, hints_1.hintRegisterInModule)(feature, (0, hints_1.standardClassNames)(name, 'Repository'));
|
|
153
|
+
});
|
|
154
|
+
program
|
|
155
|
+
.command('make:controller <target>')
|
|
156
|
+
.description('Scaffold single-responsibility controllers or route handlers inside an existing feature (<feature>/<Entity>)')
|
|
157
|
+
.action((target) => {
|
|
158
|
+
const { feature, name } = (0, target_1.parseTarget)(target);
|
|
159
|
+
const wrote = (0, controller_1.makeController)(feature, name);
|
|
160
|
+
if (wrote)
|
|
161
|
+
(0, hints_1.hintRegisterInModule)(feature, (0, hints_1.standardClassNames)(name, 'Controller'));
|
|
162
|
+
});
|
|
163
|
+
program
|
|
164
|
+
.command('make:action <target> <action>')
|
|
165
|
+
.description('Scaffold a bespoke action as its own service, repository, and controller (<feature>/<Entity> <actionName>)')
|
|
166
|
+
.option('--with-input', 'The action takes a request body validated by a Zod schema', false)
|
|
167
|
+
.option('--returns <kind>', 'list, one, or void', 'list')
|
|
168
|
+
.action((target, action, options) => {
|
|
169
|
+
const { feature, name } = (0, target_1.parseTarget)(target);
|
|
170
|
+
const returns = (0, action_1.parseReturns)(options.returns);
|
|
171
|
+
const wrote = (0, action_1.makeAction)(feature, name, action, { withInput: options.withInput, returns });
|
|
172
|
+
if (wrote) {
|
|
173
|
+
const { pascal } = (0, actions_1.actionCase)(action);
|
|
174
|
+
(0, hints_1.hintRegisterInModule)(feature, [`${pascal}Controller`, `${pascal}Service`, `${pascal}Repository`], `list ${pascal}Controller before Show${name}Controller in controllers`);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
program
|
|
178
|
+
.command('make:schema <target>')
|
|
179
|
+
.description('Scaffold Zod schemas (and Nest DTOs) for create and update operations (<feature>/<Entity>)')
|
|
180
|
+
.action((target) => {
|
|
181
|
+
const { feature, name } = (0, target_1.parseTarget)(target);
|
|
182
|
+
(0, schema_1.makeSchema)(feature, name);
|
|
183
|
+
});
|
|
184
|
+
program
|
|
185
|
+
.command('make:types <target>')
|
|
186
|
+
.description('Scaffold a types file inside an existing feature (<feature>/<Entity>)')
|
|
187
|
+
.action((target) => {
|
|
188
|
+
const { feature, name } = (0, target_1.parseTarget)(target);
|
|
189
|
+
(0, types_1.makeTypes)(feature, name);
|
|
190
|
+
});
|
|
191
|
+
program
|
|
192
|
+
.command('init')
|
|
193
|
+
.description('Write agent guidance into this project: AGENTS.md, CLAUDE.md, and .claude/skills/domain-driver/SKILL.md')
|
|
194
|
+
.action(() => {
|
|
195
|
+
for (const result of (0, init_1.runInit)(process.cwd())) {
|
|
196
|
+
console.log(`${init_1.INIT_ICONS[result.status]} ${result.file} ${result.status}`);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
program
|
|
200
|
+
.command('update')
|
|
201
|
+
.description('Update domain-driver with your package manager, then refresh the agent guidance')
|
|
202
|
+
.option('--dry-run', 'Print the command without running it', false)
|
|
203
|
+
.option('--check', 'Only report whether a newer version exists', false)
|
|
204
|
+
.action(async (options) => {
|
|
205
|
+
await (0, update_1.runUpdate)(options, deps.updateDeps());
|
|
206
|
+
});
|
|
207
|
+
return program;
|
|
208
|
+
}
|