@webpieces/code-rules 0.0.1 → 0.2.113

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 (63) hide show
  1. package/package.json +4 -3
  2. package/src/cli.d.ts +1 -0
  3. package/src/cli.js +19 -0
  4. package/src/cli.js.map +1 -0
  5. package/src/diff-utils.d.ts +24 -0
  6. package/src/{diff-utils.ts → diff-utils.js} +30 -38
  7. package/src/diff-utils.js.map +1 -0
  8. package/src/from-shared-config.d.ts +28 -0
  9. package/src/from-shared-config.js +119 -0
  10. package/src/from-shared-config.js.map +1 -0
  11. package/src/index.js +33 -0
  12. package/src/index.js.map +1 -0
  13. package/src/validate-catch-error-pattern.d.ts +47 -0
  14. package/src/{validate-catch-error-pattern.ts → validate-catch-error-pattern.js} +74 -195
  15. package/src/validate-catch-error-pattern.js.map +1 -0
  16. package/src/validate-code.d.ts +98 -0
  17. package/src/{validate-code.ts → validate-code.js} +65 -259
  18. package/src/validate-code.js.map +1 -0
  19. package/src/validate-dtos.d.ts +41 -0
  20. package/src/{validate-dtos.ts → validate-dtos.js} +88 -215
  21. package/src/validate-dtos.js.map +1 -0
  22. package/src/validate-modified-files.d.ts +24 -0
  23. package/src/{validate-modified-files.ts → validate-modified-files.js} +46 -115
  24. package/src/validate-modified-files.js.map +1 -0
  25. package/src/validate-modified-methods.d.ts +30 -0
  26. package/src/{validate-modified-methods.ts → validate-modified-methods.js} +94 -196
  27. package/src/validate-modified-methods.js.map +1 -0
  28. package/src/validate-new-methods.d.ts +27 -0
  29. package/src/{validate-new-methods.ts → validate-new-methods.js} +63 -133
  30. package/src/validate-new-methods.js.map +1 -0
  31. package/src/validate-no-any-unknown.d.ts +41 -0
  32. package/src/{validate-no-any-unknown.ts → validate-no-any-unknown.js} +69 -146
  33. package/src/validate-no-any-unknown.js.map +1 -0
  34. package/src/validate-no-destructure.d.ts +51 -0
  35. package/src/{validate-no-destructure.ts → validate-no-destructure.js} +80 -166
  36. package/src/validate-no-destructure.js.map +1 -0
  37. package/src/validate-no-direct-api-resolver.d.ts +46 -0
  38. package/src/{validate-no-direct-api-resolver.ts → validate-no-direct-api-resolver.js} +112 -211
  39. package/src/validate-no-direct-api-resolver.js.map +1 -0
  40. package/src/validate-no-implicit-any.d.ts +36 -0
  41. package/src/{validate-no-implicit-any.ts → validate-no-implicit-any.js} +94 -141
  42. package/src/validate-no-implicit-any.js.map +1 -0
  43. package/src/validate-no-inline-types.d.ts +90 -0
  44. package/src/{validate-no-inline-types.ts → validate-no-inline-types.js} +93 -198
  45. package/src/validate-no-inline-types.js.map +1 -0
  46. package/src/validate-no-unmanaged-exceptions.d.ts +43 -0
  47. package/src/{validate-no-unmanaged-exceptions.ts → validate-no-unmanaged-exceptions.js} +71 -140
  48. package/src/validate-no-unmanaged-exceptions.js.map +1 -0
  49. package/src/validate-prisma-converters.d.ts +59 -0
  50. package/src/{validate-prisma-converters.ts → validate-prisma-converters.js} +120 -307
  51. package/src/validate-prisma-converters.js.map +1 -0
  52. package/src/validate-return-types.d.ts +28 -0
  53. package/src/{validate-return-types.ts → validate-return-types.js} +84 -168
  54. package/src/validate-return-types.js.map +1 -0
  55. package/LICENSE +0 -373
  56. package/jest.config.ts +0 -20
  57. package/project.json +0 -22
  58. package/src/cli.ts +0 -17
  59. package/src/from-shared-config.ts +0 -118
  60. package/tsconfig.json +0 -22
  61. package/tsconfig.lib.json +0 -10
  62. package/tsconfig.spec.json +0 -14
  63. /package/src/{index.ts → index.d.ts} +0 -0
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  /**
2
3
  * Validate DTOs Executor
3
4
  *
@@ -27,99 +28,57 @@
27
28
  * - Dto fields must be a subset of Dbo fields
28
29
  * - Extra Dbo fields are allowed (e.g., password)
29
30
  */
