nestjs-doctor 0.1.0 → 0.1.1

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