@hatem427/code-guard-ci 3.0.0 → 3.1.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.
Files changed (102) hide show
  1. package/config/fastify.config.ts +326 -0
  2. package/config/hono.config.ts +331 -0
  3. package/config/nestjs.config.ts +500 -0
  4. package/config/node.config.ts +425 -0
  5. package/config/python.config.ts +512 -0
  6. package/dist/config/fastify.config.d.ts +17 -0
  7. package/dist/config/fastify.config.d.ts.map +1 -0
  8. package/dist/config/fastify.config.js +279 -0
  9. package/dist/config/fastify.config.js.map +1 -0
  10. package/dist/config/hono.config.d.ts +17 -0
  11. package/dist/config/hono.config.d.ts.map +1 -0
  12. package/dist/config/hono.config.js +287 -0
  13. package/dist/config/hono.config.js.map +1 -0
  14. package/dist/config/nestjs.config.d.ts +17 -0
  15. package/dist/config/nestjs.config.d.ts.map +1 -0
  16. package/dist/config/nestjs.config.js +440 -0
  17. package/dist/config/nestjs.config.js.map +1 -0
  18. package/dist/config/node.config.d.ts +17 -0
  19. package/dist/config/node.config.d.ts.map +1 -0
  20. package/dist/config/node.config.js +363 -0
  21. package/dist/config/node.config.js.map +1 -0
  22. package/dist/config/python.config.d.ts +15 -0
  23. package/dist/config/python.config.d.ts.map +1 -0
  24. package/dist/config/python.config.js +475 -0
  25. package/dist/config/python.config.js.map +1 -0
  26. package/dist/scripts/auto-fix.d.ts +5 -0
  27. package/dist/scripts/auto-fix.d.ts.map +1 -1
  28. package/dist/scripts/auto-fix.js +5 -0
  29. package/dist/scripts/auto-fix.js.map +1 -1
  30. package/dist/scripts/cli.js +2 -2
  31. package/dist/scripts/cli.js.map +1 -1
  32. package/dist/scripts/config-generators/ai-config-generator.d.ts.map +1 -1
  33. package/dist/scripts/config-generators/ai-config-generator.js +6 -0
  34. package/dist/scripts/config-generators/ai-config-generator.js.map +1 -1
  35. package/dist/scripts/config-generators/eslint-generator.d.ts.map +1 -1
  36. package/dist/scripts/config-generators/eslint-generator.js +108 -0
  37. package/dist/scripts/config-generators/eslint-generator.js.map +1 -1
  38. package/dist/scripts/config-generators/frameworks/fastify.d.ts +6 -0
  39. package/dist/scripts/config-generators/frameworks/fastify.d.ts.map +1 -0
  40. package/dist/scripts/config-generators/frameworks/fastify.js +68 -0
  41. package/dist/scripts/config-generators/frameworks/fastify.js.map +1 -0
  42. package/dist/scripts/config-generators/frameworks/hono.d.ts +6 -0
  43. package/dist/scripts/config-generators/frameworks/hono.d.ts.map +1 -0
  44. package/dist/scripts/config-generators/frameworks/hono.js +63 -0
  45. package/dist/scripts/config-generators/frameworks/hono.js.map +1 -0
  46. package/dist/scripts/config-generators/frameworks/index.d.ts +3 -0
  47. package/dist/scripts/config-generators/frameworks/index.d.ts.map +1 -1
  48. package/dist/scripts/config-generators/frameworks/index.js +7 -1
  49. package/dist/scripts/config-generators/frameworks/index.js.map +1 -1
  50. package/dist/scripts/config-generators/frameworks/nestjs.d.ts +6 -0
  51. package/dist/scripts/config-generators/frameworks/nestjs.d.ts.map +1 -0
  52. package/dist/scripts/config-generators/frameworks/nestjs.js +83 -0
  53. package/dist/scripts/config-generators/frameworks/nestjs.js.map +1 -0
  54. package/dist/scripts/config-generators/frameworks/node.d.ts +2 -2
  55. package/dist/scripts/config-generators/frameworks/node.d.ts.map +1 -1
  56. package/dist/scripts/config-generators/frameworks/node.js +56 -11
  57. package/dist/scripts/config-generators/frameworks/node.js.map +1 -1
  58. package/dist/scripts/config-generators/typescript-generator.d.ts.map +1 -1
  59. package/dist/scripts/config-generators/typescript-generator.js +33 -0
  60. package/dist/scripts/config-generators/typescript-generator.js.map +1 -1
  61. package/dist/scripts/config-generators/vscode-generator.d.ts.map +1 -1
  62. package/dist/scripts/config-generators/vscode-generator.js +73 -0
  63. package/dist/scripts/config-generators/vscode-generator.js.map +1 -1
  64. package/dist/scripts/generate-pr-checklist.d.ts +5 -0
  65. package/dist/scripts/generate-pr-checklist.d.ts.map +1 -1
  66. package/dist/scripts/generate-pr-checklist.js +6 -1
  67. package/dist/scripts/generate-pr-checklist.js.map +1 -1
  68. package/dist/scripts/postinstall.js +38 -0
  69. package/dist/scripts/postinstall.js.map +1 -1
  70. package/dist/scripts/precommit-check.d.ts +13 -0
  71. package/dist/scripts/precommit-check.d.ts.map +1 -1
  72. package/dist/scripts/precommit-check.js +288 -5
  73. package/dist/scripts/precommit-check.js.map +1 -1
  74. package/dist/scripts/utils/naming-validator.d.ts.map +1 -1
  75. package/dist/scripts/utils/naming-validator.js +96 -2
  76. package/dist/scripts/utils/naming-validator.js.map +1 -1
  77. package/dist/scripts/utils/project-detector.d.ts +12 -9
  78. package/dist/scripts/utils/project-detector.d.ts.map +1 -1
  79. package/dist/scripts/utils/project-detector.js +63 -11
  80. package/dist/scripts/utils/project-detector.js.map +1 -1
  81. package/dist/scripts/utils/structure-validator.d.ts.map +1 -1
  82. package/dist/scripts/utils/structure-validator.js +50 -0
  83. package/dist/scripts/utils/structure-validator.js.map +1 -1
  84. package/package.json +10 -3
  85. package/scripts/auto-fix.ts +5 -0
  86. package/scripts/cli.ts +2 -2
  87. package/scripts/config-generators/ai-config-generator.ts +9 -0
  88. package/scripts/config-generators/eslint-generator.ts +110 -0
  89. package/scripts/config-generators/frameworks/fastify.ts +65 -0
  90. package/scripts/config-generators/frameworks/hono.ts +60 -0
  91. package/scripts/config-generators/frameworks/index.ts +3 -0
  92. package/scripts/config-generators/frameworks/nestjs.ts +80 -0
  93. package/scripts/config-generators/frameworks/node.ts +57 -11
  94. package/scripts/config-generators/typescript-generator.ts +36 -0
  95. package/scripts/config-generators/vscode-generator.ts +84 -0
  96. package/scripts/generate-pr-checklist.ts +6 -1
  97. package/scripts/postinstall.ts +38 -0
  98. package/scripts/precommit-check.ts +334 -6
  99. package/scripts/utils/naming-validator.ts +104 -2
  100. package/scripts/utils/project-detector.ts +78 -11
  101. package/scripts/utils/structure-validator.ts +54 -0
  102. package/templates/feature-doc-backend.md +114 -0