30
-
31
- import { execSync } from 'child_process';
32
- import * as fs from 'fs';
33
- import * as path from 'path';
34
- import * as ts from 'typescript';
35
-
36
- export type ValidateDtosMode = 'OFF' | 'MODIFIED_CLASS' | 'MODIFIED_FILES';
37
-
38
- export interface ValidateDtosOptions {
39
- mode?: ValidateDtosMode;
40
- disableAllowed?: boolean;
41
- prismaSchemaPath?: string;
42
- dtoSourcePaths?: string[];
43
- ignoreModifiedUntilEpoch?: number;
44
- }
45
-
46
- export interface ExecutorResult {
47
- success: boolean;
48
- }
49
-
50
- interface DtoFieldInfo {
51
- name: string;
52
- line: number;
53
- deprecated: boolean;
54
- }
55
-
56
- interface DtoInfo {
57
- name: string;
58
- file: string;
59
- startLine: number;
60
- endLine: number;
61
- fields: DtoFieldInfo[];
62
- }
63
-
64
- interface DtoViolation {
65
- file: string;
66
- line: number;
67
- dtoName: string;
68
- fieldName: string;
69
- dboName: string;
70
- availableFields: string[];
71
- }
72
-
73
- interface DboEntry {
74
- name: string;
75
- fields: Set<string>;
76
- }
77
-
31
+ Object.defineProperty(exports, "__esModule", { value: true });
32
+ exports.default = runValidator;
33
+ const tslib_1 = require("tslib");
34
+ const child_process_1 = require("child_process");
35
+ const fs = tslib_1.__importStar(require("fs"));
36
+ const path = tslib_1.__importStar(require("path"));
37
+ const ts = tslib_1.__importStar(require("typescript"));
78
38
  /**
79
39
  * Auto-detect the base branch by finding the merge-base with origin/main.
80
40
  */
