@webpieces/dev-config 0.2.29 â 0.2.30
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/README.md +2 -2
- package/architecture/lib/graph-generator.d.ts +1 -1
- package/architecture/lib/graph-generator.js +4 -8
- package/architecture/lib/graph-generator.js.map +1 -1
- package/architecture/lib/graph-generator.ts +4 -9
- package/architecture/lib/package-validator.d.ts +2 -2
- package/architecture/lib/package-validator.js +35 -14
- package/architecture/lib/package-validator.js.map +1 -1
- package/architecture/lib/package-validator.ts +41 -15
- package/executors/help/executor.js +5 -5
- package/executors/help/executor.js.map +1 -1
- package/executors/help/executor.ts +5 -5
- package/package.json +1 -1
- package/plugin/README.md +6 -6
- package/plugin.js +102 -54
- package/src/generators/init/generator.js +5 -5
- package/src/generators/init/generator.js.map +1 -1
package/README.md
CHANGED
|
@@ -93,10 +93,10 @@ Automatically adds architecture validation and circular dependency checking to N
|
|
|
93
93
|
nx add @webpieces/dev-config
|
|
94
94
|
|
|
95
95
|
# Generate dependency graph
|
|
96
|
-
nx run
|
|
96
|
+
nx run architecture:generate
|
|
97
97
|
|
|
98
98
|
# Validate architecture
|
|
99
|
-
nx run
|
|
99
|
+
nx run architecture:validate-no-cycles
|
|
100
100
|
|
|
101
101
|
# Check project for circular dependencies
|
|
102
102
|
nx run my-project:check-circular-deps
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
export declare function generateRawGraph(): Promise<Record<string, string[]>>;
|
|
12
12
|
/**
|
|
13
|
-
* Transform project names
|
|
13
|
+
* Transform project names (sorting dependencies only - no scope transformation)
|
|
14
14
|
*/
|
|
15
15
|
export declare function transformGraph(rawGraph: Record<string, string[]>): Record<string, string[]>;
|
|
16
16
|
/**
|
|
@@ -62,18 +62,14 @@ async function generateRawGraph() {
|
|
|
62
62
|
return rawDeps;
|
|
63
63
|
}
|
|
64
64
|
/**
|
|
65
|
-
* Transform project names
|
|
65
|
+
* Transform project names (sorting dependencies only - no scope transformation)
|
|
66
66
|
*/
|
|
67
67
|
function transformGraph(rawGraph) {
|
|
68
68
|
const result = {};
|
|
69
69
|
for (const [projectName, deps] of Object.entries(rawGraph)) {
|
|
70
|
-
//
|
|
71
|
-
const transformedName = projectName
|
|
72
|
-
|
|
73
|
-
: `@webpieces/${projectName}`;
|
|
74
|
-
const transformedDeps = deps
|
|
75
|
-
.map((d) => (d.startsWith('@webpieces/') ? d : `@webpieces/${d}`))
|
|
76
|
-
.sort();
|
|
70
|
+
// Use project names as-is - don't force @webpieces scope on client projects
|
|
71
|
+
const transformedName = projectName;
|
|
72
|
+
const transformedDeps = deps.sort();
|
|
77
73
|
result[transformedName] = transformedDeps;
|
|
78
74
|
}
|
|
79
75
|
return result;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-generator.js","sourceRoot":"","sources":["../../../../../../packages/tooling/dev-config/architecture/lib/graph-generator.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;AAiDH,4CAiBC;AAKD,
|
|
1
|
+
{"version":3,"file":"graph-generator.js","sourceRoot":"","sources":["../../../../../../packages/tooling/dev-config/architecture/lib/graph-generator.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;AAiDH,4CAiBC;AAKD,wCAYC;AAKD,sCAGC;AAzFD,uCAIoB;AAEpB;;GAEG;AACH,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAS,EAAE,CAAC,CAAC;AAE9C;;GAEG;AACH,SAAS,wBAAwB,CAAC,aAAmC;IACjE,MAAM,IAAI,GAAa,EAAE,CAAC;IAE1B,+BAA+B;IAC/B,MAAM,WAAW,GAAG,aAAa,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC;IACrD,IAAI,WAAW,IAAI,WAAW,CAAC,SAAS,EAAE,CAAC;QACvC,KAAK,MAAM,GAAG,IAAI,WAAW,CAAC,SAAS,EAAE,CAAC;YACtC,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;gBAC1B,0DAA0D;gBAC1D,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;gBAC3C,IAAI,KAAK,EAAE,CAAC;oBACR,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;gBACxB,CAAC;YACL,CAAC;QACL,CAAC;IACL,CAAC;IAED,yCAAyC;IACzC,IAAI,aAAa,CAAC,oBAAoB,IAAI,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,oBAAoB,CAAC,EAAE,CAAC;QAC1F,KAAK,MAAM,GAAG,IAAI,aAAa,CAAC,oBAAoB,EAAE,CAAC;YACnD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBACjD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACnB,CAAC;QACL,CAAC;IACL,CAAC;IAED,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC;AACvB,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,gBAAgB;IAClC,MAAM,YAAY,GAAG,MAAM,IAAA,gCAAuB,GAAE,CAAC;IACrD,MAAM,cAAc,GAAG,IAAA,kDAAyC,EAAC,YAAY,CAAC,CAAC;IAC/E,MAAM,OAAO,GAA6B,EAAE,CAAC;IAE7C,KAAK,MAAM,CAAC,WAAW,EAAE,aAAa,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjF,0CAA0C;QAC1C,IAAI,iBAAiB,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;YACrC,SAAS;QACb,CAAC;QAED,4DAA4D;QAC5D,MAAM,IAAI,GAAG,wBAAwB,CAAC,aAAa,CAAC,CAAC;QACrD,OAAO,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC;IAChC,CAAC;IAED,OAAO,OAAO,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,SAAgB,cAAc,CAAC,QAAkC;IAC7D,MAAM,MAAM,GAA6B,EAAE,CAAC;IAE5C,KAAK,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzD,4EAA4E;QAC5E,MAAM,eAAe,GAAG,WAAW,CAAC;QACpC,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAEpC,MAAM,CAAC,eAAe,CAAC,GAAG,eAAe,CAAC;IAC9C,CAAC;IAED,OAAO,MAAM,CAAC;AAClB,CAAC;AAED;;GAEG;AACI,KAAK,UAAU,aAAa;IAC/B,MAAM,QAAQ,GAAG,MAAM,gBAAgB,EAAE,CAAC;IAC1C,OAAO,cAAc,CAAC,QAAQ,CAAC,CAAC;AACpC,CAAC","sourcesContent":["/**\n * Graph Generator\n *\n * Generates dependency graph from project.json files in the workspace.\n * Reads build.dependsOn and implicitDependencies to determine project relationships.\n */\n\nimport {\n createProjectGraphAsync,\n readProjectsConfigurationFromProjectGraph,\n ProjectConfiguration,\n} from '@nx/devkit';\n\n/**\n * Projects to exclude from graph validation (tools, configs, etc.)\n */\nconst EXCLUDED_PROJECTS = new Set<string>([]);\n\n/**\n * Extract project dependencies from project.json's build.dependsOn and implicitDependencies\n */\nfunction extractBuildDependencies(projectConfig: ProjectConfiguration): string[] {\n const deps: string[] = [];\n\n // 1. Read from build.dependsOn\n const buildTarget = projectConfig.targets?.['build'];\n if (buildTarget && buildTarget.dependsOn) {\n for (const dep of buildTarget.dependsOn) {\n if (typeof dep === 'string') {\n // Format: \"project-name:build\" or just \"build\" (for self)\n const match = dep.match(/^([^:]+):build$/);\n if (match) {\n deps.push(match[1]);\n }\n }\n }\n }\n\n // 2. Also read from implicitDependencies\n if (projectConfig.implicitDependencies && Array.isArray(projectConfig.implicitDependencies)) {\n for (const dep of projectConfig.implicitDependencies) {\n if (typeof dep === 'string' && !deps.includes(dep)) {\n deps.push(dep);\n }\n }\n }\n\n return deps.sort();\n}\n\n/**\n * Generate raw dependency graph from project.json files\n * Returns: { projectName: [dependencyNames] }\n */\nexport async function generateRawGraph(): Promise<Record<string, string[]>> {\n const projectGraph = await createProjectGraphAsync();\n const projectsConfig = readProjectsConfigurationFromProjectGraph(projectGraph);\n const rawDeps: Record<string, string[]> = {};\n\n for (const [projectName, projectConfig] of Object.entries(projectsConfig.projects)) {\n // Skip excluded projects (tools, plugins)\n if (EXCLUDED_PROJECTS.has(projectName)) {\n continue;\n }\n\n // Extract dependencies from build.dependsOn in project.json\n const deps = extractBuildDependencies(projectConfig);\n rawDeps[projectName] = deps;\n }\n\n return rawDeps;\n}\n\n/**\n * Transform project names (sorting dependencies only - no scope transformation)\n */\nexport function transformGraph(rawGraph: Record<string, string[]>): Record<string, string[]> {\n const result: Record<string, string[]> = {};\n\n for (const [projectName, deps] of Object.entries(rawGraph)) {\n // Use project names as-is - don't force @webpieces scope on client projects\n const transformedName = projectName;\n const transformedDeps = deps.sort();\n\n result[transformedName] = transformedDeps;\n }\n\n return result;\n}\n\n/**\n * Generate complete dependency graph with transformations\n */\nexport async function generateGraph(): Promise<Record<string, string[]>> {\n const rawGraph = await generateRawGraph();\n return transformGraph(rawGraph);\n}\n"]}
|
|
@@ -72,20 +72,15 @@ export async function generateRawGraph(): Promise<Record<string, string[]>> {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
* Transform project names
|
|
75
|
+
* Transform project names (sorting dependencies only - no scope transformation)
|
|
76
76
|
*/
|
|
77
77
|
export function transformGraph(rawGraph: Record<string, string[]>): Record<string, string[]> {
|
|
78
78
|
const result: Record<string, string[]> = {};
|
|
79
79
|
|
|
80
80
|
for (const [projectName, deps] of Object.entries(rawGraph)) {
|
|
81
|
-
//
|
|
82
|
-
const transformedName = projectName
|
|
83
|
-
|
|
84
|
-
: `@webpieces/${projectName}`;
|
|
85
|
-
|
|
86
|
-
const transformedDeps = deps
|
|
87
|
-
.map((d) => (d.startsWith('@webpieces/') ? d : `@webpieces/${d}`))
|
|
88
|
-
.sort();
|
|
81
|
+
// Use project names as-is - don't force @webpieces scope on client projects
|
|
82
|
+
const transformedName = projectName;
|
|
83
|
+
const transformedDeps = deps.sort();
|
|
89
84
|
|
|
90
85
|
result[transformedName] = transformedDeps;
|
|
91
86
|
}
|
|
@@ -26,9 +26,9 @@ export interface ValidationResult {
|
|
|
26
26
|
*
|
|
27
27
|
* For each project in the graph:
|
|
28
28
|
* - Check that all graph dependencies exist in package.json
|
|
29
|
-
* -
|
|
29
|
+
* - Maps project names to package names for accurate comparison
|
|
30
30
|
*
|
|
31
|
-
* @param graph - Enhanced graph with project dependencies
|
|
31
|
+
* @param graph - Enhanced graph with project dependencies (uses project names)
|
|
32
32
|
* @param workspaceRoot - Absolute path to workspace root
|
|
33
33
|
* @returns Validation result with errors if any
|
|
34
34
|
*/
|
|
@@ -23,11 +23,11 @@ function readPackageJsonDeps(workspaceRoot, projectRoot) {
|
|
|
23
23
|
try {
|
|
24
24
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
25
25
|
const deps = [];
|
|
26
|
-
// Collect
|
|
26
|
+
// Collect ALL dependencies from package.json
|
|
27
27
|
for (const depType of ['dependencies', 'peerDependencies']) {
|
|
28
28
|
const depObj = packageJson[depType] || {};
|
|
29
29
|
for (const depName of Object.keys(depObj)) {
|
|
30
|
-
if (
|
|
30
|
+
if (!deps.includes(depName)) {
|
|
31
31
|
deps.push(depName);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
@@ -39,42 +39,63 @@ function readPackageJsonDeps(workspaceRoot, projectRoot) {
|
|
|
39
39
|
return [];
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Build map of project names to their package names
|
|
44
|
+
* e.g., "core-util" â "@webpieces/core-util"
|
|
45
|
+
*/
|
|
46
|
+
function buildProjectToPackageMap(workspaceRoot, projectsConfig) {
|
|
47
|
+
const map = new Map();
|
|
48
|
+
for (const [projectName, config] of Object.entries(projectsConfig.projects)) {
|
|
49
|
+
const packageJsonPath = path.join(workspaceRoot, config.root, 'package.json');
|
|
50
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
51
|
+
try {
|
|
52
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
53
|
+
if (packageJson.name) {
|
|
54
|
+
map.set(projectName, packageJson.name);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Ignore parse errors
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return map;
|
|
63
|
+
}
|
|
42
64
|
/**
|
|
43
65
|
* Validate that package.json dependencies match the dependency graph
|
|
44
66
|
*
|
|
45
67
|
* For each project in the graph:
|
|
46
68
|
* - Check that all graph dependencies exist in package.json
|
|
47
|
-
* -
|
|
69
|
+
* - Maps project names to package names for accurate comparison
|
|
48
70
|
*
|
|
49
|
-
* @param graph - Enhanced graph with project dependencies
|
|
71
|
+
* @param graph - Enhanced graph with project dependencies (uses project names)
|
|
50
72
|
* @param workspaceRoot - Absolute path to workspace root
|
|
51
73
|
* @returns Validation result with errors if any
|
|
52
74
|
*/
|
|
53
75
|
async function validatePackageJsonDependencies(graph, workspaceRoot) {
|
|
54
76
|
const projectGraph = await (0, devkit_1.createProjectGraphAsync)();
|
|
55
77
|
const projectsConfig = (0, devkit_1.readProjectsConfigurationFromProjectGraph)(projectGraph);
|
|
78
|
+
// Build map: project name â package name
|
|
79
|
+
const projectToPackage = buildProjectToPackageMap(workspaceRoot, projectsConfig);
|
|
56
80
|
const errors = [];
|
|
57
81
|
const projectResults = [];
|
|
58
82
|
for (const [projectName, entry] of Object.entries(graph)) {
|
|
59
|
-
//
|
|
60
|
-
const
|
|
61
|
-
// Find the project config
|
|
62
|
-
const projectConfig = projectsConfig.projects[baseName];
|
|
83
|
+
// Find the project config using project name directly
|
|
84
|
+
const projectConfig = projectsConfig.projects[projectName];
|
|
63
85
|
if (!projectConfig) {
|
|
64
|
-
// Project not found in Nx config, skip
|
|
65
86
|
continue;
|
|
66
87
|
}
|
|
67
88
|
const projectRoot = projectConfig.root;
|
|
68
89
|
const packageJsonDeps = readPackageJsonDeps(workspaceRoot, projectRoot);
|
|
69
|
-
// Skip projects without package.json (common for apps in monorepo)
|
|
70
90
|
if (packageJsonDeps === null) {
|
|
71
91
|
continue;
|
|
72
92
|
}
|
|
73
|
-
//
|
|
93
|
+
// Convert graph dependencies (project names) to package names for comparison
|
|
74
94
|
const missingInPackageJson = [];
|
|
75
|
-
for (const
|
|
76
|
-
|
|
77
|
-
|
|
95
|
+
for (const depProjectName of entry.dependsOn) {
|
|
96
|
+
const depPackageName = projectToPackage.get(depProjectName) || depProjectName;
|
|
97
|
+
if (!packageJsonDeps.includes(depPackageName)) {
|
|
98
|
+
missingInPackageJson.push(depProjectName);
|
|
78
99
|
}
|
|
79
100
|
}
|
|
80
101
|
// Check for extra dependencies in package.json (not critical, just informational)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"package-validator.js","sourceRoot":"","sources":["../../../../../../packages/tooling/dev-config/architecture/lib/package-validator.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;
|
|
1
|
+
{"version":3,"file":"package-validator.js","sourceRoot":"","sources":["../../../../../../packages/tooling/dev-config/architecture/lib/package-validator.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Package Validator\n *\n * Validates that package.json dependencies match the project.json build.dependsOn\n * This ensures the two sources of truth don't drift apart.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport {\n createProjectGraphAsync,\n readProjectsConfigurationFromProjectGraph,\n} from '@nx/devkit';\n\n/**\n * Validation result for a single project\n */\nexport interface ProjectValidationResult {\n project: string;\n valid: boolean;\n missingInPackageJson: string[];\n extraInPackageJson: string[];\n}\n\n/**\n * Overall validation result\n */\nexport interface ValidationResult {\n valid: boolean;\n errors: string[];\n projectResults: ProjectValidationResult[];\n}\n\n/**\n * Read package.json dependencies for a project\n * Returns null if package.json doesn't exist (apps often don't have one)\n */\nfunction readPackageJsonDeps(workspaceRoot: string, projectRoot: string): string[] | null {\n const packageJsonPath = path.join(workspaceRoot, projectRoot, 'package.json');\n\n if (!fs.existsSync(packageJsonPath)) {\n return null; // No package.json - skip validation for this project\n }\n\n try {\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));\n const deps: string[] = [];\n\n // Collect ALL dependencies from package.json\n for (const depType of ['dependencies', 'peerDependencies']) {\n const depObj = packageJson[depType] || {};\n for (const depName of Object.keys(depObj)) {\n if (!deps.includes(depName)) {\n deps.push(depName);\n }\n }\n }\n\n return deps.sort();\n } catch (err: unknown) {\n console.warn(`Could not read package.json at ${packageJsonPath}`);\n return [];\n }\n}\n\n/**\n * Build map of project names to their package names\n * e.g., \"core-util\" â \"@webpieces/core-util\"\n */\nfunction buildProjectToPackageMap(\n workspaceRoot: string,\n projectsConfig: any\n): Map<string, string> {\n const map = new Map<string, string>();\n\n for (const [projectName, config] of Object.entries<any>(projectsConfig.projects)) {\n const packageJsonPath = path.join(workspaceRoot, config.root, 'package.json');\n if (fs.existsSync(packageJsonPath)) {\n try {\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));\n if (packageJson.name) {\n map.set(projectName, packageJson.name);\n }\n } catch {\n // Ignore parse errors\n }\n }\n }\n\n return map;\n}\n\n/**\n * Validate that package.json dependencies match the dependency graph\n *\n * For each project in the graph:\n * - Check that all graph dependencies exist in package.json\n * - Maps project names to package names for accurate comparison\n *\n * @param graph - Enhanced graph with project dependencies (uses project names)\n * @param workspaceRoot - Absolute path to workspace root\n * @returns Validation result with errors if any\n */\nexport async function validatePackageJsonDependencies(\n graph: Record<string, { level: number; dependsOn: string[] }>,\n workspaceRoot: string\n): Promise<ValidationResult> {\n const projectGraph = await createProjectGraphAsync();\n const projectsConfig = readProjectsConfigurationFromProjectGraph(projectGraph);\n\n // Build map: project name â package name\n const projectToPackage = buildProjectToPackageMap(workspaceRoot, projectsConfig);\n\n const errors: string[] = [];\n const projectResults: ProjectValidationResult[] = [];\n\n for (const [projectName, entry] of Object.entries(graph)) {\n // Find the project config using project name directly\n const projectConfig = projectsConfig.projects[projectName];\n if (!projectConfig) {\n continue;\n }\n\n const projectRoot = projectConfig.root;\n const packageJsonDeps = readPackageJsonDeps(workspaceRoot, projectRoot);\n\n if (packageJsonDeps === null) {\n continue;\n }\n\n // Convert graph dependencies (project names) to package names for comparison\n const missingInPackageJson: string[] = [];\n for (const depProjectName of entry.dependsOn) {\n const depPackageName = projectToPackage.get(depProjectName) || depProjectName;\n if (!packageJsonDeps.includes(depPackageName)) {\n missingInPackageJson.push(depProjectName);\n }\n }\n\n // Check for extra dependencies in package.json (not critical, just informational)\n const extraInPackageJson: string[] = [];\n for (const dep of packageJsonDeps) {\n if (!entry.dependsOn.includes(dep)) {\n extraInPackageJson.push(dep);\n }\n }\n\n const valid = missingInPackageJson.length === 0;\n\n if (!valid) {\n errors.push(\n `Project ${projectName} (${projectRoot}/package.json) is missing dependencies: ${missingInPackageJson.join(', ')}\\n` +\n ` Fix: Add these to package.json dependencies`\n );\n }\n\n projectResults.push({\n project: projectName,\n valid,\n missingInPackageJson,\n extraInPackageJson,\n });\n }\n\n return {\n valid: errors.length === 0,\n errors,\n projectResults,\n };\n}\n"]}
|
|
@@ -46,11 +46,11 @@ function readPackageJsonDeps(workspaceRoot: string, projectRoot: string): string
|
|
|
46
46
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
47
47
|
const deps: string[] = [];
|
|
48
48
|
|
|
49
|
-
// Collect
|
|
49
|
+
// Collect ALL dependencies from package.json
|
|
50
50
|
for (const depType of ['dependencies', 'peerDependencies']) {
|
|
51
51
|
const depObj = packageJson[depType] || {};
|
|
52
52
|
for (const depName of Object.keys(depObj)) {
|
|
53
|
-
if (
|
|
53
|
+
if (!deps.includes(depName)) {
|
|
54
54
|
deps.push(depName);
|
|
55
55
|
}
|
|
56
56
|
}
|
|
@@ -63,14 +63,41 @@ function readPackageJsonDeps(workspaceRoot: string, projectRoot: string): string
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Build map of project names to their package names
|
|
68
|
+
* e.g., "core-util" â "@webpieces/core-util"
|
|
69
|
+
*/
|
|
70
|
+
function buildProjectToPackageMap(
|
|
71
|
+
workspaceRoot: string,
|
|
72
|
+
projectsConfig: any
|
|
73
|
+
): Map<string, string> {
|
|
74
|
+
const map = new Map<string, string>();
|
|
75
|
+
|
|
76
|
+
for (const [projectName, config] of Object.entries<any>(projectsConfig.projects)) {
|
|
77
|
+
const packageJsonPath = path.join(workspaceRoot, config.root, 'package.json');
|
|
78
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
79
|
+
try {
|
|
80
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
81
|
+
if (packageJson.name) {
|
|
82
|
+
map.set(projectName, packageJson.name);
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
// Ignore parse errors
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return map;
|
|
91
|
+
}
|
|
92
|
+
|
|
66
93
|
/**
|
|
67
94
|
* Validate that package.json dependencies match the dependency graph
|
|
68
95
|
*
|
|
69
96
|
* For each project in the graph:
|
|
70
97
|
* - Check that all graph dependencies exist in package.json
|
|
71
|
-
* -
|
|
98
|
+
* - Maps project names to package names for accurate comparison
|
|
72
99
|
*
|
|
73
|
-
* @param graph - Enhanced graph with project dependencies
|
|
100
|
+
* @param graph - Enhanced graph with project dependencies (uses project names)
|
|
74
101
|
* @param workspaceRoot - Absolute path to workspace root
|
|
75
102
|
* @returns Validation result with errors if any
|
|
76
103
|
*/
|
|
@@ -81,33 +108,32 @@ export async function validatePackageJsonDependencies(
|
|
|
81
108
|
const projectGraph = await createProjectGraphAsync();
|
|
82
109
|
const projectsConfig = readProjectsConfigurationFromProjectGraph(projectGraph);
|
|
83
110
|
|
|
111
|
+
// Build map: project name â package name
|
|
112
|
+
const projectToPackage = buildProjectToPackageMap(workspaceRoot, projectsConfig);
|
|
113
|
+
|
|
84
114
|
const errors: string[] = [];
|
|
85
115
|
const projectResults: ProjectValidationResult[] = [];
|
|
86
116
|
|
|
87
117
|
for (const [projectName, entry] of Object.entries(graph)) {
|
|
88
|
-
//
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
// Find the project config
|
|
92
|
-
const projectConfig = projectsConfig.projects[baseName];
|
|
118
|
+
// Find the project config using project name directly
|
|
119
|
+
const projectConfig = projectsConfig.projects[projectName];
|
|
93
120
|
if (!projectConfig) {
|
|
94
|
-
// Project not found in Nx config, skip
|
|
95
121
|
continue;
|
|
96
122
|
}
|
|
97
123
|
|
|
98
124
|
const projectRoot = projectConfig.root;
|
|
99
125
|
const packageJsonDeps = readPackageJsonDeps(workspaceRoot, projectRoot);
|
|
100
126
|
|
|
101
|
-
// Skip projects without package.json (common for apps in monorepo)
|
|
102
127
|
if (packageJsonDeps === null) {
|
|
103
128
|
continue;
|
|
104
129
|
}
|
|
105
130
|
|
|
106
|
-
//
|
|
131
|
+
// Convert graph dependencies (project names) to package names for comparison
|
|
107
132
|
const missingInPackageJson: string[] = [];
|
|
108
|
-
for (const
|
|
109
|
-
|
|
110
|
-
|
|
133
|
+
for (const depProjectName of entry.dependsOn) {
|
|
134
|
+
const depPackageName = projectToPackage.get(depProjectName) || depProjectName;
|
|
135
|
+
if (!packageJsonDeps.includes(depPackageName)) {
|
|
136
|
+
missingInPackageJson.push(depProjectName);
|
|
111
137
|
}
|
|
112
138
|
}
|
|
113
139
|
|
|
@@ -25,11 +25,11 @@ async function helpExecutor(options, context) {
|
|
|
25
25
|
console.log(`${BOLD}đ Available Nx targets:${RESET}`);
|
|
26
26
|
console.log('');
|
|
27
27
|
console.log(' Workspace-level architecture validation:');
|
|
28
|
-
console.log(' nx run
|
|
29
|
-
console.log(' nx run
|
|
30
|
-
console.log(' nx run
|
|
31
|
-
console.log(' nx run
|
|
32
|
-
console.log(' nx run
|
|
28
|
+
console.log(' nx run architecture:generate # Generate dependency graph');
|
|
29
|
+
console.log(' nx run architecture:visualize # Visualize dependency graph');
|
|
30
|
+
console.log(' nx run architecture:validate-no-cycles # Check for circular dependencies');
|
|
31
|
+
console.log(' nx run architecture:validate-no-skiplevel-deps # Check for redundant dependencies');
|
|
32
|
+
console.log(' nx run architecture:validate-architecture-unchanged # Validate against blessed graph');
|
|
33
33
|
console.log('');
|
|
34
34
|
console.log(' Per-project circular dependency checking:');
|
|
35
35
|
console.log(' nx run <project>:check-circular-deps # Check project for circular deps');
|
|
@@ -1 +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,
|
|
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,sFAAsF,CAAC,CAAC;IACpG,OAAO,CAAC,GAAG,CAAC,uFAAuF,CAAC,CAAC;IACrG,OAAO,CAAC,GAAG,CAAC,4FAA4F,CAAC,CAAC;IAC1G,OAAO,CAAC,GAAG,CAAC,6FAA6F,CAAC,CAAC;IAC3G,OAAO,CAAC,GAAG,CAAC,2FAA2F,CAAC,CAAC;IACzG,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 architecture:generate # Generate dependency graph');\n console.log(' nx run architecture:visualize # Visualize dependency graph');\n console.log(' nx run architecture:validate-no-cycles # Check for circular dependencies');\n console.log(' nx run architecture:validate-no-skiplevel-deps # Check for redundant dependencies');\n console.log(' nx run architecture: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"]}
|
|
@@ -30,11 +30,11 @@ export default async function helpExecutor(
|
|
|
30
30
|
console.log(`${BOLD}đ Available Nx targets:${RESET}`);
|
|
31
31
|
console.log('');
|
|
32
32
|
console.log(' Workspace-level architecture validation:');
|
|
33
|
-
console.log(' nx run
|
|
34
|
-
console.log(' nx run
|
|
35
|
-
console.log(' nx run
|
|
36
|
-
console.log(' nx run
|
|
37
|
-
console.log(' nx run
|
|
33
|
+
console.log(' nx run architecture:generate # Generate dependency graph');
|
|
34
|
+
console.log(' nx run architecture:visualize # Visualize dependency graph');
|
|
35
|
+
console.log(' nx run architecture:validate-no-cycles # Check for circular dependencies');
|
|
36
|
+
console.log(' nx run architecture:validate-no-skiplevel-deps # Check for redundant dependencies');
|
|
37
|
+
console.log(' nx run architecture:validate-architecture-unchanged # Validate against blessed graph');
|
|
38
38
|
console.log('');
|
|
39
39
|
console.log(' Per-project circular dependency checking:');
|
|
40
40
|
console.log(' nx run <project>:check-circular-deps # Check project for circular deps');
|
package/package.json
CHANGED
package/plugin/README.md
CHANGED
|
@@ -69,19 +69,19 @@ You can also run targets directly with Nx:
|
|
|
69
69
|
|
|
70
70
|
```bash
|
|
71
71
|
# Generate the dependency graph
|
|
72
|
-
nx run
|
|
72
|
+
nx run architecture:generate
|
|
73
73
|
|
|
74
74
|
# Visualize the graph in your browser
|
|
75
|
-
nx run
|
|
75
|
+
nx run architecture:visualize
|
|
76
76
|
|
|
77
77
|
# Validate no circular dependencies
|
|
78
|
-
nx run
|
|
78
|
+
nx run architecture:validate-no-cycles
|
|
79
79
|
|
|
80
80
|
# Validate against blessed graph (for CI)
|
|
81
|
-
nx run
|
|
81
|
+
nx run architecture:validate-architecture-unchanged
|
|
82
82
|
|
|
83
83
|
# Check for redundant dependencies
|
|
84
|
-
nx run
|
|
84
|
+
nx run architecture:validate-no-skiplevel-deps
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
### Per-Project Circular Dependency Checking
|
|
@@ -114,7 +114,7 @@ Configure the plugin in `nx.json`:
|
|
|
114
114
|
},
|
|
115
115
|
"workspace": {
|
|
116
116
|
"enabled": true,
|
|
117
|
-
"targetPrefix": "
|
|
117
|
+
"targetPrefix": "architecture:",
|
|
118
118
|
"validations": {
|
|
119
119
|
"noCycles": true,
|
|
120
120
|
"noSkipLevelDeps": true,
|
package/plugin.js
CHANGED
|
@@ -63,68 +63,105 @@ function normalizeOptions(options) {
|
|
|
63
63
|
workspace,
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
|
+
async function createNodesFunction(projectFiles, options, context) {
|
|
67
|
+
const opts = normalizeOptions(options);
|
|
68
|
+
const results = [];
|
|
69
|
+
// Add workspace-level architecture targets
|
|
70
|
+
addArchitectureProject(results, projectFiles, opts, context);
|
|
71
|
+
// Add per-project circular-deps targets
|
|
72
|
+
addCircularDepsTargets(results, projectFiles, opts, context);
|
|
73
|
+
return results;
|
|
74
|
+
}
|
|
75
|
+
function addArchitectureProject(results, projectFiles, opts, context) {
|
|
76
|
+
if (!opts.workspace.enabled)
|
|
77
|
+
return;
|
|
78
|
+
const archDirPath = (0, path_1.join)(context.workspaceRoot, 'architecture');
|
|
79
|
+
if (!(0, fs_1.existsSync)(archDirPath))
|
|
80
|
+
return;
|
|
81
|
+
const workspaceTargets = createWorkspaceTargetsWithoutPrefix(opts);
|
|
82
|
+
if (Object.keys(workspaceTargets).length === 0)
|
|
83
|
+
return;
|
|
84
|
+
const result = {
|
|
85
|
+
projects: {
|
|
86
|
+
architecture: {
|
|
87
|
+
name: 'architecture',
|
|
88
|
+
root: 'architecture',
|
|
89
|
+
targets: workspaceTargets,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
const firstProjectFile = projectFiles[0];
|
|
94
|
+
if (firstProjectFile) {
|
|
95
|
+
results.push([firstProjectFile, result]);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function addCircularDepsTargets(results, projectFiles, opts, context) {
|
|
99
|
+
if (!opts.circularDeps.enabled)
|
|
100
|
+
return;
|
|
101
|
+
for (const projectFile of projectFiles) {
|
|
102
|
+
if (!projectFile.endsWith('project.json'))
|
|
103
|
+
continue;
|
|
104
|
+
const projectRoot = (0, path_1.dirname)(projectFile);
|
|
105
|
+
if (projectRoot === '.')
|
|
106
|
+
continue;
|
|
107
|
+
if (isExcluded(projectRoot, opts.circularDeps.excludePatterns))
|
|
108
|
+
continue;
|
|
109
|
+
const srcDir = (0, path_1.join)(context.workspaceRoot, projectRoot, 'src');
|
|
110
|
+
if (!(0, fs_1.existsSync)(srcDir))
|
|
111
|
+
continue;
|
|
112
|
+
const targetName = opts.circularDeps.targetName;
|
|
113
|
+
const checkCircularDepsTarget = createCircularDepsTarget(projectRoot, targetName);
|
|
114
|
+
const result = {
|
|
115
|
+
projects: {
|
|
116
|
+
[projectRoot]: {
|
|
117
|
+
targets: {
|
|
118
|
+
[targetName]: checkCircularDepsTarget,
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
results.push([projectFile, result]);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
66
126
|
/**
|
|
67
127
|
* Nx V2 Inference Plugin
|
|
68
|
-
* Matches project.json files
|
|
128
|
+
* Matches project.json files to create targets
|
|
69
129
|
*/
|
|
70
130
|
exports.createNodesV2 = [
|
|
71
|
-
// Pattern to match
|
|
131
|
+
// Pattern to match project.json files
|
|
72
132
|
'**/project.json',
|
|
73
133
|
// Inference function
|
|
74
|
-
|
|
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
|
-
},
|
|
134
|
+
createNodesFunction,
|
|
125
135
|
];
|
|
126
136
|
/**
|
|
127
|
-
* Create workspace-level architecture validation targets
|
|
137
|
+
* Create workspace-level architecture validation targets WITHOUT prefix
|
|
138
|
+
* Used for virtual 'architecture' project
|
|
139
|
+
*/
|
|
140
|
+
function createWorkspaceTargetsWithoutPrefix(opts) {
|
|
141
|
+
const targets = {};
|
|
142
|
+
const graphPath = opts.workspace.graphPath;
|
|
143
|
+
// Add help target (always available)
|
|
144
|
+
targets['help'] = createHelpTarget();
|
|
145
|
+
if (opts.workspace.features.generate) {
|
|
146
|
+
targets['generate'] = createGenerateTarget(graphPath);
|
|
147
|
+
}
|
|
148
|
+
if (opts.workspace.features.visualize) {
|
|
149
|
+
targets['visualize'] = createVisualizeTargetWithoutPrefix(graphPath);
|
|
150
|
+
}
|
|
151
|
+
if (opts.workspace.validations.noCycles) {
|
|
152
|
+
targets['validate-no-cycles'] = createValidateNoCyclesTarget();
|
|
153
|
+
}
|
|
154
|
+
if (opts.workspace.validations.architectureUnchanged) {
|
|
155
|
+
targets['validate-architecture-unchanged'] = createValidateUnchangedTarget(graphPath);
|
|
156
|
+
}
|
|
157
|
+
if (opts.workspace.validations.noSkipLevelDeps) {
|
|
158
|
+
targets['validate-no-skiplevel-deps'] = createValidateNoSkipLevelTarget();
|
|
159
|
+
}
|
|
160
|
+
return targets;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Create workspace-level architecture validation targets (DEPRECATED - keeping for backward compat)
|
|
164
|
+
* Used when root project.json exists (old style with '.' project)
|
|
128
165
|
*/
|
|
129
166
|
function createWorkspaceTargets(opts) {
|
|
130
167
|
const targets = {};
|
|
@@ -162,6 +199,17 @@ function createGenerateTarget(graphPath) {
|
|
|
162
199
|
},
|
|
163
200
|
};
|
|
164
201
|
}
|
|
202
|
+
function createVisualizeTargetWithoutPrefix(graphPath) {
|
|
203
|
+
return {
|
|
204
|
+
executor: '@webpieces/dev-config:visualize',
|
|
205
|
+
dependsOn: ['generate'],
|
|
206
|
+
options: { graphPath },
|
|
207
|
+
metadata: {
|
|
208
|
+
technologies: ['nx'],
|
|
209
|
+
description: 'Generate visual representations of the architecture graph',
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
165
213
|
function createVisualizeTarget(prefix, graphPath) {
|
|
166
214
|
return {
|
|
167
215
|
executor: '@webpieces/dev-config:visualize',
|
|
@@ -72,10 +72,10 @@ function addNpmScripts(tree) {
|
|
|
72
72
|
(0, devkit_1.updateJson)(tree, 'package.json', (pkgJson) => {
|
|
73
73
|
pkgJson.scripts = pkgJson.scripts ?? {};
|
|
74
74
|
// Add architecture validation scripts
|
|
75
|
-
pkgJson.scripts['arch:generate'] = 'nx run
|
|
76
|
-
pkgJson.scripts['arch:visualize'] = 'nx run
|
|
77
|
-
pkgJson.scripts['arch:validate'] = 'nx run
|
|
78
|
-
pkgJson.scripts['arch:validate-all'] = 'nx run
|
|
75
|
+
pkgJson.scripts['arch:generate'] = 'nx run architecture:generate';
|
|
76
|
+
pkgJson.scripts['arch:visualize'] = 'nx run architecture:visualize';
|
|
77
|
+
pkgJson.scripts['arch:validate'] = 'nx run architecture:validate-no-cycles && nx run architecture:validate-no-skiplevel-deps';
|
|
78
|
+
pkgJson.scripts['arch:validate-all'] = 'nx run architecture:validate-no-cycles && nx run architecture:validate-no-skiplevel-deps && nx run architecture:validate-architecture-unchanged';
|
|
79
79
|
// Add circular dependency checking scripts
|
|
80
80
|
pkgJson.scripts['arch:check-circular'] = 'nx run-many --target=check-circular-deps --all';
|
|
81
81
|
pkgJson.scripts['arch:check-circular-affected'] = 'nx affected --target=check-circular-deps';
|
|
@@ -188,7 +188,7 @@ function createSuccessCallback(installTask) {
|
|
|
188
188
|
console.log(` ${BOLD}npm run arch:generate${RESET} # Generate the dependency graph`);
|
|
189
189
|
console.log(` ${BOLD}npm run arch:validate-complete${RESET} # Run complete validation`);
|
|
190
190
|
console.log('');
|
|
191
|
-
console.log(`đĄ For full documentation, run: ${BOLD}nx run
|
|
191
|
+
console.log(`đĄ For full documentation, run: ${BOLD}nx run architecture:help${RESET}`);
|
|
192
192
|
console.log('');
|
|
193
193
|
};
|
|
194
194
|
}
|
|
@@ -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 * - 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 const installTask = addMadgeDependency(tree);\n createArchitectureDirectory(tree);\n addNpmScripts(tree);\n createEslintConfig(tree);\n\n if (!options.skipFormat) {\n await formatFiles(tree);\n }\n\n return createSuccessCallback(installTask);\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 nxJson.plugins.push(pluginName);\n updateNxJson(tree, nxJson);\n console.log(`â
Registered ${pluginName} plugin in nx.json`);\n } else {\n console.log(`âšī¸ ${pluginName} plugin is already registered`);\n }\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 .:arch:generate';\n pkgJson.scripts['arch:visualize'] = 'nx run .:arch:visualize';\n pkgJson.scripts['arch:validate'] = 'nx run .:arch:validate-no-cycles && nx run .:arch:validate-no-skiplevel-deps';\n pkgJson.scripts['arch:validate-all'] = 'nx run .:arch:validate-no-cycles && nx run .:arch:validate-no-skiplevel-deps && nx run .:arch:validate-architecture-unchanged';\n\n // Add circular dependency checking scripts\n pkgJson.scripts['arch:check-circular'] = 'nx run-many --target=check-circular-deps --all';\n pkgJson.scripts['arch:check-circular-affected'] = 'nx affected --target=check-circular-deps';\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 return pkgJson;\n });\n\n console.log('â
Added npm scripts for architecture validation and circular dependency checking');\n}\n\nfunction createEslintConfig(tree: Tree): void {\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 if (tree.exists(mainConfigPath)) {\n // Existing config - show them how to import\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 console.log('');\n } else {\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\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(installTask: ReturnType<typeof addDependenciesToPackageJson>) {\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 .:webpieces:help${RESET}`);\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":"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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 * - 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 const installTask = addMadgeDependency(tree);\n createArchitectureDirectory(tree);\n addNpmScripts(tree);\n createEslintConfig(tree);\n\n if (!options.skipFormat) {\n await formatFiles(tree);\n }\n\n return createSuccessCallback(installTask);\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 nxJson.plugins.push(pluginName);\n updateNxJson(tree, nxJson);\n console.log(`â
Registered ${pluginName} plugin in nx.json`);\n } else {\n console.log(`âšī¸ ${pluginName} plugin is already registered`);\n }\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-cycles && nx run architecture:validate-no-skiplevel-deps';\n pkgJson.scripts['arch:validate-all'] = 'nx run architecture:validate-no-cycles && nx run architecture:validate-no-skiplevel-deps && nx run architecture:validate-architecture-unchanged';\n\n // Add circular dependency checking scripts\n pkgJson.scripts['arch:check-circular'] = 'nx run-many --target=check-circular-deps --all';\n pkgJson.scripts['arch:check-circular-affected'] = 'nx affected --target=check-circular-deps';\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 return pkgJson;\n });\n\n console.log('â
Added npm scripts for architecture validation and circular dependency checking');\n}\n\nfunction createEslintConfig(tree: Tree): void {\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 if (tree.exists(mainConfigPath)) {\n // Existing config - show them how to import\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 console.log('');\n } else {\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\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(installTask: ReturnType<typeof addDependenciesToPackageJson>) {\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 console.log('');\n };\n}\n"]}
|