@webpieces/dev-config 0.2.26 → 0.2.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/executors/help/executor.d.ts +6 -0
- package/executors/help/executor.js +45 -0
- package/executors/help/executor.js.map +1 -0
- package/executors/help/executor.ts +50 -0
- package/executors/help/schema.json +7 -0
- package/executors/validate-eslint-sync/executor.d.ts +6 -0
- package/executors/validate-eslint-sync/executor.js +69 -0
- package/executors/validate-eslint-sync/executor.js.map +1 -0
- package/executors/validate-eslint-sync/executor.ts +83 -0
- package/executors/validate-eslint-sync/schema.json +7 -0
- package/package.json +2 -1
- package/plugin/index.d.ts +4 -0
- package/plugin/index.js +8 -0
- package/plugin/index.js.map +1 -0
- package/plugin.js +260 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = helpExecutor;
|
|
4
|
+
async function helpExecutor(options, context) {
|
|
5
|
+
// ANSI color codes
|
|
6
|
+
const GREEN = '\x1b[32m\x1b[1m';
|
|
7
|
+
const BOLD = '\x1b[1m';
|
|
8
|
+
const RESET = '\x1b[0m';
|
|
9
|
+
console.log('');
|
|
10
|
+
console.log(`${GREEN}💡 @webpieces/dev-config - Available Commands${RESET}`);
|
|
11
|
+
console.log('');
|
|
12
|
+
console.log(`${BOLD}📝 Available npm scripts (convenient shortcuts):${RESET}`);
|
|
13
|
+
console.log('');
|
|
14
|
+
console.log(' Architecture graph:');
|
|
15
|
+
console.log(' npm run arch:generate # Generate dependency graph');
|
|
16
|
+
console.log(' npm run arch:visualize # Visualize dependency graph');
|
|
17
|
+
console.log('');
|
|
18
|
+
console.log(' Validation:');
|
|
19
|
+
console.log(' npm run arch:validate # Quick validation (no-cycles + no-skiplevel-deps)');
|
|
20
|
+
console.log(' npm run arch:validate-all # Full arch validation (+ unchanged check)');
|
|
21
|
+
console.log(' npm run arch:check-circular # Check all projects for circular deps');
|
|
22
|
+
console.log(' npm run arch:check-circular-affected # Check affected projects only');
|
|
23
|
+
console.log(' npm run arch:validate-complete # Complete validation (arch + circular)');
|
|
24
|
+
console.log('');
|
|
25
|
+
console.log(`${BOLD}📝 Available Nx targets:${RESET}`);
|
|
26
|
+
console.log('');
|
|
27
|
+
console.log(' Workspace-level architecture validation:');
|
|
28
|
+
console.log(' nx run .:arch:generate # Generate dependency graph');
|
|
29
|
+
console.log(' nx run .:arch:visualize # Visualize dependency graph');
|
|
30
|
+
console.log(' nx run .:arch:validate-no-cycles # Check for circular dependencies');
|
|
31
|
+
console.log(' nx run .:arch:validate-no-skiplevel-deps # Check for redundant dependencies');
|
|
32
|
+
console.log(' nx run .:arch:validate-architecture-unchanged # Validate against blessed graph');
|
|
33
|
+
console.log('');
|
|
34
|
+
console.log(' Per-project circular dependency checking:');
|
|
35
|
+
console.log(' nx run <project>:check-circular-deps # Check project for circular deps');
|
|
36
|
+
console.log(' nx affected --target=check-circular-deps # Check all affected projects');
|
|
37
|
+
console.log(' nx run-many --target=check-circular-deps --all # Check all projects');
|
|
38
|
+
console.log('');
|
|
39
|
+
console.log(`${GREEN}💡 Quick start:${RESET}`);
|
|
40
|
+
console.log(` ${BOLD}npm run arch:generate${RESET} # Generate the graph first`);
|
|
41
|
+
console.log(` ${BOLD}npm run arch:validate-complete${RESET} # Run complete validation`);
|
|
42
|
+
console.log('');
|
|
43
|
+
return { success: true };
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../packages/tooling/dev-config/executors/help/executor.ts"],"names":[],"mappings":";;AAIA,+BA6CC;AA7Cc,KAAK,UAAU,YAAY,CACtC,OAA4B,EAC5B,OAAwB;IAExB,mBAAmB;IACnB,MAAM,KAAK,GAAG,iBAAiB,CAAC;IAChC,MAAM,IAAI,GAAG,SAAS,CAAC;IACvB,MAAM,KAAK,GAAG,SAAS,CAAC;IAExB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,gDAAgD,KAAK,EAAE,CAAC,CAAC;IAC7E,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,mDAAmD,KAAK,EAAE,CAAC,CAAC;IAC/E,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;IACrC,OAAO,CAAC,GAAG,CAAC,wEAAwE,CAAC,CAAC;IACtF,OAAO,CAAC,GAAG,CAAC,yEAAyE,CAAC,CAAC;IACvF,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IAC7B,OAAO,CAAC,GAAG,CAAC,+FAA+F,CAAC,CAAC;IAC7G,OAAO,CAAC,GAAG,CAAC,uFAAuF,CAAC,CAAC;IACrG,OAAO,CAAC,GAAG,CAAC,mFAAmF,CAAC,CAAC;IACjG,OAAO,CAAC,GAAG,CAAC,2EAA2E,CAAC,CAAC;IACzF,OAAO,CAAC,GAAG,CAAC,oFAAoF,CAAC,CAAC;IAClG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,2BAA2B,KAAK,EAAE,CAAC,CAAC;IACvD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC;IAC1D,OAAO,CAAC,GAAG,CAAC,gFAAgF,CAAC,CAAC;IAC9F,OAAO,CAAC,GAAG,CAAC,iFAAiF,CAAC,CAAC;IAC/F,OAAO,CAAC,GAAG,CAAC,sFAAsF,CAAC,CAAC;IACpG,OAAO,CAAC,GAAG,CAAC,uFAAuF,CAAC,CAAC;IACrG,OAAO,CAAC,GAAG,CAAC,qFAAqF,CAAC,CAAC;IACnG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,6CAA6C,CAAC,CAAC;IAC3D,OAAO,CAAC,GAAG,CAAC,sFAAsF,CAAC,CAAC;IACpG,OAAO,CAAC,GAAG,CAAC,kFAAkF,CAAC,CAAC;IAChG,OAAO,CAAC,GAAG,CAAC,yEAAyE,CAAC,CAAC;IACvF,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,kBAAkB,KAAK,EAAE,CAAC,CAAC;IAC/C,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,wBAAwB,KAAK,uCAAuC,CAAC,CAAC;IAC5F,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,iCAAiC,KAAK,6BAA6B,CAAC,CAAC;IAC3F,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC7B,CAAC","sourcesContent":["import type { ExecutorContext } from '@nx/devkit';\n\nexport interface HelpExecutorOptions {}\n\nexport default async function helpExecutor(\n options: HelpExecutorOptions,\n context: ExecutorContext\n): Promise<{ success: true }> {\n // ANSI color codes\n const GREEN = '\\x1b[32m\\x1b[1m';\n const BOLD = '\\x1b[1m';\n const RESET = '\\x1b[0m';\n\n console.log('');\n console.log(`${GREEN}💡 @webpieces/dev-config - Available Commands${RESET}`);\n console.log('');\n console.log(`${BOLD}📝 Available npm scripts (convenient shortcuts):${RESET}`);\n console.log('');\n console.log(' Architecture graph:');\n console.log(' npm run arch:generate # Generate dependency graph');\n console.log(' npm run arch:visualize # Visualize dependency graph');\n console.log('');\n console.log(' Validation:');\n console.log(' npm run arch:validate # Quick validation (no-cycles + no-skiplevel-deps)');\n console.log(' npm run arch:validate-all # Full arch validation (+ unchanged check)');\n console.log(' npm run arch:check-circular # Check all projects for circular deps');\n console.log(' npm run arch:check-circular-affected # Check affected projects only');\n console.log(' npm run arch:validate-complete # Complete validation (arch + circular)');\n console.log('');\n console.log(`${BOLD}📝 Available Nx targets:${RESET}`);\n console.log('');\n console.log(' Workspace-level architecture validation:');\n console.log(' nx run .:arch:generate # Generate dependency graph');\n console.log(' nx run .:arch:visualize # Visualize dependency graph');\n console.log(' nx run .:arch:validate-no-cycles # Check for circular dependencies');\n console.log(' nx run .:arch:validate-no-skiplevel-deps # Check for redundant dependencies');\n console.log(' nx run .:arch:validate-architecture-unchanged # Validate against blessed graph');\n console.log('');\n console.log(' Per-project circular dependency checking:');\n console.log(' nx run <project>:check-circular-deps # Check project for circular deps');\n console.log(' nx affected --target=check-circular-deps # Check all affected projects');\n console.log(' nx run-many --target=check-circular-deps --all # Check all projects');\n console.log('');\n console.log(`${GREEN}💡 Quick start:${RESET}`);\n console.log(` ${BOLD}npm run arch:generate${RESET} # Generate the graph first`);\n console.log(` ${BOLD}npm run arch:validate-complete${RESET} # Run complete validation`);\n console.log('');\n\n return { success: true };\n}\n"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ExecutorContext } from '@nx/devkit';
|
|
2
|
+
|
|
3
|
+
export interface HelpExecutorOptions {}
|
|
4
|
+
|
|
5
|
+
export default async function helpExecutor(
|
|
6
|
+
options: HelpExecutorOptions,
|
|
7
|
+
context: ExecutorContext
|
|
8
|
+
): Promise<{ success: true }> {
|
|
9
|
+
// ANSI color codes
|
|
10
|
+
const GREEN = '\x1b[32m\x1b[1m';
|
|
11
|
+
const BOLD = '\x1b[1m';
|
|
12
|
+
const RESET = '\x1b[0m';
|
|
13
|
+
|
|
14
|
+
console.log('');
|
|
15
|
+
console.log(`${GREEN}💡 @webpieces/dev-config - Available Commands${RESET}`);
|
|
16
|
+
console.log('');
|
|
17
|
+
console.log(`${BOLD}📝 Available npm scripts (convenient shortcuts):${RESET}`);
|
|
18
|
+
console.log('');
|
|
19
|
+
console.log(' Architecture graph:');
|
|
20
|
+
console.log(' npm run arch:generate # Generate dependency graph');
|
|
21
|
+
console.log(' npm run arch:visualize # Visualize dependency graph');
|
|
22
|
+
console.log('');
|
|
23
|
+
console.log(' Validation:');
|
|
24
|
+
console.log(' npm run arch:validate # Quick validation (no-cycles + no-skiplevel-deps)');
|
|
25
|
+
console.log(' npm run arch:validate-all # Full arch validation (+ unchanged check)');
|
|
26
|
+
console.log(' npm run arch:check-circular # Check all projects for circular deps');
|
|
27
|
+
console.log(' npm run arch:check-circular-affected # Check affected projects only');
|
|
28
|
+
console.log(' npm run arch:validate-complete # Complete validation (arch + circular)');
|
|
29
|
+
console.log('');
|
|
30
|
+
console.log(`${BOLD}📝 Available Nx targets:${RESET}`);
|
|
31
|
+
console.log('');
|
|
32
|
+
console.log(' Workspace-level architecture validation:');
|
|
33
|
+
console.log(' nx run .:arch:generate # Generate dependency graph');
|
|
34
|
+
console.log(' nx run .:arch:visualize # Visualize dependency graph');
|
|
35
|
+
console.log(' nx run .:arch:validate-no-cycles # Check for circular dependencies');
|
|
36
|
+
console.log(' nx run .:arch:validate-no-skiplevel-deps # Check for redundant dependencies');
|
|
37
|
+
console.log(' nx run .:arch:validate-architecture-unchanged # Validate against blessed graph');
|
|
38
|
+
console.log('');
|
|
39
|
+
console.log(' Per-project circular dependency checking:');
|
|
40
|
+
console.log(' nx run <project>:check-circular-deps # Check project for circular deps');
|
|
41
|
+
console.log(' nx affected --target=check-circular-deps # Check all affected projects');
|
|
42
|
+
console.log(' nx run-many --target=check-circular-deps --all # Check all projects');
|
|
43
|
+
console.log('');
|
|
44
|
+
console.log(`${GREEN}💡 Quick start:${RESET}`);
|
|
45
|
+
console.log(` ${BOLD}npm run arch:generate${RESET} # Generate the graph first`);
|
|
46
|
+
console.log(` ${BOLD}npm run arch:validate-complete${RESET} # Run complete validation`);
|
|
47
|
+
console.log('');
|
|
48
|
+
|
|
49
|
+
return { success: true };
|
|
50
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = validateEslintSyncExecutor;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const crypto_1 = require("crypto");
|
|
7
|
+
function calculateHash(content) {
|
|
8
|
+
return (0, crypto_1.createHash)('sha256').update(content).digest('hex');
|
|
9
|
+
}
|
|
10
|
+
function normalizeContent(content) {
|
|
11
|
+
// Normalize line endings and trim whitespace
|
|
12
|
+
return content.replace(/\r\n/g, '\n').trim();
|
|
13
|
+
}
|
|
14
|
+
async function validateEslintSyncExecutor(options, context) {
|
|
15
|
+
const workspaceRoot = context.root;
|
|
16
|
+
const templatePath = (0, path_1.join)(workspaceRoot, 'packages/tooling/dev-config/templates/eslint.webpieces.config.mjs');
|
|
17
|
+
const workspacePath = (0, path_1.join)(workspaceRoot, 'eslint.webpieces.config.mjs');
|
|
18
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
19
|
+
try {
|
|
20
|
+
const templateContent = (0, fs_1.readFileSync)(templatePath, 'utf-8');
|
|
21
|
+
const workspaceContent = (0, fs_1.readFileSync)(workspacePath, 'utf-8');
|
|
22
|
+
const templateRules = extractRulesSection(templateContent);
|
|
23
|
+
const workspaceRules = extractRulesSection(workspaceContent);
|
|
24
|
+
const templateHash = calculateHash(normalizeContent(templateRules));
|
|
25
|
+
const workspaceHash = calculateHash(normalizeContent(workspaceRules));
|
|
26
|
+
if (templateHash !== workspaceHash) {
|
|
27
|
+
printValidationError(templatePath, workspacePath);
|
|
28
|
+
return { success: false };
|
|
29
|
+
}
|
|
30
|
+
console.log('✅ ESLint configuration sync validated - rules match!');
|
|
31
|
+
return { success: true };
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
// Error occurred during validation - log and fail
|
|
35
|
+
// eslint-disable-next-line @webpieces/catch-error-pattern
|
|
36
|
+
console.error('❌ Error validating ESLint sync:', err);
|
|
37
|
+
return { success: false };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function printValidationError(templatePath, workspacePath) {
|
|
41
|
+
console.error('');
|
|
42
|
+
console.error('❌ ESLint configuration sync validation FAILED');
|
|
43
|
+
console.error('');
|
|
44
|
+
console.error('The @webpieces ESLint rules must be identical in both files:');
|
|
45
|
+
console.error(` 1. ${templatePath}`);
|
|
46
|
+
console.error(` 2. ${workspacePath}`);
|
|
47
|
+
console.error('');
|
|
48
|
+
console.error('These files must have identical rules sections so that:');
|
|
49
|
+
console.error(' - External clients get the same rules we use internally');
|
|
50
|
+
console.error(' - We "eat our own dog food" - same rules for everyone');
|
|
51
|
+
console.error('');
|
|
52
|
+
console.error('To fix:');
|
|
53
|
+
console.error(' 1. Modify the rules in ONE file (recommend: templates/eslint.webpieces.config.mjs)');
|
|
54
|
+
console.error(' 2. Copy the rules section to the other file');
|
|
55
|
+
console.error(' 3. Keep import statements different (template uses npm, workspace uses loadWorkspaceRules)');
|
|
56
|
+
console.error('');
|
|
57
|
+
console.error('Customization for webpieces workspace goes in: eslint.config.mjs');
|
|
58
|
+
console.error('');
|
|
59
|
+
}
|
|
60
|
+
function extractRulesSection(content) {
|
|
61
|
+
// Extract everything between "export default [" and the final "];"
|
|
62
|
+
// This includes the rules configuration
|
|
63
|
+
const match = content.match(/export default \[([\s\S]*)\];/);
|
|
64
|
+
if (!match) {
|
|
65
|
+
throw new Error('Could not extract rules section - export default not found');
|
|
66
|
+
}
|
|
67
|
+
return match[1].trim();
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../packages/tooling/dev-config/executors/validate-eslint-sync/executor.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ExecutorContext } from '@nx/devkit';\nimport { readFileSync } from 'fs';\nimport { join } from 'path';\nimport { createHash } from 'crypto';\n\nexport interface ValidateEslintSyncOptions {}\n\nfunction calculateHash(content: string): string {\n return createHash('sha256').update(content).digest('hex');\n}\n\nfunction normalizeContent(content: string): string {\n // Normalize line endings and trim whitespace\n return content.replace(/\\r\\n/g, '\\n').trim();\n}\n\nexport default async function validateEslintSyncExecutor(\n options: ValidateEslintSyncOptions,\n context: ExecutorContext\n): Promise<{ success: boolean }> {\n const workspaceRoot = context.root;\n\n const templatePath = join(workspaceRoot, 'packages/tooling/dev-config/templates/eslint.webpieces.config.mjs');\n const workspacePath = join(workspaceRoot, 'eslint.webpieces.config.mjs');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const templateContent = readFileSync(templatePath, 'utf-8');\n const workspaceContent = readFileSync(workspacePath, 'utf-8');\n\n const templateRules = extractRulesSection(templateContent);\n const workspaceRules = extractRulesSection(workspaceContent);\n\n const templateHash = calculateHash(normalizeContent(templateRules));\n const workspaceHash = calculateHash(normalizeContent(workspaceRules));\n\n if (templateHash !== workspaceHash) {\n printValidationError(templatePath, workspacePath);\n return { success: false };\n }\n\n console.log('✅ ESLint configuration sync validated - rules match!');\n return { success: true };\n\n } catch (err: any) {\n // Error occurred during validation - log and fail\n // eslint-disable-next-line @webpieces/catch-error-pattern\n console.error('❌ Error validating ESLint sync:', err);\n return { success: false };\n }\n}\n\nfunction printValidationError(templatePath: string, workspacePath: string): void {\n console.error('');\n console.error('❌ ESLint configuration sync validation FAILED');\n console.error('');\n console.error('The @webpieces ESLint rules must be identical in both files:');\n console.error(` 1. ${templatePath}`);\n console.error(` 2. ${workspacePath}`);\n console.error('');\n console.error('These files must have identical rules sections so that:');\n console.error(' - External clients get the same rules we use internally');\n console.error(' - We \"eat our own dog food\" - same rules for everyone');\n console.error('');\n console.error('To fix:');\n console.error(' 1. Modify the rules in ONE file (recommend: templates/eslint.webpieces.config.mjs)');\n console.error(' 2. Copy the rules section to the other file');\n console.error(' 3. Keep import statements different (template uses npm, workspace uses loadWorkspaceRules)');\n console.error('');\n console.error('Customization for webpieces workspace goes in: eslint.config.mjs');\n console.error('');\n}\n\nfunction extractRulesSection(content: string): string {\n // Extract everything between \"export default [\" and the final \"];\"\n // This includes the rules configuration\n const match = content.match(/export default \\[([\\s\\S]*)\\];/);\n if (!match) {\n throw new Error('Could not extract rules section - export default not found');\n }\n\n return match[1].trim();\n}\n"]}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { ExecutorContext } from '@nx/devkit';
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { createHash } from 'crypto';
|
|
5
|
+
|
|
6
|
+
export interface ValidateEslintSyncOptions {}
|
|
7
|
+
|
|
8
|
+
function calculateHash(content: string): string {
|
|
9
|
+
return createHash('sha256').update(content).digest('hex');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function normalizeContent(content: string): string {
|
|
13
|
+
// Normalize line endings and trim whitespace
|
|
14
|
+
return content.replace(/\r\n/g, '\n').trim();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default async function validateEslintSyncExecutor(
|
|
18
|
+
options: ValidateEslintSyncOptions,
|
|
19
|
+
context: ExecutorContext
|
|
20
|
+
): Promise<{ success: boolean }> {
|
|
21
|
+
const workspaceRoot = context.root;
|
|
22
|
+
|
|
23
|
+
const templatePath = join(workspaceRoot, 'packages/tooling/dev-config/templates/eslint.webpieces.config.mjs');
|
|
24
|
+
const workspacePath = join(workspaceRoot, 'eslint.webpieces.config.mjs');
|
|
25
|
+
|
|
26
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
27
|
+
try {
|
|
28
|
+
const templateContent = readFileSync(templatePath, 'utf-8');
|
|
29
|
+
const workspaceContent = readFileSync(workspacePath, 'utf-8');
|
|
30
|
+
|
|
31
|
+
const templateRules = extractRulesSection(templateContent);
|
|
32
|
+
const workspaceRules = extractRulesSection(workspaceContent);
|
|
33
|
+
|
|
34
|
+
const templateHash = calculateHash(normalizeContent(templateRules));
|
|
35
|
+
const workspaceHash = calculateHash(normalizeContent(workspaceRules));
|
|
36
|
+
|
|
37
|
+
if (templateHash !== workspaceHash) {
|
|
38
|
+
printValidationError(templatePath, workspacePath);
|
|
39
|
+
return { success: false };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log('✅ ESLint configuration sync validated - rules match!');
|
|
43
|
+
return { success: true };
|
|
44
|
+
|
|
45
|
+
} catch (err: any) {
|
|
46
|
+
// Error occurred during validation - log and fail
|
|
47
|
+
// eslint-disable-next-line @webpieces/catch-error-pattern
|
|
48
|
+
console.error('❌ Error validating ESLint sync:', err);
|
|
49
|
+
return { success: false };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function printValidationError(templatePath: string, workspacePath: string): void {
|
|
54
|
+
console.error('');
|
|
55
|
+
console.error('❌ ESLint configuration sync validation FAILED');
|
|
56
|
+
console.error('');
|
|
57
|
+
console.error('The @webpieces ESLint rules must be identical in both files:');
|
|
58
|
+
console.error(` 1. ${templatePath}`);
|
|
59
|
+
console.error(` 2. ${workspacePath}`);
|
|
60
|
+
console.error('');
|
|
61
|
+
console.error('These files must have identical rules sections so that:');
|
|
62
|
+
console.error(' - External clients get the same rules we use internally');
|
|
63
|
+
console.error(' - We "eat our own dog food" - same rules for everyone');
|
|
64
|
+
console.error('');
|
|
65
|
+
console.error('To fix:');
|
|
66
|
+
console.error(' 1. Modify the rules in ONE file (recommend: templates/eslint.webpieces.config.mjs)');
|
|
67
|
+
console.error(' 2. Copy the rules section to the other file');
|
|
68
|
+
console.error(' 3. Keep import statements different (template uses npm, workspace uses loadWorkspaceRules)');
|
|
69
|
+
console.error('');
|
|
70
|
+
console.error('Customization for webpieces workspace goes in: eslint.config.mjs');
|
|
71
|
+
console.error('');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function extractRulesSection(content: string): string {
|
|
75
|
+
// Extract everything between "export default [" and the final "];"
|
|
76
|
+
// This includes the rules configuration
|
|
77
|
+
const match = content.match(/export default \[([\s\S]*)\];/);
|
|
78
|
+
if (!match) {
|
|
79
|
+
throw new Error('Could not extract rules section - export default not found');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return match[1].trim();
|
|
83
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/dev-config",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.28",
|
|
4
4
|
"description": "Development configuration, scripts, and patterns for WebPieces projects",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"plugin/**/*",
|
|
36
36
|
"plugin.js",
|
|
37
37
|
"templates/**/*",
|
|
38
|
+
"executors/**/*",
|
|
38
39
|
"src/**/*",
|
|
39
40
|
"generators.json",
|
|
40
41
|
"architecture/**/*",
|
package/plugin/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
/**
|
|
5
|
+
* Re-export plugin from parent directory for clean imports
|
|
6
|
+
*/
|
|
7
|
+
tslib_1.__exportStar(require("../plugin"), exports);
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/dev-config/plugin/index.ts"],"names":[],"mappings":";;;AAAA;;GAEG;AACH,oDAA0B","sourcesContent":["/**\n * Re-export plugin from parent directory for clean imports\n */\nexport * from '../plugin';\n"]}
|
package/plugin.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Unified Nx Inference Plugin for @webpieces/dev-config
|
|
4
|
+
*
|
|
5
|
+
* This plugin automatically creates targets for:
|
|
6
|
+
* 1. Workspace-level architecture validation (generate, visualize, validate-*)
|
|
7
|
+
* 2. Per-project circular dependency checking
|
|
8
|
+
*
|
|
9
|
+
* Install with: nx add @webpieces/dev-config
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* Add to nx.json plugins array:
|
|
13
|
+
* {
|
|
14
|
+
* "plugins": ["@webpieces/dev-config"]
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* Then all targets appear automatically without manual project.json configuration.
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.createNodesV2 = void 0;
|
|
21
|
+
const path_1 = require("path");
|
|
22
|
+
const fs_1 = require("fs");
|
|
23
|
+
const DEFAULT_OPTIONS = {
|
|
24
|
+
circularDeps: {
|
|
25
|
+
enabled: true,
|
|
26
|
+
targetName: 'check-circular-deps',
|
|
27
|
+
excludePatterns: [],
|
|
28
|
+
},
|
|
29
|
+
workspace: {
|
|
30
|
+
enabled: true,
|
|
31
|
+
targetPrefix: 'arch:',
|
|
32
|
+
graphPath: 'architecture/dependencies.json',
|
|
33
|
+
validations: {
|
|
34
|
+
noCycles: true,
|
|
35
|
+
noSkipLevelDeps: true,
|
|
36
|
+
architectureUnchanged: true,
|
|
37
|
+
},
|
|
38
|
+
features: {
|
|
39
|
+
generate: true,
|
|
40
|
+
visualize: true,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
function normalizeOptions(options) {
|
|
45
|
+
const circularDeps = {
|
|
46
|
+
...DEFAULT_OPTIONS.circularDeps,
|
|
47
|
+
...options?.circularDeps,
|
|
48
|
+
};
|
|
49
|
+
const workspace = {
|
|
50
|
+
...DEFAULT_OPTIONS.workspace,
|
|
51
|
+
...options?.workspace,
|
|
52
|
+
validations: {
|
|
53
|
+
...DEFAULT_OPTIONS.workspace.validations,
|
|
54
|
+
...options?.workspace?.validations,
|
|
55
|
+
},
|
|
56
|
+
features: {
|
|
57
|
+
...DEFAULT_OPTIONS.workspace.features,
|
|
58
|
+
...options?.workspace?.features,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
return {
|
|
62
|
+
circularDeps,
|
|
63
|
+
workspace,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Nx V2 Inference Plugin
|
|
68
|
+
* Matches project.json files and creates architecture + circular-deps targets
|
|
69
|
+
*/
|
|
70
|
+
exports.createNodesV2 = [
|
|
71
|
+
// Pattern to match: look for project.json files
|
|
72
|
+
'**/project.json',
|
|
73
|
+
// Inference function
|
|
74
|
+
async (projectFiles, options, context) => {
|
|
75
|
+
const opts = normalizeOptions(options);
|
|
76
|
+
const results = [];
|
|
77
|
+
// Phase 1: Add workspace-level architecture targets to root
|
|
78
|
+
if (opts.workspace.enabled) {
|
|
79
|
+
const rootProject = projectFiles.find((f) => (0, path_1.dirname)(f) === '.');
|
|
80
|
+
if (rootProject) {
|
|
81
|
+
const workspaceTargets = createWorkspaceTargets(opts);
|
|
82
|
+
if (Object.keys(workspaceTargets).length > 0) {
|
|
83
|
+
const result = {
|
|
84
|
+
projects: {
|
|
85
|
+
'.': {
|
|
86
|
+
targets: workspaceTargets,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
results.push([rootProject, result]);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Phase 2: Add per-project circular-deps targets
|
|
95
|
+
if (opts.circularDeps.enabled) {
|
|
96
|
+
for (const projectFile of projectFiles) {
|
|
97
|
+
const projectRoot = (0, path_1.dirname)(projectFile);
|
|
98
|
+
// Skip workspace root (already handled)
|
|
99
|
+
if (projectRoot === '.')
|
|
100
|
+
continue;
|
|
101
|
+
// Check exclude patterns
|
|
102
|
+
if (isExcluded(projectRoot, opts.circularDeps.excludePatterns)) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
// Only create target if project has a src/ directory
|
|
106
|
+
const srcDir = (0, path_1.join)(context.workspaceRoot, projectRoot, 'src');
|
|
107
|
+
if ((0, fs_1.existsSync)(srcDir)) {
|
|
108
|
+
const targetName = opts.circularDeps.targetName;
|
|
109
|
+
const checkCircularDepsTarget = createCircularDepsTarget(projectRoot, targetName);
|
|
110
|
+
const result = {
|
|
111
|
+
projects: {
|
|
112
|
+
[projectRoot]: {
|
|
113
|
+
targets: {
|
|
114
|
+
[targetName]: checkCircularDepsTarget,
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
results.push([projectFile, result]);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return results;
|
|
124
|
+
},
|
|
125
|
+
];
|
|
126
|
+
/**
|
|
127
|
+
* Create workspace-level architecture validation targets
|
|
128
|
+
*/
|
|
129
|
+
function createWorkspaceTargets(opts) {
|
|
130
|
+
const targets = {};
|
|
131
|
+
const prefix = opts.workspace.targetPrefix;
|
|
132
|
+
const graphPath = opts.workspace.graphPath;
|
|
133
|
+
// Add help target (always available)
|
|
134
|
+
targets[`${prefix}help`] = createHelpTarget();
|
|
135
|
+
if (opts.workspace.features.generate) {
|
|
136
|
+
targets[`${prefix}generate`] = createGenerateTarget(graphPath);
|
|
137
|
+
}
|
|
138
|
+
if (opts.workspace.features.visualize) {
|
|
139
|
+
targets[`${prefix}visualize`] = createVisualizeTarget(prefix, graphPath);
|
|
140
|
+
}
|
|
141
|
+
if (opts.workspace.validations.noCycles) {
|
|
142
|
+
targets[`${prefix}validate-no-cycles`] = createValidateNoCyclesTarget();
|
|
143
|
+
}
|
|
144
|
+
if (opts.workspace.validations.architectureUnchanged) {
|
|
145
|
+
targets[`${prefix}validate-architecture-unchanged`] = createValidateUnchangedTarget(graphPath);
|
|
146
|
+
}
|
|
147
|
+
if (opts.workspace.validations.noSkipLevelDeps) {
|
|
148
|
+
targets[`${prefix}validate-no-skiplevel-deps`] = createValidateNoSkipLevelTarget();
|
|
149
|
+
}
|
|
150
|
+
return targets;
|
|
151
|
+
}
|
|
152
|
+
function createGenerateTarget(graphPath) {
|
|
153
|
+
return {
|
|
154
|
+
executor: '@webpieces/dev-config:generate',
|
|
155
|
+
cache: true,
|
|
156
|
+
inputs: ['default'],
|
|
157
|
+
outputs: ['{workspaceRoot}/architecture/dependencies.json'],
|
|
158
|
+
options: { graphPath },
|
|
159
|
+
metadata: {
|
|
160
|
+
technologies: ['nx'],
|
|
161
|
+
description: 'Generate the architecture dependency graph from project.json files',
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function createVisualizeTarget(prefix, graphPath) {
|
|
166
|
+
return {
|
|
167
|
+
executor: '@webpieces/dev-config:visualize',
|
|
168
|
+
dependsOn: [`${prefix}generate`],
|
|
169
|
+
options: { graphPath },
|
|
170
|
+
metadata: {
|
|
171
|
+
technologies: ['nx'],
|
|
172
|
+
description: 'Generate visual representations of the architecture graph',
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function createValidateNoCyclesTarget() {
|
|
177
|
+
return {
|
|
178
|
+
executor: '@webpieces/dev-config:validate-no-cycles',
|
|
179
|
+
cache: true,
|
|
180
|
+
inputs: ['default'],
|
|
181
|
+
metadata: {
|
|
182
|
+
technologies: ['nx'],
|
|
183
|
+
description: 'Validate the architecture has no circular dependencies',
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function createValidateUnchangedTarget(graphPath) {
|
|
188
|
+
return {
|
|
189
|
+
executor: '@webpieces/dev-config:validate-architecture-unchanged',
|
|
190
|
+
cache: true,
|
|
191
|
+
inputs: ['default', '{workspaceRoot}/architecture/dependencies.json'],
|
|
192
|
+
options: { graphPath },
|
|
193
|
+
metadata: {
|
|
194
|
+
technologies: ['nx'],
|
|
195
|
+
description: 'Validate the architecture matches the saved blessed graph',
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function createValidateNoSkipLevelTarget() {
|
|
200
|
+
return {
|
|
201
|
+
executor: '@webpieces/dev-config:validate-no-skiplevel-deps',
|
|
202
|
+
cache: true,
|
|
203
|
+
inputs: ['default'],
|
|
204
|
+
metadata: {
|
|
205
|
+
technologies: ['nx'],
|
|
206
|
+
description: 'Validate no project has redundant transitive dependencies',
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function createHelpTarget() {
|
|
211
|
+
return {
|
|
212
|
+
executor: '@webpieces/dev-config:help',
|
|
213
|
+
cache: true,
|
|
214
|
+
metadata: {
|
|
215
|
+
technologies: ['nx'],
|
|
216
|
+
description: 'Display help for @webpieces/dev-config commands and targets',
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Create per-project circular dependency checking target
|
|
222
|
+
*/
|
|
223
|
+
function createCircularDepsTarget(projectRoot, targetName) {
|
|
224
|
+
return {
|
|
225
|
+
executor: 'nx:run-commands',
|
|
226
|
+
cache: true,
|
|
227
|
+
inputs: ['default'],
|
|
228
|
+
outputs: [],
|
|
229
|
+
options: {
|
|
230
|
+
command: 'npx madge --circular --extensions ts,tsx src',
|
|
231
|
+
cwd: projectRoot,
|
|
232
|
+
},
|
|
233
|
+
metadata: {
|
|
234
|
+
technologies: ['madge'],
|
|
235
|
+
description: 'Check for circular dependencies using madge',
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Check if a project should be excluded based on patterns
|
|
241
|
+
*/
|
|
242
|
+
function isExcluded(projectRoot, excludePatterns) {
|
|
243
|
+
if (excludePatterns.length === 0) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
// Simple glob matching (could be enhanced with minimatch if needed)
|
|
247
|
+
return excludePatterns.some((pattern) => {
|
|
248
|
+
// Convert glob pattern to regex
|
|
249
|
+
const regexPattern = pattern
|
|
250
|
+
.replace(/\*\*/g, '.*') // ** matches any path
|
|
251
|
+
.replace(/\*/g, '[^/]*'); // * matches any string except /
|
|
252
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
253
|
+
return regex.test(projectRoot);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Export plugin as default for Nx
|
|
258
|
+
*/
|
|
259
|
+
exports.default = { createNodesV2: exports.createNodesV2 };
|
|
260
|
+
//# sourceMappingURL=plugin.js.map
|