81
- function detectBase(workspaceRoot: string): string | null {
41
+ function detectBase(workspaceRoot) {
82
42
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
83
43
  try {
84
- const mergeBase = execSync('git merge-base HEAD origin/main', {
44
+ const mergeBase = (0, child_process_1.execSync)('git merge-base HEAD origin/main', {
85
45
  cwd: workspaceRoot,
86
46
  encoding: 'utf-8',
87
47
  stdio: ['pipe', 'pipe', 'pipe'],
88
48
  }).trim();
89
-
90
49
  if (mergeBase) {
91
50
  return mergeBase;
92
51
  }
93
- } catch (err: unknown) {
52
+ }
53
+ catch (err) {
94
54
  //const error = toError(err);
95
55
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
96
56
  try {
97
- const mergeBase = execSync('git merge-base HEAD main', {
57
+ const mergeBase = (0, child_process_1.execSync)('git merge-base HEAD main', {
98
58
  cwd: workspaceRoot,
99
59
  encoding: 'utf-8',
100
60
  stdio: ['pipe', 'pipe', 'pipe'],
101
61
  }).trim();
102
-
103
62
  if (mergeBase) {
104
63
  return mergeBase;
105
64
  }
106
- } catch (err2: unknown) {
65
+ }
66
+ catch (err2) {
107
67
  //const error2 = toError(err2);
108
68
  // Ignore
109
69
  }
110
70
  }
111
71
  return null;
112
72
  }
113
-
114
73
  /**
115
74
  * Get changed files between base and head (or working tree if head not specified).
116
75
  */
117
76
  // webpieces-disable max-lines-new-methods -- Git command handling with untracked files requires multiple code paths
118
- function getChangedFiles(workspaceRoot: string, base: string, head?: string): string[] {
77
+ function getChangedFiles(workspaceRoot, base, head) {
119
78
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
120
79
  try {
121
80
  const diffTarget = head ? `${base} ${head}` : base;
122
- const output = execSync(`git diff --name-only ${diffTarget}`, {
81
+ const output = (0, child_process_1.execSync)(`git diff --name-only ${diffTarget}`, {
123
82
  cwd: workspaceRoot,
124
83
  encoding: 'utf-8',
125
84
  });
@@ -127,11 +86,10 @@ function getChangedFiles(workspaceRoot: string, base: string, head?: string): st
127
86
  .trim()
128
87
  .split('\n')
129
88
  .filter((f) => f.length > 0);
130
-
131
89
  if (!head) {
132
90
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
133
91
  try {
134
- const untrackedOutput = execSync('git ls-files --others --exclude-standard', {
92
+ const untrackedOutput = (0, child_process_1.execSync)('git ls-files --others --exclude-standard', {
135
93
  cwd: workspaceRoot,
136
94
  encoding: 'utf-8',
137
95
  });
@@ -141,39 +99,37 @@ function getChangedFiles(workspaceRoot: string, base: string, head?: string): st
141
99
  .filter((f) => f.length > 0);
142
100
  const allFiles = new Set([...changedFiles, ...untrackedFiles]);
143
101
  return Array.from(allFiles);
144
- } catch (err: unknown) {
102
+ }
103
+ catch (err) {
145
104
  //const error = toError(err);
146
105
  return changedFiles;
147
106
  }
148
107
  }
149
-
150
108
  return changedFiles;
151
- } catch (err: unknown) {
109
+ }
110
+ catch (err) {
152
111
  //const error = toError(err);
153
112
  return [];
154
113
  }
155
114
  }
156
-
157
115
  /**
158
116
  * Get the diff content for a specific file.
159
117
  */
160
- function getFileDiff(workspaceRoot: string, file: string, base: string, head?: string): string {
118
+ function getFileDiff(workspaceRoot, file, base, head) {
161
119
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
162
120
  try {
163
121
  const diffTarget = head ? `${base} ${head}` : base;
164
- const diff = execSync(`git diff ${diffTarget} -- "${file}"`, {
122
+ const diff = (0, child_process_1.execSync)(`git diff ${diffTarget} -- "${file}"`, {
165
123
  cwd: workspaceRoot,
166
124
  encoding: 'utf-8',
167
125
  });
168
-
169
126
  if (!diff && !head) {
170
127
  const fullPath = path.join(workspaceRoot, file);
171
128
  if (fs.existsSync(fullPath)) {
172
- const isUntracked = execSync(`git ls-files --others --exclude-standard "${file}"`, {
129
+ const isUntracked = (0, child_process_1.execSync)(`git ls-files --others --exclude-standard "${file}"`, {
173
130
  cwd: workspaceRoot,
174
131
  encoding: 'utf-8',
175
132
  }).trim();
176
-
177
133
  if (isUntracked) {
178
134
  const content = fs.readFileSync(fullPath, 'utf-8');
179
135
  const lines = content.split('\n');
@@ -181,94 +137,82 @@ function getFileDiff(workspaceRoot: string, file: string, base: string, head?: s
181
137
  }
182
138
  }
183
139
  }
184
-
185
140
  return diff;
186
- } catch (err: unknown) {
141
+ }
142
+ catch (err) {
187
143
  //const error = toError(err);
188
144
  return '';
189
145
  }
190
146
  }
191
-
192
147
  /**
193
148
  * Parse diff to extract changed line numbers (additions only - lines starting with +).
194
149
  */
195
- function getChangedLineNumbers(diffContent: string): Set<number> {
196
- const changedLines = new Set<number>();
150
+ function getChangedLineNumbers(diffContent) {
151
+ const changedLines = new Set();
197
152
  const lines = diffContent.split('\n');
198
153
  let currentLine = 0;
199
-
200
154
  for (const line of lines) {
201
155
  const hunkMatch = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
202
156
  if (hunkMatch) {
203
157
  currentLine = parseInt(hunkMatch[1], 10);
204
158
  continue;
205
159
  }
206
-
207
160
  if (line.startsWith('+') && !line.startsWith('+++')) {
208
161
  changedLines.add(currentLine);
209
162
  currentLine++;
210
- } else if (line.startsWith('-') && !line.startsWith('---')) {
163
+ }
164
+ else if (line.startsWith('-') && !line.startsWith('---')) {
211
165
  // Deletions don't increment line number
212
- } else {
166
+ }
167
+ else {
213
168
  currentLine++;
214
169
  }
215
170
  }
216
-
217
171
  return changedLines;
218
172
  }
219
-
220
173
  /**
221
174
  * Convert a snake_case string to camelCase.
222
175
  * e.g., "version_number" -> "versionNumber", "id" -> "id"
223
176
  */
224
- function snakeToCamel(s: string): string {
225
- return s.replace(/_([a-z])/g, (_, letter: string) => letter.toUpperCase());
177
+ function snakeToCamel(s) {
178
+ return s.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
226
179
  }
227
-
228
180
  /**
229
181
  * Parse schema.prisma to build a map of Dbo model name -> set of field names (camelCase).
230
182
  * Only models whose name ends with "Dbo" are included.
231
183
  * Field names are converted from snake_case to camelCase since Dto fields use camelCase.
232
184
  */
233
- function parsePrismaSchema(schemaPath: string): Map<string, Set<string>> {
234
- const models = new Map<string, Set<string>>();
235
-
185
+ function parsePrismaSchema(schemaPath) {
186
+ const models = new Map();
236
187
  if (!fs.existsSync(schemaPath)) {
237
188
  return models;
238
189
  }
239
-
240
190
  const content = fs.readFileSync(schemaPath, 'utf-8');
241
191
  const lines = content.split('\n');
242
-
243
- let currentModel: string | null = null;
244
- let currentFields: Set<string> | null = null;
245
-
192
+ let currentModel = null;
193
+ let currentFields = null;
246
194
  for (const line of lines) {
247
195
  const trimmed = line.trim();
248
-
249
196
  // Match model declaration: model XxxDbo {
250
197
  const modelMatch = trimmed.match(/^model\s+(\w+Dbo)\s*\{/);
251
198
  if (modelMatch) {
252
199
  currentModel = modelMatch[1];
253
- currentFields = new Set<string>();
200
+ currentFields = new Set();
254
201
  continue;
255
202
  }
256
-
257
203
  // End of model block
258
204
  if (currentModel && trimmed === '}') {
259
- models.set(currentModel, currentFields!);
205
+ models.set(currentModel, currentFields);
260
206
  currentModel = null;
261
207
  currentFields = null;
262
208
  continue;
263
209
  }
264
-
265
210
  // Inside a model block - extract field names
266
211
  if (currentModel && currentFields) {
267
212
  // Skip empty lines, comments, and model-level attributes (@@)
268
213
  if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('@@')) {
269
214
  continue;
270
215
  }
271
-
272
216
  // Field name is the first word on the line, converted to camelCase
273
217
  const fieldMatch = trimmed.match(/^(\w+)\s/);
274
218
  if (fieldMatch) {
@@ -276,50 +220,43 @@ function parsePrismaSchema(schemaPath: string): Map<string, Set<string>> {
276
220
  }
277
221
  }
278
222
  }
279
-
280
223
  return models;
281
224
  }
282
-
283
225
  /**
284
226
  * Check if a field has @deprecated in a comment above it (within 3 lines).
285
227
  */
286
- function isFieldDeprecated(fileLines: string[], fieldLine: number): boolean {
228
+ function isFieldDeprecated(fileLines, fieldLine) {
287
229
  const start = Math.max(0, fieldLine - 4);
288
230
  for (let i = start; i <= fieldLine - 1; i++) {
289
231
  const line = fileLines[i]?.trim() ?? '';
290
- if (line.includes('@deprecated')) return true;
232
+ if (line.includes('@deprecated'))
233
+ return true;
291
234
  }
292
235
  return false;
293
236
  }
294
-
295
237
  /**
296
238
  * Parse a TypeScript file to find Dto class/interface declarations and their fields.
297
239
  * Skips classes ending with "JoinDto" since they compose other Dtos.
298
240
  */
299
241
  // webpieces-disable max-lines-new-methods -- AST traversal for both class and interface Dto detection with field extraction
300
- function findDtosInFile(filePath: string, workspaceRoot: string): DtoInfo[] {
242
+ function findDtosInFile(filePath, workspaceRoot) {
301
243
  const fullPath = path.join(workspaceRoot, filePath);
302
- if (!fs.existsSync(fullPath)) return [];
303
-
244
+ if (!fs.existsSync(fullPath))
245
+ return [];
304
246
  const content = fs.readFileSync(fullPath, 'utf-8');
305
247
  const fileLines = content.split('\n');
306
248
  const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
307
-
308
- const dtos: DtoInfo[] = [];
309
-
310
- function visit(node: ts.Node): void {
249
+ const dtos = [];
250
+ function visit(node) {
311
251
  const isClass = ts.isClassDeclaration(node);
312
252
  const isInterface = ts.isInterfaceDeclaration(node);
313
-
314
253
  if ((isClass || isInterface) && node.name) {
315
254
  const name = node.name.text;
316
-
317
255
  // Must end with Dto but NOT with JoinDto
318
256
  if (name.endsWith('Dto') && !name.endsWith('JoinDto')) {
319
- const fields: DtoFieldInfo[] = [];
257
+ const fields = [];
320
258
  const nodeStart = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
321
259
  const nodeEnd = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
322
-
323
260
  for (const member of node.members) {
324
261
  if (ts.isPropertyDeclaration(member) || ts.isPropertySignature(member)) {
325
262
  if (member.name && ts.isIdentifier(member.name)) {
@@ -328,12 +265,10 @@ function findDtosInFile(filePath: string, workspaceRoot: string): DtoInfo[] {
328
265
  const pos = sourceFile.getLineAndCharacterOfPosition(startPos);
329
266
  const line = pos.line + 1;
330
267
  const deprecated = isFieldDeprecated(fileLines, line);
331
-
332
268
  fields.push({ name: fieldName, line, deprecated });
333
269
  }
334
270
  }
335
271
  }
336
-
337
272
  dtos.push({
338
273
  name,
339
274
  file: filePath,
@@ -343,50 +278,39 @@ function findDtosInFile(filePath: string, workspaceRoot: string): DtoInfo[] {
343
278
  });
344
279
  }
345
280
  }
346
-
347
281
  ts.forEachChild(node, visit);
348
282
  }
349
-
350
283
  visit(sourceFile);
351
284
  return dtos;
352
285
  }
353
-
354
286
  /**
355
287
  * Extract the prefix from a Dto/Dbo name by removing the suffix.
356
288
  * e.g., "UserDto" -> "user", "UserDbo" -> "user"
357
289
  */
358
- function extractPrefix(name: string, suffix: string): string {
290
+ function extractPrefix(name, suffix) {
359
291
  return name.slice(0, -suffix.length).toLowerCase();
360
292
  }
361
-
362
293
  /**
363
294
  * Find violations: Dto fields that don't exist in the corresponding Dbo.
364
295
  */
365
- function findViolations(
366
- dtos: DtoInfo[],
367
- dboModels: Map<string, Set<string>>
368
- ): DtoViolation[] {
369
- const violations: DtoViolation[] = [];
370
-
296
+ function findViolations(dtos, dboModels) {
297
+ const violations = [];
371
298
  // Build a lowercase prefix -> Dbo info map
372
- const dboByPrefix = new Map<string, DboEntry>();
299
+ const dboByPrefix = new Map();
373
300
  dboModels.forEach((fields, dboName) => {
374
301
  const prefix = extractPrefix(dboName, 'Dbo');
375
302
  dboByPrefix.set(prefix, { name: dboName, fields });
376
303
  });
377
-
378
304
  for (const dto of dtos) {
379
305
  const prefix = extractPrefix(dto.name, 'Dto');
380
306
  const dbo = dboByPrefix.get(prefix);
381
-
382
307
  if (!dbo) {
383
308
  // No matching Dbo found - skip (might be a Dto without a DB table)
384
309
  continue;
385
310
  }
386
-
387
311
  for (const field of dto.fields) {
388
- if (field.deprecated) continue;
389
-
312
+ if (field.deprecated)
313
+ continue;
390
314
  if (!dbo.fields.has(field.name)) {
391
315
  violations.push({
392
316
  file: dto.file,
@@ -399,29 +323,27 @@ function findViolations(
399
323
  }
400
324
  }
401
325
  }
402
-
403
326
  return violations;
404
327
  }
405
-
406
328
  /**
407
329
  * Compute similarity between two strings using longest common subsequence ratio.
408
330
  * Returns a value between 0 and 1, where 1 is an exact match.
409
331
  */
410
- function similarity(a: string, b: string): number {
332
+ function similarity(a, b) {
411
333
  const al = a.toLowerCase();
412
334
  const bl = b.toLowerCase();
413
- if (al === bl) return 1;
414
-
335
+ if (al === bl)
336
+ return 1;
415
337
  const m = al.length;
416
338
  const n = bl.length;
417
- const prev = new Array<number>(n + 1).fill(0);
418
- const curr = new Array<number>(n + 1).fill(0);
419
-
339
+ const prev = new Array(n + 1).fill(0);
340
+ const curr = new Array(n + 1).fill(0);
420
341
  for (let i = 1; i <= m; i++) {
421
342
  for (let j = 1; j <= n; j++) {
422
343
  if (al[i - 1] === bl[j - 1]) {
423
344
  curr[j] = prev[j - 1] + 1;
424
- } else {
345
+ }
346
+ else {
425
347
  curr[j] = Math.max(prev[j], curr[j - 1]);
426
348
  }
427
349
  }
@@ -430,19 +352,16 @@ function similarity(a: string, b: string): number {
430
352
  curr[j] = 0;
431
353
  }
432
354
  }
433
-
434
355
  const lcsLen = prev[n];
435
356
  return (2 * lcsLen) / (m + n);
436
357
  }
437
-
438
358
  /**
439
359
  * Find the closest matching Dbo field for a given Dto field name.
440
360
  * Returns the best match or null if nothing scores above the threshold.
441
361
  */
442
- function suggestClosestField(fieldName: string, availableFields: string[]): string | null {
443
- let bestMatch: string | null = null;
362
+ function suggestClosestField(fieldName, availableFields) {
363
+ let bestMatch = null;
444
364
  let bestScore = 0.4; // minimum threshold
445
-
446
365
  for (const candidate of availableFields) {
447
366
  const score = similarity(fieldName, candidate);
448
367
  if (score > bestScore) {
@@ -450,14 +369,12 @@ function suggestClosestField(fieldName: string, availableFields: string[]): stri
450
369
  bestMatch = candidate;
451
370
  }
452
371
  }
453
-
454
372
  return bestMatch;
455
373
  }
456
-
457
374
  /**
458
375
  * Report violations to console with prominent AI warning and rename suggestions.
459
376
  */
460
- function reportViolations(violations: DtoViolation[]): void {
377
+ function reportViolations(violations) {
461
378
  console.error('');
462
379
  console.error('❌❌❌ DTO FIELD MISMATCH - AI AGENT: READ THIS CAREFULLY ❌❌❌');
463
380
  console.error('');
@@ -473,77 +390,68 @@ function reportViolations(violations: DtoViolation[]): void {
473
390
  console.error(' contains YYDto and ZZDto fields from the other tables instead of flattening.');
474
391
  console.error(' Fields marked @deprecated in the Dto are exempt from this check.');
475
392
  console.error('');
476
-
477
393
  for (const v of violations) {
478
394
  console.error(` ❌ ${v.file}:${v.line}`);
479
395
  console.error(` ${v.dtoName}.${v.fieldName} does not exist in ${v.dboName}`);
480
-
481
396
  const suggestion = suggestClosestField(v.fieldName, v.availableFields);
482
397
  if (suggestion) {
483
398
  console.error(` 💡 Suggested rename: ${v.fieldName} → ${suggestion} (Use IDE rename on ${v.dtoName}.${v.fieldName})`);
484
- } else {
399
+ }
400
+ else {
485
401
  const preview = v.availableFields.slice(0, 8).join(', ');
486
402
  const ellipsis = v.availableFields.length > 8 ? ', ...' : '';
487
403
  console.error(` 💡 No close match found. Available: ${preview}${ellipsis}`);
488
404
  }
489
405
  }
490
406
  console.error('');
491
-
492
407
  console.error('❌❌❌ AI AGENT: Suggest to the human which are the most likely renames needed and list them ❌❌❌');
493
408
  console.error('');
494
409
  }
495
-
496
410
  /**
497
411
  * Filter changed files to only TypeScript Dto source files within configured paths.
498
412
  */
499
- function filterDtoFiles(changedFiles: string[], dtoSourcePaths: string[]): string[] {
413
+ function filterDtoFiles(changedFiles, dtoSourcePaths) {
500
414
  return changedFiles.filter((f) => {
501
- if (!f.endsWith('.ts') && !f.endsWith('.tsx')) return false;
502
- if (f.includes('.spec.ts') || f.includes('.test.ts')) return false;
415
+ if (!f.endsWith('.ts') && !f.endsWith('.tsx'))
416
+ return false;
417
+ if (f.includes('.spec.ts') || f.includes('.test.ts'))
418
+ return false;
503
419
  return dtoSourcePaths.some((srcPath) => f.startsWith(srcPath));
504
420
  });
505
421
  }
506
-
507
422
  /**
508
423
  * Collect all Dto definitions from the given files.
509
424
  */
510
- function collectDtos(dtoFiles: string[], workspaceRoot: string): DtoInfo[] {
511
- const allDtos: DtoInfo[] = [];
425
+ function collectDtos(dtoFiles, workspaceRoot) {
426
+ const allDtos = [];
512
427
  for (const file of dtoFiles) {
513
428
  const dtos = findDtosInFile(file, workspaceRoot);
514
429
  allDtos.push(...dtos);
515
430
  }
516
431
  return allDtos;
517
432
  }
518
-
519
433
  /**
520
434
  * Check if a Dto class overlaps with any changed lines in the diff.
521
435
  */
522
- function isDtoTouched(dto: DtoInfo, changedLines: Set<number>): boolean {
436
+ function isDtoTouched(dto, changedLines) {
523
437
  for (let line = dto.startLine; line <= dto.endLine; line++) {
524
- if (changedLines.has(line)) return true;
438
+ if (changedLines.has(line))
439
+ return true;
525
440
  }
526
441
  return false;
527
442
  }
528
-
529
443
  /**
530
444
  * Filter Dtos to only those that have changed lines in the diff (MODIFIED_CLASS mode).
531
445
  */
532
- function filterTouchedDtos(
533
- dtos: DtoInfo[],
534
- workspaceRoot: string,
535
- base: string,
536
- head?: string
537
- ): DtoInfo[] {
446
+ function filterTouchedDtos(dtos, workspaceRoot, base, head) {
538
447
  // Group dtos by file to avoid re-fetching diffs
539
- const byFile = new Map<string, DtoInfo[]>();
448
+ const byFile = new Map();
540
449
  for (const dto of dtos) {
541
450
  const list = byFile.get(dto.file) ?? [];
542
451
  list.push(dto);
543
452
  byFile.set(dto.file, list);
544
453
  }
545
-
546
- const touched: DtoInfo[] = [];
454
+ const touched = [];
547
455
  byFile.forEach((fileDtos, file) => {
548
456
  const diff = getFileDiff(workspaceRoot, file, base, head);
549
457
  const changedLines = getChangedLineNumbers(diff);
@@ -555,62 +463,44 @@ function filterTouchedDtos(
555
463
  });
556
464
  return touched;
557
465
  }
558
-
559
466
  /**
560
467
  * Resolve git base ref from env vars or auto-detection.
561
468
  */
562
- function resolveBase(workspaceRoot: string): string | undefined {
469
+ function resolveBase(workspaceRoot) {
563
470
  const envBase = process.env['NX_BASE'];
564
- if (envBase) return envBase;
471
+ if (envBase)
472
+ return envBase;
565
473
  return detectBase(workspaceRoot) ?? undefined;
566
474
  }
567
-
568
475
  /**
569
476
  * Run the core validation after early-exit checks have passed.
570
477
  */
571
478
  // webpieces-disable max-lines-new-methods -- Core validation orchestration with multiple early-exit checks
572
- function validateDtoFiles(
573
- workspaceRoot: string,
574
- prismaSchemaPath: string,
575
- changedFiles: string[],
576
- dtoSourcePaths: string[],
577
- mode: ValidateDtosMode,
578
- base: string,
579
- head?: string
580
- ): ExecutorResult {
479
+ function validateDtoFiles(workspaceRoot, prismaSchemaPath, changedFiles, dtoSourcePaths, mode, base, head) {
581
480
  if (changedFiles.some((f) => f.endsWith(prismaSchemaPath))) {
582
481
  console.log('⏭️ Skipping validate-dtos (schema.prisma is modified - schema in flux)');
583
482
  console.log('');
584
483
  return { success: true };
585
484
  }
586
-
587
485
  const dtoFiles = filterDtoFiles(changedFiles, dtoSourcePaths);
588
-
589
486
  if (dtoFiles.length === 0) {
590
487
  console.log('✅ No Dto files changed');
591
488
  return { success: true };
592
489
  }
593
-
594
490
  console.log(`📂 Checking ${dtoFiles.length} changed file(s) for Dto definitions...`);
595
-
596
491
  const fullSchemaPath = path.join(workspaceRoot, prismaSchemaPath);
597
492
  const dboModels = parsePrismaSchema(fullSchemaPath);
598
-
599
493
  if (dboModels.size === 0) {
600
494
  console.log('⏭️ No Dbo models found in schema.prisma');
601
495
  console.log('');
602
496
  return { success: true };
603
497
  }
604
-
605
498
  console.log(` Found ${dboModels.size} Dbo model(s) in schema.prisma`);
606
-
607
499
  let allDtos = collectDtos(dtoFiles, workspaceRoot);
608
-
609
500
  if (allDtos.length === 0) {
610
501
  console.log('✅ No Dto definitions found in changed files');
611
502
  return { success: true };
612
503
  }
613
-
614
504
  // In MODIFIED_CLASS mode, narrow to only Dtos with changed lines
615
505
  if (mode === 'MODIFIED_CLASS') {
616
506
  allDtos = filterTouchedDtos(allDtos, workspaceRoot, base, head);
@@ -619,25 +509,20 @@ function validateDtoFiles(
619
509
  return { success: true };
620
510
  }
621
511
  }
622
-
623
512
  console.log(` Validating ${allDtos.length} Dto definition(s)`);
624
-
625
513
  const violations = findViolations(allDtos, dboModels);
626
-
627
514
  if (violations.length === 0) {
628
515
  console.log('✅ All Dto fields match their Dbo models');
629
516
  return { success: true };
630
517
  }
631
-
632
518
  reportViolations(violations);
633
519
  return { success: false };
634
520
  }
635
-
636
521
  /**
637
522
  * Resolve mode considering ignoreModifiedUntilEpoch override.
638
523
  * When active, downgrades to OFF. When expired, logs a warning.
639
524
  */
640
- function resolveMode(normalMode: ValidateDtosMode, epoch: number | undefined): ValidateDtosMode {
525
+ function resolveMode(normalMode, epoch) {
641
526
  if (epoch === undefined || normalMode === 'OFF') {
642
527
  return normalMode;
643
528
  }
@@ -650,48 +535,36 @@ function resolveMode(normalMode: ValidateDtosMode, epoch: number | undefined): V
650
535
  }
651
536
  return normalMode;
652
537
  }
653
-
654
- export default async function runValidator(
655
- options: ValidateDtosOptions,
656
- workspaceRoot: string
657
- ): Promise<ExecutorResult> {
538
+ async function runValidator(options, workspaceRoot) {
658
539
  const mode = resolveMode(options.mode ?? 'OFF', options.ignoreModifiedUntilEpoch);
659
-
660
540
  if (mode === 'OFF') {
661
541
  console.log('\n⏭️ Skipping validate-dtos (mode: OFF)');
662
542
  console.log('');
663
543
  return { success: true };
664
544
  }
665
-
666
545
  const prismaSchemaPath = options.prismaSchemaPath;
667
546
  const dtoSourcePaths = options.dtoSourcePaths ?? [];
668
-
669
547
  if (!prismaSchemaPath || dtoSourcePaths.length === 0) {
670
548
  const reason = !prismaSchemaPath ? 'no prismaSchemaPath configured' : 'no dtoSourcePaths configured';
671
549
  console.log(`\n⏭️ Skipping validate-dtos (${reason})`);
672
550
  console.log('');
673
551
  return { success: true };
674
552
  }
675
-
676
553
  console.log('\n📏 Validating DTOs match Prisma Dbo models\n');
677
554
  console.log(` Mode: ${mode}`);
678
555
  console.log(` Schema: ${prismaSchemaPath}`);
679
556
  console.log(` Dto paths: ${dtoSourcePaths.join(', ')}`);
680
-
681
557
  const base = resolveBase(workspaceRoot);
682
558
  const head = process.env['NX_HEAD'];
683
-
684
559
  if (!base) {
685
560
  console.log('\n⏭️ Skipping validate-dtos (could not detect base branch)');
686
561
  console.log('');
687
562
  return { success: true };
688
563
  }
689
-
690
564
  console.log(` Base: ${base}`);
691
565
  console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);
692
566
  console.log('');
693
-
694
567
  const changedFiles = getChangedFiles(workspaceRoot, base, head);
695
-
696
568
  return validateDtoFiles(workspaceRoot, prismaSchemaPath, changedFiles, dtoSourcePaths, mode, base, head);
697
569
  }
570
+ //# sourceMappingURL=validate-dtos.js.map