@webpieces/dev-config 0.2.65 → 0.2.67

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,13 @@
1
+ import { ExecutorContext } from '@nx/devkit';
2
+ export type ValidationMode = 'STRICT' | 'NORMAL' | 'OFF';
3
+ export interface ValidateCodeOptions {
4
+ mode?: ValidationMode;
5
+ newMethodsMaxLines?: number;
6
+ strictNewMethodMaxLines?: number;
7
+ modifiedMethodsMaxLines?: number;
8
+ modifiedFilesMaxLines?: number;
9
+ }
10
+ export interface ExecutorResult {
11
+ success: boolean;
12
+ }
13
+ export default function runExecutor(options: ValidateCodeOptions, context: ExecutorContext): Promise<ExecutorResult>;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = runExecutor;
4
+ const tslib_1 = require("tslib");
5
+ const executor_1 = tslib_1.__importDefault(require("../validate-new-methods/executor"));
6
+ const executor_2 = tslib_1.__importDefault(require("../validate-modified-methods/executor"));
7
+ const executor_3 = tslib_1.__importDefault(require("../validate-modified-files/executor"));
8
+ async function runExecutor(options, context) {
9
+ const mode = options.mode ?? 'NORMAL';
10
+ if (mode === 'OFF') {
11
+ console.log('\n⏭️ Skipping all code validations (validationMode: OFF)\n');
12
+ return { success: true };
13
+ }
14
+ console.log('\n📏 Running Code Validations\n');
15
+ console.log(` Validation mode: ${mode}${mode === 'STRICT' ? ' (disable comments ignored for modified code)' : ''}`);
16
+ console.log(` New methods max: ${options.newMethodsMaxLines ?? 30} lines (soft limit)`);
17
+ if (options.strictNewMethodMaxLines) {
18
+ console.log(` New methods max: ${options.strictNewMethodMaxLines} lines (hard limit, no escape)`);
19
+ }
20
+ console.log(` Modified methods max: ${options.modifiedMethodsMaxLines ?? 80} lines`);
21
+ console.log(` Modified files max: ${options.modifiedFilesMaxLines ?? 900} lines`);
22
+ console.log('');
23
+ // Run all three validators sequentially to avoid interleaved output
24
+ const newMethodsResult = await (0, executor_1.default)({ max: options.newMethodsMaxLines ?? 30, strictMax: options.strictNewMethodMaxLines, mode }, context);
25
+ const modifiedMethodsResult = await (0, executor_2.default)({ max: options.modifiedMethodsMaxLines ?? 80, mode }, context);
26
+ const modifiedFilesResult = await (0, executor_3.default)({ max: options.modifiedFilesMaxLines ?? 900, mode }, context);
27
+ const allSuccess = newMethodsResult.success && modifiedMethodsResult.success && modifiedFilesResult.success;
28
+ if (allSuccess) {
29
+ console.log('\n✅ All code validations passed\n');
30
+ }
31
+ else {
32
+ console.log('\n❌ Some code validations failed\n');
33
+ }
34
+ return { success: allSuccess };
35
+ }
36
+ //# sourceMappingURL=executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/dev-config/architecture/executors/validate-code/executor.ts"],"names":[],"mappings":";;AAmBA,8BA8CC;;AAhED,wFAAqE;AACrE,6FAA+E;AAC/E,2FAA2E;AAgB5D,KAAK,UAAU,WAAW,CACrC,OAA4B,EAC5B,OAAwB;IAExB,MAAM,IAAI,GAAmB,OAAO,CAAC,IAAI,IAAI,QAAQ,CAAC;IAEtD,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;QACjB,OAAO,CAAC,GAAG,CAAC,6DAA6D,CAAC,CAAC;QAC3E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC7B,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;IAC/C,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,GAAG,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,+CAA+C,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACtH,OAAO,CAAC,GAAG,CAAC,uBAAuB,OAAO,CAAC,kBAAkB,IAAI,EAAE,qBAAqB,CAAC,CAAC;IAC1F,IAAI,OAAO,CAAC,uBAAuB,EAAE,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,uBAAuB,OAAO,CAAC,uBAAuB,gCAAgC,CAAC,CAAC;IACxG,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,4BAA4B,OAAO,CAAC,uBAAuB,IAAI,EAAE,QAAQ,CAAC,CAAC;IACvF,OAAO,CAAC,GAAG,CAAC,0BAA0B,OAAO,CAAC,qBAAqB,IAAI,GAAG,QAAQ,CAAC,CAAC;IACpF,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,oEAAoE;IACpE,MAAM,gBAAgB,GAAG,MAAM,IAAA,kBAAqB,EAChD,EAAE,GAAG,EAAE,OAAO,CAAC,kBAAkB,IAAI,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,uBAAuB,EAAE,IAAI,EAAE,EAC3F,OAAO,CACV,CAAC;IAEF,MAAM,qBAAqB,GAAG,MAAM,IAAA,kBAA0B,EAC1D,EAAE,GAAG,EAAE,OAAO,CAAC,uBAAuB,IAAI,EAAE,EAAE,IAAI,EAAE,EACpD,OAAO,CACV,CAAC;IAEF,MAAM,mBAAmB,GAAG,MAAM,IAAA,kBAAwB,EACtD,EAAE,GAAG,EAAE,OAAO,CAAC,qBAAqB,IAAI,GAAG,EAAE,IAAI,EAAE,EACnD,OAAO,CACV,CAAC;IAEF,MAAM,UAAU,GAAG,gBAAgB,CAAC,OAAO,IAAI,qBAAqB,CAAC,OAAO,IAAI,mBAAmB,CAAC,OAAO,CAAC;IAE5G,IAAI,UAAU,EAAE,CAAC;QACb,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;IACrD,CAAC;SAAM,CAAC;QACJ,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;IACtD,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;AACnC,CAAC","sourcesContent":["import { ExecutorContext } from '@nx/devkit';\nimport runNewMethodsExecutor from '../validate-new-methods/executor';\nimport runModifiedMethodsExecutor from '../validate-modified-methods/executor';\nimport runModifiedFilesExecutor from '../validate-modified-files/executor';\n\nexport type ValidationMode = 'STRICT' | 'NORMAL' | 'OFF';\n\nexport interface ValidateCodeOptions {\n mode?: ValidationMode;\n newMethodsMaxLines?: number;\n strictNewMethodMaxLines?: number;\n modifiedMethodsMaxLines?: number;\n modifiedFilesMaxLines?: number;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nexport default async function runExecutor(\n options: ValidateCodeOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const mode: ValidationMode = options.mode ?? 'NORMAL';\n\n if (mode === 'OFF') {\n console.log('\\n⏭️ Skipping all code validations (validationMode: OFF)\\n');\n return { success: true };\n }\n\n console.log('\\n📏 Running Code Validations\\n');\n console.log(` Validation mode: ${mode}${mode === 'STRICT' ? ' (disable comments ignored for modified code)' : ''}`);\n console.log(` New methods max: ${options.newMethodsMaxLines ?? 30} lines (soft limit)`);\n if (options.strictNewMethodMaxLines) {\n console.log(` New methods max: ${options.strictNewMethodMaxLines} lines (hard limit, no escape)`);\n }\n console.log(` Modified methods max: ${options.modifiedMethodsMaxLines ?? 80} lines`);\n console.log(` Modified files max: ${options.modifiedFilesMaxLines ?? 900} lines`);\n console.log('');\n\n // Run all three validators sequentially to avoid interleaved output\n const newMethodsResult = await runNewMethodsExecutor(\n { max: options.newMethodsMaxLines ?? 30, strictMax: options.strictNewMethodMaxLines, mode },\n context\n );\n\n const modifiedMethodsResult = await runModifiedMethodsExecutor(\n { max: options.modifiedMethodsMaxLines ?? 80, mode },\n context\n );\n\n const modifiedFilesResult = await runModifiedFilesExecutor(\n { max: options.modifiedFilesMaxLines ?? 900, mode },\n context\n );\n\n const allSuccess = newMethodsResult.success && modifiedMethodsResult.success && modifiedFilesResult.success;\n\n if (allSuccess) {\n console.log('\\n✅ All code validations passed\\n');\n } else {\n console.log('\\n❌ Some code validations failed\\n');\n }\n\n return { success: allSuccess };\n}\n"]}
@@ -0,0 +1,66 @@
1
+ import { ExecutorContext } from '@nx/devkit';
2
+ import runNewMethodsExecutor from '../validate-new-methods/executor';
3
+ import runModifiedMethodsExecutor from '../validate-modified-methods/executor';
4
+ import runModifiedFilesExecutor from '../validate-modified-files/executor';
5
+
6
+ export type ValidationMode = 'STRICT' | 'NORMAL' | 'OFF';
7
+
8
+ export interface ValidateCodeOptions {
9
+ mode?: ValidationMode;
10
+ newMethodsMaxLines?: number;
11
+ strictNewMethodMaxLines?: number;
12
+ modifiedMethodsMaxLines?: number;
13
+ modifiedFilesMaxLines?: number;
14
+ }
15
+
16
+ export interface ExecutorResult {
17
+ success: boolean;
18
+ }
19
+
20
+ export default async function runExecutor(
21
+ options: ValidateCodeOptions,
22
+ context: ExecutorContext
23
+ ): Promise<ExecutorResult> {
24
+ const mode: ValidationMode = options.mode ?? 'NORMAL';
25
+
26
+ if (mode === 'OFF') {
27
+ console.log('\n⏭️ Skipping all code validations (validationMode: OFF)\n');
28
+ return { success: true };
29
+ }
30
+
31
+ console.log('\n📏 Running Code Validations\n');
32
+ console.log(` Validation mode: ${mode}${mode === 'STRICT' ? ' (disable comments ignored for modified code)' : ''}`);
33
+ console.log(` New methods max: ${options.newMethodsMaxLines ?? 30} lines (soft limit)`);
34
+ if (options.strictNewMethodMaxLines) {
35
+ console.log(` New methods max: ${options.strictNewMethodMaxLines} lines (hard limit, no escape)`);
36
+ }
37
+ console.log(` Modified methods max: ${options.modifiedMethodsMaxLines ?? 80} lines`);
38
+ console.log(` Modified files max: ${options.modifiedFilesMaxLines ?? 900} lines`);
39
+ console.log('');
40
+
41
+ // Run all three validators sequentially to avoid interleaved output
42
+ const newMethodsResult = await runNewMethodsExecutor(
43
+ { max: options.newMethodsMaxLines ?? 30, strictMax: options.strictNewMethodMaxLines, mode },
44
+ context
45
+ );
46
+
47
+ const modifiedMethodsResult = await runModifiedMethodsExecutor(
48
+ { max: options.modifiedMethodsMaxLines ?? 80, mode },
49
+ context
50
+ );
51
+
52
+ const modifiedFilesResult = await runModifiedFilesExecutor(
53
+ { max: options.modifiedFilesMaxLines ?? 900, mode },
54
+ context
55
+ );
56
+
57
+ const allSuccess = newMethodsResult.success && modifiedMethodsResult.success && modifiedFilesResult.success;
58
+
59
+ if (allSuccess) {
60
+ console.log('\n✅ All code validations passed\n');
61
+ } else {
62
+ console.log('\n❌ Some code validations failed\n');
63
+ }
64
+
65
+ return { success: allSuccess };
66
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "title": "Validate Code Executor",
4
+ "description": "Combined validation for new methods, modified methods, and file sizes. Configure via targetDefaults in nx.json for runtime options (no cache issues).",
5
+ "type": "object",
6
+ "properties": {
7
+ "mode": {
8
+ "type": "string",
9
+ "enum": ["STRICT", "NORMAL", "OFF"],
10
+ "description": "OFF: skip all validations. NORMAL: enforce limits, disable comments work. STRICT: enforce limits, no disable comments for modified code.",
11
+ "default": "NORMAL"
12
+ },
13
+ "newMethodsMaxLines": {
14
+ "type": "number",
15
+ "description": "Soft limit: Maximum lines for NEW methods (can be bypassed with disable comment)",
16
+ "default": 30
17
+ },
18
+ "strictNewMethodMaxLines": {
19
+ "type": "number",
20
+ "description": "Hard limit: Absolute maximum lines for NEW methods (CANNOT be bypassed). If not set, no hard limit is enforced."
21
+ },
22
+ "modifiedMethodsMaxLines": {
23
+ "type": "number",
24
+ "description": "Maximum lines for MODIFIED methods",
25
+ "default": 80
26
+ },
27
+ "modifiedFilesMaxLines": {
28
+ "type": "number",
29
+ "description": "Maximum lines for MODIFIED files",
30
+ "default": 900
31
+ }
32
+ },
33
+ "required": []
34
+ }
@@ -19,6 +19,7 @@ import type { ExecutorContext } from '@nx/devkit';
19
19
  export type ValidationMode = 'STRICT' | 'NORMAL' | 'OFF';
