@webpieces/dev-config 0.2.94 → 0.2.95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/architecture/executors/validate-no-any-unknown/executor.js +2 -2
  2. package/architecture/executors/validate-no-any-unknown/executor.js.map +1 -1
  3. package/architecture/executors/validate-no-any-unknown/executor.ts +2 -2
  4. package/architecture/executors/validate-no-skiplevel-deps/executor.js.map +1 -1
  5. package/architecture/executors/validate-no-skiplevel-deps/executor.ts +2 -2
  6. package/eslint-plugin/__tests__/catch-error-pattern.test.ts +41 -26
  7. package/eslint-plugin/__tests__/no-unmanaged-exceptions.test.ts +20 -20
  8. package/eslint-plugin/rules/catch-error-pattern.d.ts +3 -3
  9. package/eslint-plugin/rules/catch-error-pattern.js +84 -137
  10. package/eslint-plugin/rules/catch-error-pattern.js.map +1 -1
  11. package/eslint-plugin/rules/catch-error-pattern.ts +123 -158
  12. package/eslint-plugin/rules/enforce-architecture.js +3 -3
  13. package/eslint-plugin/rules/enforce-architecture.js.map +1 -1
  14. package/eslint-plugin/rules/enforce-architecture.ts +8 -8
  15. package/eslint-plugin/rules/max-file-lines.js +3 -3
  16. package/eslint-plugin/rules/max-file-lines.js.map +1 -1
  17. package/eslint-plugin/rules/max-file-lines.ts +5 -5
  18. package/eslint-plugin/rules/max-method-lines.js +3 -3
  19. package/eslint-plugin/rules/max-method-lines.js.map +1 -1
  20. package/eslint-plugin/rules/max-method-lines.ts +5 -5
  21. package/executors/validate-eslint-sync/executor.d.ts +3 -2
  22. package/executors/validate-eslint-sync/executor.js.map +1 -1
  23. package/executors/validate-eslint-sync/executor.ts +6 -2
  24. package/executors/validate-versions-locked/executor.js +3 -2
  25. package/executors/validate-versions-locked/executor.js.map +1 -1
  26. package/executors/validate-versions-locked/executor.ts +5 -4
  27. package/package.json +1 -1
  28. package/src/generators/init/generator.js.map +1 -1
  29. package/templates/webpieces.exceptions.md +15 -15
@@ -2,9 +2,9 @@
2
2
  * ESLint rule to enforce standardized catch block error handling patterns
3
3
  *
4
4
  * Enforces three approved patterns:
