@hatem427/code-guard-ci 3.2.0 → 3.4.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.
- package/config/angular.config.ts +29 -708
- package/config/guidelines.config.ts +5 -130
- package/config/nextjs.config.ts +27 -511
- package/config/react.config.ts +19 -614
- package/dist/config/angular.config.d.ts +5 -8
- package/dist/config/angular.config.d.ts.map +1 -1
- package/dist/config/angular.config.js +28 -666
- package/dist/config/angular.config.js.map +1 -1
- package/dist/config/guidelines.config.d.ts.map +1 -1
- package/dist/config/guidelines.config.js +5 -127
- package/dist/config/guidelines.config.js.map +1 -1
- package/dist/config/nextjs.config.d.ts +7 -9
- package/dist/config/nextjs.config.d.ts.map +1 -1
- package/dist/config/nextjs.config.js +26 -472
- package/dist/config/nextjs.config.js.map +1 -1
- package/dist/config/react.config.d.ts +4 -5
- package/dist/config/react.config.d.ts.map +1 -1
- package/dist/config/react.config.js +19 -586
- package/dist/config/react.config.js.map +1 -1
- package/dist/scripts/auto-fix.d.ts +0 -5
- package/dist/scripts/auto-fix.d.ts.map +1 -1
- package/dist/scripts/auto-fix.js +0 -5
- package/dist/scripts/auto-fix.js.map +1 -1
- package/dist/scripts/cli.js +209 -390
- package/dist/scripts/cli.js.map +1 -1
- package/dist/scripts/config-generators/ai-config-generator.d.ts.map +1 -1
- package/dist/scripts/config-generators/ai-config-generator.js +71 -15
- package/dist/scripts/config-generators/ai-config-generator.js.map +1 -1
- package/dist/scripts/config-generators/eslint-generator.d.ts.map +1 -1
- package/dist/scripts/config-generators/eslint-generator.js +13 -625
- package/dist/scripts/config-generators/eslint-generator.js.map +1 -1
- package/dist/scripts/config-generators/index.d.ts +0 -1
- package/dist/scripts/config-generators/index.d.ts.map +1 -1
- package/dist/scripts/config-generators/index.js +1 -5
- package/dist/scripts/config-generators/index.js.map +1 -1
- package/dist/scripts/config-generators/typescript-generator.d.ts.map +1 -1
- package/dist/scripts/config-generators/typescript-generator.js +0 -33
- package/dist/scripts/config-generators/typescript-generator.js.map +1 -1
- package/dist/scripts/config-generators/vscode-generator.d.ts.map +1 -1
- package/dist/scripts/config-generators/vscode-generator.js +28 -171
- package/dist/scripts/config-generators/vscode-generator.js.map +1 -1
- package/dist/scripts/generate-pr-checklist.d.ts +0 -5
- package/dist/scripts/generate-pr-checklist.d.ts.map +1 -1
- package/dist/scripts/generate-pr-checklist.js +1 -6
- package/dist/scripts/generate-pr-checklist.js.map +1 -1
- package/dist/scripts/postinstall.js +0 -38
- package/dist/scripts/postinstall.js.map +1 -1
- package/dist/scripts/precommit-check.d.ts +0 -5
- package/dist/scripts/precommit-check.d.ts.map +1 -1
- package/dist/scripts/precommit-check.js +137 -121
- package/dist/scripts/precommit-check.js.map +1 -1
- package/dist/scripts/utils/naming-validator.d.ts.map +1 -1
- package/dist/scripts/utils/naming-validator.js +2 -96
- package/dist/scripts/utils/naming-validator.js.map +1 -1
- package/dist/scripts/utils/project-detector.d.ts +9 -12
- package/dist/scripts/utils/project-detector.d.ts.map +1 -1
- package/dist/scripts/utils/project-detector.js +11 -63
- package/dist/scripts/utils/project-detector.js.map +1 -1
- package/dist/scripts/utils/report-generator.js +5 -17
- package/dist/scripts/utils/report-generator.js.map +1 -1
- package/dist/scripts/utils/structure-validator.d.ts.map +1 -1
- package/dist/scripts/utils/structure-validator.js +0 -50
- package/dist/scripts/utils/structure-validator.js.map +1 -1
- package/package.json +1 -12
- package/scripts/auto-fix.ts +0 -5
- package/scripts/cli.ts +224 -424
- package/scripts/config-generators/ai-config-generator.ts +78 -28
- package/scripts/config-generators/eslint-generator.ts +7 -621
- package/scripts/config-generators/index.ts +0 -1
- package/scripts/config-generators/typescript-generator.ts +0 -36
- package/scripts/config-generators/vscode-generator.ts +40 -178
- package/scripts/generate-pr-checklist.ts +1 -6
- package/scripts/postinstall.ts +0 -38
- package/scripts/precommit-check.ts +143 -224
- package/scripts/utils/naming-validator.ts +2 -104
- package/scripts/utils/project-detector.ts +11 -78
- package/scripts/utils/report-generator.ts +5 -19
- package/scripts/utils/structure-validator.ts +0 -54
- package/config/fastify.config.ts +0 -326
- package/config/hono.config.ts +0 -331
- package/config/nestjs.config.ts +0 -500
- package/config/python.config.ts +0 -512
- package/templates/feature-doc-api.md +0 -101
- package/templates/feature-doc-backend.md +0 -114
- package/templates/feature-doc-service.md +0 -113
- package/templates/feature-doc-ui.md +0 -91
|
@@ -163,105 +163,6 @@ const frameworkNamingRules: Record<ProjectType, NamingRule[]> = {
|
|
|
163
163
|
],
|
|
164
164
|
|
|
165
165
|
node: [...baseNamingRules],
|
|
166
|
-
|
|
167
|
-
nestjs: [
|
|
168
|
-
...baseNamingRules,
|
|
169
|
-
{
|
|
170
|
-
filePattern: /\.module\.ts$/,
|
|
171
|
-
convention: 'kebab-case',
|
|
172
|
-
description: 'NestJS modules should use kebab-case: users.module.ts',
|
|
173
|
-
},
|
|
174
|
-
{
|
|
175
|
-
filePattern: /\.controller\.(ts|spec\.ts)$/,
|
|
176
|
-
convention: 'kebab-case',
|
|
177
|
-
description: 'NestJS controllers should use kebab-case: users.controller.ts',
|
|
178
|
-
},
|
|
179
|
-
{
|
|
180
|
-
filePattern: /\.service\.(ts|spec\.ts)$/,
|
|
181
|
-
convention: 'kebab-case',
|
|
182
|
-
description: 'NestJS services should use kebab-case: users.service.ts',
|
|
183
|
-
},
|
|
184
|
-
{
|
|
185
|
-
filePattern: /\.dto\.ts$/,
|
|
186
|
-
convention: 'kebab-case',
|
|
187
|
-
description: 'NestJS DTOs should use kebab-case: create-user.dto.ts',
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
filePattern: /\.guard\.ts$/,
|
|
191
|
-
convention: 'kebab-case',
|
|
192
|
-
description: 'NestJS guards should use kebab-case: auth.guard.ts',
|
|
193
|
-
},
|
|
194
|
-
{
|
|
195
|
-
filePattern: /\.pipe\.ts$/,
|
|
196
|
-
convention: 'kebab-case',
|
|
197
|
-
description: 'NestJS pipes should use kebab-case: parse-id.pipe.ts',
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
filePattern: /\.interceptor\.ts$/,
|
|
201
|
-
convention: 'kebab-case',
|
|
202
|
-
description: 'NestJS interceptors should use kebab-case: logging.interceptor.ts',
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
filePattern: /\.filter\.ts$/,
|
|
206
|
-
convention: 'kebab-case',
|
|
207
|
-
description: 'NestJS exception filters should use kebab-case: http-exception.filter.ts',
|
|
208
|
-
},
|
|
209
|
-
{
|
|
210
|
-
filePattern: /\.decorator\.ts$/,
|
|
211
|
-
convention: 'kebab-case',
|
|
212
|
-
description: 'NestJS decorators should use kebab-case: current-user.decorator.ts',
|
|
213
|
-
},
|
|
214
|
-
],
|
|
215
|
-
|
|
216
|
-
fastify: [
|
|
217
|
-
...baseNamingRules,
|
|
218
|
-
{
|
|
219
|
-
filePattern: /\.route\.ts$/,
|
|
220
|
-
convention: 'kebab-case',
|
|
221
|
-
description: 'Fastify route files should use kebab-case: users.route.ts',
|
|
222
|
-
},
|
|
223
|
-
{
|
|
224
|
-
filePattern: /\.plugin\.ts$/,
|
|
225
|
-
convention: 'kebab-case',
|
|
226
|
-
description: 'Fastify plugins should use kebab-case: auth.plugin.ts',
|
|
227
|
-
},
|
|
228
|
-
{
|
|
229
|
-
filePattern: /\.schema\.ts$/,
|
|
230
|
-
convention: 'kebab-case',
|
|
231
|
-
description: 'Schema files should use kebab-case: create-user.schema.ts',
|
|
232
|
-
},
|
|
233
|
-
],
|
|
234
|
-
|
|
235
|
-
hono: [
|
|
236
|
-
...baseNamingRules,
|
|
237
|
-
{
|
|
238
|
-
filePattern: /\.route\.ts$/,
|
|
239
|
-
convention: 'kebab-case',
|
|
240
|
-
description: 'Hono route files should use kebab-case: users.route.ts',
|
|
241
|
-
},
|
|
242
|
-
{
|
|
243
|
-
filePattern: /\.handler\.ts$/,
|
|
244
|
-
convention: 'kebab-case',
|
|
245
|
-
description: 'Hono handler files should use kebab-case: create-user.handler.ts',
|
|
246
|
-
},
|
|
247
|
-
{
|
|
248
|
-
filePattern: /\.middleware\.ts$/,
|
|
249
|
-
convention: 'kebab-case',
|
|
250
|
-
description: 'Hono middleware files should use kebab-case: auth.middleware.ts',
|
|
251
|
-
},
|
|
252
|
-
{
|
|
253
|
-
filePattern: /\.schema\.ts$/,
|
|
254
|
-
convention: 'kebab-case',
|
|
255
|
-
description: 'Schema files should use kebab-case: user.schema.ts',
|
|
256
|
-
},
|
|
257
|
-
],
|
|
258
|
-
|
|
259
|
-
// Python frameworks use snake_case for all files
|
|
260
|
-
python: [...baseNamingRules],
|
|
261
|
-
django: [...baseNamingRules],
|
|
262
|
-
fastapi: [...baseNamingRules],
|
|
263
|
-
flask: [...baseNamingRules],
|
|
264
|
-
|
|
265
166
|
unknown: [...baseNamingRules],
|
|
266
167
|
};
|
|
267
168
|
|
|
@@ -372,12 +273,9 @@ export function validateNaming(
|
|
|
372
273
|
}
|
|
373
274
|
|
|
374
275
|
function getNameWithoutExtensions(fileName: string): string {
|
|
375
|
-
// Remove all known suffixes
|
|
276
|
+
// Remove all known suffixes: .component.ts, .test.tsx, .service.ts, etc.
|
|
376
277
|
return fileName
|
|
377
|
-
.replace(
|
|
378
|
-
/\.(component|service|module|pipe|directive|guard|interceptor|filter|decorator|controller|dto|handler|plugin|route|schema|test|spec|util|constants?|types?|middleware)\./g,
|
|
379
|
-
'.'
|
|
380
|
-
)
|
|
278
|
+
.replace(/\.(component|service|module|pipe|directive|guard|interceptor|test|spec|util|constants?|types?)\./g, '.')
|
|
381
279
|
.replace(/\.[^.]+$/, ''); // Remove final extension
|
|
382
280
|
}
|
|
383
281
|
|
|
@@ -21,14 +21,7 @@ export type ProjectType =
|
|
|
21
21
|
| 'vue'
|
|
22
22
|
| 'nuxt'
|
|
23
23
|
| 'svelte'
|
|
24
|
-
| 'nestjs'
|
|
25
|
-
| 'fastify'
|
|
26
|
-
| 'hono'
|
|
27
24
|
| 'node'
|
|
28
|
-
| 'django'
|
|
29
|
-
| 'fastapi'
|
|
30
|
-
| 'flask'
|
|
31
|
-
| 'python'
|
|
32
25
|
| 'unknown';
|
|
33
26
|
|
|
34
27
|
export type MonorepoType = 'nx' | 'turborepo' | 'lerna' | 'pnpm-workspaces' | 'none';
|
|
@@ -169,17 +162,14 @@ function detectExistingTooling(dir: string, pkg: Record<string, any>): ExistingT
|
|
|
169
162
|
* Detect the project type based on the nearest package.json.
|
|
170
163
|
*
|
|
171
164
|
* Priority order (first match wins):
|
|
172
|
-
* 1.
|
|
173
|
-
* 2.
|
|
174
|
-
* 3.
|
|
175
|
-
* 4.
|
|
176
|
-
* 5.
|
|
177
|
-
* 6.
|
|
178
|
-
* 7.
|
|
179
|
-
* 8.
|
|
180
|
-
* 9. Hono — has `hono` as a dependency
|
|
181
|
-
* 10. Node — has `express`/`koa` or other Node.js frameworks
|
|
182
|
-
* 11. Unknown — none of the above
|
|
165
|
+
* 1. Nuxt — has `nuxt` as a dependency
|
|
166
|
+
* 2. NextJS — has `next` as a dependency
|
|
167
|
+
* 3. Angular — has `@angular/core` as a dependency
|
|
168
|
+
* 4. Svelte — has `svelte` as a dependency
|
|
169
|
+
* 5. Vue — has `vue` as a dependency
|
|
170
|
+
* 6. React — has `react` as a dependency
|
|
171
|
+
* 7. Node — has `express`/`fastify`/`koa`/`nestjs`
|
|
172
|
+
* 8. Unknown — none of the above
|
|
183
173
|
*/
|
|
184
174
|
export function detectProject(startDir: string = process.cwd()): DetectionResult {
|
|
185
175
|
const result = findPackageJson(startDir);
|
|
@@ -242,24 +232,9 @@ export function detectProject(startDir: string = process.cwd()): DetectionResult
|
|
|
242
232
|
return { ...base, type: 'react', label: 'React' };
|
|
243
233
|
}
|
|
244
234
|
|
|
245
|
-
//
|
|
246
|
-
if (hasDep(pkg, '@nestjs/core')) {
|
|
247
|
-
return { ...base, type: '
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Fastify check
|
|
251
|
-
if (hasDep(pkg, 'fastify')) {
|
|
252
|
-
return { ...base, type: 'fastify', label: 'Fastify' };
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Hono check
|
|
256
|
-
if (hasDep(pkg, 'hono')) {
|
|
257
|
-
return { ...base, type: 'hono', label: 'Hono' };
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Generic Node.js backend check (Express, Koa, etc.)
|
|
261
|
-
if (hasDep(pkg, 'express') || hasDep(pkg, 'koa')) {
|
|
262
|
-
return { ...base, type: 'node', label: 'Express (Node.js)' };
|
|
235
|
+
// Node.js backend check
|
|
236
|
+
if (hasDep(pkg, 'express') || hasDep(pkg, 'fastify') || hasDep(pkg, '@nestjs/core') || hasDep(pkg, 'koa')) {
|
|
237
|
+
return { ...base, type: 'node', label: 'Node.js Backend' };
|
|
263
238
|
}
|
|
264
239
|
|
|
265
240
|
// Fallback — additional heuristics
|
|
@@ -279,47 +254,5 @@ export function detectProject(startDir: string = process.cwd()): DetectionResult
|
|
|
279
254
|
return { ...base, type: 'vue', label: 'Vue.js (heuristic)' };
|
|
280
255
|
}
|
|
281
256
|
|
|
282
|
-
// ── Python project detection ─────────────────────────────────────────────
|
|
283
|
-
// Python projects have no package.json; detection is purely filesystem-based.
|
|
284
|
-
|
|
285
|
-
// Django — has manage.py at the root (the canonical Django project marker)
|
|
286
|
-
if (fs.existsSync(path.join(dir, 'manage.py'))) {
|
|
287
|
-
return { ...base, type: 'django', label: 'Django' };
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// FastAPI — check pyproject.toml or requirements.txt for fastapi dependency
|
|
291
|
-
const requirementsFiles = ['requirements.txt', 'requirements-dev.txt', 'requirements/base.txt'];
|
|
292
|
-
const hasFastApi = requirementsFiles.some((f) => {
|
|
293
|
-
const fp = path.join(dir, f);
|
|
294
|
-
if (!fs.existsSync(fp)) return false;
|
|
295
|
-
const content = fs.readFileSync(fp, 'utf-8').toLowerCase();
|
|
296
|
-
return content.includes('fastapi');
|
|
297
|
-
});
|
|
298
|
-
if (hasFastApi) {
|
|
299
|
-
return { ...base, type: 'fastapi', label: 'FastAPI' };
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Flask — check requirements files for flask dependency
|
|
303
|
-
const hasFlask = requirementsFiles.some((f) => {
|
|
304
|
-
const fp = path.join(dir, f);
|
|
305
|
-
if (!fs.existsSync(fp)) return false;
|
|
306
|
-
const content = fs.readFileSync(fp, 'utf-8').toLowerCase();
|
|
307
|
-
return content.includes('flask');
|
|
308
|
-
});
|
|
309
|
-
if (hasFlask) {
|
|
310
|
-
return { ...base, type: 'flask', label: 'Flask' };
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// Generic Python — has pyproject.toml, Pipfile, setup.py, or any .py files at root
|
|
314
|
-
if (
|
|
315
|
-
fs.existsSync(path.join(dir, 'pyproject.toml')) ||
|
|
316
|
-
fs.existsSync(path.join(dir, 'Pipfile')) ||
|
|
317
|
-
fs.existsSync(path.join(dir, 'setup.py')) ||
|
|
318
|
-
fs.existsSync(path.join(dir, 'setup.cfg')) ||
|
|
319
|
-
requirementsFiles.some((f) => fs.existsSync(path.join(dir, f)))
|
|
320
|
-
) {
|
|
321
|
-
return { ...base, type: 'python', label: 'Python' };
|
|
322
|
-
}
|
|
323
|
-
|
|
324
257
|
return { ...base, type: 'unknown', label: 'Unknown Project' };
|
|
325
258
|
}
|
|
@@ -261,11 +261,10 @@ function buildViolationSection(violations: Violation[]): string {
|
|
|
261
261
|
|
|
262
262
|
ruleItems.forEach((v, idx) => {
|
|
263
263
|
const line = v.line !== null ? String(v.line) : '—';
|
|
264
|
-
//
|
|
265
|
-
|
|
266
|
-
let shortMsg = firstLine
|
|
264
|
+
// Truncate long messages for the table, escape pipes
|
|
265
|
+
let shortMsg = v.message
|
|
267
266
|
.replace(/\|/g, '\\|')
|
|
268
|
-
.substring(0,
|
|
267
|
+
.substring(0, 120);
|
|
269
268
|
|
|
270
269
|
// Add quick fix hint if available
|
|
271
270
|
const hint = getQuickFixHint(v.ruleId);
|
|
@@ -278,22 +277,9 @@ function buildViolationSection(violations: Violation[]): string {
|
|
|
278
277
|
|
|
279
278
|
sections.push('');
|
|
280
279
|
|
|
281
|
-
//
|
|
282
|
-
const hasDetailedMessage = ruleItems.some(v => v.message.includes('Why:') || v.message.includes('\n'));
|
|
283
|
-
|
|
284
|
-
if (hasDetailedMessage) {
|
|
285
|
-
sections.push(`<details>`);
|
|
286
|
-
sections.push(`<summary>💡 How to fix (click to expand)</summary>\n`);
|
|
287
|
-
sections.push('```');
|
|
288
|
-
// Get the full message from the first item
|
|
289
|
-
sections.push(ruleItems[0].message);
|
|
290
|
-
sections.push('```');
|
|
291
|
-
sections.push(`\n</details>\n`);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Additional solution example from solution-examples.ts
|
|
280
|
+
// Solution example — shown once per rule
|
|
295
281
|
const solution = getSolutionExample(ruleId);
|
|
296
|
-
if (solution
|
|
282
|
+
if (solution) {
|
|
297
283
|
sections.push(`<details>`);
|
|
298
284
|
sections.push(`<summary>💡 How to fix (click to expand)</summary>\n`);
|
|
299
285
|
sections.push(solution);
|
|
@@ -114,60 +114,6 @@ const frameworkStructures: Record<ProjectType, FolderRule[]> = {
|
|
|
114
114
|
{ path: 'src/__tests__', required: false, description: 'Test files' },
|
|
115
115
|
],
|
|
116
116
|
|
|
117
|
-
nestjs: [
|
|
118
|
-
{ path: 'src', required: true, description: 'Source code root' },
|
|
119
|
-
{ path: 'src/modules', required: false, description: 'Feature modules (each domain gets a module)' },
|
|
120
|
-
{ path: 'src/common', required: false, description: 'Shared utilities across modules' },
|
|
121
|
-
{ path: 'src/common/guards', required: false, description: 'Global authentication/authorization guards' },
|
|
122
|
-
{ path: 'src/common/interceptors', required: false, description: 'Global interceptors (logging, response transform)' },
|
|
123
|
-
{ path: 'src/common/pipes', required: false, description: 'Global validation/transformation pipes' },
|
|
124
|
-
{ path: 'src/common/filters', required: false, description: 'Global exception filters' },
|
|
125
|
-
{ path: 'src/common/decorators', required: false, description: 'Custom parameter/class decorators' },
|
|
126
|
-
{ path: 'src/config', required: false, description: 'App configuration (ConfigModule setup)' },
|
|
127
|
-
{ path: 'test', required: false, description: 'End-to-end tests' },
|
|
128
|
-
],
|
|
129
|
-
|
|
130
|
-
fastify: [
|
|
131
|
-
{ path: 'src', required: true, description: 'Source code root' },
|
|
132
|
-
{ path: 'src/routes', required: false, description: 'Fastify route plugins' },
|
|
133
|
-
{ path: 'src/plugins', required: false, description: 'Shared Fastify plugins (db, auth, etc.)' },
|
|
134
|
-
{ path: 'src/schemas', required: false, description: 'JSON/TypeBox schemas for validation & serialization' },
|
|
135
|
-
{ path: 'src/services', required: false, description: 'Business logic services' },
|
|
136
|
-
{ path: 'src/hooks', required: false, description: 'Shared lifecycle hooks' },
|
|
137
|
-
{ path: 'src/config', required: false, description: 'Configuration and env validation' },
|
|
138
|
-
],
|
|
139
|
-
|
|
140
|
-
hono: [
|
|
141
|
-
{ path: 'src', required: true, description: 'Source code root' },
|
|
142
|
-
{ path: 'src/routes', required: false, description: 'Hono sub-app route files' },
|
|
143
|
-
{ path: 'src/middleware', required: false, description: 'Custom middleware functions' },
|
|
144
|
-
{ path: 'src/handlers', required: false, description: 'Route handler functions' },
|
|
145
|
-
{ path: 'src/schemas', required: false, description: 'Zod schemas for request/response validation' },
|
|
146
|
-
{ path: 'src/config', required: false, description: 'Configuration and env validation' },
|
|
147
|
-
],
|
|
148
|
-
|
|
149
|
-
// Python project structures
|
|
150
|
-
python: [
|
|
151
|
-
{ path: 'src', required: false, description: 'Source code root (or top-level package)' },
|
|
152
|
-
{ path: 'tests', required: false, description: 'Test suite' },
|
|
153
|
-
],
|
|
154
|
-
django: [
|
|
155
|
-
{ path: 'manage.py', required: true, description: 'Django management entry point' },
|
|
156
|
-
{ path: 'tests', required: false, description: 'Test suite' },
|
|
157
|
-
],
|
|
158
|
-
fastapi: [
|
|
159
|
-
{ path: 'app', required: false, description: 'FastAPI application package' },
|
|
160
|
-
{ path: 'app/routers', required: false, description: 'Route modules (APIRouter instances)' },
|
|
161
|
-
{ path: 'app/schemas', required: false, description: 'Pydantic request/response schemas' },
|
|
162
|
-
{ path: 'app/services', required: false, description: 'Business logic layer' },
|
|
163
|
-
{ path: 'tests', required: false, description: 'Test suite' },
|
|
164
|
-
],
|
|
165
|
-
flask: [
|
|
166
|
-
{ path: 'app', required: false, description: 'Flask application package' },
|
|
167
|
-
{ path: 'app/blueprints', required: false, description: 'Flask Blueprint modules' },
|
|
168
|
-
{ path: 'tests', required: false, description: 'Test suite' },
|
|
169
|
-
],
|
|
170
|
-
|
|
171
117
|
unknown: [...baseStructure],
|
|
172
118
|
};
|
|
173
119
|
|
package/config/fastify.config.ts
DELETED
|
@@ -1,326 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ============================================================================
|
|
3
|
-
* fastify.config.ts — Fastify-specific coding rules
|
|
4
|
-
* ============================================================================
|
|
5
|
-
*
|
|
6
|
-
* Registers rules that apply to Fastify projects:
|
|
7
|
-
* - Schema-first validation enforcement
|
|
8
|
-
* - Plugin scope correctness
|
|
9
|
-
* - Async handler patterns
|
|
10
|
-
* - Security: CORS, helmet, rate limiting
|
|
11
|
-
* - Error response shape
|
|
12
|
-
* - Hook lifecycle usage
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { registerRules, Rule } from './guidelines.config';
|
|
16
|
-
|
|
17
|
-
const fastifyRules: Rule[] = [
|
|
18
|
-
|
|
19
|
-
// ── Schema-First Validation ───────────────────────────────────────────────
|
|
20
|
-
|
|
21
|
-
// ─────────────────────────────────────────
|
|
22
|
-
// RULE: fastify-schema-required
|
|
23
|
-
// ROLE: Enforce schema definitions on all routes
|
|
24
|
-
// PURPOSE: Fastify uses JSON Schema / TypeBox for both request validation
|
|
25
|
-
// and response serialization. Without schemas, inputs are not
|
|
26
|
-
// validated and responses include all properties (potential data leak).
|
|
27
|
-
// Schemas also enable Fastify's fast-json-stringify for ~2× serialization speed.
|
|
28
|
-
// EXAMPLE:
|
|
29
|
-
// WRONG:
|
|
30
|
-
// fastify.post('/users', async (req, reply) => {
|
|
31
|
-
// return userService.create(req.body);
|
|
32
|
-
// });
|
|
33
|
-
// RIGHT:
|
|
34
|
-
// fastify.post('/users', {
|
|
35
|
-
// schema: {
|
|
36
|
-
// body: CreateUserSchema,
|
|
37
|
-
// response: { 201: UserResponseSchema },
|
|
38
|
-
// },
|
|
39
|
-
// }, async (req, reply) => {
|
|
40
|
-
// return userService.create(req.body);
|
|
41
|
-
// });
|
|
42
|
-
// ─────────────────────────────────────────
|
|
43
|
-
{
|
|
44
|
-
id: 'fastify-schema-required',
|
|
45
|
-
label: 'Define schema for every route (body, params, response)',
|
|
46
|
-
description:
|
|
47
|
-
'Every Fastify route must define a schema object with at minimum body/params validation and response schemas. This enables validation, serialization speed, and prevents data leaks.',
|
|
48
|
-
severity: 'error',
|
|
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('.route.ts') &&
|
|
57
|
-
!file.relativePath.endsWith('.routes.ts')
|
|
58
|
-
) {
|
|
59
|
-
return [];
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Look for fastify.post/put/patch/delete without a schema key
|
|
63
|
-
const routeRegex = /fastify\.\s*(?:post|put|patch|delete)\s*\(\s*['"`][^'"]+['"`]\s*,\s*async/g;
|
|
64
|
-
let match;
|
|
65
|
-
while ((match = routeRegex.exec(file.content)) !== null) {
|
|
66
|
-
const near = file.content.slice(Math.max(0, match.index - 200), match.index + 50);
|
|
67
|
-
if (!near.includes('schema')) {
|
|
68
|
-
const lineNum = file.content.slice(0, match.index).split('\n').length;
|
|
69
|
-
violations.push({
|
|
70
|
-
line: lineNum,
|
|
71
|
-
message:
|
|
72
|
-
'Mutating route (POST/PUT/PATCH/DELETE) is missing a schema definition. Add body validation schema and response schema.',
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return violations;
|
|
78
|
-
},
|
|
79
|
-
applicableTo: ['fastify'],
|
|
80
|
-
category: 'Validation',
|
|
81
|
-
},
|
|
82
|
-
|
|
83
|
-
// ─────────────────────────────────────────
|
|
84
|
-
// RULE: fastify-response-schema
|
|
85
|
-
// ROLE: Enforce response schema definitions
|
|
86
|
-
// PURPOSE: Without a response schema, Fastify falls back to slow JSON.stringify
|
|
87
|
-
// and may expose internal fields. Response schemas activate fast-json-stringify
|
|
88
|
-
// and act as a serialisation allowlist.
|
|
89
|
-
// EXAMPLE:
|
|
90
|
-
// WRONG:
|
|
91
|
-
// fastify.get('/users/:id', async (req, reply) => { return user; });
|
|
92
|
-
// RIGHT:
|
|
93
|
-
// fastify.get('/users/:id', {
|
|
94
|
-
// schema: { response: { 200: UserResponseSchema } },
|
|
95
|
-
// }, async (req, reply) => { return user; });
|
|
96
|
-
// ─────────────────────────────────────────
|
|
97
|
-
{
|
|
98
|
-
id: 'fastify-response-schema',
|
|
99
|
-
label: 'Always define a response schema',
|
|
100
|
-
description:
|
|
101
|
-
'Define response schemas for all routes to enable fast-json-stringify serialization and prevent accidental exposure of sensitive fields.',
|
|
102
|
-
severity: 'warning',
|
|
103
|
-
fileExtensions: ['ts', 'js'],
|
|
104
|
-
pattern: null,
|
|
105
|
-
customCheck: (file) => {
|
|
106
|
-
const violations: Array<{ line: number | null; message: string }> = [];
|
|
107
|
-
|
|
108
|
-
if (
|
|
109
|
-
!file.relativePath.includes('/routes/') &&
|
|
110
|
-
!file.relativePath.endsWith('.route.ts') &&
|
|
111
|
-
!file.relativePath.endsWith('.routes.ts')
|
|
112
|
-
) {
|
|
113
|
-
return [];
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const hasSchema = /schema\s*:\s*\{/.test(file.content);
|
|
117
|
-
const hasResponseSchema = /response\s*:\s*\{/.test(file.content);
|
|
118
|
-
|
|
119
|
-
if (hasSchema && !hasResponseSchema) {
|
|
120
|
-
violations.push({
|
|
121
|
-
line: null,
|
|
122
|
-
message:
|
|
123
|
-
'Route schema found but no response schema defined. Add response: { 200: Schema } to enable fast serialization and prevent data leaks.',
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return violations;
|
|
128
|
-
},
|
|
129
|
-
applicableTo: ['fastify'],
|
|
130
|
-
category: 'Validation',
|
|
131
|
-
},
|
|
132
|
-
|
|
133
|
-
// ── Plugin Architecture ────────────────────────────────────────────────────
|
|
134
|
-
|
|
135
|
-
// ─────────────────────────────────────────
|
|
136
|
-
// RULE: fastify-use-fastify-plugin
|
|
137
|
-
// ROLE: Enforce plugin scope correctness
|
|
138
|
-
// PURPOSE: In Fastify, plugins registered without fastify-plugin are
|
|
139
|
-
// scoped to a child context — decorations, hooks, and routes
|
|
140
|
-
// added inside won't be visible to siblings or the root instance.
|
|
141
|
-
// Shared utilities (auth, db) must use fastify-plugin to break scope.
|
|
142
|
-
// EXAMPLE:
|
|
143
|
-
// WRONG:
|
|
144
|
-
// export async function authPlugin(fastify: FastifyInstance) {
|
|
145
|
-
// fastify.decorate('verifyToken', verifyToken);
|
|
146
|
-
// }
|
|
147
|
-
// RIGHT:
|
|
148
|
-
// import fp from 'fastify-plugin';
|
|
149
|
-
// export default fp(async function authPlugin(fastify: FastifyInstance) {
|
|
150
|
-
// fastify.decorate('verifyToken', verifyToken);
|
|
151
|
-
// });
|
|
152
|
-
// ─────────────────────────────────────────
|
|
153
|
-
{
|
|
154
|
-
id: 'fastify-use-fastify-plugin',
|
|
155
|
-
label: 'Shared plugins must use fastify-plugin (fp)',
|
|
156
|
-
description:
|
|
157
|
-
'Plugins that add decorations, hooks, or services used by other plugins must be wrapped with fastify-plugin (fp) to share scope with the parent context.',
|
|
158
|
-
severity: 'warning',
|
|
159
|
-
fileExtensions: ['ts', 'js'],
|
|
160
|
-
pattern: null,
|
|
161
|
-
customCheck: (file) => {
|
|
162
|
-
if (
|
|
163
|
-
!file.relativePath.includes('/plugins/') &&
|
|
164
|
-
!file.relativePath.endsWith('.plugin.ts') &&
|
|
165
|
-
!file.relativePath.endsWith('.plugin.js')
|
|
166
|
-
) {
|
|
167
|
-
return [];
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const usesDecorate = /fastify\.decorate\s*\(/.test(file.content);
|
|
171
|
-
const usesFastifyPlugin = /fastify-plugin|fp\s*\(/.test(file.content);
|
|
172
|
-
|
|
173
|
-
if (usesDecorate && !usesFastifyPlugin) {
|
|
174
|
-
return [
|
|
175
|
-
{
|
|
176
|
-
line: null,
|
|
177
|
-
message:
|
|
178
|
-
'Plugin adds decorators but is not wrapped with fastify-plugin (fp). Other plugins/routes will not see these decorations.',
|
|
179
|
-
},
|
|
180
|
-
];
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return [];
|
|
184
|
-
},
|
|
185
|
-
applicableTo: ['fastify'],
|
|
186
|
-
category: 'Architecture',
|
|
187
|
-
},
|
|
188
|
-
|
|
189
|
-
// ── Async Handler Patterns ────────────────────────────────────────────────
|
|
190
|
-
|
|
191
|
-
// ─────────────────────────────────────────
|
|
192
|
-
// RULE: fastify-async-handler
|
|
193
|
-
// ROLE: Enforce async handler pattern
|
|
194
|
-
// PURPOSE: Mixing reply.send() with async return values causes double-send
|
|
195
|
-
// errors in Fastify. Async handlers should return the value;
|
|
196
|
-
// Fastify handles serialization. Use reply.send() OR return, not both.
|
|
197
|
-
// EXAMPLE:
|
|
198
|
-
// WRONG:
|
|
199
|
-
// fastify.get('/users', async (req, reply) => {
|
|
200
|
-
// const users = await userService.findAll();
|
|
201
|
-
// reply.send(users); // + implicit return → double send!
|
|
202
|
-
// });
|
|
203
|
-
// RIGHT:
|
|
204
|
-
// fastify.get('/users', async (req, reply) => {
|
|
205
|
-
// return userService.findAll(); // Fastify handles send automatically
|
|
206
|
-
// });
|
|
207
|
-
// ─────────────────────────────────────────
|
|
208
|
-
{
|
|
209
|
-
id: 'fastify-async-handler',
|
|
210
|
-
label: 'Return values from async handlers — avoid reply.send() + return',
|
|
211
|
-
description:
|
|
212
|
-
'In async Fastify handlers, return the response value directly. Calling reply.send() AND returning a value causes a double-send error.',
|
|
213
|
-
severity: 'error',
|
|
214
|
-
fileExtensions: ['ts', 'js'],
|
|
215
|
-
pattern: null,
|
|
216
|
-
customCheck: (file) => {
|
|
217
|
-
const violations: Array<{ line: number | null; message: string }> = [];
|
|
218
|
-
|
|
219
|
-
if (
|
|
220
|
-
!file.relativePath.includes('/routes/') &&
|
|
221
|
-
!file.relativePath.endsWith('.route.ts')
|
|
222
|
-
) {
|
|
223
|
-
return [];
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
for (let i = 0; i < file.lines.length; i++) {
|
|
227
|
-
if (/reply\.send\s*\(/.test(file.lines[i])) {
|
|
228
|
-
// Check if there's a return after send on same or next line
|
|
229
|
-
const nearby = file.lines.slice(i, i + 3).join(' ');
|
|
230
|
-
if (/reply\.send[^;]*;\s*(?:return|})/.test(nearby)) {
|
|
231
|
-
violations.push({
|
|
232
|
-
line: i + 1,
|
|
233
|
-
message:
|
|
234
|
-
'reply.send() followed by implicit return in async handler. Use: return myValue; instead of reply.send(myValue).',
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
return violations;
|
|
241
|
-
},
|
|
242
|
-
applicableTo: ['fastify'],
|
|
243
|
-
category: 'Async Patterns',
|
|
244
|
-
},
|
|
245
|
-
|
|
246
|
-
// ── Security ──────────────────────────────────────────────────────────────
|
|
247
|
-
|
|
248
|
-
// ─────────────────────────────────────────
|
|
249
|
-
// RULE: fastify-use-helmet
|
|
250
|
-
// ROLE: Enforce security headers
|
|
251
|
-
// PURPOSE: @fastify/helmet sets a baseline of secure HTTP headers.
|
|
252
|
-
// Without it, Fastify serves responses without X-Frame-Options,
|
|
253
|
-
// Content-Security-Policy, or HSTS headers.
|
|
254
|
-
// EXAMPLE:
|
|
255
|
-
// WRONG:
|
|
256
|
-
// const fastify = Fastify();
|
|
257
|
-
// fastify.register(cors);
|
|
258
|
-
// RIGHT:
|
|
259
|
-
// const fastify = Fastify();
|
|
260
|
-
// fastify.register(helmet);
|
|
261
|
-
// fastify.register(cors, { origin: allowedOrigins });
|
|
262
|
-
// ─────────────────────────────────────────
|
|
263
|
-
{
|
|
264
|
-
id: 'fastify-use-helmet',
|
|
265
|
-
label: 'Register @fastify/helmet for security headers',
|
|
266
|
-
description:
|
|
267
|
-
'Register @fastify/helmet to add baseline security headers (HSTS, X-Frame-Options, CSP, etc.) to all responses.',
|
|
268
|
-
severity: 'error',
|
|
269
|
-
fileExtensions: ['ts', 'js'],
|
|
270
|
-
pattern: null,
|
|
271
|
-
customCheck: (file) => {
|
|
272
|
-
if (
|
|
273
|
-
!file.relativePath.endsWith('server.ts') &&
|
|
274
|
-
!file.relativePath.endsWith('app.ts') &&
|
|
275
|
-
!file.relativePath.endsWith('index.ts')
|
|
276
|
-
) {
|
|
277
|
-
return [];
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const hasFastify = /Fastify\s*\(|fastify\s*\(\s*\)/.test(file.content);
|
|
281
|
-
const hasHelmet = /helmet/.test(file.content);
|
|
282
|
-
|
|
283
|
-
if (hasFastify && !hasHelmet) {
|
|
284
|
-
return [
|
|
285
|
-
{
|
|
286
|
-
line: null,
|
|
287
|
-
message:
|
|
288
|
-
'Fastify server missing @fastify/helmet. Register it: fastify.register(import("@fastify/helmet")).',
|
|
289
|
-
},
|
|
290
|
-
];
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
return [];
|
|
294
|
-
},
|
|
295
|
-
applicableTo: ['fastify'],
|
|
296
|
-
category: 'Security',
|
|
297
|
-
},
|
|
298
|
-
|
|
299
|
-
// ─────────────────────────────────────────
|
|
300
|
-
// RULE: fastify-cors-options
|
|
301
|
-
// ROLE: Enforce CORS configuration
|
|
302
|
-
// PURPOSE: Registering @fastify/cors without an origin restriction allows
|
|
303
|
-
// cross-origin requests from any domain, defeating CORS entirely.
|
|
304
|
-
// EXAMPLE:
|
|
305
|
-
// WRONG:
|
|
306
|
-
// fastify.register(cors); // allows all origins
|
|
307
|
-
// RIGHT:
|
|
308
|
-
// fastify.register(cors, { origin: ['https://myapp.com'] });
|
|
309
|
-
// ─────────────────────────────────────────
|
|
310
|
-
{
|
|
311
|
-
id: 'fastify-cors-options',
|
|
312
|
-
label: '@fastify/cors must restrict origin',
|
|
313
|
-
description:
|
|
314
|
-
'Do not register @fastify/cors without an explicit origin allowlist. An unrestricted CORS policy allows any website to make authenticated requests to your API.',
|
|
315
|
-
severity: 'warning',
|
|
316
|
-
fileExtensions: ['ts', 'js'],
|
|
317
|
-
pattern: /register\s*\(\s*cors\s*\)/g,
|
|
318
|
-
applicableTo: ['fastify'],
|
|
319
|
-
category: 'Security',
|
|
320
|
-
},
|
|
321
|
-
];
|
|
322
|
-
|
|
323
|
-
// Register all Fastify-specific rules
|
|
324
|
-
registerRules('fastify', fastifyRules);
|
|
325
|
-
|
|
326
|
-
export { fastifyRules };
|