@webpieces/dev-config 0.2.52 → 0.2.56

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.
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Validate Versions Locked Executor
3
3
  *
4
- * Validates that package.json versions are LOCKED (exact versions, no semver ranges)
5
- * and checks npm ci compatibility for peer dependency conflicts.
4
+ * Validates that package.json versions are:
5
+ * 1. LOCKED (exact versions, no semver ranges like ^, ~, *)
6
+ * 2. CONSISTENT across all package.json files (no version conflicts)
6
7
  *
7
8
  * Why locked versions matter:
8
9
  * - Micro bugs ARE introduced via patch versions (1.4.5 → 1.4.6)
@@ -2,8 +2,9 @@
2
2
  /**
3
3
  * Validate Versions Locked Executor
4
4
  *
5
- * Validates that package.json versions are LOCKED (exact versions, no semver ranges)
6
- * and checks npm ci compatibility for peer dependency conflicts.
5
+ * Validates that package.json versions are:
6
+ * 1. LOCKED (exact versions, no semver ranges like ^, ~, *)
7
+ * 2. CONSISTENT across all package.json files (no version conflicts)
7
8
  *
8
9
  * Why locked versions matter:
9
10
  * - Micro bugs ARE introduced via patch versions (1.4.5 → 1.4.6)
@@ -16,7 +17,6 @@
16
17
  Object.defineProperty(exports, "__esModule", { value: true });
17
18
  exports.default = runExecutor;
18
19
  const tslib_1 = require("tslib");
19
- const child_process_1 = require("child_process");
20
20
  const fs = tslib_1.__importStar(require("fs"));
21
21
  const path = tslib_1.__importStar(require("path"));
22
22
  // webpieces-disable max-lines-new-methods -- Existing method from renamed validate-versions file
@@ -114,48 +114,87 @@ function validatePackageJson(filePath) {
114
114
  return [`Failed to parse ${filePath}: ${err.message}`];
115
115
  }
116
116
  }
117
- // webpieces-disable max-lines-new-methods -- Existing method from renamed validate-versions file
118
- // Check npm ci compatibility
119
- function checkNpmCiCompatibility(workspaceRoot) {
120
- // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
121
- try {
122
- // Run npm install --package-lock-only to check for peer dependency conflicts
123
- // This simulates what npm ci does without actually installing
124
- // Use --ignore-scripts to prevent infinite recursion (avoid triggering preinstall hook)
125
- console.log(' Running npm dependency resolution check (10s timeout)...');
126
- (0, child_process_1.execSync)('npm install --package-lock-only --ignore-scripts 2>&1', {
127
- cwd: workspaceRoot,
128
- stdio: 'pipe',
129
- encoding: 'utf-8',
130
- timeout: 10000 // 10 second timeout
131
- });
132
- return [];
133
- }
134
- catch (err) {
135
- //const error = toError(err);
136
- // Check if it's a timeout
137
- if (err.killed) {
138
- return ['npm dependency check timed out - this might indicate a hang or network issue'];
117
+ // webpieces-disable max-lines-new-methods -- Collecting dependencies from all package.json files
118
+ // Collect all dependency versions from all package.json files
119
+ function collectAllDependencies(workspaceRoot) {
120
+ const dependencyMap = new Map();
121
+ const packageFiles = findPackageJsonFiles(workspaceRoot);
122
+ for (const filePath of packageFiles) {
123
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
124
+ try {
125
+ const content = fs.readFileSync(filePath, 'utf-8');
126
+ const pkg = JSON.parse(content);
127
+ const relativePath = path.relative(workspaceRoot, filePath);
128
+ // Collect dependencies
129
+ if (pkg.dependencies) {
130
+ for (const [name, version] of Object.entries(pkg.dependencies)) {
131
+ // Skip internal workspace packages
132
+ if (name.startsWith('@webpieces/'))
133
+ continue;
134
+ const usage = {
135
+ version: version,
136
+ file: relativePath,
137
+ type: 'dependencies'
138
+ };
139
+ if (!dependencyMap.has(name)) {
140
+ dependencyMap.set(name, []);
141
+ }
142
+ dependencyMap.get(name).push(usage);
143
+ }
144
+ }
145
+ // Collect devDependencies
146
+ if (pkg.devDependencies) {
147
+ for (const [name, version] of Object.entries(pkg.devDependencies)) {
148
+ // Skip internal workspace packages
149
+ if (name.startsWith('@webpieces/'))
150
+ continue;
151
+ const usage = {
152
+ version: version,
153
+ file: relativePath,
154
+ type: 'devDependencies'
155
+ };
156
+ if (!dependencyMap.has(name)) {
157
+ dependencyMap.set(name, []);
158
+ }
159
+ dependencyMap.get(name).push(usage);
160
+ }
161
+ }
139
162
  }
140
- // Parse the error output to extract peer dependency conflicts
141
- const output = err.stdout || err.stderr || err.message;
142
- // Ignore errors about internal @webpieces/* packages with 0.0.0-dev versions
143
- // These don't exist on npm registry (only locally) but that's expected in development/CI
144
- if (output.includes('npm error') && output.includes('@webpieces/') && output.includes('0.0.0-dev')) {
145
- console.log(' ⏭️ Skipping npm ci check - internal @webpieces packages use local 0.0.0-dev versions');
146
- return [];
163
+ catch (err) {
164
+ // const error = toError(err);
165
+ // Intentionally skip files that can't be parsed - this is expected for some package.json files
147
166
  }
148
- // Check if it's a peer dependency error (npm error, not npm warn)
149
- if (output.includes('npm error') && (output.includes('ERESOLVE') || output.includes('peer'))) {
150
- return [output];
167
+ }
168
+ return dependencyMap;
169
+ }
170
+ // webpieces-disable max-lines-new-methods -- Simple iteration logic, splitting would reduce clarity
171
+ // Check for version conflicts across package.json files
172
+ function checkVersionConflicts(workspaceRoot) {
173
+ console.log('\n🔍 Checking for version conflicts across package.json files:');
174
+ const dependencyMap = collectAllDependencies(workspaceRoot);
175
+ const conflicts = [];
176
+ for (const [packageName, usages] of dependencyMap.entries()) {
177
+ // Get unique versions (ignoring workspace: and file: protocols)
178
+ const versions = new Set(usages
179
+ .map(u => u.version)
180
+ .filter(v => !v.startsWith('workspace:') && !v.startsWith('file:')));
181
+ if (versions.size > 1) {
182
+ const conflictDetails = usages
183
+ .filter(u => !u.version.startsWith('workspace:') && !u.version.startsWith('file:'))
184
+ .map(u => ` ${u.file} (${u.type}): ${u.version}`)
185
+ .join('\n');
186
+ conflicts.push(` ❌ ${packageName} has ${versions.size} different versions:\n${conflictDetails}`);
151
187
  }
152
- // If it's just warnings, not errors, we're OK
153
- if (output.includes('npm warn') && !output.includes('npm error')) {
154
- return [];
188
+ }
189
+ if (conflicts.length === 0) {
190
+ console.log(' ✅ No version conflicts found');
191
+ }
192
+ else {
193
+ for (const conflict of conflicts) {
194
+ console.log(conflict);
155
195
  }
156
- // Some other error - return it
157
- return [`npm dependency check failed: ${output}`];
158
196
  }
197
+ return conflicts;
159
198
  }
160
199
  /**
161
200
  * Prints the educational message explaining why semver ranges are forbidden.
@@ -234,49 +273,31 @@ function checkSemverRanges(workspaceRoot) {
234
273
  return { errors: semverErrors };
235
274
  }
236
275
  async function runExecutor(_options, context) {
237
- console.log('\n🔒 Validating Package Versions are LOCKED (no semver ranges)\n');
276
+ console.log('\n🔒 Validating Package Versions are LOCKED and CONSISTENT\n');
238
277
  const workspaceRoot = context.root;
239
- // Step 1: Check npm ci compatibility
240
- console.log('🔄 Checking npm ci compatibility (peer dependencies):');
241
- const npmCiErrors = checkNpmCiCompatibility(workspaceRoot);
242
- if (npmCiErrors.length > 0) {
243
- console.log(' ❌ npm ci compatibility check failed:');
244
- console.log(' This means "npm ci" will fail in CI even though "npm install" works locally.\n');
245
- for (const error of npmCiErrors) {
246
- const errorLines = error.split('\n').slice(0, 30);
247
- for (const line of errorLines) {
248
- console.log(` ${line}`);
249
- }
250
- if (error.split('\n').length > 30) {
251
- console.log(' ... (truncated)');
252
- }
253
- }
254
- console.log('');
255
- }
256
- else {
257
- console.log(' ✅ npm ci compatibility check passed');
258
- }
259
- // Step 2: Check for semver ranges (FAILS if any found)
278
+ // Step 1: Check for semver ranges (FAILS if any found)
260
279
  const { errors: semverErrors } = checkSemverRanges(workspaceRoot);
261
280
  const packageFiles = findPackageJsonFiles(workspaceRoot);
281
+ // Step 2: Check for version conflicts across package.json files
282
+ const versionConflicts = checkVersionConflicts(workspaceRoot);
262
283
  // Summary
263
284
  console.log(`\n📊 Summary:`);
264
- console.log(` npm ci compatibility: ${npmCiErrors.length === 0 ? '✅' : '❌'}`);
265
285
  console.log(` Files checked: ${packageFiles.length}`);
266
- console.log(` Unlocked versions: ${semverErrors}`);
267
- console.log(` Peer dep errors: ${npmCiErrors.length}`);
268
- // Fail on npm ci errors
269
- if (npmCiErrors.length > 0) {
270
- console.log('\n❌ VALIDATION FAILED!');
271
- console.log(' Fix peer dependency conflicts to avoid CI failures.\n');
272
- return { success: false };
273
- }
286
+ console.log(` Unlocked versions (semver ranges): ${semverErrors}`);
287
+ console.log(` Version conflicts: ${versionConflicts.length}`);
274
288
  // Fail on semver ranges with educational message
275
289
  if (semverErrors > 0) {
276
290
  printSemverRangeEducationalMessage(semverErrors);
277
291
  return { success: false };
278
292
  }
279
- console.log('\n✅ VALIDATION PASSED! All versions are locked.');
293
+ // Fail on version conflicts
294
+ if (versionConflicts.length > 0) {
295
+ console.log('\n❌ VALIDATION FAILED!');
296
+ console.log(' Fix version conflicts - all package.json files must use the same version for each dependency.');
297
+ console.log(' This prevents "works on my machine" bugs where different projects use different library versions.\n');
298
+ return { success: false };
299
+ }
300
+ console.log('\n✅ VALIDATION PASSED! All versions are locked and consistent.');
280
301
  return { success: true };
281
302
  }
282
303
  //# sourceMappingURL=executor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../packages/tooling/dev-config/executors/validate-versions-locked/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Versions Locked Executor\n *\n * Validates that package.json versions are LOCKED (exact versions, no semver ranges)\n * and checks npm ci compatibility for peer dependency conflicts.\n *\n * Why locked versions matter:\n * - Micro bugs ARE introduced via patch versions (1.4.5 → 1.4.6)\n * - git bisect fails when software changes OUTSIDE of git\n * - Library upgrades must be explicit via PR/commit, not implicit drift\n *\n * Usage:\n * nx run architecture:validate-versions-locked\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { execSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nexport interface ValidateVersionsLockedOptions {\n // No options needed\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\n// webpieces-disable max-lines-new-methods -- Existing method from renamed validate-versions file\n// Find all package.json files except node_modules, dist, .nx, .angular\nfunction findPackageJsonFiles(dir: string, basePath = ''): string[] {\n const files: string[] = [];\n const items = fs.readdirSync(dir);\n\n for (const item of items) {\n const fullPath = path.join(dir, item);\n const relativePath = path.join(basePath, item);\n\n // Skip these directories\n if (\n ['node_modules', 'dist', '.nx', '.angular', 'tmp', '.git'].includes(\n item,\n )\n ) {\n continue;\n }\n\n // Skip all hidden directories (starting with .)\n if (item.startsWith('.')) {\n continue;\n }\n\n const stat = fs.statSync(fullPath);\n if (stat.isDirectory()) {\n files.push(...findPackageJsonFiles(fullPath, relativePath));\n } else if (item === 'package.json') {\n files.push(fullPath);\n }\n }\n\n return files;\n}\n\n// Check if a version string uses semver ranges\nfunction hasSemverRange(version: string): boolean {\n // Allow workspace protocol\n if (version.startsWith('workspace:')) {\n return false;\n }\n\n // Allow file: protocol (for local packages)\n if (version.startsWith('file:')) {\n return false;\n }\n\n // Check for common semver range patterns\n const semverPatterns = [\n /^\\^/, // ^1.2.3\n /^~/, // ~1.2.3\n /^\\+/, // +1.2.3\n /^\\*/, // *\n /^>/, // >1.2.3\n /^</, // <1.2.3\n /^>=/, // >=1.2.3\n /^<=/, // <=1.2.3\n /\\|\\|/, // 1.2.3 || 2.x\n / - /, // 1.2.3 - 2.3.4\n /^\\d+\\.x/, // 1.x, 1.2.x\n /^latest$/, // latest\n /^next$/, // next\n ];\n\n return semverPatterns.some((pattern) => pattern.test(version));\n}\n\n// webpieces-disable max-lines-new-methods -- Existing method from renamed validate-versions file\n// Validate a single package.json file for semver ranges\nfunction validatePackageJson(filePath: string): string[] {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const content = fs.readFileSync(filePath, 'utf-8');\n const pkg = JSON.parse(content);\n const errors: string[] = [];\n\n // Check dependencies\n if (pkg.dependencies) {\n for (const [name, version] of Object.entries(pkg.dependencies)) {\n // Skip internal workspace packages\n if (name.startsWith('@webpieces/')) {\n continue;\n }\n\n if (hasSemverRange(version as string)) {\n errors.push(\n `dependencies.${name}: \"${version}\" uses semver range (must be locked to exact version)`,\n );\n }\n }\n }\n\n // Check devDependencies\n if (pkg.devDependencies) {\n for (const [name, version] of Object.entries(pkg.devDependencies)) {\n // Skip internal workspace packages\n if (name.startsWith('@webpieces/')) {\n continue;\n }\n\n if (hasSemverRange(version as string)) {\n errors.push(\n `devDependencies.${name}: \"${version}\" uses semver range (must be locked to exact version)`,\n );\n }\n }\n }\n\n // Check peerDependencies (these can have ranges for compatibility)\n // We don't validate peerDependencies for semver ranges since they're meant to be flexible\n\n return errors;\n } catch (err: any) {\n //const error = toError(err);\n return [`Failed to parse ${filePath}: ${err.message}`];\n }\n}\n\n// webpieces-disable max-lines-new-methods -- Existing method from renamed validate-versions file\n// Check npm ci compatibility\nfunction checkNpmCiCompatibility(workspaceRoot: string): string[] {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Run npm install --package-lock-only to check for peer dependency conflicts\n // This simulates what npm ci does without actually installing\n // Use --ignore-scripts to prevent infinite recursion (avoid triggering preinstall hook)\n console.log(' Running npm dependency resolution check (10s timeout)...');\n execSync('npm install --package-lock-only --ignore-scripts 2>&1', {\n cwd: workspaceRoot,\n stdio: 'pipe',\n encoding: 'utf-8',\n timeout: 10000 // 10 second timeout\n });\n return [];\n } catch (err: any) {\n //const error = toError(err);\n // Check if it's a timeout\n if (err.killed) {\n return ['npm dependency check timed out - this might indicate a hang or network issue'];\n }\n\n // Parse the error output to extract peer dependency conflicts\n const output = err.stdout || err.stderr || err.message;\n\n // Ignore errors about internal @webpieces/* packages with 0.0.0-dev versions\n // These don't exist on npm registry (only locally) but that's expected in development/CI\n if (output.includes('npm error') && output.includes('@webpieces/') && output.includes('0.0.0-dev')) {\n console.log(' ⏭️ Skipping npm ci check - internal @webpieces packages use local 0.0.0-dev versions');\n return [];\n }\n\n // Check if it's a peer dependency error (npm error, not npm warn)\n if (output.includes('npm error') && (output.includes('ERESOLVE') || output.includes('peer'))) {\n return [output];\n }\n\n // If it's just warnings, not errors, we're OK\n if (output.includes('npm warn') && !output.includes('npm error')) {\n return [];\n }\n\n // Some other error - return it\n return [`npm dependency check failed: ${output}`];\n }\n}\n\n/**\n * Prints the educational message explaining why semver ranges are forbidden.\n * This helps developers understand the rationale behind locked versions.\n */\n// webpieces-disable max-lines-new-methods -- Educational message template, splitting reduces clarity\nfunction printSemverRangeEducationalMessage(semverErrors: number): void {\n console.log(`\n❌ SEMVER RANGES DETECTED - BUILD FAILED\n\nFound ${semverErrors} package(s) using semver ranges (^, ~, *, etc.) instead of locked versions.\n\nWHY THIS IS A HARD FAILURE:\n═══════════════════════════════════════════════════════════════════════════════\n\n1. MICRO BUGS ARE REAL\n Thinking that patch versions (1.4.5 → 1.4.6) don't introduce bugs is wrong.\n They do. Sometimes what looks like an \"easy fix\" breaks things in subtle ways.\n\n2. GIT BISECT BECOMES USELESS\n When you run \"git bisect\" to find when a bug was introduced, it fails if\n software changed OUTSIDE of git. You checkout an old commit, but node_modules\n has different versions than when that commit was made. The bug persists even\n in \"known good\" commits because the library versions drifted.\n\n3. THE \"MAGIC BUG\" PROBLEM\n You checkout code from 6 months ago to debug an issue. The bug is still there!\n But it wasn't there 6 months ago... The culprit: a minor version upgrade that\n happened silently without any PR or git commit. Impossible to track down.\n\n4. CHANGES OUTSIDE GIT = BAD\n Every change to your software should be tracked in version control.\n Implicit library upgrades via semver ranges violate this principle.\n\nTHE SOLUTION:\n═══════════════════════════════════════════════════════════════════════════════\n\nUse LOCKED (exact) versions for all dependencies:\n ❌ \"lodash\": \"^4.17.21\" <- BAD: allows 4.17.22, 4.18.0, etc.\n ❌ \"lodash\": \"~4.17.21\" <- BAD: allows 4.17.22, 4.17.23, etc.\n ✅ \"lodash\": \"4.17.21\" <- GOOD: locked to this exact version\n\nTo upgrade libraries, use an explicit process:\n 1. Run: npm update <package-name>\n 2. Test thoroughly\n 3. Commit the package.json AND package-lock.json changes\n 4. Create a PR so the upgrade is reviewed and tracked in git history\n\nThis way, every library change is:\n • Intentional (not accidental)\n • Reviewed (via PR)\n • Tracked (in git history)\n • Bisectable (git bisect works correctly)\n\n`);\n}\n\n// Check semver ranges in all package.json files - FAILS if any found\nfunction checkSemverRanges(workspaceRoot: string): { errors: number } {\n console.log('\\n📋 Checking for unlocked versions (semver ranges):');\n const packageFiles = findPackageJsonFiles(workspaceRoot);\n let semverErrors = 0;\n\n for (const filePath of packageFiles) {\n const relativePath = path.relative(workspaceRoot, filePath);\n const errors = validatePackageJson(filePath);\n\n if (errors.length > 0) {\n console.log(` ❌ ${relativePath}:`);\n for (const error of errors) {\n console.log(` ${error}`);\n }\n semverErrors += errors.length;\n } else {\n console.log(` ✅ ${relativePath}`);\n }\n }\n\n return { errors: semverErrors };\n}\n\nexport default async function runExecutor(\n _options: ValidateVersionsLockedOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n console.log('\\n🔒 Validating Package Versions are LOCKED (no semver ranges)\\n');\n\n const workspaceRoot = context.root;\n\n // Step 1: Check npm ci compatibility\n console.log('🔄 Checking npm ci compatibility (peer dependencies):');\n const npmCiErrors = checkNpmCiCompatibility(workspaceRoot);\n if (npmCiErrors.length > 0) {\n console.log(' ❌ npm ci compatibility check failed:');\n console.log(' This means \"npm ci\" will fail in CI even though \"npm install\" works locally.\\n');\n for (const error of npmCiErrors) {\n const errorLines = error.split('\\n').slice(0, 30);\n for (const line of errorLines) {\n console.log(` ${line}`);\n }\n if (error.split('\\n').length > 30) {\n console.log(' ... (truncated)');\n }\n }\n console.log('');\n } else {\n console.log(' ✅ npm ci compatibility check passed');\n }\n\n // Step 2: Check for semver ranges (FAILS if any found)\n const { errors: semverErrors } = checkSemverRanges(workspaceRoot);\n const packageFiles = findPackageJsonFiles(workspaceRoot);\n\n // Summary\n console.log(`\\n📊 Summary:`);\n console.log(` npm ci compatibility: ${npmCiErrors.length === 0 ? '✅' : '❌'}`);\n console.log(` Files checked: ${packageFiles.length}`);\n console.log(` Unlocked versions: ${semverErrors}`);\n console.log(` Peer dep errors: ${npmCiErrors.length}`);\n\n // Fail on npm ci errors\n if (npmCiErrors.length > 0) {\n console.log('\\n❌ VALIDATION FAILED!');\n console.log(' Fix peer dependency conflicts to avoid CI failures.\\n');\n return { success: false };\n }\n\n // Fail on semver ranges with educational message\n if (semverErrors > 0) {\n printSemverRangeEducationalMessage(semverErrors);\n return { success: false };\n }\n\n console.log('\\n✅ VALIDATION PASSED! All versions are locked.');\n return { success: true };\n}\n"]}
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../packages/tooling/dev-config/executors/validate-versions-locked/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Versions Locked Executor\n *\n * Validates that package.json versions are:\n * 1. LOCKED (exact versions, no semver ranges like ^, ~, *)\n * 2. CONSISTENT across all package.json files (no version conflicts)\n *\n * Why locked versions matter:\n * - Micro bugs ARE introduced via patch versions (1.4.5 → 1.4.6)\n * - git bisect fails when software changes OUTSIDE of git\n * - Library upgrades must be explicit via PR/commit, not implicit drift\n *\n * Usage:\n * nx run architecture:validate-versions-locked\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nexport interface ValidateVersionsLockedOptions {\n // No options needed\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\n// webpieces-disable max-lines-new-methods -- Existing method from renamed validate-versions file\n// Find all package.json files except node_modules, dist, .nx, .angular\nfunction findPackageJsonFiles(dir: string, basePath = ''): string[] {\n const files: string[] = [];\n const items = fs.readdirSync(dir);\n\n for (const item of items) {\n const fullPath = path.join(dir, item);\n const relativePath = path.join(basePath, item);\n\n // Skip these directories\n if (\n ['node_modules', 'dist', '.nx', '.angular', 'tmp', '.git'].includes(\n item,\n )\n ) {\n continue;\n }\n\n // Skip all hidden directories (starting with .)\n if (item.startsWith('.')) {\n continue;\n }\n\n const stat = fs.statSync(fullPath);\n if (stat.isDirectory()) {\n files.push(...findPackageJsonFiles(fullPath, relativePath));\n } else if (item === 'package.json') {\n files.push(fullPath);\n }\n }\n\n return files;\n}\n\n// Check if a version string uses semver ranges\nfunction hasSemverRange(version: string): boolean {\n // Allow workspace protocol\n if (version.startsWith('workspace:')) {\n return false;\n }\n\n // Allow file: protocol (for local packages)\n if (version.startsWith('file:')) {\n return false;\n }\n\n // Check for common semver range patterns\n const semverPatterns = [\n /^\\^/, // ^1.2.3\n /^~/, // ~1.2.3\n /^\\+/, // +1.2.3\n /^\\*/, // *\n /^>/, // >1.2.3\n /^</, // <1.2.3\n /^>=/, // >=1.2.3\n /^<=/, // <=1.2.3\n /\\|\\|/, // 1.2.3 || 2.x\n / - /, // 1.2.3 - 2.3.4\n /^\\d+\\.x/, // 1.x, 1.2.x\n /^latest$/, // latest\n /^next$/, // next\n ];\n\n return semverPatterns.some((pattern) => pattern.test(version));\n}\n\n// webpieces-disable max-lines-new-methods -- Existing method from renamed validate-versions file\n// Validate a single package.json file for semver ranges\nfunction validatePackageJson(filePath: string): string[] {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const content = fs.readFileSync(filePath, 'utf-8');\n const pkg = JSON.parse(content);\n const errors: string[] = [];\n\n // Check dependencies\n if (pkg.dependencies) {\n for (const [name, version] of Object.entries(pkg.dependencies)) {\n // Skip internal workspace packages\n if (name.startsWith('@webpieces/')) {\n continue;\n }\n\n if (hasSemverRange(version as string)) {\n errors.push(\n `dependencies.${name}: \"${version}\" uses semver range (must be locked to exact version)`,\n );\n }\n }\n }\n\n // Check devDependencies\n if (pkg.devDependencies) {\n for (const [name, version] of Object.entries(pkg.devDependencies)) {\n // Skip internal workspace packages\n if (name.startsWith('@webpieces/')) {\n continue;\n }\n\n if (hasSemverRange(version as string)) {\n errors.push(\n `devDependencies.${name}: \"${version}\" uses semver range (must be locked to exact version)`,\n );\n }\n }\n }\n\n // Check peerDependencies (these can have ranges for compatibility)\n // We don't validate peerDependencies for semver ranges since they're meant to be flexible\n\n return errors;\n } catch (err: any) {\n //const error = toError(err);\n return [`Failed to parse ${filePath}: ${err.message}`];\n }\n}\n\n// Track all dependency versions across the monorepo\ninterface DependencyUsage {\n version: string;\n file: string;\n type: 'dependencies' | 'devDependencies';\n}\n\n// webpieces-disable max-lines-new-methods -- Collecting dependencies from all package.json files\n// Collect all dependency versions from all package.json files\nfunction collectAllDependencies(workspaceRoot: string): Map<string, DependencyUsage[]> {\n const dependencyMap = new Map<string, DependencyUsage[]>();\n const packageFiles = findPackageJsonFiles(workspaceRoot);\n\n for (const filePath of packageFiles) {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const content = fs.readFileSync(filePath, 'utf-8');\n const pkg = JSON.parse(content);\n const relativePath = path.relative(workspaceRoot, filePath);\n\n // Collect dependencies\n if (pkg.dependencies) {\n for (const [name, version] of Object.entries(pkg.dependencies)) {\n // Skip internal workspace packages\n if (name.startsWith('@webpieces/')) continue;\n\n const usage: DependencyUsage = {\n version: version as string,\n file: relativePath,\n type: 'dependencies'\n };\n\n if (!dependencyMap.has(name)) {\n dependencyMap.set(name, []);\n }\n dependencyMap.get(name)!.push(usage);\n }\n }\n\n // Collect devDependencies\n if (pkg.devDependencies) {\n for (const [name, version] of Object.entries(pkg.devDependencies)) {\n // Skip internal workspace packages\n if (name.startsWith('@webpieces/')) continue;\n\n const usage: DependencyUsage = {\n version: version as string,\n file: relativePath,\n type: 'devDependencies'\n };\n\n if (!dependencyMap.has(name)) {\n dependencyMap.set(name, []);\n }\n dependencyMap.get(name)!.push(usage);\n }\n }\n } catch (err: any) {\n // const error = toError(err);\n // Intentionally skip files that can't be parsed - this is expected for some package.json files\n }\n }\n\n return dependencyMap;\n}\n\n// webpieces-disable max-lines-new-methods -- Simple iteration logic, splitting would reduce clarity\n// Check for version conflicts across package.json files\nfunction checkVersionConflicts(workspaceRoot: string): string[] {\n console.log('\\n🔍 Checking for version conflicts across package.json files:');\n\n const dependencyMap = collectAllDependencies(workspaceRoot);\n const conflicts: string[] = [];\n\n for (const [packageName, usages] of dependencyMap.entries()) {\n // Get unique versions (ignoring workspace: and file: protocols)\n const versions = new Set(\n usages\n .map(u => u.version)\n .filter(v => !v.startsWith('workspace:') && !v.startsWith('file:'))\n );\n\n if (versions.size > 1) {\n const conflictDetails = usages\n .filter(u => !u.version.startsWith('workspace:') && !u.version.startsWith('file:'))\n .map(u => ` ${u.file} (${u.type}): ${u.version}`)\n .join('\\n');\n\n conflicts.push(` ❌ ${packageName} has ${versions.size} different versions:\\n${conflictDetails}`);\n }\n }\n\n if (conflicts.length === 0) {\n console.log(' ✅ No version conflicts found');\n } else {\n for (const conflict of conflicts) {\n console.log(conflict);\n }\n }\n\n return conflicts;\n}\n\n/**\n * Prints the educational message explaining why semver ranges are forbidden.\n * This helps developers understand the rationale behind locked versions.\n */\n// webpieces-disable max-lines-new-methods -- Educational message template, splitting reduces clarity\nfunction printSemverRangeEducationalMessage(semverErrors: number): void {\n console.log(`\n❌ SEMVER RANGES DETECTED - BUILD FAILED\n\nFound ${semverErrors} package(s) using semver ranges (^, ~, *, etc.) instead of locked versions.\n\nWHY THIS IS A HARD FAILURE:\n═══════════════════════════════════════════════════════════════════════════════\n\n1. MICRO BUGS ARE REAL\n Thinking that patch versions (1.4.5 → 1.4.6) don't introduce bugs is wrong.\n They do. Sometimes what looks like an \"easy fix\" breaks things in subtle ways.\n\n2. GIT BISECT BECOMES USELESS\n When you run \"git bisect\" to find when a bug was introduced, it fails if\n software changed OUTSIDE of git. You checkout an old commit, but node_modules\n has different versions than when that commit was made. The bug persists even\n in \"known good\" commits because the library versions drifted.\n\n3. THE \"MAGIC BUG\" PROBLEM\n You checkout code from 6 months ago to debug an issue. The bug is still there!\n But it wasn't there 6 months ago... The culprit: a minor version upgrade that\n happened silently without any PR or git commit. Impossible to track down.\n\n4. CHANGES OUTSIDE GIT = BAD\n Every change to your software should be tracked in version control.\n Implicit library upgrades via semver ranges violate this principle.\n\nTHE SOLUTION:\n═══════════════════════════════════════════════════════════════════════════════\n\nUse LOCKED (exact) versions for all dependencies:\n ❌ \"lodash\": \"^4.17.21\" <- BAD: allows 4.17.22, 4.18.0, etc.\n ❌ \"lodash\": \"~4.17.21\" <- BAD: allows 4.17.22, 4.17.23, etc.\n ✅ \"lodash\": \"4.17.21\" <- GOOD: locked to this exact version\n\nTo upgrade libraries, use an explicit process:\n 1. Run: npm update <package-name>\n 2. Test thoroughly\n 3. Commit the package.json AND package-lock.json changes\n 4. Create a PR so the upgrade is reviewed and tracked in git history\n\nThis way, every library change is:\n • Intentional (not accidental)\n • Reviewed (via PR)\n • Tracked (in git history)\n • Bisectable (git bisect works correctly)\n\n`);\n}\n\n// Check semver ranges in all package.json files - FAILS if any found\nfunction checkSemverRanges(workspaceRoot: string): { errors: number } {\n console.log('\\n📋 Checking for unlocked versions (semver ranges):');\n const packageFiles = findPackageJsonFiles(workspaceRoot);\n let semverErrors = 0;\n\n for (const filePath of packageFiles) {\n const relativePath = path.relative(workspaceRoot, filePath);\n const errors = validatePackageJson(filePath);\n\n if (errors.length > 0) {\n console.log(` ❌ ${relativePath}:`);\n for (const error of errors) {\n console.log(` ${error}`);\n }\n semverErrors += errors.length;\n } else {\n console.log(` ✅ ${relativePath}`);\n }\n }\n\n return { errors: semverErrors };\n}\n\nexport default async function runExecutor(\n _options: ValidateVersionsLockedOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n console.log('\\n🔒 Validating Package Versions are LOCKED and CONSISTENT\\n');\n\n const workspaceRoot = context.root;\n\n // Step 1: Check for semver ranges (FAILS if any found)\n const { errors: semverErrors } = checkSemverRanges(workspaceRoot);\n const packageFiles = findPackageJsonFiles(workspaceRoot);\n\n // Step 2: Check for version conflicts across package.json files\n const versionConflicts = checkVersionConflicts(workspaceRoot);\n\n // Summary\n console.log(`\\n📊 Summary:`);\n console.log(` Files checked: ${packageFiles.length}`);\n console.log(` Unlocked versions (semver ranges): ${semverErrors}`);\n console.log(` Version conflicts: ${versionConflicts.length}`);\n\n // Fail on semver ranges with educational message\n if (semverErrors > 0) {\n printSemverRangeEducationalMessage(semverErrors);\n return { success: false };\n }\n\n // Fail on version conflicts\n if (versionConflicts.length > 0) {\n console.log('\\n❌ VALIDATION FAILED!');\n console.log(' Fix version conflicts - all package.json files must use the same version for each dependency.');\n console.log(' This prevents \"works on my machine\" bugs where different projects use different library versions.\\n');\n return { success: false };\n }\n\n console.log('\\n✅ VALIDATION PASSED! All versions are locked and consistent.');\n return { success: true };\n}\n"]}
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Validate Versions Locked Executor
3
3
  *
4
- * Validates that package.json versions are LOCKED (exact versions, no semver ranges)
5
- * and checks npm ci compatibility for peer dependency conflicts.
4
+ * Validates that package.json versions are:
5
+ * 1. LOCKED (exact versions, no semver ranges like ^, ~, *)
6
+ * 2. CONSISTENT across all package.json files (no version conflicts)
6
7
  *
7
8
  * Why locked versions matter:
8
9
  * - Micro bugs ARE introduced via patch versions (1.4.5 → 1.4.6)
@@ -14,7 +15,6 @@
14
15
  */
15
16
 
16
17
  import type { ExecutorContext } from '@nx/devkit';
17
- import { execSync } from 'child_process';
18
18
  import * as fs from 'fs';
19
19
  import * as path from 'path';
20
20
 
@@ -144,52 +144,107 @@ function validatePackageJson(filePath: string): string[] {
144
144
  }
145
145
  }
