@webpieces/dev-config 0.2.95 → 0.2.97

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 (181) hide show
  1. package/config/eslint/base.mjs +1 -1
  2. package/executors.json +6 -91
  3. package/package.json +6 -19
  4. package/{executors → src/executors}/help/executor.d.ts +4 -2
  5. package/src/executors/help/executor.js.map +1 -0
  6. package/src/executors/validate-eslint-sync/executor.js.map +1 -0
  7. package/{executors → src/executors}/validate-versions-locked/executor.js +2 -1
  8. package/src/executors/validate-versions-locked/executor.js.map +1 -0
  9. package/src/index.d.ts +1 -1
  10. package/src/index.js +1 -1
  11. package/src/index.js.map +1 -1
  12. package/src/plugin.d.ts +86 -0
  13. package/{plugin.js → src/plugin.js} +31 -15
  14. package/src/plugin.js.map +1 -0
  15. package/src/toError.d.ts +5 -0
  16. package/src/toError.js +37 -0
  17. package/src/toError.js.map +1 -0
  18. package/templates/eslint.webpieces.config.mjs +1 -1
  19. package/architecture/executors/diff-utils.d.ts +0 -24
  20. package/architecture/executors/diff-utils.js +0 -119
  21. package/architecture/executors/diff-utils.js.map +0 -1
  22. package/architecture/executors/diff-utils.ts +0 -127
  23. package/architecture/executors/generate/executor.d.ts +0 -16
  24. package/architecture/executors/generate/executor.js +0 -44
  25. package/architecture/executors/generate/executor.js.map +0 -1
  26. package/architecture/executors/generate/executor.ts +0 -59
  27. package/architecture/executors/generate/schema.json +0 -14
  28. package/architecture/executors/validate-architecture-unchanged/executor.d.ts +0 -17
  29. package/architecture/executors/validate-architecture-unchanged/executor.js +0 -229
  30. package/architecture/executors/validate-architecture-unchanged/executor.js.map +0 -1
  31. package/architecture/executors/validate-architecture-unchanged/executor.ts +0 -251
  32. package/architecture/executors/validate-architecture-unchanged/schema.json +0 -14
  33. package/architecture/executors/validate-code/executor.d.ts +0 -78
  34. package/architecture/executors/validate-code/executor.js +0 -243
  35. package/architecture/executors/validate-code/executor.js.map +0 -1
  36. package/architecture/executors/validate-code/executor.ts +0 -406
  37. package/architecture/executors/validate-code/schema.json +0 -227
  38. package/architecture/executors/validate-dtos/executor.d.ts +0 -42
  39. package/architecture/executors/validate-dtos/executor.js +0 -561
  40. package/architecture/executors/validate-dtos/executor.js.map +0 -1
  41. package/architecture/executors/validate-dtos/executor.ts +0 -689
  42. package/architecture/executors/validate-dtos/schema.json +0 -33
  43. package/architecture/executors/validate-modified-files/executor.d.ts +0 -25
  44. package/architecture/executors/validate-modified-files/executor.js +0 -501
  45. package/architecture/executors/validate-modified-files/executor.js.map +0 -1
  46. package/architecture/executors/validate-modified-files/executor.ts +0 -571
  47. package/architecture/executors/validate-modified-files/schema.json +0 -25
  48. package/architecture/executors/validate-modified-methods/executor.d.ts +0 -31
  49. package/architecture/executors/validate-modified-methods/executor.js +0 -694
  50. package/architecture/executors/validate-modified-methods/executor.js.map +0 -1
  51. package/architecture/executors/validate-modified-methods/executor.ts +0 -797
  52. package/architecture/executors/validate-modified-methods/schema.json +0 -25
  53. package/architecture/executors/validate-new-methods/executor.d.ts +0 -28
  54. package/architecture/executors/validate-new-methods/executor.js +0 -513
  55. package/architecture/executors/validate-new-methods/executor.js.map +0 -1
  56. package/architecture/executors/validate-new-methods/executor.ts +0 -584
  57. package/architecture/executors/validate-new-methods/schema.json +0 -25
  58. package/architecture/executors/validate-no-any-unknown/executor.d.ts +0 -42
  59. package/architecture/executors/validate-no-any-unknown/executor.js +0 -462
  60. package/architecture/executors/validate-no-any-unknown/executor.js.map +0 -1
  61. package/architecture/executors/validate-no-any-unknown/executor.ts +0 -540
  62. package/architecture/executors/validate-no-any-unknown/schema.json +0 -24
  63. package/architecture/executors/validate-no-architecture-cycles/executor.d.ts +0 -16
  64. package/architecture/executors/validate-no-architecture-cycles/executor.js +0 -48
  65. package/architecture/executors/validate-no-architecture-cycles/executor.js.map +0 -1
  66. package/architecture/executors/validate-no-architecture-cycles/executor.ts +0 -60
  67. package/architecture/executors/validate-no-architecture-cycles/schema.json +0 -8
  68. package/architecture/executors/validate-no-destructure/executor.d.ts +0 -52
  69. package/architecture/executors/validate-no-destructure/executor.js +0 -491
  70. package/architecture/executors/validate-no-destructure/executor.js.map +0 -1
  71. package/architecture/executors/validate-no-destructure/executor.ts +0 -578
  72. package/architecture/executors/validate-no-destructure/schema.json +0 -24
  73. package/architecture/executors/validate-no-direct-api-resolver/executor.d.ts +0 -47
  74. package/architecture/executors/validate-no-direct-api-resolver/executor.js +0 -566
  75. package/architecture/executors/validate-no-direct-api-resolver/executor.js.map +0 -1
  76. package/architecture/executors/validate-no-direct-api-resolver/executor.ts +0 -666
  77. package/architecture/executors/validate-no-direct-api-resolver/schema.json +0 -29
  78. package/architecture/executors/validate-no-inline-types/executor.d.ts +0 -91
  79. package/architecture/executors/validate-no-inline-types/executor.js +0 -669
  80. package/architecture/executors/validate-no-inline-types/executor.js.map +0 -1
  81. package/architecture/executors/validate-no-inline-types/executor.ts +0 -775
  82. package/architecture/executors/validate-no-inline-types/schema.json +0 -24
  83. package/architecture/executors/validate-no-skiplevel-deps/executor.d.ts +0 -19
  84. package/architecture/executors/validate-no-skiplevel-deps/executor.js +0 -227
  85. package/architecture/executors/validate-no-skiplevel-deps/executor.js.map +0 -1
  86. package/architecture/executors/validate-no-skiplevel-deps/executor.ts +0 -267
  87. package/architecture/executors/validate-no-skiplevel-deps/schema.json +0 -8
  88. package/architecture/executors/validate-packagejson/executor.d.ts +0 -16
  89. package/architecture/executors/validate-packagejson/executor.js +0 -57
  90. package/architecture/executors/validate-packagejson/executor.js.map +0 -1
  91. package/architecture/executors/validate-packagejson/executor.ts +0 -74
  92. package/architecture/executors/validate-packagejson/schema.json +0 -8
  93. package/architecture/executors/validate-prisma-converters/executor.d.ts +0 -60
  94. package/architecture/executors/validate-prisma-converters/executor.js +0 -634
  95. package/architecture/executors/validate-prisma-converters/executor.js.map +0 -1
  96. package/architecture/executors/validate-prisma-converters/executor.ts +0 -822
  97. package/architecture/executors/validate-prisma-converters/schema.json +0 -38
  98. package/architecture/executors/validate-return-types/executor.d.ts +0 -29
  99. package/architecture/executors/validate-return-types/executor.js +0 -439
  100. package/architecture/executors/validate-return-types/executor.js.map +0 -1
  101. package/architecture/executors/validate-return-types/executor.ts +0 -524
  102. package/architecture/executors/validate-return-types/schema.json +0 -24
  103. package/architecture/executors/visualize/executor.d.ts +0 -17
  104. package/architecture/executors/visualize/executor.js +0 -49
  105. package/architecture/executors/visualize/executor.js.map +0 -1
  106. package/architecture/executors/visualize/executor.ts +0 -63
  107. package/architecture/executors/visualize/schema.json +0 -14
  108. package/architecture/index.d.ts +0 -19
  109. package/architecture/index.js +0 -23
  110. package/architecture/index.js.map +0 -1
  111. package/architecture/index.ts +0 -20
  112. package/architecture/lib/graph-comparator.d.ts +0 -39
  113. package/architecture/lib/graph-comparator.js +0 -100
  114. package/architecture/lib/graph-comparator.js.map +0 -1
  115. package/architecture/lib/graph-comparator.ts +0 -141
  116. package/architecture/lib/graph-generator.d.ts +0 -19
  117. package/architecture/lib/graph-generator.js +0 -84
  118. package/architecture/lib/graph-generator.js.map +0 -1
  119. package/architecture/lib/graph-generator.ts +0 -97
  120. package/architecture/lib/graph-loader.d.ts +0 -31
  121. package/architecture/lib/graph-loader.js +0 -98
  122. package/architecture/lib/graph-loader.js.map +0 -1
  123. package/architecture/lib/graph-loader.ts +0 -116
  124. package/architecture/lib/graph-sorter.d.ts +0 -37
  125. package/architecture/lib/graph-sorter.js +0 -110
  126. package/architecture/lib/graph-sorter.js.map +0 -1
  127. package/architecture/lib/graph-sorter.ts +0 -137
  128. package/architecture/lib/graph-visualizer.d.ts +0 -29
  129. package/architecture/lib/graph-visualizer.js +0 -217
  130. package/architecture/lib/graph-visualizer.js.map +0 -1
  131. package/architecture/lib/graph-visualizer.ts +0 -231
  132. package/architecture/lib/package-validator.d.ts +0 -38
  133. package/architecture/lib/package-validator.js +0 -126
  134. package/architecture/lib/package-validator.js.map +0 -1
  135. package/architecture/lib/package-validator.ts +0 -170
  136. package/eslint-plugin/__tests__/catch-error-pattern.test.ts +0 -374
  137. package/eslint-plugin/__tests__/max-file-lines.test.ts +0 -207
  138. package/eslint-plugin/__tests__/max-method-lines.test.ts +0 -258
  139. package/eslint-plugin/__tests__/no-unmanaged-exceptions.test.ts +0 -359
  140. package/eslint-plugin/index.d.ts +0 -23
  141. package/eslint-plugin/index.js +0 -30
  142. package/eslint-plugin/index.js.map +0 -1
  143. package/eslint-plugin/index.ts +0 -29
  144. package/eslint-plugin/rules/catch-error-pattern.d.ts +0 -11
  145. package/eslint-plugin/rules/catch-error-pattern.js +0 -143
  146. package/eslint-plugin/rules/catch-error-pattern.js.map +0 -1
  147. package/eslint-plugin/rules/catch-error-pattern.ts +0 -246
  148. package/eslint-plugin/rules/enforce-architecture.d.ts +0 -15
  149. package/eslint-plugin/rules/enforce-architecture.js +0 -476
  150. package/eslint-plugin/rules/enforce-architecture.js.map +0 -1
  151. package/eslint-plugin/rules/enforce-architecture.ts +0 -543
  152. package/eslint-plugin/rules/max-file-lines.d.ts +0 -12
  153. package/eslint-plugin/rules/max-file-lines.js +0 -257
  154. package/eslint-plugin/rules/max-file-lines.js.map +0 -1
  155. package/eslint-plugin/rules/max-file-lines.ts +0 -272
  156. package/eslint-plugin/rules/max-method-lines.d.ts +0 -12
  157. package/eslint-plugin/rules/max-method-lines.js +0 -240
  158. package/eslint-plugin/rules/max-method-lines.js.map +0 -1
  159. package/eslint-plugin/rules/max-method-lines.ts +0 -287
  160. package/eslint-plugin/rules/no-unmanaged-exceptions.d.ts +0 -22
  161. package/eslint-plugin/rules/no-unmanaged-exceptions.js +0 -160
  162. package/eslint-plugin/rules/no-unmanaged-exceptions.js.map +0 -1
  163. package/eslint-plugin/rules/no-unmanaged-exceptions.ts +0 -179
  164. package/executors/help/executor.js.map +0 -1
  165. package/executors/help/executor.ts +0 -61
  166. package/executors/validate-eslint-sync/executor.js.map +0 -1
  167. package/executors/validate-eslint-sync/executor.ts +0 -87
  168. package/executors/validate-versions-locked/executor.js.map +0 -1
  169. package/executors/validate-versions-locked/executor.ts +0 -368
  170. package/plugin/README.md +0 -243
  171. package/plugin/index.d.ts +0 -4
  172. package/plugin/index.js +0 -8
  173. package/plugin/index.js.map +0 -1
  174. package/plugin/index.ts +0 -4
  175. /package/{executors → src/executors}/help/executor.js +0 -0
  176. /package/{executors → src/executors}/help/schema.json +0 -0
  177. /package/{executors → src/executors}/validate-eslint-sync/executor.d.ts +0 -0
  178. /package/{executors → src/executors}/validate-eslint-sync/executor.js +0 -0
  179. /package/{executors → src/executors}/validate-eslint-sync/schema.json +0 -0
  180. /package/{executors → src/executors}/validate-versions-locked/executor.d.ts +0 -0
  181. /package/{executors → src/executors}/validate-versions-locked/schema.json +0 -0
