@empline/preflight 1.1.55 → 1.1.57
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/dist/checks/accessibility/accessibility-validation.d.ts +10 -0
- package/dist/checks/accessibility/accessibility-validation.d.ts.map +1 -0
- package/dist/checks/accessibility/accessibility-validation.js +472 -0
- package/dist/checks/accessibility/accessibility-validation.js.map +1 -0
- package/dist/checks/database/prisma-naming-conventions.d.ts +10 -0
- package/dist/checks/database/prisma-naming-conventions.d.ts.map +1 -0
- package/dist/checks/database/prisma-naming-conventions.js +456 -0
- package/dist/checks/database/prisma-naming-conventions.js.map +1 -0
- package/dist/checks/react/vercel-react-best-practices.d.ts.map +1 -1
- package/dist/checks/react/vercel-react-best-practices.js +60 -8
- package/dist/checks/react/vercel-react-best-practices.js.map +1 -1
- package/dist/checks/seo/seo-validation.d.ts +10 -0
- package/dist/checks/seo/seo-validation.d.ts.map +1 -0
- package/dist/checks/seo/seo-validation.js +497 -0
- package/dist/checks/seo/seo-validation.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.tags = exports.blocking = exports.category = exports.description = exports.name = exports.id = void 0;
|
|
38
|
+
exports.run = run;
|
|
39
|
+
/**
|
|
40
|
+
* Prisma Naming Conventions Preflight (BLOCKING)
|
|
41
|
+
*
|
|
42
|
+
* Enforces consistent naming conventions in Prisma schema:
|
|
43
|
+
*
|
|
44
|
+
* 1. Model Naming:
|
|
45
|
+
* - Must be PascalCase
|
|
46
|
+
* - Must have @@map("snake_case_table") directive
|
|
47
|
+
*
|
|
48
|
+
* 2. Field Naming:
|
|
49
|
+
* - Must be camelCase in Prisma
|
|
50
|
+
* - Scalar fields must have @map("snake_case") directive
|
|
51
|
+
* - Exception: 'id' field (commonly left as-is)
|
|
52
|
+
*
|
|
53
|
+
* 3. Enum Naming:
|
|
54
|
+
* - Enum names must be PascalCase
|
|
55
|
+
* - Enum values must be SCREAMING_SNAKE_CASE
|
|
56
|
+
*
|
|
57
|
+
* This prevents:
|
|
58
|
+
* - Inconsistent DB column naming
|
|
59
|
+
* - Mixed conventions in schema
|
|
60
|
+
* - DB columns that don't follow snake_case standard
|
|
61
|
+
*/
|
|
62
|
+
const fs = __importStar(require("node:fs"));
|
|
63
|
+
const path = __importStar(require("node:path"));
|
|
64
|
+
const console_chars_1 = require("../../utils/console-chars");
|
|
65
|
+
exports.id = "database/prisma-naming-conventions";
|
|
66
|
+
exports.name = "Prisma Naming Conventions";
|
|
67
|
+
exports.description = "Enforces consistent naming conventions in Prisma schema (@map directives, casing)";
|
|
68
|
+
exports.category = "database";
|
|
69
|
+
exports.blocking = true;
|
|
70
|
+
exports.tags = ["database", "prisma", "naming", "conventions", "schema"];
|
|
71
|
+
// Fields that are commonly exempt from @map requirement
|
|
72
|
+
const EXEMPT_FIELDS = new Set([
|
|
73
|
+
"id", // Primary key - often left as 'id' in DB too
|
|
74
|
+
]);
|
|
75
|
+
// Scalar types (non-relation)
|
|
76
|
+
const SCALAR_TYPES = new Set([
|
|
77
|
+
"String",
|
|
78
|
+
"Int",
|
|
79
|
+
"Float",
|
|
80
|
+
"Boolean",
|
|
81
|
+
"DateTime",
|
|
82
|
+
"Json",
|
|
83
|
+
"Decimal",
|
|
84
|
+
"BigInt",
|
|
85
|
+
"Bytes",
|
|
86
|
+
]);
|
|
87
|
+
function isPascalCase(str) {
|
|
88
|
+
return /^[A-Z][a-zA-Z0-9]*$/.test(str);
|
|
89
|
+
}
|
|
90
|
+
function isCamelCase(str) {
|
|
91
|
+
return /^[a-z][a-zA-Z0-9]*$/.test(str);
|
|
92
|
+
}
|
|
93
|
+
function isSnakeCase(str) {
|
|
94
|
+
return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(str);
|
|
95
|
+
}
|
|
96
|
+
function isScreamingSnakeCase(str) {
|
|
97
|
+
return /^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$/.test(str);
|
|
98
|
+
}
|
|
99
|
+
function toSnakeCase(str) {
|
|
100
|
+
return str
|
|
101
|
+
.replace(/([A-Z])/g, "_$1")
|
|
102
|
+
.toLowerCase()
|
|
103
|
+
.replace(/^_/, "");
|
|
104
|
+
}
|
|
105
|
+
function parseSchema(schemaPath) {
|
|
106
|
+
const content = fs.readFileSync(schemaPath, "utf-8");
|
|
107
|
+
const lines = content.split("\n");
|
|
108
|
+
const models = [];
|
|
109
|
+
const enums = [];
|
|
110
|
+
let currentModel = null;
|
|
111
|
+
let currentEnum = null;
|
|
112
|
+
let braceDepth = 0;
|
|
113
|
+
for (let i = 0; i < lines.length; i++) {
|
|
114
|
+
const line = lines[i];
|
|
115
|
+
const trimmed = line.trim();
|
|
116
|
+
const lineNumber = i + 1;
|
|
117
|
+
// Start of model
|
|
118
|
+
const modelMatch = trimmed.match(/^model\s+(\w+)\s*\{/);
|
|
119
|
+
if (modelMatch) {
|
|
120
|
+
currentModel = {
|
|
121
|
+
name: modelMatch[1],
|
|
122
|
+
lineNumber,
|
|
123
|
+
hasMapDirective: false,
|
|
124
|
+
fields: [],
|
|
125
|
+
};
|
|
126
|
+
braceDepth = 1;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
// Start of enum
|
|
130
|
+
const enumMatch = trimmed.match(/^enum\s+(\w+)\s*\{/);
|
|
131
|
+
if (enumMatch) {
|
|
132
|
+
currentEnum = {
|
|
133
|
+
name: enumMatch[1],
|
|
134
|
+
lineNumber,
|
|
135
|
+
values: [],
|
|
136
|
+
};
|
|
137
|
+
braceDepth = 1;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
// Inside a model
|
|
141
|
+
if (currentModel) {
|
|
142
|
+
braceDepth += (line.match(/\{/g) || []).length;
|
|
143
|
+
braceDepth -= (line.match(/\}/g) || []).length;
|
|
144
|
+
// End of model
|
|
145
|
+
if (braceDepth === 0) {
|
|
146
|
+
models.push(currentModel);
|
|
147
|
+
currentModel = null;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
// @@map directive
|
|
151
|
+
const mapMatch = trimmed.match(/@@map\s*\(\s*["']([^"']+)["']\s*\)/);
|
|
152
|
+
if (mapMatch) {
|
|
153
|
+
currentModel.hasMapDirective = true;
|
|
154
|
+
currentModel.mapValue = mapMatch[1];
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
// Skip comments, empty lines, and other directives
|
|
158
|
+
if (trimmed.startsWith("//") ||
|
|
159
|
+
trimmed.startsWith("@@") ||
|
|
160
|
+
trimmed === "" ||
|
|
161
|
+
trimmed === "{") {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
// Parse field
|
|
165
|
+
const fieldMatch = trimmed.match(/^(\w+)\s+(\w+)(\[\])?\??/);
|
|
166
|
+
if (fieldMatch) {
|
|
167
|
+
const fieldName = fieldMatch[1];
|
|
168
|
+
const fieldType = fieldMatch[2];
|
|
169
|
+
const isArray = !!fieldMatch[3];
|
|
170
|
+
// Check for @map directive on field
|
|
171
|
+
const fieldMapMatch = trimmed.match(/@map\s*\(\s*["']([^"']+)["']\s*\)/);
|
|
172
|
+
// Determine if it's a relation
|
|
173
|
+
const isRelation = trimmed.includes("@relation") ||
|
|
174
|
+
(/^[A-Z]/.test(fieldType) && !SCALAR_TYPES.has(fieldType));
|
|
175
|
+
// Check for @id
|
|
176
|
+
const isId = trimmed.includes("@id");
|
|
177
|
+
currentModel.fields.push({
|
|
178
|
+
name: fieldName,
|
|
179
|
+
type: fieldType + (isArray ? "[]" : ""),
|
|
180
|
+
lineNumber,
|
|
181
|
+
isRelation,
|
|
182
|
+
isId,
|
|
183
|
+
hasMapDirective: !!fieldMapMatch,
|
|
184
|
+
mapValue: fieldMapMatch?.[1],
|
|
185
|
+
rawLine: trimmed,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// Inside an enum
|
|
190
|
+
if (currentEnum) {
|
|
191
|
+
braceDepth += (line.match(/\{/g) || []).length;
|
|
192
|
+
braceDepth -= (line.match(/\}/g) || []).length;
|
|
193
|
+
// End of enum
|
|
194
|
+
if (braceDepth === 0) {
|
|
195
|
+
enums.push(currentEnum);
|
|
196
|
+
currentEnum = null;
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
// Skip comments and empty lines
|
|
200
|
+
if (trimmed.startsWith("//") || trimmed === "" || trimmed === "{") {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
// Parse enum value (may have @map)
|
|
204
|
+
const enumValueMatch = trimmed.match(/^(\w+)/);
|
|
205
|
+
if (enumValueMatch) {
|
|
206
|
+
currentEnum.values.push({
|
|
207
|
+
name: enumValueMatch[1],
|
|
208
|
+
lineNumber,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return { models, enums };
|
|
214
|
+
}
|
|
215
|
+
function validateNamingConventions(models, enums) {
|
|
216
|
+
const findings = [];
|
|
217
|
+
// Validate models
|
|
218
|
+
for (const model of models) {
|
|
219
|
+
// Check model name is PascalCase
|
|
220
|
+
if (!isPascalCase(model.name)) {
|
|
221
|
+
findings.push({
|
|
222
|
+
level: "error",
|
|
223
|
+
message: `Model "${model.name}" should be PascalCase`,
|
|
224
|
+
file: "prisma/schema.prisma",
|
|
225
|
+
startLine: model.lineNumber,
|
|
226
|
+
ruleId: "model-pascal-case",
|
|
227
|
+
suggestion: `Rename to "${model.name.charAt(0).toUpperCase() + model.name.slice(1)}"`,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
// Check model has @@map directive
|
|
231
|
+
if (!model.hasMapDirective) {
|
|
232
|
+
const expectedTableName = toSnakeCase(model.name);
|
|
233
|
+
findings.push({
|
|
234
|
+
level: "error",
|
|
235
|
+
message: `Model "${model.name}" missing @@map directive`,
|
|
236
|
+
file: "prisma/schema.prisma",
|
|
237
|
+
startLine: model.lineNumber,
|
|
238
|
+
ruleId: "model-map-required",
|
|
239
|
+
suggestion: `Add: @@map("${expectedTableName}")`,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
else if (model.mapValue && !isSnakeCase(model.mapValue)) {
|
|
243
|
+
findings.push({
|
|
244
|
+
level: "warning",
|
|
245
|
+
message: `Model "${model.name}" @@map value "${model.mapValue}" should be snake_case`,
|
|
246
|
+
file: "prisma/schema.prisma",
|
|
247
|
+
startLine: model.lineNumber,
|
|
248
|
+
ruleId: "model-map-snake-case",
|
|
249
|
+
suggestion: `Use: @@map("${toSnakeCase(model.name)}")`,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
// Validate fields
|
|
253
|
+
for (const field of model.fields) {
|
|
254
|
+
// Skip relations - they don't need @map
|
|
255
|
+
if (field.isRelation) {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
// Skip exempt fields (like 'id')
|
|
259
|
+
if (EXEMPT_FIELDS.has(field.name)) {
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
// Check field name is camelCase
|
|
263
|
+
if (!isCamelCase(field.name) && !EXEMPT_FIELDS.has(field.name)) {
|
|
264
|
+
findings.push({
|
|
265
|
+
level: "warning",
|
|
266
|
+
message: `Field "${model.name}.${field.name}" should be camelCase`,
|
|
267
|
+
file: "prisma/schema.prisma",
|
|
268
|
+
startLine: field.lineNumber,
|
|
269
|
+
ruleId: "field-camel-case",
|
|
270
|
+
suggestion: `Consider renaming to camelCase`,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
// Check scalar fields have @map directive
|
|
274
|
+
if (!field.hasMapDirective) {
|
|
275
|
+
const expectedColumnName = toSnakeCase(field.name);
|
|
276
|
+
// Only require @map if the snake_case version differs from the field name
|
|
277
|
+
if (expectedColumnName !== field.name) {
|
|
278
|
+
findings.push({
|
|
279
|
+
level: "error",
|
|
280
|
+
message: `Field "${model.name}.${field.name}" missing @map directive`,
|
|
281
|
+
file: "prisma/schema.prisma",
|
|
282
|
+
startLine: field.lineNumber,
|
|
283
|
+
ruleId: "field-map-required",
|
|
284
|
+
suggestion: `Add: @map("${expectedColumnName}")`,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
else if (field.mapValue && !isSnakeCase(field.mapValue)) {
|
|
289
|
+
findings.push({
|
|
290
|
+
level: "warning",
|
|
291
|
+
message: `Field "${model.name}.${field.name}" @map value "${field.mapValue}" should be snake_case`,
|
|
292
|
+
file: "prisma/schema.prisma",
|
|
293
|
+
startLine: field.lineNumber,
|
|
294
|
+
ruleId: "field-map-snake-case",
|
|
295
|
+
suggestion: `Use: @map("${toSnakeCase(field.name)}")`,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
// Validate enums
|
|
301
|
+
for (const enumDef of enums) {
|
|
302
|
+
// Check enum name is PascalCase
|
|
303
|
+
if (!isPascalCase(enumDef.name)) {
|
|
304
|
+
findings.push({
|
|
305
|
+
level: "warning",
|
|
306
|
+
message: `Enum "${enumDef.name}" should be PascalCase`,
|
|
307
|
+
file: "prisma/schema.prisma",
|
|
308
|
+
startLine: enumDef.lineNumber,
|
|
309
|
+
ruleId: "enum-pascal-case",
|
|
310
|
+
suggestion: `Rename to PascalCase`,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
// Check enum values are SCREAMING_SNAKE_CASE
|
|
314
|
+
for (const value of enumDef.values) {
|
|
315
|
+
if (!isScreamingSnakeCase(value.name)) {
|
|
316
|
+
findings.push({
|
|
317
|
+
level: "warning",
|
|
318
|
+
message: `Enum value "${enumDef.name}.${value.name}" should be SCREAMING_SNAKE_CASE`,
|
|
319
|
+
file: "prisma/schema.prisma",
|
|
320
|
+
startLine: value.lineNumber,
|
|
321
|
+
ruleId: "enum-value-screaming-snake-case",
|
|
322
|
+
suggestion: `Rename to "${value.name.toUpperCase().replace(/([a-z])([A-Z])/g, "$1_$2")}"`,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return findings;
|
|
328
|
+
}
|
|
329
|
+
async function run() {
|
|
330
|
+
const startTime = Date.now();
|
|
331
|
+
const schemaPath = path.join(process.cwd(), "prisma", "schema.prisma");
|
|
332
|
+
if (!fs.existsSync(schemaPath)) {
|
|
333
|
+
return {
|
|
334
|
+
passed: true,
|
|
335
|
+
findings: [{
|
|
336
|
+
level: "info",
|
|
337
|
+
message: "No Prisma schema found - skipping naming convention check",
|
|
338
|
+
ruleId: "no-schema",
|
|
339
|
+
}],
|
|
340
|
+
duration: Date.now() - startTime,
|
|
341
|
+
metadata: { skipped: true },
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
const { models, enums } = parseSchema(schemaPath);
|
|
345
|
+
const findings = validateNamingConventions(models, enums);
|
|
346
|
+
const errors = findings.filter(f => f.level === "error");
|
|
347
|
+
const warnings = findings.filter(f => f.level === "warning");
|
|
348
|
+
return {
|
|
349
|
+
passed: errors.length === 0,
|
|
350
|
+
findings,
|
|
351
|
+
duration: Date.now() - startTime,
|
|
352
|
+
metadata: {
|
|
353
|
+
modelsChecked: models.length,
|
|
354
|
+
enumsChecked: enums.length,
|
|
355
|
+
fieldsChecked: models.reduce((sum, m) => sum + m.fields.length, 0),
|
|
356
|
+
errors: errors.length,
|
|
357
|
+
warnings: warnings.length,
|
|
358
|
+
},
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
async function main() {
|
|
362
|
+
console.log(`\n${console_chars_1.emoji.database} PRISMA NAMING CONVENTIONS`);
|
|
363
|
+
console.log((0, console_chars_1.createDivider)(65, "heavy"));
|
|
364
|
+
const result = await run();
|
|
365
|
+
const { modelsChecked, enumsChecked, fieldsChecked, errors, warnings } = result.metadata || {};
|
|
366
|
+
if (result.metadata?.skipped) {
|
|
367
|
+
console.log(`\n${console_chars_1.emoji.info} No Prisma schema found - skipping`);
|
|
368
|
+
process.exit(0);
|
|
369
|
+
}
|
|
370
|
+
console.log(`\n${console_chars_1.emoji.search} Checking naming conventions...`);
|
|
371
|
+
console.log(` Models: ${modelsChecked}`);
|
|
372
|
+
console.log(` Enums: ${enumsChecked}`);
|
|
373
|
+
console.log(` Fields: ${fieldsChecked}`);
|
|
374
|
+
console.log(`\n${console_chars_1.emoji.chart} Summary:`);
|
|
375
|
+
console.log(` Errors: ${errors}`);
|
|
376
|
+
console.log(` Warnings: ${warnings}`);
|
|
377
|
+
if (result.passed && warnings === 0) {
|
|
378
|
+
console.log(`\n${console_chars_1.emoji.success} PRISMA NAMING CONVENTIONS PASSED`);
|
|
379
|
+
console.log(`\nAll models and fields follow naming conventions.`);
|
|
380
|
+
process.exit(0);
|
|
381
|
+
}
|
|
382
|
+
// Group findings by rule
|
|
383
|
+
const findingsByRule = new Map();
|
|
384
|
+
for (const finding of result.findings) {
|
|
385
|
+
const ruleId = finding.ruleId || "unknown";
|
|
386
|
+
if (!findingsByRule.has(ruleId)) {
|
|
387
|
+
findingsByRule.set(ruleId, []);
|
|
388
|
+
}
|
|
389
|
+
findingsByRule.get(ruleId).push(finding);
|
|
390
|
+
}
|
|
391
|
+
// Print errors first
|
|
392
|
+
const errorFindings = result.findings.filter(f => f.level === "error");
|
|
393
|
+
if (errorFindings.length > 0) {
|
|
394
|
+
console.log(`\n${console_chars_1.emoji.error} Errors (blocking):`);
|
|
395
|
+
// Group by rule for cleaner output
|
|
396
|
+
const errorsByRule = new Map();
|
|
397
|
+
for (const finding of errorFindings) {
|
|
398
|
+
const ruleId = finding.ruleId || "unknown";
|
|
399
|
+
if (!errorsByRule.has(ruleId)) {
|
|
400
|
+
errorsByRule.set(ruleId, []);
|
|
401
|
+
}
|
|
402
|
+
errorsByRule.get(ruleId).push(finding);
|
|
403
|
+
}
|
|
404
|
+
for (const [ruleId, ruleFindings] of errorsByRule) {
|
|
405
|
+
console.log(`\n ${ruleId} (${ruleFindings.length}):`);
|
|
406
|
+
for (const finding of ruleFindings.slice(0, 10)) {
|
|
407
|
+
console.log(` Line ${finding.startLine}: ${finding.message}`);
|
|
408
|
+
if (finding.suggestion) {
|
|
409
|
+
console.log(` ${console_chars_1.emoji.hint} ${finding.suggestion}`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
if (ruleFindings.length > 10) {
|
|
413
|
+
console.log(` ... and ${ruleFindings.length - 10} more`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
// Print warnings
|
|
418
|
+
const warningFindings = result.findings.filter(f => f.level === "warning");
|
|
419
|
+
if (warningFindings.length > 0) {
|
|
420
|
+
console.log(`\n${console_chars_1.emoji.warning} Warnings:`);
|
|
421
|
+
const warningsByRule = new Map();
|
|
422
|
+
for (const finding of warningFindings) {
|
|
423
|
+
const ruleId = finding.ruleId || "unknown";
|
|
424
|
+
if (!warningsByRule.has(ruleId)) {
|
|
425
|
+
warningsByRule.set(ruleId, []);
|
|
426
|
+
}
|
|
427
|
+
warningsByRule.get(ruleId).push(finding);
|
|
428
|
+
}
|
|
429
|
+
for (const [ruleId, ruleFindings] of warningsByRule) {
|
|
430
|
+
console.log(`\n ${ruleId} (${ruleFindings.length}):`);
|
|
431
|
+
for (const finding of ruleFindings.slice(0, 5)) {
|
|
432
|
+
console.log(` Line ${finding.startLine}: ${finding.message}`);
|
|
433
|
+
}
|
|
434
|
+
if (ruleFindings.length > 5) {
|
|
435
|
+
console.log(` ... and ${ruleFindings.length - 5} more`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
console.log(`\n${console_chars_1.emoji.info} Naming Convention Rules:`);
|
|
440
|
+
console.log(` Models: PascalCase + @@map("snake_case")`);
|
|
441
|
+
console.log(` Fields: camelCase + @map("snake_case")`);
|
|
442
|
+
console.log(` Enums: PascalCase names, SCREAMING_SNAKE_CASE values`);
|
|
443
|
+
if (!result.passed) {
|
|
444
|
+
console.log(`\n${console_chars_1.emoji.error} PRISMA NAMING CONVENTIONS FAILED`);
|
|
445
|
+
process.exit(1);
|
|
446
|
+
}
|
|
447
|
+
console.log(`\n${console_chars_1.emoji.warning} PRISMA NAMING CONVENTIONS PASSED WITH WARNINGS`);
|
|
448
|
+
process.exit(0);
|
|
449
|
+
}
|
|
450
|
+
if (require.main === module) {
|
|
451
|
+
main().catch((err) => {
|
|
452
|
+
console.error(`${console_chars_1.emoji.error} Preflight failed:`, err);
|
|
453
|
+
process.exit(1);
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
//# sourceMappingURL=prisma-naming-conventions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prisma-naming-conventions.js","sourceRoot":"","sources":["../../../src/checks/database/prisma-naming-conventions.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vercel-react-best-practices.d.ts","sourceRoot":"","sources":["../../../src/checks/react/vercel-react-best-practices.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;GAaG;AAQH,UAAU,oBAAoB;IAC5B,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,UAAU,gBAAgB;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAcD,eAAO,MAAM,EAAE,gCAAgC,CAAC;AAChD,eAAO,MAAM,IAAI,gCAAgC,CAAC;AAClD,eAAO,MAAM,QAAQ,UAAU,CAAC;AAChC,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAC9B,eAAO,MAAM,WAAW,yEAAyE,CAAC;AAClG,eAAO,MAAM,IAAI,UAA8E,CAAC;
|
|
1
|
+
{"version":3,"file":"vercel-react-best-practices.d.ts","sourceRoot":"","sources":["../../../src/checks/react/vercel-react-best-practices.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;GAaG;AAQH,UAAU,oBAAoB;IAC5B,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,UAAU,gBAAgB;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAcD,eAAO,MAAM,EAAE,gCAAgC,CAAC;AAChD,eAAO,MAAM,IAAI,gCAAgC,CAAC;AAClD,eAAO,MAAM,QAAQ,UAAU,CAAC;AAChC,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAC9B,eAAO,MAAM,WAAW,yEAAyE,CAAC;AAClG,eAAO,MAAM,IAAI,UAA8E,CAAC;AAiiBhG,wBAAsB,GAAG,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAkJzD"}
|
|
@@ -208,42 +208,80 @@ function detectSequentialAwaits(content, filePath) {
|
|
|
208
208
|
*/
|
|
209
209
|
function detectUnauthenticatedServerActions(content, filePath) {
|
|
210
210
|
const findings = [];
|
|
211
|
+
// Normalize path for cross-platform
|
|
212
|
+
const normalizedPath = filePath.replace(/\\/g, "/").toLowerCase();
|
|
211
213
|
// Only check files with "use server"
|
|
212
214
|
if (!content.includes('"use server"') && !content.includes("'use server'")) {
|
|
213
215
|
return findings;
|
|
214
216
|
}
|
|
217
|
+
// Skip intentionally public action files
|
|
218
|
+
const publicPathPatterns = [
|
|
219
|
+
"/login/",
|
|
220
|
+
"/register/",
|
|
221
|
+
"/signup/",
|
|
222
|
+
"/reset-password/",
|
|
223
|
+
"/forgot-password/",
|
|
224
|
+
"/verify-email/",
|
|
225
|
+
"/auth/",
|
|
226
|
+
"/public/",
|
|
227
|
+
];
|
|
228
|
+
if (publicPathPatterns.some((pattern) => normalizedPath.includes(pattern))) {
|
|
229
|
+
return findings;
|
|
230
|
+
}
|
|
215
231
|
const lines = content.split("\n");
|
|
216
232
|
let inServerAction = false;
|
|
217
233
|
let actionStartLine = 0;
|
|
234
|
+
let actionName = "";
|
|
218
235
|
let hasAuthCheck = false;
|
|
219
236
|
let braceDepth = 0;
|
|
220
237
|
for (let i = 0; i < lines.length; i++) {
|
|
221
238
|
const line = lines[i];
|
|
222
239
|
// Detect function start after "use server"
|
|
223
|
-
|
|
240
|
+
const funcMatch = /export\s+(async\s+)?function\s+(\w+)/.exec(line);
|
|
241
|
+
if (funcMatch) {
|
|
224
242
|
inServerAction = true;
|
|
225
243
|
actionStartLine = i + 1;
|
|
244
|
+
actionName = funcMatch[2] || "";
|
|
226
245
|
hasAuthCheck = false;
|
|
227
246
|
braceDepth = 0;
|
|
247
|
+
// Check for public-action comment on the line(s) before the function
|
|
248
|
+
for (let j = i - 1; j >= Math.max(0, i - 3); j--) {
|
|
249
|
+
const prevLine = lines[j] || "";
|
|
250
|
+
if (/\/\/\s*public-action|\/\/\s*no-auth-required|@public/.test(prevLine)) {
|
|
251
|
+
hasAuthCheck = true;
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
// Stop if we hit another function or non-comment line
|
|
255
|
+
if (prevLine.trim() && !prevLine.trim().startsWith("//") && !prevLine.trim().startsWith("*") && !prevLine.trim().startsWith("/*")) {
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
228
259
|
}
|
|
229
260
|
if (inServerAction) {
|
|
230
261
|
// Count braces to track function scope
|
|
231
262
|
braceDepth += (line.match(/{/g) || []).length;
|
|
232
263
|
braceDepth -= (line.match(/}/g) || []).length;
|
|
233
|
-
// Check for auth patterns
|
|
234
|
-
|
|
264
|
+
// Check for auth patterns (extended list)
|
|
265
|
+
// Matches: auth(), await auth(), session = await auth(), getSession, etc.
|
|
266
|
+
if (/(?:await\s+)?auth\s*\(\s*\)|verifySession|getSession|getCurrentUser|checkAuth|isAuthenticated|requireAuth|getServerSession|validateSession|session\s*=\s*await/.test(line)) {
|
|
267
|
+
hasAuthCheck = true;
|
|
268
|
+
}
|
|
269
|
+
// Check for public action markers (comment-based opt-out)
|
|
270
|
+
if (/\/\/\s*public-action|\/\/\s*no-auth-required|@public/.test(line)) {
|
|
235
271
|
hasAuthCheck = true;
|
|
236
272
|
}
|
|
237
273
|
// Function ended
|
|
238
274
|
if (braceDepth <= 0 && line.includes("}")) {
|
|
239
|
-
|
|
275
|
+
// Skip functions with "public" or "guest" in the name
|
|
276
|
+
const isPublicAction = /public|guest|anonymous|unauth/i.test(actionName);
|
|
277
|
+
if (!hasAuthCheck && !isPublicAction) {
|
|
240
278
|
findings.push({
|
|
241
279
|
file: filePath,
|
|
242
280
|
line: actionStartLine,
|
|
243
281
|
rule: "server-auth",
|
|
244
282
|
impact: "CRITICAL",
|
|
245
283
|
message: "Server Action without authentication check",
|
|
246
|
-
suggestion: "Add auth check: const session = await verifySession(); if (!session) throw unauthorized()",
|
|
284
|
+
suggestion: "Add auth check: const session = await verifySession(); if (!session) throw unauthorized(). Or add '// public-action' comment if intentionally public.",
|
|
247
285
|
});
|
|
248
286
|
}
|
|
249
287
|
inServerAction = false;
|
|
@@ -328,6 +366,16 @@ function detectEagerStateInit(content, filePath) {
|
|
|
328
366
|
*/
|
|
329
367
|
function detectSortMutations(content, filePath) {
|
|
330
368
|
const findings = [];
|
|
369
|
+
// Normalize path for cross-platform
|
|
370
|
+
const normalizedPath = filePath.replace(/\\/g, "/").toLowerCase();
|
|
371
|
+
// Skip server-side code where .sort() on local arrays is fine
|
|
372
|
+
if (normalizedPath.includes("/api/") ||
|
|
373
|
+
normalizedPath.includes("/actions/") ||
|
|
374
|
+
normalizedPath.includes("route.ts") ||
|
|
375
|
+
content.includes('"use server"') ||
|
|
376
|
+
content.includes("'use server'")) {
|
|
377
|
+
return findings;
|
|
378
|
+
}
|
|
331
379
|
const lines = content.split("\n");
|
|
332
380
|
for (let i = 0; i < lines.length; i++) {
|
|
333
381
|
const line = lines[i];
|
|
@@ -341,7 +389,7 @@ function detectSortMutations(content, filePath) {
|
|
|
341
389
|
rule: "js-tosorted",
|
|
342
390
|
impact: "MEDIUM",
|
|
343
391
|
message: ".sort() mutates array in place, may corrupt React state/props",
|
|
344
|
-
suggestion: "Use .
|
|
392
|
+
suggestion: "Use [...arr].sort() to avoid mutation (or .slice().sort())",
|
|
345
393
|
});
|
|
346
394
|
}
|
|
347
395
|
}
|
|
@@ -418,8 +466,12 @@ function detectEagerAnalytics(content, filePath) {
|
|
|
418
466
|
*/
|
|
419
467
|
function detectMissingReactCache(content, filePath) {
|
|
420
468
|
const findings = [];
|
|
421
|
-
//
|
|
422
|
-
|
|
469
|
+
// Normalize path separators for cross-platform support
|
|
470
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
471
|
+
// Only check server component pages (app directory with exported generateMetadata function)
|
|
472
|
+
// Must have "export async function generateMetadata" or "export function generateMetadata"
|
|
473
|
+
const hasExportedGenerateMetadata = /export\s+(async\s+)?function\s+generateMetadata/.test(content);
|
|
474
|
+
if (!normalizedPath.includes("app/") || !hasExportedGenerateMetadata) {
|
|
423
475
|
return findings;
|
|
424
476
|
}
|
|
425
477
|
// Skip if React.cache is already imported/used
|