146
146
 
147
- // webpieces-disable max-lines-new-methods -- Existing method from renamed validate-versions file
148
- // Check npm ci compatibility
149
- function checkNpmCiCompatibility(workspaceRoot: string): string[] {
150
- // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
151
- try {
152
- // Run npm install --package-lock-only to check for peer dependency conflicts
153
- // This simulates what npm ci does without actually installing
154
- // Use --ignore-scripts to prevent infinite recursion (avoid triggering preinstall hook)
155
- console.log(' Running npm dependency resolution check (10s timeout)...');
156
- execSync('npm install --package-lock-only --ignore-scripts 2>&1', {
157
- cwd: workspaceRoot,
158
- stdio: 'pipe',
159
- encoding: 'utf-8',
160
- timeout: 10000 // 10 second timeout
161
- });
162
- return [];
163
- } catch (err: any) {
164
- //const error = toError(err);
165
- // Check if it's a timeout
166
- if (err.killed) {
167
- return ['npm dependency check timed out - this might indicate a hang or network issue'];
168
- }
147
+ // Track all dependency versions across the monorepo
148
+ interface DependencyUsage {
149
+ version: string;
150
+ file: string;
151
+ type: 'dependencies' | 'devDependencies';
152
+ }
169
153
 
170
- // Parse the error output to extract peer dependency conflicts
171
- const output = err.stdout || err.stderr || err.message;
154
+ // webpieces-disable max-lines-new-methods -- Collecting dependencies from all package.json files
155
+ // Collect all dependency versions from all package.json files
156
+ function collectAllDependencies(workspaceRoot: string): Map<string, DependencyUsage[]> {
157
+ const dependencyMap = new Map<string, DependencyUsage[]>();
158
+ const packageFiles = findPackageJsonFiles(workspaceRoot);
172
159
 
173
- // Ignore errors about internal @webpieces/* packages with 0.0.0-dev versions
174
- // These don't exist on npm registry (only locally) but that's expected in development/CI
175
- if (output.includes('npm error') && output.includes('@webpieces/') && output.includes('0.0.0-dev')) {
176
- console.log(' ⏭️ Skipping npm ci check - internal @webpieces packages use local 0.0.0-dev versions');
177
- return [];
178
- }
160
+ for (const filePath of packageFiles) {
161
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
162
+ try {
163
+ const content = fs.readFileSync(filePath, 'utf-8');
164
+ const pkg = JSON.parse(content);
165
+ const relativePath = path.relative(workspaceRoot, filePath);
166
+
167
+ // Collect dependencies
168
+ if (pkg.dependencies) {
169
+ for (const [name, version] of Object.entries(pkg.dependencies)) {
170
+ // Skip internal workspace packages
171
+ if (name.startsWith('@webpieces/')) continue;
172
+
173
+ const usage: DependencyUsage = {
174
+ version: version as string,
175
+ file: relativePath,
176
+ type: 'dependencies'
177
+ };
178
+
179
+ if (!dependencyMap.has(name)) {
180
+ dependencyMap.set(name, []);
181
+ }
182
+ dependencyMap.get(name)!.push(usage);
183
+ }
184
+ }
179
185
 
180
- // Check if it's a peer dependency error (npm error, not npm warn)
181
- if (output.includes('npm error') && (output.includes('ERESOLVE') || output.includes('peer'))) {
182
- return [output];
186
+ // Collect devDependencies
187
+ if (pkg.devDependencies) {
188
+ for (const [name, version] of Object.entries(pkg.devDependencies)) {
189
+ // Skip internal workspace packages
190
+ if (name.startsWith('@webpieces/')) continue;
191
+
192
+ const usage: DependencyUsage = {
193
+ version: version as string,
194
+ file: relativePath,
195
+ type: 'devDependencies'
196
+ };
197
+
198
+ if (!dependencyMap.has(name)) {
199
+ dependencyMap.set(name, []);
200
+ }
201
+ dependencyMap.get(name)!.push(usage);
202
+ }
203
+ }
204
+ } catch (err: any) {
205
+ // const error = toError(err);
206
+ // Intentionally skip files that can't be parsed - this is expected for some package.json files
183
207
  }
208
+ }
209
+
210
+ return dependencyMap;
211
+ }
184
212
 
185
- // If it's just warnings, not errors, we're OK
186
- if (output.includes('npm warn') && !output.includes('npm error')) {
187
- return [];
213
+ // webpieces-disable max-lines-new-methods -- Simple iteration logic, splitting would reduce clarity
214
+ // Check for version conflicts across package.json files
215
+ function checkVersionConflicts(workspaceRoot: string): string[] {
216
+ console.log('\n🔍 Checking for version conflicts across package.json files:');
217
+
218
+ const dependencyMap = collectAllDependencies(workspaceRoot);
219
+ const conflicts: string[] = [];
220
+
221
+ for (const [packageName, usages] of dependencyMap.entries()) {
222
+ // Get unique versions (ignoring workspace: and file: protocols)
223
+ const versions = new Set(
224
+ usages
225
+ .map(u => u.version)
226
+ .filter(v => !v.startsWith('workspace:') && !v.startsWith('file:'))
227
+ );
228
+
229
+ if (versions.size > 1) {
230
+ const conflictDetails = usages
231
+ .filter(u => !u.version.startsWith('workspace:') && !u.version.startsWith('file:'))
232
+ .map(u => ` ${u.file} (${u.type}): ${u.version}`)
233
+ .join('\n');
234
+
235
+ conflicts.push(` ❌ ${packageName} has ${versions.size} different versions:\n${conflictDetails}`);
188
236
  }
237
+ }
189
238
 
190
- // Some other error - return it
191
- return [`npm dependency check failed: ${output}`];
239
+ if (conflicts.length === 0) {
240
+ console.log(' ✅ No version conflicts found');
241
+ } else {
242
+ for (const conflict of conflicts) {
243
+ console.log(conflict);
244
+ }
192
245
  }
246
+
247
+ return conflicts;
193
248
  }
194
249
 
195
250
  /**
@@ -276,47 +331,22 @@ export default async function runExecutor(
276
331
  _options: ValidateVersionsLockedOptions,
277
332
  context: ExecutorContext
278
333
  ): Promise<ExecutorResult> {
279
- console.log('\n🔒 Validating Package Versions are LOCKED (no semver ranges)\n');
334
+ console.log('\n🔒 Validating Package Versions are LOCKED and CONSISTENT\n');
280
335
 
281
336
  const workspaceRoot = context.root;
282
337
 
283
- // Step 1: Check npm ci compatibility
284
- console.log('🔄 Checking npm ci compatibility (peer dependencies):');
285
- const npmCiErrors = checkNpmCiCompatibility(workspaceRoot);
286
- if (npmCiErrors.length > 0) {
287
- console.log(' ❌ npm ci compatibility check failed:');
288
- console.log(' This means "npm ci" will fail in CI even though "npm install" works locally.\n');
289
- for (const error of npmCiErrors) {
290
- const errorLines = error.split('\n').slice(0, 30);
291
- for (const line of errorLines) {
292
- console.log(` ${line}`);
293
- }
294
- if (error.split('\n').length > 30) {
295
- console.log(' ... (truncated)');
296
- }
297
- }
298
- console.log('');
299
- } else {
300
- console.log(' ✅ npm ci compatibility check passed');
301
- }
302
-
303
- // Step 2: Check for semver ranges (FAILS if any found)
338
+ // Step 1: Check for semver ranges (FAILS if any found)
304
339
  const { errors: semverErrors } = checkSemverRanges(workspaceRoot);
305
340
  const packageFiles = findPackageJsonFiles(workspaceRoot);
306
341
 
342
+ // Step 2: Check for version conflicts across package.json files
343
+ const versionConflicts = checkVersionConflicts(workspaceRoot);
344
+
307
345
  // Summary
308
346
  console.log(`\n📊 Summary:`);
309
- console.log(` npm ci compatibility: ${npmCiErrors.length === 0 ? '✅' : '❌'}`);
310
347
  console.log(` Files checked: ${packageFiles.length}`);
311
- console.log(` Unlocked versions: ${semverErrors}`);
312
- console.log(` Peer dep errors: ${npmCiErrors.length}`);
313
-
314
- // Fail on npm ci errors
315
- if (npmCiErrors.length > 0) {
316
- console.log('\n❌ VALIDATION FAILED!');
317
- console.log(' Fix peer dependency conflicts to avoid CI failures.\n');
318
- return { success: false };
319
- }
348
+ console.log(` Unlocked versions (semver ranges): ${semverErrors}`);
349
+ console.log(` Version conflicts: ${versionConflicts.length}`);
320
350
 
321
351
  // Fail on semver ranges with educational message
322
352
  if (semverErrors > 0) {
@@ -324,6 +354,14 @@ export default async function runExecutor(
324
354
  return { success: false };
325
355
  }
326
356
 
327
- console.log('\n✅ VALIDATION PASSED! All versions are locked.');
357
+ // Fail on version conflicts
358
+ if (versionConflicts.length > 0) {
359
+ console.log('\n❌ VALIDATION FAILED!');
360
+ console.log(' Fix version conflicts - all package.json files must use the same version for each dependency.');
361
+ console.log(' This prevents "works on my machine" bugs where different projects use different library versions.\n');
362
+ return { success: false };
363
+ }
364
+
365
+ console.log('\n✅ VALIDATION PASSED! All versions are locked and consistent.');
328
366
  return { success: true };
329
367
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/schema",
3
3
  "title": "Validate Versions Locked Executor",
4
- "description": "Validates package.json versions are locked (no semver ranges) and npm ci compatible",
4
+ "description": "Validates package.json versions are locked (no semver ranges) and consistent across all projects",
5
5
  "type": "object",
6
6
  "properties": {},
7
7
  "required": []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/dev-config",
3
- "version": "0.2.52",
3
+ "version": "0.2.56",
4
4
  "description": "Development configuration, scripts, and patterns for WebPieces projects",
5
5
  "type": "commonjs",
6
6
  "bin": {
package/plugin.js CHANGED
@@ -116,9 +116,7 @@ function addPerProjectTargets(results, projectFiles, opts, context) {
116
116
  targets[targetName] = createCircularDepsTarget(projectRoot, targetName);
117
117
  }
118
118
  }
119
- // Add ci target - composite target that runs lint, build, test, and typecheck
120
- // The 'typecheck' target from @nx/js/typescript plugin already type-checks ALL files
121
- // including *.spec.ts and *.test.ts via tsconfig.json references
119
+ // Add ci target - composite target that runs lint, build, and test
122
120
  targets['ci'] = createCiTarget();
123
121
  if (Object.keys(targets).length === 0)
124
122
  continue;
@@ -354,7 +352,7 @@ function createValidateVersionsLockedTarget() {
354
352
  inputs: ['default'],
355
353
  metadata: {
356
354
  technologies: ['nx'],
357
- description: 'Validate package.json versions are locked (no semver ranges) and npm ci compatible',
355
+ description: 'Validate package.json versions are locked (no semver ranges) and consistent across projects',
358
356
  },
359
357
  };
360
358
  }
@@ -371,20 +369,19 @@ function createValidateCompleteTarget(validationTargets) {
371
369
  }
372
370
  /**
373
371
  * Create per-project ci target - Gradle-style composite target
374
- * Runs lint, build, test, and typecheck in parallel
372
+ * Runs lint, build, and test in parallel
375
373
  * (with test depending on build via targetDefaults)
376
374
  *
377
- * The 'typecheck' target from @nx/js/typescript plugin type-checks ALL *.ts files
378
- * including test files (*.spec.ts, *.test.ts) via tsconfig.json references.
375
+ * NOTE: Type checking is done by the build target (@nx/js:tsc) during compilation.
379
376
  */
380
377
  function createCiTarget() {
381
378
  return {
382
379
  executor: 'nx:noop',
383
380
  cache: true,
384
- dependsOn: ['lint', 'build', 'test', 'typecheck'],
381
+ dependsOn: ['lint', 'build', 'test'],
385
382
  metadata: {
386
383
  technologies: ['nx'],
387
- description: 'Run all CI checks: lint, build, test, and typecheck (Gradle-style composite target)',
384
+ description: 'Run all CI checks: lint, build, and test (Gradle-style composite target)',
388
385
  },
389
386
  };
390
387
  }
@@ -198,21 +198,14 @@ function createArchitectureDirectory(tree) {
198
198
  function addNpmScripts(tree) {
199
199
  (0, devkit_1.updateJson)(tree, 'package.json', (pkgJson) => {
200
200
  pkgJson.scripts = pkgJson.scripts ?? {};
201
- // Add architecture validation scripts
201
+ // Add architecture scripts
202
202
  pkgJson.scripts['arch:generate'] = 'nx run architecture:generate';
203
203
  pkgJson.scripts['arch:visualize'] = 'nx run architecture:visualize';
204
- pkgJson.scripts['arch:validate'] = 'nx run architecture:validate-no-architecture-cycles && nx run architecture:validate-no-skiplevel-deps';
205
- pkgJson.scripts['arch:validate-all'] = 'nx run architecture:validate-no-architecture-cycles && nx run architecture:validate-no-skiplevel-deps && nx run architecture:validate-architecture-unchanged';
206
- // Add file import cycle checking scripts
207
- pkgJson.scripts['arch:check-circular'] = 'nx run-many --target=validate-no-file-import-cycles --all';
208
- pkgJson.scripts['arch:check-circular-affected'] = 'nx affected --target=validate-no-file-import-cycles';
209
- // Complete validation including circular deps
210
- pkgJson.scripts['arch:validate-complete'] = 'npm run arch:validate-all && npm run arch:check-circular';
211
204
  // Add CI script that runs lint, build, test on affected projects
212
205
  pkgJson.scripts['webpieces:ci'] = 'npx nx affected --target=ci';
213
206
  return pkgJson;
214
207
  });
215
- console.log('✅ Added npm scripts for architecture validation, circular dependency checking, and CI');
208
+ console.log('✅ Added npm scripts for architecture generation and CI');
216
209
  }
217
210
  function createEslintConfig(tree) {
218
211
  const webpiecesConfigPath = 'eslint.webpieces.config.mjs';
@@ -299,8 +292,8 @@ function createSuccessCallback(installTask, hasExistingEslintConfig) {
299
292
  console.log(`${GREEN}✅ @webpieces/dev-config plugin initialized!${RESET}`);
300
293
  console.log('');
301
294
  console.log(`${GREEN}💡 Quick start:${RESET}`);
302
- console.log(` ${BOLD}npm run arch:generate${RESET} # Generate the dependency graph`);
303
- console.log(` ${BOLD}npm run arch:validate-complete${RESET} # Run complete validation`);
295
+ console.log(` ${BOLD}npm run arch:generate${RESET} # Generate the dependency graph`);
296
+ console.log(` ${BOLD}npm run webpieces:ci${RESET} # Run CI on affected projects`);
304
297
  console.log('');
305
298
  console.log(`💡 For full documentation, run: ${BOLD}nx run architecture:help${RESET}`);
306
299
  // Show ESLint integration instructions if they have an existing config
@@ -1 +1 @@
1
- {"version":3,"file":"generator.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/dev-config/src/generators/init/generator.ts"],"names":[],"mappings":"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","sourcesContent":["import { formatFiles, readNxJson, Tree, updateNxJson, updateJson, addDependenciesToPackageJson } from '@nx/devkit';\nimport { createHash } from 'crypto';\n\nexport interface InitGeneratorSchema {\n skipFormat?: boolean;\n}\n\nfunction calculateHash(content: string): string {\n return createHash('sha256').update(content).digest('hex');\n}\n\nfunction getPackageVersion(tree: Tree): string {\n const content = tree.read('node_modules/@webpieces/dev-config/package.json', 'utf-8');\n if (!content) {\n throw new Error('Could not read package.json from node_modules/@webpieces/dev-config');\n }\n const pkgJson = JSON.parse(content);\n return pkgJson.version;\n}\n\n/**\n * Init generator for @webpieces/dev-config\n *\n * Automatically runs when users execute: nx add @webpieces/dev-config\n *\n * Responsibilities:\n * - Registers the plugin in nx.json\n * - Adds architecture validation to targetDefaults (runs once before all builds)\n * - Creates architecture/ directory if needed\n * - Adds madge as a devDependency (required for circular dep checking)\n * - Adds convenient npm scripts to package.json\n * - Always creates eslint.webpieces.config.mjs with @webpieces rules\n * - Creates eslint.config.mjs (if not exists) that imports eslint.webpieces.config.mjs\n * - If eslint.config.mjs exists, shows user how to import eslint.webpieces.config.mjs\n * - Provides helpful output about available targets\n */\nexport default async function initGenerator(tree: Tree, options: InitGeneratorSchema) {\n registerPlugin(tree);\n addTargetDefaults(tree);\n const installTask = addMadgeDependency(tree);\n createArchitectureDirectory(tree);\n addNpmScripts(tree);\n const hasExistingEslintConfig = createEslintConfig(tree);\n\n if (!options.skipFormat) {\n await formatFiles(tree);\n }\n\n return createSuccessCallback(installTask, hasExistingEslintConfig);\n}\n\nfunction registerPlugin(tree: Tree): void {\n const nxJson = readNxJson(tree);\n if (!nxJson) {\n throw new Error('Could not read nx.json. Are you in an Nx workspace?');\n }\n\n if (!nxJson.plugins) {\n nxJson.plugins = [];\n }\n\n const pluginName = '@webpieces/dev-config';\n const alreadyRegistered = nxJson.plugins.some(\n (p) => typeof p === 'string' ? p === pluginName : p.plugin === pluginName\n );\n\n if (!alreadyRegistered) {\n // Register plugin with default options for method size validation\n nxJson.plugins.push({\n plugin: pluginName,\n options: {\n workspace: {\n validations: {\n newMethodsMaxLines: 30,\n modifiedAndNewMethodsMaxLines: 80,\n },\n },\n },\n });\n updateNxJson(tree, nxJson);\n console.log(`✅ Registered ${pluginName} plugin in nx.json with default options`);\n } else {\n console.log(`ℹ️ ${pluginName} plugin is already registered`);\n }\n}\n\nfunction addTargetDefaults(tree: Tree): void {\n const nxJson = readNxJson(tree);\n if (!nxJson) {\n throw new Error('Could not read nx.json. Are you in an Nx workspace?');\n }\n\n if (!nxJson.targetDefaults) {\n nxJson.targetDefaults = {};\n }\n\n // Find which executors are actually used in this workspace\n const usedExecutors = findUsedExecutors(tree);\n\n // Only add targetDefaults for executors that are actually used\n let updated = false;\n\n usedExecutors.forEach((executor) => {\n if (!nxJson.targetDefaults![executor]) {\n nxJson.targetDefaults![executor] = {};\n }\n\n const targetDef = nxJson.targetDefaults![executor];\n let dependsOn = targetDef.dependsOn || [];\n\n // Ensure dependsOn is an array\n if (!Array.isArray(dependsOn)) {\n dependsOn = [dependsOn];\n }\n\n // Check if architecture validation is already in dependsOn\n const hasArchValidation = dependsOn.some((dep) => {\n if (typeof dep === 'string') {\n return dep === 'architecture:validate-complete';\n }\n return dep.target === 'architecture:validate-complete';\n });\n\n // Check if circular deps validation is already in dependsOn\n const hasCircularDepsValidation = dependsOn.some((dep) => {\n if (typeof dep === 'string') {\n return dep === 'validate-no-file-import-cycles';\n }\n return dep.target === 'validate-no-file-import-cycles';\n });\n\n if (!hasCircularDepsValidation) {\n // Add circular deps validation (per-project check)\n dependsOn.unshift('validate-no-file-import-cycles');\n targetDef.dependsOn = dependsOn;\n updated = true;\n console.log(` ✅ Added circular deps validation to ${executor}`);\n }\n\n if (!hasArchValidation) {\n // Add architecture validation before other dependencies\n dependsOn.unshift('architecture:validate-complete');\n targetDef.dependsOn = dependsOn;\n updated = true;\n console.log(` ✅ Added architecture validation to ${executor}`);\n }\n });\n\n if (updated) {\n updateNxJson(tree, nxJson);\n console.log('✅ Added architecture validation to targetDefaults for used executors');\n } else {\n console.log('ℹ️ Architecture validation already configured in targetDefaults');\n }\n}\n\n/**\n * Scan all project.json files to find which build executors are actually used\n */\nfunction findUsedExecutors(tree: Tree): Set<string> {\n const usedExecutors = new Set<string>();\n\n // Known build executors we care about\n const buildExecutors = new Set([\n '@nx/js:tsc',\n '@nx/esbuild:esbuild',\n '@nx/webpack:webpack',\n '@nx/rollup:rollup',\n '@nx/vite:build',\n '@angular/build:application',\n '@angular-devkit/build-angular:browser',\n '@angular-devkit/build-angular:application'\n ]);\n\n // Scan all project.json files\n tree.listChanges(); // Force tree to be aware of all files\n\n const scanDir = (dir: string) => {\n for (const child of tree.children(dir)) {\n const childPath = dir === '.' ? child : `${dir}/${child}`;\n\n if (tree.isFile(childPath)) {\n if (child === 'project.json') {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- Intentionally ignoring JSON parse errors for malformed project.json files\n try {\n const content = tree.read(childPath, 'utf-8');\n if (content) {\n const projectJson = JSON.parse(content);\n if (projectJson.targets) {\n for (const target of Object.values(projectJson.targets)) {\n const executor = (target as { executor?: string })?.executor;\n if (executor && buildExecutors.has(executor)) {\n usedExecutors.add(executor);\n }\n }\n }\n }\n } catch (err: any) {\n //const error = toError(err);\n }\n }\n } else {\n // Skip node_modules and dist\n if (child !== 'node_modules' && child !== 'dist' && child !== '.git') {\n scanDir(childPath);\n }\n }\n }\n };\n\n scanDir('.');\n\n console.log(`ℹ️ Found ${usedExecutors.size} build executors in use: ${[...usedExecutors].join(', ')}`);\n\n return usedExecutors;\n}\n\nfunction addMadgeDependency(tree: Tree) {\n return addDependenciesToPackageJson(tree, {}, { 'madge': '^8.0.0' });\n}\n\nfunction createArchitectureDirectory(tree: Tree): void {\n if (!tree.exists('architecture')) {\n tree.write('architecture/.gitkeep', '');\n console.log('✅ Created architecture/ directory');\n }\n}\n\nfunction addNpmScripts(tree: Tree): void {\n updateJson(tree, 'package.json', (pkgJson) => {\n pkgJson.scripts = pkgJson.scripts ?? {};\n\n // Add architecture validation scripts\n pkgJson.scripts['arch:generate'] = 'nx run architecture:generate';\n pkgJson.scripts['arch:visualize'] = 'nx run architecture:visualize';\n pkgJson.scripts['arch:validate'] = 'nx run architecture:validate-no-architecture-cycles && nx run architecture:validate-no-skiplevel-deps';\n pkgJson.scripts['arch:validate-all'] = 'nx run architecture:validate-no-architecture-cycles && nx run architecture:validate-no-skiplevel-deps && nx run architecture:validate-architecture-unchanged';\n\n // Add file import cycle checking scripts\n pkgJson.scripts['arch:check-circular'] = 'nx run-many --target=validate-no-file-import-cycles --all';\n pkgJson.scripts['arch:check-circular-affected'] = 'nx affected --target=validate-no-file-import-cycles';\n\n // Complete validation including circular deps\n pkgJson.scripts['arch:validate-complete'] = 'npm run arch:validate-all && npm run arch:check-circular';\n\n // Add CI script that runs lint, build, test on affected projects\n pkgJson.scripts['webpieces:ci'] = 'npx nx affected --target=ci';\n\n return pkgJson;\n });\n\n console.log('✅ Added npm scripts for architecture validation, circular dependency checking, and CI');\n}\n\nfunction createEslintConfig(tree: Tree): boolean {\n const webpiecesConfigPath = 'eslint.webpieces.config.mjs';\n const mainConfigPath = 'eslint.config.mjs';\n\n // Always create eslint.webpieces.config.mjs with our rules\n createWebpiecesEslintConfig(tree, webpiecesConfigPath);\n\n // Check if main eslint.config.mjs exists\n const hasExistingConfig = tree.exists(mainConfigPath);\n\n if (!hasExistingConfig) {\n // No existing config - create one that imports webpieces config\n const mainConfig = `// ESLint configuration\n// Imports @webpieces/dev-config rules\n\nimport webpiecesConfig from './eslint.webpieces.config.mjs';\n\n// Export the webpieces configuration\n// You can add your own rules after spreading webpiecesConfig\nexport default [\n ...webpiecesConfig,\n // Add your custom ESLint configuration here\n];\n`;\n\n tree.write(mainConfigPath, mainConfig);\n console.log('✅ Created eslint.config.mjs with @webpieces/dev-config rules');\n }\n\n return hasExistingConfig;\n}\n\nfunction getWebpiecesEslintConfigTemplate(tree: Tree): string {\n // Read from canonical template file (single source of truth)\n const templatePath = 'node_modules/@webpieces/dev-config/templates/eslint.webpieces.config.mjs';\n const template = tree.read(templatePath, 'utf-8');\n\n if (!template) {\n throw new Error(`Could not read ESLint template from ${templatePath}`);\n }\n\n return template;\n}\n\nfunction warnConfigChanges(tree: Tree, configPath: string, newConfig: string): void {\n const version = getPackageVersion(tree);\n const versionedFilename = `${configPath}.v${version}`;\n\n tree.write(versionedFilename, newConfig);\n\n console.log('');\n console.log(`⚠️ ${configPath} has changes`);\n console.log('');\n console.log(' Either you modified the file OR @webpieces/dev-config has updates.');\n console.log('');\n console.log(` Created: ${versionedFilename} with latest version`);\n console.log('');\n console.log(' Please review and merge if needed:');\n console.log(` - Your current: ${configPath}`);\n console.log(` - New version: ${versionedFilename}`);\n console.log('');\n}\n\nfunction createWebpiecesEslintConfig(tree: Tree, configPath: string): void {\n const webpiecesConfig = getWebpiecesEslintConfigTemplate(tree);\n\n if (!tree.exists(configPath)) {\n tree.write(configPath, webpiecesConfig);\n console.log(`✅ Created ${configPath}`);\n return;\n }\n\n const currentContent = tree.read(configPath, 'utf-8');\n if (!currentContent) {\n tree.write(configPath, webpiecesConfig);\n console.log(`✅ Created ${configPath}`);\n return;\n }\n\n const currentHash = calculateHash(currentContent);\n const newHash = calculateHash(webpiecesConfig);\n\n if (currentHash === newHash) {\n console.log(`✅ ${configPath} is up to date`);\n return;\n }\n\n warnConfigChanges(tree, configPath, webpiecesConfig);\n}\n\nfunction createSuccessCallback(\n installTask: ReturnType<typeof addDependenciesToPackageJson>,\n hasExistingEslintConfig: boolean\n) {\n return async () => {\n await installTask();\n\n // ANSI color codes for formatted output\n const GREEN = '\\x1b[32m\\x1b[1m';\n const BOLD = '\\x1b[1m';\n const RESET = '\\x1b[0m';\n\n console.log('');\n console.log('✅ Added madge to devDependencies');\n console.log('');\n console.log(`${GREEN}✅ @webpieces/dev-config plugin initialized!${RESET}`);\n console.log('');\n console.log(`${GREEN}💡 Quick start:${RESET}`);\n console.log(` ${BOLD}npm run arch:generate${RESET} # Generate the dependency graph`);\n console.log(` ${BOLD}npm run arch:validate-complete${RESET} # Run complete validation`);\n console.log('');\n console.log(`💡 For full documentation, run: ${BOLD}nx run architecture:help${RESET}`);\n\n // Show ESLint integration instructions if they have an existing config\n if (hasExistingEslintConfig) {\n console.log('');\n console.log('📋 Existing eslint.config.mjs detected');\n console.log('');\n console.log('To use @webpieces/dev-config ESLint rules, add this import to your eslint.config.mjs:');\n console.log('');\n console.log(' import webpiecesConfig from \\'./eslint.webpieces.config.mjs\\';');\n console.log('');\n console.log('Then spread it into your config array:');\n console.log('');\n console.log(' export default [');\n console.log(' ...webpiecesConfig, // Add this line');\n console.log(' // ... your existing config');\n console.log(' ];');\n }\n\n console.log('');\n };\n}\n"]}
1
+ {"version":3,"file":"generator.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/dev-config/src/generators/init/generator.ts"],"names":[],"mappings":";;AAoCA,gCAaC;AAjDD,uCAAmH;AACnH,mCAAoC;AAMpC,SAAS,aAAa,CAAC,OAAe;IAClC,OAAO,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC9D,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAU;IACjC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,iDAAiD,EAAE,OAAO,CAAC,CAAC;IACtF,IAAI,CAAC,OAAO,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,qEAAqE,CAAC,CAAC;IAC3F,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACpC,OAAO,OAAO,CAAC,OAAO,CAAC;AAC3B,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACY,KAAK,UAAU,aAAa,CAAC,IAAU,EAAE,OAA4B;IAChF,cAAc,CAAC,IAAI,CAAC,CAAC;IACrB,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACxB,MAAM,WAAW,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IAC7C,2BAA2B,CAAC,IAAI,CAAC,CAAC;IAClC,aAAa,CAAC,IAAI,CAAC,CAAC;IACpB,MAAM,uBAAuB,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IAEzD,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;QACtB,MAAM,IAAA,oBAAW,EAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAED,OAAO,qBAAqB,CAAC,WAAW,EAAE,uBAAuB,CAAC,CAAC;AACvE,CAAC;AAED,SAAS,cAAc,CAAC,IAAU;IAC9B,MAAM,MAAM,GAAG,IAAA,mBAAU,EAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,MAAM,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IAC3E,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QAClB,MAAM,CAAC,OAAO,GAAG,EAAE,CAAC;IACxB,CAAC;IAED,MAAM,UAAU,GAAG,uBAAuB,CAAC;IAC3C,MAAM,iBAAiB,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CACzC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,UAAU,CAC5E,CAAC;IAEF,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACrB,kEAAkE;QAClE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;YAChB,MAAM,EAAE,UAAU;YAClB,OAAO,EAAE;gBACL,SAAS,EAAE;oBACP,WAAW,EAAE;wBACT,kBAAkB,EAAE,EAAE;wBACtB,6BAA6B,EAAE,EAAE;qBACpC;iBACJ;aACJ;SACJ,CAAC,CAAC;QACH,IAAA,qBAAY,EAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,gBAAgB,UAAU,yCAAyC,CAAC,CAAC;IACrF,CAAC;SAAM,CAAC;QACJ,OAAO,CAAC,GAAG,CAAC,OAAO,UAAU,+BAA+B,CAAC,CAAC;IAClE,CAAC;AACL,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAU;IACjC,MAAM,MAAM,GAAG,IAAA,mBAAU,EAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,MAAM,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IAC3E,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;QACzB,MAAM,CAAC,cAAc,GAAG,EAAE,CAAC;IAC/B,CAAC;IAED,2DAA2D;IAC3D,MAAM,aAAa,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAE9C,+DAA+D;IAC/D,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,aAAa,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE;QAC/B,IAAI,CAAC,MAAM,CAAC,cAAe,CAAC,QAAQ,CAAC,EAAE,CAAC;YACpC,MAAM,CAAC,cAAe,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC;QAC1C,CAAC;QAED,MAAM,SAAS,GAAG,MAAM,CAAC,cAAe,CAAC,QAAQ,CAAC,CAAC;QACnD,IAAI,SAAS,GAAG,SAAS,CAAC,SAAS,IAAI,EAAE,CAAC;QAE1C,+BAA+B;QAC/B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;YAC5B,SAAS,GAAG,CAAC,SAAS,CAAC,CAAC;QAC5B,CAAC;QAED,2DAA2D;QAC3D,MAAM,iBAAiB,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;YAC7C,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;gBAC1B,OAAO,GAAG,KAAK,gCAAgC,CAAC;YACpD,CAAC;YACD,OAAO,GAAG,CAAC,MAAM,KAAK,gCAAgC,CAAC;QAC3D,CAAC,CAAC,CAAC;QAEH,4DAA4D;QAC5D,MAAM,yBAAyB,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;YACrD,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;gBAC1B,OAAO,GAAG,KAAK,gCAAgC,CAAC;YACpD,CAAC;YACD,OAAO,GAAG,CAAC,MAAM,KAAK,gCAAgC,CAAC;QAC3D,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,yBAAyB,EAAE,CAAC;YAC7B,mDAAmD;YACnD,SAAS,CAAC,OAAO,CAAC,gCAAgC,CAAC,CAAC;YACpD,SAAS,CAAC,SAAS,GAAG,SAAS,CAAC;YAChC,OAAO,GAAG,IAAI,CAAC;YACf,OAAO,CAAC,GAAG,CAAC,yCAAyC,QAAQ,EAAE,CAAC,CAAC;QACrE,CAAC;QAED,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACrB,wDAAwD;YACxD,SAAS,CAAC,OAAO,CAAC,gCAAgC,CAAC,CAAC;YACpD,SAAS,CAAC,SAAS,GAAG,SAAS,CAAC;YAChC,OAAO,GAAG,IAAI,CAAC;YACf,OAAO,CAAC,GAAG,CAAC,wCAAwC,QAAQ,EAAE,CAAC,CAAC;QACpE,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,IAAI,OAAO,EAAE,CAAC;QACV,IAAA,qBAAY,EAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,sEAAsE,CAAC,CAAC;IACxF,CAAC;SAAM,CAAC;QACJ,OAAO,CAAC,GAAG,CAAC,kEAAkE,CAAC,CAAC;IACpF,CAAC;AACL,CAAC;AAED;;GAEG;AACH,SAAS,iBAAiB,CAAC,IAAU;IACjC,MAAM,aAAa,GAAG,IAAI,GAAG,EAAU,CAAC;IAExC,sCAAsC;IACtC,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC;QAC3B,YAAY;QACZ,qBAAqB;QACrB,qBAAqB;QACrB,mBAAmB;QACnB,gBAAgB;QAChB,4BAA4B;QAC5B,uCAAuC;QACvC,2CAA2C;KAC9C,CAAC,CAAC;IAEH,8BAA8B;IAC9B,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,sCAAsC;IAE1D,MAAM,OAAO,GAAG,CAAC,GAAW,EAAE,EAAE;QAC5B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACrC,MAAM,SAAS,GAAG,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,KAAK,EAAE,CAAC;YAE1D,IAAI,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;gBACzB,IAAI,KAAK,KAAK,cAAc,EAAE,CAAC;oBAC3B,2IAA2I;oBAC3I,IAAI,CAAC;wBACD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;wBAC9C,IAAI,OAAO,EAAE,CAAC;4BACV,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;4BACxC,IAAI,WAAW,CAAC,OAAO,EAAE,CAAC;gCACtB,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;oCACtD,MAAM,QAAQ,GAAI,MAAgC,EAAE,QAAQ,CAAC;oCAC7D,IAAI,QAAQ,IAAI,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;wCAC3C,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;oCAChC,CAAC;gCACL,CAAC;4BACL,CAAC;wBACL,CAAC;oBACL,CAAC;oBAAC,OAAO,GAAQ,EAAE,CAAC;wBAChB,6BAA6B;oBACjC,CAAC;gBACL,CAAC;YACL,CAAC;iBAAM,CAAC;gBACJ,6BAA6B;gBAC7B,IAAI,KAAK,KAAK,cAAc,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;oBACnE,OAAO,CAAC,SAAS,CAAC,CAAC;gBACvB,CAAC;YACL,CAAC;QACL,CAAC;IACL,CAAC,CAAC;IAEF,OAAO,CAAC,GAAG,CAAC,CAAC;IAEb,OAAO,CAAC,GAAG,CAAC,aAAa,aAAa,CAAC,IAAI,4BAA4B,CAAC,GAAG,aAAa,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAExG,OAAO,aAAa,CAAC;AACzB,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAU;IAClC,OAAO,IAAA,qCAA4B,EAAC,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;AACzE,CAAC;AAED,SAAS,2BAA2B,CAAC,IAAU;IAC3C,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,EAAE,CAAC;QAC/B,IAAI,CAAC,KAAK,CAAC,uBAAuB,EAAE,EAAE,CAAC,CAAC;QACxC,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;IACrD,CAAC;AACL,CAAC;AAED,SAAS,aAAa,CAAC,IAAU;IAC7B,IAAA,mBAAU,EAAC,IAAI,EAAE,cAAc,EAAE,CAAC,OAAO,EAAE,EAAE;QACzC,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC;QAExC,2BAA2B;QAC3B,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,GAAG,8BAA8B,CAAC;QAClE,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,GAAG,+BAA+B,CAAC;QAEpE,iEAAiE;QACjE,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,GAAG,6BAA6B,CAAC;QAEhE,OAAO,OAAO,CAAC;IACnB,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,GAAG,CAAC,wDAAwD,CAAC,CAAC;AAC1E,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAU;IAClC,MAAM,mBAAmB,GAAG,6BAA6B,CAAC;IAC1D,MAAM,cAAc,GAAG,mBAAmB,CAAC;IAE3C,2DAA2D;IAC3D,2BAA2B,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;IAEvD,yCAAyC;IACzC,MAAM,iBAAiB,GAAG,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IAEtD,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACrB,gEAAgE;QAChE,MAAM,UAAU,GAAG;;;;;;;;;;;CAW1B,CAAC;QAEM,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;QACvC,OAAO,CAAC,GAAG,CAAC,8DAA8D,CAAC,CAAC;IAChF,CAAC;IAED,OAAO,iBAAiB,CAAC;AAC7B,CAAC;AAED,SAAS,gCAAgC,CAAC,IAAU;IAChD,6DAA6D;IAC7D,MAAM,YAAY,GAAG,0EAA0E,CAAC;IAChG,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IAElD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,uCAAuC,YAAY,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED,OAAO,QAAQ,CAAC;AACpB,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAU,EAAE,UAAkB,EAAE,SAAiB;IACxE,MAAM,OAAO,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,iBAAiB,GAAG,GAAG,UAAU,KAAK,OAAO,EAAE,CAAC;IAEtD,IAAI,CAAC,KAAK,CAAC,iBAAiB,EAAE,SAAS,CAAC,CAAC;IAEzC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,OAAO,UAAU,cAAc,CAAC,CAAC;IAC7C,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,uEAAuE,CAAC,CAAC;IACrF,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,eAAe,iBAAiB,sBAAsB,CAAC,CAAC;IACpE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC;IACrD,OAAO,CAAC,GAAG,CAAC,wBAAwB,UAAU,EAAE,CAAC,CAAC;IAClD,OAAO,CAAC,GAAG,CAAC,wBAAwB,iBAAiB,EAAE,CAAC,CAAC;IACzD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AACpB,CAAC;AAED,SAAS,2BAA2B,CAAC,IAAU,EAAE,UAAkB;IAC/D,MAAM,eAAe,GAAG,gCAAgC,CAAC,IAAI,CAAC,CAAC;IAE/D,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3B,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;QACxC,OAAO,CAAC,GAAG,CAAC,aAAa,UAAU,EAAE,CAAC,CAAC;QACvC,OAAO;IACX,CAAC;IAED,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IACtD,IAAI,CAAC,cAAc,EAAE,CAAC;QAClB,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;QACxC,OAAO,CAAC,GAAG,CAAC,aAAa,UAAU,EAAE,CAAC,CAAC;QACvC,OAAO;IACX,CAAC;IAED,MAAM,WAAW,GAAG,aAAa,CAAC,cAAc,CAAC,CAAC;IAClD,MAAM,OAAO,GAAG,aAAa,CAAC,eAAe,CAAC,CAAC;IAE/C,IAAI,WAAW,KAAK,OAAO,EAAE,CAAC;QAC1B,OAAO,CAAC,GAAG,CAAC,KAAK,UAAU,gBAAgB,CAAC,CAAC;QAC7C,OAAO;IACX,CAAC;IAED,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC;AACzD,CAAC;AAED,SAAS,qBAAqB,CAC1B,WAA4D,EAC5D,uBAAgC;IAEhC,OAAO,KAAK,IAAI,EAAE;QACd,MAAM,WAAW,EAAE,CAAC;QAEpB,wCAAwC;QACxC,MAAM,KAAK,GAAG,iBAAiB,CAAC;QAChC,MAAM,IAAI,GAAG,SAAS,CAAC;QACvB,MAAM,KAAK,GAAG,SAAS,CAAC;QAExB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;QAChD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,8CAA8C,KAAK,EAAE,CAAC,CAAC;QAC3E,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,kBAAkB,KAAK,EAAE,CAAC,CAAC;QAC/C,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,wBAAwB,KAAK,qCAAqC,CAAC,CAAC;QAC1F,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,uBAAuB,KAAK,oCAAoC,CAAC,CAAC;QACxF,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,mCAAmC,IAAI,2BAA2B,KAAK,EAAE,CAAC,CAAC;QAEvF,uEAAuE;QACvE,IAAI,uBAAuB,EAAE,CAAC;YAC1B,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,wCAAwC,CAAC,CAAC;YACtD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,uFAAuF,CAAC,CAAC;YACrG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,kEAAkE,CAAC,CAAC;YAChF,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,wCAAwC,CAAC,CAAC;YACtD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;YAClC,OAAO,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAC;YACzD,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;YAC/C,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACxB,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACpB,CAAC,CAAC;AACN,CAAC","sourcesContent":["import { formatFiles, readNxJson, Tree, updateNxJson, updateJson, addDependenciesToPackageJson } from '@nx/devkit';\nimport { createHash } from 'crypto';\n\nexport interface InitGeneratorSchema {\n skipFormat?: boolean;\n}\n\nfunction calculateHash(content: string): string {\n return createHash('sha256').update(content).digest('hex');\n}\n\nfunction getPackageVersion(tree: Tree): string {\n const content = tree.read('node_modules/@webpieces/dev-config/package.json', 'utf-8');\n if (!content) {\n throw new Error('Could not read package.json from node_modules/@webpieces/dev-config');\n }\n const pkgJson = JSON.parse(content);\n return pkgJson.version;\n}\n\n/**\n * Init generator for @webpieces/dev-config\n *\n * Automatically runs when users execute: nx add @webpieces/dev-config\n *\n * Responsibilities:\n * - Registers the plugin in nx.json\n * - Adds architecture validation to targetDefaults (runs once before all builds)\n * - Creates architecture/ directory if needed\n * - Adds madge as a devDependency (required for circular dep checking)\n * - Adds convenient npm scripts to package.json\n * - Always creates eslint.webpieces.config.mjs with @webpieces rules\n * - Creates eslint.config.mjs (if not exists) that imports eslint.webpieces.config.mjs\n * - If eslint.config.mjs exists, shows user how to import eslint.webpieces.config.mjs\n * - Provides helpful output about available targets\n */\nexport default async function initGenerator(tree: Tree, options: InitGeneratorSchema) {\n registerPlugin(tree);\n addTargetDefaults(tree);\n const installTask = addMadgeDependency(tree);\n createArchitectureDirectory(tree);\n addNpmScripts(tree);\n const hasExistingEslintConfig = createEslintConfig(tree);\n\n if (!options.skipFormat) {\n await formatFiles(tree);\n }\n\n return createSuccessCallback(installTask, hasExistingEslintConfig);\n}\n\nfunction registerPlugin(tree: Tree): void {\n const nxJson = readNxJson(tree);\n if (!nxJson) {\n throw new Error('Could not read nx.json. Are you in an Nx workspace?');\n }\n\n if (!nxJson.plugins) {\n nxJson.plugins = [];\n }\n\n const pluginName = '@webpieces/dev-config';\n const alreadyRegistered = nxJson.plugins.some(\n (p) => typeof p === 'string' ? p === pluginName : p.plugin === pluginName\n );\n\n if (!alreadyRegistered) {\n // Register plugin with default options for method size validation\n nxJson.plugins.push({\n plugin: pluginName,\n options: {\n workspace: {\n validations: {\n newMethodsMaxLines: 30,\n modifiedAndNewMethodsMaxLines: 80,\n },\n },\n },\n });\n updateNxJson(tree, nxJson);\n console.log(`✅ Registered ${pluginName} plugin in nx.json with default options`);\n } else {\n console.log(`ℹ️ ${pluginName} plugin is already registered`);\n }\n}\n\nfunction addTargetDefaults(tree: Tree): void {\n const nxJson = readNxJson(tree);\n if (!nxJson) {\n throw new Error('Could not read nx.json. Are you in an Nx workspace?');\n }\n\n if (!nxJson.targetDefaults) {\n nxJson.targetDefaults = {};\n }\n\n // Find which executors are actually used in this workspace\n const usedExecutors = findUsedExecutors(tree);\n\n // Only add targetDefaults for executors that are actually used\n let updated = false;\n\n usedExecutors.forEach((executor) => {\n if (!nxJson.targetDefaults![executor]) {\n nxJson.targetDefaults![executor] = {};\n }\n\n const targetDef = nxJson.targetDefaults![executor];\n let dependsOn = targetDef.dependsOn || [];\n\n // Ensure dependsOn is an array\n if (!Array.isArray(dependsOn)) {\n dependsOn = [dependsOn];\n }\n\n // Check if architecture validation is already in dependsOn\n const hasArchValidation = dependsOn.some((dep) => {\n if (typeof dep === 'string') {\n return dep === 'architecture:validate-complete';\n }\n return dep.target === 'architecture:validate-complete';\n });\n\n // Check if circular deps validation is already in dependsOn\n const hasCircularDepsValidation = dependsOn.some((dep) => {\n if (typeof dep === 'string') {\n return dep === 'validate-no-file-import-cycles';\n }\n return dep.target === 'validate-no-file-import-cycles';\n });\n\n if (!hasCircularDepsValidation) {\n // Add circular deps validation (per-project check)\n dependsOn.unshift('validate-no-file-import-cycles');\n targetDef.dependsOn = dependsOn;\n updated = true;\n console.log(` ✅ Added circular deps validation to ${executor}`);\n }\n\n if (!hasArchValidation) {\n // Add architecture validation before other dependencies\n dependsOn.unshift('architecture:validate-complete');\n targetDef.dependsOn = dependsOn;\n updated = true;\n console.log(` ✅ Added architecture validation to ${executor}`);\n }\n });\n\n if (updated) {\n updateNxJson(tree, nxJson);\n console.log('✅ Added architecture validation to targetDefaults for used executors');\n } else {\n console.log('ℹ️ Architecture validation already configured in targetDefaults');\n }\n}\n\n/**\n * Scan all project.json files to find which build executors are actually used\n */\nfunction findUsedExecutors(tree: Tree): Set<string> {\n const usedExecutors = new Set<string>();\n\n // Known build executors we care about\n const buildExecutors = new Set([\n '@nx/js:tsc',\n '@nx/esbuild:esbuild',\n '@nx/webpack:webpack',\n '@nx/rollup:rollup',\n '@nx/vite:build',\n '@angular/build:application',\n '@angular-devkit/build-angular:browser',\n '@angular-devkit/build-angular:application'\n ]);\n\n // Scan all project.json files\n tree.listChanges(); // Force tree to be aware of all files\n\n const scanDir = (dir: string) => {\n for (const child of tree.children(dir)) {\n const childPath = dir === '.' ? child : `${dir}/${child}`;\n\n if (tree.isFile(childPath)) {\n if (child === 'project.json') {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- Intentionally ignoring JSON parse errors for malformed project.json files\n try {\n const content = tree.read(childPath, 'utf-8');\n if (content) {\n const projectJson = JSON.parse(content);\n if (projectJson.targets) {\n for (const target of Object.values(projectJson.targets)) {\n const executor = (target as { executor?: string })?.executor;\n if (executor && buildExecutors.has(executor)) {\n usedExecutors.add(executor);\n }\n }\n }\n }\n } catch (err: any) {\n //const error = toError(err);\n }\n }\n } else {\n // Skip node_modules and dist\n if (child !== 'node_modules' && child !== 'dist' && child !== '.git') {\n scanDir(childPath);\n }\n }\n }\n };\n\n scanDir('.');\n\n console.log(`ℹ️ Found ${usedExecutors.size} build executors in use: ${[...usedExecutors].join(', ')}`);\n\n return usedExecutors;\n}\n\nfunction addMadgeDependency(tree: Tree) {\n return addDependenciesToPackageJson(tree, {}, { 'madge': '^8.0.0' });\n}\n\nfunction createArchitectureDirectory(tree: Tree): void {\n if (!tree.exists('architecture')) {\n tree.write('architecture/.gitkeep', '');\n console.log('✅ Created architecture/ directory');\n }\n}\n\nfunction addNpmScripts(tree: Tree): void {\n updateJson(tree, 'package.json', (pkgJson) => {\n pkgJson.scripts = pkgJson.scripts ?? {};\n\n // Add architecture scripts\n pkgJson.scripts['arch:generate'] = 'nx run architecture:generate';\n pkgJson.scripts['arch:visualize'] = 'nx run architecture:visualize';\n\n // Add CI script that runs lint, build, test on affected projects\n pkgJson.scripts['webpieces:ci'] = 'npx nx affected --target=ci';\n\n return pkgJson;\n });\n\n console.log('✅ Added npm scripts for architecture generation and CI');\n}\n\nfunction createEslintConfig(tree: Tree): boolean {\n const webpiecesConfigPath = 'eslint.webpieces.config.mjs';\n const mainConfigPath = 'eslint.config.mjs';\n\n // Always create eslint.webpieces.config.mjs with our rules\n createWebpiecesEslintConfig(tree, webpiecesConfigPath);\n\n // Check if main eslint.config.mjs exists\n const hasExistingConfig = tree.exists(mainConfigPath);\n\n if (!hasExistingConfig) {\n // No existing config - create one that imports webpieces config\n const mainConfig = `// ESLint configuration\n// Imports @webpieces/dev-config rules\n\nimport webpiecesConfig from './eslint.webpieces.config.mjs';\n\n// Export the webpieces configuration\n// You can add your own rules after spreading webpiecesConfig\nexport default [\n ...webpiecesConfig,\n // Add your custom ESLint configuration here\n];\n`;\n\n tree.write(mainConfigPath, mainConfig);\n console.log('✅ Created eslint.config.mjs with @webpieces/dev-config rules');\n }\n\n return hasExistingConfig;\n}\n\nfunction getWebpiecesEslintConfigTemplate(tree: Tree): string {\n // Read from canonical template file (single source of truth)\n const templatePath = 'node_modules/@webpieces/dev-config/templates/eslint.webpieces.config.mjs';\n const template = tree.read(templatePath, 'utf-8');\n\n if (!template) {\n throw new Error(`Could not read ESLint template from ${templatePath}`);\n }\n\n return template;\n}\n\nfunction warnConfigChanges(tree: Tree, configPath: string, newConfig: string): void {\n const version = getPackageVersion(tree);\n const versionedFilename = `${configPath}.v${version}`;\n\n tree.write(versionedFilename, newConfig);\n\n console.log('');\n console.log(`⚠️ ${configPath} has changes`);\n console.log('');\n console.log(' Either you modified the file OR @webpieces/dev-config has updates.');\n console.log('');\n console.log(` Created: ${versionedFilename} with latest version`);\n console.log('');\n console.log(' Please review and merge if needed:');\n console.log(` - Your current: ${configPath}`);\n console.log(` - New version: ${versionedFilename}`);\n console.log('');\n}\n\nfunction createWebpiecesEslintConfig(tree: Tree, configPath: string): void {\n const webpiecesConfig = getWebpiecesEslintConfigTemplate(tree);\n\n if (!tree.exists(configPath)) {\n tree.write(configPath, webpiecesConfig);\n console.log(`✅ Created ${configPath}`);\n return;\n }\n\n const currentContent = tree.read(configPath, 'utf-8');\n if (!currentContent) {\n tree.write(configPath, webpiecesConfig);\n console.log(`✅ Created ${configPath}`);\n return;\n }\n\n const currentHash = calculateHash(currentContent);\n const newHash = calculateHash(webpiecesConfig);\n\n if (currentHash === newHash) {\n console.log(`✅ ${configPath} is up to date`);\n return;\n }\n\n warnConfigChanges(tree, configPath, webpiecesConfig);\n}\n\nfunction createSuccessCallback(\n installTask: ReturnType<typeof addDependenciesToPackageJson>,\n hasExistingEslintConfig: boolean\n) {\n return async () => {\n await installTask();\n\n // ANSI color codes for formatted output\n const GREEN = '\\x1b[32m\\x1b[1m';\n const BOLD = '\\x1b[1m';\n const RESET = '\\x1b[0m';\n\n console.log('');\n console.log('✅ Added madge to devDependencies');\n console.log('');\n console.log(`${GREEN}✅ @webpieces/dev-config plugin initialized!${RESET}`);\n console.log('');\n console.log(`${GREEN}💡 Quick start:${RESET}`);\n console.log(` ${BOLD}npm run arch:generate${RESET} # Generate the dependency graph`);\n console.log(` ${BOLD}npm run webpieces:ci${RESET} # Run CI on affected projects`);\n console.log('');\n console.log(`💡 For full documentation, run: ${BOLD}nx run architecture:help${RESET}`);\n\n // Show ESLint integration instructions if they have an existing config\n if (hasExistingEslintConfig) {\n console.log('');\n console.log('📋 Existing eslint.config.mjs detected');\n console.log('');\n console.log('To use @webpieces/dev-config ESLint rules, add this import to your eslint.config.mjs:');\n console.log('');\n console.log(' import webpiecesConfig from \\'./eslint.webpieces.config.mjs\\';');\n console.log('');\n console.log('Then spread it into your config array:');\n console.log('');\n console.log(' export default [');\n console.log(' ...webpiecesConfig, // Add this line');\n console.log(' // ... your existing config');\n console.log(' ];');\n }\n\n console.log('');\n };\n}\n"]}
@@ -20,6 +20,7 @@ export default [
20
20
  },
21
21
  rules: {
22
22
  '@webpieces/catch-error-pattern': 'error',
23
+ // READ tmp/webpieces/webpieces.exceptions.md for AI rollout instructions and rationale
23
24
  '@webpieces/no-unmanaged-exceptions': 'error',
24
25
  // '@webpieces/max-method-lines': ['error', { max: 70 }],
25
26
  // '@webpieces/max-file-lines': ['error', { max: 700 }],
@@ -2,6 +2,23 @@
2
2
 
3
3
  **READ THIS FILE to understand why try-catch blocks are restricted and how to fix violations**
4
4
 
5
+ ## GETTING STARTED: Rolling Out This Rule
6
+
7
+ **Why this rule exists**: AI agents tend to randomly add try-catch blocks ~50% of the time, creating pointless error handling that swallows exceptions and breaks debugging.
8
+
9
+ **How to roll out on existing codebases**:
10
+ 1. Enable the rule: `'@webpieces/no-unmanaged-exceptions': 'error'`
11
+ 2. Have AI add `// eslint-disable-next-line @webpieces/no-unmanaged-exceptions` to EACH try-catch line (NOT file-level disables)
12
+ 3. This forces AI to consciously acknowledge each exception handling location
13
+ 4. Going forward, the rule makes AI think twice before adding new try-catch blocks
14
+
15
+ **What the global error handler provides** (when exceptions bubble up properly):
16
+ 1. **Logs it** - Full error with stack trace and traceId
17
+ 2. **Reports to operations** - Sends to monitoring (Sentry/Datadog) so AI/team can fix
18
+ 3. **Shows user-friendly error** - Pops error dialog with errorId (user receives email with same ID for support)
19
+
20
+ **Per-line disables are intentional**: Each disable comment serves as documentation explaining WHY that specific try-catch exists, making code review and future AI sessions aware of the exception handling decision.
21
+
5
22
  ## Core Principle
6
23
 
7
24
  **EXCEPTIONS MUST BUBBLE TO GLOBAL HANDLER WITH TRACEID FOR DEBUGGABILITY.**