@webpieces/nx-webpieces-rules 0.4.490 → 0.4.491

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/nx-webpieces-rules",
3
- "version": "0.4.490",
3
+ "version": "0.4.491",
4
4
  "description": "Nx-specific webpieces validation rules and graph tooling. Bundles all @webpieces rule packages with Nx graph validators and an inference plugin.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -21,11 +21,11 @@
21
21
  "README.md"
22
22
  ],
23
23
  "dependencies": {
24
- "@webpieces/ai-hook-rules": "0.4.490",
25
- "@webpieces/code-rules": "0.4.490",
26
- "@webpieces/eslint-rules": "0.4.490",
27
- "@webpieces/pr-gate": "0.4.490",
28
- "@webpieces/rules-config": "0.4.490",
24
+ "@webpieces/ai-hook-rules": "0.4.491",
25
+ "@webpieces/code-rules": "0.4.491",
26
+ "@webpieces/eslint-rules": "0.4.491",
27
+ "@webpieces/pr-gate": "0.4.491",
28
+ "@webpieces/rules-config": "0.4.491",
29
29
  "madge": "8.0.0"
30
30
  },
31
31
  "peerDependencies": {
@@ -16,7 +16,7 @@ const rule_gate_1 = require("../../lib/rule-gate");
16
16
  const toError_1 = require("../../toError");
17
17
  async function runExecutor(_options, context) {
18
18
  // Epoch-gateable: the existing cycle set can be grandfathered while a refactor lands, so this
19
- // rule honors ignoreModifiedUntilEpoch / ignoreRuleWhileOnBranch in addition to mode: OFF.
19
+ // rule honors turnOffRuleUntilEpoch / turnOffRuleWhileOnBranch in addition to mode: OFF.
20
20
  if (new rule_gate_1.RuleGate().isDisabled(context.root, 'validate-no-architecture-cycles', true)) {
21
21
  return { success: true };
22
22
  }
@@ -1 +1 @@
1
- {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-no-architecture-cycles/executor.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;AAgBH,8BA6CC;AA1DD,+DAA0D;AAC1D,yDAAgE;AAChE,mDAA+C;AAC/C,2CAAwC;AAUzB,KAAK,UAAU,WAAW,CACrC,QAAiC,EACjC,OAAwB;IAExB,8FAA8F;IAC9F,2FAA2F;IAC3F,IAAI,IAAI,oBAAQ,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,iCAAiC,EAAE,IAAI,CAAC,EAAE,CAAC;QACnF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC7B,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC;IAE1D,8DAA8D;IAC9D,IAAI,CAAC;QACD,yDAAyD;QACzD,OAAO,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAC;QACzE,MAAM,QAAQ,GAAG,MAAM,IAAA,+BAAa,GAAE,CAAC;QAEvC,+CAA+C;QAC/C,OAAO,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC;QAC5D,8DAA8D;QAC9D,IAAI,CAAC;YACD,IAAA,qCAAsB,EAAC,QAAQ,CAAC,CAAC;YACjC,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC;YAEpD,gBAAgB;YAChB,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC;YAClD,OAAO,CAAC,GAAG,CAAC,iBAAiB,YAAY,wBAAwB,CAAC,CAAC;YAEnE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC7B,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;YACjD,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC7B,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;YAC7C,OAAO,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAAC;YAClE,OAAO,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;YAC3C,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAC9B,CAAC;IACL,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAC3D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC;AACL,CAAC","sourcesContent":["/**\n * Validate No Architecture Cycles Executor\n *\n * Validates that the architecture dependency graph has no circular dependencies.\n * This is a fast check that only validates acyclicity at the project level.\n *\n * Usage:\n * nx run architecture:validate-no-architecture-cycles\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { generateGraph } from '../../lib/graph-generator';\nimport { sortGraphTopologically } from '../../lib/graph-sorter';\nimport { RuleGate } from '../../lib/rule-gate';\nimport { toError } from '../../toError';\n\nexport interface ValidateNoCyclesOptions {\n // No options needed\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nexport default async function runExecutor(\n _options: ValidateNoCyclesOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n // Epoch-gateable: the existing cycle set can be grandfathered while a refactor lands, so this\n // rule honors ignoreModifiedUntilEpoch / ignoreRuleWhileOnBranch in addition to mode: OFF.\n if (new RuleGate().isDisabled(context.root, 'validate-no-architecture-cycles', true)) {\n return { success: true };\n }\n\n console.log('\\nšŸ”„ Validating No Circular Dependencies\\n');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Step 1: Generate current graph from project.json files\n console.log('šŸ“Š Generating dependency graph from project.json files...');\n const rawGraph = await generateGraph();\n\n // Step 2: Topological sort (validates acyclic)\n console.log('šŸ”„ Checking for cycles (topological sort)...');\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n sortGraphTopologically(rawGraph);\n console.log('āœ… No circular dependencies detected!');\n\n // Print summary\n const projectCount = Object.keys(rawGraph).length;\n console.log(`\\nšŸ“ˆ Summary: ${projectCount} projects, all acyclic`);\n\n return { success: true };\n } catch (err: unknown) {\n const error = toError(err);\n console.error('āŒ Circular dependency detected!');\n console.error(error.message);\n console.error('\\nTo fix:');\n console.error(' 1. Review the cycle above');\n console.error(' 2. Break the cycle by refactoring dependencies');\n console.error(' 3. Run this check again');\n return { success: false };\n }\n } catch (err: unknown) {\n const error = toError(err);\n console.error('āŒ Cycle validation failed:', error.message);\n return { success: false };\n }\n}\n"]}
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-no-architecture-cycles/executor.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;AAgBH,8BA6CC;AA1DD,+DAA0D;AAC1D,yDAAgE;AAChE,mDAA+C;AAC/C,2CAAwC;AAUzB,KAAK,UAAU,WAAW,CACrC,QAAiC,EACjC,OAAwB;IAExB,8FAA8F;IAC9F,yFAAyF;IACzF,IAAI,IAAI,oBAAQ,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,iCAAiC,EAAE,IAAI,CAAC,EAAE,CAAC;QACnF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC7B,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC;IAE1D,8DAA8D;IAC9D,IAAI,CAAC;QACD,yDAAyD;QACzD,OAAO,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAC;QACzE,MAAM,QAAQ,GAAG,MAAM,IAAA,+BAAa,GAAE,CAAC;QAEvC,+CAA+C;QAC/C,OAAO,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC;QAC5D,8DAA8D;QAC9D,IAAI,CAAC;YACD,IAAA,qCAAsB,EAAC,QAAQ,CAAC,CAAC;YACjC,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC;YAEpD,gBAAgB;YAChB,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC;YAClD,OAAO,CAAC,GAAG,CAAC,iBAAiB,YAAY,wBAAwB,CAAC,CAAC;YAEnE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC7B,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;YACjD,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC7B,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;YAC7C,OAAO,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAAC;YAClE,OAAO,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;YAC3C,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAC9B,CAAC;IACL,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAC3D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC;AACL,CAAC","sourcesContent":["/**\n * Validate No Architecture Cycles Executor\n *\n * Validates that the architecture dependency graph has no circular dependencies.\n * This is a fast check that only validates acyclicity at the project level.\n *\n * Usage:\n * nx run architecture:validate-no-architecture-cycles\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { generateGraph } from '../../lib/graph-generator';\nimport { sortGraphTopologically } from '../../lib/graph-sorter';\nimport { RuleGate } from '../../lib/rule-gate';\nimport { toError } from '../../toError';\n\nexport interface ValidateNoCyclesOptions {\n // No options needed\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nexport default async function runExecutor(\n _options: ValidateNoCyclesOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n // Epoch-gateable: the existing cycle set can be grandfathered while a refactor lands, so this\n // rule honors turnOffRuleUntilEpoch / turnOffRuleWhileOnBranch in addition to mode: OFF.\n if (new RuleGate().isDisabled(context.root, 'validate-no-architecture-cycles', true)) {\n return { success: true };\n }\n\n console.log('\\nšŸ”„ Validating No Circular Dependencies\\n');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Step 1: Generate current graph from project.json files\n console.log('šŸ“Š Generating dependency graph from project.json files...');\n const rawGraph = await generateGraph();\n\n // Step 2: Topological sort (validates acyclic)\n console.log('šŸ”„ Checking for cycles (topological sort)...');\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n sortGraphTopologically(rawGraph);\n console.log('āœ… No circular dependencies detected!');\n\n // Print summary\n const projectCount = Object.keys(rawGraph).length;\n console.log(`\\nšŸ“ˆ Summary: ${projectCount} projects, all acyclic`);\n\n return { success: true };\n } catch (err: unknown) {\n const error = toError(err);\n console.error('āŒ Circular dependency detected!');\n console.error(error.message);\n console.error('\\nTo fix:');\n console.error(' 1. Review the cycle above');\n console.error(' 2. Break the cycle by refactoring dependencies');\n console.error(' 3. Run this check again');\n return { success: false };\n }\n } catch (err: unknown) {\n const error = toError(err);\n console.error('āŒ Cycle validation failed:', error.message);\n return { success: false };\n }\n}\n"]}
@@ -48,6 +48,17 @@ const SOURCE_EXTENSIONS = new Set([
48
48
  '.mjs',
49
49
  '.cjs',
50
50
  ]);
51
+ /**
52
+ * Directory names whose contents SHIP to consumers as scaffolding (copied into a consumer repo by the
53
+ * installer), so their imports are consumer-facing PRODUCTION deps — NOT this project's own dev-time
54
+ * tooling. A file like `templates/eslint.webpieces.config.mjs` otherwise trips DEV_CONFIG_RE on its
55
+ * name and gets mis-bucketed as dev, which then flags a genuinely-required dependency (e.g.
56
+ * `@webpieces/eslint-rules`, which those very templates import) as "test-only in dependencies".
57
+ * A `templates/` segment therefore short-circuits isDevFile to production, ahead of the name heuristics.
58
+ */
59
+ const SHIPPED_DIR_NAMES = new Set([
60
+ 'templates',
61
+ ]);
51
62
  /**
52
63
  * Directory names that make everything below them test/dev-only.
53
64
  * Kept tight on purpose — a false "this is a test dir" would let a real
@@ -111,6 +122,12 @@ class DepUsageScanner {
111
122
  const normalized = relPath.split(path.sep).join('/');
112
123
  const segments = normalized.split('/');
113
124
  const fileName = segments[segments.length - 1];
125
+ // Shipped scaffolding wins over every dev heuristic below: a template file's imports are
126
+ // consumer-facing production deps, even when its NAME looks like a dev config (eslint.*.config.mjs).
127
+ for (const dir of segments.slice(0, -1)) {
128
+ if (SHIPPED_DIR_NAMES.has(dir))
129
+ return false;
130
+ }
114
131
  for (const dir of segments.slice(0, -1)) {
115
132
  if (TEST_DIR_NAMES.has(dir))
116
133
  return true;
@@ -1 +1 @@
1
- {"version":3,"file":"dep-usage-scanner.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/dep-usage-scanner.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;;;AAEH,+CAAyB;AACzB,mDAA6B;AAC7B,wCAAqC;AAErC,2EAA2E;AAC3E,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC;IACtB,cAAc;IACd,MAAM;IACN,OAAO;IACP,SAAS;IACT,UAAU;IACV,KAAK;IACL,MAAM;IACN,KAAK;IACL,UAAU;CACb,CAAC,CAAC;AAEH,qDAAqD;AACrD,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IAC9B,KAAK;IACL,MAAM;IACN,MAAM;IACN,MAAM;IACN,KAAK;IACL,MAAM;IACN,MAAM;IACN,MAAM;CACT,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC;IAC3B,WAAW;IACX,WAAW;IACX,cAAc;IACd,MAAM;IACN,OAAO;IACP,KAAK;IACL,WAAW;CACd,CAAC,CAAC;AAEH,+EAA+E;AAC/E,MAAM,YAAY,GAAG,wEAAwE,CAAC;AAE9F,8DAA8D;AAC9D,MAAM,aAAa,GACf,yGAAyG,CAAC;AAE9G,+DAA+D;AAC/D,MAAM,YAAY,GAAG,+CAA+C,CAAC;AAErE,uFAAuF;AACvF,MAAM,SAAS,GACX,+EAA+E,CAAC;AAEpF;;;GAGG;AACH,MAAa,QAAQ;IACjB,YAAY,CAAc;IAC1B,YAAY,CAAc;IAE1B,YAAY,YAAyB,EAAE,YAAyB;QAC5D,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;IACrC,CAAC;IAED,4EAA4E;IAC5E,UAAU,CAAC,WAAmB;QAC1B,OAAO,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IACrF,CAAC;IAED,wFAAwF;IACxF,QAAQ,CAAC,WAAmB;QACxB,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IACtF,CAAC;CACJ;AAlBD,4BAkBC;AAED,MAAa,eAAe;IACxB;;;OAGG;IACH,IAAI,CAAC,aAAqB;QACtB,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,IAAI,GAAG,EAAU,EAAE,IAAI,GAAG,EAAU,CAAC,CAAC;QACjE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC;YAAE,OAAO,KAAK,CAAC;QAChD,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,aAAa,EAAE,KAAK,CAAC,CAAC;QAC/C,OAAO,KAAK,CAAC;IACjB,CAAC;IAED;;;OAGG;IACH,SAAS,CAAC,OAAe;QACrB,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrD,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACvC,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAE/C,KAAK,MAAM,GAAG,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACtC,IAAI,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,OAAO,IAAI,CAAC;QAC7C,CAAC;QACD,IAAI,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAC;QAC7C,IAAI,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAC;QAC9C,IAAI,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAC;QAC7C,OAAO,KAAK,CAAC;IACjB,CAAC;IAED;;;OAGG;IACH,aAAa,CAAC,SAAiB;QAC3B,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACxC,IAAI,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QACxE,IAAI,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC;QAC/C,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC5B,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,IAAI,CAAC;YAClC,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QACrC,CAAC;QACD,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAEO,IAAI,CAAC,MAAc,EAAE,WAAmB,EAAE,KAAe;QAC7D,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,MAAM,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAChE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC1B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAC9C,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACtB,IAAI,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;oBAAE,SAAS;gBACxC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC;gBACvC,SAAS;YACb,CAAC;YACD,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;gBAAE,SAAS;YAC9B,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAAE,SAAS;YAC/D,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC;QAC/C,CAAC;IACL,CAAC;IAEO,QAAQ,CAAC,OAAe,EAAE,WAAmB,EAAE,KAAe;QAClE,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACtC,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO;QAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QACpD,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC;QACjF,KAAK,MAAM,WAAW,IAAI,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,EAAE,CAAC;YACzD,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAC5B,CAAC;IACL,CAAC;IAEO,mBAAmB,CAAC,MAAc;QACtC,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,SAAS,CAAC,SAAS,GAAG,CAAC,CAAC;QACxB,IAAI,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnC,OAAO,KAAK,KAAK,IAAI,EAAE,CAAC;YACpB,MAAM,WAAW,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACjD,IAAI,WAAW,KAAK,IAAI;gBAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAClD,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnC,CAAC;QACD,OAAO,KAAK,CAAC;IACjB,CAAC;IAEO,QAAQ,CAAC,OAAe;QAC5B,8DAA8D;QAC9D,IAAI,CAAC;YACD,OAAO,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC7C,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,gFAAgF;YAChF,gFAAgF;YAChF,OAAO,CAAC,IAAI,CAAC,kBAAkB,OAAO,4BAA4B,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YACnF,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;CACJ;AA/FD,0CA+FC","sourcesContent":["/**\n * Dep Usage Scanner\n *\n * Answers ONE question for a project: \"is package X reached from production\n * source, or ONLY from test/dev files?\"\n *\n * WHY this exists: nx derives graph edges from ALL TypeScript sources, specs\n * included. Without this scan, a package imported only by `*.spec.ts` looks\n * identical to a package imported by a controller, so the validator forces it\n * into `dependencies` — and `pnpm deploy --prod` then ships test machinery\n * (auth-bypass hooks, canned credentials, fakes) into the production image.\n * Splitting the scan by file kind lets `devDependencies` be the REQUIRED home\n * for test-only packages.\n *\n * The scan is deliberately conservative: a package is \"test-only\" ONLY when it\n * is imported by at least one test/dev file and by ZERO production files. Any\n * doubt (no import found at all, e.g. a runtime-only/reflection dependency)\n * resolves to \"production\", so this can never push a runtime-required package\n * out of `dependencies`.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { toError } from '../toError';\n\n/** Directories never worth scanning (build output, vendored code, VCS). */\nconst SKIP_DIRS = new Set([\n 'node_modules',\n 'dist',\n 'build',\n 'out-tsc',\n 'coverage',\n '.nx',\n '.git',\n 'tmp',\n '.angular',\n]);\n\n/** Source extensions whose imports we understand. */\nconst SOURCE_EXTENSIONS = new Set([\n '.ts',\n '.tsx',\n '.mts',\n '.cts',\n '.js',\n '.jsx',\n '.mjs',\n '.cjs',\n]);\n\n/**\n * Directory names that make everything below them test/dev-only.\n * Kept tight on purpose — a false \"this is a test dir\" would let a real\n * production import be classified as test-only.\n */\nconst TEST_DIR_NAMES = new Set([\n '__tests__',\n '__mocks__',\n '__fixtures__',\n 'test',\n 'tests',\n 'e2e',\n 'e2e-tests',\n]);\n\n/** `foo.spec.ts`, `foo.test.tsx`, `foo-e2e.spec.mts`, `foo.testkit.ts`, ... */\nconst TEST_FILE_RE = /[.-](spec|test|e2e|testkit|mock|mocks|fixture|fixtures)\\.[cm]?[jt]sx?$/;\n\n/** Tooling config/bootstrap files: dev-time by definition. */\nconst DEV_CONFIG_RE =\n /^(vitest|vite|jest|playwright|cypress|karma|webpack|rollup|eslint|prettier)\\.[\\w.-]*config\\.[cm]?[jt]s$/;\n\n/** `jest.setup.ts`, `vitest.setup.ts`, `test-setup.ts`, ... */\nconst DEV_SETUP_RE = /^([\\w-]*[.-])?(setup|test-setup)\\.[cm]?[jt]s$/;\n\n/** Bare-import extraction: `from 'x'`, `import 'x'`, `import('x')`, `require('x')`. */\nconst IMPORT_RE =\n /(?:\\bfrom\\s*|\\bimport\\s*|\\brequire\\s*\\(\\s*|\\bimport\\s*\\(\\s*)['\"]([^'\"]+)['\"]/g;\n\n/**\n * Which packages a project reaches from production code vs. only from test/dev code.\n * Data-only: no logic lives here (see CLAUDE.md — data structures are classes).\n */\nexport class DepUsage {\n prodPackages: Set<string>;\n testPackages: Set<string>;\n\n constructor(prodPackages: Set<string>, testPackages: Set<string>) {\n this.prodPackages = prodPackages;\n this.testPackages = testPackages;\n }\n\n /** True when the package is imported by tests and by no production file. */\n isTestOnly(packageName: string): boolean {\n return this.testPackages.has(packageName) && !this.prodPackages.has(packageName);\n }\n\n /** True when we saw the package in NO file at all (kind is unknown → treat as prod). */\n isUnseen(packageName: string): boolean {\n return !this.testPackages.has(packageName) && !this.prodPackages.has(packageName);\n }\n}\n\nexport class DepUsageScanner {\n /**\n * Walk a project directory and record every bare import specifier, bucketed\n * by whether the importing file is production or test/dev.\n */\n scan(absProjectDir: string): DepUsage {\n const usage = new DepUsage(new Set<string>(), new Set<string>());\n if (!fs.existsSync(absProjectDir)) return usage;\n this.walk(absProjectDir, absProjectDir, usage);\n return usage;\n }\n\n /**\n * Is this path a test/dev file? Path is relative to the project root and\n * uses either separator.\n */\n isDevFile(relPath: string): boolean {\n const normalized = relPath.split(path.sep).join('/');\n const segments = normalized.split('/');\n const fileName = segments[segments.length - 1];\n\n for (const dir of segments.slice(0, -1)) {\n if (TEST_DIR_NAMES.has(dir)) return true;\n }\n if (TEST_FILE_RE.test(fileName)) return true;\n if (DEV_CONFIG_RE.test(fileName)) return true;\n if (DEV_SETUP_RE.test(fileName)) return true;\n return false;\n }\n\n /**\n * The package a bare specifier belongs to, or null for relative/absolute\n * paths and node: builtins. `@scope/pkg/sub` → `@scope/pkg`; `pkg/sub` → `pkg`.\n */\n toPackageName(specifier: string): string | null {\n if (specifier.length === 0) return null;\n if (specifier.startsWith('.') || specifier.startsWith('/')) return null;\n if (specifier.startsWith('node:')) return null;\n const parts = specifier.split('/');\n if (specifier.startsWith('@')) {\n if (parts.length < 2) return null;\n return `${parts[0]}/${parts[1]}`;\n }\n return parts[0];\n }\n\n private walk(absDir: string, projectRoot: string, usage: DepUsage): void {\n const entries = fs.readdirSync(absDir, { withFileTypes: true });\n for (const entry of entries) {\n const absPath = path.join(absDir, entry.name);\n if (entry.isDirectory()) {\n if (SKIP_DIRS.has(entry.name)) continue;\n this.walk(absPath, projectRoot, usage);\n continue;\n }\n if (!entry.isFile()) continue;\n if (!SOURCE_EXTENSIONS.has(path.extname(entry.name))) continue;\n this.scanFile(absPath, projectRoot, usage);\n }\n }\n\n private scanFile(absPath: string, projectRoot: string, usage: DepUsage): void {\n const source = this.readFile(absPath);\n if (source === null) return;\n const relPath = path.relative(projectRoot, absPath);\n const bucket = this.isDevFile(relPath) ? usage.testPackages : usage.prodPackages;\n for (const packageName of this.extractPackageNames(source)) {\n bucket.add(packageName);\n }\n }\n\n private extractPackageNames(source: string): string[] {\n const names: string[] = [];\n IMPORT_RE.lastIndex = 0;\n let match = IMPORT_RE.exec(source);\n while (match !== null) {\n const packageName = this.toPackageName(match[1]);\n if (packageName !== null) names.push(packageName);\n match = IMPORT_RE.exec(source);\n }\n return names;\n }\n\n private readFile(absPath: string): string | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return fs.readFileSync(absPath, 'utf-8');\n } catch (err: unknown) {\n const error = toError(err);\n // An unreadable file cannot change the classification of a package; skipping it\n // only ever makes the scan MORE conservative (fewer test-only classifications).\n console.warn(`Could not read ${absPath} while classifying deps: ${error.message}`);\n return null;\n }\n }\n}\n"]}
1
+ {"version":3,"file":"dep-usage-scanner.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/dep-usage-scanner.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;;;AAEH,+CAAyB;AACzB,mDAA6B;AAC7B,wCAAqC;AAErC,2EAA2E;AAC3E,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC;IACtB,cAAc;IACd,MAAM;IACN,OAAO;IACP,SAAS;IACT,UAAU;IACV,KAAK;IACL,MAAM;IACN,KAAK;IACL,UAAU;CACb,CAAC,CAAC;AAEH,qDAAqD;AACrD,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IAC9B,KAAK;IACL,MAAM;IACN,MAAM;IACN,MAAM;IACN,KAAK;IACL,MAAM;IACN,MAAM;IACN,MAAM;CACT,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IAC9B,WAAW;CACd,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC;IAC3B,WAAW;IACX,WAAW;IACX,cAAc;IACd,MAAM;IACN,OAAO;IACP,KAAK;IACL,WAAW;CACd,CAAC,CAAC;AAEH,+EAA+E;AAC/E,MAAM,YAAY,GAAG,wEAAwE,CAAC;AAE9F,8DAA8D;AAC9D,MAAM,aAAa,GACf,yGAAyG,CAAC;AAE9G,+DAA+D;AAC/D,MAAM,YAAY,GAAG,+CAA+C,CAAC;AAErE,uFAAuF;AACvF,MAAM,SAAS,GACX,+EAA+E,CAAC;AAEpF;;;GAGG;AACH,MAAa,QAAQ;IACjB,YAAY,CAAc;IAC1B,YAAY,CAAc;IAE1B,YAAY,YAAyB,EAAE,YAAyB;QAC5D,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;IACrC,CAAC;IAED,4EAA4E;IAC5E,UAAU,CAAC,WAAmB;QAC1B,OAAO,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IACrF,CAAC;IAED,wFAAwF;IACxF,QAAQ,CAAC,WAAmB;QACxB,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IACtF,CAAC;CACJ;AAlBD,4BAkBC;AAED,MAAa,eAAe;IACxB;;;OAGG;IACH,IAAI,CAAC,aAAqB;QACtB,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,IAAI,GAAG,EAAU,EAAE,IAAI,GAAG,EAAU,CAAC,CAAC;QACjE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC;YAAE,OAAO,KAAK,CAAC;QAChD,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,aAAa,EAAE,KAAK,CAAC,CAAC;QAC/C,OAAO,KAAK,CAAC;IACjB,CAAC;IAED;;;OAGG;IACH,SAAS,CAAC,OAAe;QACrB,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrD,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACvC,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAE/C,yFAAyF;QACzF,qGAAqG;QACrG,KAAK,MAAM,GAAG,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACtC,IAAI,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,OAAO,KAAK,CAAC;QACjD,CAAC;QACD,KAAK,MAAM,GAAG,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACtC,IAAI,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,OAAO,IAAI,CAAC;QAC7C,CAAC;QACD,IAAI,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAC;QAC7C,IAAI,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAC;QAC9C,IAAI,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAC;QAC7C,OAAO,KAAK,CAAC;IACjB,CAAC;IAED;;;OAGG;IACH,aAAa,CAAC,SAAiB;QAC3B,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACxC,IAAI,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QACxE,IAAI,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC;QAC/C,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC5B,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,IAAI,CAAC;YAClC,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QACrC,CAAC;QACD,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAEO,IAAI,CAAC,MAAc,EAAE,WAAmB,EAAE,KAAe;QAC7D,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,MAAM,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAChE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC1B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAC9C,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACtB,IAAI,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;oBAAE,SAAS;gBACxC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC;gBACvC,SAAS;YACb,CAAC;YACD,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;gBAAE,SAAS;YAC9B,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAAE,SAAS;YAC/D,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC;QAC/C,CAAC;IACL,CAAC;IAEO,QAAQ,CAAC,OAAe,EAAE,WAAmB,EAAE,KAAe;QAClE,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACtC,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO;QAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QACpD,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC;QACjF,KAAK,MAAM,WAAW,IAAI,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,EAAE,CAAC;YACzD,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAC5B,CAAC;IACL,CAAC;IAEO,mBAAmB,CAAC,MAAc;QACtC,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,SAAS,CAAC,SAAS,GAAG,CAAC,CAAC;QACxB,IAAI,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnC,OAAO,KAAK,KAAK,IAAI,EAAE,CAAC;YACpB,MAAM,WAAW,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACjD,IAAI,WAAW,KAAK,IAAI;gBAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAClD,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnC,CAAC;QACD,OAAO,KAAK,CAAC;IACjB,CAAC;IAEO,QAAQ,CAAC,OAAe;QAC5B,8DAA8D;QAC9D,IAAI,CAAC;YACD,OAAO,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC7C,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,gFAAgF;YAChF,gFAAgF;YAChF,OAAO,CAAC,IAAI,CAAC,kBAAkB,OAAO,4BAA4B,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YACnF,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;CACJ;AApGD,0CAoGC","sourcesContent":["/**\n * Dep Usage Scanner\n *\n * Answers ONE question for a project: \"is package X reached from production\n * source, or ONLY from test/dev files?\"\n *\n * WHY this exists: nx derives graph edges from ALL TypeScript sources, specs\n * included. Without this scan, a package imported only by `*.spec.ts` looks\n * identical to a package imported by a controller, so the validator forces it\n * into `dependencies` — and `pnpm deploy --prod` then ships test machinery\n * (auth-bypass hooks, canned credentials, fakes) into the production image.\n * Splitting the scan by file kind lets `devDependencies` be the REQUIRED home\n * for test-only packages.\n *\n * The scan is deliberately conservative: a package is \"test-only\" ONLY when it\n * is imported by at least one test/dev file and by ZERO production files. Any\n * doubt (no import found at all, e.g. a runtime-only/reflection dependency)\n * resolves to \"production\", so this can never push a runtime-required package\n * out of `dependencies`.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { toError } from '../toError';\n\n/** Directories never worth scanning (build output, vendored code, VCS). */\nconst SKIP_DIRS = new Set([\n 'node_modules',\n 'dist',\n 'build',\n 'out-tsc',\n 'coverage',\n '.nx',\n '.git',\n 'tmp',\n '.angular',\n]);\n\n/** Source extensions whose imports we understand. */\nconst SOURCE_EXTENSIONS = new Set([\n '.ts',\n '.tsx',\n '.mts',\n '.cts',\n '.js',\n '.jsx',\n '.mjs',\n '.cjs',\n]);\n\n/**\n * Directory names whose contents SHIP to consumers as scaffolding (copied into a consumer repo by the\n * installer), so their imports are consumer-facing PRODUCTION deps — NOT this project's own dev-time\n * tooling. A file like `templates/eslint.webpieces.config.mjs` otherwise trips DEV_CONFIG_RE on its\n * name and gets mis-bucketed as dev, which then flags a genuinely-required dependency (e.g.\n * `@webpieces/eslint-rules`, which those very templates import) as \"test-only in dependencies\".\n * A `templates/` segment therefore short-circuits isDevFile to production, ahead of the name heuristics.\n */\nconst SHIPPED_DIR_NAMES = new Set([\n 'templates',\n]);\n\n/**\n * Directory names that make everything below them test/dev-only.\n * Kept tight on purpose — a false \"this is a test dir\" would let a real\n * production import be classified as test-only.\n */\nconst TEST_DIR_NAMES = new Set([\n '__tests__',\n '__mocks__',\n '__fixtures__',\n 'test',\n 'tests',\n 'e2e',\n 'e2e-tests',\n]);\n\n/** `foo.spec.ts`, `foo.test.tsx`, `foo-e2e.spec.mts`, `foo.testkit.ts`, ... */\nconst TEST_FILE_RE = /[.-](spec|test|e2e|testkit|mock|mocks|fixture|fixtures)\\.[cm]?[jt]sx?$/;\n\n/** Tooling config/bootstrap files: dev-time by definition. */\nconst DEV_CONFIG_RE =\n /^(vitest|vite|jest|playwright|cypress|karma|webpack|rollup|eslint|prettier)\\.[\\w.-]*config\\.[cm]?[jt]s$/;\n\n/** `jest.setup.ts`, `vitest.setup.ts`, `test-setup.ts`, ... */\nconst DEV_SETUP_RE = /^([\\w-]*[.-])?(setup|test-setup)\\.[cm]?[jt]s$/;\n\n/** Bare-import extraction: `from 'x'`, `import 'x'`, `import('x')`, `require('x')`. */\nconst IMPORT_RE =\n /(?:\\bfrom\\s*|\\bimport\\s*|\\brequire\\s*\\(\\s*|\\bimport\\s*\\(\\s*)['\"]([^'\"]+)['\"]/g;\n\n/**\n * Which packages a project reaches from production code vs. only from test/dev code.\n * Data-only: no logic lives here (see CLAUDE.md — data structures are classes).\n */\nexport class DepUsage {\n prodPackages: Set<string>;\n testPackages: Set<string>;\n\n constructor(prodPackages: Set<string>, testPackages: Set<string>) {\n this.prodPackages = prodPackages;\n this.testPackages = testPackages;\n }\n\n /** True when the package is imported by tests and by no production file. */\n isTestOnly(packageName: string): boolean {\n return this.testPackages.has(packageName) && !this.prodPackages.has(packageName);\n }\n\n /** True when we saw the package in NO file at all (kind is unknown → treat as prod). */\n isUnseen(packageName: string): boolean {\n return !this.testPackages.has(packageName) && !this.prodPackages.has(packageName);\n }\n}\n\nexport class DepUsageScanner {\n /**\n * Walk a project directory and record every bare import specifier, bucketed\n * by whether the importing file is production or test/dev.\n */\n scan(absProjectDir: string): DepUsage {\n const usage = new DepUsage(new Set<string>(), new Set<string>());\n if (!fs.existsSync(absProjectDir)) return usage;\n this.walk(absProjectDir, absProjectDir, usage);\n return usage;\n }\n\n /**\n * Is this path a test/dev file? Path is relative to the project root and\n * uses either separator.\n */\n isDevFile(relPath: string): boolean {\n const normalized = relPath.split(path.sep).join('/');\n const segments = normalized.split('/');\n const fileName = segments[segments.length - 1];\n\n // Shipped scaffolding wins over every dev heuristic below: a template file's imports are\n // consumer-facing production deps, even when its NAME looks like a dev config (eslint.*.config.mjs).\n for (const dir of segments.slice(0, -1)) {\n if (SHIPPED_DIR_NAMES.has(dir)) return false;\n }\n for (const dir of segments.slice(0, -1)) {\n if (TEST_DIR_NAMES.has(dir)) return true;\n }\n if (TEST_FILE_RE.test(fileName)) return true;\n if (DEV_CONFIG_RE.test(fileName)) return true;\n if (DEV_SETUP_RE.test(fileName)) return true;\n return false;\n }\n\n /**\n * The package a bare specifier belongs to, or null for relative/absolute\n * paths and node: builtins. `@scope/pkg/sub` → `@scope/pkg`; `pkg/sub` → `pkg`.\n */\n toPackageName(specifier: string): string | null {\n if (specifier.length === 0) return null;\n if (specifier.startsWith('.') || specifier.startsWith('/')) return null;\n if (specifier.startsWith('node:')) return null;\n const parts = specifier.split('/');\n if (specifier.startsWith('@')) {\n if (parts.length < 2) return null;\n return `${parts[0]}/${parts[1]}`;\n }\n return parts[0];\n }\n\n private walk(absDir: string, projectRoot: string, usage: DepUsage): void {\n const entries = fs.readdirSync(absDir, { withFileTypes: true });\n for (const entry of entries) {\n const absPath = path.join(absDir, entry.name);\n if (entry.isDirectory()) {\n if (SKIP_DIRS.has(entry.name)) continue;\n this.walk(absPath, projectRoot, usage);\n continue;\n }\n if (!entry.isFile()) continue;\n if (!SOURCE_EXTENSIONS.has(path.extname(entry.name))) continue;\n this.scanFile(absPath, projectRoot, usage);\n }\n }\n\n private scanFile(absPath: string, projectRoot: string, usage: DepUsage): void {\n const source = this.readFile(absPath);\n if (source === null) return;\n const relPath = path.relative(projectRoot, absPath);\n const bucket = this.isDevFile(relPath) ? usage.testPackages : usage.prodPackages;\n for (const packageName of this.extractPackageNames(source)) {\n bucket.add(packageName);\n }\n }\n\n private extractPackageNames(source: string): string[] {\n const names: string[] = [];\n IMPORT_RE.lastIndex = 0;\n let match = IMPORT_RE.exec(source);\n while (match !== null) {\n const packageName = this.toPackageName(match[1]);\n if (packageName !== null) names.push(packageName);\n match = IMPORT_RE.exec(source);\n }\n return names;\n }\n\n private readFile(absPath: string): string | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return fs.readFileSync(absPath, 'utf-8');\n } catch (err: unknown) {\n const error = toError(err);\n // An unreadable file cannot change the classification of a package; skipping it\n // only ever makes the scan MORE conservative (fewer test-only classifications).\n console.warn(`Could not read ${absPath} while classifying deps: ${error.message}`);\n return null;\n }\n }\n}\n"]}
@@ -12,13 +12,11 @@
12
12
  * - rule entry ABSENT (or no webpieces.config.json at all) → RUN. Fail-safe: an older config, or a
13
13
  * repo that has not adopted the keys yet, behaves exactly as it did before this gate existed.
14
14
  * - `"mode": "OFF"` → skip with a reason.
15
- * - the time-box / branch escape hatches (turnOffRuleUntilEpoch / turnOffRuleWhileOnBranch, and their
16
- * ignoreModifiedUntilEpoch / ignoreRuleWhileOnBranch aliases) → honored when the caller passes
17
- * `honorEpoch: true`. All five executors now pass true: a schedule ("do not enforce until <epoch>
18
- * / off <branch>") is coherent for any of them, baseline or not (see the comment block above
19
- * ValidateArchitectureUnchangedConfig in rules-config/src/rule-configs.ts). The param is retained
20
- * so a future caller can still opt a rule out of time-boxing. loadAndValidate has already
21
- * canonicalized the new field names onto the ignore* pair, so this reads only the originals.
15
+ * - the time-box / branch escape hatches (turnOffRuleUntilEpoch / turnOffRuleWhileOnBranch) → honored
16
+ * when the caller passes `honorEpoch: true`. All five executors now pass true: a schedule ("do not
17
+ * enforce until <epoch> / off <branch>") is coherent for any of them, baseline or not (see the
18
+ * comment block above ValidateArchitectureUnchangedConfig in rules-config/src/rule-configs.ts). The
19
+ * param is retained so a future caller can still opt a rule out of time-boxing.
22
20
  */
23
21
  export declare class RuleGate {
24
22
  /**
@@ -13,13 +13,11 @@
13
13
  * - rule entry ABSENT (or no webpieces.config.json at all) → RUN. Fail-safe: an older config, or a
14
14
  * repo that has not adopted the keys yet, behaves exactly as it did before this gate existed.
15
15
  * - `"mode": "OFF"` → skip with a reason.
16
- * - the time-box / branch escape hatches (turnOffRuleUntilEpoch / turnOffRuleWhileOnBranch, and their
17
- * ignoreModifiedUntilEpoch / ignoreRuleWhileOnBranch aliases) → honored when the caller passes
18
- * `honorEpoch: true`. All five executors now pass true: a schedule ("do not enforce until <epoch>
19
- * / off <branch>") is coherent for any of them, baseline or not (see the comment block above
20
- * ValidateArchitectureUnchangedConfig in rules-config/src/rule-configs.ts). The param is retained
21
- * so a future caller can still opt a rule out of time-boxing. loadAndValidate has already
22
- * canonicalized the new field names onto the ignore* pair, so this reads only the originals.
16
+ * - the time-box / branch escape hatches (turnOffRuleUntilEpoch / turnOffRuleWhileOnBranch) → honored
17
+ * when the caller passes `honorEpoch: true`. All five executors now pass true: a schedule ("do not
18
+ * enforce until <epoch> / off <branch>") is coherent for any of them, baseline or not (see the
19
+ * comment block above ValidateArchitectureUnchangedConfig in rules-config/src/rule-configs.ts). The
20
+ * param is retained so a future caller can still opt a rule out of time-boxing.
23
21
  */
24
22
  Object.defineProperty(exports, "__esModule", { value: true });
25
23
  exports.RuleGate = void 0;
@@ -40,8 +38,8 @@ class RuleGate {
40
38
  return 'mode: OFF';
41
39
  if (!honorEpoch)
42
40
  return null;
43
- const epoch = rule.options['ignoreModifiedUntilEpoch'];
44
- const branch = rule.options['ignoreRuleWhileOnBranch'];
41
+ const epoch = rule.options['turnOffRuleUntilEpoch'];
42
+ const branch = rule.options['turnOffRuleWhileOnBranch'];
45
43
  const skip = (0, rules_config_1.shouldSkipRule)(epoch, branch);
46
44
  return skip.skip ? (skip.reason ?? 'temporarily disabled') : null;
47
45
  }
@@ -1 +1 @@
1
- {"version":3,"file":"rule-gate.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/rule-gate.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;;;AAEH,0DAA0E;AAE1E,MAAa,QAAQ;IACjB;;;;;OAKG;IACH,UAAU,CAAC,aAAqB,EAAE,QAAgB,EAAE,UAAmB;QACnE,MAAM,IAAI,GAAG,IAAA,8BAAe,EAAC,aAAa,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACzE,uEAAuE;QACvE,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QACvB,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO,WAAW,CAAC;QACnC,IAAI,CAAC,UAAU;YAAE,OAAO,IAAI,CAAC;QAE7B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,0BAA0B,CAAuB,CAAC;QAC7E,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,yBAAyB,CAAuB,CAAC;QAC7E,MAAM,IAAI,GAAG,IAAA,6BAAc,EAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAC3C,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,IAAI,sBAAsB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACtE,CAAC;IAED;;;OAGG;IACH,UAAU,CAAC,aAAqB,EAAE,QAAgB,EAAE,UAAmB;QACnE,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,aAAa,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;QACpE,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,kBAAkB,QAAQ,KAAK,MAAM,2CAA2C,CAAC,CAAC;QAC9F,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ;AA9BD,4BA8BC","sourcesContent":["/**\n * Config gate for the Nx infrastructure validators.\n *\n * Every other rule in the system (eslint rules, code-rules, ai-hook rules, validate-ts-in-src) can be\n * turned off or time-boxed from webpieces.config.json. The five infrastructure executors\n * (validate-architecture-unchanged, validate-no-architecture-cycles, validate-packagejson,\n * validate-versions-locked, validate-eslint-sync) hardcoded their behavior instead, which made them\n * the only checks a consuming repo could not adopt gradually. This gate gives them the same\n * semantics, from the same source of truth (`loadAndValidate` → webpieces.config.json).\n *\n * Semantics:\n * - rule entry ABSENT (or no webpieces.config.json at all) → RUN. Fail-safe: an older config, or a\n * repo that has not adopted the keys yet, behaves exactly as it did before this gate existed.\n * - `\"mode\": \"OFF\"` → skip with a reason.\n * - the time-box / branch escape hatches (turnOffRuleUntilEpoch / turnOffRuleWhileOnBranch, and their\n * ignoreModifiedUntilEpoch / ignoreRuleWhileOnBranch aliases) → honored when the caller passes\n * `honorEpoch: true`. All five executors now pass true: a schedule (\"do not enforce until <epoch>\n * / off <branch>\") is coherent for any of them, baseline or not (see the comment block above\n * ValidateArchitectureUnchangedConfig in rules-config/src/rule-configs.ts). The param is retained\n * so a future caller can still opt a rule out of time-boxing. loadAndValidate has already\n * canonicalized the new field names onto the ignore* pair, so this reads only the originals.\n */\n\nimport { loadAndValidate, shouldSkipRule } from '@webpieces/rules-config';\n\nexport class RuleGate {\n /**\n * @param workspaceRoot the Nx `context.root` — where webpieces.config.json is looked up from.\n * @param ruleName the webpieces.config.json key, identical to the Nx target name.\n * @param honorEpoch true to honor the time-box/branch escape hatches (all five executors pass true).\n * @returns a human-readable reason when the executor must SKIP, or null when it must RUN.\n */\n skipReason(workspaceRoot: string, ruleName: string, honorEpoch: boolean): string | null {\n const rule = loadAndValidate(workspaceRoot).resolved.rules.get(ruleName);\n // Absent ⇒ run. Never invent a default that silently disables a check.\n if (!rule) return null;\n if (rule.isOff) return 'mode: OFF';\n if (!honorEpoch) return null;\n\n const epoch = rule.options['ignoreModifiedUntilEpoch'] as number | undefined;\n const branch = rule.options['ignoreRuleWhileOnBranch'] as string | undefined;\n const skip = shouldSkipRule(epoch, branch);\n return skip.skip ? (skip.reason ?? 'temporarily disabled') : null;\n }\n\n /**\n * Convenience wrapper: prints the standard skip line and reports whether the caller should stop.\n * Keeps the four-line preamble identical across all five executors.\n */\n isDisabled(workspaceRoot: string, ruleName: string, honorEpoch: boolean): boolean {\n const reason = this.skipReason(workspaceRoot, ruleName, honorEpoch);\n if (reason === null) return false;\n console.log(`\\nā­ļø Skipping ${ruleName} (${reason}) — configured in webpieces.config.json\\n`);\n return true;\n }\n}\n"]}
1
+ {"version":3,"file":"rule-gate.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/rule-gate.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;;AAEH,0DAA0E;AAE1E,MAAa,QAAQ;IACjB;;;;;OAKG;IACH,UAAU,CAAC,aAAqB,EAAE,QAAgB,EAAE,UAAmB;QACnE,MAAM,IAAI,GAAG,IAAA,8BAAe,EAAC,aAAa,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACzE,uEAAuE;QACvE,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QACvB,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO,WAAW,CAAC;QACnC,IAAI,CAAC,UAAU;YAAE,OAAO,IAAI,CAAC;QAE7B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,uBAAuB,CAAuB,CAAC;QAC1E,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,0BAA0B,CAA8B,CAAC;QACrF,MAAM,IAAI,GAAG,IAAA,6BAAc,EAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAC3C,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,IAAI,sBAAsB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACtE,CAAC;IAED;;;OAGG;IACH,UAAU,CAAC,aAAqB,EAAE,QAAgB,EAAE,UAAmB;QACnE,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,aAAa,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;QACpE,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,kBAAkB,QAAQ,KAAK,MAAM,2CAA2C,CAAC,CAAC;QAC9F,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ;AA9BD,4BA8BC","sourcesContent":["/**\n * Config gate for the Nx infrastructure validators.\n *\n * Every other rule in the system (eslint rules, code-rules, ai-hook rules, validate-ts-in-src) can be\n * turned off or time-boxed from webpieces.config.json. The five infrastructure executors\n * (validate-architecture-unchanged, validate-no-architecture-cycles, validate-packagejson,\n * validate-versions-locked, validate-eslint-sync) hardcoded their behavior instead, which made them\n * the only checks a consuming repo could not adopt gradually. This gate gives them the same\n * semantics, from the same source of truth (`loadAndValidate` → webpieces.config.json).\n *\n * Semantics:\n * - rule entry ABSENT (or no webpieces.config.json at all) → RUN. Fail-safe: an older config, or a\n * repo that has not adopted the keys yet, behaves exactly as it did before this gate existed.\n * - `\"mode\": \"OFF\"` → skip with a reason.\n * - the time-box / branch escape hatches (turnOffRuleUntilEpoch / turnOffRuleWhileOnBranch) → honored\n * when the caller passes `honorEpoch: true`. All five executors now pass true: a schedule (\"do not\n * enforce until <epoch> / off <branch>\") is coherent for any of them, baseline or not (see the\n * comment block above ValidateArchitectureUnchangedConfig in rules-config/src/rule-configs.ts). The\n * param is retained so a future caller can still opt a rule out of time-boxing.\n */\n\nimport { loadAndValidate, shouldSkipRule } from '@webpieces/rules-config';\n\nexport class RuleGate {\n /**\n * @param workspaceRoot the Nx `context.root` — where webpieces.config.json is looked up from.\n * @param ruleName the webpieces.config.json key, identical to the Nx target name.\n * @param honorEpoch true to honor the time-box/branch escape hatches (all five executors pass true).\n * @returns a human-readable reason when the executor must SKIP, or null when it must RUN.\n */\n skipReason(workspaceRoot: string, ruleName: string, honorEpoch: boolean): string | null {\n const rule = loadAndValidate(workspaceRoot).resolved.rules.get(ruleName);\n // Absent ⇒ run. Never invent a default that silently disables a check.\n if (!rule) return null;\n if (rule.isOff) return 'mode: OFF';\n if (!honorEpoch) return null;\n\n const epoch = rule.options['turnOffRuleUntilEpoch'] as number | undefined;\n const branch = rule.options['turnOffRuleWhileOnBranch'] as string | null | undefined;\n const skip = shouldSkipRule(epoch, branch);\n return skip.skip ? (skip.reason ?? 'temporarily disabled') : null;\n }\n\n /**\n * Convenience wrapper: prints the standard skip line and reports whether the caller should stop.\n * Keeps the four-line preamble identical across all five executors.\n */\n isDisabled(workspaceRoot: string, ruleName: string, honorEpoch: boolean): boolean {\n const reason = this.skipReason(workspaceRoot, ruleName, honorEpoch);\n if (reason === null) return false;\n console.log(`\\nā­ļø Skipping ${ruleName} (${reason}) — configured in webpieces.config.json\\n`);\n return true;\n }\n}\n"]}