@webpieces/nx-webpieces-rules 0.4.899 → 0.4.901

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/package.json +7 -7
  2. package/src/lib/graph-generator.d.ts +3 -1
  3. package/src/lib/graph-generator.js +22 -3
  4. package/src/lib/graph-generator.js.map +1 -1
  5. package/src/lib/graph-legend.js +2 -2
  6. package/src/lib/graph-legend.js.map +1 -1
  7. package/src/lib/graph-loader.d.ts +2 -2
  8. package/src/lib/graph-loader.js +2 -2
  9. package/src/lib/graph-loader.js.map +1 -1
  10. package/src/lib/graph-metadata.d.ts +1 -1
  11. package/src/lib/graph-metadata.js +3 -4
  12. package/src/lib/graph-metadata.js.map +1 -1
  13. package/src/lib/graph-mode-state.client.js +1 -1
  14. package/src/lib/graph-mode-state.client.js.map +1 -1
  15. package/src/lib/graph-page-shell.d.ts +1 -1
  16. package/src/lib/graph-page-shell.js +2 -2
  17. package/src/lib/graph-page-shell.js.map +1 -1
  18. package/src/lib/graph-products.d.ts +24 -42
  19. package/src/lib/graph-products.js +31 -127
  20. package/src/lib/graph-products.js.map +1 -1
  21. package/src/lib/graph-sorter.d.ts +3 -6
  22. package/src/lib/graph-sorter.js.map +1 -1
  23. package/src/lib/product-dependencies.d.ts +5 -3
  24. package/src/lib/product-dependencies.js +6 -5
  25. package/src/lib/product-dependencies.js.map +1 -1
  26. package/src/lib/product-resolver.d.ts +5 -6
  27. package/src/lib/product-resolver.js +5 -6
  28. package/src/lib/product-resolver.js.map +1 -1
  29. package/src/lib/runtime-graph-model.d.ts +4 -6
  30. package/src/lib/runtime-graph-model.js.map +1 -1
  31. package/src/lib/runtime-graph.js +1 -1
  32. package/src/lib/runtime-graph.js.map +1 -1
  33. package/src/lib/tag-truth.d.ts +2 -2
  34. package/src/lib/tag-truth.js +2 -2
  35. package/src/lib/tag-truth.js.map +1 -1
  36. package/src/lib/visible-transitive-reduction.d.ts +33 -0
  37. package/src/lib/visible-transitive-reduction.js +60 -0
  38. package/src/lib/visible-transitive-reduction.js.map +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/nx-webpieces-rules",
3
- "version": "0.4.899",
3
+ "version": "0.4.901",
4
4
  "description": "Nx-specific webpieces validation rules, graph tooling, executors, and inference plugin.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -20,12 +20,12 @@
20
20
  "README.md"
21
21
  ],
22
22
  "dependencies": {
23
- "@webpieces/api-doc-model": "0.4.899",
24
- "@webpieces/core-util": "0.4.899",
25
- "@webpieces/eslint-rules": "0.4.899",
26
- "@webpieces/rules-config": "0.4.899",
23
+ "@webpieces/api-doc-model": "0.4.901",
24
+ "@webpieces/core-util": "0.4.901",
25
+ "@webpieces/eslint-rules": "0.4.901",
26
+ "@webpieces/rules-config": "0.4.901",
27
27
  "madge": "8.0.0",
28
- "@webpieces/rules-sdk": "0.4.899"
28
+ "@webpieces/rules-sdk": "0.4.901"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "@nx/devkit": ">=18.0.0"
@@ -41,7 +41,7 @@
41
41
  "access": "public"
42
42
  },
43
43
  "devDependencies": {
44
- "@webpieces/tooling-testkit": "0.4.899"
44
+ "@webpieces/tooling-testkit": "0.4.901"
45
45
  },
46
46
  "types": "./src/index.d.ts",
