nestjs-doctor 0.1.0 → 0.1.2
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 +278 -0
- package/dist/api/index.cjs +1638 -236
- package/dist/api/index.d.cts +55 -14
- package/dist/api/index.d.cts.map +1 -1
- package/dist/api/{index.d.ts → index.d.mts} +56 -15
- package/dist/api/index.d.mts.map +1 -0
- package/dist/api/index.mjs +2567 -0
- package/dist/api/index.mjs.map +1 -0
- package/dist/cli/index.mjs +2884 -0
- package/package.json +15 -6
- package/dist/api/index.d.ts.map +0 -1
- package/dist/api/index.js +0 -1175
- package/dist/api/index.js.map +0 -1
- package/dist/cli/index.js +0 -1505
package/README.md
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<h1 align="center">nestjs-doctor</h1>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<b>Diagnose and fix your NestJS code in one command.</b>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://npmjs.com/package/nestjs-doctor"><img src="https://img.shields.io/npm/v/nestjs-doctor?style=flat&colorA=18181b&colorB=18181b" alt="version"></a>
|
|
11
|
+
<a href="https://npmjs.com/package/nestjs-doctor"><img src="https://img.shields.io/npm/dt/nestjs-doctor?style=flat&colorA=18181b&colorB=18181b" alt="downloads"></a>
|
|
12
|
+
<a href="https://github.com/RoloBits/nestjs-doctor/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/nestjs-doctor?style=flat&colorA=18181b&colorB=18181b" alt="license"></a>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
<p align="center">
|
|
16
|
+
47 built-in rules across <b>security</b>, <b>performance</b>, <b>correctness</b>, and <b>architecture</b>. Outputs a <b>0-100 score</b> with actionable diagnostics. Zero config. Monorepo support. Built to catch the anti-patterns that AI-generated code loves to introduce.
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx nestjs-doctor@latest .
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
For file paths and line numbers:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx nestjs-doctor@latest . --verbose
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
No config, no plugins, no setup.
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
┌──────────────────────────────────────────────────────────┐
|
|
37
|
+
│ ┌───────┐ │
|
|
38
|
+
│ │ ◠ ◠ ◠ │ NestJS Doctor │
|
|
39
|
+
│ │ ╰───╯ │ │
|
|
40
|
+
│ └───────┘ │
|
|
41
|
+
│ │
|
|
42
|
+
│ 82 / 100 ★★★★☆ Good │
|
|
43
|
+
│ │
|
|
44
|
+
│ █████████████████████████████████████████░░░░░░░░░░░░░ │
|
|
45
|
+
│ │
|
|
46
|
+
│ ✗ 2 errors ⚠ 5 warnings across 12/127 files in 1.2s │
|
|
47
|
+
└──────────────────────────────────────────────────────────┘
|
|
48
|
+
|
|
49
|
+
Project: my-api | NestJS 10.0.0 | prisma | 14 modules
|
|
50
|
+
|
|
51
|
+
✗ Controller injects ORM type 'PrismaService' directly (2)
|
|
52
|
+
Inject a service that wraps the ORM instead.
|
|
53
|
+
|
|
54
|
+
✗ Possible hardcoded Secret key detected
|
|
55
|
+
Move secrets to environment variables and access them via ConfigService.
|
|
56
|
+
|
|
57
|
+
⚠ Constructor parameter should be readonly (5)
|
|
58
|
+
Add the 'readonly' modifier to the constructor parameter.
|
|
59
|
+
|
|
60
|
+
Run with --verbose for file paths and line numbers
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## CI
|
|
66
|
+
|
|
67
|
+
Exit code `1` when errors are found. Use `--score` for threshold checks:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
SCORE=$(npx nestjs-doctor@latest . --score)
|
|
71
|
+
if [ "$SCORE" -lt 75 ]; then
|
|
72
|
+
echo "Health score $SCORE is below threshold (75)"
|
|
73
|
+
exit 1
|
|
74
|
+
fi
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
Usage: nestjs-doctor [directory] [options]
|
|
79
|
+
|
|
80
|
+
--verbose Show file paths and line numbers per diagnostic
|
|
81
|
+
--score Output only the numeric score (for CI)
|
|
82
|
+
--json JSON output (for tooling)
|
|
83
|
+
--config <p> Path to config file
|
|
84
|
+
-h, --help Show help
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Configuration
|
|
90
|
+
|
|
91
|
+
Optional. Create `nestjs-doctor.config.json` in your project root:
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"ignore": {
|
|
96
|
+
"rules": ["architecture/no-orm-in-services"],
|
|
97
|
+
"files": ["src/generated/**"]
|
|
98
|
+
},
|
|
99
|
+
"rules": {
|
|
100
|
+
"architecture/prefer-interface-injection": false
|
|
101
|
+
},
|
|
102
|
+
"categories": {
|
|
103
|
+
"performance": false
|
|
104
|
+
},
|
|
105
|
+
"thresholds": {
|
|
106
|
+
"godModuleProviders": 15,
|
|
107
|
+
"godModuleImports": 20,
|
|
108
|
+
"godServiceMethods": 12,
|
|
109
|
+
"godServiceDeps": 10
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Or use a `"nestjs-doctor"` key in `package.json`.
|
|
115
|
+
|
|
116
|
+
| Key | Type | Description |
|
|
117
|
+
|-----|------|-------------|
|
|
118
|
+
| `include` | `string[]` | Glob patterns to scan (default: `["**/*.ts"]`) |
|
|
119
|
+
| `exclude` | `string[]` | Glob patterns to skip (default includes `node_modules`, `dist`, test files) |
|
|
120
|
+
| `ignore.rules` | `string[]` | Rule IDs to suppress |
|
|
121
|
+
| `ignore.files` | `string[]` | Glob patterns for files whose diagnostics are hidden |
|
|
122
|
+
| `rules` | `Record<string, boolean>` | Enable/disable individual rules |
|
|
123
|
+
| `categories` | `Record<string, boolean>` | Enable/disable entire categories |
|
|
124
|
+
| `thresholds` | `object` | Customize limits for god-module / god-service rules |
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Monorepo Support
|
|
129
|
+
|
|
130
|
+
Auto-detected from `nest-cli.json`. When `"monorepo": true` is set, each sub-project is scanned independently and results are merged.
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"monorepo": true,
|
|
135
|
+
"projects": {
|
|
136
|
+
"api": { "root": "apps/api" },
|
|
137
|
+
"admin": { "root": "apps/admin" },
|
|
138
|
+
"shared": { "root": "libs/shared" }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The report shows a combined score plus a per-project breakdown.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Scoring
|
|
148
|
+
|
|
149
|
+
Weighted by severity and category, normalized by file count:
|
|
150
|
+
|
|
151
|
+
| Severity | Weight | | Category | Multiplier |
|
|
152
|
+
|----------|--------|-|----------|------------|
|
|
153
|
+
| error | 3.0 | | security | 1.5x |
|
|
154
|
+
| warning | 1.5 | | correctness | 1.3x |
|
|
155
|
+
| info | 0.5 | | architecture | 1.0x |
|
|
156
|
+
| | | | performance | 0.8x |
|
|
157
|
+
|
|
158
|
+
| Score | Label |
|
|
159
|
+
|-------|-------|
|
|
160
|
+
| 90-100 | Excellent |
|
|
161
|
+
| 75-89 | Good |
|
|
162
|
+
| 50-74 | Fair |
|
|
163
|
+
| 25-49 | Poor |
|
|
164
|
+
| 0-24 | Critical |
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Node.js API
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
import { diagnose, diagnoseMonorepo } from "nestjs-doctor";
|
|
172
|
+
|
|
173
|
+
const result = await diagnose("./my-nestjs-app");
|
|
174
|
+
result.score; // { value: 82, label: "Good" }
|
|
175
|
+
result.diagnostics; // Diagnostic[]
|
|
176
|
+
result.summary; // { total, errors, warnings, info, byCategory }
|
|
177
|
+
|
|
178
|
+
const mono = await diagnoseMonorepo("./my-monorepo");
|
|
179
|
+
mono.isMonorepo; // true
|
|
180
|
+
mono.subProjects; // [{ name: "api", result }, ...]
|
|
181
|
+
mono.combined; // Merged DiagnoseResult
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Rules (47)
|
|
187
|
+
|
|
188
|
+
### Security (11)
|
|
189
|
+
|
|
190
|
+
| Rule | Severity | What it catches |
|
|
191
|
+
|------|----------|-----------------|
|
|
192
|
+
| `no-hardcoded-secrets` | error | API keys, tokens, passwords in source code |
|
|
193
|
+
| `no-wildcard-cors` | error | CORS with `origin: '*'` or `origin: true` |
|
|
194
|
+
| `no-unsafe-raw-query` | error | Template literal interpolation in raw SQL |
|
|
195
|
+
| `no-eval` | error | `eval()` or `new Function()` usage |
|
|
196
|
+
| `no-csrf-disabled` | error | Explicitly disabling CSRF protection |
|
|
197
|
+
| `no-dangerous-redirects` | error | Redirects with user-controlled input |
|
|
198
|
+
| `no-weak-crypto` | warning | `createHash('md5')` or `createHash('sha1')` |
|
|
199
|
+
| `no-exposed-env-vars` | warning | Direct `process.env` in Injectable/Controller |
|
|
200
|
+
| `require-validation-pipe` | warning | `@Body()` param without validation pipe |
|
|
201
|
+
| `no-exposed-stack-trace` | warning | `error.stack` exposed in responses |
|
|
202
|
+
| `require-auth-guard` | info | Controller without `@UseGuards()` |
|
|
203
|
+
|
|
204
|
+
### Correctness (13)
|
|
205
|
+
|
|
206
|
+
| Rule | Severity | What it catches |
|
|
207
|
+
|------|----------|-----------------|
|
|
208
|
+
| `no-missing-injectable` | error | Provider in module missing `@Injectable()` |
|
|
209
|
+
| `no-duplicate-routes` | error | Same method + path twice in a controller |
|
|
210
|
+
| `no-missing-guard-method` | error | Guard class missing `canActivate()` |
|
|
211
|
+
| `no-missing-pipe-method` | error | Pipe class missing `transform()` |
|
|
212
|
+
| `no-missing-filter-catch` | error | `@Catch()` class missing `catch()` |
|
|
213
|
+
| `no-missing-interceptor-method` | error | Interceptor class missing `intercept()` |
|
|
214
|
+
| `require-inject-decorator` | error | Untyped constructor param without `@Inject()` |
|
|
215
|
+
| `prefer-readonly-injection` | warning | Constructor DI params missing `readonly` |
|
|
216
|
+
| `require-lifecycle-interface` | warning | Lifecycle method without corresponding interface |
|
|
217
|
+
| `no-empty-handlers` | warning | HTTP handler with empty body |
|
|
218
|
+
| `no-async-without-await` | warning | Async function/method with no `await` |
|
|
219
|
+
| `no-duplicate-module-metadata` | warning | Duplicate entries in `@Module()` arrays |
|
|
220
|
+
| `no-missing-module-decorator` | warning | Class named `*Module` without `@Module()` |
|
|
221
|
+
|
|
222
|
+
### Architecture (13)
|
|
223
|
+
|
|
224
|
+
| Rule | Severity | What it catches |
|
|
225
|
+
|------|----------|-----------------|
|
|
226
|
+
| `no-business-logic-in-controllers` | error | Loops, branches, data transforms in HTTP handlers |
|
|
227
|
+
| `no-repository-in-controllers` | error | Repository injection in controllers |
|
|
228
|
+
| `no-orm-in-controllers` | error | PrismaService / EntityManager / DataSource in controllers |
|
|
229
|
+
| `no-circular-module-deps` | error | Cycles in `@Module()` import graph |
|
|
230
|
+
| `no-manual-instantiation` | error | `new SomeService()` for injectable classes |
|
|
231
|
+
| `no-orm-in-services` | warning | Services using ORM directly (should use repositories) |
|
|
232
|
+
| `no-god-module` | warning | >10 providers or >15 imports |
|
|
233
|
+
| `no-god-service` | warning | >10 public methods or >8 dependencies |
|
|
234
|
+
| `require-feature-modules` | warning | AppModule declaring too many providers directly |
|
|
235
|
+
| `prefer-constructor-injection` | warning | `@Inject()` property injection |
|
|
236
|
+
| `require-module-boundaries` | info | Deep imports into other modules' internals |
|
|
237
|
+
| `prefer-interface-injection` | info | Concrete service-to-service injection |
|
|
238
|
+
| `no-barrel-export-internals` | info | Re-exporting repositories from barrel files |
|
|
239
|
+
|
|
240
|
+
### Performance (10)
|
|
241
|
+
|
|
242
|
+
| Rule | Severity | What it catches |
|
|
243
|
+
|------|----------|-----------------|
|
|
244
|
+
| `no-sync-io` | warning | `readFileSync`, `writeFileSync`, etc. |
|
|
245
|
+
| `no-query-in-loop` | warning | `await` inside loops (N+1 pattern) |
|
|
246
|
+
| `no-blocking-constructor` | warning | Loops/await in Injectable/Controller constructors |
|
|
247
|
+
| `no-dynamic-require` | warning | `require()` with non-literal argument |
|
|
248
|
+
| `no-unused-providers` | warning | Provider never injected anywhere |
|
|
249
|
+
| `no-logging-in-loops` | info | `console.*` / `this.logger.*` in loops |
|
|
250
|
+
| `no-unnecessary-async` | info | Async method with no `await` |
|
|
251
|
+
| `prefer-pagination` | info | `findMany()` / `find()` without pagination args |
|
|
252
|
+
| `no-unused-module-exports` | info | Module exports unused by importers |
|
|
253
|
+
| `no-orphan-modules` | info | Module never imported by any other module |
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Contributing
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
git clone https://github.com/RoloBits/nestjs-doctor.git
|
|
261
|
+
cd nestjs-doctor
|
|
262
|
+
pnpm install
|
|
263
|
+
pnpm run build
|
|
264
|
+
pnpm test
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Adding a rule
|
|
268
|
+
|
|
269
|
+
1. Create `src/rules/<category>/my-rule.ts`
|
|
270
|
+
2. Export a `Rule` (file-scoped) or `ProjectRule` (project-scoped) object
|
|
271
|
+
3. Register it in `src/rules/index.ts`
|
|
272
|
+
4. Add tests in `tests/unit/rules/`
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## License
|
|
277
|
+
|
|
278
|
+
[MIT]
|