@@ -1,571 +0,0 @@
1
- /**
2
- * Validate Modified Files Executor
3
- *
4
- * Validates that modified files don't exceed a maximum line count (default 900).
5
- * This encourages keeping files small and focused - when you touch a file,
6
- * you must bring it under the limit.
7
- *
8
- * Usage:
9
- * nx affected --target=validate-modified-files --base=origin/main
10
- *
11
- * Escape hatch: Add webpieces-disable max-lines-modified-files comment with date and justification
12
- * Format: // webpieces-disable max-lines-modified-files 2025/01/15 -- [reason]
13
- * The disable expires after 1 month from the date specified.
14
- */
15
-
16
- import type { ExecutorContext } from '@nx/devkit';
17
- import { execSync } from 'child_process';
18
- import * as fs from 'fs';
19
- import * as path from 'path';
20
-
21
- export type FileMaxLimitMode = 'OFF' | 'MODIFIED_FILES';
22
-
23
- export interface ValidateModifiedFilesOptions {
24
- limit?: number;
25
- mode?: FileMaxLimitMode;
26
- disableAllowed?: boolean;
27
- }
28
-
29
- export interface ExecutorResult {
30
- success: boolean;
31
- }
32
-
33
- interface FileViolation {
34
- file: string;
35
- lines: number;
36
- expiredDisable?: boolean;
37
- expiredDate?: string;
38
- }
39
-
40
- const TMP_DIR = 'tmp/webpieces';
41
- const TMP_MD_FILE = 'webpieces.filesize.md';
42
-
43
- const FILESIZE_DOC_CONTENT = `# AI Agent Instructions: File Too Long
44
-
45
- **READ THIS FILE to fix files that are too long**
46
-
47
- ## Core Principle
48
-
49
- With **stateless systems + dependency injection, refactor is trivial**.
50
- Pick a method or a few and move to new class XXXXX, then inject XXXXX
51
- into all users of those methods via the constructor.
52
- Delete those methods from original class.
53
-
54
- **99% of files can be less than the configured max lines of code.**
55
-
56
- Files should contain a SINGLE COHESIVE UNIT.
57
- - One class per file (Java convention)
58
- - If class is too large, extract child responsibilities
59
- - Use dependency injection to compose functionality
60
-
61
- ## Command: Reduce File Size
62
-
63
- ### Step 1: Check for Multiple Classes
64
- If the file contains multiple classes, **SEPARATE each class into its own file**.
65
-
66
- \`\`\`typescript
67
- // BAD: UserController.ts (multiple classes)
68
- export class UserController { /* ... */ }
69
- export class UserValidator { /* ... */ }
70
- export class UserNotifier { /* ... */ }
71
-
72
- // GOOD: Three separate files
73
- // UserController.ts
74
- export class UserController { /* ... */ }
75
-
76
- // UserValidator.ts
77
- export class UserValidator { /* ... */ }
78
-
79
- // UserNotifier.ts
80
- export class UserNotifier { /* ... */ }
81
- \`\`\`
82
-
83
- ### Step 2: Extract Child Responsibilities (if single class is too large)
84
-
85
- #### Pattern: Create New Service Class with Dependency Injection
86
-
87
- \`\`\`typescript
88
- // BAD: UserController.ts (800 lines, single class)
89
- @provideSingleton()
90
- @Controller()
91
- export class UserController {
92
- // 200 lines: CRUD operations
93
- // 300 lines: validation logic
94
- // 200 lines: notification logic
95
- // 100 lines: analytics logic
96
- }
97
-
98
- // GOOD: Extract validation service
99
- // 1. Create UserValidationService.ts
100
- @provideSingleton()
101
- export class UserValidationService {
102
- validateUserData(data: UserData): ValidationResult {
103
- // 300 lines of validation logic moved here
104
- }
105
-
106
- validateEmail(email: string): boolean { /* ... */ }
107
- validatePassword(password: string): boolean { /* ... */ }
108
- }
109
-
110
- // 2. Inject into UserController.ts
111
- @provideSingleton()
112
- @Controller()
113
- export class UserController {
114
- constructor(
115
- @inject(TYPES.UserValidationService)
116
- private validator: UserValidationService
117
- ) {}
118
-
119
- async createUser(data: UserData): Promise<User> {
120
- const validation = this.validator.validateUserData(data);
121
- if (!validation.isValid) {
122
- throw new ValidationError(validation.errors);
123
- }
124
- // ... rest of logic
125
- }
126
- }
127
- \`\`\`
128
-
129
- ## AI Agent Action Steps
130
-
131
- 1. **ANALYZE** the file structure:
132
- - Count classes (if >1, separate immediately)
133
- - Identify logical responsibilities within single class
134
-
135
- 2. **IDENTIFY** "child code" to extract:
136
- - Validation logic -> ValidationService
137
- - Notification logic -> NotificationService
138
- - Data transformation -> TransformerService
139
- - External API calls -> ApiService
140
- - Business rules -> RulesEngine
141
-
142
- 3. **CREATE** new service file(s):
143
- - Start with temporary name: \`XXXX.ts\` or \`ChildService.ts\`
144
- - Add \`@provideSingleton()\` decorator
145
- - Move child methods to new class
146
-
147
- 4. **UPDATE** dependency injection:
148
- - Add to \`TYPES\` constants (if using symbol-based DI)
149
- - Inject new service into original class constructor
150
- - Replace direct method calls with \`this.serviceName.method()\`
151
-
152
- 5. **RENAME** extracted file:
153
- - Read the extracted code to understand its purpose
154
- - Rename \`XXXX.ts\` to logical name (e.g., \`UserValidationService.ts\`)
155
-
156
- 6. **VERIFY** file sizes:
157
- - Original file should now be under the limit
158
- - Each extracted file should be under the limit
159
- - If still too large, extract more services
160
-
161
- ## Examples of Child Responsibilities to Extract
162
-
163
- | If File Contains | Extract To | Pattern |
164
- |-----------------|------------|---------|
165
- | Validation logic (200+ lines) | \`XValidator.ts\` or \`XValidationService.ts\` | Singleton service |
166
- | Notification logic (150+ lines) | \`XNotifier.ts\` or \`XNotificationService.ts\` | Singleton service |
167
- | Data transformation (200+ lines) | \`XTransformer.ts\` | Singleton service |
168
- | External API calls (200+ lines) | \`XApiClient.ts\` | Singleton service |
169
- | Complex business rules (300+ lines) | \`XRulesEngine.ts\` | Singleton service |
170
- | Database queries (200+ lines) | \`XRepository.ts\` | Singleton service |
171
-
172
- ## WebPieces Dependency Injection Pattern
173
-
174
- \`\`\`typescript
175
- // 1. Define service with @provideSingleton
176
- import { provideSingleton } from '@webpieces/http-routing';
177
-
178
- @provideSingleton()
179
- export class MyService {
180
- doSomething(): void { /* ... */ }
181
- }
182
-
183
- // 2. Inject into consumer
184
- import { inject } from 'inversify';
185
- import { TYPES } from './types';
186
-
187
- @provideSingleton()
188
- @Controller()
189
- export class MyController {
190
- constructor(
191
- @inject(TYPES.MyService) private service: MyService
192
- ) {}
193
- }
194
- \`\`\`
195
-
196
- ## Escape Hatch
197
-
198
- If refactoring is genuinely not feasible (generated files, complex algorithms, etc.),
199
- add a disable comment at the TOP of the file (within first 5 lines) with a DATE:
200
-
201
- \`\`\`typescript
202
- // webpieces-disable max-lines-modified-files 2025/01/15 -- Complex generated file, refactoring would break generation
203
- \`\`\`
204
-
205
- **IMPORTANT**: The date format is yyyy/mm/dd. The disable will EXPIRE after 1 month from this date.
206
- After expiration, you must either fix the file or update the date to get another month.
207
- This ensures that disable comments are reviewed periodically.
208
-
209
- Remember: Find the "child code" and pull it down into a new class. Once moved, the code's purpose becomes clear, making it easy to rename to a logical name.
210
- `;
211
-
212
- /**
213
- * Write the instructions documentation to tmp directory
214
- */
215
- function writeTmpInstructions(workspaceRoot: string): string {
216
- const tmpDir = path.join(workspaceRoot, TMP_DIR);
217
- const mdPath = path.join(tmpDir, TMP_MD_FILE);
218
-
219
- fs.mkdirSync(tmpDir, { recursive: true });
220
- fs.writeFileSync(mdPath, FILESIZE_DOC_CONTENT);
221
-
222
- return mdPath;
223
- }
224
-
225
- /**
226
- * Get changed TypeScript files between base and head (or working tree if head not specified).
227
- * Uses `git diff base [head]` to match what `nx affected` does.
228
- * When head is NOT specified, also includes untracked files (matching nx affected behavior).
229
- */
230
- function getChangedTypeScriptFiles(workspaceRoot: string, base: string, head?: string): string[] {
231
- try {
232
- // If head is specified, diff base to head; otherwise diff base to working tree
233
- const diffTarget = head ? `${base} ${head}` : base;
234
- const output = execSync(`git diff --name-only ${diffTarget} -- '*.ts' '*.tsx'`, {
235
- cwd: workspaceRoot,
236
- encoding: 'utf-8',
237
- });
238
- const changedFiles = output
239
- .trim()
240
- .split('\n')
241
- .filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));
242
-
243
- // When comparing to working tree (no head specified), also include untracked files
244
- // This matches what nx affected does: "All modified files not yet committed or tracked will also be added"
245
- if (!head) {
246
- try {
247
- const untrackedOutput = execSync(`git ls-files --others --exclude-standard '*.ts' '*.tsx'`, {
248
- cwd: workspaceRoot,
249
- encoding: 'utf-8',
250
- });
251
- const untrackedFiles = untrackedOutput
252
- .trim()
253
- .split('\n')
254
- .filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));
255
- // Merge and dedupe
256
- const allFiles = new Set([...changedFiles, ...untrackedFiles]);
257
- return Array.from(allFiles);
258
- } catch {
259
- // If ls-files fails, just return the changed files
260
- return changedFiles;
261
- }
262
- }
263
-
264
- return changedFiles;
265
- } catch {
266
- return [];
267
- }
268
- }
269
-
270
- /**
271
- * Parse a date string in yyyy/mm/dd format and return a Date object.
272
- * Returns null if the format is invalid.
273
- */
274
- function parseDisableDate(dateStr: string): Date | null {
275
- // Match yyyy/mm/dd format
276
- const match = dateStr.match(/^(\d{4})\/(\d{2})\/(\d{2})$/);
277
- if (!match) return null;
278
-
279
- const year = parseInt(match[1], 10);
280
- const month = parseInt(match[2], 10) - 1; // JS months are 0-indexed
281
- const day = parseInt(match[3], 10);
282
-
283
- const date = new Date(year, month, day);
284
-
285
- // Validate the date is valid (e.g., not Feb 30)
286
- if (date.getFullYear() !== year || date.getMonth() !== month || date.getDate() !== day) {
287
- return null;
288
- }
289
-
290
- return date;
291
- }
292
-
293
- /**
294
- * Check if a date is within the last month (not expired).
295
- */
296
- function isDateWithinMonth(date: Date): boolean {
297
- const now = new Date();
298
- const oneMonthAgo = new Date(now.getFullYear(), now.getMonth() - 1, now.getDate());
299
- return date >= oneMonthAgo;
300
- }
301
-
302
- interface DisableStatus {
303
- hasDisable: boolean;
304
- isValid: boolean;
305
- isExpired: boolean;
306
- date?: string;
307
- }
308
-
309
- /**
310
- * Check if a file has a valid, non-expired disable comment at the top (within first 5 lines).
311
- * Returns status object with details about the disable comment.
312
- */
313
- // webpieces-disable max-lines-new-methods -- Date validation logic requires checking multiple conditions
314
- function checkDisableComment(content: string): DisableStatus {
315
- const lines = content.split('\n').slice(0, 5);
316
-
317
- for (const line of lines) {
318
- if (line.includes('webpieces-disable') && line.includes('max-lines-modified-files')) {
319
- // Found disable comment, now check for date
320
- // Format: // webpieces-disable max-lines-modified-files yyyy/mm/dd -- reason
321
- const dateMatch = line.match(/max-lines-modified-files\s+(\d{4}\/\d{2}\/\d{2}|XXXX\/XX\/XX)/);
322
-
323
- if (!dateMatch) {
324
- // No date found - invalid disable comment
325
- return { hasDisable: true, isValid: false, isExpired: false };
326
- }
327
-
328
- const dateStr = dateMatch[1];
329
-
330
- // Secret permanent disable
331
- if (dateStr === 'XXXX/XX/XX') {
332
- return { hasDisable: true, isValid: true, isExpired: false, date: dateStr };
333
- }
334
-
335
- const date = parseDisableDate(dateStr);
336
- if (!date) {
337
- // Invalid date format
338
- return { hasDisable: true, isValid: false, isExpired: false, date: dateStr };
339
- }
340
-
341
- if (!isDateWithinMonth(date)) {
342
- // Date is expired (older than 1 month)
343
- return { hasDisable: true, isValid: true, isExpired: true, date: dateStr };
344
- }
345
-
346
- // Valid and not expired
347
- return { hasDisable: true, isValid: true, isExpired: false, date: dateStr };
348
- }
349
- }
350
-
351
- return { hasDisable: false, isValid: false, isExpired: false };
352
- }
353
-
354
- /**
355
- * Count lines in a file and check for violations
356
- */
357
- // webpieces-disable max-lines-new-methods -- File iteration with disable checking logic
358
- function findViolations(workspaceRoot: string, changedFiles: string[], limit: number, disableAllowed: boolean): FileViolation[] {
359
- const violations: FileViolation[] = [];
360
-
361
- for (const file of changedFiles) {
362
- const fullPath = path.join(workspaceRoot, file);
363
-
364
- if (!fs.existsSync(fullPath)) continue;
365
-
366
- const content = fs.readFileSync(fullPath, 'utf-8');
367
- const lineCount = content.split('\n').length;
368
-
369
- // Skip files under the limit
370
- if (lineCount <= limit) continue;
371
-
372
- // When disableAllowed is false, ignore all disable comments
373
- if (!disableAllowed) {
374
- violations.push({ file, lines: lineCount });
375
- continue;
376
- }
377
-
378
- // Check for disable comment
379
- const disableStatus = checkDisableComment(content);
380
-
381
- if (disableStatus.hasDisable) {
382
- if (disableStatus.isValid && !disableStatus.isExpired) {
383
- // Valid, non-expired disable - skip this file
384
- continue;
385
- }
386
-
387
- if (disableStatus.isExpired) {
388
- // Expired disable - report as violation with expired info
389
- violations.push({
390
- file,
391
- lines: lineCount,
392
- expiredDisable: true,
393
- expiredDate: disableStatus.date,
394
- });
395
- continue;
396
- }
397
-
398
- // Invalid disable (missing/bad date) - fall through to report as violation
399
- }
400
-
401
- violations.push({
402
- file,
403
- lines: lineCount,
404
- });
405
- }
406
-
407
- return violations;
408
- }
409
-
410
- /**
411
- * Auto-detect the base branch by finding the merge-base with origin/main.
412
- */
413
- function detectBase(workspaceRoot: string): string | null {
414
- try {
415
- const mergeBase = execSync('git merge-base HEAD origin/main', {
416
- cwd: workspaceRoot,
417
- encoding: 'utf-8',
418
- stdio: ['pipe', 'pipe', 'pipe'],
419
- }).trim();
420
-
421
- if (mergeBase) {
422
- return mergeBase;
423
- }
424
- } catch {
425
- try {
426
- const mergeBase = execSync('git merge-base HEAD main', {
427
- cwd: workspaceRoot,
428
- encoding: 'utf-8',
429
- stdio: ['pipe', 'pipe', 'pipe'],
430
- }).trim();
431
-
432
- if (mergeBase) {
433
- return mergeBase;
434
- }
435
- } catch {
436
- // Ignore
437
- }
438
- }
439
- return null;
440
- }
441
-
442
- /**
443
- * Get today's date in yyyy/mm/dd format for error messages
444
- */
445
- function getTodayDateString(): string {
446
- const now = new Date();
447
- const year = now.getFullYear();
448
- const month = String(now.getMonth() + 1).padStart(2, '0');
449
- const day = String(now.getDate()).padStart(2, '0');
450
- return `${year}/${month}/${day}`;
451
- }
452
-
453
- /**
454
- * Report violations to console
455
- */
456
- // webpieces-disable max-lines-new-methods -- Error output formatting with multiple message sections
457
- function reportViolations(violations: FileViolation[], limit: number, disableAllowed: boolean): void {
458
- console.error('');
459
- console.error('\u274c YOU MUST FIX THIS AND NOT be more than ' + limit + ' lines of code per file');
460
- console.error(' as it slows down IDEs AND is VERY VERY EASY to refactor.');
461
- console.error('');
462
- console.error('\ud83d\udcda With stateless systems + dependency injection, refactor is trivial:');
463
- console.error(' Pick a method or a few and move to new class XXXXX, then inject XXXXX');
464
- console.error(' into all users of those methods via the constructor.');
465
- console.error(' Delete those methods from original class.');
466
- console.error(' 99% of files can be less than ' + limit + ' lines of code.');
467
- console.error('');
468
- console.error('\u26a0\ufe0f *** READ tmp/webpieces/webpieces.filesize.md for detailed guidance on how to fix this easily *** \u26a0\ufe0f');
469
- console.error('');
470
-
471
- for (const v of violations) {
472
- if (v.expiredDisable) {
473
- console.error(` \u274c ${v.file} (${v.lines} lines, max: ${limit})`);
474
- console.error(` \u23f0 EXPIRED DISABLE: Your disable comment dated ${v.expiredDate} has expired (>1 month old).`);
475
- console.error(` You must either FIX the file or UPDATE the date to get another month.`);
476
- } else {
477
- console.error(` \u274c ${v.file} (${v.lines} lines, max: ${limit})`);
478
- }
479
- }
480
- console.error('');
481
-
482
- // Only show escape hatch instructions when disableAllowed is true
483
- if (disableAllowed) {
484
- console.error(' You can disable this error, but you will be forced to fix again in 1 month');
485
- console.error(' since 99% of files can be less than ' + limit + ' lines of code.');
486
- console.error('');
487
- console.error(' Use escape with DATE (expires in 1 month):');
488
- console.error(` // webpieces-disable max-lines-modified-files ${getTodayDateString()} -- [your reason]`);
489
- console.error('');
490
- } else {
491
- console.error(' \u26a0\ufe0f disableAllowed is false - disable comments are NOT allowed.');
492
- console.error(' This rule must be met and cannot be disabled since nx.json disableAllowed is set to false.');
493
- console.error(' You MUST refactor to reduce file size.');
494
- console.error('');
495
- console.error(' For a major refactor, a human can add "ignoreModifiedUntilEpoch" to nx.json validate-code options.');
496
- console.error(' This is an expiry timestamp (epoch ms) for when we start forcing everyone to meet size rules again.');
497
- console.error(' Sometimes for speed, we allow files to expand during a refactor and over time,');
498
- console.error(' each PR reduces files as they get touched.');
499
- console.error(' AI agents should NOT add ignoreModifiedUntilEpoch - ask a human to do it.');
500
- console.error('');
501
- }
502
- }
503
-
504
- export default async function runExecutor(
505
- options: ValidateModifiedFilesOptions,
506
- context: ExecutorContext
507
- ): Promise<ExecutorResult> {
508
- const workspaceRoot = context.root;
509
- const limit = options.limit ?? 900;
510
- const mode: FileMaxLimitMode = options.mode ?? 'MODIFIED_FILES';
511
- const disableAllowed = options.disableAllowed ?? true;
512
-
513
- // Skip validation entirely if mode is OFF
514
- if (mode === 'OFF') {
515
- console.log('\n\u23ed\ufe0f Skipping modified files validation (mode: OFF)');
516
- console.log('');
517
- return { success: true };
518
- }
519
-
520
- // If NX_HEAD is set (via nx affected --head=X), use it; otherwise compare to working tree
521
- let base = process.env['NX_BASE'];
522
- const head = process.env['NX_HEAD'];
523
-
524
- if (!base) {
525
- base = detectBase(workspaceRoot) ?? undefined;
526
-
527
- if (!base) {
528
- console.log('\n\u23ed\ufe0f Skipping modified files validation (could not detect base branch)');
529
- console.log(' To run explicitly: nx affected --target=validate-modified-files --base=origin/main');
530
- console.log('');
531
- return { success: true };
532
- }
533
-
534
- console.log('\n\ud83d\udccf Validating Modified File Sizes (auto-detected base)\n');
535
- } else {
536
- console.log('\n\ud83d\udccf Validating Modified File Sizes\n');
537
- }
538
-
539
- console.log(` Base: ${base}`);
540
- console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);
541
- console.log(` Mode: ${mode}`);
542
- console.log(` Max lines for modified files: ${limit}`);
543
- console.log(` Disable allowed: ${disableAllowed}${!disableAllowed ? ' (no escape hatch)' : ''}`);
544
- console.log('');
545
-
546
- try {
547
- const changedFiles = getChangedTypeScriptFiles(workspaceRoot, base, head);
548
-
549
- if (changedFiles.length === 0) {
550
- console.log('\u2705 No TypeScript files changed');
551
- return { success: true };
552
- }
553
-
554
- console.log(`\ud83d\udcc2 Checking ${changedFiles.length} changed file(s)...`);
555
-
556
- const violations = findViolations(workspaceRoot, changedFiles, limit, disableAllowed);
557
-
558
- if (violations.length === 0) {
559
- console.log('\u2705 All modified files are under ' + limit + ' lines');
560
- return { success: true };
561
- }
562
-
563
- writeTmpInstructions(workspaceRoot);
564
- reportViolations(violations, limit, disableAllowed);
565
- return { success: false };
566
- } catch (err: unknown) {
567
- const error = err instanceof Error ? err : new Error(String(err));
568
- console.error('\u274c Modified files validation failed:', error.message);
569
- return { success: false };
570
- }
571
- }
@@ -1,25 +0,0 @@
1
- {
2
- "$schema": "http://json-schema.org/schema",
3
- "title": "Validate Modified Files Executor",
4
- "description": "Validates that modified files don't exceed a maximum line count. Encourages keeping files small and focused.",
5
- "type": "object",
6
- "properties": {
7
- "limit": {
8
- "type": "number",
9
- "description": "Maximum number of lines allowed for modified files",
10
- "default": 900
11
- },
12
- "mode": {
13
- "type": "string",
14
- "enum": ["OFF", "MODIFIED_FILES"],
15
- "description": "OFF: skip validation. MODIFIED_FILES: validate all code in modified files.",
16
- "default": "MODIFIED_FILES"
17
- },
18
- "disableAllowed": {
19
- "type": "boolean",
20
- "description": "Whether disable comments work. When false, no escape hatch (like old STRICT mode).",
21
- "default": true
22
- }
23
- },
24
- "required": []
25
- }
@@ -1,31 +0,0 @@
1
- /**
2
- * Validate Modified Methods Executor
3
- *
4
- * Validates that modified methods don't exceed a maximum line count (default 80).
5
- * This encourages gradual cleanup of legacy long methods - when you touch a method,
6
- * you must bring it under the limit.
7
- *
8
- * Combined with validate-new-methods, this creates a gradual
9
- * transition to cleaner code:
10
- * - New methods: must be under limit
11
- * - Modified methods: must be under limit (cleanup when touched)
12
- * - Untouched methods: no limit (legacy allowed)
13
- *
14
- * Usage:
15
- * nx affected --target=validate-modified-methods --base=origin/main
16
- *
17
- * Escape hatch: Add webpieces-disable max-lines-modified comment with date and justification
18
- * Format: // webpieces-disable max-lines-modified 2025/01/15 -- [reason]
19
- * The disable expires after 1 month from the date specified.
20
- */
21
- import type { ExecutorContext } from '@nx/devkit';
22
- export type MethodMaxLimitMode = 'OFF' | 'NEW_METHODS' | 'NEW_AND_MODIFIED_METHODS' | 'MODIFIED_FILES';
23
- export interface ValidateModifiedMethodsOptions {
24
- limit?: number;
25
- mode?: MethodMaxLimitMode;
26
- disableAllowed?: boolean;
27
- }
28
- export interface ExecutorResult {
29
- success: boolean;
30
- }
31
- export default function runExecutor(options: ValidateModifiedMethodsOptions, context: ExecutorContext): Promise<ExecutorResult>;