@dawudesign/node-hexa-cli 0.2.4 → 0.2.6
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 +43 -115
- package/dist/index.js +6 -119
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,17 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
> Scaffold and enforce **NestJS Hexagonal DDD** architecture from the command line.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- **
|
|
8
|
-
- **Enforce** — statically analyzes your TypeScript source and reports architecture violations
|
|
9
|
-
- **Document** — exports a Mermaid diagram and architecture report as Markdown or SVG
|
|
5
|
+
- **Scaffold** — generate a full NestJS project or bounded context in one command
|
|
6
|
+
- **Enforce** — statically analyze your TypeScript source and report architecture violations
|
|
7
|
+
- **Document** — export a Mermaid diagram and architecture report
|
|
10
8
|
|
|
11
9
|
---
|
|
12
10
|
|
|
13
11
|
## Requirements
|
|
14
12
|
|
|
15
|
-
-
|
|
13
|
+
- Node.js ≥ 20
|
|
14
|
+
- npm or pnpm
|
|
16
15
|
|
|
17
16
|
---
|
|
18
17
|
|
|
@@ -22,37 +21,13 @@
|
|
|
22
21
|
npm install -g @dawudesign/node-hexa-cli
|
|
23
22
|
```
|
|
24
23
|
|
|
25
|
-
Verify:
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
node-hexa --version
|
|
29
|
-
node-hexa --help
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## Quickstart
|
|
35
|
-
|
|
36
|
-
```bash
|
|
37
|
-
# 1. Create a new NestJS Hexagonal DDD project
|
|
38
|
-
node-hexa init my-app
|
|
39
|
-
cd my-app
|
|
40
|
-
|
|
41
|
-
# 2. Start the server
|
|
42
|
-
pnpm start:dev
|
|
43
|
-
|
|
44
|
-
# 3. Verify architecture is clean
|
|
45
|
-
node-hexa check .
|
|
46
|
-
# ✓ Architecture check passed
|
|
47
|
-
```
|
|
48
|
-
|
|
49
24
|
---
|
|
50
25
|
|
|
51
26
|
## Commands
|
|
52
27
|
|
|
53
28
|
### `init`
|
|
54
29
|
|
|
55
|
-
|
|
30
|
+
Create a new NestJS project with the full Hexagonal DDD structure.
|
|
56
31
|
|
|
57
32
|
```bash
|
|
58
33
|
node-hexa init <name>
|
|
@@ -60,112 +35,80 @@ node-hexa init <name>
|
|
|
60
35
|
|
|
61
36
|
```bash
|
|
62
37
|
node-hexa init my-app
|
|
38
|
+
cd my-app
|
|
39
|
+
pnpm start:dev
|
|
63
40
|
```
|
|
64
41
|
|
|
65
|
-
|
|
42
|
+
Generated structure:
|
|
66
43
|
|
|
67
|
-
```
|
|
44
|
+
```
|
|
68
45
|
my-app/
|
|
69
46
|
├── src/
|
|
70
47
|
│ ├── main.ts
|
|
71
48
|
│ ├── app.module.ts
|
|
72
|
-
│
|
|
73
|
-
│
|
|
74
|
-
│ ├── iam.module.ts
|
|
75
|
-
│ ├── domain/
|
|
76
|
-
│ │ ├── entities/user.entity.ts
|
|
77
|
-
│ │ └── ports/user.repository.port.ts
|
|
78
|
-
│ ├── application/
|
|
79
|
-
│ │ └── use-cases/create-user.usecase.ts
|
|
80
|
-
│ └── infrastructure/
|
|
81
|
-
│ ├── http/user.controller.ts
|
|
82
|
-
│ └── persistence/in-memory-user.repository.ts
|
|
49
|
+
│ ├── contexts/ ← add your bounded contexts here
|
|
50
|
+
│ └── shared/
|
|
83
51
|
└── node-hexa.config.json
|
|
84
52
|
```
|
|
85
53
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
### `generate context`
|
|
89
|
-
|
|
90
|
-
Generates a complete bounded context inside an existing NestJS project.
|
|
91
|
-
|
|
92
|
-
```bash
|
|
93
|
-
node-hexa generate context <name>
|
|
94
|
-
```
|
|
54
|
+
Then add your first bounded context:
|
|
95
55
|
|
|
96
56
|
```bash
|
|
97
57
|
cd my-app
|
|
98
58
|
node-hexa generate context orders
|
|
99
|
-
# ✓ Context 'orders' generated at src/contexts/orders/
|
|
100
|
-
# → Import OrdersModule in your AppModule to activate it.
|
|
101
59
|
```
|
|
102
60
|
|
|
103
61
|
---
|
|
104
62
|
|
|
105
|
-
### `generate
|
|
63
|
+
### `generate`
|
|
106
64
|
|
|
107
|
-
|
|
65
|
+
Generate scaffolding inside an existing project.
|
|
108
66
|
|
|
109
67
|
```bash
|
|
68
|
+
node-hexa generate context <name>
|
|
110
69
|
node-hexa generate usecase <name> <context>
|
|
70
|
+
node-hexa generate aggregate <name> <context>
|
|
111
71
|
```
|
|
112
72
|
|
|
113
73
|
```bash
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Generated files:
|
|
119
|
-
|
|
120
|
-
```text
|
|
121
|
-
src/contexts/iam/application/use-cases/
|
|
122
|
-
├── delete-user.usecase.ts
|
|
123
|
-
├── delete-user.dto.ts
|
|
124
|
-
└── delete-user.usecase.spec.ts
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
---
|
|
128
|
-
|
|
129
|
-
### `generate aggregate`
|
|
130
|
-
|
|
131
|
-
Generates a full DDD aggregate (entity, value object, port, use case, in-memory repository, HTTP controller, NestJS module).
|
|
74
|
+
# New bounded context
|
|
75
|
+
node-hexa generate context orders
|
|
132
76
|
|
|
133
|
-
|
|
134
|
-
node-hexa generate
|
|
135
|
-
```
|
|
77
|
+
# Use case inside a context
|
|
78
|
+
node-hexa generate usecase delete-user iam
|
|
136
79
|
|
|
137
|
-
|
|
80
|
+
# Full DDD aggregate (entity, port, use case, controller, repository)
|
|
138
81
|
node-hexa generate aggregate product catalog
|
|
139
|
-
# ✓ Aggregate 'product' generated in context 'catalog'
|
|
140
82
|
```
|
|
141
83
|
|
|
142
84
|
---
|
|
143
85
|
|
|
144
86
|
### `check`
|
|
145
87
|
|
|
146
|
-
|
|
88
|
+
Check architecture violations. Exits `0` if clean, `1` if violations found. Designed for CI.
|
|
147
89
|
|
|
148
90
|
```bash
|
|
149
|
-
node-hexa check <path>
|
|
91
|
+
node-hexa check <path>
|
|
92
|
+
node-hexa check <path> --watch
|
|
150
93
|
```
|
|
151
94
|
|
|
152
95
|
```bash
|
|
153
96
|
# One-shot (CI)
|
|
154
97
|
node-hexa check .
|
|
155
98
|
|
|
156
|
-
# Watch mode
|
|
99
|
+
# Watch mode
|
|
157
100
|
node-hexa check . --watch
|
|
158
101
|
```
|
|
159
102
|
|
|
160
103
|
Output:
|
|
161
104
|
|
|
162
|
-
```
|
|
105
|
+
```
|
|
163
106
|
✓ Architecture check passed
|
|
164
107
|
```
|
|
165
108
|
|
|
166
109
|
or:
|
|
167
110
|
|
|
168
|
-
```
|
|
111
|
+
```
|
|
169
112
|
✗ Architecture violations detected
|
|
170
113
|
|
|
171
114
|
[CRITICAL] Domain must not depend on infrastructure → UserEntity
|
|
@@ -182,27 +125,19 @@ Full analysis: layers, violations, bounded contexts, Mermaid diagram, and score.
|
|
|
182
125
|
node-hexa analyze <path>
|
|
183
126
|
```
|
|
184
127
|
|
|
185
|
-
```bash
|
|
186
|
-
node-hexa analyze .
|
|
187
|
-
```
|
|
188
|
-
|
|
189
128
|
---
|
|
190
129
|
|
|
191
130
|
### `list`
|
|
192
131
|
|
|
193
|
-
|
|
132
|
+
List all bounded contexts and their components.
|
|
194
133
|
|
|
195
134
|
```bash
|
|
196
135
|
node-hexa list <path>
|
|
197
136
|
```
|
|
198
137
|
|
|
199
|
-
```bash
|
|
200
|
-
node-hexa list .
|
|
201
|
-
```
|
|
202
|
-
|
|
203
138
|
Output:
|
|
204
139
|
|
|
205
|
-
```
|
|
140
|
+
```
|
|
206
141
|
Bounded Contexts (2)
|
|
207
142
|
|
|
208
143
|
IAM
|
|
@@ -220,27 +155,21 @@ Bounded Contexts (2)
|
|
|
220
155
|
|
|
221
156
|
### `docs`
|
|
222
157
|
|
|
223
|
-
|
|
158
|
+
Generate an `architecture.md` at the project root with the Mermaid diagram and violations.
|
|
224
159
|
|
|
225
160
|
```bash
|
|
226
161
|
node-hexa docs <path>
|
|
227
162
|
```
|
|
228
163
|
|
|
229
|
-
```bash
|
|
230
|
-
node-hexa docs .
|
|
231
|
-
# Architecture documentation generated: ./architecture.md
|
|
232
|
-
```
|
|
233
|
-
|
|
234
164
|
---
|
|
235
165
|
|
|
236
166
|
### `graph`
|
|
237
167
|
|
|
238
|
-
|
|
168
|
+
Generate an `architecture.svg` dependency graph (requires `@mermaid-js/mermaid-cli`).
|
|
239
169
|
|
|
240
170
|
```bash
|
|
241
171
|
npm install -g @mermaid-js/mermaid-cli
|
|
242
|
-
node-hexa graph
|
|
243
|
-
# Architecture graph generated: ./architecture.svg
|
|
172
|
+
node-hexa graph <path>
|
|
244
173
|
```
|
|
245
174
|
|
|
246
175
|
---
|
|
@@ -258,8 +187,8 @@ node-hexa graph .
|
|
|
258
187
|
```
|
|
259
188
|
|
|
260
189
|
| Key | Type | Default | Description |
|
|
261
|
-
|
|
262
|
-
| `architecture` | `string` |
|
|
190
|
+
|-----|------|---------|-------------|
|
|
191
|
+
| `architecture` | `string` | `"hexagonal-ddd"` | Architecture type |
|
|
263
192
|
| `strict` | `boolean` | `true` | `false` silences `MEDIUM` violations |
|
|
264
193
|
| `contextsDir` | `string` | `"src/contexts"` | Path to bounded contexts directory |
|
|
265
194
|
|
|
@@ -267,17 +196,16 @@ node-hexa graph .
|
|
|
267
196
|
|
|
268
197
|
## Violation rules
|
|
269
198
|
|
|
270
|
-
| Violation | Severity |
|
|
271
|
-
|
|
272
|
-
| Domain imports from infrastructure
|
|
273
|
-
| Domain imports from application | `CRITICAL` |
|
|
274
|
-
|
|
|
275
|
-
|
|
|
276
|
-
|
|
277
|
-
Score = `100 − sum of penalties`, minimum 0.
|
|
199
|
+
| Violation | Severity |
|
|
200
|
+
|-----------|----------|
|
|
201
|
+
| Domain imports from infrastructure or adapter | `CRITICAL` |
|
|
202
|
+
| Domain imports from application | `CRITICAL` |
|
|
203
|
+
| Domain imports a framework (`@nestjs/*`, `prisma`…) | `CRITICAL` |
|
|
204
|
+
| Application imports from infrastructure or adapter | `HIGH` |
|
|
278
205
|
|
|
279
206
|
---
|
|
280
207
|
|
|
281
208
|
## License
|
|
282
209
|
|
|
283
|
-
MIT
|
|
210
|
+
MIT
|
|
211
|
+
|
package/dist/index.js
CHANGED
|
@@ -403,10 +403,7 @@ function generateProject(name) {
|
|
|
403
403
|
const src = import_node_path5.default.join(base, "src");
|
|
404
404
|
import_node_fs4.default.rmSync(src, { recursive: true, force: true });
|
|
405
405
|
const dirs = [
|
|
406
|
-
"src/contexts
|
|
407
|
-
"src/contexts/iam/application/use-cases",
|
|
408
|
-
"src/contexts/iam/infrastructure/http",
|
|
409
|
-
"src/contexts/iam/infrastructure/persistence",
|
|
406
|
+
"src/contexts",
|
|
410
407
|
"src/shared"
|
|
411
408
|
];
|
|
412
409
|
dirs.forEach((dir) => {
|
|
@@ -427,127 +424,17 @@ bootstrap();
|
|
|
427
424
|
import_node_fs4.default.writeFileSync(
|
|
428
425
|
import_node_path5.default.join(base, "src/app.module.ts"),
|
|
429
426
|
`import { Module } from '@nestjs/common';
|
|
430
|
-
import { IamModule } from './contexts/iam/iam.module';
|
|
431
427
|
|
|
432
428
|
@Module({
|
|
433
|
-
imports: [
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
`
|
|
437
|
-
);
|
|
438
|
-
import_node_fs4.default.writeFileSync(
|
|
439
|
-
import_node_path5.default.join(base, "src/contexts/iam/domain/entities/user.entity.ts"),
|
|
440
|
-
`export class User {
|
|
441
|
-
constructor(
|
|
442
|
-
public readonly id: string,
|
|
443
|
-
public readonly email: string,
|
|
444
|
-
public readonly name: string,
|
|
445
|
-
) {}
|
|
446
|
-
}
|
|
447
|
-
`
|
|
448
|
-
);
|
|
449
|
-
import_node_fs4.default.writeFileSync(
|
|
450
|
-
import_node_path5.default.join(base, "src/contexts/iam/domain/ports/user.repository.port.ts"),
|
|
451
|
-
`import { User } from '../entities/user.entity';
|
|
452
|
-
|
|
453
|
-
export const USER_REPOSITORY_PORT = Symbol('UserRepositoryPort');
|
|
454
|
-
|
|
455
|
-
export interface UserRepositoryPort {
|
|
456
|
-
save(user: User): Promise<void>;
|
|
457
|
-
findById(id: string): Promise<User | null>;
|
|
458
|
-
}
|
|
459
|
-
`
|
|
460
|
-
);
|
|
461
|
-
import_node_fs4.default.writeFileSync(
|
|
462
|
-
import_node_path5.default.join(base, "src/contexts/iam/application/use-cases/create-user.usecase.ts"),
|
|
463
|
-
`import { Inject, Injectable } from '@nestjs/common';
|
|
464
|
-
import { User } from '../../domain/entities/user.entity';
|
|
465
|
-
import {
|
|
466
|
-
USER_REPOSITORY_PORT,
|
|
467
|
-
UserRepositoryPort,
|
|
468
|
-
} from '../../domain/ports/user.repository.port';
|
|
469
|
-
import { randomUUID } from 'node:crypto';
|
|
470
|
-
|
|
471
|
-
export interface CreateUserDto {
|
|
472
|
-
email: string;
|
|
473
|
-
name: string;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
@Injectable()
|
|
477
|
-
export class CreateUserUseCase {
|
|
478
|
-
constructor(
|
|
479
|
-
@Inject(USER_REPOSITORY_PORT)
|
|
480
|
-
private readonly userRepository: UserRepositoryPort,
|
|
481
|
-
) {}
|
|
482
|
-
|
|
483
|
-
async execute(dto: CreateUserDto): Promise<User> {
|
|
484
|
-
const user = new User(randomUUID(), dto.email, dto.name);
|
|
485
|
-
await this.userRepository.save(user);
|
|
486
|
-
return user;
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
`
|
|
490
|
-
);
|
|
491
|
-
import_node_fs4.default.writeFileSync(
|
|
492
|
-
import_node_path5.default.join(
|
|
493
|
-
base,
|
|
494
|
-
"src/contexts/iam/infrastructure/persistence/in-memory-user.repository.ts"
|
|
495
|
-
),
|
|
496
|
-
`import { Injectable } from '@nestjs/common';
|
|
497
|
-
import { User } from '../../domain/entities/user.entity';
|
|
498
|
-
import { UserRepositoryPort } from '../../domain/ports/user.repository.port';
|
|
499
|
-
|
|
500
|
-
@Injectable()
|
|
501
|
-
export class InMemoryUserRepository implements UserRepositoryPort {
|
|
502
|
-
private readonly store = new Map<string, User>();
|
|
503
|
-
|
|
504
|
-
async save(user: User): Promise<void> {
|
|
505
|
-
this.store.set(user.id, user);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
async findById(id: string): Promise<User | null> {
|
|
509
|
-
return this.store.get(id) ?? null;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
`
|
|
513
|
-
);
|
|
514
|
-
import_node_fs4.default.writeFileSync(
|
|
515
|
-
import_node_path5.default.join(base, "src/contexts/iam/infrastructure/http/user.controller.ts"),
|
|
516
|
-
`import { Body, Controller, Post } from '@nestjs/common';
|
|
517
|
-
import { CreateUserUseCase, CreateUserDto } from '../../application/use-cases/create-user.usecase';
|
|
518
|
-
|
|
519
|
-
@Controller('users')
|
|
520
|
-
export class UserController {
|
|
521
|
-
constructor(private readonly createUser: CreateUserUseCase) {}
|
|
522
|
-
|
|
523
|
-
@Post()
|
|
524
|
-
async create(@Body() dto: CreateUserDto) {
|
|
525
|
-
return this.createUser.execute(dto);
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
`
|
|
529
|
-
);
|
|
530
|
-
import_node_fs4.default.writeFileSync(
|
|
531
|
-
import_node_path5.default.join(base, "src/contexts/iam/iam.module.ts"),
|
|
532
|
-
`import { Module } from '@nestjs/common';
|
|
533
|
-
import { USER_REPOSITORY_PORT } from './domain/ports/user.repository.port';
|
|
534
|
-
import { InMemoryUserRepository } from './infrastructure/persistence/in-memory-user.repository';
|
|
535
|
-
import { CreateUserUseCase } from './application/use-cases/create-user.usecase';
|
|
536
|
-
import { UserController } from './infrastructure/http/user.controller';
|
|
537
|
-
|
|
538
|
-
@Module({
|
|
539
|
-
controllers: [UserController],
|
|
540
|
-
providers: [
|
|
541
|
-
{
|
|
542
|
-
provide: USER_REPOSITORY_PORT,
|
|
543
|
-
useClass: InMemoryUserRepository,
|
|
544
|
-
},
|
|
545
|
-
CreateUserUseCase,
|
|
429
|
+
imports: [
|
|
430
|
+
// Import your bounded context modules here
|
|
431
|
+
// e.g. import { OrdersModule } from './contexts/orders/orders.module';
|
|
546
432
|
],
|
|
547
433
|
})
|
|
548
|
-
export class
|
|
434
|
+
export class AppModule {}
|
|
549
435
|
`
|
|
550
436
|
);
|
|
437
|
+
import_node_fs4.default.writeFileSync(import_node_path5.default.join(base, "src/contexts/.gitkeep"), "");
|
|
551
438
|
import_node_fs4.default.writeFileSync(import_node_path5.default.join(base, "src/shared/.gitkeep"), "");
|
|
552
439
|
import_node_fs4.default.writeFileSync(
|
|
553
440
|
import_node_path5.default.join(base, "node-hexa.config.json"),
|