20
20
  export interface ValidateNewMethodsOptions {
21
21
  max?: number;
22
+ strictMax?: number;
22
23
  mode?: ValidationMode;
23
24
  }
24
25
  export interface ExecutorResult {
@@ -304,7 +304,7 @@ function findMethodsInFile(filePath, workspaceRoot) {
304
304
  /**
305
305
  * Find new methods that exceed the line limit
306
306
  */
307
- function findViolations(workspaceRoot, changedFiles, base, maxLines) {
307
+ function findViolations(workspaceRoot, changedFiles, base, maxLines, strictMaxLines) {
308
308
  const violations = [];
309
309
  for (const file of changedFiles) {
310
310
  // Get the diff to find which methods are NEW (not just modified)
@@ -315,14 +315,30 @@ function findViolations(workspaceRoot, changedFiles, base, maxLines) {
315
315
  // Parse the current file to get method line counts
316
316
  const methods = findMethodsInFile(file, workspaceRoot);
317
317
  for (const method of methods) {
318
- // Only check NEW methods that don't have webpieces-disable comment
319
- if (newMethodNames.has(method.name) && method.lines > maxLines && !method.hasDisableComment) {
318
+ if (!newMethodNames.has(method.name))
319
+ continue;
320
+ // Check hard limit first (if defined) - NO escape possible
321
+ if (strictMaxLines && method.lines > strictMaxLines) {
320
322
  violations.push({
321
323
  file,
322
324
  methodName: method.name,
323
325
  line: method.line,
324
326
  lines: method.lines,
325
327
  isNew: true,
328
+ isHardLimit: true,
329
+ limit: strictMaxLines,
330
+ });
331
+ }
332
+ // Check soft limit - can be escaped with disable comment
333
+ else if (method.lines > maxLines && !method.hasDisableComment) {
334
+ violations.push({
335
+ file,
336
+ methodName: method.name,
337
+ line: method.line,
338
+ lines: method.lines,
339
+ isNew: true,
340
+ isHardLimit: false,
341
+ limit: maxLines,
326
342
  });
327
343
  }
328
344
  }
@@ -366,9 +382,12 @@ function detectBase(workspaceRoot) {
366
382
  /**
367
383
  * Report violations to the user with helpful instructions
368
384
  */
369
- function reportViolations(violations, maxLines) {
385
+ // webpieces-disable max-lines-new-methods -- Console output formatting requires distinct sections for hard/soft violations
386
+ function reportViolations(violations, maxLines, strictMaxLines) {
387
+ const hardViolations = violations.filter((v) => v.isHardLimit);
388
+ const softViolations = violations.filter((v) => !v.isHardLimit);
370
389
  console.error('');
371
- console.error('❌ New methods exceed ' + maxLines + ' lines!');
390
+ console.error('❌ New methods exceed line limits!');
372
391
  console.error('');
373
392
  console.error('📚 Methods should read like a "table of contents" - each method call');
374
393
  console.error(' describes a larger piece of work. You can refactor');
@@ -376,18 +395,37 @@ function reportViolations(violations, maxLines) {
376
395
  console.error('');
377
396
  console.error('⚠️ *** READ tmp/webpieces/webpieces.methodsize.md for detailed guidance on how to fix this easily *** ⚠️');
378
397
  console.error('');
379
- for (const v of violations) {
380
- console.error(` ❌ ${v.file}:${v.line}`);
381
- console.error(` Method: ${v.methodName} (${v.lines} lines, max: ${maxLines})`);
398
+ if (hardViolations.length > 0) {
399
+ console.error('🚫 HARD LIMIT VIOLATIONS (cannot be bypassed with disable comment):');
400
+ console.error('');
401
+ for (const v of hardViolations) {
402
+ console.error(` ❌ ${v.file}:${v.line}`);
403
+ console.error(` Method: ${v.methodName} (${v.lines} lines, hard max: ${strictMaxLines})`);
404
+ }
405
+ console.error('');
406
+ console.error(' These methods MUST be refactored - no escape hatch available.');
407
+ console.error('');
408
+ }
409
+ if (softViolations.length > 0) {
410
+ console.error('⚠️ SOFT LIMIT VIOLATIONS (can be bypassed with disable comment):');
411
+ console.error('');
412
+ for (const v of softViolations) {
413
+ console.error(` ❌ ${v.file}:${v.line}`);
414
+ console.error(` Method: ${v.methodName} (${v.lines} lines, soft max: ${maxLines})`);
415
+ }
416
+ console.error('');
417
+ console.error(' If you REALLY REALLY need more than ' + maxLines + ' lines, this happens 50% of the time,');
418
+ console.error(' so use escape: // webpieces-disable max-lines-new-methods -- [your reason]');
419
+ if (strictMaxLines) {
420
+ console.error(` NOTE: Even with escape, you cannot exceed the hard limit of ${strictMaxLines} lines.`);
421
+ }
422
+ console.error('');
382
423
  }
383
- console.error('');
384
- console.error(' If you REALLY REALLY need more than ' + maxLines + ' lines, this happens 50% of the time,');
385
- console.error(' so use escape: // webpieces-disable max-lines-new-methods -- [your reason]');
386
- console.error('');
387
424
  }
388
425
  async function runExecutor(options, context) {
389
426
  const workspaceRoot = context.root;
390
427
  const maxLines = options.max ?? 30;
428
+ const strictMaxLines = options.strictMax;
391
429
  const mode = options.mode ?? 'NORMAL';
392
430
  // Skip validation entirely if mode is OFF
393
431
  if (mode === 'OFF') {
@@ -415,7 +453,10 @@ async function runExecutor(options, context) {
415
453
  }
416
454
  console.log(` Base: ${base}`);
417
455
  console.log(` Comparing to: working tree (includes uncommitted changes)`);
418
- console.log(` Max lines for new methods: ${maxLines}`);
456
+ console.log(` Soft limit for new methods: ${maxLines} lines (can escape with disable comment)`);
457
+ if (strictMaxLines) {
458
+ console.log(` Hard limit for new methods: ${strictMaxLines} lines (NO escape possible)`);
459
+ }
419
460
  console.log('');
420
461
  try {
421
462
  // Get changed TypeScript files (base to working tree, like nx affected)
@@ -426,14 +467,17 @@ async function runExecutor(options, context) {
426
467
  }
427
468
  console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);
428
469
  // Find violations
429
- const violations = findViolations(workspaceRoot, changedFiles, base, maxLines);
470
+ const violations = findViolations(workspaceRoot, changedFiles, base, maxLines, strictMaxLines);
430
471
  if (violations.length === 0) {
431
- console.log('✅ All new methods are under ' + maxLines + ' lines');
472
+ const limitMsg = strictMaxLines
473
+ ? `soft limit (${maxLines}) or hard limit (${strictMaxLines})`
474
+ : `${maxLines} lines`;
475
+ console.log('✅ All new methods are within ' + limitMsg);
432
476
  return { success: true };
433
477
  }
434
478
  // Write instructions file and report violations
435
479
  writeTmpInstructions(workspaceRoot);
436
- reportViolations(violations, maxLines);
480
+ reportViolations(violations, maxLines, strictMaxLines);
437
481
  return { success: false };
438
482
  }
439
483
  catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/dev-config/architecture/executors/validate-new-methods/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate New Methods Executor\n *\n * Validates that newly added methods don't exceed a maximum line count.\n * Runs in affected mode when:\n * 1. NX_BASE environment variable is set (via nx affected), OR\n * 2. Auto-detects base by finding merge-base with origin/main\n *\n * This validator encourages writing methods that read like a \"table of contents\"\n * where each method call describes a larger piece of work.\n *\n * Usage:\n * nx affected --target=validate-new-methods --base=origin/main\n * OR: runs automatically via build's architecture:validate-complete dependency\n *\n * Escape hatch: Add webpieces-disable max-lines-new-methods comment with justification\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { execSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as ts from 'typescript';\n\nexport type ValidationMode = 'STRICT' | 'NORMAL' | 'OFF';\n\nexport interface ValidateNewMethodsOptions {\n max?: number;\n mode?: ValidationMode;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\ninterface MethodViolation {\n file: string;\n methodName: string;\n line: number;\n lines: number;\n isNew: boolean;\n}\n\nconst TMP_DIR = 'tmp/webpieces';\nconst TMP_MD_FILE = 'webpieces.methodsize.md';\n\nconst METHODSIZE_DOC_CONTENT = `# Instructions: New Method Too Long\n\n## Requirement\n\n**~50% of the time**, you can stay under the \\`newMethodsMaxLines\\` limit from nx.json\nby extracting logical units into well-named methods.\n\n**~99% of the time**, you can stay under the \\`modifiedAndNewMethodsMaxLines\\` limit from nx.json.\nNearly all software can be written with methods under this size.\n\n## The \"Table of Contents\" Principle\n\nGood code reads like a book's table of contents:\n- Chapter titles (method names) tell you WHAT happens\n- Reading chapter titles gives you the full story\n- You can dive into chapters (implementations) for details\n\n## Why Limit New Methods?\n\nMethods under the limit are:\n- Easy to review in a single screen\n- Simple to understand without scrolling\n- Quick for AI to analyze and suggest improvements\n- More testable in isolation\n- Self-documenting through well-named extracted methods\n\nExtracting logical units into well-named methods makes code more readable for both\nAI and humans.\n\n## How to Refactor\n\nInstead of:\n\\`\\`\\`typescript\nasync processOrder(order: Order): Promise<Result> {\n // 50 lines of validation, transformation, saving, notifications...\n}\n\\`\\`\\`\n\nWrite:\n\\`\\`\\`typescript\nasync processOrder(order: Order): Promise<Result> {\n const validated = this.validateOrder(order);\n const transformed = this.applyBusinessRules(validated);\n const saved = await this.saveToDatabase(transformed);\n await this.notifyStakeholders(saved);\n return this.buildResult(saved);\n}\n\\`\\`\\`\n\nNow the main method is a \"table of contents\" - each line tells part of the story!\n\n## Patterns for Extraction\n\n### Pattern 1: Extract Loop Bodies\n\\`\\`\\`typescript\n// BEFORE\nfor (const item of items) {\n // 20 lines of processing\n}\n\n// AFTER\nfor (const item of items) {\n this.processItem(item);\n}\n\\`\\`\\`\n\n### Pattern 2: Extract Conditional Blocks\n\\`\\`\\`typescript\n// BEFORE\nif (isAdmin(user)) {\n // 15 lines of admin logic\n}\n\n// AFTER\nif (isAdmin(user)) {\n this.handleAdminUser(user);\n}\n\\`\\`\\`\n\n### Pattern 3: Extract Data Transformations\n\\`\\`\\`typescript\n// BEFORE\nconst result = {\n // 10+ lines of object construction\n};\n\n// AFTER\nconst result = this.buildResultObject(data);\n\\`\\`\\`\n\n## If Refactoring Is Not Feasible\n\nSometimes methods genuinely need to be longer (complex algorithms, state machines, etc.).\n\n**Escape hatch**: Add a webpieces-disable comment with justification:\n\n\\`\\`\\`typescript\n// webpieces-disable max-lines-new-methods -- Complex state machine, splitting reduces clarity\nasync complexStateMachine(): Promise<void> {\n // ... longer method with justification\n}\n\\`\\`\\`\n\n## AI Agent Action Steps\n\n1. **READ** the method to understand its logical sections\n2. **IDENTIFY** logical units that can be extracted\n3. **EXTRACT** into well-named private methods\n4. **VERIFY** the main method now reads like a table of contents\n5. **IF NOT FEASIBLE**: Add webpieces-disable max-lines-new-methods comment with clear justification\n\n## Remember\n\n- Every method you write today will be read many times tomorrow\n- The best code explains itself through structure\n- When in doubt, extract and name it\n`;\n\n/**\n * Write the instructions documentation to tmp directory\n */\nfunction writeTmpInstructions(workspaceRoot: string): string {\n const tmpDir = path.join(workspaceRoot, TMP_DIR);\n const mdPath = path.join(tmpDir, TMP_MD_FILE);\n\n fs.mkdirSync(tmpDir, { recursive: true });\n fs.writeFileSync(mdPath, METHODSIZE_DOC_CONTENT);\n\n return mdPath;\n}\n\n/**\n * Get changed TypeScript files between base and working tree.\n * Uses `git diff base` (no three-dots) to match what `nx affected` does -\n * this includes both committed and uncommitted changes in one diff.\n */\nfunction getChangedTypeScriptFiles(workspaceRoot: string, base: string): string[] {\n try {\n // Use two-dot diff (base to working tree) - same as nx affected\n const output = execSync(`git diff --name-only ${base} -- '*.ts' '*.tsx'`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n return output\n .trim()\n .split('\\n')\n .filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));\n } catch {\n return [];\n }\n}\n\n/**\n * Get the diff content for a specific file between base and working tree.\n * Uses `git diff base` (no three-dots) to match what `nx affected` does -\n * this includes both committed and uncommitted changes in one diff.\n */\nfunction getFileDiff(workspaceRoot: string, file: string, base: string): string {\n try {\n // Use two-dot diff (base to working tree) - same as nx affected\n return execSync(`git diff ${base} -- \"${file}\"`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n } catch {\n return '';\n }\n}\n\n/**\n * Parse diff to find newly added method signatures\n */\nfunction findNewMethodSignaturesInDiff(diffContent: string): Set<string> {\n const newMethods = new Set<string>();\n const lines = diffContent.split('\\n');\n\n // Patterns to match method definitions\n const patterns = [\n // [export] [async] function methodName( - most explicit, check first\n /^\\+\\s*(?:export\\s+)?(?:async\\s+)?function\\s+(\\w+)\\s*\\(/,\n // [export] const/let methodName = [async] (\n /^\\+\\s*(?:export\\s+)?(?:const|let)\\s+(\\w+)\\s*=\\s*(?:async\\s*)?\\(/,\n // [export] const/let methodName = [async] function\n /^\\+\\s*(?:export\\s+)?(?:const|let)\\s+(\\w+)\\s*=\\s*(?:async\\s+)?function/,\n // class method: [async] methodName( - but NOT constructor, if, for, while, etc.\n /^\\+\\s*(?:async\\s+)?(\\w+)\\s*\\(/,\n ];\n\n for (const line of lines) {\n if (line.startsWith('+') && !line.startsWith('+++')) {\n for (const pattern of patterns) {\n const match = line.match(pattern);\n if (match) {\n // Extract method name - now always in capture group 1\n const methodName = match[1];\n if (methodName && !['if', 'for', 'while', 'switch', 'catch', 'constructor'].includes(methodName)) {\n newMethods.add(methodName);\n }\n break;\n }\n }\n }\n }\n\n return newMethods;\n}\n\n/**\n * Check if a line contains a webpieces-disable comment that exempts from new method validation.\n * Both max-lines-new-methods AND max-lines-modified are accepted here.\n * - max-lines-new-methods: Exempts from 30-line check, still checked by 80-line validator\n * - max-lines-modified: Exempts from both validators (ultimate escape hatch)\n */\nfunction hasDisableComment(lines: string[], lineNumber: number): boolean {\n // Check the line before the method (lineNumber is 1-indexed, array is 0-indexed)\n // We need to check a few lines before in case there's JSDoc or decorators\n const startCheck = Math.max(0, lineNumber - 5);\n for (let i = lineNumber - 2; i >= startCheck; i--) {\n const line = lines[i]?.trim() ?? '';\n // Stop if we hit another function/class/etc\n if (line.startsWith('function ') || line.startsWith('class ') || line.endsWith('}')) {\n break;\n }\n if (line.includes('webpieces-disable')) {\n // Either escape hatch exempts from the 30-line new method check\n if (line.includes('max-lines-new-methods') || line.includes('max-lines-modified')) {\n return true;\n }\n }\n }\n return false;\n}\n\n/**\n * Parse a TypeScript file and find methods with their line counts\n */\nfunction findMethodsInFile(\n filePath: string,\n workspaceRoot: string\n): Array<{ name: string; line: number; lines: number; hasDisableComment: boolean }> {\n const fullPath = path.join(workspaceRoot, filePath);\n if (!fs.existsSync(fullPath)) return [];\n\n const content = fs.readFileSync(fullPath, 'utf-8');\n const fileLines = content.split('\\n');\n const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);\n\n const methods: Array<{ name: string; line: number; lines: number; hasDisableComment: boolean }> = [];\n\n function visit(node: ts.Node): void {\n let methodName: string | undefined;\n let startLine: number | undefined;\n let endLine: number | undefined;\n\n if (ts.isMethodDeclaration(node) && node.name) {\n methodName = node.name.getText(sourceFile);\n const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());\n startLine = start.line + 1;\n endLine = end.line + 1;\n } else if (ts.isFunctionDeclaration(node) && node.name) {\n methodName = node.name.getText(sourceFile);\n const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());\n startLine = start.line + 1;\n endLine = end.line + 1;\n } else if (ts.isArrowFunction(node)) {\n // Check if it's assigned to a variable\n if (ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {\n methodName = node.parent.name.getText(sourceFile);\n const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());\n startLine = start.line + 1;\n endLine = end.line + 1;\n }\n }\n\n if (methodName && startLine !== undefined && endLine !== undefined) {\n methods.push({\n name: methodName,\n line: startLine,\n lines: endLine - startLine + 1,\n hasDisableComment: hasDisableComment(fileLines, startLine),\n });\n }\n\n ts.forEachChild(node, visit);\n }\n\n visit(sourceFile);\n return methods;\n}\n\n/**\n * Find new methods that exceed the line limit\n */\nfunction findViolations(\n workspaceRoot: string,\n changedFiles: string[],\n base: string,\n maxLines: number\n): MethodViolation[] {\n const violations: MethodViolation[] = [];\n\n for (const file of changedFiles) {\n // Get the diff to find which methods are NEW (not just modified)\n const diff = getFileDiff(workspaceRoot, file, base);\n const newMethodNames = findNewMethodSignaturesInDiff(diff);\n\n if (newMethodNames.size === 0) continue;\n\n // Parse the current file to get method line counts\n const methods = findMethodsInFile(file, workspaceRoot);\n\n for (const method of methods) {\n // Only check NEW methods that don't have webpieces-disable comment\n if (newMethodNames.has(method.name) && method.lines > maxLines && !method.hasDisableComment) {\n violations.push({\n file,\n methodName: method.name,\n line: method.line,\n lines: method.lines,\n isNew: true,\n });\n }\n }\n }\n\n return violations;\n}\n\n/**\n * Auto-detect the base branch by finding the merge-base with origin/main.\n * This allows the executor to run even when NX_BASE isn't set (e.g., via dependsOn).\n */\nfunction detectBase(workspaceRoot: string): string | null {\n try {\n // First, try to get merge-base with origin/main\n const mergeBase = execSync('git merge-base HEAD origin/main', {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n if (mergeBase) {\n return mergeBase;\n }\n } catch {\n // origin/main might not exist, try main\n try {\n const mergeBase = execSync('git merge-base HEAD main', {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n if (mergeBase) {\n return mergeBase;\n }\n } catch {\n // Ignore - will return null\n }\n }\n return null;\n}\n\n/**\n * Report violations to the user with helpful instructions\n */\nfunction reportViolations(violations: MethodViolation[], maxLines: number): void {\n console.error('');\n console.error('❌ New methods exceed ' + maxLines + ' lines!');\n console.error('');\n console.error('📚 Methods should read like a \"table of contents\" - each method call');\n console.error(' describes a larger piece of work. You can refactor');\n console.error(' to stay under ' + maxLines + ' lines 50% of the time.');\n console.error('');\n console.error('⚠️ *** READ tmp/webpieces/webpieces.methodsize.md for detailed guidance on how to fix this easily *** ⚠️');\n console.error('');\n\n for (const v of violations) {\n console.error(` ❌ ${v.file}:${v.line}`);\n console.error(` Method: ${v.methodName} (${v.lines} lines, max: ${maxLines})`);\n }\n console.error('');\n console.error(' If you REALLY REALLY need more than ' + maxLines + ' lines, this happens 50% of the time,');\n console.error(' so use escape: // webpieces-disable max-lines-new-methods -- [your reason]');\n console.error('');\n}\n\nexport default async function runExecutor(\n options: ValidateNewMethodsOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const workspaceRoot = context.root;\n const maxLines = options.max ?? 30;\n const mode: ValidationMode = options.mode ?? 'NORMAL';\n\n // Skip validation entirely if mode is OFF\n if (mode === 'OFF') {\n console.log('\\n⏭️ Skipping new method validation (validationMode: OFF)');\n console.log('');\n return { success: true };\n }\n\n // Check if running in affected mode via NX_BASE, or auto-detect\n // We use NX_BASE as the base, and compare to WORKING TREE (not NX_HEAD)\n // This matches what `nx affected` does - it compares base to working tree\n let base = process.env['NX_BASE'];\n\n if (!base) {\n // Try to auto-detect base from git merge-base\n base = detectBase(workspaceRoot) ?? undefined;\n\n if (!base) {\n console.log('\\n⏭️ Skipping new method validation (could not detect base branch)');\n console.log(' To run explicitly: nx affected --target=validate-new-methods --base=origin/main');\n console.log('');\n return { success: true };\n }\n\n console.log('\\n📏 Validating New Method Sizes (auto-detected base)\\n');\n } else {\n console.log('\\n📏 Validating New Method Sizes\\n');\n }\n\n console.log(` Base: ${base}`);\n console.log(` Comparing to: working tree (includes uncommitted changes)`);\n console.log(` Max lines for new methods: ${maxLines}`);\n console.log('');\n\n try {\n // Get changed TypeScript files (base to working tree, like nx affected)\n const changedFiles = getChangedTypeScriptFiles(workspaceRoot, base);\n\n if (changedFiles.length === 0) {\n console.log('✅ No TypeScript files changed');\n return { success: true };\n }\n\n console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);\n\n // Find violations\n const violations = findViolations(workspaceRoot, changedFiles, base, maxLines);\n\n if (violations.length === 0) {\n console.log('✅ All new methods are under ' + maxLines + ' lines');\n return { success: true };\n }\n\n // Write instructions file and report violations\n writeTmpInstructions(workspaceRoot);\n reportViolations(violations, maxLines);\n\n return { success: false };\n } catch (err: unknown) {\n const error = err instanceof Error ? err : new Error(String(err));\n console.error('❌ New method validation failed:', error.message);\n return { success: false };\n }\n}\n"]}
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/dev-config/architecture/executors/validate-new-methods/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate New Methods Executor\n *\n * Validates that newly added methods don't exceed a maximum line count.\n * Runs in affected mode when:\n * 1. NX_BASE environment variable is set (via nx affected), OR\n * 2. Auto-detects base by finding merge-base with origin/main\n *\n * This validator encourages writing methods that read like a \"table of contents\"\n * where each method call describes a larger piece of work.\n *\n * Usage:\n * nx affected --target=validate-new-methods --base=origin/main\n * OR: runs automatically via build's architecture:validate-complete dependency\n *\n * Escape hatch: Add webpieces-disable max-lines-new-methods comment with justification\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { execSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as ts from 'typescript';\n\nexport type ValidationMode = 'STRICT' | 'NORMAL' | 'OFF';\n\nexport interface ValidateNewMethodsOptions {\n max?: number;\n strictMax?: number;\n mode?: ValidationMode;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\ninterface MethodViolation {\n file: string;\n methodName: string;\n line: number;\n lines: number;\n isNew: boolean;\n isHardLimit: boolean;\n limit: number;\n}\n\nconst TMP_DIR = 'tmp/webpieces';\nconst TMP_MD_FILE = 'webpieces.methodsize.md';\n\nconst METHODSIZE_DOC_CONTENT = `# Instructions: New Method Too Long\n\n## Requirement\n\n**~50% of the time**, you can stay under the \\`newMethodsMaxLines\\` limit from nx.json\nby extracting logical units into well-named methods.\n\n**~99% of the time**, you can stay under the \\`modifiedAndNewMethodsMaxLines\\` limit from nx.json.\nNearly all software can be written with methods under this size.\n\n## The \"Table of Contents\" Principle\n\nGood code reads like a book's table of contents:\n- Chapter titles (method names) tell you WHAT happens\n- Reading chapter titles gives you the full story\n- You can dive into chapters (implementations) for details\n\n## Why Limit New Methods?\n\nMethods under the limit are:\n- Easy to review in a single screen\n- Simple to understand without scrolling\n- Quick for AI to analyze and suggest improvements\n- More testable in isolation\n- Self-documenting through well-named extracted methods\n\nExtracting logical units into well-named methods makes code more readable for both\nAI and humans.\n\n## How to Refactor\n\nInstead of:\n\\`\\`\\`typescript\nasync processOrder(order: Order): Promise<Result> {\n // 50 lines of validation, transformation, saving, notifications...\n}\n\\`\\`\\`\n\nWrite:\n\\`\\`\\`typescript\nasync processOrder(order: Order): Promise<Result> {\n const validated = this.validateOrder(order);\n const transformed = this.applyBusinessRules(validated);\n const saved = await this.saveToDatabase(transformed);\n await this.notifyStakeholders(saved);\n return this.buildResult(saved);\n}\n\\`\\`\\`\n\nNow the main method is a \"table of contents\" - each line tells part of the story!\n\n## Patterns for Extraction\n\n### Pattern 1: Extract Loop Bodies\n\\`\\`\\`typescript\n// BEFORE\nfor (const item of items) {\n // 20 lines of processing\n}\n\n// AFTER\nfor (const item of items) {\n this.processItem(item);\n}\n\\`\\`\\`\n\n### Pattern 2: Extract Conditional Blocks\n\\`\\`\\`typescript\n// BEFORE\nif (isAdmin(user)) {\n // 15 lines of admin logic\n}\n\n// AFTER\nif (isAdmin(user)) {\n this.handleAdminUser(user);\n}\n\\`\\`\\`\n\n### Pattern 3: Extract Data Transformations\n\\`\\`\\`typescript\n// BEFORE\nconst result = {\n // 10+ lines of object construction\n};\n\n// AFTER\nconst result = this.buildResultObject(data);\n\\`\\`\\`\n\n## If Refactoring Is Not Feasible\n\nSometimes methods genuinely need to be longer (complex algorithms, state machines, etc.).\n\n**Escape hatch**: Add a webpieces-disable comment with justification:\n\n\\`\\`\\`typescript\n// webpieces-disable max-lines-new-methods -- Complex state machine, splitting reduces clarity\nasync complexStateMachine(): Promise<void> {\n // ... longer method with justification\n}\n\\`\\`\\`\n\n## AI Agent Action Steps\n\n1. **READ** the method to understand its logical sections\n2. **IDENTIFY** logical units that can be extracted\n3. **EXTRACT** into well-named private methods\n4. **VERIFY** the main method now reads like a table of contents\n5. **IF NOT FEASIBLE**: Add webpieces-disable max-lines-new-methods comment with clear justification\n\n## Remember\n\n- Every method you write today will be read many times tomorrow\n- The best code explains itself through structure\n- When in doubt, extract and name it\n`;\n\n/**\n * Write the instructions documentation to tmp directory\n */\nfunction writeTmpInstructions(workspaceRoot: string): string {\n const tmpDir = path.join(workspaceRoot, TMP_DIR);\n const mdPath = path.join(tmpDir, TMP_MD_FILE);\n\n fs.mkdirSync(tmpDir, { recursive: true });\n fs.writeFileSync(mdPath, METHODSIZE_DOC_CONTENT);\n\n return mdPath;\n}\n\n/**\n * Get changed TypeScript files between base and working tree.\n * Uses `git diff base` (no three-dots) to match what `nx affected` does -\n * this includes both committed and uncommitted changes in one diff.\n */\nfunction getChangedTypeScriptFiles(workspaceRoot: string, base: string): string[] {\n try {\n // Use two-dot diff (base to working tree) - same as nx affected\n const output = execSync(`git diff --name-only ${base} -- '*.ts' '*.tsx'`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n return output\n .trim()\n .split('\\n')\n .filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));\n } catch {\n return [];\n }\n}\n\n/**\n * Get the diff content for a specific file between base and working tree.\n * Uses `git diff base` (no three-dots) to match what `nx affected` does -\n * this includes both committed and uncommitted changes in one diff.\n */\nfunction getFileDiff(workspaceRoot: string, file: string, base: string): string {\n try {\n // Use two-dot diff (base to working tree) - same as nx affected\n return execSync(`git diff ${base} -- \"${file}\"`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n } catch {\n return '';\n }\n}\n\n/**\n * Parse diff to find newly added method signatures\n */\nfunction findNewMethodSignaturesInDiff(diffContent: string): Set<string> {\n const newMethods = new Set<string>();\n const lines = diffContent.split('\\n');\n\n // Patterns to match method definitions\n const patterns = [\n // [export] [async] function methodName( - most explicit, check first\n /^\\+\\s*(?:export\\s+)?(?:async\\s+)?function\\s+(\\w+)\\s*\\(/,\n // [export] const/let methodName = [async] (\n /^\\+\\s*(?:export\\s+)?(?:const|let)\\s+(\\w+)\\s*=\\s*(?:async\\s*)?\\(/,\n // [export] const/let methodName = [async] function\n /^\\+\\s*(?:export\\s+)?(?:const|let)\\s+(\\w+)\\s*=\\s*(?:async\\s+)?function/,\n // class method: [async] methodName( - but NOT constructor, if, for, while, etc.\n /^\\+\\s*(?:async\\s+)?(\\w+)\\s*\\(/,\n ];\n\n for (const line of lines) {\n if (line.startsWith('+') && !line.startsWith('+++')) {\n for (const pattern of patterns) {\n const match = line.match(pattern);\n if (match) {\n // Extract method name - now always in capture group 1\n const methodName = match[1];\n if (methodName && !['if', 'for', 'while', 'switch', 'catch', 'constructor'].includes(methodName)) {\n newMethods.add(methodName);\n }\n break;\n }\n }\n }\n }\n\n return newMethods;\n}\n\n/**\n * Check if a line contains a webpieces-disable comment that exempts from new method validation.\n * Both max-lines-new-methods AND max-lines-modified are accepted here.\n * - max-lines-new-methods: Exempts from 30-line check, still checked by 80-line validator\n * - max-lines-modified: Exempts from both validators (ultimate escape hatch)\n */\nfunction hasDisableComment(lines: string[], lineNumber: number): boolean {\n // Check the line before the method (lineNumber is 1-indexed, array is 0-indexed)\n // We need to check a few lines before in case there's JSDoc or decorators\n const startCheck = Math.max(0, lineNumber - 5);\n for (let i = lineNumber - 2; i >= startCheck; i--) {\n const line = lines[i]?.trim() ?? '';\n // Stop if we hit another function/class/etc\n if (line.startsWith('function ') || line.startsWith('class ') || line.endsWith('}')) {\n break;\n }\n if (line.includes('webpieces-disable')) {\n // Either escape hatch exempts from the 30-line new method check\n if (line.includes('max-lines-new-methods') || line.includes('max-lines-modified')) {\n return true;\n }\n }\n }\n return false;\n}\n\n/**\n * Parse a TypeScript file and find methods with their line counts\n */\nfunction findMethodsInFile(\n filePath: string,\n workspaceRoot: string\n): Array<{ name: string; line: number; lines: number; hasDisableComment: boolean }> {\n const fullPath = path.join(workspaceRoot, filePath);\n if (!fs.existsSync(fullPath)) return [];\n\n const content = fs.readFileSync(fullPath, 'utf-8');\n const fileLines = content.split('\\n');\n const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);\n\n const methods: Array<{ name: string; line: number; lines: number; hasDisableComment: boolean }> = [];\n\n function visit(node: ts.Node): void {\n let methodName: string | undefined;\n let startLine: number | undefined;\n let endLine: number | undefined;\n\n if (ts.isMethodDeclaration(node) && node.name) {\n methodName = node.name.getText(sourceFile);\n const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());\n startLine = start.line + 1;\n endLine = end.line + 1;\n } else if (ts.isFunctionDeclaration(node) && node.name) {\n methodName = node.name.getText(sourceFile);\n const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());\n startLine = start.line + 1;\n endLine = end.line + 1;\n } else if (ts.isArrowFunction(node)) {\n // Check if it's assigned to a variable\n if (ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {\n methodName = node.parent.name.getText(sourceFile);\n const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());\n startLine = start.line + 1;\n endLine = end.line + 1;\n }\n }\n\n if (methodName && startLine !== undefined && endLine !== undefined) {\n methods.push({\n name: methodName,\n line: startLine,\n lines: endLine - startLine + 1,\n hasDisableComment: hasDisableComment(fileLines, startLine),\n });\n }\n\n ts.forEachChild(node, visit);\n }\n\n visit(sourceFile);\n return methods;\n}\n\n/**\n * Find new methods that exceed the line limit\n */\nfunction findViolations(\n workspaceRoot: string,\n changedFiles: string[],\n base: string,\n maxLines: number,\n strictMaxLines?: number\n): MethodViolation[] {\n const violations: MethodViolation[] = [];\n\n for (const file of changedFiles) {\n // Get the diff to find which methods are NEW (not just modified)\n const diff = getFileDiff(workspaceRoot, file, base);\n const newMethodNames = findNewMethodSignaturesInDiff(diff);\n\n if (newMethodNames.size === 0) continue;\n\n // Parse the current file to get method line counts\n const methods = findMethodsInFile(file, workspaceRoot);\n\n for (const method of methods) {\n if (!newMethodNames.has(method.name)) continue;\n\n // Check hard limit first (if defined) - NO escape possible\n if (strictMaxLines && method.lines > strictMaxLines) {\n violations.push({\n file,\n methodName: method.name,\n line: method.line,\n lines: method.lines,\n isNew: true,\n isHardLimit: true,\n limit: strictMaxLines,\n });\n }\n // Check soft limit - can be escaped with disable comment\n else if (method.lines > maxLines && !method.hasDisableComment) {\n violations.push({\n file,\n methodName: method.name,\n line: method.line,\n lines: method.lines,\n isNew: true,\n isHardLimit: false,\n limit: maxLines,\n });\n }\n }\n }\n\n return violations;\n}\n\n/**\n * Auto-detect the base branch by finding the merge-base with origin/main.\n * This allows the executor to run even when NX_BASE isn't set (e.g., via dependsOn).\n */\nfunction detectBase(workspaceRoot: string): string | null {\n try {\n // First, try to get merge-base with origin/main\n const mergeBase = execSync('git merge-base HEAD origin/main', {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n if (mergeBase) {\n return mergeBase;\n }\n } catch {\n // origin/main might not exist, try main\n try {\n const mergeBase = execSync('git merge-base HEAD main', {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n if (mergeBase) {\n return mergeBase;\n }\n } catch {\n // Ignore - will return null\n }\n }\n return null;\n}\n\n/**\n * Report violations to the user with helpful instructions\n */\n// webpieces-disable max-lines-new-methods -- Console output formatting requires distinct sections for hard/soft violations\nfunction reportViolations(violations: MethodViolation[], maxLines: number, strictMaxLines?: number): void {\n const hardViolations = violations.filter((v) => v.isHardLimit);\n const softViolations = violations.filter((v) => !v.isHardLimit);\n\n console.error('');\n console.error('❌ New methods exceed line limits!');\n console.error('');\n console.error('📚 Methods should read like a \"table of contents\" - each method call');\n console.error(' describes a larger piece of work. You can refactor');\n console.error(' to stay under ' + maxLines + ' lines 50% of the time.');\n console.error('');\n console.error('⚠️ *** READ tmp/webpieces/webpieces.methodsize.md for detailed guidance on how to fix this easily *** ⚠️');\n console.error('');\n\n if (hardViolations.length > 0) {\n console.error('🚫 HARD LIMIT VIOLATIONS (cannot be bypassed with disable comment):');\n console.error('');\n for (const v of hardViolations) {\n console.error(` ❌ ${v.file}:${v.line}`);\n console.error(` Method: ${v.methodName} (${v.lines} lines, hard max: ${strictMaxLines})`);\n }\n console.error('');\n console.error(' These methods MUST be refactored - no escape hatch available.');\n console.error('');\n }\n\n if (softViolations.length > 0) {\n console.error('⚠️ SOFT LIMIT VIOLATIONS (can be bypassed with disable comment):');\n console.error('');\n for (const v of softViolations) {\n console.error(` ❌ ${v.file}:${v.line}`);\n console.error(` Method: ${v.methodName} (${v.lines} lines, soft max: ${maxLines})`);\n }\n console.error('');\n console.error(' If you REALLY REALLY need more than ' + maxLines + ' lines, this happens 50% of the time,');\n console.error(' so use escape: // webpieces-disable max-lines-new-methods -- [your reason]');\n if (strictMaxLines) {\n console.error(` NOTE: Even with escape, you cannot exceed the hard limit of ${strictMaxLines} lines.`);\n }\n console.error('');\n }\n}\n\nexport default async function runExecutor(\n options: ValidateNewMethodsOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const workspaceRoot = context.root;\n const maxLines = options.max ?? 30;\n const strictMaxLines = options.strictMax;\n const mode: ValidationMode = options.mode ?? 'NORMAL';\n\n // Skip validation entirely if mode is OFF\n if (mode === 'OFF') {\n console.log('\\n⏭️ Skipping new method validation (validationMode: OFF)');\n console.log('');\n return { success: true };\n }\n\n // Check if running in affected mode via NX_BASE, or auto-detect\n // We use NX_BASE as the base, and compare to WORKING TREE (not NX_HEAD)\n // This matches what `nx affected` does - it compares base to working tree\n let base = process.env['NX_BASE'];\n\n if (!base) {\n // Try to auto-detect base from git merge-base\n base = detectBase(workspaceRoot) ?? undefined;\n\n if (!base) {\n console.log('\\n⏭️ Skipping new method validation (could not detect base branch)');\n console.log(' To run explicitly: nx affected --target=validate-new-methods --base=origin/main');\n console.log('');\n return { success: true };\n }\n\n console.log('\\n📏 Validating New Method Sizes (auto-detected base)\\n');\n } else {\n console.log('\\n📏 Validating New Method Sizes\\n');\n }\n\n console.log(` Base: ${base}`);\n console.log(` Comparing to: working tree (includes uncommitted changes)`);\n console.log(` Soft limit for new methods: ${maxLines} lines (can escape with disable comment)`);\n if (strictMaxLines) {\n console.log(` Hard limit for new methods: ${strictMaxLines} lines (NO escape possible)`);\n }\n console.log('');\n\n try {\n // Get changed TypeScript files (base to working tree, like nx affected)\n const changedFiles = getChangedTypeScriptFiles(workspaceRoot, base);\n\n if (changedFiles.length === 0) {\n console.log('✅ No TypeScript files changed');\n return { success: true };\n }\n\n console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);\n\n // Find violations\n const violations = findViolations(workspaceRoot, changedFiles, base, maxLines, strictMaxLines);\n\n if (violations.length === 0) {\n const limitMsg = strictMaxLines\n ? `soft limit (${maxLines}) or hard limit (${strictMaxLines})`\n : `${maxLines} lines`;\n console.log('✅ All new methods are within ' + limitMsg);\n return { success: true };\n }\n\n // Write instructions file and report violations\n writeTmpInstructions(workspaceRoot);\n reportViolations(violations, maxLines, strictMaxLines);\n\n return { success: false };\n } catch (err: unknown) {\n const error = err instanceof Error ? err : new Error(String(err));\n console.error('❌ New method validation failed:', error.message);\n return { success: false };\n }\n}\n"]}
@@ -26,6 +26,7 @@ export type ValidationMode = 'STRICT' | 'NORMAL' | 'OFF';
26
26
 
27
27
  export interface ValidateNewMethodsOptions {
28
28
  max?: number;
29
+ strictMax?: number;
29
30
  mode?: ValidationMode;
30
31
  }
31
32
 
@@ -39,6 +40,8 @@ interface MethodViolation {
39
40
  line: number;
40
41
  lines: number;
41
42
  isNew: boolean;
43
+ isHardLimit: boolean;
44
+ limit: number;
42
45
  }
43
46
 
44
47
  const TMP_DIR = 'tmp/webpieces';
@@ -344,7 +347,8 @@ function findViolations(
344
347
  workspaceRoot: string,
345
348
  changedFiles: string[],
346
349
  base: string,
347
- maxLines: number
350
+ maxLines: number,
351
+ strictMaxLines?: number
348
352
  ): MethodViolation[] {
349
353
  const violations: MethodViolation[] = [];
350
354
 
@@ -359,14 +363,30 @@ function findViolations(
359
363
  const methods = findMethodsInFile(file, workspaceRoot);
360
364
 
361
365
  for (const method of methods) {
362
- // Only check NEW methods that don't have webpieces-disable comment
363
- if (newMethodNames.has(method.name) && method.lines > maxLines && !method.hasDisableComment) {
366
+ if (!newMethodNames.has(method.name)) continue;
367
+
368
+ // Check hard limit first (if defined) - NO escape possible
369
+ if (strictMaxLines && method.lines > strictMaxLines) {
370
+ violations.push({
371
+ file,
372
+ methodName: method.name,
373
+ line: method.line,
374
+ lines: method.lines,
375
+ isNew: true,
376
+ isHardLimit: true,
377
+ limit: strictMaxLines,
378
+ });
379
+ }
380
+ // Check soft limit - can be escaped with disable comment
381
+ else if (method.lines > maxLines && !method.hasDisableComment) {
364
382
  violations.push({
365
383
  file,
366
384
  methodName: method.name,
367
385
  line: method.line,
368
386
  lines: method.lines,
369
387
  isNew: true,
388
+ isHardLimit: false,
389
+ limit: maxLines,
370
390
  });
371
391
  }
372
392
  }
@@ -413,9 +433,13 @@ function detectBase(workspaceRoot: string): string | null {
413
433
  /**
414
434
  * Report violations to the user with helpful instructions
415
435
  */
416
- function reportViolations(violations: MethodViolation[], maxLines: number): void {
436
+ // webpieces-disable max-lines-new-methods -- Console output formatting requires distinct sections for hard/soft violations
437
+ function reportViolations(violations: MethodViolation[], maxLines: number, strictMaxLines?: number): void {
438
+ const hardViolations = violations.filter((v) => v.isHardLimit);
439
+ const softViolations = violations.filter((v) => !v.isHardLimit);
440
+
417
441
  console.error('');
418
- console.error('❌ New methods exceed ' + maxLines + ' lines!');
442
+ console.error('❌ New methods exceed line limits!');
419
443
  console.error('');
420
444
  console.error('📚 Methods should read like a "table of contents" - each method call');
421
445
  console.error(' describes a larger piece of work. You can refactor');
@@ -424,14 +448,33 @@ function reportViolations(violations: MethodViolation[], maxLines: number): void
424
448
  console.error('⚠️ *** READ tmp/webpieces/webpieces.methodsize.md for detailed guidance on how to fix this easily *** ⚠️');
425
449
  console.error('');
426
450
 
427
- for (const v of violations) {
428
- console.error(` ❌ ${v.file}:${v.line}`);
429
- console.error(` Method: ${v.methodName} (${v.lines} lines, max: ${maxLines})`);
451
+ if (hardViolations.length > 0) {
452
+ console.error('🚫 HARD LIMIT VIOLATIONS (cannot be bypassed with disable comment):');
453
+ console.error('');
454
+ for (const v of hardViolations) {
455
+ console.error(` ❌ ${v.file}:${v.line}`);
456
+ console.error(` Method: ${v.methodName} (${v.lines} lines, hard max: ${strictMaxLines})`);
457
+ }
458
+ console.error('');
459
+ console.error(' These methods MUST be refactored - no escape hatch available.');
460
+ console.error('');
461
+ }
462
+
463
+ if (softViolations.length > 0) {
464
+ console.error('⚠️ SOFT LIMIT VIOLATIONS (can be bypassed with disable comment):');
465
+ console.error('');
466
+ for (const v of softViolations) {
467
+ console.error(` ❌ ${v.file}:${v.line}`);
468
+ console.error(` Method: ${v.methodName} (${v.lines} lines, soft max: ${maxLines})`);
469
+ }
470
+ console.error('');
471
+ console.error(' If you REALLY REALLY need more than ' + maxLines + ' lines, this happens 50% of the time,');
472
+ console.error(' so use escape: // webpieces-disable max-lines-new-methods -- [your reason]');
473
+ if (strictMaxLines) {
474
+ console.error(` NOTE: Even with escape, you cannot exceed the hard limit of ${strictMaxLines} lines.`);
475
+ }
476
+ console.error('');
430
477
  }
431
- console.error('');
432
- console.error(' If you REALLY REALLY need more than ' + maxLines + ' lines, this happens 50% of the time,');
433
- console.error(' so use escape: // webpieces-disable max-lines-new-methods -- [your reason]');
434
- console.error('');
435
478
  }
436
479
 
437
480
  export default async function runExecutor(
@@ -440,6 +483,7 @@ export default async function runExecutor(
440
483
  ): Promise<ExecutorResult> {
441
484
  const workspaceRoot = context.root;
442
485
  const maxLines = options.max ?? 30;
486
+ const strictMaxLines = options.strictMax;
443
487
  const mode: ValidationMode = options.mode ?? 'NORMAL';
444
488
 
445
489
  // Skip validation entirely if mode is OFF
@@ -472,7 +516,10 @@ export default async function runExecutor(
472
516
 
473
517
  console.log(` Base: ${base}`);
474
518
  console.log(` Comparing to: working tree (includes uncommitted changes)`);
475
- console.log(` Max lines for new methods: ${maxLines}`);
519
+ console.log(` Soft limit for new methods: ${maxLines} lines (can escape with disable comment)`);
520
+ if (strictMaxLines) {
521
+ console.log(` Hard limit for new methods: ${strictMaxLines} lines (NO escape possible)`);
522
+ }
476
523
  console.log('');
477
524
 
478
525
  try {
@@ -487,16 +534,19 @@ export default async function runExecutor(
487
534
  console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);
488
535
 
489
536
  // Find violations
490
- const violations = findViolations(workspaceRoot, changedFiles, base, maxLines);
537
+ const violations = findViolations(workspaceRoot, changedFiles, base, maxLines, strictMaxLines);
491
538
 
492
539
  if (violations.length === 0) {
493
- console.log('✅ All new methods are under ' + maxLines + ' lines');
540
+ const limitMsg = strictMaxLines
541
+ ? `soft limit (${maxLines}) or hard limit (${strictMaxLines})`
542
+ : `${maxLines} lines`;
543
+ console.log('✅ All new methods are within ' + limitMsg);
494
544
  return { success: true };
495
545
  }
496
546
 
497
547
  // Write instructions file and report violations
498
548
  writeTmpInstructions(workspaceRoot);
499
- reportViolations(violations, maxLines);
549
+ reportViolations(violations, maxLines, strictMaxLines);
500
550
 
501
551
  return { success: false };
502
552
  } catch (err: unknown) {
@@ -6,13 +6,17 @@
6
6
  "properties": {
7
7
  "max": {
8
8
  "type": "number",
9
- "description": "Maximum number of lines allowed for new methods",
9
+ "description": "Soft limit: Maximum lines for new methods (can be bypassed with disable comment)",
10
10
  "default": 30
11
11
  },
12
+ "strictMax": {
13
+ "type": "number",
14
+ "description": "Hard limit: Absolute maximum lines for new methods (CANNOT be bypassed with disable comment). If not set, no hard limit is enforced."
15
+ },
12
16
  "mode": {
13
17
  "type": "string",
14
18
  "enum": ["STRICT", "NORMAL", "OFF"],
15
- "description": "OFF: skip validation. NORMAL/STRICT: validate (disable comments always work for new methods).",
19
+ "description": "OFF: skip validation. NORMAL/STRICT: validate (disable comments work for soft limit only).",
16
20
  "default": "NORMAL"
17
21
  }
18
22
  },
package/executors.json CHANGED
@@ -59,6 +59,11 @@
59
59
  "implementation": "./architecture/executors/validate-modified-files/executor",
60
60
  "schema": "./architecture/executors/validate-modified-files/schema.json",
61
61
  "description": "Validate modified files don't exceed max line count"
62
+ },
63
+ "validate-code": {
64
+ "implementation": "./architecture/executors/validate-code/executor",
65
+ "schema": "./architecture/executors/validate-code/schema.json",
66
+ "description": "Combined validation for new methods, modified methods, and file sizes"
62
67
  }
63
68
  }
64
69
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/dev-config",
3
- "version": "0.2.65",
3
+ "version": "0.2.67",
4
4
  "description": "Development configuration, scripts, and patterns for WebPieces projects",
5
5
  "type": "commonjs",
6
6
  "bin": {
package/plugin.js CHANGED
@@ -156,12 +156,10 @@ function buildValidationTargetsList(validations) {
156
156
  targets.push('validate-no-skiplevel-deps');
157
157
  if (validations.validatePackageJson)
158
158
  targets.push('validate-packagejson');
159
- if (validations.validateNewMethods)
160
- targets.push('validate-new-methods');
161
- if (validations.validateModifiedMethods)
162
- targets.push('validate-modified-methods');
163
- if (validations.validateModifiedFiles)
164
- targets.push('validate-modified-files');
159
+ // Use combined validate-code instead of 3 separate targets
160
+ if (validations.validateNewMethods || validations.validateModifiedMethods || validations.validateModifiedFiles) {
161
+ targets.push('validate-code');
162
+ }
165
163
  if (validations.validateVersionsLocked)
166
164
  targets.push('validate-versions-locked');
167
165
  return targets;
@@ -194,14 +192,10 @@ function createWorkspaceTargetsWithoutPrefix(opts) {
194
192
  if (validations.validatePackageJson) {
195
193
  targets['validate-packagejson'] = createValidatePackageJsonTarget();
196
194
  }
197
- if (validations.validateNewMethods) {
198
- targets['validate-new-methods'] = createValidateNewMethodsTarget(validations.newMethodsMaxLines, validations.validationMode);
199
- }
200
- if (validations.validateModifiedMethods) {
201
- targets['validate-modified-methods'] = createValidateModifiedMethodsTarget(validations.modifiedAndNewMethodsMaxLines, validations.validationMode);
202
- }
203
- if (validations.validateModifiedFiles) {
204
- targets['validate-modified-files'] = createValidateModifiedFilesTarget(validations.modifiedFilesMaxLines, validations.validationMode);
195
+ // Use combined validate-code instead of 3 separate targets
196
+ // Options come from targetDefaults in nx.json (applied at runtime, no cache issues)
197
+ if (validations.validateNewMethods || validations.validateModifiedMethods || validations.validateModifiedFiles) {
198
+ targets['validate-code'] = createValidateCodeTarget();
205
199
  }
206
200
  if (validations.validateVersionsLocked) {
207
201
  targets['validate-versions-locked'] = createValidateVersionsLockedTarget();
@@ -241,14 +235,10 @@ function createWorkspaceTargets(opts) {
241
235
  if (opts.workspace.validations.validatePackageJson) {
242
236
  targets[`${prefix}validate-packagejson`] = createValidatePackageJsonTarget();
243
237
  }
244
- if (opts.workspace.validations.validateNewMethods) {
245
- targets[`${prefix}validate-new-methods`] = createValidateNewMethodsTarget(opts.workspace.validations.newMethodsMaxLines, opts.workspace.validations.validationMode);
246
- }
247
- if (opts.workspace.validations.validateModifiedMethods) {
248
- targets[`${prefix}validate-modified-methods`] = createValidateModifiedMethodsTarget(opts.workspace.validations.modifiedAndNewMethodsMaxLines, opts.workspace.validations.validationMode);
249
- }
250
- if (opts.workspace.validations.validateModifiedFiles) {
251
- targets[`${prefix}validate-modified-files`] = createValidateModifiedFilesTarget(opts.workspace.validations.modifiedFilesMaxLines, opts.workspace.validations.validationMode);
238
+ // Use combined validate-code instead of 3 separate targets
239
+ // Options come from targetDefaults in nx.json (applied at runtime, no cache issues)
240
+ if (opts.workspace.validations.validateNewMethods || opts.workspace.validations.validateModifiedMethods || opts.workspace.validations.validateModifiedFiles) {
241
+ targets[`${prefix}validate-code`] = createValidateCodeTarget();
252
242
  }
253
243
  return targets;
254
244
  }
@@ -368,6 +358,22 @@ function createValidateModifiedFilesTarget(maxLines, mode) {
368
358
  },
369
359
  };
370
360
  }
361
+ /**
362
+ * Create combined validate-code target
363
+ * Options come from targetDefaults in nx.json (applied at runtime, no cache issues)
364
+ */
365
+ function createValidateCodeTarget() {
366
+ return {
367
+ executor: '@webpieces/dev-config:validate-code',
368
+ cache: false, // Don't cache - depends on git state
369
+ inputs: ['default'],
370
+ // No options here - they come from targetDefaults at runtime
371
+ metadata: {
372
+ technologies: ['nx'],
373
+ description: 'Combined validation for new methods, modified methods, and file sizes',
374
+ },
375
+ };
376
+ }
371
377
  function createValidateVersionsLockedTarget() {
372
378
  return {
373
379
  executor: '@webpieces/dev-config:validate-versions-locked',