5
- * 1. Standard: catch (err: any) { const error = toError(err); }
6
- * 2. Ignored: catch (err: any) { //const error = toError(err); }
7
- * 3. Nested: catch (err2: any) { const error2 = toError(err2); }
5
+ * 1. Standard: catch (err: unknown) { const error = toError(err); }
6
+ * 2. Ignored: catch (err: unknown) { //const error = toError(err); }
7
+ * 3. Nested: catch (err2: unknown) { const error2 = toError(err2); }
8
8
  */
9
9
 
10
10
  import type { Rule } from 'eslint';
@@ -58,6 +58,119 @@ interface CallExpressionNode {
58
58
  [key: string]: any;
59
59
  }
60
60
 
61
+ function validateParamName(
62
+ context: Rule.RuleContext,
63
+ param: IdentifierNode,
64
+ expectedParamName: string,
65
+ ): void {
66
+ if (param.type === 'Identifier' && param.name !== expectedParamName) {
67
+ context.report({
68
+ node: param,
69
+ messageId: 'wrongParameterName',
70
+ data: { actual: param.name },
71
+ });
72
+ }
73
+ }
74
+
75
+ function validateTypeAnnotation(
76
+ context: Rule.RuleContext,
77
+ param: IdentifierNode,
78
+ expectedParamName: string,
79
+ ): void {
80
+ if (
81
+ !param.typeAnnotation ||
82
+ !param.typeAnnotation.typeAnnotation ||
83
+ param.typeAnnotation.typeAnnotation.type !== 'TSUnknownKeyword'
84
+ ) {
85
+ context.report({
86
+ node: param,
87
+ messageId: 'missingTypeAnnotation',
88
+ data: { param: param.name || expectedParamName },
89
+ });
90
+ }
91
+ }
92
+
93
+ function hasIgnoreComment(
94
+ catchNode: CatchClauseNode,
95
+ sourceCode: Rule.RuleContext['sourceCode'],
96
+ expectedVarName: string,
97
+ actualParamName: string,
98
+ ): boolean {
99
+ const catchBlockStart = catchNode.body.range![0];
100
+ const catchBlockEnd = catchNode.body.range![1];
101
+ const catchBlockText = sourceCode.text.substring(catchBlockStart, catchBlockEnd);
102
+
103
+ const ignorePattern = new RegExp(
104
+ `//\\s*const\\s+${expectedVarName}\\s*=\\s*toError\\(${actualParamName}\\)`,
105
+ );
106
+
107
+ return ignorePattern.test(catchBlockText);
108
+ }
109
+
110
+ function reportMissingToError(
111
+ context: Rule.RuleContext,
112
+ // webpieces-disable no-any-unknown -- ESLint AST node param requires any type
113
+ node: any,
114
+ paramName: string,
115
+ ): void {
116
+ context.report({
117
+ node,
118
+ messageId: 'missingToError',
119
+ data: { param: paramName },
120
+ });
121
+ }
122
+
123
+ function validateToErrorCall(
124
+ context: Rule.RuleContext,
125
+ // webpieces-disable no-any-unknown -- ESLint AST node param requires any type
126
+ firstStatement: any,
127
+ expectedParamName: string,
128
+ expectedVarName: string,
129
+ actualParamName: string,
130
+ ): void {
131
+ if (firstStatement.type !== 'VariableDeclaration') {
132
+ reportMissingToError(context, firstStatement, expectedParamName);
133
+ return;
134
+ }
135
+
136
+ const varDecl = firstStatement as VariableDeclarationNode;
137
+ const declaration = varDecl.declarations[0];
138
+ if (!declaration) {
139
+ reportMissingToError(context, firstStatement, expectedParamName);
140
+ return;
141
+ }
142
+
143
+ if (declaration.id.type !== 'Identifier' || declaration.id.name !== expectedVarName) {
144
+ context.report({
145
+ node: declaration.id,
146
+ messageId: 'wrongVariableName',
147
+ data: { expected: expectedVarName, actual: declaration.id.name || 'unknown' },
148
+ });
149
+ return;
150
+ }
151
+
152
+ if (!declaration.init || declaration.init.type !== 'CallExpression') {
153
+ reportMissingToError(context, declaration.init || declaration, expectedParamName);
154
+ return;
155
+ }
156
+
157
+ const callExpr = declaration.init as CallExpressionNode;
158
+ const callee = callExpr.callee;
159
+ if (callee.type !== 'Identifier' || callee.name !== 'toError') {
160
+ reportMissingToError(context, callee, expectedParamName);
161
+ return;
162
+ }
163
+
164
+ const args = callExpr.arguments;
165
+ if (
166
+ args.length !== 1 ||
167
+ args[0].type !== 'Identifier' ||
168
+ (args[0] as IdentifierNode).name !== actualParamName
169
+ ) {
170
+ reportMissingToError(context, callExpr, actualParamName);
171
+ }
172
+ }
173
+
61
174
  const rule: Rule.RuleModule = {
62
175
  meta: {
63
176
  type: 'problem',
@@ -71,7 +184,7 @@ const rule: Rule.RuleModule = {
71
184
  missingToError:
72
185
  'Catch block must call toError({{param}}) as first statement or comment it out to explicitly ignore errors',
73
186
  wrongVariableName: 'Error variable must be named "{{expected}}", got "{{actual}}"',
74
- missingTypeAnnotation: 'Catch parameter must be typed as "any": catch ({{param}}: any)',
187
+ missingTypeAnnotation: 'Catch parameter must be typed as "unknown": catch ({{param}}: unknown)',
75
188
  wrongParameterName:
76
189
  'Catch parameter must be named "err" (or "err2", "err3" for nested catches), got "{{actual}}"',
77
190
  toErrorNotFirst: 'toError({{param}}) must be the first statement in the catch block',
@@ -81,26 +194,20 @@ const rule: Rule.RuleModule = {
81
194
  },
82
195
 
83
196
  create(context: Rule.RuleContext): Rule.RuleListener {
84
- // Track nesting depth for err, err2, err3, etc.
85
197
  const catchStack: CatchClauseNode[] = [];
86
198
 
87
199
  return {
88
200
  CatchClause(node: any): void {
89
201
  const catchNode = node as CatchClauseNode;
90
-
91
- // Calculate depth (1-based: first catch is depth 1)
92
202
  const depth = catchStack.length + 1;
93
203
  catchStack.push(catchNode);
94
204
 
95
- // Build expected names based on depth
96
205
  const suffix = depth === 1 ? '' : String(depth);
97
206
  const expectedParamName = 'err' + suffix;
98
207
  const expectedVarName = 'error' + suffix;
99
208
 
100
- // Get the catch parameter
101
209
  const param = catchNode.param;
102
210
  if (!param) {
103
- // No parameter - unusual but technically valid (though not our pattern)
104
211
  context.report({
105
212
  node: catchNode,
106
213
  messageId: 'missingTypeAnnotation',
@@ -109,166 +216,24 @@ const rule: Rule.RuleModule = {
109
216
  return;
110
217
  }
111
218
 
112
- // Track the actual parameter name for validation (may differ from expected)
113
219
  const actualParamName =
114
220
  param.type === 'Identifier' ? param.name : expectedParamName;
115
221
 
116
- // RULE 1: Parameter must be named correctly (err, err2, err3, etc.)
117
- if (param.type === 'Identifier' && param.name !== expectedParamName) {
118
- context.report({
119
- node: param,
120
- messageId: 'wrongParameterName',
121
- data: {
122
- actual: param.name,
123
- },
124
- });
125
- }
222
+ validateParamName(context, param, expectedParamName);
223
+ validateTypeAnnotation(context, param, expectedParamName);
126
224
 
127
- // RULE 2: Must have type annotation ": any"
128
- if (
129
- !param.typeAnnotation ||
130
- !param.typeAnnotation.typeAnnotation ||
131
- param.typeAnnotation.typeAnnotation.type !== 'TSAnyKeyword'
132
- ) {
133
- context.report({
134
- node: param,
135
- messageId: 'missingTypeAnnotation',
136
- data: {
137
- param: param.name || expectedParamName,
138
- },
139
- });
140
- }
141
-
142
- // RULE 3: Check first statement in catch block
143
- const body = catchNode.body.body;
144
225
  const sourceCode = context.sourceCode || context.getSourceCode();
145
-
146
- // IMPORTANT: Check for commented ignore pattern FIRST (before checking if body is empty)
147
- // This allows Pattern 2 (empty catch with only comment) to be valid
148
- // Look for: //const error = toError(err);
149
- const catchBlockStart = catchNode.body.range![0];
150
- const catchBlockEnd = catchNode.body.range![1];
151
- const catchBlockText = sourceCode.text.substring(catchBlockStart, catchBlockEnd);
152
-
153
- const ignorePattern = new RegExp(
154
- `//\\s*const\\s+${expectedVarName}\\s*=\\s*toError\\(${actualParamName}\\)`,
155
- );
156
-
157
- if (ignorePattern.test(catchBlockText)) {
158
- // Pattern 2: Explicitly ignored - valid!
226
+ if (hasIgnoreComment(catchNode, sourceCode, expectedVarName, actualParamName)) {
159
227
  return;
160
228
  }
161
229
 
162
- // Now check if body is empty (after checking for commented pattern)
230
+ const body = catchNode.body.body;
163
231
  if (body.length === 0) {
164
- // Empty catch block without comment - not allowed
165
- context.report({
166
- node: catchNode.body,
167
- messageId: 'missingToError',
168
- data: {
169
- param: expectedParamName,
170
- },
171
- });
172
- return;
173
- }
174
-
175
- const firstStatement = body[0];
176
-
177
- // Check if first statement is: const error = toError(err)
178
- if (firstStatement.type !== 'VariableDeclaration') {
179
- context.report({
180
- node: firstStatement,
181
- messageId: 'missingToError',
182
- data: {
183
- param: expectedParamName,
184
- },
185
- });
186
- return;
187
- }
188
-
189
- const varDecl = firstStatement as VariableDeclarationNode;
190
- const declaration = varDecl.declarations[0];
191
- if (!declaration) {
192
- context.report({
193
- node: firstStatement,
194
- messageId: 'missingToError',
195
- data: {
196
- param: expectedParamName,
197
- },
198
- });
199
- return;
200
- }
201
-
202
- // Check variable name
203
- if (
204
- declaration.id.type !== 'Identifier' ||
205
- declaration.id.name !== expectedVarName
206
- ) {
207
- context.report({
208
- node: declaration.id,
209
- messageId: 'wrongVariableName',
210
- data: {
211
- expected: expectedVarName,
212
- actual: declaration.id.name || 'unknown',
213
- },
214
- });
215
- return;
216
- }
217
-
218
- // Check initialization: toError(err)
219
- if (!declaration.init) {
220
- context.report({
221
- node: declaration,
222
- messageId: 'missingToError',
223
- data: {
224
- param: expectedParamName,
225
- },
226
- });
227
- return;
228
- }
229
-
230
- if (declaration.init.type !== 'CallExpression') {
231
- context.report({
232
- node: declaration.init,
233
- messageId: 'missingToError',
234
- data: {
235
- param: expectedParamName,
236
- },
237
- });
238
- return;
239
- }
240
-
241
- const callExpr = declaration.init as CallExpressionNode;
242
- const callee = callExpr.callee;
243
- if (callee.type !== 'Identifier' || callee.name !== 'toError') {
244
- context.report({
245
- node: callee,
246
- messageId: 'missingToError',
247
- data: {
248
- param: expectedParamName,
249
- },
250
- });
251
- return;
252
- }
253
-
254
- // Check argument: must be the catch parameter (use actual param name)
255
- const args = callExpr.arguments;
256
- if (
257
- args.length !== 1 ||
258
- args[0].type !== 'Identifier' ||
259
- (args[0] as IdentifierNode).name !== actualParamName
260
- ) {
261
- context.report({
262
- node: callExpr,
263
- messageId: 'missingToError',
264
- data: {
265
- param: actualParamName,
266
- },
267
- });
232
+ reportMissingToError(context, catchNode.body, expectedParamName);
268
233
  return;
269
234
  }
270
235
 
271
- // All checks passed!
236
+ validateToErrorCall(context, body[0], expectedParamName, expectedVarName, actualParamName);
272
237
  },
273
238
 
274
239
  'CatchClause:exit'(): void {
@@ -14,6 +14,7 @@
14
14
  const tslib_1 = require("tslib");
15
15
  const fs = tslib_1.__importStar(require("fs"));
16
16
  const path = tslib_1.__importStar(require("path"));
17
+ const toError_1 = require("../../toError");
17
18
  const DEPENDENCIES_DOC_CONTENT = `# Instructions: Architecture Dependency Violation
18
19
 
19
20
  IN GENERAL, it is better to avoid these changes and find a different way by moving classes
@@ -230,9 +231,8 @@ function loadBlessedGraph(workspaceRoot) {
230
231
  return cachedGraph;
231
232
  }
232
233
  catch (err) {
233
- //const error = toError(err);
234
- // err is used below
235
- console.error(`[ESLint @webpieces/enforce-architecture] Could not load graph: ${err}`);
234
+ const error = (0, toError_1.toError)(err);
235
+ console.error(`[ESLint @webpieces/enforce-architecture] Could not load graph: ${error.message}`);
236
236
  return null;
237
237
  }
238
238
  }
@@ -1 +1 @@
1
- {"version":3,"file":"enforce-architecture.js","sourceRoot":"","sources":["../../../../../../packages/tooling/dev-config/eslint-plugin/rules/enforce-architecture.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ESLint rule to enforce architecture boundaries\n *\n * Validates that imports from @webpieces/* packages comply with the\n * blessed dependency graph in .graphs/dependencies.json\n *\n * Supports transitive dependencies: if A depends on B and B depends on C,\n * then A can import from C.\n *\n * Configuration:\n * '@webpieces/enforce-architecture': 'error'\n */\n\nimport type { Rule } from 'eslint';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nconst DEPENDENCIES_DOC_CONTENT = `# Instructions: Architecture Dependency Violation\n\nIN GENERAL, it is better to avoid these changes and find a different way by moving classes\naround to existing packages you already depend on. It is not always avoidable though.\nA clean dependency graph keeps you out of huge trouble later.\n\nIf you are a human, simply run these commands:\n* nx run architecture:visualize - to see the new dependencies and validate that change is desired\n* nx run architecture:generate - updates the dep graph\n* git diff architecture/dependencies.json - to see the deps changes you made\n\n**READ THIS FILE FIRST before making any changes!**\n\n## ⚠️ CRITICAL WARNING ⚠️\n\n**This is a VERY IMPORTANT change that has LARGE REPERCUSSIONS later!**\n\nAdding new dependencies creates technical debt that compounds over time:\n- Creates coupling between packages that may be hard to undo\n- Can create circular dependency tangles\n- Makes packages harder to test in isolation\n- Increases build times and bundle sizes\n- May force unnecessary upgrades across the codebase\n\n**DO NOT add dependencies without senior developer approval!**\n\n## Understanding the Error\n\nYou've attempted to import from a package that is not in your project's allowed dependencies.\nThe architecture enforces a layered dependency structure where:\n- Level 0 packages are foundation packages with NO dependencies on other @webpieces packages\n- Higher level packages can only depend on lower level packages\n- All dependencies must be explicitly declared\n\n## Steps to Resolve\n\n### Step 1: Generate Current Dependency Graph\nRun this command to see the current architecture:\n\\`\\`\\`bash\nnpx nx run architecture:generate\n\\`\\`\\`\nThis creates/updates \\`architecture/dependencies.json\\` showing all packages and their levels.\n\n### Step 2: Analyze the Proposed Change\nAsk yourself:\n1. **Is this import truly necessary?** Can you refactor to avoid it?\n2. **Should the code move instead?** Maybe the code belongs in a different package.\n3. **Will this create a cycle?** Use \\`npx nx graph\\` to visualize dependencies.\n4. **Can you use an interface/abstraction?** Define interface in lower-level package, implement in higher-level.\n\n### Step 3: Get Senior Developer Approval\n\n## 🛑 AI AGENTS: STOP HERE AND ASK FOR HUMAN APPROVAL! 🛑\n\n**YOU MUST NOT PROCEED TO STEP 4 WITHOUT EXPLICIT HUMAN APPROVAL!**\n\n**REQUIRED**: Discuss this architectural change with a senior developer before proceeding.\n- Explain why the dependency is needed\n- Show you've considered alternatives (Step 2)\n- **WAIT for explicit approval before making ANY changes to project.json or package.json**\n\n**AI Agent Instructions:**\n1. Present your analysis from Step 2 to the human\n2. Explain which package needs which dependency and why\n3. ASK: \"Do you approve adding this dependency?\"\n4. **DO NOT modify project.json or package.json until you receive explicit \"yes\" or approval**\n\n### Step 4: If Approved, Add the Dependency\n\n## ⛔ NEVER MODIFY THESE FILES WITHOUT HUMAN APPROVAL FROM STEP 3! ⛔\n\nOnly after receiving explicit human approval in Step 3, make these changes:\n\n1. **Update project.json** - Add to \\`build.dependsOn\\`:\n \\`\\`\\`json\n {\n \"targets\": {\n \"build\": {\n \"dependsOn\": [\"^build\", \"dep1:build\", \"NEW_PACKAGE:build\"]\n }\n }\n }\n \\`\\`\\`\n\n2. **Update package.json** - Add to \\`dependencies\\`:\n \\`\\`\\`json\n {\n \"dependencies\": {\n \"@webpieces/NEW_PACKAGE\": \"*\"\n }\n }\n \\`\\`\\`\n\n### Step 5: Update Architecture Definition\nRun this command to validate and update the architecture:\n\\`\\`\\`bash\nnpx nx run architecture:generate\n\\`\\`\\`\n\nThis will:\n- Detect any cycles (which MUST be fixed before proceeding)\n- Update \\`architecture/dependencies.json\\` with the new dependency\n- Recalculate package levels\n\n### Step 6: Verify No Cycles\n\\`\\`\\`bash\nnpx nx run architecture:validate-no-architecture-cycles\n\\`\\`\\`\n\nIf cycles are detected, you MUST refactor to break the cycle. Common strategies:\n- Move shared code to a lower-level package\n- Use dependency inversion (interfaces in low-level, implementations in high-level)\n- Restructure package boundaries\n\n## Alternative Solutions (Preferred over adding dependencies)\n\n### Option A: Move the Code\nIf you need functionality from another package, consider moving that code to a shared lower-level package.\n\n### Option B: Dependency Inversion\nDefine an interface in the lower-level package, implement it in the higher-level package:\n\\`\\`\\`typescript\n// In foundation package (level 0)\nexport interface Logger { log(msg: string): void; }\n\n// In higher-level package\nexport class ConsoleLogger implements Logger { ... }\n\\`\\`\\`\n\n### Option C: Pass Dependencies as Parameters\nInstead of importing, receive the dependency as a constructor or method parameter.\n\n## Remember\n- Every dependency you add today is technical debt for tomorrow\n- The best dependency is the one you don't need\n- When in doubt, refactor rather than add dependencies\n`;\n\n// Module-level flag to prevent redundant file creation\nlet dependenciesDocCreated = false;\n\n/**\n * Ensure a documentation file exists at the given path.\n */\nfunction ensureDocFile(docPath: string, content: string): boolean {\n try {\n fs.mkdirSync(path.dirname(docPath), { recursive: true });\n fs.writeFileSync(docPath, content, 'utf-8');\n return true;\n } catch (err: any) {\n void err;\n console.warn(`[webpieces] Could not create doc file: ${docPath}`);\n return false;\n }\n}\n\n/**\n * Ensure the dependencies documentation file exists.\n * Called when an architecture violation is detected.\n */\nfunction ensureDependenciesDoc(workspaceRoot: string): void {\n if (dependenciesDocCreated) return;\n const docPath = path.join(workspaceRoot, 'tmp', 'webpieces', 'webpieces.dependencies.md');\n if (ensureDocFile(docPath, DEPENDENCIES_DOC_CONTENT)) {\n dependenciesDocCreated = true;\n }\n}\n\n/**\n * Graph entry format from .graphs/dependencies.json\n */\ninterface GraphEntry {\n level: number;\n dependsOn: string[];\n}\n\ntype EnhancedGraph = Record<string, GraphEntry>;\n\n/**\n * Project mapping entry\n */\ninterface ProjectMapping {\n root: string;\n name: string;\n}\n\n// Cache for blessed graph (loaded once per lint run)\nlet cachedGraph: EnhancedGraph | null = null;\nlet cachedGraphPath: string | null = null;\n\n// Cache for project mappings\nlet cachedProjectMappings: ProjectMapping[] | null = null;\n\n/**\n * Find workspace root by walking up from file location\n */\nfunction findWorkspaceRoot(startPath: string): string {\n let currentDir = path.dirname(startPath);\n\n for (let i = 0; i < 20; i++) {\n const packagePath = path.join(currentDir, 'package.json');\n if (fs.existsSync(packagePath)) {\n try {\n const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));\n if (pkg.workspaces || pkg.name === 'webpieces-ts') {\n return currentDir;\n }\n } catch (err: any) {\n //const error = toError(err);\n void err;\n }\n }\n\n const parent = path.dirname(currentDir);\n if (parent === currentDir) break;\n currentDir = parent;\n }\n\n return process.cwd();\n}\n\n/**\n * Load blessed graph from architecture/dependencies.json\n */\nfunction loadBlessedGraph(workspaceRoot: string): EnhancedGraph | null {\n const graphPath = path.join(workspaceRoot, 'architecture', 'dependencies.json');\n\n // Return cached if same path\n if (cachedGraphPath === graphPath && cachedGraph !== null) {\n return cachedGraph;\n }\n\n if (!fs.existsSync(graphPath)) {\n return null;\n }\n\n try {\n const content = fs.readFileSync(graphPath, 'utf-8');\n cachedGraph = JSON.parse(content) as EnhancedGraph;\n cachedGraphPath = graphPath;\n return cachedGraph;\n } catch (err: any) {\n //const error = toError(err);\n // err is used below\n console.error(`[ESLint @webpieces/enforce-architecture] Could not load graph: ${err}`);\n return null;\n }\n}\n\n/**\n * Build set of all workspace package names (from package.json files)\n * Used to detect workspace imports (works for any scope or unscoped)\n */\nfunction buildWorkspacePackageNames(workspaceRoot: string): Set<string> {\n const packageNames = new Set<string>();\n const mappings = buildProjectMappings(workspaceRoot);\n\n for (const mapping of mappings) {\n const pkgJsonPath = path.join(workspaceRoot, mapping.root, 'package.json');\n if (fs.existsSync(pkgJsonPath)) {\n try {\n const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));\n if (pkgJson.name) {\n packageNames.add(pkgJson.name);\n }\n } catch {\n // Ignore parse errors\n }\n }\n }\n\n return packageNames;\n}\n\n/**\n * Check if an import path is a workspace project\n * Works for scoped (@scope/name) or unscoped (name) packages\n */\nfunction isWorkspaceImport(importPath: string, workspaceRoot: string): boolean {\n const workspacePackages = buildWorkspacePackageNames(workspaceRoot);\n return workspacePackages.has(importPath);\n}\n\n/**\n * Get project name from package name\n * e.g., '@webpieces/client' → 'client', 'apis' → 'apis'\n */\nfunction getProjectNameFromPackageName(packageName: string, workspaceRoot: string): string {\n const mappings = buildProjectMappings(workspaceRoot);\n\n // Try to find by reading package.json files\n for (const mapping of mappings) {\n const pkgJsonPath = path.join(workspaceRoot, mapping.root, 'package.json');\n if (fs.existsSync(pkgJsonPath)) {\n try {\n const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));\n if (pkgJson.name === packageName) {\n return mapping.name; // Return project name\n }\n } catch {\n // Ignore parse errors\n }\n }\n }\n\n // Fallback: return package name as-is (might be unscoped project name)\n return packageName;\n}\n\n/**\n * Build project mappings from project.json files in workspace\n */\nfunction buildProjectMappings(workspaceRoot: string): ProjectMapping[] {\n if (cachedProjectMappings !== null) {\n return cachedProjectMappings;\n }\n\n const mappings: ProjectMapping[] = [];\n\n // Scan common locations for project.json files\n const searchDirs = ['packages', 'apps', 'libs', 'libraries', 'services'];\n\n for (const searchDir of searchDirs) {\n const searchPath = path.join(workspaceRoot, searchDir);\n if (!fs.existsSync(searchPath)) continue;\n\n scanForProjects(searchPath, workspaceRoot, mappings);\n }\n\n // Sort by path length (longest first) for more specific matching\n mappings.sort((a, b) => b.root.length - a.root.length);\n\n cachedProjectMappings = mappings;\n return mappings;\n}\n\n/**\n * Recursively scan for project.json files\n */\nfunction scanForProjects(\n dir: string,\n workspaceRoot: string,\n mappings: ProjectMapping[]\n): void {\n try {\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n\n if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {\n // Check for project.json in this directory\n const projectJsonPath = path.join(fullPath, 'project.json');\n if (fs.existsSync(projectJsonPath)) {\n try {\n const projectJson = JSON.parse(fs.readFileSync(projectJsonPath, 'utf-8'));\n const projectRoot = path.relative(workspaceRoot, fullPath);\n\n // Use project name from project.json as-is (no scope forcing)\n const projectName = projectJson.name || entry.name;\n\n mappings.push({\n root: projectRoot,\n name: projectName,\n });\n } catch (err: any) {\n //const error = toError(err);\n void err;\n }\n }\n\n // Continue scanning subdirectories\n scanForProjects(fullPath, workspaceRoot, mappings);\n }\n }\n } catch (err: any) {\n //const error = toError(err);\n void err;\n }\n}\n\n/**\n * Get project name from file path\n */\nfunction getProjectFromFile(filePath: string, workspaceRoot: string): string | null {\n const relativePath = path.relative(workspaceRoot, filePath).replace(/\\\\/g, '/');\n const mappings = buildProjectMappings(workspaceRoot);\n\n for (const mapping of mappings) {\n if (relativePath.startsWith(mapping.root + '/') || relativePath.startsWith(mapping.root)) {\n return mapping.name;\n }\n }\n\n return null;\n}\n\n/**\n * Compute all transitive dependencies for a project\n */\nfunction computeTransitiveDependencies(project: string, graph: EnhancedGraph): Set<string> {\n const result = new Set<string>();\n const visited = new Set<string>();\n\n function visit(currentProject: string): void {\n if (visited.has(currentProject)) return;\n visited.add(currentProject);\n\n const entry = graph[currentProject];\n if (!entry || !entry.dependsOn) return;\n\n for (const dep of entry.dependsOn) {\n result.add(dep);\n visit(dep);\n }\n }\n\n visit(project);\n return result;\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Enforce blessed architecture dependencies',\n category: 'Best Practices',\n recommended: true,\n url: 'https://github.com/deanhiller/webpieces-ts',\n },\n messages: {\n illegalImport:\n \"⚠️ AI Agent: READ tmp/webpieces/webpieces.dependencies.md for resolution steps!\\n\\n\" +\n \"Import '{{imported}}' violates architecture boundaries.\\n\\n\" +\n \"Project '{{project}}' (level {{level}}) can only import from:\\n\" +\n '{{allowedList}}',\n noGraph:\n 'No architecture graph found at architecture/dependencies.json\\n' +\n 'Run: nx run architecture:validate --mode=update',\n },\n schema: [],\n },\n\n create(context: Rule.RuleContext): Rule.RuleListener {\n const filename = context.filename || context.getFilename();\n const workspaceRoot = findWorkspaceRoot(filename);\n\n return {\n ImportDeclaration(node: any): void {\n const importPath = node.source.value as string;\n\n // Check if this is a workspace import (works for any scope or unscoped)\n if (!isWorkspaceImport(importPath, workspaceRoot)) {\n return; // Not a workspace import, skip validation\n }\n\n // Determine which project this file belongs to\n const sourceProject = getProjectFromFile(filename, workspaceRoot);\n if (!sourceProject) {\n // File not in any known project (e.g., tools/, scripts/)\n return;\n }\n\n // Convert import (package name) to project name\n const targetProject = getProjectNameFromPackageName(importPath, workspaceRoot);\n\n // Self-import is always allowed\n if (targetProject === sourceProject) {\n return;\n }\n\n // Load blessed graph\n const graph = loadBlessedGraph(workspaceRoot);\n if (!graph) {\n // No graph file - warn but don't fail (allows gradual adoption)\n return;\n }\n\n // Get project entry\n const projectEntry = graph[sourceProject];\n if (!projectEntry) {\n // Project not in graph (new project?) - allow\n return;\n }\n\n // Compute allowed dependencies (direct + transitive)\n const allowedDeps = computeTransitiveDependencies(sourceProject, graph);\n\n // Check if import is allowed (use project name, not package name)\n if (!allowedDeps.has(targetProject)) {\n // Write documentation file for AI/developer to read\n ensureDependenciesDoc(workspaceRoot);\n\n // Build list of all allowed deps (direct + transitive) sorted by level\n const allAllowedDeps = Array.from(allowedDeps);\n // Sort by level (highest first) then alphabetically\n allAllowedDeps.sort((a, b) => {\n const levelA = graph[a]?.level ?? 0;\n const levelB = graph[b]?.level ?? 0;\n if (levelB !== levelA) return levelB - levelA;\n return a.localeCompare(b);\n });\n const allowedList =\n allAllowedDeps.length > 0\n ? allAllowedDeps\n .map((dep) => ` - ${dep} (level ${graph[dep]?.level ?? '?'})`)\n .join('\\n')\n : ' (none - this is a foundation project)';\n\n context.report({\n node: node.source,\n messageId: 'illegalImport',\n data: {\n imported: importPath,\n project: sourceProject,\n level: String(projectEntry.level),\n allowedList: allowedList,\n },\n });\n }\n },\n };\n },\n};\n\nexport = rule;\n"]}
1
+ {"version":3,"file":"enforce-architecture.js","sourceRoot":"","sources":["../../../../../../packages/tooling/dev-config/eslint-plugin/rules/enforce-architecture.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ESLint rule to enforce architecture boundaries\n *\n * Validates that imports from @webpieces/* packages comply with the\n * blessed dependency graph in .graphs/dependencies.json\n *\n * Supports transitive dependencies: if A depends on B and B depends on C,\n * then A can import from C.\n *\n * Configuration:\n * '@webpieces/enforce-architecture': 'error'\n */\n\nimport type { Rule } from 'eslint';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { toError } from '../../toError';\n\nconst DEPENDENCIES_DOC_CONTENT = `# Instructions: Architecture Dependency Violation\n\nIN GENERAL, it is better to avoid these changes and find a different way by moving classes\naround to existing packages you already depend on. It is not always avoidable though.\nA clean dependency graph keeps you out of huge trouble later.\n\nIf you are a human, simply run these commands:\n* nx run architecture:visualize - to see the new dependencies and validate that change is desired\n* nx run architecture:generate - updates the dep graph\n* git diff architecture/dependencies.json - to see the deps changes you made\n\n**READ THIS FILE FIRST before making any changes!**\n\n## ⚠️ CRITICAL WARNING ⚠️\n\n**This is a VERY IMPORTANT change that has LARGE REPERCUSSIONS later!**\n\nAdding new dependencies creates technical debt that compounds over time:\n- Creates coupling between packages that may be hard to undo\n- Can create circular dependency tangles\n- Makes packages harder to test in isolation\n- Increases build times and bundle sizes\n- May force unnecessary upgrades across the codebase\n\n**DO NOT add dependencies without senior developer approval!**\n\n## Understanding the Error\n\nYou've attempted to import from a package that is not in your project's allowed dependencies.\nThe architecture enforces a layered dependency structure where:\n- Level 0 packages are foundation packages with NO dependencies on other @webpieces packages\n- Higher level packages can only depend on lower level packages\n- All dependencies must be explicitly declared\n\n## Steps to Resolve\n\n### Step 1: Generate Current Dependency Graph\nRun this command to see the current architecture:\n\\`\\`\\`bash\nnpx nx run architecture:generate\n\\`\\`\\`\nThis creates/updates \\`architecture/dependencies.json\\` showing all packages and their levels.\n\n### Step 2: Analyze the Proposed Change\nAsk yourself:\n1. **Is this import truly necessary?** Can you refactor to avoid it?\n2. **Should the code move instead?** Maybe the code belongs in a different package.\n3. **Will this create a cycle?** Use \\`npx nx graph\\` to visualize dependencies.\n4. **Can you use an interface/abstraction?** Define interface in lower-level package, implement in higher-level.\n\n### Step 3: Get Senior Developer Approval\n\n## 🛑 AI AGENTS: STOP HERE AND ASK FOR HUMAN APPROVAL! 🛑\n\n**YOU MUST NOT PROCEED TO STEP 4 WITHOUT EXPLICIT HUMAN APPROVAL!**\n\n**REQUIRED**: Discuss this architectural change with a senior developer before proceeding.\n- Explain why the dependency is needed\n- Show you've considered alternatives (Step 2)\n- **WAIT for explicit approval before making ANY changes to project.json or package.json**\n\n**AI Agent Instructions:**\n1. Present your analysis from Step 2 to the human\n2. Explain which package needs which dependency and why\n3. ASK: \"Do you approve adding this dependency?\"\n4. **DO NOT modify project.json or package.json until you receive explicit \"yes\" or approval**\n\n### Step 4: If Approved, Add the Dependency\n\n## ⛔ NEVER MODIFY THESE FILES WITHOUT HUMAN APPROVAL FROM STEP 3! ⛔\n\nOnly after receiving explicit human approval in Step 3, make these changes:\n\n1. **Update project.json** - Add to \\`build.dependsOn\\`:\n \\`\\`\\`json\n {\n \"targets\": {\n \"build\": {\n \"dependsOn\": [\"^build\", \"dep1:build\", \"NEW_PACKAGE:build\"]\n }\n }\n }\n \\`\\`\\`\n\n2. **Update package.json** - Add to \\`dependencies\\`:\n \\`\\`\\`json\n {\n \"dependencies\": {\n \"@webpieces/NEW_PACKAGE\": \"*\"\n }\n }\n \\`\\`\\`\n\n### Step 5: Update Architecture Definition\nRun this command to validate and update the architecture:\n\\`\\`\\`bash\nnpx nx run architecture:generate\n\\`\\`\\`\n\nThis will:\n- Detect any cycles (which MUST be fixed before proceeding)\n- Update \\`architecture/dependencies.json\\` with the new dependency\n- Recalculate package levels\n\n### Step 6: Verify No Cycles\n\\`\\`\\`bash\nnpx nx run architecture:validate-no-architecture-cycles\n\\`\\`\\`\n\nIf cycles are detected, you MUST refactor to break the cycle. Common strategies:\n- Move shared code to a lower-level package\n- Use dependency inversion (interfaces in low-level, implementations in high-level)\n- Restructure package boundaries\n\n## Alternative Solutions (Preferred over adding dependencies)\n\n### Option A: Move the Code\nIf you need functionality from another package, consider moving that code to a shared lower-level package.\n\n### Option B: Dependency Inversion\nDefine an interface in the lower-level package, implement it in the higher-level package:\n\\`\\`\\`typescript\n// In foundation package (level 0)\nexport interface Logger { log(msg: string): void; }\n\n// In higher-level package\nexport class ConsoleLogger implements Logger { ... }\n\\`\\`\\`\n\n### Option C: Pass Dependencies as Parameters\nInstead of importing, receive the dependency as a constructor or method parameter.\n\n## Remember\n- Every dependency you add today is technical debt for tomorrow\n- The best dependency is the one you don't need\n- When in doubt, refactor rather than add dependencies\n`;\n\n// Module-level flag to prevent redundant file creation\nlet dependenciesDocCreated = false;\n\n/**\n * Ensure a documentation file exists at the given path.\n */\nfunction ensureDocFile(docPath: string, content: string): boolean {\n try {\n fs.mkdirSync(path.dirname(docPath), { recursive: true });\n fs.writeFileSync(docPath, content, 'utf-8');\n return true;\n } catch (err: unknown) {\n void err;\n console.warn(`[webpieces] Could not create doc file: ${docPath}`);\n return false;\n }\n}\n\n/**\n * Ensure the dependencies documentation file exists.\n * Called when an architecture violation is detected.\n */\nfunction ensureDependenciesDoc(workspaceRoot: string): void {\n if (dependenciesDocCreated) return;\n const docPath = path.join(workspaceRoot, 'tmp', 'webpieces', 'webpieces.dependencies.md');\n if (ensureDocFile(docPath, DEPENDENCIES_DOC_CONTENT)) {\n dependenciesDocCreated = true;\n }\n}\n\n/**\n * Graph entry format from .graphs/dependencies.json\n */\ninterface GraphEntry {\n level: number;\n dependsOn: string[];\n}\n\ntype EnhancedGraph = Record<string, GraphEntry>;\n\n/**\n * Project mapping entry\n */\ninterface ProjectMapping {\n root: string;\n name: string;\n}\n\n// Cache for blessed graph (loaded once per lint run)\nlet cachedGraph: EnhancedGraph | null = null;\nlet cachedGraphPath: string | null = null;\n\n// Cache for project mappings\nlet cachedProjectMappings: ProjectMapping[] | null = null;\n\n/**\n * Find workspace root by walking up from file location\n */\nfunction findWorkspaceRoot(startPath: string): string {\n let currentDir = path.dirname(startPath);\n\n for (let i = 0; i < 20; i++) {\n const packagePath = path.join(currentDir, 'package.json');\n if (fs.existsSync(packagePath)) {\n try {\n const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));\n if (pkg.workspaces || pkg.name === 'webpieces-ts') {\n return currentDir;\n }\n } catch (err: unknown) {\n //const error = toError(err);\n void err;\n }\n }\n\n const parent = path.dirname(currentDir);\n if (parent === currentDir) break;\n currentDir = parent;\n }\n\n return process.cwd();\n}\n\n/**\n * Load blessed graph from architecture/dependencies.json\n */\nfunction loadBlessedGraph(workspaceRoot: string): EnhancedGraph | null {\n const graphPath = path.join(workspaceRoot, 'architecture', 'dependencies.json');\n\n // Return cached if same path\n if (cachedGraphPath === graphPath && cachedGraph !== null) {\n return cachedGraph;\n }\n\n if (!fs.existsSync(graphPath)) {\n return null;\n }\n\n try {\n const content = fs.readFileSync(graphPath, 'utf-8');\n cachedGraph = JSON.parse(content) as EnhancedGraph;\n cachedGraphPath = graphPath;\n return cachedGraph;\n } catch (err: unknown) {\n const error = toError(err);\n console.error(`[ESLint @webpieces/enforce-architecture] Could not load graph: ${error.message}`);\n return null;\n }\n}\n\n/**\n * Build set of all workspace package names (from package.json files)\n * Used to detect workspace imports (works for any scope or unscoped)\n */\nfunction buildWorkspacePackageNames(workspaceRoot: string): Set<string> {\n const packageNames = new Set<string>();\n const mappings = buildProjectMappings(workspaceRoot);\n\n for (const mapping of mappings) {\n const pkgJsonPath = path.join(workspaceRoot, mapping.root, 'package.json');\n if (fs.existsSync(pkgJsonPath)) {\n try {\n const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));\n if (pkgJson.name) {\n packageNames.add(pkgJson.name);\n }\n } catch {\n // Ignore parse errors\n }\n }\n }\n\n return packageNames;\n}\n\n/**\n * Check if an import path is a workspace project\n * Works for scoped (@scope/name) or unscoped (name) packages\n */\nfunction isWorkspaceImport(importPath: string, workspaceRoot: string): boolean {\n const workspacePackages = buildWorkspacePackageNames(workspaceRoot);\n return workspacePackages.has(importPath);\n}\n\n/**\n * Get project name from package name\n * e.g., '@webpieces/client' → 'client', 'apis' → 'apis'\n */\nfunction getProjectNameFromPackageName(packageName: string, workspaceRoot: string): string {\n const mappings = buildProjectMappings(workspaceRoot);\n\n // Try to find by reading package.json files\n for (const mapping of mappings) {\n const pkgJsonPath = path.join(workspaceRoot, mapping.root, 'package.json');\n if (fs.existsSync(pkgJsonPath)) {\n try {\n const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));\n if (pkgJson.name === packageName) {\n return mapping.name; // Return project name\n }\n } catch {\n // Ignore parse errors\n }\n }\n }\n\n // Fallback: return package name as-is (might be unscoped project name)\n return packageName;\n}\n\n/**\n * Build project mappings from project.json files in workspace\n */\nfunction buildProjectMappings(workspaceRoot: string): ProjectMapping[] {\n if (cachedProjectMappings !== null) {\n return cachedProjectMappings;\n }\n\n const mappings: ProjectMapping[] = [];\n\n // Scan common locations for project.json files\n const searchDirs = ['packages', 'apps', 'libs', 'libraries', 'services'];\n\n for (const searchDir of searchDirs) {\n const searchPath = path.join(workspaceRoot, searchDir);\n if (!fs.existsSync(searchPath)) continue;\n\n scanForProjects(searchPath, workspaceRoot, mappings);\n }\n\n // Sort by path length (longest first) for more specific matching\n mappings.sort((a, b) => b.root.length - a.root.length);\n\n cachedProjectMappings = mappings;\n return mappings;\n}\n\n/**\n * Recursively scan for project.json files\n */\nfunction scanForProjects(\n dir: string,\n workspaceRoot: string,\n mappings: ProjectMapping[]\n): void {\n try {\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n\n if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {\n // Check for project.json in this directory\n const projectJsonPath = path.join(fullPath, 'project.json');\n if (fs.existsSync(projectJsonPath)) {\n try {\n const projectJson = JSON.parse(fs.readFileSync(projectJsonPath, 'utf-8'));\n const projectRoot = path.relative(workspaceRoot, fullPath);\n\n // Use project name from project.json as-is (no scope forcing)\n const projectName = projectJson.name || entry.name;\n\n mappings.push({\n root: projectRoot,\n name: projectName,\n });\n } catch (err: unknown) {\n //const error = toError(err);\n void err;\n }\n }\n\n // Continue scanning subdirectories\n scanForProjects(fullPath, workspaceRoot, mappings);\n }\n }\n } catch (err: unknown) {\n //const error = toError(err);\n void err;\n }\n}\n\n/**\n * Get project name from file path\n */\nfunction getProjectFromFile(filePath: string, workspaceRoot: string): string | null {\n const relativePath = path.relative(workspaceRoot, filePath).replace(/\\\\/g, '/');\n const mappings = buildProjectMappings(workspaceRoot);\n\n for (const mapping of mappings) {\n if (relativePath.startsWith(mapping.root + '/') || relativePath.startsWith(mapping.root)) {\n return mapping.name;\n }\n }\n\n return null;\n}\n\n/**\n * Compute all transitive dependencies for a project\n */\nfunction computeTransitiveDependencies(project: string, graph: EnhancedGraph): Set<string> {\n const result = new Set<string>();\n const visited = new Set<string>();\n\n function visit(currentProject: string): void {\n if (visited.has(currentProject)) return;\n visited.add(currentProject);\n\n const entry = graph[currentProject];\n if (!entry || !entry.dependsOn) return;\n\n for (const dep of entry.dependsOn) {\n result.add(dep);\n visit(dep);\n }\n }\n\n visit(project);\n return result;\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Enforce blessed architecture dependencies',\n category: 'Best Practices',\n recommended: true,\n url: 'https://github.com/deanhiller/webpieces-ts',\n },\n messages: {\n illegalImport:\n \"⚠️ AI Agent: READ tmp/webpieces/webpieces.dependencies.md for resolution steps!\\n\\n\" +\n \"Import '{{imported}}' violates architecture boundaries.\\n\\n\" +\n \"Project '{{project}}' (level {{level}}) can only import from:\\n\" +\n '{{allowedList}}',\n noGraph:\n 'No architecture graph found at architecture/dependencies.json\\n' +\n 'Run: nx run architecture:validate --mode=update',\n },\n schema: [],\n },\n\n create(context: Rule.RuleContext): Rule.RuleListener {\n const filename = context.filename || context.getFilename();\n const workspaceRoot = findWorkspaceRoot(filename);\n\n return {\n ImportDeclaration(node: any): void {\n const importPath = node.source.value as string;\n\n // Check if this is a workspace import (works for any scope or unscoped)\n if (!isWorkspaceImport(importPath, workspaceRoot)) {\n return; // Not a workspace import, skip validation\n }\n\n // Determine which project this file belongs to\n const sourceProject = getProjectFromFile(filename, workspaceRoot);\n if (!sourceProject) {\n // File not in any known project (e.g., tools/, scripts/)\n return;\n }\n\n // Convert import (package name) to project name\n const targetProject = getProjectNameFromPackageName(importPath, workspaceRoot);\n\n // Self-import is always allowed\n if (targetProject === sourceProject) {\n return;\n }\n\n // Load blessed graph\n const graph = loadBlessedGraph(workspaceRoot);\n if (!graph) {\n // No graph file - warn but don't fail (allows gradual adoption)\n return;\n }\n\n // Get project entry\n const projectEntry = graph[sourceProject];\n if (!projectEntry) {\n // Project not in graph (new project?) - allow\n return;\n }\n\n // Compute allowed dependencies (direct + transitive)\n const allowedDeps = computeTransitiveDependencies(sourceProject, graph);\n\n // Check if import is allowed (use project name, not package name)\n if (!allowedDeps.has(targetProject)) {\n // Write documentation file for AI/developer to read\n ensureDependenciesDoc(workspaceRoot);\n\n // Build list of all allowed deps (direct + transitive) sorted by level\n const allAllowedDeps = Array.from(allowedDeps);\n // Sort by level (highest first) then alphabetically\n allAllowedDeps.sort((a, b) => {\n const levelA = graph[a]?.level ?? 0;\n const levelB = graph[b]?.level ?? 0;\n if (levelB !== levelA) return levelB - levelA;\n return a.localeCompare(b);\n });\n const allowedList =\n allAllowedDeps.length > 0\n ? allAllowedDeps\n .map((dep) => ` - ${dep} (level ${graph[dep]?.level ?? '?'})`)\n .join('\\n')\n : ' (none - this is a foundation project)';\n\n context.report({\n node: node.source,\n messageId: 'illegalImport',\n data: {\n imported: importPath,\n project: sourceProject,\n level: String(projectEntry.level),\n allowedList: allowedList,\n },\n });\n }\n },\n };\n },\n};\n\nexport = rule;\n"]}
@@ -14,6 +14,7 @@
14
14
  import type { Rule } from 'eslint';
15
15
  import * as fs from 'fs';
16
16
  import * as path from 'path';
17
+ import { toError } from '../../toError';
17
18
 
18
19
  const DEPENDENCIES_DOC_CONTENT = `# Instructions: Architecture Dependency Violation
19
20
 
@@ -164,7 +165,7 @@ function ensureDocFile(docPath: string, content: string): boolean {
164
165
  fs.mkdirSync(path.dirname(docPath), { recursive: true });
165
166
  fs.writeFileSync(docPath, content, 'utf-8');
166
167
  return true;
167
- } catch (err: any) {
168
+ } catch (err: unknown) {
168
169
  void err;
169
170
  console.warn(`[webpieces] Could not create doc file: ${docPath}`);
170
171
  return false;
@@ -222,7 +223,7 @@ function findWorkspaceRoot(startPath: string): string {
222
223
  if (pkg.workspaces || pkg.name === 'webpieces-ts') {
223
224
  return currentDir;
224
225
  }
225
- } catch (err: any) {
226
+ } catch (err: unknown) {
226
227
  //const error = toError(err);
227
228
  void err;
228
229
  }
@@ -256,10 +257,9 @@ function loadBlessedGraph(workspaceRoot: string): EnhancedGraph | null {
256
257
  cachedGraph = JSON.parse(content) as EnhancedGraph;
257
258
  cachedGraphPath = graphPath;
258
259
  return cachedGraph;
259
- } catch (err: any) {
260
- //const error = toError(err);
261
- // err is used below
262
- console.error(`[ESLint @webpieces/enforce-architecture] Could not load graph: ${err}`);
260
+ } catch (err: unknown) {
261
+ const error = toError(err);
262
+ console.error(`[ESLint @webpieces/enforce-architecture] Could not load graph: ${error.message}`);
263
263
  return null;
264
264
  }
265
265
  }
@@ -380,7 +380,7 @@ function scanForProjects(
380
380
  root: projectRoot,
381
381
  name: projectName,
382
382
  });
383
- } catch (err: any) {
383
+ } catch (err: unknown) {
384
384
  //const error = toError(err);
385
385
  void err;
386
386
  }
@@ -390,7 +390,7 @@ function scanForProjects(
390
390
  scanForProjects(fullPath, workspaceRoot, mappings);
391
391
  }
392
392
  }
393
- } catch (err: any) {
393
+ } catch (err: unknown) {
394
394
  //const error = toError(err);
395
395
  void err;
396
396
  }
@@ -11,6 +11,7 @@
11
11
  const tslib_1 = require("tslib");
12
12
  const fs = tslib_1.__importStar(require("fs"));
13
13
  const path = tslib_1.__importStar(require("path"));
14
+ const toError_1 = require("../../toError");
14
15
  const FILE_DOC_CONTENT = `# AI Agent Instructions: File Too Long
15
16
 
16
17
  **READ THIS FILE to fix files that are too long**
@@ -189,9 +190,8 @@ function ensureDocFile(docPath, content) {
189
190
  return true;
190
191
  }
191
192
  catch (err) {
192
- //const error = toError(err);
193
- // err is used in console.warn below
194
- console.warn(`[webpieces] Could not create doc file: ${docPath}`, err);
193
+ const error = (0, toError_1.toError)(err);
194
+ console.warn(`[webpieces] Could not create doc file: ${docPath}`, error);
195
195
  return false;
196
196
  }
197
197
  }
@@ -1 +1 @@
1
- {"version":3,"file":"max-file-lines.js","sourceRoot":"","sources":["../../../../../../packages/tooling/dev-config/eslint-plugin/rules/max-file-lines.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ESLint rule to enforce maximum file length\n *\n * Enforces a configurable maximum line count for files.\n * Default: 700 lines\n *\n * Configuration:\n * '@webpieces/max-file-lines': ['error', { max: 700 }]\n */\n\nimport type { Rule } from 'eslint';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\ninterface FileLinesOptions {\n max: number;\n}\n\nconst FILE_DOC_CONTENT = `# AI Agent Instructions: File Too Long\n\n**READ THIS FILE to fix files that are too long**\n\n## Core Principle\nFiles should contain a SINGLE COHESIVE UNIT.\n- One class per file (Java convention)\n- If class is too large, extract child responsibilities\n- Use dependency injection to compose functionality\n\n## Command: Reduce File Size\n\n### Step 1: Check for Multiple Classes\nIf the file contains multiple classes, **SEPARATE each class into its own file**.\n\n\\`\\`\\`typescript\n// BAD: UserController.ts (multiple classes)\nexport class UserController { /* ... */ }\nexport class UserValidator { /* ... */ }\nexport class UserNotifier { /* ... */ }\n\n// GOOD: Three separate files\n// UserController.ts\nexport class UserController { /* ... */ }\n\n// UserValidator.ts\nexport class UserValidator { /* ... */ }\n\n// UserNotifier.ts\nexport class UserNotifier { /* ... */ }\n\\`\\`\\`\n\n### Step 2: Extract Child Responsibilities (if single class is too large)\n\n#### Pattern: Create New Service Class with Dependency Injection\n\n\\`\\`\\`typescript\n// BAD: UserController.ts (800 lines, single class)\n@provideSingleton()\n@Controller()\nexport class UserController {\n // 200 lines: CRUD operations\n // 300 lines: validation logic\n // 200 lines: notification logic\n // 100 lines: analytics logic\n}\n\n// GOOD: Extract validation service\n// 1. Create UserValidationService.ts\n@provideSingleton()\nexport class UserValidationService {\n validateUserData(data: UserData): ValidationResult {\n // 300 lines of validation logic moved here\n }\n\n validateEmail(email: string): boolean { /* ... */ }\n validatePassword(password: string): boolean { /* ... */ }\n}\n\n// 2. Inject into UserController.ts\n@provideSingleton()\n@Controller()\nexport class UserController {\n constructor(\n @inject(TYPES.UserValidationService)\n private validator: UserValidationService\n ) {}\n\n async createUser(data: UserData): Promise<User> {\n const validation = this.validator.validateUserData(data);\n if (!validation.isValid) {\n throw new ValidationError(validation.errors);\n }\n // ... rest of logic\n }\n}\n\\`\\`\\`\n\n## AI Agent Action Steps\n\n1. **ANALYZE** the file structure:\n - Count classes (if >1, separate immediately)\n - Identify logical responsibilities within single class\n\n2. **IDENTIFY** \"child code\" to extract:\n - Validation logic -> ValidationService\n - Notification logic -> NotificationService\n - Data transformation -> TransformerService\n - External API calls -> ApiService\n - Business rules -> RulesEngine\n\n3. **CREATE** new service file(s):\n - Start with temporary name: \\`XXXX.ts\\` or \\`ChildService.ts\\`\n - Add \\`@provideSingleton()\\` decorator\n - Move child methods to new class\n\n4. **UPDATE** dependency injection:\n - Add to \\`TYPES\\` constants (if using symbol-based DI)\n - Inject new service into original class constructor\n - Replace direct method calls with \\`this.serviceName.method()\\`\n\n5. **RENAME** extracted file:\n - Read the extracted code to understand its purpose\n - Rename \\`XXXX.ts\\` to logical name (e.g., \\`UserValidationService.ts\\`)\n\n6. **VERIFY** file sizes:\n - Original file should now be <700 lines\n - Each extracted file should be <700 lines\n - If still too large, extract more services\n\n## Examples of Child Responsibilities to Extract\n\n| If File Contains | Extract To | Pattern |\n|-----------------|------------|---------|\n| Validation logic (200+ lines) | \\`XValidator.ts\\` or \\`XValidationService.ts\\` | Singleton service |\n| Notification logic (150+ lines) | \\`XNotifier.ts\\` or \\`XNotificationService.ts\\` | Singleton service |\n| Data transformation (200+ lines) | \\`XTransformer.ts\\` | Singleton service |\n| External API calls (200+ lines) | \\`XApiClient.ts\\` | Singleton service |\n| Complex business rules (300+ lines) | \\`XRulesEngine.ts\\` | Singleton service |\n| Database queries (200+ lines) | \\`XRepository.ts\\` | Singleton service |\n\n## WebPieces Dependency Injection Pattern\n\n\\`\\`\\`typescript\n// 1. Define service with @provideSingleton\nimport { provideSingleton } from '@webpieces/http-routing';\n\n@provideSingleton()\nexport class MyService {\n doSomething(): void { /* ... */ }\n}\n\n// 2. Inject into consumer\nimport { inject } from 'inversify';\nimport { TYPES } from './types';\n\n@provideSingleton()\n@Controller()\nexport class MyController {\n constructor(\n @inject(TYPES.MyService) private service: MyService\n ) {}\n}\n\\`\\`\\`\n\nRemember: Find the \"child code\" and pull it down into a new class. Once moved, the code's purpose becomes clear, making it easy to rename to a logical name.\n`;\n\n// Module-level flag to prevent redundant file creation\nlet fileDocCreated = false;\n\nfunction getWorkspaceRoot(context: Rule.RuleContext): string {\n const filename = context.filename || context.getFilename();\n let dir = path.dirname(filename);\n\n // Walk up directory tree to find workspace root\n while (dir !== path.dirname(dir)) {\n const pkgPath = path.join(dir, 'package.json');\n if (fs.existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));\n if (pkg.workspaces || pkg.name === 'webpieces-ts') {\n return dir;\n }\n } catch (err: any) {\n //const error = toError(err);\n void err; // Continue searching if JSON parse fails\n }\n }\n dir = path.dirname(dir);\n }\n return process.cwd(); // Fallback\n}\n\nfunction ensureDocFile(docPath: string, content: string): boolean {\n try {\n fs.mkdirSync(path.dirname(docPath), { recursive: true });\n fs.writeFileSync(docPath, content, 'utf-8');\n return true;\n } catch (err: any) {\n //const error = toError(err);\n // err is used in console.warn below\n console.warn(`[webpieces] Could not create doc file: ${docPath}`, err);\n return false;\n }\n}\n\nfunction ensureFileDoc(context: Rule.RuleContext): void {\n if (fileDocCreated) return; // Performance: only create once per lint run\n\n const workspaceRoot = getWorkspaceRoot(context);\n const docPath = path.join(workspaceRoot, 'tmp', 'webpieces', 'webpieces.filesize.md');\n\n if (ensureDocFile(docPath, FILE_DOC_CONTENT)) {\n fileDocCreated = true;\n }\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'suggestion',\n docs: {\n description: 'Enforce maximum file length',\n category: 'Best Practices',\n recommended: false,\n url: 'https://github.com/deanhiller/webpieces-ts',\n },\n messages: {\n tooLong:\n 'AI Agent: READ tmp/webpieces/webpieces.filesize.md for fix instructions. File has {{actual}} lines (max: {{max}})',\n },\n fixable: undefined,\n schema: [\n {\n type: 'object',\n properties: {\n max: {\n type: 'integer',\n minimum: 1,\n },\n },\n additionalProperties: false,\n },\n ],\n },\n\n create(context: Rule.RuleContext): Rule.RuleListener {\n const options = context.options[0] as FileLinesOptions | undefined;\n const maxLines = options?.max ?? 700;\n\n return {\n Program(node: any): void {\n ensureFileDoc(context);\n\n const sourceCode = context.sourceCode || context.getSourceCode();\n const lines = sourceCode.lines;\n const lineCount = lines.length;\n\n if (lineCount > maxLines) {\n context.report({\n node,\n messageId: 'tooLong',\n data: {\n actual: String(lineCount),\n max: String(maxLines),\n },\n });\n }\n },\n };\n },\n};\n\nexport = rule;\n"]}
1
+ {"version":3,"file":"max-file-lines.js","sourceRoot":"","sources":["../../../../../../packages/tooling/dev-config/eslint-plugin/rules/max-file-lines.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ESLint rule to enforce maximum file length\n *\n * Enforces a configurable maximum line count for files.\n * Default: 700 lines\n *\n * Configuration:\n * '@webpieces/max-file-lines': ['error', { max: 700 }]\n */\n\nimport type { Rule } from 'eslint';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { toError } from '../../toError';\n\ninterface FileLinesOptions {\n max: number;\n}\n\nconst FILE_DOC_CONTENT = `# AI Agent Instructions: File Too Long\n\n**READ THIS FILE to fix files that are too long**\n\n## Core Principle\nFiles should contain a SINGLE COHESIVE UNIT.\n- One class per file (Java convention)\n- If class is too large, extract child responsibilities\n- Use dependency injection to compose functionality\n\n## Command: Reduce File Size\n\n### Step 1: Check for Multiple Classes\nIf the file contains multiple classes, **SEPARATE each class into its own file**.\n\n\\`\\`\\`typescript\n// BAD: UserController.ts (multiple classes)\nexport class UserController { /* ... */ }\nexport class UserValidator { /* ... */ }\nexport class UserNotifier { /* ... */ }\n\n// GOOD: Three separate files\n// UserController.ts\nexport class UserController { /* ... */ }\n\n// UserValidator.ts\nexport class UserValidator { /* ... */ }\n\n// UserNotifier.ts\nexport class UserNotifier { /* ... */ }\n\\`\\`\\`\n\n### Step 2: Extract Child Responsibilities (if single class is too large)\n\n#### Pattern: Create New Service Class with Dependency Injection\n\n\\`\\`\\`typescript\n// BAD: UserController.ts (800 lines, single class)\n@provideSingleton()\n@Controller()\nexport class UserController {\n // 200 lines: CRUD operations\n // 300 lines: validation logic\n // 200 lines: notification logic\n // 100 lines: analytics logic\n}\n\n// GOOD: Extract validation service\n// 1. Create UserValidationService.ts\n@provideSingleton()\nexport class UserValidationService {\n validateUserData(data: UserData): ValidationResult {\n // 300 lines of validation logic moved here\n }\n\n validateEmail(email: string): boolean { /* ... */ }\n validatePassword(password: string): boolean { /* ... */ }\n}\n\n// 2. Inject into UserController.ts\n@provideSingleton()\n@Controller()\nexport class UserController {\n constructor(\n @inject(TYPES.UserValidationService)\n private validator: UserValidationService\n ) {}\n\n async createUser(data: UserData): Promise<User> {\n const validation = this.validator.validateUserData(data);\n if (!validation.isValid) {\n throw new ValidationError(validation.errors);\n }\n // ... rest of logic\n }\n}\n\\`\\`\\`\n\n## AI Agent Action Steps\n\n1. **ANALYZE** the file structure:\n - Count classes (if >1, separate immediately)\n - Identify logical responsibilities within single class\n\n2. **IDENTIFY** \"child code\" to extract:\n - Validation logic -> ValidationService\n - Notification logic -> NotificationService\n - Data transformation -> TransformerService\n - External API calls -> ApiService\n - Business rules -> RulesEngine\n\n3. **CREATE** new service file(s):\n - Start with temporary name: \\`XXXX.ts\\` or \\`ChildService.ts\\`\n - Add \\`@provideSingleton()\\` decorator\n - Move child methods to new class\n\n4. **UPDATE** dependency injection:\n - Add to \\`TYPES\\` constants (if using symbol-based DI)\n - Inject new service into original class constructor\n - Replace direct method calls with \\`this.serviceName.method()\\`\n\n5. **RENAME** extracted file:\n - Read the extracted code to understand its purpose\n - Rename \\`XXXX.ts\\` to logical name (e.g., \\`UserValidationService.ts\\`)\n\n6. **VERIFY** file sizes:\n - Original file should now be <700 lines\n - Each extracted file should be <700 lines\n - If still too large, extract more services\n\n## Examples of Child Responsibilities to Extract\n\n| If File Contains | Extract To | Pattern |\n|-----------------|------------|---------|\n| Validation logic (200+ lines) | \\`XValidator.ts\\` or \\`XValidationService.ts\\` | Singleton service |\n| Notification logic (150+ lines) | \\`XNotifier.ts\\` or \\`XNotificationService.ts\\` | Singleton service |\n| Data transformation (200+ lines) | \\`XTransformer.ts\\` | Singleton service |\n| External API calls (200+ lines) | \\`XApiClient.ts\\` | Singleton service |\n| Complex business rules (300+ lines) | \\`XRulesEngine.ts\\` | Singleton service |\n| Database queries (200+ lines) | \\`XRepository.ts\\` | Singleton service |\n\n## WebPieces Dependency Injection Pattern\n\n\\`\\`\\`typescript\n// 1. Define service with @provideSingleton\nimport { provideSingleton } from '@webpieces/http-routing';\n\n@provideSingleton()\nexport class MyService {\n doSomething(): void { /* ... */ }\n}\n\n// 2. Inject into consumer\nimport { inject } from 'inversify';\nimport { TYPES } from './types';\n\n@provideSingleton()\n@Controller()\nexport class MyController {\n constructor(\n @inject(TYPES.MyService) private service: MyService\n ) {}\n}\n\\`\\`\\`\n\nRemember: Find the \"child code\" and pull it down into a new class. Once moved, the code's purpose becomes clear, making it easy to rename to a logical name.\n`;\n\n// Module-level flag to prevent redundant file creation\nlet fileDocCreated = false;\n\nfunction getWorkspaceRoot(context: Rule.RuleContext): string {\n const filename = context.filename || context.getFilename();\n let dir = path.dirname(filename);\n\n // Walk up directory tree to find workspace root\n while (dir !== path.dirname(dir)) {\n const pkgPath = path.join(dir, 'package.json');\n if (fs.existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));\n if (pkg.workspaces || pkg.name === 'webpieces-ts') {\n return dir;\n }\n } catch (err: unknown) {\n //const error = toError(err);\n void err; // Continue searching if JSON parse fails\n }\n }\n dir = path.dirname(dir);\n }\n return process.cwd(); // Fallback\n}\n\nfunction ensureDocFile(docPath: string, content: string): boolean {\n try {\n fs.mkdirSync(path.dirname(docPath), { recursive: true });\n fs.writeFileSync(docPath, content, 'utf-8');\n return true;\n } catch (err: unknown) {\n const error = toError(err);\n console.warn(`[webpieces] Could not create doc file: ${docPath}`, error);\n return false;\n }\n}\n\nfunction ensureFileDoc(context: Rule.RuleContext): void {\n if (fileDocCreated) return; // Performance: only create once per lint run\n\n const workspaceRoot = getWorkspaceRoot(context);\n const docPath = path.join(workspaceRoot, 'tmp', 'webpieces', 'webpieces.filesize.md');\n\n if (ensureDocFile(docPath, FILE_DOC_CONTENT)) {\n fileDocCreated = true;\n }\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'suggestion',\n docs: {\n description: 'Enforce maximum file length',\n category: 'Best Practices',\n recommended: false,\n url: 'https://github.com/deanhiller/webpieces-ts',\n },\n messages: {\n tooLong:\n 'AI Agent: READ tmp/webpieces/webpieces.filesize.md for fix instructions. File has {{actual}} lines (max: {{max}})',\n },\n fixable: undefined,\n schema: [\n {\n type: 'object',\n properties: {\n max: {\n type: 'integer',\n minimum: 1,\n },\n },\n additionalProperties: false,\n },\n ],\n },\n\n create(context: Rule.RuleContext): Rule.RuleListener {\n const options = context.options[0] as FileLinesOptions | undefined;\n const maxLines = options?.max ?? 700;\n\n return {\n Program(node: any): void {\n ensureFileDoc(context);\n\n const sourceCode = context.sourceCode || context.getSourceCode();\n const lines = sourceCode.lines;\n const lineCount = lines.length;\n\n if (lineCount > maxLines) {\n context.report({\n node,\n messageId: 'tooLong',\n data: {\n actual: String(lineCount),\n max: String(maxLines),\n },\n });\n }\n },\n };\n },\n};\n\nexport = rule;\n"]}