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 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]