@webpieces/nx-webpieces-rules 0.3.128 → 0.3.130

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.
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "title": "Validate Nx Wiring Executor",
4
+ "description": "Validates that the webpieces validators are wired into the build via nx.json targetDefaults dependsOn, so a build cannot pass while running zero validators.",
5
+ "type": "object",
6
+ "properties": {
7
+ "requiredDeps": {
8
+ "type": "array",
9
+ "items": { "type": "string" },
10
+ "description": "Target names that each compile executor's dependsOn must include. Defaults: architecture:validate-complete, validate-no-file-import-cycles."
11
+ },
12
+ "compileExecutors": {
13
+ "type": "array",
14
+ "items": { "type": "string" },
15
+ "description": "Compile executors to require wiring on, but only when actually used in the project graph. Defaults: @nx/js:tsc, @angular/build:application."
16
+ }
17
+ },
18
+ "required": []
19
+ }
@@ -19,17 +19,24 @@ async function runExecutor(options, context) {
19
19
  console.log('\nšŸ“¦ Validating Package.json Dependencies\n');
20
20
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
21
21
  try {
22
- // Step 1: Generate current graph from project.json files
23
- console.log('šŸ“Š Generating dependency graph from project.json files...');
24
- const rawGraph = await (0, graph_generator_1.generateGraph)();
22
+ // Step 1: Build the full graph from nx, then transitively reduce it (the view)
23
+ console.log("šŸ“Š Generating dependency graph from nx's project graph...");
24
+ const reducedGraph = await (0, graph_generator_1.generateReducedGraph)();
25
25
  // Step 2: Topological sort (to get enhanced graph with levels)
26
26
  console.log('šŸ”„ Computing topological layers...');
27
- const enhancedGraph = (0, graph_sorter_1.sortGraphTopologically)(rawGraph);
27
+ const enhancedGraph = (0, graph_sorter_1.sortGraphTopologically)(reducedGraph);
28
28
  // Step 3: Validate package.json dependencies match
29
- console.log('šŸ“¦ Validating package.json dependencies match project.json...');
29
+ console.log('šŸ“¦ Validating package.json dependencies match the architecture graph...');
30
30
  const packageValidation = await (0, package_validator_1.validatePackageJsonDependencies)(enhancedGraph, workspaceRoot);
31
+ // Warnings never fail the build (e.g. runtime-only / peer deps).
32
+ if (packageValidation.warnings.length > 0) {
33
+ console.warn('\nāš ļø Package.json notices (non-fatal):');
34
+ for (const warning of packageValidation.warnings) {
35
+ console.warn(` ${warning}`);
36
+ }
37
+ }
31
38
  if (!packageValidation.valid) {
32
- console.error('āŒ Package.json validation failed!');
39
+ console.error('\nāŒ Package.json validation failed!');
33
40
  console.error('\nErrors:');
34
41
  for (const error of packageValidation.errors) {
35
42
  console.error(` ${error}`);
@@ -37,10 +44,9 @@ async function runExecutor(options, context) {
37
44
  console.error('\nTo fix:');
38
45
  console.error(' 1. Review the missing dependencies above');
39
46
  console.error(' 2. Add the missing dependencies to the respective package.json files');
40
- console.error(' 3. Ensure dependencies in package.json match build.dependsOn in project.json');
41
47
  return { success: false };
42
48
  }
43
- console.log('āœ… Package.json dependencies match project.json');
49
+ console.log('āœ… Package.json dependencies cover the architecture graph');
44
50
  // Print summary
45
51
  const validProjects = packageValidation.projectResults.filter(r => r.valid).length;
46
52
  const totalProjects = packageValidation.projectResults.length;
@@ -1 +1 @@
1
- {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-packagejson/executor.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;AAgBH,8BAmDC;AAhED,+DAA0D;AAC1D,yDAAgE;AAChE,mEAA8E;AAC9E,2CAAwC;AAUzB,KAAK,UAAU,WAAW,CACrC,OAAmC,EACnC,OAAwB;IAExB,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAEnC,OAAO,CAAC,GAAG,CAAC,6CAA6C,CAAC,CAAC;IAE3D,8DAA8D;IAC9D,IAAI,CAAC;QACD,yDAAyD;QACzD,OAAO,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAC;QACzE,MAAM,QAAQ,GAAG,MAAM,IAAA,+BAAa,GAAE,CAAC;QAEvC,+DAA+D;QAC/D,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;QAClD,MAAM,aAAa,GAAG,IAAA,qCAAsB,EAAC,QAAQ,CAAC,CAAC;QAEvD,mDAAmD;QACnD,OAAO,CAAC,GAAG,CAAC,+DAA+D,CAAC,CAAC;QAC7E,MAAM,iBAAiB,GAAG,MAAM,IAAA,mDAA+B,EAAC,aAAa,EAAE,aAAa,CAAC,CAAC;QAE9F,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;YACnD,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;YAC3B,KAAK,MAAM,KAAK,IAAI,iBAAiB,CAAC,MAAM,EAAE,CAAC;gBAC3C,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC;YAChC,CAAC;YACD,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;YAC5D,OAAO,CAAC,KAAK,CAAC,wEAAwE,CAAC,CAAC;YACxF,OAAO,CAAC,KAAK,CAAC,gFAAgF,CAAC,CAAC;YAChG,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAC9B,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,gDAAgD,CAAC,CAAC;QAE9D,gBAAgB;QAChB,MAAM,aAAa,GAAG,iBAAiB,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC;QACnF,MAAM,aAAa,GAAG,iBAAiB,CAAC,cAAc,CAAC,MAAM,CAAC;QAC9D,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;QACxC,OAAO,CAAC,GAAG,CAAC,0BAA0B,aAAa,EAAE,CAAC,CAAC;QACvD,OAAO,CAAC,GAAG,CAAC,aAAa,aAAa,EAAE,CAAC,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,eAAe,aAAa,GAAG,aAAa,EAAE,CAAC,CAAC;QAE5D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC7B,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,OAAO,CAAC,KAAK,CAAC,mCAAmC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAClE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC;AACL,CAAC","sourcesContent":["/**\n * Validate Package.json Executor\n *\n * Validates that package.json dependencies match project.json build dependencies.\n * This ensures the two sources of truth don't drift apart.\n *\n * Usage:\n * nx run architecture:validate-packagejson\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { generateGraph } from '../../lib/graph-generator';\nimport { sortGraphTopologically } from '../../lib/graph-sorter';\nimport { validatePackageJsonDependencies } from '../../lib/package-validator';\nimport { toError } from '../../toError';\n\nexport interface ValidatePackageJsonOptions {\n // No options needed for now\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nexport default async function runExecutor(\n options: ValidatePackageJsonOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const workspaceRoot = context.root;\n\n console.log('\\nšŸ“¦ Validating Package.json Dependencies\\n');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Step 1: Generate current graph from project.json files\n console.log('šŸ“Š Generating dependency graph from project.json files...');\n const rawGraph = await generateGraph();\n\n // Step 2: Topological sort (to get enhanced graph with levels)\n console.log('šŸ”„ Computing topological layers...');\n const enhancedGraph = sortGraphTopologically(rawGraph);\n\n // Step 3: Validate package.json dependencies match\n console.log('šŸ“¦ Validating package.json dependencies match project.json...');\n const packageValidation = await validatePackageJsonDependencies(enhancedGraph, workspaceRoot);\n\n if (!packageValidation.valid) {\n console.error('āŒ Package.json validation failed!');\n console.error('\\nErrors:');\n for (const error of packageValidation.errors) {\n console.error(` ${error}`);\n }\n console.error('\\nTo fix:');\n console.error(' 1. Review the missing dependencies above');\n console.error(' 2. Add the missing dependencies to the respective package.json files');\n console.error(' 3. Ensure dependencies in package.json match build.dependsOn in project.json');\n return { success: false };\n }\n\n console.log('āœ… Package.json dependencies match project.json');\n\n // Print summary\n const validProjects = packageValidation.projectResults.filter(r => r.valid).length;\n const totalProjects = packageValidation.projectResults.length;\n console.log(`\\nšŸ“ˆ Validation Summary:`);\n console.log(` Projects validated: ${totalProjects}`);\n console.log(` Valid: ${validProjects}`);\n console.log(` Invalid: ${totalProjects - validProjects}`);\n\n return { success: true };\n } catch (err: unknown) {\n const error = toError(err);\n console.error('āŒ Package.json validation failed:', error.message);\n return { success: false };\n }\n}\n"]}
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/validate-packagejson/executor.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;AAgBH,8BA0DC;AAvED,+DAAiE;AACjE,yDAAgE;AAChE,mEAA8E;AAC9E,2CAAwC;AAUzB,KAAK,UAAU,WAAW,CACrC,OAAmC,EACnC,OAAwB;IAExB,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAEnC,OAAO,CAAC,GAAG,CAAC,6CAA6C,CAAC,CAAC;IAE3D,8DAA8D;IAC9D,IAAI,CAAC;QACD,+EAA+E;QAC/E,OAAO,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAC;QACzE,MAAM,YAAY,GAAG,MAAM,IAAA,sCAAoB,GAAE,CAAC;QAElD,+DAA+D;QAC/D,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;QAClD,MAAM,aAAa,GAAG,IAAA,qCAAsB,EAAC,YAAY,CAAC,CAAC;QAE3D,mDAAmD;QACnD,OAAO,CAAC,GAAG,CAAC,yEAAyE,CAAC,CAAC;QACvF,MAAM,iBAAiB,GAAG,MAAM,IAAA,mDAA+B,EAAC,aAAa,EAAE,aAAa,CAAC,CAAC;QAE9F,iEAAiE;QACjE,IAAI,iBAAiB,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxC,OAAO,CAAC,IAAI,CAAC,yCAAyC,CAAC,CAAC;YACxD,KAAK,MAAM,OAAO,IAAI,iBAAiB,CAAC,QAAQ,EAAE,CAAC;gBAC/C,OAAO,CAAC,IAAI,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC;YACjC,CAAC;QACL,CAAC;QAED,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;YACrD,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;YAC3B,KAAK,MAAM,KAAK,IAAI,iBAAiB,CAAC,MAAM,EAAE,CAAC;gBAC3C,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC;YAChC,CAAC;YACD,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;YAC5D,OAAO,CAAC,KAAK,CAAC,wEAAwE,CAAC,CAAC;YACxF,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAC9B,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,0DAA0D,CAAC,CAAC;QAExE,gBAAgB;QAChB,MAAM,aAAa,GAAG,iBAAiB,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC;QACnF,MAAM,aAAa,GAAG,iBAAiB,CAAC,cAAc,CAAC,MAAM,CAAC;QAC9D,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;QACxC,OAAO,CAAC,GAAG,CAAC,0BAA0B,aAAa,EAAE,CAAC,CAAC;QACvD,OAAO,CAAC,GAAG,CAAC,aAAa,aAAa,EAAE,CAAC,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,eAAe,aAAa,GAAG,aAAa,EAAE,CAAC,CAAC;QAE5D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC7B,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,OAAO,CAAC,KAAK,CAAC,mCAAmC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAClE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC;AACL,CAAC","sourcesContent":["/**\n * Validate Package.json Executor\n *\n * Validates that package.json dependencies match project.json build dependencies.\n * This ensures the two sources of truth don't drift apart.\n *\n * Usage:\n * nx run architecture:validate-packagejson\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { generateReducedGraph } from '../../lib/graph-generator';\nimport { sortGraphTopologically } from '../../lib/graph-sorter';\nimport { validatePackageJsonDependencies } from '../../lib/package-validator';\nimport { toError } from '../../toError';\n\nexport interface ValidatePackageJsonOptions {\n // No options needed for now\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nexport default async function runExecutor(\n options: ValidatePackageJsonOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const workspaceRoot = context.root;\n\n console.log('\\nšŸ“¦ Validating Package.json Dependencies\\n');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Step 1: Build the full graph from nx, then transitively reduce it (the view)\n console.log(\"šŸ“Š Generating dependency graph from nx's project graph...\");\n const reducedGraph = await generateReducedGraph();\n\n // Step 2: Topological sort (to get enhanced graph with levels)\n console.log('šŸ”„ Computing topological layers...');\n const enhancedGraph = sortGraphTopologically(reducedGraph);\n\n // Step 3: Validate package.json dependencies match\n console.log('šŸ“¦ Validating package.json dependencies match the architecture graph...');\n const packageValidation = await validatePackageJsonDependencies(enhancedGraph, workspaceRoot);\n\n // Warnings never fail the build (e.g. runtime-only / peer deps).\n if (packageValidation.warnings.length > 0) {\n console.warn('\\nāš ļø Package.json notices (non-fatal):');\n for (const warning of packageValidation.warnings) {\n console.warn(` ${warning}`);\n }\n }\n\n if (!packageValidation.valid) {\n console.error('\\nāŒ Package.json validation failed!');\n console.error('\\nErrors:');\n for (const error of packageValidation.errors) {\n console.error(` ${error}`);\n }\n console.error('\\nTo fix:');\n console.error(' 1. Review the missing dependencies above');\n console.error(' 2. Add the missing dependencies to the respective package.json files');\n return { success: false };\n }\n\n console.log('āœ… Package.json dependencies cover the architecture graph');\n\n // Print summary\n const validProjects = packageValidation.projectResults.filter(r => r.valid).length;\n const totalProjects = packageValidation.projectResults.length;\n console.log(`\\nšŸ“ˆ Validation Summary:`);\n console.log(` Projects validated: ${totalProjects}`);\n console.log(` Valid: ${validProjects}`);\n console.log(` Invalid: ${totalProjects - validProjects}`);\n\n return { success: true };\n } catch (err: unknown) {\n const error = toError(err);\n console.error('āŒ Package.json validation failed:', error.message);\n return { success: false };\n }\n}\n"]}
@@ -1,19 +1,38 @@
1
1
  /**
2
2
  * Graph Generator
3
3
  *
4
- * Generates dependency graph from project.json files in the workspace.
5
- * Reads build.dependsOn and implicitDependencies to determine project relationships.
4
+ * Builds the workspace dependency graph from nx's OWN project graph
5
+ * (createProjectGraphAsync). nx already derives those edges from BOTH source
6
+ * imports AND package.json workspace deps, so there is no hand-maintained edge
7
+ * list and no separate import scan — we consume what nx already computed.
8
+ *
9
+ * - generateGraph() → the FULL graph (every workspace edge nx knows).
10
+ * This is what build order follows via `^build`.
11
+ * - generateReducedGraph() → transitive reduction of the full graph: the minimal
12
+ * edge set with identical reachability, used as the
13
+ * architecture VIEW written to dependencies.json.
6
14
  */
7
15
  /**
8
- * Generate raw dependency graph from project.json files
9
- * Returns: { projectName: [dependencyNames] }
16
+ * Build the full dependency graph from nx's project graph.
17
+ *
18
+ * nx's `projectGraph.nodes` are the workspace projects; `projectGraph.dependencies`
19
+ * holds every edge nx inferred (imports + package.json). We keep only edges whose
20
+ * target is another workspace project (dropping `npm:` externals) and drop excluded
21
+ * projects.
22
+ *
23
+ * Returns: { projectName: [workspaceDependencyNames] } (deps sorted, deduped)
10
24
  */
11
25
  export declare function generateRawGraph(): Promise<Record<string, string[]>>;
12
26
  /**
13
- * Transform project names (sorting dependencies only - no scope transformation)
27
+ * The full workspace dependency graph (every edge nx knows).
14
28
  */
15
- export declare function transformGraph(rawGraph: Record<string, string[]>): Record<string, string[]>;
29
+ export declare function generateGraph(): Promise<Record<string, string[]>>;
16
30
  /**
17
- * Generate complete dependency graph with transformations
31
+ * The transitively-reduced view of the full graph. This is the canonical
32
+ * "architecture graph" written to and validated against dependencies.json.
33
+ *
34
+ * Reduction is undefined on cycles; callers that care about cycles
35
+ * (validate-no-architecture-cycles) run on the FULL graph and throw first. For the
36
+ * view executors, the downstream topological sort also throws on any cycle.
18
37
  */
19
- export declare function generateGraph(): Promise<Record<string, string[]>>;
38
+ export declare function generateReducedGraph(): Promise<Record<string, string[]>>;
@@ -2,83 +2,78 @@
2
2
  /**
3
3
  * Graph Generator
4
4
  *
5
- * Generates dependency graph from project.json files in the workspace.
6
- * Reads build.dependsOn and implicitDependencies to determine project relationships.
5
+ * Builds the workspace dependency graph from nx's OWN project graph
6
+ * (createProjectGraphAsync). nx already derives those edges from BOTH source
7
+ * imports AND package.json workspace deps, so there is no hand-maintained edge
8
+ * list and no separate import scan — we consume what nx already computed.
9
+ *
10
+ * - generateGraph() → the FULL graph (every workspace edge nx knows).
11
+ * This is what build order follows via `^build`.
12
+ * - generateReducedGraph() → transitive reduction of the full graph: the minimal
13
+ * edge set with identical reachability, used as the
14
+ * architecture VIEW written to dependencies.json.
7
15
  */
8
16
  Object.defineProperty(exports, "__esModule", { value: true });
9
17
  exports.generateRawGraph = generateRawGraph;
10
- exports.transformGraph = transformGraph;
11
18
  exports.generateGraph = generateGraph;
19
+ exports.generateReducedGraph = generateReducedGraph;
12
20
  const devkit_1 = require("@nx/devkit");
21
+ const transitive_reduction_1 = require("./transitive-reduction");
13
22
  /**
14
23
  * Projects to exclude from graph validation (tools, configs, etc.)
15
24
  */
16
25
  const EXCLUDED_PROJECTS = new Set(['architecture']);
17
26
  /**
18
- * Extract project dependencies from project.json's build.dependsOn and implicitDependencies
19
- */
20
- function extractBuildDependencies(projectConfig) {
21
- const deps = [];
22
- // 1. Read from build.dependsOn
23
- const buildTarget = projectConfig.targets?.['build'];
24
- if (buildTarget && buildTarget.dependsOn) {
25
- for (const dep of buildTarget.dependsOn) {
26
- if (typeof dep === 'string') {
27
- // Format: "project-name:build" or just "build" (for self)
28
- const match = dep.match(/^([^:]+):build$/);
29
- if (match) {
30
- deps.push(match[1]);
31
- }
32
- }
33
- }
34
- }
35
- // 2. Also read from implicitDependencies
36
- if (projectConfig.implicitDependencies && Array.isArray(projectConfig.implicitDependencies)) {
37
- for (const dep of projectConfig.implicitDependencies) {
38
- if (typeof dep === 'string' && !deps.includes(dep)) {
39
- deps.push(dep);
40
- }
41
- }
42
- }
43
- return deps.sort();
44
- }
45
- /**
46
- * Generate raw dependency graph from project.json files
47
- * Returns: { projectName: [dependencyNames] }
27
+ * Build the full dependency graph from nx's project graph.
28
+ *
29
+ * nx's `projectGraph.nodes` are the workspace projects; `projectGraph.dependencies`
30
+ * holds every edge nx inferred (imports + package.json). We keep only edges whose
31
+ * target is another workspace project (dropping `npm:` externals) and drop excluded
32
+ * projects.
33
+ *
34
+ * Returns: { projectName: [workspaceDependencyNames] } (deps sorted, deduped)
48
35
  */
49
36
  async function generateRawGraph() {
50
37
  const projectGraph = await (0, devkit_1.createProjectGraphAsync)();
51
- const projectsConfig = (0, devkit_1.readProjectsConfigurationFromProjectGraph)(projectGraph);
38
+ const workspaceProjects = new Set(Object.keys(projectGraph.nodes));
52
39
  const rawDeps = {};
53
- for (const [projectName, projectConfig] of Object.entries(projectsConfig.projects)) {
54
- // Skip excluded projects (tools, plugins)
40
+ for (const projectName of workspaceProjects) {
55
41
  if (EXCLUDED_PROJECTS.has(projectName)) {
56
42
  continue;
57
43
  }
58
- // Extract dependencies from build.dependsOn in project.json
59
- const deps = extractBuildDependencies(projectConfig);
60
- rawDeps[projectName] = deps;
44
+ const edges = projectGraph.dependencies[projectName] ?? [];
45
+ const deps = new Set();
46
+ for (const edge of edges) {
47
+ const target = edge.target;
48
+ // Keep only workspace→workspace edges; skip self and excluded projects.
49
+ if (target === projectName)
50
+ continue;
51
+ if (!workspaceProjects.has(target))
52
+ continue; // drops npm: externals
53
+ if (EXCLUDED_PROJECTS.has(target))
54
+ continue;
55
+ deps.add(target);
56
+ }
57
+ rawDeps[projectName] = Array.from(deps).sort();
61
58
  }
62
59
  return rawDeps;
63
60
  }
64
61
  /**
65
- * Transform project names (sorting dependencies only - no scope transformation)
62
+ * The full workspace dependency graph (every edge nx knows).
66
63
  */
67
- function transformGraph(rawGraph) {
68
- const result = {};
69
- for (const [projectName, deps] of Object.entries(rawGraph)) {
70
- // Use project names as-is - don't force @webpieces scope on client projects
71
- const transformedName = projectName;
72
- const transformedDeps = deps.sort();
73
- result[transformedName] = transformedDeps;
74
- }
75
- return result;
64
+ async function generateGraph() {
65
+ return generateRawGraph();
76
66
  }
77
67
  /**
78
- * Generate complete dependency graph with transformations
68
+ * The transitively-reduced view of the full graph. This is the canonical
69
+ * "architecture graph" written to and validated against dependencies.json.
70
+ *
71
+ * Reduction is undefined on cycles; callers that care about cycles
72
+ * (validate-no-architecture-cycles) run on the FULL graph and throw first. For the
73
+ * view executors, the downstream topological sort also throws on any cycle.
79
74
  */
80
- async function generateGraph() {
81
- const rawGraph = await generateRawGraph();
82
- return transformGraph(rawGraph);
75
+ async function generateReducedGraph() {
76
+ const full = await generateGraph();
77
+ return (0, transitive_reduction_1.transitiveReduction)(full);
83
78
  }
84
79
  //# sourceMappingURL=graph-generator.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"graph-generator.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/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,CAAC,cAAc,CAAC,CAAC,CAAC;AAE5D;;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>(['architecture']);\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"]}
1
+ {"version":3,"file":"graph-generator.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-generator.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;AAoBH,4CA0BC;AAKD,sCAEC;AAUD,oDAGC;AAhED,uCAAqD;AACrD,iEAA6D;AAE7D;;GAEG;AACH,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAS,CAAC,cAAc,CAAC,CAAC,CAAC;AAE5D;;;;;;;;;GASG;AACI,KAAK,UAAU,gBAAgB;IAClC,MAAM,YAAY,GAAG,MAAM,IAAA,gCAAuB,GAAE,CAAC;IACrD,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;IAEnE,MAAM,OAAO,GAA6B,EAAE,CAAC;IAE7C,KAAK,MAAM,WAAW,IAAI,iBAAiB,EAAE,CAAC;QAC1C,IAAI,iBAAiB,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;YACrC,SAAS;QACb,CAAC;QAED,MAAM,KAAK,GAAG,YAAY,CAAC,YAAY,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;QAC3D,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACvB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;YAC3B,wEAAwE;YACxE,IAAI,MAAM,KAAK,WAAW;gBAAE,SAAS;YACrC,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,MAAM,CAAC;gBAAE,SAAS,CAAC,uBAAuB;YACrE,IAAI,iBAAiB,CAAC,GAAG,CAAC,MAAM,CAAC;gBAAE,SAAS;YAC5C,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACrB,CAAC;QAED,OAAO,CAAC,WAAW,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;IACnD,CAAC;IAED,OAAO,OAAO,CAAC;AACnB,CAAC;AAED;;GAEG;AACI,KAAK,UAAU,aAAa;IAC/B,OAAO,gBAAgB,EAAE,CAAC;AAC9B,CAAC;AAED;;;;;;;GAOG;AACI,KAAK,UAAU,oBAAoB;IACtC,MAAM,IAAI,GAAG,MAAM,aAAa,EAAE,CAAC;IACnC,OAAO,IAAA,0CAAmB,EAAC,IAAI,CAAC,CAAC;AACrC,CAAC","sourcesContent":["/**\n * Graph Generator\n *\n * Builds the workspace dependency graph from nx's OWN project graph\n * (createProjectGraphAsync). nx already derives those edges from BOTH source\n * imports AND package.json workspace deps, so there is no hand-maintained edge\n * list and no separate import scan — we consume what nx already computed.\n *\n * - generateGraph() → the FULL graph (every workspace edge nx knows).\n * This is what build order follows via `^build`.\n * - generateReducedGraph() → transitive reduction of the full graph: the minimal\n * edge set with identical reachability, used as the\n * architecture VIEW written to dependencies.json.\n */\n\nimport { createProjectGraphAsync } from '@nx/devkit';\nimport { transitiveReduction } from './transitive-reduction';\n\n/**\n * Projects to exclude from graph validation (tools, configs, etc.)\n */\nconst EXCLUDED_PROJECTS = new Set<string>(['architecture']);\n\n/**\n * Build the full dependency graph from nx's project graph.\n *\n * nx's `projectGraph.nodes` are the workspace projects; `projectGraph.dependencies`\n * holds every edge nx inferred (imports + package.json). We keep only edges whose\n * target is another workspace project (dropping `npm:` externals) and drop excluded\n * projects.\n *\n * Returns: { projectName: [workspaceDependencyNames] } (deps sorted, deduped)\n */\nexport async function generateRawGraph(): Promise<Record<string, string[]>> {\n const projectGraph = await createProjectGraphAsync();\n const workspaceProjects = new Set(Object.keys(projectGraph.nodes));\n\n const rawDeps: Record<string, string[]> = {};\n\n for (const projectName of workspaceProjects) {\n if (EXCLUDED_PROJECTS.has(projectName)) {\n continue;\n }\n\n const edges = projectGraph.dependencies[projectName] ?? [];\n const deps = new Set<string>();\n for (const edge of edges) {\n const target = edge.target;\n // Keep only workspace→workspace edges; skip self and excluded projects.\n if (target === projectName) continue;\n if (!workspaceProjects.has(target)) continue; // drops npm: externals\n if (EXCLUDED_PROJECTS.has(target)) continue;\n deps.add(target);\n }\n\n rawDeps[projectName] = Array.from(deps).sort();\n }\n\n return rawDeps;\n}\n\n/**\n * The full workspace dependency graph (every edge nx knows).\n */\nexport async function generateGraph(): Promise<Record<string, string[]>> {\n return generateRawGraph();\n}\n\n/**\n * The transitively-reduced view of the full graph. This is the canonical\n * \"architecture graph\" written to and validated against dependencies.json.\n *\n * Reduction is undefined on cycles; callers that care about cycles\n * (validate-no-architecture-cycles) run on the FULL graph and throw first. For the\n * view executors, the downstream topological sort also throws on any cycle.\n */\nexport async function generateReducedGraph(): Promise<Record<string, string[]>> {\n const full = await generateGraph();\n return transitiveReduction(full);\n}\n"]}
@@ -15,10 +15,20 @@ export interface ProjectValidationResult {
15
15
  }
16
16
  /**
17
17
  * Overall validation result
18
+ *
19
+ * `errors` fail the build; their only fix is ADDITIVE ("add to package.json"), so they can
20
+ * never push a user toward removing a runtime-required dependency.
21
+ *
22
+ * `warnings` never fail the build. Workspace deps in package.json that the architecture graph
23
+ * can't reach are reported here, NOT as errors: a transitively-reachable or even unreachable
24
+ * entry can still be a real runtime dependency (e.g. a peerDependency or a generated client
25
+ * that nx's import analysis doesn't traverse). Erroring on these is the "runtime-validity trap"
26
+ * that previously forced a bad package.json edit — so we only warn.
18
27
  */
19
28
  export interface ValidationResult {
20
29
  valid: boolean;
21
30
  errors: string[];
31
+ warnings: string[];
22
32
  projectResults: ProjectValidationResult[];
23
33
  }
24
34
  /**
@@ -132,13 +132,17 @@ function validateSingleProject(projectName, entry, projectRoot, packageJsonDeps,
132
132
  errors.push(`Project ${projectName} (${projectRoot}/package.json) is missing dependencies: ${classification.missingInPackageJson.join(', ')}\n` +
133
133
  ` Fix: Add these to package.json dependencies`);
134
134
  }
135
+ // Unreachable workspace extras are WARN-ONLY: they may be real runtime deps that nx's
136
+ // import analysis can't see (peerDependency / generated client). Never error — that is
137
+ // the runtime-validity trap. We surface them so genuine drift is still visible.
138
+ const warnings = [];
135
139
  for (const extraPkg of classification.extraWorkspaceDeps) {
136
140
  const extraProject = packageToProject.get(extraPkg);
137
- errors.push(`Project ${projectName} (${projectRoot}/package.json) has "${extraPkg}" in package.json but architecture/dependencies.json has no path ${projectName} → ${extraProject} (not even transitively).\n` +
138
- ` Fix: Either add "${extraProject}:build" to project.json:build.dependsOn (then run \`nx run architecture:generate\`), or remove "${extraPkg}" from package.json dependencies.`);
141
+ warnings.push(`Project ${projectName} (${projectRoot}/package.json) has "${extraPkg}" but the architecture graph has no path ${projectName} → ${extraProject}.\n` +
142
+ ` This is allowed (it may be a runtime-only/peer dependency). If it is genuinely unused, you may remove it.`);
139
143
  }
140
- const valid = classification.missingInPackageJson.length === 0 &&
141
- classification.extraWorkspaceDeps.length === 0;
144
+ // extraWorkspaceDeps do NOT affect validity — only missing (additive-fix) errors do.
145
+ const valid = classification.missingInPackageJson.length === 0;
142
146
  return {
143
147
  result: {
144
148
  project: projectName,
@@ -147,6 +151,7 @@ function validateSingleProject(projectName, entry, projectRoot, packageJsonDeps,
147
151
  extraInPackageJson: classification.extraInPackageJson,
148
152
  },
149
153
  errors,
154
+ warnings,
150
155
  };
151
156
  }
152
157
  async function validatePackageJsonDependencies(graph, workspaceRoot) {
@@ -158,6 +163,7 @@ async function validatePackageJsonDependencies(graph, workspaceRoot) {
158
163
  packageToProject.set(pkgName, projName);
159
164
  }
160
165
  const errors = [];
166
+ const warnings = [];
161
167
  const projectResults = [];
162
168
  for (const [projectName, entry] of Object.entries(graph)) {
163
169
  const projectConfig = projectsConfig.projects[projectName];
@@ -169,7 +175,8 @@ async function validatePackageJsonDependencies(graph, workspaceRoot) {
169
175
  const validation = validateSingleProject(projectName, entry, projectConfig.root, packageJsonDeps, graph, projectToPackage, packageToProject);
170
176
  projectResults.push(validation.result);
171
177
  errors.push(...validation.errors);
178
+ warnings.push(...validation.warnings);
172
179
  }
173
- return { valid: errors.length === 0, errors, projectResults };
180
+ return { valid: errors.length === 0, errors, warnings, projectResults };
174
181
  }
175
182
  //# sourceMappingURL=package-validator.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"package-validator.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/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';\nimport { toError } from '../toError';\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 // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\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 //const error = toError(err);\n void err;\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 // webpieces-disable no-any-unknown -- Nx devkit projectsConfig type is dynamic and not strongly typed\n projectsConfig: any\n): Map<string, string> {\n const map = new Map<string, string>();\n\n // webpieces-disable no-any-unknown -- Nx devkit projects config entries are untyped\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 // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\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 (err: unknown) {\n //const error = toError(err);\n void err;\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 */\ninterface GraphEntry {\n level: number;\n dependsOn: string[];\n}\n\ninterface DepClassification {\n missingInPackageJson: string[];\n extraInPackageJson: string[];\n extraWorkspaceDeps: string[];\n}\n\n/**\n * Compute the transitive closure of a project's dependencies in the graph.\n * Example: server → [core-meta, http-server]; transitive closure includes\n * http-server and everything http-server reaches (http-routing, http-filters,\n * core-context, core-util, http-api).\n *\n * Used to allow package.json entries for transitive deps (a legitimate pattern:\n * npm install brings the whole dependency tree, so a consumer may list any\n * reachable package directly).\n */\nfunction computeTransitiveClosure(\n projectName: string,\n graph: Record<string, GraphEntry>\n): Set<string> {\n const closure = new Set<string>();\n const stack = [projectName];\n while (stack.length > 0) {\n const current = stack.pop()!;\n const entry = graph[current];\n if (!entry) continue;\n for (const dep of entry.dependsOn) {\n if (!closure.has(dep)) {\n closure.add(dep);\n stack.push(dep);\n }\n }\n }\n return closure;\n}\n\ninterface SingleProjectValidation {\n result: ProjectValidationResult;\n errors: string[];\n}\n\nfunction classifyDeps(\n packageJsonDeps: string[],\n entry: GraphEntry,\n transitiveClosure: Set<string>,\n projectToPackage: Map<string, string>,\n packageToProject: Map<string, string>\n): DepClassification {\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 // Workspace extras are OK if reachable via transitive closure (matches the\n // ESLint enforce-architecture rule which also allows transitive imports).\n // Only flag extras that are NOT reachable at all — real graph drift.\n const extraInPackageJson: string[] = [];\n const extraWorkspaceDeps: string[] = [];\n for (const dep of packageJsonDeps) {\n const depProjectName = packageToProject.get(dep);\n if (depProjectName === undefined) {\n extraInPackageJson.push(dep);\n continue;\n }\n if (entry.dependsOn.includes(depProjectName)) continue;\n if (transitiveClosure.has(depProjectName)) continue;\n extraWorkspaceDeps.push(dep);\n }\n\n return { missingInPackageJson, extraInPackageJson, extraWorkspaceDeps };\n}\n\nfunction validateSingleProject(\n projectName: string,\n entry: GraphEntry,\n projectRoot: string,\n packageJsonDeps: string[],\n graph: Record<string, GraphEntry>,\n projectToPackage: Map<string, string>,\n packageToProject: Map<string, string>\n): SingleProjectValidation {\n const transitiveClosure = computeTransitiveClosure(projectName, graph);\n const classification = classifyDeps(\n packageJsonDeps,\n entry,\n transitiveClosure,\n projectToPackage,\n packageToProject\n );\n\n const errors: string[] = [];\n if (classification.missingInPackageJson.length > 0) {\n errors.push(\n `Project ${projectName} (${projectRoot}/package.json) is missing dependencies: ${classification.missingInPackageJson.join(', ')}\\n` +\n ` Fix: Add these to package.json dependencies`\n );\n }\n for (const extraPkg of classification.extraWorkspaceDeps) {\n const extraProject = packageToProject.get(extraPkg);\n errors.push(\n `Project ${projectName} (${projectRoot}/package.json) has \"${extraPkg}\" in package.json but architecture/dependencies.json has no path ${projectName} → ${extraProject} (not even transitively).\\n` +\n ` Fix: Either add \"${extraProject}:build\" to project.json:build.dependsOn (then run \\`nx run architecture:generate\\`), or remove \"${extraPkg}\" from package.json dependencies.`\n );\n }\n\n const valid =\n classification.missingInPackageJson.length === 0 &&\n classification.extraWorkspaceDeps.length === 0;\n return {\n result: {\n project: projectName,\n valid,\n missingInPackageJson: classification.missingInPackageJson,\n extraInPackageJson: classification.extraInPackageJson,\n },\n errors,\n };\n}\n\nexport async function validatePackageJsonDependencies(\n graph: Record<string, GraphEntry>,\n workspaceRoot: string\n): Promise<ValidationResult> {\n const projectGraph = await createProjectGraphAsync();\n const projectsConfig = readProjectsConfigurationFromProjectGraph(projectGraph);\n\n const projectToPackage = buildProjectToPackageMap(workspaceRoot, projectsConfig);\n const packageToProject = new Map<string, string>();\n for (const [projName, pkgName] of projectToPackage.entries()) {\n packageToProject.set(pkgName, projName);\n }\n\n const errors: string[] = [];\n const projectResults: ProjectValidationResult[] = [];\n\n for (const [projectName, entry] of Object.entries(graph)) {\n const projectConfig = projectsConfig.projects[projectName];\n if (!projectConfig) continue;\n\n const packageJsonDeps = readPackageJsonDeps(workspaceRoot, projectConfig.root);\n if (packageJsonDeps === null) continue;\n\n const validation = validateSingleProject(\n projectName,\n entry,\n projectConfig.root,\n packageJsonDeps,\n graph,\n projectToPackage,\n packageToProject\n );\n projectResults.push(validation.result);\n errors.push(...validation.errors);\n }\n\n return { valid: errors.length === 0, errors, projectResults };\n}\n"]}
1
+ {"version":3,"file":"package-validator.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/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';\nimport { toError } from '../toError';\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 *\n * `errors` fail the build; their only fix is ADDITIVE (\"add to package.json\"), so they can\n * never push a user toward removing a runtime-required dependency.\n *\n * `warnings` never fail the build. Workspace deps in package.json that the architecture graph\n * can't reach are reported here, NOT as errors: a transitively-reachable or even unreachable\n * entry can still be a real runtime dependency (e.g. a peerDependency or a generated client\n * that nx's import analysis doesn't traverse). Erroring on these is the \"runtime-validity trap\"\n * that previously forced a bad package.json edit — so we only warn.\n */\nexport interface ValidationResult {\n valid: boolean;\n errors: string[];\n warnings: 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 // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\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 //const error = toError(err);\n void err;\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 // webpieces-disable no-any-unknown -- Nx devkit projectsConfig type is dynamic and not strongly typed\n projectsConfig: any\n): Map<string, string> {\n const map = new Map<string, string>();\n\n // webpieces-disable no-any-unknown -- Nx devkit projects config entries are untyped\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 // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\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 (err: unknown) {\n //const error = toError(err);\n void err;\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 */\ninterface GraphEntry {\n level: number;\n dependsOn: string[];\n}\n\ninterface DepClassification {\n missingInPackageJson: string[];\n extraInPackageJson: string[];\n extraWorkspaceDeps: string[];\n}\n\n/**\n * Compute the transitive closure of a project's dependencies in the graph.\n * Example: server → [core-meta, http-server]; transitive closure includes\n * http-server and everything http-server reaches (http-routing, http-filters,\n * core-context, core-util, http-api).\n *\n * Used to allow package.json entries for transitive deps (a legitimate pattern:\n * npm install brings the whole dependency tree, so a consumer may list any\n * reachable package directly).\n */\nfunction computeTransitiveClosure(\n projectName: string,\n graph: Record<string, GraphEntry>\n): Set<string> {\n const closure = new Set<string>();\n const stack = [projectName];\n while (stack.length > 0) {\n const current = stack.pop()!;\n const entry = graph[current];\n if (!entry) continue;\n for (const dep of entry.dependsOn) {\n if (!closure.has(dep)) {\n closure.add(dep);\n stack.push(dep);\n }\n }\n }\n return closure;\n}\n\ninterface SingleProjectValidation {\n result: ProjectValidationResult;\n errors: string[];\n warnings: string[];\n}\n\nfunction classifyDeps(\n packageJsonDeps: string[],\n entry: GraphEntry,\n transitiveClosure: Set<string>,\n projectToPackage: Map<string, string>,\n packageToProject: Map<string, string>\n): DepClassification {\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 // Workspace extras are OK if reachable via transitive closure (matches the\n // ESLint enforce-architecture rule which also allows transitive imports).\n // Only flag extras that are NOT reachable at all — real graph drift.\n const extraInPackageJson: string[] = [];\n const extraWorkspaceDeps: string[] = [];\n for (const dep of packageJsonDeps) {\n const depProjectName = packageToProject.get(dep);\n if (depProjectName === undefined) {\n extraInPackageJson.push(dep);\n continue;\n }\n if (entry.dependsOn.includes(depProjectName)) continue;\n if (transitiveClosure.has(depProjectName)) continue;\n extraWorkspaceDeps.push(dep);\n }\n\n return { missingInPackageJson, extraInPackageJson, extraWorkspaceDeps };\n}\n\nfunction validateSingleProject(\n projectName: string,\n entry: GraphEntry,\n projectRoot: string,\n packageJsonDeps: string[],\n graph: Record<string, GraphEntry>,\n projectToPackage: Map<string, string>,\n packageToProject: Map<string, string>\n): SingleProjectValidation {\n const transitiveClosure = computeTransitiveClosure(projectName, graph);\n const classification = classifyDeps(\n packageJsonDeps,\n entry,\n transitiveClosure,\n projectToPackage,\n packageToProject\n );\n\n const errors: string[] = [];\n if (classification.missingInPackageJson.length > 0) {\n errors.push(\n `Project ${projectName} (${projectRoot}/package.json) is missing dependencies: ${classification.missingInPackageJson.join(', ')}\\n` +\n ` Fix: Add these to package.json dependencies`\n );\n }\n\n // Unreachable workspace extras are WARN-ONLY: they may be real runtime deps that nx's\n // import analysis can't see (peerDependency / generated client). Never error — that is\n // the runtime-validity trap. We surface them so genuine drift is still visible.\n const warnings: string[] = [];\n for (const extraPkg of classification.extraWorkspaceDeps) {\n const extraProject = packageToProject.get(extraPkg);\n warnings.push(\n `Project ${projectName} (${projectRoot}/package.json) has \"${extraPkg}\" but the architecture graph has no path ${projectName} → ${extraProject}.\\n` +\n ` This is allowed (it may be a runtime-only/peer dependency). If it is genuinely unused, you may remove it.`\n );\n }\n\n // extraWorkspaceDeps do NOT affect validity — only missing (additive-fix) errors do.\n const valid = classification.missingInPackageJson.length === 0;\n return {\n result: {\n project: projectName,\n valid,\n missingInPackageJson: classification.missingInPackageJson,\n extraInPackageJson: classification.extraInPackageJson,\n },\n errors,\n warnings,\n };\n}\n\nexport async function validatePackageJsonDependencies(\n graph: Record<string, GraphEntry>,\n workspaceRoot: string\n): Promise<ValidationResult> {\n const projectGraph = await createProjectGraphAsync();\n const projectsConfig = readProjectsConfigurationFromProjectGraph(projectGraph);\n\n const projectToPackage = buildProjectToPackageMap(workspaceRoot, projectsConfig);\n const packageToProject = new Map<string, string>();\n for (const [projName, pkgName] of projectToPackage.entries()) {\n packageToProject.set(pkgName, projName);\n }\n\n const errors: string[] = [];\n const warnings: string[] = [];\n const projectResults: ProjectValidationResult[] = [];\n\n for (const [projectName, entry] of Object.entries(graph)) {\n const projectConfig = projectsConfig.projects[projectName];\n if (!projectConfig) continue;\n\n const packageJsonDeps = readPackageJsonDeps(workspaceRoot, projectConfig.root);\n if (packageJsonDeps === null) continue;\n\n const validation = validateSingleProject(\n projectName,\n entry,\n projectConfig.root,\n packageJsonDeps,\n graph,\n projectToPackage,\n packageToProject\n );\n projectResults.push(validation.result);\n errors.push(...validation.errors);\n warnings.push(...validation.warnings);\n }\n\n return { valid: errors.length === 0, errors, warnings, projectResults };\n}\n"]}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Transitive Reduction
3
+ *
4
+ * Computes the transitive reduction of a DAG: the minimal set of edges that
5
+ * preserves the exact same reachability (transitive closure) as the input graph.
6
+ *
7
+ * For a DAG the transitive reduction is unique. An edge u → v is redundant when v
8
+ * is reachable from u through some OTHER direct child w of u (w ≠ v). Removing all
9
+ * redundant edges yields the reduced graph.
10
+ *
11
+ * This is purely a VIEW transformation for architecture/dependencies.json — it must
12
+ * never feed back into package.json or build order. Build order continues to follow
13
+ * nx's full project graph (via `^build`); reduction preserves reachability, so any
14
+ * topological order valid for the full graph is also valid for the reduced graph.
15
+ *
16
+ * The input MUST be acyclic. Reduction is undefined on cycles; callers run the
17
+ * cycle-detecting topological sort (graph-sorter) which throws on cycles first.
18
+ */
19
+ /**
20
+ * Compute the transitive reduction of a DAG.
21
+ *
22
+ * @param graph - Full DAG as { project: [directChildren] }
23
+ * @returns Reduced graph { project: [minimalDirectChildren] } (children sorted)
24
+ */
25
+ export declare function transitiveReduction(graph: Record<string, string[]>): Record<string, string[]>;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ /**
3
+ * Transitive Reduction
4
+ *
5
+ * Computes the transitive reduction of a DAG: the minimal set of edges that
6
+ * preserves the exact same reachability (transitive closure) as the input graph.
7
+ *
8
+ * For a DAG the transitive reduction is unique. An edge u → v is redundant when v
9
+ * is reachable from u through some OTHER direct child w of u (w ≠ v). Removing all
10
+ * redundant edges yields the reduced graph.
11
+ *
12
+ * This is purely a VIEW transformation for architecture/dependencies.json — it must
13
+ * never feed back into package.json or build order. Build order continues to follow
14
+ * nx's full project graph (via `^build`); reduction preserves reachability, so any
15
+ * topological order valid for the full graph is also valid for the reduced graph.
16
+ *
17
+ * The input MUST be acyclic. Reduction is undefined on cycles; callers run the
18
+ * cycle-detecting topological sort (graph-sorter) which throws on cycles first.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.transitiveReduction = transitiveReduction;
22
+ /**
23
+ * Compute the transitive reduction of a DAG.
24
+ *
25
+ * @param graph - Full DAG as { project: [directChildren] }
26
+ * @returns Reduced graph { project: [minimalDirectChildren] } (children sorted)
27
+ */
28
+ function transitiveReduction(graph) {
29
+ // Memoized reachability (transitive closure) per node.
30
+ const closure = new Map();
31
+ function reach(node) {
32
+ const cached = closure.get(node);
33
+ if (cached)
34
+ return cached;
35
+ const acc = new Set();
36
+ // Set before recursion: safe for a DAG, and guards against runaway recursion
37
+ // if the input is unexpectedly cyclic (closure stays finite).
38
+ closure.set(node, acc);
39
+ for (const child of graph[node] ?? []) {
40
+ acc.add(child);
41
+ for (const reachable of reach(child)) {
42
+ acc.add(reachable);
43
+ }
44
+ }
45
+ return acc;
46
+ }
47
+ const reduced = {};
48
+ for (const u of Object.keys(graph)) {
49
+ const children = graph[u] ?? [];
50
+ // Keep u → v only if NO sibling w (w ≠ v) already reaches v.
51
+ const kept = children.filter((v) => !children.some((w) => w !== v && reach(w).has(v)));
52
+ reduced[u] = kept.sort();
53
+ }
54
+ return reduced;
55
+ }
56
+ //# sourceMappingURL=transitive-reduction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transitive-reduction.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/transitive-reduction.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;AAQH,kDAiCC;AAvCD;;;;;GAKG;AACH,SAAgB,mBAAmB,CAC/B,KAA+B;IAE/B,uDAAuD;IACvD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAuB,CAAC;IAE/C,SAAS,KAAK,CAAC,IAAY;QACvB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;QAE1B,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;QAC9B,6EAA6E;QAC7E,8DAA8D;QAC9D,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACvB,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;YACpC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACf,KAAK,MAAM,SAAS,IAAI,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;gBACnC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YACvB,CAAC;QACL,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;IAED,MAAM,OAAO,GAA6B,EAAE,CAAC;IAC7C,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACjC,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAChC,6DAA6D;QAC7D,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CACxB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAC3D,CAAC;QACF,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC7B,CAAC;IACD,OAAO,OAAO,CAAC;AACnB,CAAC","sourcesContent":["/**\n * Transitive Reduction\n *\n * Computes the transitive reduction of a DAG: the minimal set of edges that\n * preserves the exact same reachability (transitive closure) as the input graph.\n *\n * For a DAG the transitive reduction is unique. An edge u → v is redundant when v\n * is reachable from u through some OTHER direct child w of u (w ≠ v). Removing all\n * redundant edges yields the reduced graph.\n *\n * This is purely a VIEW transformation for architecture/dependencies.json — it must\n * never feed back into package.json or build order. Build order continues to follow\n * nx's full project graph (via `^build`); reduction preserves reachability, so any\n * topological order valid for the full graph is also valid for the reduced graph.\n *\n * The input MUST be acyclic. Reduction is undefined on cycles; callers run the\n * cycle-detecting topological sort (graph-sorter) which throws on cycles first.\n */\n\n/**\n * Compute the transitive reduction of a DAG.\n *\n * @param graph - Full DAG as { project: [directChildren] }\n * @returns Reduced graph { project: [minimalDirectChildren] } (children sorted)\n */\nexport function transitiveReduction(\n graph: Record<string, string[]>\n): Record<string, string[]> {\n // Memoized reachability (transitive closure) per node.\n const closure = new Map<string, Set<string>>();\n\n function reach(node: string): Set<string> {\n const cached = closure.get(node);\n if (cached) return cached;\n\n const acc = new Set<string>();\n // Set before recursion: safe for a DAG, and guards against runaway recursion\n // if the input is unexpectedly cyclic (closure stays finite).\n closure.set(node, acc);\n for (const child of graph[node] ?? []) {\n acc.add(child);\n for (const reachable of reach(child)) {\n acc.add(reachable);\n }\n }\n return acc;\n }\n\n const reduced: Record<string, string[]> = {};\n for (const u of Object.keys(graph)) {\n const children = graph[u] ?? [];\n // Keep u → v only if NO sibling w (w ≠ v) already reaches v.\n const kept = children.filter(\n (v) => !children.some((w) => w !== v && reach(w).has(v))\n );\n reduced[u] = kept.sort();\n }\n return reduced;\n}\n"]}
package/src/plugin.d.ts CHANGED
@@ -37,6 +37,7 @@ export interface ValidationOptions {
37
37
  validateModifiedFiles?: boolean;
38
38
  validateVersionsLocked?: boolean;
39
39
  validateTsInSrc?: boolean;
40
+ validateNxWiring?: boolean;
40
41
  newMethodsMaxLines?: number;
41
42
  modifiedAndNewMethodsMaxLines?: number;
42
43
  modifiedFilesMaxLines?: number;
package/src/plugin.js CHANGED
@@ -32,7 +32,10 @@ const DEFAULT_OPTIONS = {
32
32
  graphPath: 'architecture/dependencies.json',
33
33
  validations: {
34
34
  noCycles: true,
35
- noSkipLevelDeps: true,
35
+ // Retired: the architecture graph is now auto-reduced in `generate`, so the
36
+ // committed graph can never contain a skip-level edge. Defaults off; the
37
+ // executor is a no-op kept for one release. See validate-no-skiplevel-deps.
38
+ noSkipLevelDeps: false,
36
39
  architectureUnchanged: true,
37
40
  validatePackageJson: true,
38
41
  validateNewMethods: true,
@@ -40,6 +43,7 @@ const DEFAULT_OPTIONS = {
40
43
  validateModifiedFiles: true,
41
44
  validateVersionsLocked: true,
42
45
  validateTsInSrc: true,
46
+ validateNxWiring: true,
43
47
  newMethodsMaxLines: 30,
44
48
  modifiedAndNewMethodsMaxLines: 80,
45
49
  modifiedFilesMaxLines: 900,
@@ -184,6 +188,8 @@ function buildValidationTargetsList(validations) {
184
188
  targets.push('validate-versions-locked');
185
189
  if (validations.validateTsInSrc)
186
190
  targets.push('validate-ts-in-src');
191
+ if (validations.validateNxWiring)
192
+ targets.push('validate-nx-wiring');
187
193
  return targets;
188
194
  }
189
195
  /**
@@ -228,6 +234,9 @@ function createWorkspaceTargetsWithoutPrefix(opts) {
228
234
  if (validations.validateTsInSrc) {
229
235
  targets['validate-ts-in-src'] = createValidateTsInSrcTarget();
230
236
  }
237
+ if (validations.validateNxWiring) {
238
+ targets['validate-nx-wiring'] = createValidateNxWiringTarget();
239
+ }
231
240
  // Add validate-complete target that runs all enabled validations
232
241
  const validationTargets = buildValidationTargetsList(validations);
233
242
  if (validationTargets.length > 0) {
@@ -428,6 +437,17 @@ function createValidateTsInSrcTarget() {
428
437
  },
429
438
  };
430
439
  }
440
+ function createValidateNxWiringTarget() {
441
+ return {
442
+ executor: '@webpieces/nx-webpieces-rules:validate-nx-wiring',
443
+ cache: false, // Cheap; depends on nx.json + project graph, not worth caching
444
+ inputs: ['{workspaceRoot}/nx.json', '{workspaceRoot}/webpieces.config.json'],
445
+ metadata: {
446
+ technologies: ['nx'],
447
+ description: 'Validate the webpieces validators are wired into the build via nx.json dependsOn',
448
+ },
449
+ };
450
+ }
431
451
  function createValidateCompleteTarget(validationTargets) {
432
452
  return {
433
453
  executor: 'nx:noop',