@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.
@@ -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;AAkehG,wBAAsB,GAAG,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAkJzD"}
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
- if (/export\s+(async\s+)?function\s+\w+/.test(line)) {
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
- if (/auth\s*\(|verifySession|getSession|getCurrentUser|checkAuth|isAuthenticated/.test(line)) {
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
- if (!hasAuthCheck) {
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 .toSorted() or [...arr].sort() for immutability",
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
- // Only check server component pages (app directory with generateMetadata)
422
- if (!filePath.includes("app/") || !content.includes("generateMetadata")) {
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