@webpieces/code-rules 0.0.1

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,491 @@
1
+ import { loadConfig } from '@webpieces/rules-config';
2
+ import { toValidateCodeOptions } from './from-shared-config';
3
+ import runNewMethodsExecutor from './validate-new-methods';
4
+ import runModifiedMethodsExecutor from './validate-modified-methods';
5
+ import runModifiedFilesExecutor from './validate-modified-files';
6
+ import runReturnTypesExecutor, { ReturnTypeMode } from './validate-return-types';
7
+ import runNoInlineTypesExecutor, { NoInlineTypesMode } from './validate-no-inline-types';
8
+ import runNoAnyUnknownExecutor, { NoAnyUnknownMode } from './validate-no-any-unknown';
9
+ import runNoImplicitAnyExecutor, { NoImplicitAnyMode } from './validate-no-implicit-any';
10
+ import runValidateDtosExecutor, { ValidateDtosMode } from './validate-dtos';
11
+ import runPrismaConvertersExecutor, { PrismaConverterMode } from './validate-prisma-converters';
12
+ import runNoDestructureExecutor, { NoDestructureMode } from './validate-no-destructure';
13
+ import runCatchErrorPatternExecutor, { CatchErrorPatternMode } from './validate-catch-error-pattern';
14
+ import runNoUnmanagedExceptionsExecutor, { NoUnmanagedExceptionsMode } from './validate-no-unmanaged-exceptions';
15
+ import runNoDirectApiResolverExecutor, { NoDirectApiResolverMode } from './validate-no-direct-api-resolver';
16
+
17
+ export type MethodMaxLimitMode = 'OFF' | 'NEW_METHODS' | 'NEW_AND_MODIFIED_METHODS' | 'MODIFIED_FILES';
18
+ export type FileMaxLimitMode = 'OFF' | 'MODIFIED_FILES';
19
+
20
+ export interface MethodMaxLimitConfig {
21
+ limit?: number;
22
+ mode?: MethodMaxLimitMode;
23
+ disableAllowed?: boolean;
24
+ ignoreModifiedUntilEpoch?: number;
25
+ }
26
+
27
+ export interface FileMaxLimitConfig {
28
+ limit?: number;
29
+ mode?: FileMaxLimitMode;
30
+ disableAllowed?: boolean;
31
+ ignoreModifiedUntilEpoch?: number;
32
+ }
33
+
34
+ export interface RequireReturnTypeConfig {
35
+ mode?: ReturnTypeMode;
36
+ disableAllowed?: boolean;
37
+ ignoreModifiedUntilEpoch?: number;
38
+ }
39
+
40
+ export interface NoInlineTypeLiteralsConfig {
41
+ mode?: NoInlineTypesMode;
42
+ disableAllowed?: boolean;
43
+ ignoreModifiedUntilEpoch?: number;
44
+ }
45
+
46
+ export interface NoAnyUnknownConfig {
47
+ mode?: NoAnyUnknownMode;
48
+ disableAllowed?: boolean;
49
+ ignoreModifiedUntilEpoch?: number;
50
+ }
51
+
52
+ export interface NoImplicitAnyConfig {
53
+ mode?: NoImplicitAnyMode;
54
+ disableAllowed?: boolean;
55
+ ignoreModifiedUntilEpoch?: number;
56
+ }
57
+
58
+ export interface ValidateDtosConfig {
59
+ mode?: ValidateDtosMode;
60
+ disableAllowed?: boolean;
61
+ prismaSchemaPath?: string;
62
+ dtoSourcePaths?: string[];
63
+ ignoreModifiedUntilEpoch?: number;
64
+ }
65
+
66
+ export interface PrismaConverterConfig {
67
+ mode?: PrismaConverterMode;
68
+ disableAllowed?: boolean;
69
+ schemaPath?: string;
70
+ convertersPaths?: string[];
71
+ enforcePaths?: string[];
72
+ ignoreModifiedUntilEpoch?: number;
73
+ }
74
+
75
+ export interface NoDestructureConfig {
76
+ mode?: NoDestructureMode;
77
+ disableAllowed?: boolean;
78
+ ignoreModifiedUntilEpoch?: number;
79
+ }
80
+
81
+ export interface CatchErrorPatternConfig {
82
+ mode?: CatchErrorPatternMode;
83
+ disableAllowed?: boolean;
84
+ ignoreModifiedUntilEpoch?: number;
85
+ }
86
+
87
+ export interface NoUnmanagedExceptionsConfig {
88
+ mode?: NoUnmanagedExceptionsMode;
89
+ disableAllowed?: boolean;
90
+ ignoreModifiedUntilEpoch?: number;
91
+ }
92
+
93
+ export interface NoDirectApiResolverConfig {
94
+ mode?: NoDirectApiResolverMode;
95
+ disableAllowed?: boolean;
96
+ ignoreModifiedUntilEpoch?: number;
97
+ enforcePaths?: string[];
98
+ }
99
+
100
+ export interface ValidateCodeOptions {
101
+ methodMaxLimit?: MethodMaxLimitConfig;
102
+ fileMaxLimit?: FileMaxLimitConfig;
103
+ requireReturnType?: RequireReturnTypeConfig;
104
+ noInlineTypeLiterals?: NoInlineTypeLiteralsConfig;
105
+ noAnyUnknown?: NoAnyUnknownConfig;
106
+ noImplicitAny?: NoImplicitAnyConfig;
107
+ validateDtos?: ValidateDtosConfig;
108
+ prismaConverter?: PrismaConverterConfig;
109
+ noDestructure?: NoDestructureConfig;
110
+ catchErrorPattern?: CatchErrorPatternConfig;
111
+ noUnmanagedExceptions?: NoUnmanagedExceptionsConfig;
112
+ noDirectApiInResolver?: NoDirectApiResolverConfig;
113
+ }
114
+
115
+ export interface ExecutorResult {
116
+ success: boolean;
117
+ }
118
+
119
+ interface OverrideInfo {
120
+ active: boolean;
121
+ normalMode: string;
122
+ expiresDate: string;
123
+ }
124
+
125
+ interface ParsedConfig {
126
+ methodLimit: number;
127
+ methodMode: MethodMaxLimitMode;
128
+ methodDisableAllowed: boolean;
129
+ methodOverride: OverrideInfo | undefined;
130
+ fileLimit: number;
131
+ fileMode: FileMaxLimitMode;
132
+ fileDisableAllowed: boolean;
133
+ fileOverride: OverrideInfo | undefined;
134
+ returnTypeMode: ReturnTypeMode;
135
+ returnTypeDisableAllowed: boolean;
136
+ returnTypeIgnoreEpoch: number | undefined;
137
+ noInlineTypesMode: NoInlineTypesMode;
138
+ noInlineTypesDisableAllowed: boolean;
139
+ noInlineTypesIgnoreEpoch: number | undefined;
140
+ noAnyUnknownMode: NoAnyUnknownMode;
141
+ noAnyUnknownDisableAllowed: boolean;
142
+ noAnyUnknownIgnoreEpoch: number | undefined;
143
+ noImplicitAnyMode: NoImplicitAnyMode;
144
+ noImplicitAnyDisableAllowed: boolean;
145
+ noImplicitAnyIgnoreEpoch: number | undefined;
146
+ validateDtosMode: ValidateDtosMode;
147
+ validateDtosDisableAllowed: boolean;
148
+ validateDtosPrismaPath: string | undefined;
149
+ validateDtosSrcPaths: string[];
150
+ validateDtosIgnoreEpoch: number | undefined;
151
+ prismaConverterMode: PrismaConverterMode;
152
+ prismaConverterDisableAllowed: boolean;
153
+ prismaConverterSchemaPath: string | undefined;
154
+ prismaConverterConvertersPaths: string[];
155
+ prismaConverterEnforcePaths: string[];
156
+ prismaConverterIgnoreEpoch: number | undefined;
157
+ noDestructureMode: NoDestructureMode;
158
+ noDestructureDisableAllowed: boolean;
159
+ noDestructureIgnoreEpoch: number | undefined;
160
+ catchErrorPatternMode: CatchErrorPatternMode;
161
+ catchErrorPatternDisableAllowed: boolean;
162
+ catchErrorPatternIgnoreEpoch: number | undefined;
163
+ noUnmanagedExceptionsMode: NoUnmanagedExceptionsMode;
164
+ noUnmanagedExceptionsDisableAllowed: boolean;
165
+ noUnmanagedExceptionsIgnoreEpoch: number | undefined;
166
+ noDirectApiResolverMode: NoDirectApiResolverMode;
167
+ noDirectApiResolverDisableAllowed: boolean;
168
+ noDirectApiResolverIgnoreEpoch: number | undefined;
169
+ noDirectApiResolverEnforcePaths: string[];
170
+ }
171
+
172
+ interface ResolvedMethodMode {
173
+ mode: MethodMaxLimitMode;
174
+ override: OverrideInfo | undefined;
175
+ }
176
+
177
+ interface ResolvedFileMode {
178
+ mode: FileMaxLimitMode;
179
+ override: OverrideInfo | undefined;
180
+ }
181
+
182
+ const VALID_MODES: Record<string, string[]> = {
183
+ methodMaxLimit: ['OFF', 'NEW_METHODS', 'NEW_AND_MODIFIED_METHODS', 'MODIFIED_FILES'],
184
+ fileMaxLimit: ['OFF', 'MODIFIED_FILES'],
185
+ requireReturnType: ['OFF', 'NEW_METHODS', 'NEW_AND_MODIFIED_METHODS', 'MODIFIED_FILES'],
186
+ noInlineTypeLiterals: ['OFF', 'NEW_METHODS', 'NEW_AND_MODIFIED_METHODS', 'MODIFIED_FILES'],
187
+ noAnyUnknown: ['OFF', 'MODIFIED_CODE', 'MODIFIED_FILES'],
188
+ noImplicitAny: ['OFF', 'MODIFIED_CODE', 'MODIFIED_FILES'],
189
+ validateDtos: ['OFF', 'MODIFIED_CLASS', 'MODIFIED_FILES'],
190
+ prismaConverter: ['OFF', 'NEW_AND_MODIFIED_METHODS', 'MODIFIED_FILES'],
191
+ noDestructure: ['OFF', 'MODIFIED_CODE', 'MODIFIED_FILES'],
192
+ catchErrorPattern: ['OFF', 'MODIFIED_CODE', 'MODIFIED_FILES'],
193
+ noUnmanagedExceptions: ['OFF', 'MODIFIED_CODE', 'MODIFIED_FILES'],
194
+ noDirectApiInResolver: ['OFF', 'MODIFIED_CODE', 'NEW_AND_MODIFIED_METHODS', 'MODIFIED_FILES'],
195
+ };
196
+
197
+ /**
198
+ * Validate that all configured modes are valid. Produces clear error messages naming the rule.
199
+ */
200
+ function validateModes(options: ValidateCodeOptions): string[] {
201
+ const errors: string[] = [];
202
+
203
+ type ModeEntry = [string, string | undefined];
204
+ const modeEntries: ModeEntry[] = [
205
+ ['methodMaxLimit', options.methodMaxLimit?.mode],
206
+ ['fileMaxLimit', options.fileMaxLimit?.mode],
207
+ ['requireReturnType', options.requireReturnType?.mode],
208
+ ['noInlineTypeLiterals', options.noInlineTypeLiterals?.mode],
209
+ ['noAnyUnknown', options.noAnyUnknown?.mode],
210
+ ['noImplicitAny', options.noImplicitAny?.mode],
211
+ ['validateDtos', options.validateDtos?.mode],
212
+ ['prismaConverter', options.prismaConverter?.mode],
213
+ ['noDestructure', options.noDestructure?.mode],
214
+ ['catchErrorPattern', options.catchErrorPattern?.mode],
215
+ ['noUnmanagedExceptions', options.noUnmanagedExceptions?.mode],
216
+ ['noDirectApiInResolver', options.noDirectApiInResolver?.mode],
217
+ ];
218
+
219
+ for (const entry of modeEntries) {
220
+ const ruleName = entry[0];
221
+ const modeValue = entry[1];
222
+ if (modeValue === undefined) continue;
223
+ const validModes = VALID_MODES[ruleName];
224
+ if (!validModes.includes(modeValue)) {
225
+ errors.push(`${ruleName}.mode = '${modeValue}' is invalid. Valid modes: ${validModes.join(', ')}`);
226
+ }
227
+ }
228
+
229
+ return errors;
230
+ }
231
+
232
+ function formatEpochDate(epoch: number): string {
233
+ return new Date(epoch * 1000).toISOString().split('T')[0];
234
+ }
235
+
236
+ function resolveMethodMode(
237
+ normalMode: MethodMaxLimitMode, epoch: number | undefined
238
+ ): ResolvedMethodMode {
239
+ if (epoch === undefined) {
240
+ return { mode: normalMode, override: undefined };
241
+ }
242
+ const nowSeconds = Date.now() / 1000;
243
+ if (nowSeconds < epoch) {
244
+ // Active: downgrade to skip modified checking
245
+ const downgraded: MethodMaxLimitMode =
246
+ normalMode === 'OFF' ? 'OFF' : 'NEW_METHODS';
247
+ return {
248
+ mode: downgraded,
249
+ override: { active: true, normalMode, expiresDate: formatEpochDate(epoch) },
250
+ };
251
+ }
252
+ // Expired
253
+ return { mode: normalMode, override: undefined };
254
+ }
255
+
256
+ function resolveFileMode(
257
+ normalMode: FileMaxLimitMode, epoch: number | undefined
258
+ ): ResolvedFileMode {
259
+ if (epoch === undefined) {
260
+ return { mode: normalMode, override: undefined };
261
+ }
262
+ const nowSeconds = Date.now() / 1000;
263
+ if (nowSeconds < epoch) {
264
+ // Active: file checking is inherently about modified files, so skip entirely
265
+ return {
266
+ mode: 'OFF',
267
+ override: { active: true, normalMode, expiresDate: formatEpochDate(epoch) },
268
+ };
269
+ }
270
+ // Expired
271
+ return { mode: normalMode, override: undefined };
272
+ }
273
+
274
+ function parseConfig(options: ValidateCodeOptions): ParsedConfig {
275
+ const methodConfig: MethodMaxLimitConfig = options.methodMaxLimit ?? {};
276
+ const fileConfig: FileMaxLimitConfig = options.fileMaxLimit ?? {};
277
+
278
+ const normalMethodMode = methodConfig.mode ?? 'NEW_AND_MODIFIED_METHODS';
279
+ const normalFileMode = fileConfig.mode ?? 'MODIFIED_FILES';
280
+
281
+ const methodResolved = resolveMethodMode(normalMethodMode, methodConfig.ignoreModifiedUntilEpoch);
282
+ const fileResolved = resolveFileMode(normalFileMode, fileConfig.ignoreModifiedUntilEpoch);
283
+
284
+ return {
285
+ methodLimit: methodConfig.limit ?? 80,
286
+ methodMode: methodResolved.mode,
287
+ methodDisableAllowed: methodConfig.disableAllowed ?? true,
288
+ methodOverride: methodResolved.override,
289
+ fileLimit: fileConfig.limit ?? 900,
290
+ fileMode: fileResolved.mode,
291
+ fileDisableAllowed: fileConfig.disableAllowed ?? true,
292
+ fileOverride: fileResolved.override,
293
+ returnTypeMode: options.requireReturnType?.mode ?? 'OFF',
294
+ returnTypeDisableAllowed: options.requireReturnType?.disableAllowed ?? true,
295
+ returnTypeIgnoreEpoch: options.requireReturnType?.ignoreModifiedUntilEpoch,
296
+ noInlineTypesMode: options.noInlineTypeLiterals?.mode ?? 'OFF',
297
+ noInlineTypesDisableAllowed: options.noInlineTypeLiterals?.disableAllowed ?? true,
298
+ noInlineTypesIgnoreEpoch: options.noInlineTypeLiterals?.ignoreModifiedUntilEpoch,
299
+ noAnyUnknownMode: options.noAnyUnknown?.mode ?? 'OFF',
300
+ noAnyUnknownDisableAllowed: options.noAnyUnknown?.disableAllowed ?? true,
301
+ noAnyUnknownIgnoreEpoch: options.noAnyUnknown?.ignoreModifiedUntilEpoch,
302
+ noImplicitAnyMode: options.noImplicitAny?.mode ?? 'OFF',
303
+ noImplicitAnyDisableAllowed: options.noImplicitAny?.disableAllowed ?? true,
304
+ noImplicitAnyIgnoreEpoch: options.noImplicitAny?.ignoreModifiedUntilEpoch,
305
+ validateDtosMode: options.validateDtos?.mode ?? 'OFF',
306
+ validateDtosDisableAllowed: options.validateDtos?.disableAllowed ?? true,
307
+ validateDtosPrismaPath: options.validateDtos?.prismaSchemaPath,
308
+ validateDtosSrcPaths: options.validateDtos?.dtoSourcePaths ?? [],
309
+ validateDtosIgnoreEpoch: options.validateDtos?.ignoreModifiedUntilEpoch,
310
+ prismaConverterMode: options.prismaConverter?.mode ?? 'OFF',
311
+ prismaConverterDisableAllowed: options.prismaConverter?.disableAllowed ?? true,
312
+ prismaConverterSchemaPath: options.prismaConverter?.schemaPath,
313
+ prismaConverterConvertersPaths: options.prismaConverter?.convertersPaths ?? [],
314
+ prismaConverterEnforcePaths: options.prismaConverter?.enforcePaths ?? [],
315
+ prismaConverterIgnoreEpoch: options.prismaConverter?.ignoreModifiedUntilEpoch,
316
+ noDestructureMode: options.noDestructure?.mode ?? 'OFF',
317
+ noDestructureDisableAllowed: options.noDestructure?.disableAllowed ?? true,
318
+ noDestructureIgnoreEpoch: options.noDestructure?.ignoreModifiedUntilEpoch,
319
+ catchErrorPatternMode: options.catchErrorPattern?.mode ?? 'OFF',
320
+ catchErrorPatternDisableAllowed: options.catchErrorPattern?.disableAllowed ?? true,
321
+ catchErrorPatternIgnoreEpoch: options.catchErrorPattern?.ignoreModifiedUntilEpoch,
322
+ noUnmanagedExceptionsMode: options.noUnmanagedExceptions?.mode ?? 'OFF',
323
+ noUnmanagedExceptionsDisableAllowed: options.noUnmanagedExceptions?.disableAllowed ?? true,
324
+ noUnmanagedExceptionsIgnoreEpoch: options.noUnmanagedExceptions?.ignoreModifiedUntilEpoch,
325
+ noDirectApiResolverMode: options.noDirectApiInResolver?.mode ?? 'OFF',
326
+ noDirectApiResolverDisableAllowed: options.noDirectApiInResolver?.disableAllowed ?? true,
327
+ noDirectApiResolverIgnoreEpoch: options.noDirectApiInResolver?.ignoreModifiedUntilEpoch,
328
+ noDirectApiResolverEnforcePaths: options.noDirectApiInResolver?.enforcePaths ?? [],
329
+ };
330
+ }
331
+
332
+ function formatOverride(override: OverrideInfo | undefined): string {
333
+ if (!override) {
334
+ return '';
335
+ }
336
+ return ` (override active, normal: ${override.normalMode}, expires: ${override.expiresDate})`;
337
+ }
338
+
339
+ function logConfig(config: ParsedConfig): void {
340
+ console.log('\n\ud83d\udccf Running Code Validations\n');
341
+ console.log(` Method limits: mode=${config.methodMode}${formatOverride(config.methodOverride)}, limit=${config.methodLimit}, disableAllowed=${config.methodDisableAllowed}`);
342
+ console.log(` File limits: mode=${config.fileMode}${formatOverride(config.fileOverride)}, limit=${config.fileLimit}, disableAllowed=${config.fileDisableAllowed}`);
343
+ console.log(` Require return types: mode=${config.returnTypeMode}, disableAllowed=${config.returnTypeDisableAllowed}`);
344
+ console.log(` No inline type literals: mode=${config.noInlineTypesMode}, disableAllowed=${config.noInlineTypesDisableAllowed}`);
345
+ console.log(` No any/unknown: mode=${config.noAnyUnknownMode}, disableAllowed=${config.noAnyUnknownDisableAllowed}`);
346
+ console.log(` No implicit any: mode=${config.noImplicitAnyMode}, disableAllowed=${config.noImplicitAnyDisableAllowed}`);
347
+ console.log(` Validate DTOs: mode=${config.validateDtosMode}, disableAllowed=${config.validateDtosDisableAllowed}`);
348
+ console.log(` Prisma converters: mode=${config.prismaConverterMode}, disableAllowed=${config.prismaConverterDisableAllowed}`);
349
+ console.log(` No destructure: mode=${config.noDestructureMode}, disableAllowed=${config.noDestructureDisableAllowed}`);
350
+ console.log(` Catch error pattern: mode=${config.catchErrorPatternMode}, disableAllowed=${config.catchErrorPatternDisableAllowed}`);
351
+ console.log(` No unmanaged exceptions: mode=${config.noUnmanagedExceptionsMode}, disableAllowed=${config.noUnmanagedExceptionsDisableAllowed}`);
352
+ console.log(` No direct API in resolver: mode=${config.noDirectApiResolverMode}, disableAllowed=${config.noDirectApiResolverDisableAllowed}`);
353
+ console.log('');
354
+ }
355
+
356
+ function isAllOff(config: ParsedConfig): boolean {
357
+ return config.methodMode === 'OFF' && config.fileMode === 'OFF' &&
358
+ config.returnTypeMode === 'OFF' && config.noInlineTypesMode === 'OFF' &&
359
+ config.noAnyUnknownMode === 'OFF' && config.noImplicitAnyMode === 'OFF' &&
360
+ config.validateDtosMode === 'OFF' &&
361
+ config.prismaConverterMode === 'OFF' && config.noDestructureMode === 'OFF' &&
362
+ config.catchErrorPatternMode === 'OFF' && config.noUnmanagedExceptionsMode === 'OFF' &&
363
+ config.noDirectApiResolverMode === 'OFF';
364
+ }
365
+
366
+ async function runMethodValidators(config: ParsedConfig, workspaceRoot: string): Promise<ExecutorResult[]> {
367
+ const results: ExecutorResult[] = [];
368
+ const runNew = config.methodMode === 'NEW_METHODS' || config.methodMode === 'NEW_AND_MODIFIED_METHODS';
369
+ const runModified = config.methodMode === 'NEW_AND_MODIFIED_METHODS' || config.methodMode === 'MODIFIED_FILES';
370
+
371
+ if (runNew) {
372
+ results.push(await runNewMethodsExecutor({
373
+ limit: config.methodLimit,
374
+ mode: config.methodMode, disableAllowed: config.methodDisableAllowed,
375
+ }, workspaceRoot));
376
+ }
377
+ if (runModified) {
378
+ results.push(await runModifiedMethodsExecutor({
379
+ limit: config.methodLimit, mode: config.methodMode, disableAllowed: config.methodDisableAllowed,
380
+ }, workspaceRoot));
381
+ }
382
+ return results;
383
+ }
384
+
385
+ async function runAllValidators(config: ParsedConfig, workspaceRoot: string): Promise<ExecutorResult[]> {
386
+ const results: ExecutorResult[] = [];
387
+ const methodResults = await runMethodValidators(config, workspaceRoot);
388
+ results.push(...methodResults);
389
+ results.push(await runModifiedFilesExecutor({
390
+ limit: config.fileLimit, mode: config.fileMode, disableAllowed: config.fileDisableAllowed,
391
+ }, workspaceRoot));
392
+ results.push(await runReturnTypesExecutor({
393
+ mode: config.returnTypeMode,
394
+ disableAllowed: config.returnTypeDisableAllowed,
395
+ ignoreModifiedUntilEpoch: config.returnTypeIgnoreEpoch,
396
+ }, workspaceRoot));
397
+ results.push(await runNoInlineTypesExecutor({
398
+ mode: config.noInlineTypesMode,
399
+ disableAllowed: config.noInlineTypesDisableAllowed,
400
+ ignoreModifiedUntilEpoch: config.noInlineTypesIgnoreEpoch,
401
+ }, workspaceRoot));
402
+ results.push(await runNoAnyUnknownExecutor({
403
+ mode: config.noAnyUnknownMode,
404
+ disableAllowed: config.noAnyUnknownDisableAllowed,
405
+ ignoreModifiedUntilEpoch: config.noAnyUnknownIgnoreEpoch,
406
+ }, workspaceRoot));
407
+ results.push(await runNoImplicitAnyExecutor({
408
+ mode: config.noImplicitAnyMode,
409
+ disableAllowed: config.noImplicitAnyDisableAllowed,
410
+ ignoreModifiedUntilEpoch: config.noImplicitAnyIgnoreEpoch,
411
+ }, workspaceRoot));
412
+ results.push(await runValidateDtosExecutor({
413
+ mode: config.validateDtosMode,
414
+ disableAllowed: config.validateDtosDisableAllowed,
415
+ prismaSchemaPath: config.validateDtosPrismaPath,
416
+ dtoSourcePaths: config.validateDtosSrcPaths,
417
+ ignoreModifiedUntilEpoch: config.validateDtosIgnoreEpoch,
418
+ }, workspaceRoot));
419
+ results.push(await runPrismaConvertersExecutor({
420
+ mode: config.prismaConverterMode,
421
+ disableAllowed: config.prismaConverterDisableAllowed,
422
+ schemaPath: config.prismaConverterSchemaPath,
423
+ convertersPaths: config.prismaConverterConvertersPaths,
424
+ enforcePaths: config.prismaConverterEnforcePaths,
425
+ ignoreModifiedUntilEpoch: config.prismaConverterIgnoreEpoch,
426
+ }, workspaceRoot));
427
+ results.push(await runNoDestructureExecutor({
428
+ mode: config.noDestructureMode,
429
+ disableAllowed: config.noDestructureDisableAllowed,
430
+ ignoreModifiedUntilEpoch: config.noDestructureIgnoreEpoch,
431
+ }, workspaceRoot));
432
+ results.push(await runCatchErrorPatternExecutor({
433
+ mode: config.catchErrorPatternMode,
434
+ disableAllowed: config.catchErrorPatternDisableAllowed,
435
+ ignoreModifiedUntilEpoch: config.catchErrorPatternIgnoreEpoch,
436
+ }, workspaceRoot));
437
+ results.push(await runNoUnmanagedExceptionsExecutor({
438
+ mode: config.noUnmanagedExceptionsMode,
439
+ disableAllowed: config.noUnmanagedExceptionsDisableAllowed,
440
+ ignoreModifiedUntilEpoch: config.noUnmanagedExceptionsIgnoreEpoch,
441
+ }, workspaceRoot));
442
+ results.push(await runNoDirectApiResolverExecutor({
443
+ mode: config.noDirectApiResolverMode,
444
+ disableAllowed: config.noDirectApiResolverDisableAllowed,
445
+ ignoreModifiedUntilEpoch: config.noDirectApiResolverIgnoreEpoch,
446
+ enforcePaths: config.noDirectApiResolverEnforcePaths,
447
+ }, workspaceRoot));
448
+ return results;
449
+ }
450
+
451
+ export default async function runValidator(
452
+ _nxOptions: ValidateCodeOptions,
453
+ workspaceRoot: string
454
+ ): Promise<ExecutorResult> {
455
+ // Config comes from webpieces.config.json at the workspace root,
456
+ // loaded via the shared @webpieces/rules-config loader so ai-hooks and
457
+ // this executor agree on every rule's enabled/mode/options.
458
+ const shared = loadConfig(workspaceRoot);
459
+ if (!shared.configPath) {
460
+ console.error('\nāŒ No webpieces.config.json found at workspace root (or any ancestor).\n');
461
+ return { success: false };
462
+ }
463
+ const options = toValidateCodeOptions(shared);
464
+
465
+ const modeErrors = validateModes(options);
466
+ if (modeErrors.length > 0) {
467
+ console.error('');
468
+ for (const err of modeErrors) {
469
+ console.error(`āŒ ${err}`);
470
+ }
471
+ console.error('');
472
+ return { success: false };
473
+ }
474
+
475
+ console.log(`\nšŸ“„ Loaded config: ${shared.configPath}`);
476
+
477
+ const config = parseConfig(options);
478
+
479
+ if (isAllOff(config)) {
480
+ console.log('\n\u23ed\ufe0f Skipping all code validations (all modes: OFF)\n');
481
+ return { success: true };
482
+ }
483
+
484
+ logConfig(config);
485
+
486
+ const results = await runAllValidators(config, workspaceRoot);
487
+ const allSuccess = results.every((r) => r.success);
488
+
489
+ console.log(allSuccess ? '\n\u2705 All code validations passed\n' : '\n\u274c Some code validations failed\n');
490
+ return { success: allSuccess };
491
+ }