47
47
  "bin": {
@@ -10,7 +10,9 @@
10
10
  * This is what build order follows via `^build`.
11
11
  * - generateReducedGraph() → transitive reduction of the full graph: the minimal
12
12
  * edge set with identical reachability, used as the
13
- * architecture VIEW written to dependencies.json.
13
+ * architecture VIEW written to dependencies.json. A path
14
+ * through a `drawOnGraph:false` project never removes a
15
+ * visible edge (see visible-transitive-reduction.ts).
14
16
  */
15
17
  /**
16
18
  * Build the full dependency graph from nx's project graph.
@@ -11,14 +11,18 @@
11
11
  * This is what build order follows via `^build`.
12
12
  * - generateReducedGraph() → transitive reduction of the full graph: the minimal
13
13
  * edge set with identical reachability, used as the
14
- * architecture VIEW written to dependencies.json.
14
+ * architecture VIEW written to dependencies.json. A path
15
+ * through a `drawOnGraph:false` project never removes a
16
+ * visible edge (see visible-transitive-reduction.ts).
15
17
  */
16
18
  Object.defineProperty(exports, "__esModule", { value: true });
17
19
  exports.generateRawGraph = generateRawGraph;
18
20
  exports.generateGraph = generateGraph;
19
21
  exports.generateReducedGraph = generateReducedGraph;
20
22
  const devkit_1 = require("@nx/devkit");
21
- const transitive_reduction_1 = require("./transitive-reduction");
23
+ const graph_metadata_1 = require("./graph-metadata");
24
+ const draw_on_graph_resolver_1 = require("./draw-on-graph-resolver");
25
+ const visible_transitive_reduction_1 = require("./visible-transitive-reduction");
22
26
  /**
23
27
  * Projects to exclude from graph validation (tools, configs, etc.)
24
28
  */
@@ -74,6 +78,21 @@ async function generateGraph() {
74
78
  */
75
79
  async function generateReducedGraph() {
76
80
  const full = await generateGraph();
77
- return (0, transitive_reduction_1.transitiveReduction)(full);
81
+ const hidden = await new HiddenProjectFinder().find();
82
+ return new visible_transitive_reduction_1.VisibleTransitiveReduction().reduce(full, hidden);
83
+ }
84
+ /**
85
+ * The projects tagged `drawOnGraph:false`. A malformed tag is NOT reported here: enrichGraph
86
+ * reports it with every other metadata problem, and such a project is treated as drawn.
87
+ */
88
+ class HiddenProjectFinder {
89
+ async find() {
90
+ const hidden = new Set();
91
+ for (const info of (await (0, graph_metadata_1.collectProjectInfo)()).values()) {
92
+ if ((0, draw_on_graph_resolver_1.resolveDrawOnGraph)(info).drawOnGraph === false)
93
+ hidden.add(info.name);
94
+ }
95
+ return hidden;
96
+ }
78
97
  }
79
98
  //# 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;;;;;;;;;;;;;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"]}
1
+ {"version":3,"file":"graph-generator.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-generator.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;;AAsBH,4CA0BC;AAKD,sCAEC;AAUD,oDAIC;AAnED,uCAAqD;AACrD,qDAAsD;AACtD,qEAA8D;AAC9D,iFAA4E;AAE5E;;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,MAAM,MAAM,GAAG,MAAM,IAAI,mBAAmB,EAAE,CAAC,IAAI,EAAE,CAAC;IACtD,OAAO,IAAI,yDAA0B,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AACjE,CAAC;AAED;;;GAGG;AACH,MAAM,mBAAmB;IACrB,KAAK,CAAC,IAAI;QACN,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;QACjC,KAAK,MAAM,IAAI,IAAI,CAAC,MAAM,IAAA,mCAAkB,GAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;YACvD,IAAI,IAAA,2CAAkB,EAAC,IAAI,CAAC,CAAC,WAAW,KAAK,KAAK;gBAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9E,CAAC;QACD,OAAO,MAAM,CAAC;IAClB,CAAC;CACJ","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. A path\n * through a `drawOnGraph:false` project never removes a\n * visible edge (see visible-transitive-reduction.ts).\n */\n\nimport { createProjectGraphAsync } from '@nx/devkit';\nimport { collectProjectInfo } from './graph-metadata';\nimport { resolveDrawOnGraph } from './draw-on-graph-resolver';\nimport { VisibleTransitiveReduction } from './visible-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 const hidden = await new HiddenProjectFinder().find();\n return new VisibleTransitiveReduction().reduce(full, hidden);\n}\n\n/**\n * The projects tagged `drawOnGraph:false`. A malformed tag is NOT reported here: enrichGraph\n * reports it with every other metadata problem, and such a project is treated as drawn.\n */\nclass HiddenProjectFinder {\n async find(): Promise<Set<string>> {\n const hidden = new Set<string>();\n for (const info of (await collectProjectInfo()).values()) {\n if (resolveDrawOnGraph(info).drawOnGraph === false) hidden.add(info.name);\n }\n return hidden;\n }\n}\n"]}
@@ -83,7 +83,7 @@ class GraphLegend {
83
83
  */
84
84
  productRows(facts) {
85
85
  if (facts.products.length === 0)
86
- return this.row('no-products', this.solid(graph_products_1.PRODUCT_NONE_FILL, graph_products_1.PRODUCT_NONE_BORDER, '3 2'), 'no products declared', 'tag servers, clients and apps product:<name> to see each product');
86
+ return this.row('no-products', this.solid(graph_products_1.PRODUCT_NONE_FILL, graph_products_1.PRODUCT_NONE_BORDER, '3 2'), 'no products declared', 'tag each project product:<name> to see each product');
87
87
  const palette = new graph_products_1.ProductPalette(facts.products.map((count) => count.product));
88
88
  let rows = '';
89
89
  for (const count of facts.products) {
@@ -98,7 +98,7 @@ class GraphLegend {
98
98
  if (facts.multiProduct)
99
99
  rows += this.row('several', this.productStripes(palette), 'several products', 'one stripe per product');
100
100
  if (facts.noProduct > 0)
101
- rows += this.row('no-product', this.solid(graph_products_1.PRODUCT_NONE_FILL, graph_products_1.PRODUCT_NONE_BORDER, '3 2'), `no product · ${facts.noProduct}`, 'no tagged server, client or app reaches it');
101
+ rows += this.row('no-product', this.solid(graph_products_1.PRODUCT_NONE_FILL, graph_products_1.PRODUCT_NONE_BORDER, '3 2'), `no product · ${facts.noProduct}`, 'needs a product:<name> tag');
102
102
  return rows + this.boundaryRows(facts, palette);
103
103
  }
104
104
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"graph-legend.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-legend.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The architecture graph's legend, generated per color mode FROM THE PROJECTS ACTUALLY DRAWN.\n *\n * The hand-written legend this replaces listed five frameworks when six existed and never mentioned\n * the `app` and `bundle` borders the renderer drew (#1155). Generating the rows from the same tables\n * the renderer colors with (graph-color-modes.ts) means a color in use always has a row, and a color\n * nothing uses never does.\n *\n * Swatches are inline SVG drawn with presentation attributes, never `style=`, so the page carries\n * no inline CSS.\n */\n\nimport { KNOWN_FRAMEWORKS, KnownFramework } from '@webpieces/rules-sdk';\nimport {\n BASE_ORDER,\n FRAMEWORK_STYLES,\n FrameworkStyle,\n GraphMode,\n IMPACT_AFFECTED,\n IMPACT_BUILD_INPUT,\n IMPACT_BUILD_INPUT_BORDER,\n IMPACT_TOUCHED,\n IMPACT_UNTOUCHED,\n LegendFacts,\n NO_RUNTIME_COLOR,\n ProductCount,\n ROLE_STYLES,\n RoleStyle,\n} from './graph-color-modes';\nimport {\n PRODUCT_NONE_BORDER,\n PRODUCT_NONE_FILL,\n PRODUCT_PALETTE_SIZE,\n PRODUCT_SHARED_FILL,\n ProductColor,\n ProductPalette,\n} from './graph-products';\nimport type { ProductDependencyEntry } from '../configs/tag-truth-configs';\n\nconst SWATCH_W = 26;\nconst SWATCH_H = 16;\nconst FRAME = '#3a3c46';\n\nexport class GraphLegend {\n /** One section per mode; the page shows the section of the current mode and hides the rest. */\n sections(facts: LegendFacts): string {\n return (\n this.section(GraphMode.RUNTIME, this.runtimeRows(facts), false) +\n this.section(GraphMode.ARCHITECTURE, this.architectureRows(facts), true) +\n this.section(GraphMode.IMPACT, this.impactRows(), true) +\n this.section(GraphMode.PRODUCT, this.productRows(facts), true)\n );\n }\n\n /** The edge key: why a line exists. Static — every graph can carry every edge kind. */\n edgeKey(): string {\n return (\n '<div class=\"wp-legend-list\">' +\n this.edgeRow('#e9eaf0', '', 'uses', 'calls the API, or a plain library import') +\n this.edgeRow('#e9eaf0', '4 3', 'implements', 'serves the API (labeled with its contracts)') +\n this.edgeRow('#7aa2ff', '4 3', 'uses + implements', 'implements some contracts, uses others') +\n '</div>'\n );\n }\n\n private section(mode: GraphMode, rows: string, hidden: boolean): string {\n const hide = hidden ? ' hidden' : '';\n return `<div class=\"wp-legend-list\" data-wp-legend-mode=\"${mode}\"${hide}>${rows}</div>`;\n }\n\n /** Base runtimes first (stripe order), then specializations, in the shared vocabulary's order. */\n runtimeRows(facts: LegendFacts): string {\n const present = (framework: KnownFramework): boolean => facts.frameworks.includes(framework);\n const ordered: FrameworkStyle[] = [\n ...BASE_ORDER.map((base: KnownFramework): FrameworkStyle => FRAMEWORK_STYLES[base]),\n ...KNOWN_FRAMEWORKS.map((f: KnownFramework): FrameworkStyle => FRAMEWORK_STYLES[f]).filter(\n (style: FrameworkStyle): boolean => !style.isBase(),\n ),\n ];\n let rows = '';\n for (const style of ordered) {\n if (!present(style.framework)) continue;\n rows += this.row(style.framework, this.envSwatch(style), style.framework, style.description);\n }\n if (facts.multiRuntime)\n rows += this.row('multi', this.stripesSwatch(), 'several runtimes', 'one equal stripe per runtime');\n if (facts.noRuntime)\n rows += this.row('none', this.solid(NO_RUNTIME_COLOR, '', ''), 'no framework tag', 'runs nowhere declared');\n return rows;\n }\n\n architectureRows(facts: LegendFacts): string {\n let rows = '';\n for (const style of ROLE_STYLES) {\n if (!facts.roles.includes(style.role)) continue;\n rows += this.row(style.role, this.roleSwatch(style), style.role, style.description);\n }\n return rows;\n }\n\n /**\n * Which box is which is only known from the branch's sidecar, so all four rows always show. The\n * words are the Filter's (#1179): changed · dependent · dependency · not in this build.\n */\n impactRows(): string {\n return (\n this.row('touched', this.solid(IMPACT_TOUCHED, '', ''), 'changed', 'owns a changed file') +\n this.row(\n 'affected',\n this.solid(IMPACT_AFFECTED, IMPACT_TOUCHED, ''),\n 'dependent',\n 'uses a changed project: its tests and build re-run',\n ) +\n this.row(\n 'build-input',\n this.solid(IMPACT_BUILD_INPUT, IMPACT_BUILD_INPUT_BORDER, '3 2'),\n 'dependency (built, unchanged)',\n 'compiled or restored from cache for this build',\n ) +\n this.row('untouched', this.solid(IMPACT_UNTOUCHED, '', ''), 'not in this build', 'nothing here changed or depends on a change') +\n '<p class=\"wp-impact-note\" data-wp-impact-note></p>'\n );\n }\n\n /**\n * Product mode (#1179): one row per product with its counts, then the shared, several-products and\n * no-product rows that some drawn box actually uses.\n */\n productRows(facts: LegendFacts): string {\n if (facts.products.length === 0)\n return this.row(\n 'no-products',\n this.solid(PRODUCT_NONE_FILL, PRODUCT_NONE_BORDER, '3 2'),\n 'no products declared',\n 'tag servers, clients and apps product:<name> to see each product',\n );\n const palette = new ProductPalette(facts.products.map((count: ProductCount): string => count.product));\n let rows = '';\n for (const count of facts.products) {\n const color = palette.colorOf(count.product);\n const detail = color.inPalette\n ? `${count.only} only ${count.product}`\n : `${count.only} only ${count.product} — past the palette's ${PRODUCT_PALETTE_SIZE}: neutral fill`;\n rows += this.row(`product:${count.product}`, this.solid(color.color, '', ''), `${count.product} · ${count.total}`, detail);\n }\n if (facts.sharedByAll > 0)\n rows += this.row(\n 'shared',\n this.solid(PRODUCT_SHARED_FILL, '', ''),\n `shared by every product · ${facts.sharedByAll}`,\n 'a change here is a change to every product',\n );\n if (facts.multiProduct)\n rows += this.row('several', this.productStripes(palette), 'several products', 'one stripe per product');\n if (facts.noProduct > 0)\n rows += this.row(\n 'no-product',\n this.solid(PRODUCT_NONE_FILL, PRODUCT_NONE_BORDER, '3 2'),\n `no product · ${facts.noProduct}`,\n 'no tagged server, client or app reaches it',\n );\n return rows + this.boundaryRows(facts, palette);\n }\n\n /**\n * #1204: the dashed cross-product edge, and the product dependency matrix that allows each such\n * edge — `example → openapi, platform` — when the `product-tags` rule supplied one.\n */\n boundaryRows(facts: LegendFacts, palette: ProductPalette): string {\n let rows = '';\n if (facts.crossProductEdges > 0)\n rows += this.row(\n 'cross-product',\n this.line(PRODUCT_NONE_BORDER, '4 3'),\n `crosses into another product · ${facts.crossProductEdges}`,\n \"dashed, in the color of the product it depends on\",\n );\n const matrix = facts.productDependencies;\n if (matrix === null) return rows;\n const stated = matrix.filter((entry: ProductDependencyEntry): boolean => entry.allowedToDependOn.length > 0);\n if (stated.length === 0)\n return (\n rows +\n this.row(\n 'allowed-none',\n this.line(PRODUCT_NONE_BORDER, ''),\n 'no product may depend on another',\n 'product-tags.productDependencies allows no crossing',\n )\n );\n for (const entry of [...stated].sort((a: ProductDependencyEntry, b: ProductDependencyEntry): number => a.name.localeCompare(b.name)))\n rows += this.row(\n `allowed:${entry.name}`,\n this.line(palette.colorOf(entry.name).color, ''),\n `${entry.name} → ${[...entry.allowedToDependOn].sort().join(', ')}`,\n 'allowed by product-tags.productDependencies',\n );\n return rows;\n }\n\n private row(key: string, swatch: string, title: string, detail: string): string {\n return (\n `<div class=\"wp-legend-row\" data-wp-legend=\"${key}\">${swatch}` +\n `<span>${title}<small>${detail}</small></span></div>`\n );\n }\n\n private open(): string {\n return `<svg width=\"${SWATCH_W}\" height=\"${SWATCH_H}\" viewBox=\"0 0 ${SWATCH_W} ${SWATCH_H}\" aria-hidden=\"true\">`;\n }\n\n private frame(): string {\n return `<rect x=\"0.5\" y=\"0.5\" width=\"${SWATCH_W - 1}\" height=\"${SWATCH_H - 1}\" fill=\"none\" stroke=\"${FRAME}\"/></svg>`;\n }\n\n /** A base runtime is solid; a specialization is its inset on the base color. */\n private envSwatch(style: FrameworkStyle): string {\n const base = `<rect width=\"${SWATCH_W}\" height=\"${SWATCH_H}\" fill=\"${FRAMEWORK_STYLES[style.base].color}\"/>`;\n if (style.isBase()) return this.open() + base + this.frame();\n const inset = `<rect x=\"4\" y=\"4\" width=\"${SWATCH_W - 8}\" height=\"${SWATCH_H - 8}\" rx=\"2\" fill=\"${style.color}\"/>`;\n return this.open() + base + inset + this.frame();\n }\n\n private stripesSwatch(): string {\n const width = SWATCH_W / BASE_ORDER.length;\n const stripes = BASE_ORDER.map(\n (base: KnownFramework, index: number): string =>\n `<rect x=\"${(index * width).toFixed(2)}\" width=\"${width.toFixed(2)}\" height=\"${SWATCH_H}\" fill=\"${FRAMEWORK_STYLES[base].color}\"/>`,\n ).join('');\n return this.open() + stripes + this.frame();\n }\n\n /** The first three product colors as stripes: the \"several products\" swatch. */\n private productStripes(palette: ProductPalette): string {\n const colors = palette.colors.slice(0, 3);\n const width = SWATCH_W / colors.length;\n const stripes = colors\n .map(\n (color: ProductColor, index: number): string =>\n `<rect x=\"${(index * width).toFixed(2)}\" width=\"${width.toFixed(2)}\" height=\"${SWATCH_H}\" fill=\"${color.color}\"/>`,\n )\n .join('');\n return this.open() + stripes + this.frame();\n }\n\n private roleSwatch(style: RoleStyle): string {\n return this.solid(style.fill, '', '');\n }\n\n /** A filled swatch, optionally with an inner ring (dashed when `dash` is set). */\n private solid(fill: string, outline: string, dash: string): string {\n const dashed = dash === '' ? '' : ` stroke-dasharray=\"${dash}\"`;\n const ring =\n outline === ''\n ? ''\n : `<rect x=\"2\" y=\"2\" width=\"${SWATCH_W - 4}\" height=\"${SWATCH_H - 4}\" fill=\"none\" stroke=\"${outline}\" stroke-width=\"2\"${dashed}/>`;\n return this.open() + `<rect width=\"${SWATCH_W}\" height=\"${SWATCH_H}\" fill=\"${fill}\"/>` + ring + this.frame();\n }\n\n private edgeRow(color: string, dash: string, title: string, detail: string): string {\n return this.row(title, this.line(color, dash), title, detail);\n }\n\n /** An edge swatch: a short line, dashed when `dash` is set. */\n private line(color: string, dash: string): string {\n const dashed = dash === '' ? '' : ` stroke-dasharray=\"${dash}\"`;\n return `<svg width=\"28\" height=\"10\" aria-hidden=\"true\"><line x1=\"0\" y1=\"5\" x2=\"28\" y2=\"5\" stroke=\"${color}\" stroke-width=\"1.6\"${dashed}/></svg>`;\n }\n}\n"]}
1
+ {"version":3,"file":"graph-legend.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-legend.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The architecture graph's legend, generated per color mode FROM THE PROJECTS ACTUALLY DRAWN.\n *\n * The hand-written legend this replaces listed five frameworks when six existed and never mentioned\n * the `app` and `bundle` borders the renderer drew (#1155). Generating the rows from the same tables\n * the renderer colors with (graph-color-modes.ts) means a color in use always has a row, and a color\n * nothing uses never does.\n *\n * Swatches are inline SVG drawn with presentation attributes, never `style=`, so the page carries\n * no inline CSS.\n */\n\nimport { KNOWN_FRAMEWORKS, KnownFramework } from '@webpieces/rules-sdk';\nimport {\n BASE_ORDER,\n FRAMEWORK_STYLES,\n FrameworkStyle,\n GraphMode,\n IMPACT_AFFECTED,\n IMPACT_BUILD_INPUT,\n IMPACT_BUILD_INPUT_BORDER,\n IMPACT_TOUCHED,\n IMPACT_UNTOUCHED,\n LegendFacts,\n NO_RUNTIME_COLOR,\n ProductCount,\n ROLE_STYLES,\n RoleStyle,\n} from './graph-color-modes';\nimport {\n PRODUCT_NONE_BORDER,\n PRODUCT_NONE_FILL,\n PRODUCT_PALETTE_SIZE,\n PRODUCT_SHARED_FILL,\n ProductColor,\n ProductPalette,\n} from './graph-products';\nimport type { ProductDependencyEntry } from '../configs/tag-truth-configs';\n\nconst SWATCH_W = 26;\nconst SWATCH_H = 16;\nconst FRAME = '#3a3c46';\n\nexport class GraphLegend {\n /** One section per mode; the page shows the section of the current mode and hides the rest. */\n sections(facts: LegendFacts): string {\n return (\n this.section(GraphMode.RUNTIME, this.runtimeRows(facts), false) +\n this.section(GraphMode.ARCHITECTURE, this.architectureRows(facts), true) +\n this.section(GraphMode.IMPACT, this.impactRows(), true) +\n this.section(GraphMode.PRODUCT, this.productRows(facts), true)\n );\n }\n\n /** The edge key: why a line exists. Static — every graph can carry every edge kind. */\n edgeKey(): string {\n return (\n '<div class=\"wp-legend-list\">' +\n this.edgeRow('#e9eaf0', '', 'uses', 'calls the API, or a plain library import') +\n this.edgeRow('#e9eaf0', '4 3', 'implements', 'serves the API (labeled with its contracts)') +\n this.edgeRow('#7aa2ff', '4 3', 'uses + implements', 'implements some contracts, uses others') +\n '</div>'\n );\n }\n\n private section(mode: GraphMode, rows: string, hidden: boolean): string {\n const hide = hidden ? ' hidden' : '';\n return `<div class=\"wp-legend-list\" data-wp-legend-mode=\"${mode}\"${hide}>${rows}</div>`;\n }\n\n /** Base runtimes first (stripe order), then specializations, in the shared vocabulary's order. */\n runtimeRows(facts: LegendFacts): string {\n const present = (framework: KnownFramework): boolean => facts.frameworks.includes(framework);\n const ordered: FrameworkStyle[] = [\n ...BASE_ORDER.map((base: KnownFramework): FrameworkStyle => FRAMEWORK_STYLES[base]),\n ...KNOWN_FRAMEWORKS.map((f: KnownFramework): FrameworkStyle => FRAMEWORK_STYLES[f]).filter(\n (style: FrameworkStyle): boolean => !style.isBase(),\n ),\n ];\n let rows = '';\n for (const style of ordered) {\n if (!present(style.framework)) continue;\n rows += this.row(style.framework, this.envSwatch(style), style.framework, style.description);\n }\n if (facts.multiRuntime)\n rows += this.row('multi', this.stripesSwatch(), 'several runtimes', 'one equal stripe per runtime');\n if (facts.noRuntime)\n rows += this.row('none', this.solid(NO_RUNTIME_COLOR, '', ''), 'no framework tag', 'runs nowhere declared');\n return rows;\n }\n\n architectureRows(facts: LegendFacts): string {\n let rows = '';\n for (const style of ROLE_STYLES) {\n if (!facts.roles.includes(style.role)) continue;\n rows += this.row(style.role, this.roleSwatch(style), style.role, style.description);\n }\n return rows;\n }\n\n /**\n * Which box is which is only known from the branch's sidecar, so all four rows always show. The\n * words are the Filter's (#1179): changed · dependent · dependency · not in this build.\n */\n impactRows(): string {\n return (\n this.row('touched', this.solid(IMPACT_TOUCHED, '', ''), 'changed', 'owns a changed file') +\n this.row(\n 'affected',\n this.solid(IMPACT_AFFECTED, IMPACT_TOUCHED, ''),\n 'dependent',\n 'uses a changed project: its tests and build re-run',\n ) +\n this.row(\n 'build-input',\n this.solid(IMPACT_BUILD_INPUT, IMPACT_BUILD_INPUT_BORDER, '3 2'),\n 'dependency (built, unchanged)',\n 'compiled or restored from cache for this build',\n ) +\n this.row('untouched', this.solid(IMPACT_UNTOUCHED, '', ''), 'not in this build', 'nothing here changed or depends on a change') +\n '<p class=\"wp-impact-note\" data-wp-impact-note></p>'\n );\n }\n\n /**\n * Product mode (#1179): one row per product with its counts, then the shared, several-products and\n * no-product rows that some drawn box actually uses.\n */\n productRows(facts: LegendFacts): string {\n if (facts.products.length === 0)\n return this.row(\n 'no-products',\n this.solid(PRODUCT_NONE_FILL, PRODUCT_NONE_BORDER, '3 2'),\n 'no products declared',\n 'tag each project product:<name> to see each product',\n );\n const palette = new ProductPalette(facts.products.map((count: ProductCount): string => count.product));\n let rows = '';\n for (const count of facts.products) {\n const color = palette.colorOf(count.product);\n const detail = color.inPalette\n ? `${count.only} only ${count.product}`\n : `${count.only} only ${count.product} — past the palette's ${PRODUCT_PALETTE_SIZE}: neutral fill`;\n rows += this.row(`product:${count.product}`, this.solid(color.color, '', ''), `${count.product} · ${count.total}`, detail);\n }\n if (facts.sharedByAll > 0)\n rows += this.row(\n 'shared',\n this.solid(PRODUCT_SHARED_FILL, '', ''),\n `shared by every product · ${facts.sharedByAll}`,\n 'a change here is a change to every product',\n );\n if (facts.multiProduct)\n rows += this.row('several', this.productStripes(palette), 'several products', 'one stripe per product');\n if (facts.noProduct > 0)\n rows += this.row(\n 'no-product',\n this.solid(PRODUCT_NONE_FILL, PRODUCT_NONE_BORDER, '3 2'),\n `no product · ${facts.noProduct}`,\n 'needs a product:<name> tag',\n );\n return rows + this.boundaryRows(facts, palette);\n }\n\n /**\n * #1204: the dashed cross-product edge, and the product dependency matrix that allows each such\n * edge — `example → openapi, platform` — when the `product-tags` rule supplied one.\n */\n boundaryRows(facts: LegendFacts, palette: ProductPalette): string {\n let rows = '';\n if (facts.crossProductEdges > 0)\n rows += this.row(\n 'cross-product',\n this.line(PRODUCT_NONE_BORDER, '4 3'),\n `crosses into another product · ${facts.crossProductEdges}`,\n \"dashed, in the color of the product it depends on\",\n );\n const matrix = facts.productDependencies;\n if (matrix === null) return rows;\n const stated = matrix.filter((entry: ProductDependencyEntry): boolean => entry.allowedToDependOn.length > 0);\n if (stated.length === 0)\n return (\n rows +\n this.row(\n 'allowed-none',\n this.line(PRODUCT_NONE_BORDER, ''),\n 'no product may depend on another',\n 'product-tags.productDependencies allows no crossing',\n )\n );\n for (const entry of [...stated].sort((a: ProductDependencyEntry, b: ProductDependencyEntry): number => a.name.localeCompare(b.name)))\n rows += this.row(\n `allowed:${entry.name}`,\n this.line(palette.colorOf(entry.name).color, ''),\n `${entry.name} → ${[...entry.allowedToDependOn].sort().join(', ')}`,\n 'allowed by product-tags.productDependencies',\n );\n return rows;\n }\n\n private row(key: string, swatch: string, title: string, detail: string): string {\n return (\n `<div class=\"wp-legend-row\" data-wp-legend=\"${key}\">${swatch}` +\n `<span>${title}<small>${detail}</small></span></div>`\n );\n }\n\n private open(): string {\n return `<svg width=\"${SWATCH_W}\" height=\"${SWATCH_H}\" viewBox=\"0 0 ${SWATCH_W} ${SWATCH_H}\" aria-hidden=\"true\">`;\n }\n\n private frame(): string {\n return `<rect x=\"0.5\" y=\"0.5\" width=\"${SWATCH_W - 1}\" height=\"${SWATCH_H - 1}\" fill=\"none\" stroke=\"${FRAME}\"/></svg>`;\n }\n\n /** A base runtime is solid; a specialization is its inset on the base color. */\n private envSwatch(style: FrameworkStyle): string {\n const base = `<rect width=\"${SWATCH_W}\" height=\"${SWATCH_H}\" fill=\"${FRAMEWORK_STYLES[style.base].color}\"/>`;\n if (style.isBase()) return this.open() + base + this.frame();\n const inset = `<rect x=\"4\" y=\"4\" width=\"${SWATCH_W - 8}\" height=\"${SWATCH_H - 8}\" rx=\"2\" fill=\"${style.color}\"/>`;\n return this.open() + base + inset + this.frame();\n }\n\n private stripesSwatch(): string {\n const width = SWATCH_W / BASE_ORDER.length;\n const stripes = BASE_ORDER.map(\n (base: KnownFramework, index: number): string =>\n `<rect x=\"${(index * width).toFixed(2)}\" width=\"${width.toFixed(2)}\" height=\"${SWATCH_H}\" fill=\"${FRAMEWORK_STYLES[base].color}\"/>`,\n ).join('');\n return this.open() + stripes + this.frame();\n }\n\n /** The first three product colors as stripes: the \"several products\" swatch. */\n private productStripes(palette: ProductPalette): string {\n const colors = palette.colors.slice(0, 3);\n const width = SWATCH_W / colors.length;\n const stripes = colors\n .map(\n (color: ProductColor, index: number): string =>\n `<rect x=\"${(index * width).toFixed(2)}\" width=\"${width.toFixed(2)}\" height=\"${SWATCH_H}\" fill=\"${color.color}\"/>`,\n )\n .join('');\n return this.open() + stripes + this.frame();\n }\n\n private roleSwatch(style: RoleStyle): string {\n return this.solid(style.fill, '', '');\n }\n\n /** A filled swatch, optionally with an inner ring (dashed when `dash` is set). */\n private solid(fill: string, outline: string, dash: string): string {\n const dashed = dash === '' ? '' : ` stroke-dasharray=\"${dash}\"`;\n const ring =\n outline === ''\n ? ''\n : `<rect x=\"2\" y=\"2\" width=\"${SWATCH_W - 4}\" height=\"${SWATCH_H - 4}\" fill=\"none\" stroke=\"${outline}\" stroke-width=\"2\"${dashed}/>`;\n return this.open() + `<rect width=\"${SWATCH_W}\" height=\"${SWATCH_H}\" fill=\"${fill}\"/>` + ring + this.frame();\n }\n\n private edgeRow(color: string, dash: string, title: string, detail: string): string {\n return this.row(title, this.line(color, dash), title, detail);\n }\n\n /** An edge swatch: a short line, dashed when `dash` is set. */\n private line(color: string, dash: string): string {\n const dashed = dash === '' ? '' : ` stroke-dasharray=\"${dash}\"`;\n return `<svg width=\"28\" height=\"10\" aria-hidden=\"true\"><line x1=\"0\" y1=\"5\" x2=\"28\" y2=\"5\" stroke=\"${color}\" stroke-width=\"1.6\"${dashed}/></svg>`;\n }\n}\n"]}
@@ -18,8 +18,8 @@
18
18
  * `level` is the project's dependency depth, except that every server, client and app is pinned to
19
19
  * the TOP level (bundles above them), so the top row is exactly the entry points (#1179).
20
20
  *
21
- * `products` (#1179) is the sorted list of products whose `product:<name>`-tagged projects reach
22
- * this project over `dependsOn` — its blast radius. Absent when no product reaches it.
21
+ * `products` (#1179) is the sorted list of the project's own declared `product:<name>` tags (#1216).
22
+ * Absent for an untagged project.
23
23
  *
24
24
  * `framework` is the project's libType — the SET of runtime environments it is
25
25
  * validated to run in, drawn from browser | react | angular | node | express
@@ -19,8 +19,8 @@
19
19
  * `level` is the project's dependency depth, except that every server, client and app is pinned to
20
20
  * the TOP level (bundles above them), so the top row is exactly the entry points (#1179).
21
21
  *
22
- * `products` (#1179) is the sorted list of products whose `product:<name>`-tagged projects reach
23
- * this project over `dependsOn` — its blast radius. Absent when no product reaches it.
22
+ * `products` (#1179) is the sorted list of the project's own declared `product:<name>` tags (#1216).
23
+ * Absent for an untagged project.
24
24
  *
25
25
  * `framework` is the project's libType — the SET of runtime environments it is
26
26
  * validated to run in, drawn from browser | react | angular | node | express
@@ -1 +1 @@
1
- {"version":3,"file":"graph-loader.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-loader.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Graph Loader\n *\n * Handles loading and saving the blessed dependency graph file.\n * The graph is stored at architecture/dependencies.json in the workspace root.\n *\n * File format (schema aimed at AI consumers):\n * {\n * \"aiInstructions\": \"...how AI should use the per-project fields...\",\n * \"apiContractFiles\": { \"<ApiName>\": \"apis/<ApiName>.json\" },\n * \"remoteServices\": { \"<serviceName>\": { \"label\"?, \"implements\": [{ \"api\", \"type\" }] } },\n * \"projects\": {\n * \"<project>\": { level, framework, products, shortDescription,\n * responsibilitiesFile, designFile, dependsOn }\n * }\n * }\n *\n * `level` is the project's dependency depth, except that every server, client and app is pinned to\n * the TOP level (bundles above them), so the top row is exactly the entry points (#1179).\n *\n * `products` (#1179) is the sorted list of products whose `product:<name>`-tagged projects reach\n * this project over `dependsOn` — its blast radius. Absent when no product reaches it.\n *\n * `framework` is the project's libType — the SET of runtime environments it is\n * validated to run in, drawn from browser | react | angular | node | express\n * (e.g. [\"browser\",\"node\"]). It comes from the project's `framework:` nx tags\n * and is enforced across edges by the `library-types-match-client` rule.\n *\n * The legacy format (flat { \"<project>\": { level, dependsOn } } map) is still\n * readable so validation against a pre-upgrade file produces a clean\n * \"re-run architecture:generate\" diff instead of a parse failure.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { EnhancedGraph, GraphEntry } from './graph-sorter';\nimport { RuleFailError, Option } from '@webpieces/rules-config';\nimport type { ExternalSystemDecls, ProjectApiRelations } from './api-usage/api-relations';\nimport { API_CONTRACT_FILES_RULE, API_CONTRACTS_DIR } from './api-contract-files';\nimport type { ApiContractFileRefs } from './api-contract-files';\nimport { RemoteServicesCodec } from './remote-services';\nimport type { RemoteServices } from './remote-services';\nimport { toError } from '../toError';\n\n/**\n * Default path for the dependencies file (relative to workspace root)\n */\nexport const DEFAULT_GRAPH_PATH = 'architecture/dependencies.json';\n\n/**\n * Top-level instructions embedded in dependencies.json telling AI how to use\n * the per-project metadata fields.\n */\nexport const AI_INSTRUCTIONS =\n \"Each project's shortDescription is only a summary. BEFORE adding code to a project, \" +\n 'read its responsibilitiesFile for the full responsibilities (what belongs in that ' +\n 'project and what does not), and read its designFile to understand the DI design ' +\n 'before reading the code. Use the entries in `commands` to regenerate these files ' +\n 'or display any of the graphs in a browser. To hide a project (its box AND every ' +\n 'edge touching it) from BOTH rendered architecture graphs, add the nx tag ' +\n \"'drawOnGraph:false' to that project's project.json tags and regenerate; it stays \" +\n 'in this file (marked \"drawOnGraph\": false) but is omitted from the HTML. A role:server ' +\n 'with no webpiecesRuntime package anywhere in its dependency closure and no apiRelations is ' +\n 'omitted from the RUNTIME drawing the same way — it speaks none of the webpieces runtime, so ' +\n \"it can only ever draw as a disconnected box; it stays in both JSON files. An API's endpoints \" +\n '(name, kind, path, httpMethod, parameters, queueName, caller) are NOT in this file: ' +\n '`apiContractFiles` maps each API to its generated contract file, apis/<ApiName>.json relative to ' +\n \"this file — read that file for the API's endpoints.\";\n\n/**\n * Named command → \"command — what it does\" map embedded in dependencies.json.\n */\nexport type CommandMap = Record<string, string>;\n\n/**\n * Commands embedded in dependencies.json so AI (and humans) know how to\n * regenerate and DISPLAY the architecture + design graphs. These work in any\n * repo consuming @webpieces/nx-webpieces-rules.\n */\nexport const GRAPH_COMMANDS: CommandMap = {\n regenerateArchitecture:\n 'pnpm nx run architecture:generate — rewrites architecture/dependencies.json, ' +\n 'architecture/apis/<ApiName>.json and architecture/runtime-dependencies.json; run after ' +\n 'reviewing project runtime-deps.json and API contract candidates into architecture/apis; generation reads approvals and never approves endpoint changes',\n visualizeArchitecture:\n 'pnpm nx run architecture:visualize — opens the monorepo dependency graph (this file) ' +\n 'as HTML in a browser from saved data, without refreshing; freshness unknown',\n visualizeRuntimeArchitecture:\n 'pnpm nx run architecture:visualize-runtime — opens the runtime microservice call graph ' +\n 'as HTML in a browser from saved data, without refreshing; freshness unknown',\n regenerateDesigns:\n \"pnpm nx run-many --target=di-graph-generate — rewrites every project's \" +\n 'design.json/design.md (also runs automatically on every build)',\n visualizeDesign:\n \"pnpm wp-design-visualize <project> — opens a project's DI designs (its designFile) as \" +\n 'HTML, one graph per controller with the controller at the top; no args = interactive picker',\n};\n\n/**\n * The full contents of architecture/dependencies.json.\n */\nexport class DependenciesFile {\n constructor(\n public readonly aiInstructions: string,\n public readonly commands: CommandMap,\n public readonly projects: EnhancedGraph,\n /**\n * apiClassName -> its contract file (`apis/<ApiName>.json`, relative to this file's\n * directory). Only the LINK lives here, so adding an endpoint never changes dependencies.json;\n * the contract itself is read with ApiContractFiles.load.\n */\n public readonly apiContractFiles: ApiContractFileRefs = {},\n /**\n * Declared external systems (databases, buckets, ...) keyed by identity. MUST be persisted:\n * the runtime graph is derived from the committed files, so a declaration that is scanned but\n * not written would make generate and validate disagree.\n */\n public readonly externalSystems: ExternalSystemDecls = {},\n /**\n * REVIEWED out-of-workspace services a `uses` may target (#1203), keyed by service name. Like\n * `apiContractFiles` it is approval metadata: generation reads it and writes it back verbatim,\n * and never infers or adds an entry. See remote-services.ts.\n */\n public readonly remoteServices: RemoteServices = {},\n ) {}\n}\n\n/**\n * Load the blessed graph from disk. Understands both the current wrapper\n * format and the legacy flat map (which loads with empty aiInstructions).\n *\n * @param workspaceRoot - Absolute path to workspace root\n * @param graphPath - Relative path to graph file (default: architecture/dependencies.json)\n * @returns The blessed graph file, or null if it doesn't exist\n */\nexport function loadBlessedGraph(\n workspaceRoot: string,\n graphPath: string = DEFAULT_GRAPH_PATH,\n): DependenciesFile | null {\n const fullPath = path.join(workspaceRoot, graphPath);\n\n if (!fs.existsSync(fullPath)) {\n return null;\n }\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const content = fs.readFileSync(fullPath, 'utf-8');\n const parsed = JSON.parse(content);\n if (parsed !== null && typeof parsed === 'object' && 'apiContracts' in parsed) {\n throw movedApiContractsError(fullPath, graphPath);\n }\n if (parsed !== null && typeof parsed === 'object' && 'projects' in parsed) {\n return new DependenciesFile(\n typeof parsed.aiInstructions === 'string' ? parsed.aiInstructions : '',\n parsed.commands !== null && typeof parsed.commands === 'object'\n ? (parsed.commands as CommandMap)\n : {},\n parsed.projects as EnhancedGraph,\n parsed.apiContractFiles !== null && typeof parsed.apiContractFiles === 'object'\n ? (parsed.apiContractFiles as ApiContractFileRefs)\n : {},\n // Absent in any file written before external systems could be declared; an empty\n // table simply draws no shaped nodes, which is exactly the old rendering.\n parsed.externalSystems !== null && typeof parsed.externalSystems === 'object'\n ? (parsed.externalSystems as ExternalSystemDecls)\n : {},\n new RemoteServicesCodec().decode(parsed.remoteServices, fullPath),\n );\n }\n // Legacy flat format: the whole object is the project map\n return new DependenciesFile('', {}, parsed as EnhancedGraph);\n } catch (err: unknown) {\n const error = toError(err);\n if (error instanceof RuleFailError) throw error;\n throw new Error(`Failed to load graph from ${fullPath}`, { cause: error });\n }\n}\n\n/**\n * The endpoint table moved out of dependencies.json (#949). A file still carrying `apiContracts` was\n * written by an older generator; it is REJECTED rather than read, so there is one place a contract\n * lives and the runtime graph can never read a stale copy of it.\n */\n// webpieces-disable no-function-outside-class -- error builder for loadBlessedGraph, module-scope like its siblings\nfunction movedApiContractsError(fullPath: string, graphPath: string): RuleFailError {\n const apisDir = path.join(path.dirname(graphPath), API_CONTRACTS_DIR);\n return new RuleFailError(\n API_CONTRACT_FILES_RULE,\n `${fullPath} still carries the \\`apiContracts\\` key. It moved: each API's contract now lives in ` +\n `${apisDir}/<ApiName>.json, and dependencies.json only links to it under \\`apiContractFiles\\`.`,\n undefined,\n undefined,\n [\n new Option(\n `Explicitly move each saved apiContracts.<ApiName> value into ${apisDir}/<ApiName>.json, replace apiContracts with reviewed apiContractFiles entries mapping each name to apis/<ApiName>.json, preserve externalSystems, and approve runtime-deps.json for all runtime owners. Then run pnpm nx run architecture:generate and review the result.`,\n true,\n ),\n ],\n );\n}\n\n/**\n * Format the dependencies file as JSON with multi-line arrays for readability\n */\nfunction formatGraphJson(file: DependenciesFile): string {\n const lines: string[] = ['{'];\n lines.push(` \"aiInstructions\": ${JSON.stringify(file.aiInstructions)},`);\n lines.push(` \"commands\": {`);\n const commandNames = Object.keys(file.commands);\n commandNames.forEach((name: string, index: number) => {\n const comma = index === commandNames.length - 1 ? '' : ',';\n lines.push(\n ` ${JSON.stringify(name)}: ${JSON.stringify(file.commands[name])}${comma}`,\n );\n });\n lines.push(` },`);\n lines.push(...apiContractFilesLines(file.apiContractFiles));\n lines.push(...externalSystemsLines(file.externalSystems));\n lines.push(...remoteServicesLines(file.remoteServices));\n lines.push(` \"projects\": {`);\n\n const keys = Object.keys(file.projects).sort();\n keys.forEach((key: string, index: number) => {\n const entry = file.projects[key];\n const isLast = index === keys.length - 1;\n const comma = isLast ? '' : ',';\n\n lines.push(` ${JSON.stringify(key)}: {`);\n lines.push(...formatEntryLines(entry));\n lines.push(` }${comma}`);\n });\n\n lines.push(' }');\n lines.push('}');\n return lines.join('\\n') + '\\n';\n}\n\n/**\n * The `\"apiContractFiles\": {...}` block (4-space indent), with a trailing comma since `projects`\n * always follows it. Omitted entirely when empty. Keys are sorted, so the output is deterministic.\n */\n// webpieces-disable no-function-outside-class -- module-scope formatter, matches the sibling formatters here\nfunction apiContractFilesLines(refs: ApiContractFileRefs): string[] {\n if (Object.keys(refs).length === 0) return [];\n const sorted: ApiContractFileRefs = {};\n for (const api of Object.keys(refs).sort()) sorted[api] = refs[api];\n const pretty = JSON.stringify(sorted, null, 4).split('\\n');\n return pretty.map((line: string, index: number) => {\n const prefix = index === 0 ? '\"apiContractFiles\": ' : '';\n const suffix = index === pretty.length - 1 ? ',' : '';\n return ` ${prefix}${line}${suffix}`;\n });\n}\n\n/**\n * The `\"externalSystems\": {...}` block (4-space indent), with a trailing comma since `projects`\n * always follows. Omitted entirely when empty, so a repo that declares none keeps the exact file\n * shape it had before this existed — which is what makes adopting it a no-op diff.\n */\n// webpieces-disable no-function-outside-class -- module-scope formatter, matches the sibling formatters here\nfunction externalSystemsLines(systems: ExternalSystemDecls): string[] {\n if (Object.keys(systems).length === 0) return [];\n const sorted: ExternalSystemDecls = {};\n for (const identity of Object.keys(systems).sort()) sorted[identity] = systems[identity];\n const pretty = JSON.stringify(sorted, null, 4).split('\\n');\n return pretty.map((line: string, index: number) => {\n const prefix = index === 0 ? '\"externalSystems\": ' : '';\n const suffix = index === pretty.length - 1 ? ',' : '';\n return ` ${prefix}${line}${suffix}`;\n });\n}\n\n/**\n * The reviewed `\"remoteServices\": {...}` block (4-space indent), with a trailing comma since `projects`\n * always follows. Omitted entirely when empty, so a repo that declares none keeps its exact file shape.\n * Service names are sorted; each entry is written as it was reviewed.\n */\n// webpieces-disable no-function-outside-class -- module-scope formatter, matches the sibling formatters here\nfunction remoteServicesLines(services: RemoteServices): string[] {\n if (Object.keys(services).length === 0) return [];\n const sorted: RemoteServices = {};\n for (const name of Object.keys(services).sort()) sorted[name] = services[name];\n const pretty = JSON.stringify(sorted, null, 4).split('\\n');\n return pretty.map((line: string, index: number) => {\n const prefix = index === 0 ? '\"remoteServices\": ' : '';\n const suffix = index === pretty.length - 1 ? ',' : '';\n return ` ${prefix}${line}${suffix}`;\n });\n}\n\n/**\n * Format one project entry's fields (12-space indent). Optional metadata\n * fields are only emitted when present.\n */\nfunction formatEntryLines(entry: GraphEntry): string[] {\n const lines: string[] = [];\n lines.push(` \"level\": ${entry.level},`);\n\n pushOptionalArrayField(lines, 'framework', entry.framework);\n pushOptionalField(lines, 'role', entry.role);\n // Right after role — role says what the project IS, this says what runtime it speaks. MUST be\n // persisted: validate re-derives the runtime graph from this file, so a field written only\n // in-memory by generate would make the two derive different graphs.\n pushOptionalArrayField(lines, 'webpiecesRuntime', entry.webpiecesRuntime);\n pushOptionalField(lines, 'serviceName', entry.serviceName);\n pushOptionalCallsServiceField(lines, entry.callsService);\n pushOptionalBooleanField(lines, 'drawOnGraph', entry.drawOnGraph);\n // MUST be persisted: validate re-derives the runtime graph from this file, so a cut written only\n // in-memory by generate would make generate and validate level (and cycle-check) different graphs.\n pushOptionalArrayField(lines, 'cutLegacyCycle', entry.cutLegacyCycle);\n // MUST be persisted: the runtime graph's product membership is re-derived from this file during\n // validate, and the page's Product filter / color mode read it (#1179).\n pushOptionalArrayField(lines, 'products', entry.products);\n pushOptionalField(lines, 'shortDescription', entry.shortDescription);\n pushOptionalField(lines, 'responsibilitiesFile', entry.responsibilitiesFile);\n pushOptionalField(lines, 'designFile', entry.designFile);\n pushApiRelationsField(lines, entry.apiRelations);\n pushOptionalField(lines, 'runtimeDeclaration', entry.runtimeDeclaration);\n pushOptionalBooleanField(lines, 'runtimeComposition', entry.runtimeComposition);\n\n if (entry.dependsOn.length === 0) {\n lines.push(` \"dependsOn\": []`);\n } else {\n lines.push(` \"dependsOn\": [`);\n entry.dependsOn.forEach((dep: string, depIndex: number) => {\n const depComma = depIndex === entry.dependsOn.length - 1 ? '' : ',';\n lines.push(` ${JSON.stringify(dep)}${depComma}`);\n });\n lines.push(` ]`);\n }\n return lines;\n}\n\n/**\n * Emit one optional string field (12-space indent), skipped when undefined.\n */\nfunction pushOptionalField(lines: string[], field: string, value: string | undefined): void {\n if (value !== undefined) {\n lines.push(` ${JSON.stringify(field)}: ${JSON.stringify(value)},`);\n }\n}\n\n/**\n * Emit the optional `callsService` field (12-space indent). Unlike serviceName it may be EITHER a\n * single service name (string) OR an { apiClassName: serviceName } map, so it is emitted with\n * JSON.stringify to cover both inline. MUST be persisted here: the runtime graph's target resolution\n * reads it back from the committed dependencies.json during validate — dropping it would make\n * generate (which enriches it in-memory) and validate (which loads this file) derive different graphs.\n */\n// webpieces-disable no-function-outside-class -- module-scope formatter, matches the sibling push*Field helpers here\nfunction pushOptionalCallsServiceField(\n lines: string[],\n value: string | Record<string, string> | undefined,\n): void {\n if (value !== undefined) {\n lines.push(` \"callsService\": ${JSON.stringify(value)},`);\n }\n}\n\n/**\n * Emit one optional boolean field (12-space indent) as a raw JSON boolean\n * (e.g. `\"drawOnGraph\": false,`), skipped when undefined.\n */\n// webpieces-disable no-function-outside-class -- module-scope formatter, matches the sibling push*Field helpers here\nfunction pushOptionalBooleanField(\n lines: string[],\n field: string,\n value: boolean | undefined,\n): void {\n if (value !== undefined) {\n lines.push(` ${JSON.stringify(field)}: ${JSON.stringify(value)},`);\n }\n}\n\n/**\n * Emit one optional string-array field (12-space indent) as a compact inline\n * JSON array (e.g. `\"framework\": [\"browser\",\"node\"],`), skipped when undefined.\n */\nfunction pushOptionalArrayField(lines: string[], field: string, value: string[] | undefined): void {\n if (value !== undefined) {\n lines.push(` ${JSON.stringify(field)}: ${JSON.stringify(value)},`);\n }\n}\n\n/**\n * Emit the optional `apiRelations` object (pretty, multi-line, reindented under\n * the 12-space entry block) with a trailing comma, since `dependsOn` always\n * follows it. Skipped when absent/empty so plain-lib-only projects stay compact.\n * The scanner already sorts owners + refs, so the JSON is deterministic.\n */\n// webpieces-disable no-function-outside-class -- module-scope formatter, matches the sibling push*Field helpers here\nfunction pushApiRelationsField(lines: string[], value: ProjectApiRelations | undefined): void {\n if (value === undefined || Object.keys(value).length === 0) return;\n const pretty = JSON.stringify(value, null, 4).split('\\n');\n pretty.forEach((line: string, index: number) => {\n const prefix = index === 0 ? '\"apiRelations\": ' : '';\n const suffix = index === pretty.length - 1 ? ',' : '';\n lines.push(` ${prefix}${line}${suffix}`);\n });\n}\n\n/**\n * Save the graph to disk in the wrapper format with the standard aiInstructions.\n *\n * @param graph - The enriched project graph to save\n * @param workspaceRoot - Absolute path to workspace root\n * @param graphPath - Relative path to graph file (default: architecture/dependencies.json)\n */\nexport function saveGraph(\n graph: EnhancedGraph,\n workspaceRoot: string,\n graphPath: string = DEFAULT_GRAPH_PATH,\n apiContractFiles: ApiContractFileRefs = {},\n externalSystems: ExternalSystemDecls = {},\n remoteServices: RemoteServices = {},\n): void {\n const fullPath = path.join(workspaceRoot, graphPath);\n const dir = path.dirname(fullPath);\n\n // Ensure directory exists\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n // Sort keys for deterministic output\n const sortedGraph: EnhancedGraph = {};\n const sortedKeys = Object.keys(graph).sort();\n for (const key of sortedKeys) {\n sortedGraph[key] = graph[key];\n }\n\n const content = formatGraphJson(\n new DependenciesFile(\n AI_INSTRUCTIONS,\n GRAPH_COMMANDS,\n sortedGraph,\n apiContractFiles,\n externalSystems,\n remoteServices,\n ),\n );\n fs.writeFileSync(fullPath, content, 'utf-8');\n}\n\n/**\n * Check if the graph file exists\n */\nexport function graphFileExists(\n workspaceRoot: string,\n graphPath: string = DEFAULT_GRAPH_PATH,\n): boolean {\n const fullPath = path.join(workspaceRoot, graphPath);\n return fs.existsSync(fullPath);\n}\n"]}
1
+ {"version":3,"file":"graph-loader.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-loader.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Graph Loader\n *\n * Handles loading and saving the blessed dependency graph file.\n * The graph is stored at architecture/dependencies.json in the workspace root.\n *\n * File format (schema aimed at AI consumers):\n * {\n * \"aiInstructions\": \"...how AI should use the per-project fields...\",\n * \"apiContractFiles\": { \"<ApiName>\": \"apis/<ApiName>.json\" },\n * \"remoteServices\": { \"<serviceName>\": { \"label\"?, \"implements\": [{ \"api\", \"type\" }] } },\n * \"projects\": {\n * \"<project>\": { level, framework, products, shortDescription,\n * responsibilitiesFile, designFile, dependsOn }\n * }\n * }\n *\n * `level` is the project's dependency depth, except that every server, client and app is pinned to\n * the TOP level (bundles above them), so the top row is exactly the entry points (#1179).\n *\n * `products` (#1179) is the sorted list of the project's own declared `product:<name>` tags (#1216).\n * Absent for an untagged project.\n *\n * `framework` is the project's libType — the SET of runtime environments it is\n * validated to run in, drawn from browser | react | angular | node | express\n * (e.g. [\"browser\",\"node\"]). It comes from the project's `framework:` nx tags\n * and is enforced across edges by the `library-types-match-client` rule.\n *\n * The legacy format (flat { \"<project>\": { level, dependsOn } } map) is still\n * readable so validation against a pre-upgrade file produces a clean\n * \"re-run architecture:generate\" diff instead of a parse failure.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { EnhancedGraph, GraphEntry } from './graph-sorter';\nimport { RuleFailError, Option } from '@webpieces/rules-config';\nimport type { ExternalSystemDecls, ProjectApiRelations } from './api-usage/api-relations';\nimport { API_CONTRACT_FILES_RULE, API_CONTRACTS_DIR } from './api-contract-files';\nimport type { ApiContractFileRefs } from './api-contract-files';\nimport { RemoteServicesCodec } from './remote-services';\nimport type { RemoteServices } from './remote-services';\nimport { toError } from '../toError';\n\n/**\n * Default path for the dependencies file (relative to workspace root)\n */\nexport const DEFAULT_GRAPH_PATH = 'architecture/dependencies.json';\n\n/**\n * Top-level instructions embedded in dependencies.json telling AI how to use\n * the per-project metadata fields.\n */\nexport const AI_INSTRUCTIONS =\n \"Each project's shortDescription is only a summary. BEFORE adding code to a project, \" +\n 'read its responsibilitiesFile for the full responsibilities (what belongs in that ' +\n 'project and what does not), and read its designFile to understand the DI design ' +\n 'before reading the code. Use the entries in `commands` to regenerate these files ' +\n 'or display any of the graphs in a browser. To hide a project (its box AND every ' +\n 'edge touching it) from BOTH rendered architecture graphs, add the nx tag ' +\n \"'drawOnGraph:false' to that project's project.json tags and regenerate; it stays \" +\n 'in this file (marked \"drawOnGraph\": false) but is omitted from the HTML. A role:server ' +\n 'with no webpiecesRuntime package anywhere in its dependency closure and no apiRelations is ' +\n 'omitted from the RUNTIME drawing the same way — it speaks none of the webpieces runtime, so ' +\n \"it can only ever draw as a disconnected box; it stays in both JSON files. An API's endpoints \" +\n '(name, kind, path, httpMethod, parameters, queueName, caller) are NOT in this file: ' +\n '`apiContractFiles` maps each API to its generated contract file, apis/<ApiName>.json relative to ' +\n \"this file — read that file for the API's endpoints.\";\n\n/**\n * Named command → \"command — what it does\" map embedded in dependencies.json.\n */\nexport type CommandMap = Record<string, string>;\n\n/**\n * Commands embedded in dependencies.json so AI (and humans) know how to\n * regenerate and DISPLAY the architecture + design graphs. These work in any\n * repo consuming @webpieces/nx-webpieces-rules.\n */\nexport const GRAPH_COMMANDS: CommandMap = {\n regenerateArchitecture:\n 'pnpm nx run architecture:generate — rewrites architecture/dependencies.json, ' +\n 'architecture/apis/<ApiName>.json and architecture/runtime-dependencies.json; run after ' +\n 'reviewing project runtime-deps.json and API contract candidates into architecture/apis; generation reads approvals and never approves endpoint changes',\n visualizeArchitecture:\n 'pnpm nx run architecture:visualize — opens the monorepo dependency graph (this file) ' +\n 'as HTML in a browser from saved data, without refreshing; freshness unknown',\n visualizeRuntimeArchitecture:\n 'pnpm nx run architecture:visualize-runtime — opens the runtime microservice call graph ' +\n 'as HTML in a browser from saved data, without refreshing; freshness unknown',\n regenerateDesigns:\n \"pnpm nx run-many --target=di-graph-generate — rewrites every project's \" +\n 'design.json/design.md (also runs automatically on every build)',\n visualizeDesign:\n \"pnpm wp-design-visualize <project> — opens a project's DI designs (its designFile) as \" +\n 'HTML, one graph per controller with the controller at the top; no args = interactive picker',\n};\n\n/**\n * The full contents of architecture/dependencies.json.\n */\nexport class DependenciesFile {\n constructor(\n public readonly aiInstructions: string,\n public readonly commands: CommandMap,\n public readonly projects: EnhancedGraph,\n /**\n * apiClassName -> its contract file (`apis/<ApiName>.json`, relative to this file's\n * directory). Only the LINK lives here, so adding an endpoint never changes dependencies.json;\n * the contract itself is read with ApiContractFiles.load.\n */\n public readonly apiContractFiles: ApiContractFileRefs = {},\n /**\n * Declared external systems (databases, buckets, ...) keyed by identity. MUST be persisted:\n * the runtime graph is derived from the committed files, so a declaration that is scanned but\n * not written would make generate and validate disagree.\n */\n public readonly externalSystems: ExternalSystemDecls = {},\n /**\n * REVIEWED out-of-workspace services a `uses` may target (#1203), keyed by service name. Like\n * `apiContractFiles` it is approval metadata: generation reads it and writes it back verbatim,\n * and never infers or adds an entry. See remote-services.ts.\n */\n public readonly remoteServices: RemoteServices = {},\n ) {}\n}\n\n/**\n * Load the blessed graph from disk. Understands both the current wrapper\n * format and the legacy flat map (which loads with empty aiInstructions).\n *\n * @param workspaceRoot - Absolute path to workspace root\n * @param graphPath - Relative path to graph file (default: architecture/dependencies.json)\n * @returns The blessed graph file, or null if it doesn't exist\n */\nexport function loadBlessedGraph(\n workspaceRoot: string,\n graphPath: string = DEFAULT_GRAPH_PATH,\n): DependenciesFile | null {\n const fullPath = path.join(workspaceRoot, graphPath);\n\n if (!fs.existsSync(fullPath)) {\n return null;\n }\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const content = fs.readFileSync(fullPath, 'utf-8');\n const parsed = JSON.parse(content);\n if (parsed !== null && typeof parsed === 'object' && 'apiContracts' in parsed) {\n throw movedApiContractsError(fullPath, graphPath);\n }\n if (parsed !== null && typeof parsed === 'object' && 'projects' in parsed) {\n return new DependenciesFile(\n typeof parsed.aiInstructions === 'string' ? parsed.aiInstructions : '',\n parsed.commands !== null && typeof parsed.commands === 'object'\n ? (parsed.commands as CommandMap)\n : {},\n parsed.projects as EnhancedGraph,\n parsed.apiContractFiles !== null && typeof parsed.apiContractFiles === 'object'\n ? (parsed.apiContractFiles as ApiContractFileRefs)\n : {},\n // Absent in any file written before external systems could be declared; an empty\n // table simply draws no shaped nodes, which is exactly the old rendering.\n parsed.externalSystems !== null && typeof parsed.externalSystems === 'object'\n ? (parsed.externalSystems as ExternalSystemDecls)\n : {},\n new RemoteServicesCodec().decode(parsed.remoteServices, fullPath),\n );\n }\n // Legacy flat format: the whole object is the project map\n return new DependenciesFile('', {}, parsed as EnhancedGraph);\n } catch (err: unknown) {\n const error = toError(err);\n if (error instanceof RuleFailError) throw error;\n throw new Error(`Failed to load graph from ${fullPath}`, { cause: error });\n }\n}\n\n/**\n * The endpoint table moved out of dependencies.json (#949). A file still carrying `apiContracts` was\n * written by an older generator; it is REJECTED rather than read, so there is one place a contract\n * lives and the runtime graph can never read a stale copy of it.\n */\n// webpieces-disable no-function-outside-class -- error builder for loadBlessedGraph, module-scope like its siblings\nfunction movedApiContractsError(fullPath: string, graphPath: string): RuleFailError {\n const apisDir = path.join(path.dirname(graphPath), API_CONTRACTS_DIR);\n return new RuleFailError(\n API_CONTRACT_FILES_RULE,\n `${fullPath} still carries the \\`apiContracts\\` key. It moved: each API's contract now lives in ` +\n `${apisDir}/<ApiName>.json, and dependencies.json only links to it under \\`apiContractFiles\\`.`,\n undefined,\n undefined,\n [\n new Option(\n `Explicitly move each saved apiContracts.<ApiName> value into ${apisDir}/<ApiName>.json, replace apiContracts with reviewed apiContractFiles entries mapping each name to apis/<ApiName>.json, preserve externalSystems, and approve runtime-deps.json for all runtime owners. Then run pnpm nx run architecture:generate and review the result.`,\n true,\n ),\n ],\n );\n}\n\n/**\n * Format the dependencies file as JSON with multi-line arrays for readability\n */\nfunction formatGraphJson(file: DependenciesFile): string {\n const lines: string[] = ['{'];\n lines.push(` \"aiInstructions\": ${JSON.stringify(file.aiInstructions)},`);\n lines.push(` \"commands\": {`);\n const commandNames = Object.keys(file.commands);\n commandNames.forEach((name: string, index: number) => {\n const comma = index === commandNames.length - 1 ? '' : ',';\n lines.push(\n ` ${JSON.stringify(name)}: ${JSON.stringify(file.commands[name])}${comma}`,\n );\n });\n lines.push(` },`);\n lines.push(...apiContractFilesLines(file.apiContractFiles));\n lines.push(...externalSystemsLines(file.externalSystems));\n lines.push(...remoteServicesLines(file.remoteServices));\n lines.push(` \"projects\": {`);\n\n const keys = Object.keys(file.projects).sort();\n keys.forEach((key: string, index: number) => {\n const entry = file.projects[key];\n const isLast = index === keys.length - 1;\n const comma = isLast ? '' : ',';\n\n lines.push(` ${JSON.stringify(key)}: {`);\n lines.push(...formatEntryLines(entry));\n lines.push(` }${comma}`);\n });\n\n lines.push(' }');\n lines.push('}');\n return lines.join('\\n') + '\\n';\n}\n\n/**\n * The `\"apiContractFiles\": {...}` block (4-space indent), with a trailing comma since `projects`\n * always follows it. Omitted entirely when empty. Keys are sorted, so the output is deterministic.\n */\n// webpieces-disable no-function-outside-class -- module-scope formatter, matches the sibling formatters here\nfunction apiContractFilesLines(refs: ApiContractFileRefs): string[] {\n if (Object.keys(refs).length === 0) return [];\n const sorted: ApiContractFileRefs = {};\n for (const api of Object.keys(refs).sort()) sorted[api] = refs[api];\n const pretty = JSON.stringify(sorted, null, 4).split('\\n');\n return pretty.map((line: string, index: number) => {\n const prefix = index === 0 ? '\"apiContractFiles\": ' : '';\n const suffix = index === pretty.length - 1 ? ',' : '';\n return ` ${prefix}${line}${suffix}`;\n });\n}\n\n/**\n * The `\"externalSystems\": {...}` block (4-space indent), with a trailing comma since `projects`\n * always follows. Omitted entirely when empty, so a repo that declares none keeps the exact file\n * shape it had before this existed — which is what makes adopting it a no-op diff.\n */\n// webpieces-disable no-function-outside-class -- module-scope formatter, matches the sibling formatters here\nfunction externalSystemsLines(systems: ExternalSystemDecls): string[] {\n if (Object.keys(systems).length === 0) return [];\n const sorted: ExternalSystemDecls = {};\n for (const identity of Object.keys(systems).sort()) sorted[identity] = systems[identity];\n const pretty = JSON.stringify(sorted, null, 4).split('\\n');\n return pretty.map((line: string, index: number) => {\n const prefix = index === 0 ? '\"externalSystems\": ' : '';\n const suffix = index === pretty.length - 1 ? ',' : '';\n return ` ${prefix}${line}${suffix}`;\n });\n}\n\n/**\n * The reviewed `\"remoteServices\": {...}` block (4-space indent), with a trailing comma since `projects`\n * always follows. Omitted entirely when empty, so a repo that declares none keeps its exact file shape.\n * Service names are sorted; each entry is written as it was reviewed.\n */\n// webpieces-disable no-function-outside-class -- module-scope formatter, matches the sibling formatters here\nfunction remoteServicesLines(services: RemoteServices): string[] {\n if (Object.keys(services).length === 0) return [];\n const sorted: RemoteServices = {};\n for (const name of Object.keys(services).sort()) sorted[name] = services[name];\n const pretty = JSON.stringify(sorted, null, 4).split('\\n');\n return pretty.map((line: string, index: number) => {\n const prefix = index === 0 ? '\"remoteServices\": ' : '';\n const suffix = index === pretty.length - 1 ? ',' : '';\n return ` ${prefix}${line}${suffix}`;\n });\n}\n\n/**\n * Format one project entry's fields (12-space indent). Optional metadata\n * fields are only emitted when present.\n */\nfunction formatEntryLines(entry: GraphEntry): string[] {\n const lines: string[] = [];\n lines.push(` \"level\": ${entry.level},`);\n\n pushOptionalArrayField(lines, 'framework', entry.framework);\n pushOptionalField(lines, 'role', entry.role);\n // Right after role — role says what the project IS, this says what runtime it speaks. MUST be\n // persisted: validate re-derives the runtime graph from this file, so a field written only\n // in-memory by generate would make the two derive different graphs.\n pushOptionalArrayField(lines, 'webpiecesRuntime', entry.webpiecesRuntime);\n pushOptionalField(lines, 'serviceName', entry.serviceName);\n pushOptionalCallsServiceField(lines, entry.callsService);\n pushOptionalBooleanField(lines, 'drawOnGraph', entry.drawOnGraph);\n // MUST be persisted: validate re-derives the runtime graph from this file, so a cut written only\n // in-memory by generate would make generate and validate level (and cycle-check) different graphs.\n pushOptionalArrayField(lines, 'cutLegacyCycle', entry.cutLegacyCycle);\n // MUST be persisted: the runtime graph's product membership is re-derived from this file during\n // validate, and the page's Product filter / color mode read it (#1179).\n pushOptionalArrayField(lines, 'products', entry.products);\n pushOptionalField(lines, 'shortDescription', entry.shortDescription);\n pushOptionalField(lines, 'responsibilitiesFile', entry.responsibilitiesFile);\n pushOptionalField(lines, 'designFile', entry.designFile);\n pushApiRelationsField(lines, entry.apiRelations);\n pushOptionalField(lines, 'runtimeDeclaration', entry.runtimeDeclaration);\n pushOptionalBooleanField(lines, 'runtimeComposition', entry.runtimeComposition);\n\n if (entry.dependsOn.length === 0) {\n lines.push(` \"dependsOn\": []`);\n } else {\n lines.push(` \"dependsOn\": [`);\n entry.dependsOn.forEach((dep: string, depIndex: number) => {\n const depComma = depIndex === entry.dependsOn.length - 1 ? '' : ',';\n lines.push(` ${JSON.stringify(dep)}${depComma}`);\n });\n lines.push(` ]`);\n }\n return lines;\n}\n\n/**\n * Emit one optional string field (12-space indent), skipped when undefined.\n */\nfunction pushOptionalField(lines: string[], field: string, value: string | undefined): void {\n if (value !== undefined) {\n lines.push(` ${JSON.stringify(field)}: ${JSON.stringify(value)},`);\n }\n}\n\n/**\n * Emit the optional `callsService` field (12-space indent). Unlike serviceName it may be EITHER a\n * single service name (string) OR an { apiClassName: serviceName } map, so it is emitted with\n * JSON.stringify to cover both inline. MUST be persisted here: the runtime graph's target resolution\n * reads it back from the committed dependencies.json during validate — dropping it would make\n * generate (which enriches it in-memory) and validate (which loads this file) derive different graphs.\n */\n// webpieces-disable no-function-outside-class -- module-scope formatter, matches the sibling push*Field helpers here\nfunction pushOptionalCallsServiceField(\n lines: string[],\n value: string | Record<string, string> | undefined,\n): void {\n if (value !== undefined) {\n lines.push(` \"callsService\": ${JSON.stringify(value)},`);\n }\n}\n\n/**\n * Emit one optional boolean field (12-space indent) as a raw JSON boolean\n * (e.g. `\"drawOnGraph\": false,`), skipped when undefined.\n */\n// webpieces-disable no-function-outside-class -- module-scope formatter, matches the sibling push*Field helpers here\nfunction pushOptionalBooleanField(\n lines: string[],\n field: string,\n value: boolean | undefined,\n): void {\n if (value !== undefined) {\n lines.push(` ${JSON.stringify(field)}: ${JSON.stringify(value)},`);\n }\n}\n\n/**\n * Emit one optional string-array field (12-space indent) as a compact inline\n * JSON array (e.g. `\"framework\": [\"browser\",\"node\"],`), skipped when undefined.\n */\nfunction pushOptionalArrayField(lines: string[], field: string, value: string[] | undefined): void {\n if (value !== undefined) {\n lines.push(` ${JSON.stringify(field)}: ${JSON.stringify(value)},`);\n }\n}\n\n/**\n * Emit the optional `apiRelations` object (pretty, multi-line, reindented under\n * the 12-space entry block) with a trailing comma, since `dependsOn` always\n * follows it. Skipped when absent/empty so plain-lib-only projects stay compact.\n * The scanner already sorts owners + refs, so the JSON is deterministic.\n */\n// webpieces-disable no-function-outside-class -- module-scope formatter, matches the sibling push*Field helpers here\nfunction pushApiRelationsField(lines: string[], value: ProjectApiRelations | undefined): void {\n if (value === undefined || Object.keys(value).length === 0) return;\n const pretty = JSON.stringify(value, null, 4).split('\\n');\n pretty.forEach((line: string, index: number) => {\n const prefix = index === 0 ? '\"apiRelations\": ' : '';\n const suffix = index === pretty.length - 1 ? ',' : '';\n lines.push(` ${prefix}${line}${suffix}`);\n });\n}\n\n/**\n * Save the graph to disk in the wrapper format with the standard aiInstructions.\n *\n * @param graph - The enriched project graph to save\n * @param workspaceRoot - Absolute path to workspace root\n * @param graphPath - Relative path to graph file (default: architecture/dependencies.json)\n */\nexport function saveGraph(\n graph: EnhancedGraph,\n workspaceRoot: string,\n graphPath: string = DEFAULT_GRAPH_PATH,\n apiContractFiles: ApiContractFileRefs = {},\n externalSystems: ExternalSystemDecls = {},\n remoteServices: RemoteServices = {},\n): void {\n const fullPath = path.join(workspaceRoot, graphPath);\n const dir = path.dirname(fullPath);\n\n // Ensure directory exists\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n // Sort keys for deterministic output\n const sortedGraph: EnhancedGraph = {};\n const sortedKeys = Object.keys(graph).sort();\n for (const key of sortedKeys) {\n sortedGraph[key] = graph[key];\n }\n\n const content = formatGraphJson(\n new DependenciesFile(\n AI_INSTRUCTIONS,\n GRAPH_COMMANDS,\n sortedGraph,\n apiContractFiles,\n externalSystems,\n remoteServices,\n ),\n );\n fs.writeFileSync(fullPath, content, 'utf-8');\n}\n\n/**\n * Check if the graph file exists\n */\nexport function graphFileExists(\n workspaceRoot: string,\n graphPath: string = DEFAULT_GRAPH_PATH,\n): boolean {\n const fullPath = path.join(workspaceRoot, graphPath);\n return fs.existsSync(fullPath);\n}\n"]}
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Fills the AI-oriented fields on each architecture/dependencies.json entry:
5
5
  * framework — from `framework:<x>` nx tag or package.json inference
6
- * products — the products reaching it, from the `product:<name>` nx tags (#1179)
6
+ * products — exactly its declared `product:<name>` nx tags (#1179, #1216)
7
7
  * level — re-levelled so servers, clients and apps form the top row (#1179)
8
8
  * serviceName — from project.json metadata.webpieces.serviceName; the
9
9
  * name clients address this app by at runtime
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Fills the AI-oriented fields on each architecture/dependencies.json entry:
6
6
  * framework — from `framework:<x>` nx tag or package.json inference
7
- * products — the products reaching it, from the `product:<name>` nx tags (#1179)
7
+ * products — exactly its declared `product:<name>` nx tags (#1179, #1216)
8
8
  * level — re-levelled so servers, clients and apps form the top row (#1179)
9
9
  * serviceName — from project.json metadata.webpieces.serviceName; the
10
10
  * name clients address this app by at runtime
@@ -75,7 +75,7 @@ function enrichGraph(graph, infos, workspaceRoot) {
75
75
  const problems = [];
76
76
  // project -> declared serviceName, collected so duplicates can be reported across the workspace.
77
77
  const serviceNames = new Map();
78
- // project -> the products it DECLARES (`product:` tags); membership is derived from these below.
78
+ // project -> the products it DECLARES (`product:` tags); membership is exactly these (#1216).
79
79
  const productSeeds = new Map();
80
80
  const products = new product_resolver_1.ProductResolver();
81
81
  for (const [projectName, entry] of Object.entries(graph)) {
@@ -108,8 +108,7 @@ function enrichGraph(graph, infos, workspaceRoot) {
108
108
  if (problems.length > 0) {
109
109
  throw new MetadataValidationError(problems);
110
110
  }
111
- // #1179/#1201: declared product tags are final; an untagged library belongs to every product whose
112
- // tagged project reaches it without passing through another tagged project.
111
+ // #1179/#1216: a project's products are exactly its declared product tags; an untagged one has none.
113
112
  new graph_products_1.ProductMembership().compute(productSeeds, graph).forEach((member, projectName) => {
114
113
  graph[projectName].products = member;
115
114
  });