@@ -0,0 +1,425 @@
1
+ /**
2
+ * ============================================================================
3
+ * node.config.ts — Express / Node.js-specific coding rules
4
+ * ============================================================================
5
+ *
6
+ * Registers rules that apply to Express and generic Node.js backend projects:
7
+ * - Route handler / service separation
8
+ * - Async error propagation
9
+ * - Input validation enforcement
10
+ * - Security: helmet, CORS, parameterized queries
11
+ * - Environment configuration patterns
12
+ * - Graceful shutdown
13
+ */
14
+
15
+ import { registerRules, Rule } from './guidelines.config';
16
+
17
+ const nodeRules: Rule[] = [
18
+
19
+ // ── Architecture ──────────────────────────────────────────────────────────
20
+
21
+ // ─────────────────────────────────────────
22
+ // RULE: node-thin-route-handler
23
+ // ROLE: Enforce separation of HTTP concerns from business logic
24
+ // PURPOSE: Route handlers that contain business logic cannot be unit-tested
25
+ // without spinning up an Express server. Logic should live in
26
+ // service functions that are independently testable.
27
+ // EXAMPLE:
28
+ // WRONG:
29
+ // router.post('/users', async (req, res) => {
30
+ // const salt = await bcrypt.genSalt();
31
+ // const hash = await bcrypt.hash(req.body.password, salt);
32
+ // const user = await db.users.create({ ...req.body, password: hash });
33
+ // res.status(201).json(user);
34
+ // });
35
+ // RIGHT:
36
+ // router.post('/users', async (req, res, next) => {
37
+ // try {
38
+ // const user = await userService.createUser(req.body);
39
+ // res.status(201).json(user);
40
+ // } catch (err) { next(err); }
41
+ // });
42
+ // ─────────────────────────────────────────
43
+ {
44
+ id: 'node-thin-route-handler',
45
+ label: 'Keep route handlers thin — delegate to services',
46
+ description:
47
+ 'Route handlers should only parse the request, call a service, and send the response. Business logic belongs in service modules.',
48
+ severity: 'warning',
49
+ fileExtensions: ['ts', 'js'],
50
+ pattern: null,
51
+ customCheck: (file) => {
52
+ const violations: Array<{ line: number | null; message: string }> = [];
53
+
54
+ if (
55
+ !file.relativePath.includes('/routes/') &&
56
+ !file.relativePath.endsWith('.router.ts') &&
57
+ !file.relativePath.endsWith('.routes.ts')
58
+ ) {
59
+ return [];
60
+ }
61
+
62
+ const businessPatterns = [
63
+ { regex: /bcrypt\.(hash|compare|genSalt)/g, label: 'bcrypt hashing' },
64
+ { regex: /\.save\s*\(|\.create\s*\(/g, label: 'direct ORM calls' },
65
+ { regex: /jwt\.sign|jwt\.verify/g, label: 'JWT operations' },
66
+ ];
67
+
68
+ for (let i = 0; i < file.lines.length; i++) {
69
+ for (const { regex, label } of businessPatterns) {
70
+ regex.lastIndex = 0;
71
+ if (regex.test(file.lines[i])) {
72
+ violations.push({
73
+ line: i + 1,
74
+ message: `Business logic (${label}) found in route handler. Move to a service module.`,
75
+ });
76
+ }
77
+ }
78
+ }
79
+
80
+ return violations;
81
+ },
82
+ applicableTo: ['node'],
83
+ category: 'Architecture',
84
+ },
85
+
86
+ // ── Async & Error Handling ─────────────────────────────────────────────────
87
+
88
+ // ─────────────────────────────────────────
89
+ // RULE: node-async-next-error
90
+ // ROLE: Enforce error propagation in async route handlers
91
+ // PURPOSE: Without next(err), an error thrown in an async handler
92
+ // produces an unhandled promise rejection, not a proper HTTP 500.
93
+ // Wrapping with try/catch + next(err) — or using express-async-errors
94
+ // — ensures errors reach the global error-handling middleware.
95
+ // EXAMPLE:
96
+ // WRONG:
97
+ // router.get('/users', async (req, res) => {
98
+ // const users = await userService.findAll(); // throws → unhandled rejection
99
+ // res.json(users);
100
+ // });
101
+ // RIGHT:
102
+ // router.get('/users', async (req, res, next) => {
103
+ // try {
104
+ // const users = await userService.findAll();
105
+ // res.json(users);
106
+ // } catch (err) { next(err); }
107
+ // });
108
+ // ─────────────────────────────────────────
109
+ {
110
+ id: 'node-async-next-error',
111
+ label: 'Async route handlers must forward errors to next()',
112
+ description:
113
+ 'Async Express route handlers must use try/catch and call next(err) on failure, or use express-async-errors. Unhandled rejections bypass error-handling middleware.',
114
+ severity: 'error',
115
+ fileExtensions: ['ts', 'js'],
116
+ pattern: null,
117
+ customCheck: (file) => {
118
+ const violations: Array<{ line: number | null; message: string }> = [];
119
+
120
+ if (
121
+ !file.relativePath.includes('/routes/') &&
122
+ !file.relativePath.endsWith('.router.ts') &&
123
+ !file.relativePath.endsWith('.routes.ts') &&
124
+ !file.relativePath.endsWith('.router.js')
125
+ ) {
126
+ return [];
127
+ }
128
+
129
+ // Detect async route handlers without try/catch or next(err)
130
+ const asyncHandlerRegex = /router\.\w+\(['"`][^'"]+['"`]\s*,\s*async\s*\(req/g;
131
+ let match;
132
+ while ((match = asyncHandlerRegex.exec(file.content)) !== null) {
133
+ const afterMatch = file.content.slice(match.index, match.index + 500);
134
+ if (!afterMatch.includes('try {') && !afterMatch.includes('next(err')) {
135
+ // Approximate line number
136
+ const lineNum = file.content.slice(0, match.index).split('\n').length;
137
+ violations.push({
138
+ line: lineNum,
139
+ message:
140
+ 'Async route handler without error handling. Wrap with try/catch and call next(err), or install express-async-errors.',
141
+ });
142
+ }
143
+ }
144
+
145
+ return violations;
146
+ },
147
+ applicableTo: ['node'],
148
+ category: 'Error Handling',
149
+ },
150
+
151
+ // ─────────────────────────────────────────
152
+ // RULE: node-error-middleware-signature
153
+ // ROLE: Enforce 4-argument error middleware signature
154
+ // PURPOSE: Express only treats a middleware as an error handler when it
155
+ // has exactly 4 parameters (err, req, res, next). A 3-param
156
+ // function that happens to receive an error object will not work.
157
+ // EXAMPLE:
158
+ // WRONG:
159
+ // app.use((err, res) => { res.status(500).json({ error: err.message }); });
160
+ // RIGHT:
161
+ // app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
162
+ // res.status(err.statusCode ?? 500).json({ message: err.message });
163
+ // });
164
+ // ─────────────────────────────────────────
165
+ {
166
+ id: 'node-error-middleware-signature',
167
+ label: 'Global error handler must have 4 parameters (err, req, res, next)',
168
+ description:
169
+ 'Express error-handling middleware must declare exactly 4 parameters: (err, req, res, next). With fewer params, Express will not recognise it as an error handler.',
170
+ severity: 'error',
171
+ fileExtensions: ['ts', 'js'],
172
+ pattern: null,
173
+ customCheck: (file) => {
174
+ if (
175
+ !file.relativePath.includes('error') &&
176
+ !file.relativePath.includes('middleware') &&
177
+ !file.relativePath.endsWith('app.ts') &&
178
+ !file.relativePath.endsWith('server.ts')
179
+ ) {
180
+ return [];
181
+ }
182
+
183
+ const violations: Array<{ line: number | null; message: string }> = [];
184
+
185
+ for (let i = 0; i < file.lines.length; i++) {
186
+ const line = file.lines[i];
187
+ // Detect app.use with callback that looks like error handler but has <4 params
188
+ if (/app\.use\s*\(\s*(?:['"`][^']+['"`]\s*,\s*)?\s*(?:async\s*)?\([^)]*\)\s*=>/.test(line)) {
189
+ const params = (line.match(/\(([^)]*)\)/) || [])[1] || '';
190
+ const count = params.split(',').filter((p: string) => p.trim()).length;
191
+ if (count === 3 && /err|error/i.test(params)) {
192
+ violations.push({
193
+ line: i + 1,
194
+ message:
195
+ 'Error-handling middleware must declare 4 parameters: (err, req, res, next). Express uses arity to detect error handlers.',
196
+ });
197
+ }
198
+ }
199
+ }
200
+
201
+ return violations;
202
+ },
203
+ applicableTo: ['node'],
204
+ category: 'Error Handling',
205
+ },
206
+
207
+ // ── Input Validation ──────────────────────────────────────────────────────
208
+
209
+ // ─────────────────────────────────────────
210
+ // RULE: node-validate-request-body
211
+ // ROLE: Enforce input sanitisation before use
212
+ // PURPOSE: Using req.body without validation exposes the service to
213
+ // malformed data, SQL injection, and XSS. Every route that reads
214
+ // req.body must apply a schema validation middleware first.
215
+ // EXAMPLE:
216
+ // WRONG:
217
+ // router.post('/users', async (req, res) => {
218
+ // const { email, name } = req.body; // unvalidated
219
+ // });
220
+ // RIGHT:
221
+ // import { createUserSchema } from '../schemas/user.schema';
222
+ // router.post('/users', validate(createUserSchema), async (req, res) => { ... });
223
+ // ─────────────────────────────────────────
224
+ {
225
+ id: 'node-validate-request-body',
226
+ label: 'Validate req.body before use',
227
+ description:
228
+ 'Always define and apply a validation schema (Zod, Joi, express-validator) for routes that read req.body. Unvalidated input is the root cause of most injection vulnerabilities.',
229
+ severity: 'warning',
230
+ fileExtensions: ['ts', 'js'],
231
+ pattern: null,
232
+ customCheck: (file) => {
233
+ const violations: Array<{ line: number | null; message: string }> = [];
234
+
235
+ if (
236
+ !file.relativePath.includes('/routes/') &&
237
+ !file.relativePath.endsWith('.router.ts') &&
238
+ !file.relativePath.endsWith('.routes.ts')
239
+ ) {
240
+ return [];
241
+ }
242
+
243
+ const hasValidation = /validate\s*\(|zValidator|z\.object|Joi\.object|checkSchema|body\s*\(/.test(file.content);
244
+
245
+ if (!hasValidation && /req\.body/.test(file.content)) {
246
+ violations.push({
247
+ line: null,
248
+ message:
249
+ 'req.body is used without any visible validation middleware (Zod, Joi, express-validator). Add a schema validation step before the route handler.',
250
+ });
251
+ }
252
+
253
+ return violations;
254
+ },
255
+ applicableTo: ['node'],
256
+ category: 'Security',
257
+ },
258
+
259
+ // ── Security ──────────────────────────────────────────────────────────────
260
+
261
+ // ─────────────────────────────────────────
262
+ // RULE: node-use-helmet
263
+ // ROLE: Enforce security headers
264
+ // PURPOSE: Without Helmet, Express servers are vulnerable to well-known
265
+ // HTTP header-based attacks (clickjacking, MIME-sniffing, XSS via
266
+ // missing CSP). Helmet sets a secure baseline in one call.
267
+ // EXAMPLE:
268
+ // WRONG:
269
+ // const app = express();
270
+ // app.use(cors());
271
+ // RIGHT:
272
+ // const app = express();
273
+ // app.use(helmet());
274
+ // app.use(cors({ origin: allowedOrigins }));
275
+ // ─────────────────────────────────────────
276
+ {
277
+ id: 'node-use-helmet',
278
+ label: 'Use helmet() for security headers',
279
+ description:
280
+ 'Apply helmet() middleware in the Express app setup to set secure HTTP headers (Content-Security-Policy, X-Frame-Options, HSTS, etc.).',
281
+ severity: 'error',
282
+ fileExtensions: ['ts', 'js'],
283
+ pattern: null,
284
+ customCheck: (file) => {
285
+ if (
286
+ !file.relativePath.endsWith('app.ts') &&
287
+ !file.relativePath.endsWith('server.ts') &&
288
+ !file.relativePath.endsWith('app.js') &&
289
+ !file.relativePath.endsWith('server.js')
290
+ ) {
291
+ return [];
292
+ }
293
+
294
+ const hasExpress = /express\s*\(\s*\)/.test(file.content);
295
+ const hasHelmet = /helmet\s*\(\s*\)/.test(file.content);
296
+
297
+ if (hasExpress && !hasHelmet) {
298
+ return [
299
+ {
300
+ line: null,
301
+ message:
302
+ 'Express app is missing helmet(). Install helmet and add app.use(helmet()) before other middleware.',
303
+ },
304
+ ];
305
+ }
306
+
307
+ return [];
308
+ },
309
+ applicableTo: ['node'],
310
+ category: 'Security',
311
+ },
312
+
313
+ // ─────────────────────────────────────────
314
+ // RULE: node-no-process-env-scatter
315
+ // ROLE: Centralise configuration access
316
+ // PURPOSE: Using process.env in route handlers and services scatters
317
+ // configuration reads, prevents startup validation, and makes
318
+ // tests hard to write (no clean injection point).
319
+ // Validate env vars once at startup in a config module.
320
+ // EXAMPLE:
321
+ // WRONG:
322
+ // const secret = process.env['JWT_SECRET'];
323
+ // RIGHT:
324
+ // import { config } from '../config'; // validated at startup
325
+ // const secret = config.jwtSecret;
326
+ // ─────────────────────────────────────────
327
+ {
328
+ id: 'node-no-process-env-scatter',
329
+ label: 'Centralise process.env access in a config module',
330
+ description:
331
+ 'Do not read process.env in route handlers, services, or models. Validate env vars at startup in a dedicated config module and import from there.',
332
+ severity: 'warning',
333
+ fileExtensions: ['ts', 'js'],
334
+ pattern: null,
335
+ customCheck: (file) => {
336
+ const violations: Array<{ line: number | null; message: string }> = [];
337
+
338
+ // Skip config/env files
339
+ if (
340
+ file.relativePath.includes('/config/') ||
341
+ file.relativePath.includes('.config.') ||
342
+ file.relativePath.endsWith('env.ts') ||
343
+ file.relativePath.endsWith('env.js') ||
344
+ file.relativePath.endsWith('main.ts') ||
345
+ file.relativePath.endsWith('server.ts')
346
+ ) {
347
+ return [];
348
+ }
349
+
350
+ for (let i = 0; i < file.lines.length; i++) {
351
+ if (/process\.env\b/.test(file.lines[i])) {
352
+ violations.push({
353
+ line: i + 1,
354
+ message:
355
+ 'process.env read inside business code. Move to a config module (validated at startup) and import from there.',
356
+ });
357
+ }
358
+ }
359
+
360
+ return violations;
361
+ },
362
+ applicableTo: ['node'],
363
+ category: 'Security',
364
+ },
365
+
366
+ // ── Graceful Shutdown ─────────────────────────────────────────────────────
367
+
368
+ // ─────────────────────────────────────────
369
+ // RULE: node-graceful-shutdown
370
+ // ROLE: Enforce graceful process shutdown
371
+ // PURPOSE: Without SIGTERM handling, a Node.js server terminates
372
+ // immediately when the container or OS signals shutdown.
373
+ // In-flight requests are cut short, and DB connections leak.
374
+ // Graceful shutdown waits for open connections to close first.
375
+ // EXAMPLE:
376
+ // WRONG:
377
+ // server.listen(port);
378
+ // RIGHT:
379
+ // server.listen(port);
380
+ // process.on('SIGTERM', () => {
381
+ // server.close(() => { db.end(); process.exit(0); });
382
+ // });
383
+ // ─────────────────────────────────────────
384
+ {
385
+ id: 'node-graceful-shutdown',
386
+ label: 'Handle SIGTERM for graceful shutdown',
387
+ description:
388
+ 'Listen for SIGTERM and SIGINT signals to close the HTTP server and DB connections gracefully before exiting. Required in containerised environments.',
389
+ severity: 'warning',
390
+ fileExtensions: ['ts', 'js'],
391
+ pattern: null,
392
+ customCheck: (file) => {
393
+ if (
394
+ !file.relativePath.endsWith('main.ts') &&
395
+ !file.relativePath.endsWith('server.ts') &&
396
+ !file.relativePath.endsWith('index.ts') &&
397
+ !file.relativePath.endsWith('server.js')
398
+ ) {
399
+ return [];
400
+ }
401
+
402
+ const hasSigterm = /SIGTERM|SIGINT/.test(file.content);
403
+ const hasServerListen = /\.listen\s*\(/.test(file.content);
404
+
405
+ if (hasServerListen && !hasSigterm) {
406
+ return [
407
+ {
408
+ line: null,
409
+ message:
410
+ 'Server starts but no SIGTERM/SIGINT handler found. Add graceful shutdown: process.on("SIGTERM", () => server.close(() => process.exit(0)))',
411
+ },
412
+ ];
413
+ }
414
+
415
+ return [];
416
+ },
417
+ applicableTo: ['node'],
418
+ category: 'Reliability',
419
+ },
420
+ ];
421
+
422
+ // Register all Node.js (Express) specific rules
423
+ registerRules('node', nodeRules);
424
+
425
+ export { nodeRules };