@webpieces/nx-webpieces-rules 0.4.882 → 0.4.884
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -13
- package/package.json +7 -7
- package/src/configs/tag-truth-configs.d.ts +15 -0
- package/src/configs/tag-truth-configs.js +20 -1
- package/src/configs/tag-truth-configs.js.map +1 -1
- package/src/executors/generate/executor.js +3 -0
- package/src/executors/generate/executor.js.map +1 -1
- package/src/index.d.ts +6 -3
- package/src/index.js +15 -1
- package/src/index.js.map +1 -1
- package/src/lib/graph-color-modes.d.ts +50 -6
- package/src/lib/graph-color-modes.js +88 -8
- package/src/lib/graph-color-modes.js.map +1 -1
- package/src/lib/graph-comparator.js +2 -0
- package/src/lib/graph-comparator.js.map +1 -1
- package/src/lib/graph-entry-levels.d.ts +35 -0
- package/src/lib/graph-entry-levels.js +60 -0
- package/src/lib/graph-entry-levels.js.map +1 -0
- package/src/lib/graph-impact.d.ts +5 -5
- package/src/lib/graph-impact.js +5 -5
- package/src/lib/graph-impact.js.map +1 -1
- package/src/lib/graph-legend.d.ts +11 -1
- package/src/lib/graph-legend.js +44 -6
- package/src/lib/graph-legend.js.map +1 -1
- package/src/lib/graph-level-bands.js +3 -1
- package/src/lib/graph-level-bands.js.map +1 -1
- package/src/lib/graph-loader.d.ts +7 -1
- package/src/lib/graph-loader.js +10 -1
- package/src/lib/graph-loader.js.map +1 -1
- package/src/lib/graph-metadata.d.ts +2 -0
- package/src/lib/graph-metadata.js +19 -0
- package/src/lib/graph-metadata.js.map +1 -1
- package/src/lib/graph-page-shell.d.ts +28 -6
- package/src/lib/graph-page-shell.js +55 -18
- package/src/lib/graph-page-shell.js.map +1 -1
- package/src/lib/graph-page-styles.js +3 -1
- package/src/lib/graph-page-styles.js.map +1 -1
- package/src/lib/graph-products.d.ts +79 -0
- package/src/lib/graph-products.js +199 -0
- package/src/lib/graph-products.js.map +1 -0
- package/src/lib/graph-render-model.d.ts +13 -2
- package/src/lib/graph-render-model.js +36 -4
- package/src/lib/graph-render-model.js.map +1 -1
- package/src/lib/graph-sorter.d.ts +13 -0
- package/src/lib/graph-sorter.js.map +1 -1
- package/src/lib/graph-visualizer.client.d.ts +20 -10
- package/src/lib/graph-visualizer.client.js +67 -31
- package/src/lib/graph-visualizer.client.js.map +1 -1
- package/src/lib/graph-visualizer.js +7 -6
- package/src/lib/graph-visualizer.js.map +1 -1
- package/src/lib/product-resolver.d.ts +38 -0
- package/src/lib/product-resolver.js +63 -0
- package/src/lib/product-resolver.js.map +1 -0
- package/src/lib/runtime-graph-model.d.ts +7 -0
- package/src/lib/runtime-graph-model.js.map +1 -1
- package/src/lib/runtime-graph.js +3 -0
- package/src/lib/runtime-graph.js.map +1 -1
- package/src/lib/runtime-html-page.d.ts +7 -0
- package/src/lib/runtime-html-page.js +29 -0
- package/src/lib/runtime-html-page.js.map +1 -1
- package/src/lib/runtime-visualizer.client.d.ts +7 -0
- package/src/lib/runtime-visualizer.client.js +32 -0
- package/src/lib/runtime-visualizer.client.js.map +1 -1
- package/src/lib/runtime-visualizer.js +3 -1
- package/src/lib/runtime-visualizer.js.map +1 -1
- package/src/lib/saved-snapshot.js +6 -2
- package/src/lib/saved-snapshot.js.map +1 -1
- package/src/lib/tag-truth.d.ts +20 -3
- package/src/lib/tag-truth.js +48 -7
- package/src/lib/tag-truth.js.map +1 -1
- package/src/nx-policy-registry.js +1 -0
- package/src/nx-policy-registry.js.map +1 -1
- package/src/rule-help.js +1 -0
- package/src/rule-help.js.map +1 -1
- package/src/rule-settings.js +12 -1
- package/src/rule-settings.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-visualizer.client.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-visualizer.client.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,+FAA+F;AAC/F,qGAAqG;AACrG,mGAAmG;AAEnG,+FAA+F;AAC/F,MAAM,SAAS,GAAG,IAAI,CAAC;AACvB,oEAAoE;AACpE,MAAM,SAAS,GAAG,EAAE,CAAC;AAErB,6FAA6F;AAC7F,MAAM,MAAM;IAEK;IACA;IACA;IACA;IAJb,YACa,EAAU,EACV,EAAU,EACV,EAAU,EACV,EAAU;QAHV,OAAE,GAAF,EAAE,CAAQ;QACV,OAAE,GAAF,EAAE,CAAQ;QACV,OAAE,GAAF,EAAE,CAAQ;QACV,OAAE,GAAF,EAAE,CAAQ;IACpB,CAAC;IAEJ,KAAK;QACD,OAAO,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;IAC7B,CAAC;IAED,UAAU;QACN,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IACnC,CAAC;CACJ;AAED;;;;;GAKG;AACH,MAAM,cAAc;IACR,MAAM,CAAU,MAAM,GAAG,4BAA4B,CAAC;IAE9D,OAAO,CAAC,GAAkB;QACtB,GAAG,CAAC,gBAAgB,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,CAAC,IAAa,EAAQ,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACjG,CAAC;IAED;;;;;;OAMG;IACK,OAAO,CAAC,IAAa;QACzB,MAAM,MAAM,GAAG,IAAI,CAAC,gBAAgB,CAAC,yBAAyB,CAAC,CAAC;QAChE,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAChC,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAClC,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,CAAC,KAAK,EAAE,GAAG,SAAS;YAAE,OAAO;QAEpD,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACxB,MAAM,IAAI,GAAG,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC;QAClD,MAAM,MAAM,GAAG,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC;QACvD,sFAAsF;QACtF,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QAC1C,MAAM,CAAC,OAAO,CAAC,CAAC,KAAc,EAAQ,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAE9D,MAAM,EAAE,GAAG,GAAG,CAAC,UAAU,EAAE,CAAC;QAC5B,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,EAAE,SAAS,CAAC,CAAC;QAC9C,MAAM,IAAI,GACN,GAAG,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,EAAE;YAClC,IAAI,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,EAAE;YACnC,IAAI,GAAG,EAAE,GAAG,GAAG,GAAG,EAAE,GAAG,SAAS,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,EAAE;YAC/D,IAAI,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,EAAE;YACnC,IAAI,GAAG,EAAE,GAAG,GAAG,GAAG,EAAE,GAAG,SAAS,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC;QAC3E,sFAAsF;QACtF,oEAAoE;QACpE,MAAM,GAAG,GACL,GAAG,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,EAAE;YAClC,IAAI,GAAG,EAAE,GAAG,GAAG,GAAG,EAAE,GAAG,SAAS,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,EAAE,CAAC;QAEpE,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC;QAC/B,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,WAAW,KAAK,IAAI;YAAE,OAAO;QAC3D,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;QACvE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;QAEpF,2FAA2F;QAC3F,gGAAgG;QAChG,yFAAyF;QACzF,uCAAuC;IAC3C,CAAC;IAED;;;;;OAKG;IACK,SAAS,CAAC,GAAW,EAAE,EAAU,EAAE,SAAwB;QAC/D,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,GAAG,SAAS,CAAC,CAAC;QAC/C,IAAI,SAAS,KAAK,IAAI;YAAE,OAAO,UAAU,CAAC;QAC1C,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACjE,CAAC;IAED;;;OAGG;IACK,UAAU,CAAC,MAA2B;QAC1C,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACrC,IAAI,KAAK,CAAC,OAAO,KAAK,UAAU;gBAAE,SAAS;YAC3C,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;YACrE,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;gBAAE,SAAS;YAC/C,MAAM,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YAC9B,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG;gBAAE,OAAO,CAAC,CAAC;QAC1D,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,6FAA6F;IACrF,QAAQ,CAAC,MAA2B;QACxC,MAAM,EAAE,GAAa,EAAE,CAAC;QACxB,MAAM,EAAE,GAAa,EAAE,CAAC;QACxB,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACrC,MAAM,GAAG,GAAG,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;YACzC,MAAM,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;YACpD,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;YACvC,IAAI,IAAI,KAAK,IAAI;gBAAE,SAAS;YAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC1C,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC7B,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YACrC,CAAC;QACL,CAAC;QACD,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACjC,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IAC1F,CAAC;IAEO,MAAM,CAAC,IAAY,EAAE,MAAc,EAAE,CAAS;QAClD,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACnE,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC9B,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAClC,EAAE,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACxB,OAAO,EAAE,CAAC;IACd,CAAC;;AAGL,sFAAsF;AACtF,MAAM,eAAe;IACY;IAAqC;IAAmC;IAArG,YAA6B,GAAkB,EAAmB,IAAgB,EAAmB,IAAiB;QAAzF,QAAG,GAAH,GAAG,CAAe;QAAmB,SAAI,GAAJ,IAAI,CAAY;QAAmB,SAAI,GAAJ,IAAI,CAAa;IAAG,CAAC;IAE1H,IAAI;QACA,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAY,EAAE,IAAiB,EAAoB,EAAE;YAC5E,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC;YAC3D,OAAO,CAAC,IAAI,cAAc,CAAC,KAAK,EAAE,GAAS,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;QAClH,CAAC,CAAC,CAAC;IACP,CAAC;CACJ;AAED,MAAM,WAAY,SAAQ,YAAY;IAC1B,IAAI,GAAsB,IAAI,CAAC;IAEpB,cAAc,CAAC,GAAyB;QACvD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC;QAC3B,OAAO,GAAS,EAAE;YACd,IAAI,CAAC,IAAI,GAAG,QAAQ,CAAC;YACrB,IAAI,GAAG,KAAK,IAAI;gBAAE,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7C,CAAC,CAAC;IACN,CAAC;IAEkB,UAAU,CAAC,GAAkB,IAAU,IAAI,cAAc,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAEpF,OAAO,CAAC,GAAkB;QAChC,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI;YAAE,IAAI,CAAC,IAAI,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC;;YACnD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC3B,IAAI,eAAe,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;QACjD,GAAG,CAAC,gBAAgB,CAAc,QAAQ,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;YACvD,IAAI,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,WAAW,KAAK,IAAI,CAAC,MAAM;gBAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QACzG,CAAC,CAAC,CAAC;IACP,CAAC;CACJ;AAED,IAAI,WAAW,CAAC,gBAAgB,CAAC,CAAC,MAAM,EAAE,CAAC","sourcesContent":["/*\n * Browser-side script for tmp/webpieces/runtime-architecture.html.\n *\n * runtime-visualizer.ts reads this file's COMPILED output with readFileSync, substitutes the DOT\n * placeholder (see viz-client-globals.d.ts) with the JSON-encoded DOT, and inlines the result into a <script> tag. It runs in a\n * browser, never in node — but it is ordinary TypeScript, compiled in place by tsc like the package's\n * bin entry points, and linted like every other file here. (It used to be a committed .js asset\n * exempted from `no-js-files`; see graph-visualizer.client.ts for why that exemption is gone.)\n *\n * Three jobs:\n * 1. render the DOT with @viz-js/viz v3 (instance() resolves the WASM renderer, and\n * renderSVGElement is then SYNCHRONOUS — v2's returned a promise);\n * 2. upgrade every queue node into a TRUE horizontal cylinder;\n * 3. make every node clickable, opening the SHARED floating menu (graph-node-menu.ts — the same one\n * architecture/dependencies.html and every design.html use) with independent Lock/Unlock and chain filtering.\n *\n * Why (2) is post-processing rather than a shape:\n *\n * Graphviz has exactly one `cylinder` and it is upright-only. `orientation=` is documented as rotating\n * POLYGON shapes, and `cylinder` is drawn with beziers, so it silently ignores the attribute — graphviz\n * issue #2244, open since 2022 and still reproducible on Graphviz 15, the version this page's renderer\n * carries. All 54 native shapes were compared at the real label size; none is a horizontal cylinder. So\n * the DOT emits `Mrecord` (which renders sensibly on its own for anyone running `dot` over the\n * committed .dot file) and this script redraws it in the browser, where the geometry is computed from\n * each node's actual bounding box and therefore fits any label width — which a fixed image asset never\n * can.\n */\n\n// The DOT placeholder and the `Viz` global are declared once in viz-client-globals.d.ts — both\n// scripts share a global scope, so they cannot each declare them. Do NOT spell the placeholder token\n// in a comment: the inliner is a blind split/join, so every literal occurrence gets the whole DOT.\n\n/** Cap width as a fraction of the node's half-height. Below ~0.6 it reads as a rounded box. */\nconst CAP_RATIO = 0.75;\n/** A node narrower than this is not a queue box worth reshaping. */\nconst MIN_WIDTH = 40;\n\n/** The union bounding box of a node's shape elements, in user space. Data-only → a class. */\nclass Bounds {\n constructor(\n readonly x0: number,\n readonly x1: number,\n readonly y0: number,\n readonly y1: number,\n ) {}\n\n width(): number {\n return this.x1 - this.x0;\n }\n\n halfHeight(): number {\n return (this.y1 - this.y0) / 2;\n }\n}\n\n/**\n * Redraws the nodes the DOT marked `wp_queue` as cylinders lying on their side.\n *\n * Selected by CLASS, not by id prefix: a queue-kind external system shares the `system__` id space with\n * databases, and a database is an UPRIGHT cylinder that must be left alone.\n */\nclass QueueCylinders {\n private static readonly SVG_NS = 'http://www.w3.org/2000/svg';\n\n applyTo(svg: SVGSVGElement): void {\n svg.querySelectorAll('g.wp_queue').forEach((node: Element): void => { this.reshape(node); });\n }\n\n /**\n * Replace one node's shape elements with a horizontal cylinder sized to its own bounding box.\n *\n * The bbox is measured from the shape elements BEFORE they are removed, never from getBBox() on the\n * whole group — the group includes the text, which is inset, so using it would shrink the cylinder\n * inside the very label it is meant to contain.\n */\n private reshape(node: Element): void {\n const shapes = node.querySelectorAll('polygon, path, polyline');\n if (shapes.length === 0) return;\n const box = this.boundsOf(shapes);\n if (box === null || box.width() < MIN_WIDTH) return;\n\n const first = shapes[0];\n const fill = first.getAttribute('fill') ?? 'none';\n const stroke = first.getAttribute('stroke') ?? 'black';\n // Measured BEFORE the shapes go: the record separator is where the label text starts.\n const separator = this.separatorX(shapes);\n shapes.forEach((shape: Element): void => { shape.remove(); });\n\n const ry = box.halfHeight();\n const rx = this.capRadius(box, ry, separator);\n const body =\n 'M' + (box.x0 + rx) + ',' + box.y0 +\n ' L' + (box.x1 - rx) + ',' + box.y0 +\n ' A' + rx + ',' + ry + ' 0 0 1 ' + (box.x1 - rx) + ',' + box.y1 +\n ' L' + (box.x0 + rx) + ',' + box.y1 +\n ' A' + rx + ',' + ry + ' 0 0 1 ' + (box.x0 + rx) + ',' + box.y0 + ' Z';\n // Only the NEAR end cap is drawn: that single arc is what reads as \"tube\" rather than\n // \"stadium\", and a real cylinder hides the far one behind the body.\n const cap =\n 'M' + (box.x0 + rx) + ',' + box.y0 +\n ' A' + rx + ',' + ry + ' 0 0 1 ' + (box.x0 + rx) + ',' + box.y1;\n\n const anchor = node.firstChild;\n if (anchor === null || anchor.nextSibling === null) return;\n node.insertBefore(this.pathEl(fill, stroke, body), anchor.nextSibling);\n node.insertBefore(this.pathEl('none', stroke, cap), anchor.nextSibling.nextSibling);\n\n // The label is deliberately NOT nudged. QUEUE_LABEL_PREFIX already gives the node an empty\n // leading record field, so Graphviz has centred the text in the space to the RIGHT of where the\n // cap lands. Shifting it again double-counts that offset and pushes the longest line out\n // through the far end of the cylinder.\n }\n\n /**\n * A box listing several queues is TALL, and a cap scaled off half-height alone would then reach\n * past the empty leading field and sit on top of the first characters of the label. Graphviz sizes\n * that field from the font, not from the label's height, so the separator is the only honest bound\n * on how far right the cap may go.\n */\n private capRadius(box: Bounds, ry: number, separator: number | null): number {\n const fromHeight = Math.max(6, ry * CAP_RATIO);\n if (separator === null) return fromHeight;\n return Math.max(6, Math.min(fromHeight, separator - box.x0));\n }\n\n /**\n * The x of the record's field separator — the VERTICAL polyline Graphviz draws between the empty\n * leading field and the label — or null if this node has none.\n */\n private separatorX(shapes: NodeListOf<Element>): number | null {\n for (const shape of Array.from(shapes)) {\n if (shape.tagName !== 'polyline') continue;\n const nums = (shape.getAttribute('points') ?? '').match(/-?[\\d.]+/g);\n if (nums === null || nums.length < 4) continue;\n const x = parseFloat(nums[0]);\n if (Math.abs(x - parseFloat(nums[2])) < 0.5) return x;\n }\n return null;\n }\n\n /** The union bounding box of some SVG shape elements, from their raw geometry attributes. */\n private boundsOf(shapes: NodeListOf<Element>): Bounds | null {\n const xs: number[] = [];\n const ys: number[] = [];\n for (const shape of Array.from(shapes)) {\n const pts = shape.getAttribute('points');\n const source = pts ?? shape.getAttribute('d') ?? '';\n const nums = source.match(/-?[\\d.]+/g);\n if (nums === null) continue;\n for (let n = 0; n + 1 < nums.length; n += 2) {\n xs.push(parseFloat(nums[n]));\n ys.push(parseFloat(nums[n + 1]));\n }\n }\n if (xs.length === 0) return null;\n return new Bounds(Math.min(...xs), Math.max(...xs), Math.min(...ys), Math.max(...ys));\n }\n\n private pathEl(fill: string, stroke: string, d: string): SVGPathElement {\n const el = document.createElementNS(QueueCylinders.SVG_NS, 'path');\n el.setAttribute('fill', fill);\n el.setAttribute('stroke', stroke);\n el.setAttribute('d', d);\n return el;\n }\n}\n\n/** Runtime keeps the single-box Lock while Filter follows the full directed chain. */\nclass RuntimeNodeMenu {\n constructor(private readonly svg: SVGSVGElement, private readonly lock: WpNodeLock, private readonly page: RuntimePage) {}\n\n wire(): void {\n WpNodeMenu.wire(this.svg, (name: string, node: SVGGElement): WpNodeMenuItem[] => {\n const label = this.lock.isLocked(name) ? 'Unlock' : 'Lock';\n return [new WpNodeMenuItem(label, (): void => { this.lock.toggle(name, node); }), this.page.filterItem(name)];\n });\n }\n}\n\nclass RuntimePage extends WpFilterPage {\n private lock: WpNodeLock | null = null;\n\n protected override captureBinding(svg: SVGSVGElement | null): () => void {\n const previous = this.lock;\n return (): void => {\n this.lock = previous;\n if (svg !== null) this.lock?.rebind(svg);\n };\n }\n\n protected override prepareSvg(svg: SVGSVGElement): void { new QueueCylinders().applyTo(svg); }\n\n protected wireSvg(svg: SVGSVGElement): void {\n if (this.lock === null) this.lock = new WpNodeLock(svg);\n else this.lock.rebind(svg);\n new RuntimeNodeMenu(svg, this.lock, this).wire();\n svg.querySelectorAll<SVGGElement>('g.node').forEach(node => {\n if (node.querySelector('title')?.textContent === this.anchor) node.classList.add('wp-filter-anchor');\n });\n }\n}\n\nnew RuntimePage(__RENDER_MODEL__).render();\n"]}
|
|
1
|
+
{"version":3,"file":"runtime-visualizer.client.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-visualizer.client.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Browser-side script for tmp/webpieces/runtime-architecture.html.\n *\n * runtime-visualizer.ts reads this file's COMPILED output with readFileSync, substitutes the DOT\n * placeholder (see viz-client-globals.d.ts) with the JSON-encoded DOT, and inlines the result into a <script> tag. It runs in a\n * browser, never in node — but it is ordinary TypeScript, compiled in place by tsc like the package's\n * bin entry points, and linted like every other file here. (It used to be a committed .js asset\n * exempted from `no-js-files`; see graph-visualizer.client.ts for why that exemption is gone.)\n *\n * Three jobs:\n * 1. render the DOT with @viz-js/viz v3 (instance() resolves the WASM renderer, and\n * renderSVGElement is then SYNCHRONOUS — v2's returned a promise);\n * 2. upgrade every queue node into a TRUE horizontal cylinder;\n * 3. make every node clickable, opening the SHARED floating menu (graph-node-menu.ts — the same one\n * architecture/dependencies.html and every design.html use) with independent Lock/Unlock and chain filtering.\n *\n * Why (2) is post-processing rather than a shape:\n *\n * Graphviz has exactly one `cylinder` and it is upright-only. `orientation=` is documented as rotating\n * POLYGON shapes, and `cylinder` is drawn with beziers, so it silently ignores the attribute — graphviz\n * issue #2244, open since 2022 and still reproducible on Graphviz 15, the version this page's renderer\n * carries. All 54 native shapes were compared at the real label size; none is a horizontal cylinder. So\n * the DOT emits `Mrecord` (which renders sensibly on its own for anyone running `dot` over the\n * committed .dot file) and this script redraws it in the browser, where the geometry is computed from\n * each node's actual bounding box and therefore fits any label width — which a fixed image asset never\n * can.\n */\n\n// The DOT placeholder and the `Viz` global are declared once in viz-client-globals.d.ts — both\n// scripts share a global scope, so they cannot each declare them. Do NOT spell the placeholder token\n// in a comment: the inliner is a blind split/join, so every literal occurrence gets the whole DOT.\n\n/** Cap width as a fraction of the node's half-height. Below ~0.6 it reads as a rounded box. */\nconst CAP_RATIO = 0.75;\n/** A node narrower than this is not a queue box worth reshaping. */\nconst MIN_WIDTH = 40;\n\n/** The union bounding box of a node's shape elements, in user space. Data-only → a class. */\nclass Bounds {\n constructor(\n readonly x0: number,\n readonly x1: number,\n readonly y0: number,\n readonly y1: number,\n ) {}\n\n width(): number {\n return this.x1 - this.x0;\n }\n\n halfHeight(): number {\n return (this.y1 - this.y0) / 2;\n }\n}\n\n/**\n * Redraws the nodes the DOT marked `wp_queue` as cylinders lying on their side.\n *\n * Selected by CLASS, not by id prefix: a queue-kind external system shares the `system__` id space with\n * databases, and a database is an UPRIGHT cylinder that must be left alone.\n */\nclass QueueCylinders {\n private static readonly SVG_NS = 'http://www.w3.org/2000/svg';\n\n applyTo(svg: SVGSVGElement): void {\n svg.querySelectorAll('g.wp_queue').forEach((node: Element): void => { this.reshape(node); });\n }\n\n /**\n * Replace one node's shape elements with a horizontal cylinder sized to its own bounding box.\n *\n * The bbox is measured from the shape elements BEFORE they are removed, never from getBBox() on the\n * whole group — the group includes the text, which is inset, so using it would shrink the cylinder\n * inside the very label it is meant to contain.\n */\n private reshape(node: Element): void {\n const shapes = node.querySelectorAll('polygon, path, polyline');\n if (shapes.length === 0) return;\n const box = this.boundsOf(shapes);\n if (box === null || box.width() < MIN_WIDTH) return;\n\n const first = shapes[0];\n const fill = first.getAttribute('fill') ?? 'none';\n const stroke = first.getAttribute('stroke') ?? 'black';\n // Measured BEFORE the shapes go: the record separator is where the label text starts.\n const separator = this.separatorX(shapes);\n shapes.forEach((shape: Element): void => { shape.remove(); });\n\n const ry = box.halfHeight();\n const rx = this.capRadius(box, ry, separator);\n const body =\n 'M' + (box.x0 + rx) + ',' + box.y0 +\n ' L' + (box.x1 - rx) + ',' + box.y0 +\n ' A' + rx + ',' + ry + ' 0 0 1 ' + (box.x1 - rx) + ',' + box.y1 +\n ' L' + (box.x0 + rx) + ',' + box.y1 +\n ' A' + rx + ',' + ry + ' 0 0 1 ' + (box.x0 + rx) + ',' + box.y0 + ' Z';\n // Only the NEAR end cap is drawn: that single arc is what reads as \"tube\" rather than\n // \"stadium\", and a real cylinder hides the far one behind the body.\n const cap =\n 'M' + (box.x0 + rx) + ',' + box.y0 +\n ' A' + rx + ',' + ry + ' 0 0 1 ' + (box.x0 + rx) + ',' + box.y1;\n\n const anchor = node.firstChild;\n if (anchor === null || anchor.nextSibling === null) return;\n node.insertBefore(this.pathEl(fill, stroke, body), anchor.nextSibling);\n node.insertBefore(this.pathEl('none', stroke, cap), anchor.nextSibling.nextSibling);\n\n // The label is deliberately NOT nudged. QUEUE_LABEL_PREFIX already gives the node an empty\n // leading record field, so Graphviz has centred the text in the space to the RIGHT of where the\n // cap lands. Shifting it again double-counts that offset and pushes the longest line out\n // through the far end of the cylinder.\n }\n\n /**\n * A box listing several queues is TALL, and a cap scaled off half-height alone would then reach\n * past the empty leading field and sit on top of the first characters of the label. Graphviz sizes\n * that field from the font, not from the label's height, so the separator is the only honest bound\n * on how far right the cap may go.\n */\n private capRadius(box: Bounds, ry: number, separator: number | null): number {\n const fromHeight = Math.max(6, ry * CAP_RATIO);\n if (separator === null) return fromHeight;\n return Math.max(6, Math.min(fromHeight, separator - box.x0));\n }\n\n /**\n * The x of the record's field separator — the VERTICAL polyline Graphviz draws between the empty\n * leading field and the label — or null if this node has none.\n */\n private separatorX(shapes: NodeListOf<Element>): number | null {\n for (const shape of Array.from(shapes)) {\n if (shape.tagName !== 'polyline') continue;\n const nums = (shape.getAttribute('points') ?? '').match(/-?[\\d.]+/g);\n if (nums === null || nums.length < 4) continue;\n const x = parseFloat(nums[0]);\n if (Math.abs(x - parseFloat(nums[2])) < 0.5) return x;\n }\n return null;\n }\n\n /** The union bounding box of some SVG shape elements, from their raw geometry attributes. */\n private boundsOf(shapes: NodeListOf<Element>): Bounds | null {\n const xs: number[] = [];\n const ys: number[] = [];\n for (const shape of Array.from(shapes)) {\n const pts = shape.getAttribute('points');\n const source = pts ?? shape.getAttribute('d') ?? '';\n const nums = source.match(/-?[\\d.]+/g);\n if (nums === null) continue;\n for (let n = 0; n + 1 < nums.length; n += 2) {\n xs.push(parseFloat(nums[n]));\n ys.push(parseFloat(nums[n + 1]));\n }\n }\n if (xs.length === 0) return null;\n return new Bounds(Math.min(...xs), Math.max(...xs), Math.min(...ys), Math.max(...ys));\n }\n\n private pathEl(fill: string, stroke: string, d: string): SVGPathElement {\n const el = document.createElementNS(QueueCylinders.SVG_NS, 'path');\n el.setAttribute('fill', fill);\n el.setAttribute('stroke', stroke);\n el.setAttribute('d', d);\n return el;\n }\n}\n\n/** Runtime keeps the single-box Lock while Filter follows the full directed chain. */\nclass RuntimeNodeMenu {\n constructor(private readonly svg: SVGSVGElement, private readonly lock: WpNodeLock, private readonly page: RuntimePage) {}\n\n wire(): void {\n WpNodeMenu.wire(this.svg, (name: string, node: SVGGElement): WpNodeMenuItem[] => {\n const label = this.lock.isLocked(name) ? 'Unlock' : 'Lock';\n return [new WpNodeMenuItem(label, (): void => { this.lock.toggle(name, node); }), this.page.filterItem(name)];\n });\n }\n}\n\nclass RuntimePage extends WpFilterPage {\n private lock: WpNodeLock | null = null;\n /** The pressed product chips (#1179); empty keeps every box. */\n private readonly products = new Set<string>();\n\n /** Each product chip toggles itself and redraws; the narrowing happens in narrow(). */\n protected override wireControls(): void {\n document.querySelectorAll<HTMLButtonElement>('[data-wp-product]').forEach((chip: HTMLButtonElement): void => {\n chip.addEventListener('click', (): void => {\n const product = chip.dataset['wpProduct'] ?? '';\n if (this.products.has(product)) this.products.delete(product);\n else this.products.add(product);\n chip.setAttribute('aria-pressed', String(this.products.has(product)));\n this.redraw();\n });\n });\n }\n\n /** Pressed chips keep the UNION of their products' boxes, intersected with Filter Unconnected. */\n protected override narrow(retained: Set<string>): Set<string> {\n if (this.products.size === 0) return retained;\n const kept = new Set<string>();\n for (const node of this.model.nodes) {\n if (!retained.has(node.id)) continue;\n if (node.products.some((product: string): boolean => this.products.has(product))) kept.add(node.id);\n }\n return kept;\n }\n\n protected override usesFullDot(): boolean {\n return this.products.size === 0;\n }\n\n protected override captureBinding(svg: SVGSVGElement | null): () => void {\n const previous = this.lock;\n return (): void => {\n this.lock = previous;\n if (svg !== null) this.lock?.rebind(svg);\n };\n }\n\n protected override prepareSvg(svg: SVGSVGElement): void { new QueueCylinders().applyTo(svg); }\n\n protected wireSvg(svg: SVGSVGElement): void {\n if (this.lock === null) this.lock = new WpNodeLock(svg);\n else this.lock.rebind(svg);\n new RuntimeNodeMenu(svg, this.lock, this).wire();\n svg.querySelectorAll<SVGGElement>('g.node').forEach(node => {\n if (node.querySelector('title')?.textContent === this.anchor) node.classList.add('wp-filter-anchor');\n });\n }\n}\n\nnew RuntimePage(__RENDER_MODEL__).render();\n"]}
|
|
@@ -459,7 +459,7 @@ function generateRuntimeRenderModel(graph, title = 'WebPieces Runtime Architectu
|
|
|
459
459
|
continue;
|
|
460
460
|
const svc = graph.services[name];
|
|
461
461
|
const color = runtime_viz_theme_1.LEVEL_COLORS[svc.level] || '#F5F5F5';
|
|
462
|
-
dot += model.node(name, ` "${serviceNodeId(name)}" [fillcolor="${color}", label="${nodeLabel(name, svc, details.nodes[name].used.length)}"];\n
|
|
462
|
+
dot += model.node(name, ` "${serviceNodeId(name)}" [fillcolor="${color}", label="${nodeLabel(name, svc, details.nodes[name].used.length)}"];\n`, svc.products ?? []);
|
|
463
463
|
}
|
|
464
464
|
dot += '\n';
|
|
465
465
|
for (const edge of graph.runtimeEdges) {
|
|
@@ -486,6 +486,8 @@ function generateRuntimeRenderModel(graph, title = 'WebPieces Runtime Architectu
|
|
|
486
486
|
(0, dot_syntax_1.assertValidDot)(dot, 'runtime-architecture.dot');
|
|
487
487
|
model.footer = dot.slice(footerStart);
|
|
488
488
|
model.completeEndpoints();
|
|
489
|
+
// #1179: a queue, trigger or external system belongs to the products of the services it touches.
|
|
490
|
+
model.attachProducts((id) => graph.services[id] !== undefined);
|
|
489
491
|
model.fullDot = dot;
|
|
490
492
|
return model;
|
|
491
493
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-visualizer.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-visualizer.ts"],"names":[],"mappings":"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","sourcesContent":["import { RuntimeDetails } from './runtime-details';\n/**\n * Runtime Visualizer\n *\n * Renders the runtime microservice graph (services + inferred Z -> X edges,\n * each labeled with the api(s) they flow over) to DOT + interactive HTML in\n * tmp/webpieces/runtime-architecture.{dot,html}.\n *\n * Each service node names the contracts it IMPLEMENTS. That list used to be\n * collapsed into a server/client boolean and thrown away — leaving an api that a\n * server serves but nothing in-repo calls completely invisible, and making a\n * correct api design look like a detection failure. What a service USES is NOT\n * listed: every use already draws an outgoing arrow labeled with the same\n * contract, so repeating it in the box only made every box wider.\n *\n * Shape says what a node IS and line style says what a call IS. Solid = rpc: the\n * request follows the arrow and the response flows back. Dashed = event: it flows\n * in the arrow's direction and returns once it is queued. A queue is a sideways\n * cylinder, a datastore an upright one. A queue box lists ONE LINE PER QUEUE: the\n * unit underneath is still the METHOD (what Terraform creates, and what\n * runtime-dependencies.json records), but queues of one contract sharing the same\n * producers and consumers are drawn together instead of as adjacent near-identical\n * boxes — every queue is still named, only the node count drops.\n *\n * Calls that leave the repo (a contract NOTHING in-repo implements — firestore,\n * gmail, ...) are drawn as terminal nodes, so the vendor systems that actually\n * page you at 3am stop being missing from the picture. One that DECLARES what it\n * is, via an `@externalSystem` JSDoc tag or an `external:` nx tag, gets the shape\n * of the thing it is; the rest stay generic dashed boxes. They are RENDER-ONLY:\n * derivation, levels and cycle detection never see them.\n *\n * The same is true in the other direction for endpoints nothing in-repo CALLS: a\n * `cron` method hangs off a clock and an `external` method off a box naming the\n * CALLER that posts to it (`twilio`), in the same id space as the outbound\n * systems, so a vendor we both call and are called by is ONE box. Those are the\n * entry points that wake a service up at 3am, and a graph built only from in-repo\n * callers cannot show them at all.\n *\n * EVERY node on the rendered page is clickable and opens the SHARED floating menu\n * (graph-node-menu.ts — one implementation, also used by architecture/dependencies.html\n * and every project's design.html). Lock/Unlock dims every\n * other node and every edge and lights the locked box alone. There is no \"View Design\"\n * item here — a node is a running service, queue, datastore or third-party system, not\n * an nx project, so no design.html exists to point at and the item is absent rather than\n * dead. Filter Unconnected follows separate incoming/outgoing chains and compacts the rendered\n * topology; Turn off Filter restores the full picture. Lock remains independent of Filter.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type {\n RuntimeGraph,\n RuntimeEdge,\n RuntimeQueue,\n RuntimeService,\n RuntimeTrigger,\n} from './runtime-graph';\nimport { dotValue, recordValue, assertValidDot } from './dot-syntax';\nimport { GraphRenderModel } from './graph-render-model';\nimport { RuntimeHtmlPage } from './runtime-html-page';\nimport {\n LEVEL_COLORS,\n QUEUE_FILL,\n QUEUE_SHAPE,\n QUEUE_LABEL_PREFIX,\n QUEUE_CLASS,\n EXTERNAL_SHAPES,\n EXTERNAL_FILLS,\n EXTERNAL_FILL,\n EXTERNAL_BORDER,\n CRON_FILL,\n CRON_BORDER,\n CUT_CYCLE_EDGE_ATTRS,\n} from './runtime-viz-theme';\n\n/** Apis per line inside a node label — beyond this the box grows wider than it is readable. */\nconst APIS_PER_LABEL_LINE = 3;\n\n/** Separator for the (service, external-library) grouping key; illegal in both project names. */\nconst PAIR_SEP = '|';\n\n/** Render options for the runtime graph. */\nexport class RuntimeVizOptions {\n constructor(\n /**\n * Draw the dashed terminal nodes for contracts nothing in-repo implements. On by default;\n * a repo whose external surface is noisy can turn them off in the declared Nx owner file's\n * direct policy-ID map (runtime-architecture.showExternalNodes).\n */\n public readonly showExternalNodes: boolean = true,\n ) {}\n}\n\n/**\n * The DISPLAY name of a project/service: scope stripped. LABELS ONLY.\n *\n * Never an identity. `@scope/public-api` and `public-api` are two distinct projects that strip to\n * the same string, and keying nodes or edges on that fuses them into one box (and draws the edge\n * between them as a self-loop). See {@link serviceNodeId} and lib/graph-names.ts.\n */\nfunction getShortName(name: string): string {\n return name.includes('/') ? name.split('/').pop()! : name;\n}\n\n/** The DOT identity of a runtime service: its project key, escaped — never the display name. */\n// webpieces-disable no-function-outside-class -- DOT id builder, matching getShortName in this file\nfunction serviceNodeId(name: string): string {\n return dotValue(name);\n}\n\n/** Chunk a list into `\\n`-separated label lines of at most APIS_PER_LABEL_LINE entries. */\n// webpieces-disable no-function-outside-class -- DOT label builder, matching getShortName in this file\nfunction labelList(entries: string[]): string {\n const lines: string[] = [];\n const safe = entries.map((entry: string) => dotValue(entry));\n for (let i = 0; i < safe.length; i += APIS_PER_LABEL_LINE) {\n lines.push(safe.slice(i, i + APIS_PER_LABEL_LINE).join(', '));\n }\n return lines.join('\\\\n');\n}\n\n/** Compact affordances carry full details in the HTML, including hidden external uses. */\n// webpieces-disable no-function-outside-class -- DOT label builder, matching getShortName in this file\nfunction nodeLabel(name: string, svc: RuntimeService, usesCount: number): string {\n // The DECLARED role when the graph carries one; the old inference only as a fallback for a\n // runtime-dependencies.json committed before `role` existed. Inferring it labelled every server\n // with no implements as a \"client\", which is exactly wrong for a queue-driven service.\n const role = svc.role ?? (svc.implements.length > 0 ? 'server' : 'client');\n // The declared name is quoted for the reader — those quotes MUST be DOT-escaped, or they end\n // the label string and the whole graph stops parsing.\n const declared = svc.serviceName === undefined ? '' : `, \\\\\"${dotValue(svc.serviceName)}\\\\\"`;\n let label = `${dotValue(getShortName(name))}\\\\n(${role}, L${svc.level}${declared})`;\n label += `\\\\nImplements (${svc.implements.length})\\\\nUses (${usesCount})`;\n return label;\n}\n\n/**\n * DOT for ONE NON-QUEUED runtime edge: a direct labeled SOLID arrow (producer calls consumer, the\n * response comes back). Queued (pubsub) hops are drawn by {@link queuesDot} instead, because they\n * are merged across methods and therefore cannot be emitted one edge at a time.\n *\n * Dashed is the graph's one line-level distinction, and it now says one of two things: an event that\n * returns as soon as it is queued, or (in red, labelled \"legacy cycle\") an edge a `cutLegacyCycle:`\n * tag holds out of leveling. A cut edge is still DRAWN — erasing the arrow would erase exactly the\n * debt the tag records — it is only drawn as the debt it is. See lib/cut-legacy-cycle-resolver.ts.\n */\n// webpieces-disable no-function-outside-class -- DOT string builder, matching getShortName in this file\nfunction edgeDot(edge: RuntimeEdge): string {\n const from = serviceNodeId(edge.from);\n const to = serviceNodeId(edge.to);\n const viaRaw =\n edge.via.length > 2\n ? `Uses (${edge.via.length}) ▾`\n : edge.via.map((v: string) => getShortName(v)).join(', ');\n const cut = edge.cutLegacyCycle === true;\n const label = cut ? `${dotValue(viaRaw)}\\\\nlegacy cycle` : dotValue(viaRaw);\n return ` \"${from}\" -> \"${to}\" [label=\"${label}\"${cut ? CUT_CYCLE_EDGE_ATTRS : ''}];\\n`;\n}\n\n/** The VISIBLE producers and consumers of one queue, gathered from the edges that survived hiding. */\nclass QueueEndpoints {\n readonly producers = new Set<string>();\n readonly consumers = new Set<string>();\n}\n\n/**\n * One drawn queue BOX: the queue keys (`Api.method`) it shows, and the endpoints they all share.\n * Several keys land in one box only when they agree on all three of contract, producers and\n * consumers — see {@link queuesDot}.\n */\nclass QueueGroup {\n constructor(\n public readonly members: string[],\n public readonly producers: string[],\n public readonly consumers: string[],\n ) {}\n}\n\n/**\n * The label line for ONE queue inside a box: `Api.method`, and the Terraform queue name ONLY when it\n * is not the derived `${Api}-${method}`.\n *\n * Printing a derived name was pure restatement — the line above it already said `Api.method`, so\n * every queue box carried a second line that added nothing and doubled its height. A `@Queue(...)`\n * OVERRIDE is the opposite: that string appears nowhere else on the graph, and it is the one string\n * Terraform must match, so it stays.\n */\n// webpieces-disable no-function-outside-class -- DOT label builder, matching getShortName in this file\nfunction queueLine(key: string, queue: RuntimeQueue | undefined): string {\n const line = recordValue(key);\n if (queue === undefined) return line;\n if (queue.queueName === `${queue.api}-${queue.method}`) return line;\n return `${line}\\\\nqueue: ${recordValue(queue.queueName)}`;\n}\n\n/** The node statement + enqueue/deliver arrows for one queue box. Service identities remain raw; only emitted DOT is escaped. */\n// webpieces-disable no-function-outside-class -- DOT string builder, matching getShortName in this file\nfunction queueBoxDot(\n id: string,\n body: string,\n producers: string[],\n consumers: string[],\n model: GraphRenderModel,\n): string {\n // Record-mode label: the text must clear recordValue(), and QUEUE_LABEL_PREFIX supplies the\n // empty leading field that draws the cylinder's end cap. Drop it and the node silently\n // degrades to a plain box.\n let dot = model.node(\n id,\n ` \"${id}\" [shape=${QUEUE_SHAPE}, style=\"filled\", fillcolor=\"${QUEUE_FILL}\", ` +\n `class=\"${QUEUE_CLASS}\", label=\"${QUEUE_LABEL_PREFIX}${body}\"];\\n`,\n );\n for (const producer of producers)\n dot += model.edge(\n producer,\n id,\n ` \"${serviceNodeId(producer)}\" -> \"${id}\" [label=\"enqueue\", style=dashed];\\n`,\n );\n for (const consumer of consumers)\n dot += model.edge(\n id,\n consumer,\n ` \"${id}\" -> \"${serviceNodeId(consumer)}\" [label=\"deliver\", style=dashed];\\n`,\n );\n return dot;\n}\n\n/**\n * Group the queued hops into boxes. The key is (contract, producer SET, consumer SET) — NOT the\n * (from,to) pair of some edge.\n *\n * A queue node converges EVERY producer and consumer of its method onto one box, so keying by a\n * service PAIR would re-split a two-producer queue into two boxes and invent topology nobody wrote.\n * Only queues of the same contract whose producer and consumer sets are IDENTICAL may share a box;\n * anything else stays separate, because merging it would assert a routing that does not exist.\n */\n// webpieces-disable no-function-outside-class -- DOT string builder, matching getShortName in this file\nfunction groupQueues(byQueue: Map<string, QueueEndpoints>, graph: RuntimeGraph): QueueGroup[] {\n const groups = new Map<string, QueueGroup>();\n // Sorted keys → the first member of every group, and therefore its node id, is deterministic.\n for (const key of [...byQueue.keys()].sort()) {\n const ends = byQueue.get(key)!;\n const producers = [...ends.producers].sort();\n const consumers = [...ends.consumers].sort();\n const api = graph.queues[key]?.api ?? key.split('.')[0];\n const mergeKey = [api, producers.join(','), consumers.join(',')].join(PAIR_SEP);\n const existing = groups.get(mergeKey);\n if (existing === undefined)\n groups.set(mergeKey, new QueueGroup([key], producers, consumers));\n else existing.members.push(key);\n }\n return [...groups.values()];\n}\n\n/**\n * The queued half of the graph: producer → QUEUE → consumer, drawn with a sideways-cylinder node and\n * DASHED arrows (an event returns as soon as it is queued).\n *\n * One BOX may list several queues, one per line. The unit Cloud Tasks and Terraform create is still\n * the METHOD — that is what runtime-dependencies.json records and this render never changes it — but\n * two methods of one contract flowing between exactly the same producers and consumers drew two\n * adjacent boxes whose only difference was the method name, and each carried a `queue:` line that\n * merely restated it. Listing them inside ONE box keeps every queue named (you can still see which\n * one is stuck) at a fraction of the node count.\n */\n// webpieces-disable no-function-outside-class -- DOT string builder, matching getShortName in this file\nfunction queuesDot(graph: RuntimeGraph, hidden: Set<string>, model: GraphRenderModel): string {\n const queued = graph.runtimeEdges.filter(\n (e: RuntimeEdge) => e.type === 'pubsub' && !hidden.has(e.from) && !hidden.has(e.to),\n );\n if (queued.length === 0) return '';\n\n let dot =\n '\\n // Queued hops. Each LINE in a box is one Cloud Tasks queue; queues of one contract\\n' +\n ' // sharing the same producers AND consumers are drawn in a single box.\\n';\n\n // A contract with no method table (no architecture/apis/<Api>.json for it) has no\n // per-method queue at all, so it keeps the historical unnamed per-pair box.\n const byQueue = new Map<string, QueueEndpoints>();\n for (const edge of queued) {\n const from = edge.from;\n const to = edge.to;\n if (edge.queue === undefined) {\n // Kept RAW: recordValue already applies dotValue, so escaping here would double it.\n const viaRaw =\n edge.via.length > 2\n ? `Uses (${edge.via.length}) ▾`\n : edge.via.map((v: string) => getShortName(v)).join(', ');\n dot += queueBoxDot(\n `queue__${dotId(edge.from)}__${dotId(edge.to)}`,\n `${recordValue(viaRaw)}\\\\nqueue`,\n [from],\n [to],\n model,\n );\n continue;\n }\n if (!byQueue.has(edge.queue)) byQueue.set(edge.queue, new QueueEndpoints());\n byQueue.get(edge.queue)!.producers.add(from);\n byQueue.get(edge.queue)!.consumers.add(to);\n }\n\n for (const group of groupQueues(byQueue, graph)) {\n const body = group.members\n .map((key: string) => queueLine(key, graph.queues[key]))\n .join('\\\\n');\n dot += queueBoxDot(\n `queue__${dotId(group.members[0])}`,\n body,\n group.producers,\n group.consumers,\n model,\n );\n }\n return dot;\n}\n\n/** A DOT-safe node-id fragment: anything but letters, digits and `_` becomes `_`. */\n// webpieces-disable no-function-outside-class -- DOT id builder, matching getShortName in this file\nfunction dotId(raw: string): string {\n return raw.replace(/[^A-Za-z0-9_]/g, '_');\n}\n\n/**\n * ONE external-system node statement: the shape/fill of what the system IS, its label, and a\n * parenthesised subtitle saying which way it faces.\n *\n * Shared by the OUTBOUND systems ({@link externalSystemsDot}) and the INBOUND callers\n * ({@link triggerDot}) on purpose. They emit into the SAME `system__<identity>` id space, so a\n * vendor this repo both calls and is called by is one box with arrows in both directions — and a\n * copy-pasted attribute string would have let the two drift until they stopped being one box.\n */\n// webpieces-disable no-function-outside-class -- DOT string builder, matching getShortName in this file\nfunction externalSystemNodeDot(\n identity: string,\n kind: string,\n label: string,\n subtitle: string,\n model: GraphRenderModel,\n): string {\n const shape = EXTERNAL_SHAPES[kind] ?? 'box';\n const fill = EXTERNAL_FILLS[kind] ?? EXTERNAL_FILL;\n // An Mrecord-shaped system needs the same empty leading field as a queue node, or it renders as\n // a plain box and silently loses the sideways-cylinder read.\n const isQueue = shape === QUEUE_SHAPE;\n const prefix = isQueue ? QUEUE_LABEL_PREFIX : '';\n const text = isQueue ? recordValue(label) : dotValue(label);\n // Only a queue-kind system is marked: a database here is an UPRIGHT cylinder and must not be\n // caught by the browser-side reshaping that lays queues on their side.\n const marker = isQueue ? `class=\"${QUEUE_CLASS}\", ` : '';\n return model.node(\n `system__${dotId(identity)}`,\n ` \"system__${dotId(identity)}\" [shape=${shape}, style=\"filled\", fillcolor=\"${fill}\", ` +\n `${marker}label=\"${prefix}${text}\\\\n(${subtitle})\"];\\n`,\n );\n}\n\n/**\n * The clock and outside-system nodes for endpoints NOTHING in-repo calls.\n *\n * A cron sweep and a GCP push subscription are real runtime entry points with real Terraform behind\n * them, but they produce no runtime EDGE (there is no in-repo caller), so until now they were simply\n * absent — a server's most operationally interesting endpoint could be invisible on its own graph.\n * Both are drawn pointing INTO the service that serves them, the opposite direction from\n * {@link externalDot}'s outbound vendor calls.\n *\n * The inbound box names the CALLER (`twilio`), never the contract. Naming the contract was the whole\n * bug: it restated what the service box directly below already says, while the one fact the box\n * exists to convey — which vendor is posting to us — appeared nowhere. The contract keeps its place\n * on the EDGE label, where `WhatsAppApi.inbound` reads as \"…posts to this method\".\n *\n * Identity is the caller's label in the SAME `system__` space {@link externalSystemsDot} uses, which\n * buys three things at once: two vendors hitting one contract are two boxes, one vendor hitting three\n * methods is one box with three arrows, and a vendor this repo also CALLS is that same single box.\n * `options` is passed in for the last of those — when that outbound half will be drawn, this half\n * must not restate the node statement.\n */\n// webpieces-disable no-function-outside-class -- DOT string builder, matching getShortName in this file\nfunction triggerDot(\n graph: RuntimeGraph,\n hidden: Set<string>,\n options: RuntimeVizOptions,\n model: GraphRenderModel,\n): string {\n const triggers = graph.triggers.filter((t: RuntimeTrigger) => !hidden.has(t.service));\n if (triggers.length === 0) return '';\n\n // Identities externalSystemsDot is about to draw; skipped here so the node is stated ONCE.\n const drawnOutbound = new Set(\n options.showExternalNodes ? Object.keys(graph.externalSystems ?? {}) : [],\n );\n const emitted = new Set<string>();\n let dot =\n '\\n // Entry points nothing in this repo calls: a clock, or a system outside the repo.\\n';\n for (const trigger of triggers) {\n const service = serviceNodeId(trigger.service);\n const label = dotValue(`${trigger.api}.${trigger.method}`);\n if (trigger.kind === 'cron') {\n const id = `cron__${dotId(`${trigger.api}_${trigger.method}`)}`;\n const schedule = dotValue(trigger.queueName ?? `${trigger.api}-${trigger.method}`);\n dot +=\n model.node(\n id,\n ` \"${id}\" [shape=circle, style=\"filled\", fillcolor=\"${CRON_FILL}\", ` +\n `color=\"${CRON_BORDER}\", label=\"⏰\\\\ncron\"];\\n`,\n ) +\n model.edge(\n id,\n trigger.service,\n ` \"${id}\" -> \"${service}\" [label=\"${label}\\\\n${schedule}\", color=\"${CRON_BORDER}\"];\\n`,\n );\n continue;\n }\n const caller = trigger.caller;\n // Undeclared: only possible for a graph generated BEFORE the caller was required (generation\n // now fails instead). DOTTED and question-marked, so \"we were never told who this is\" cannot\n // be mistaken for a named vendor.\n const id =\n caller === undefined\n ? `inbound__${dotId(trigger.api)}`\n : `system__${dotId(caller.label)}`;\n if (!emitted.has(id) && !(caller !== undefined && drawnOutbound.has(caller.label))) {\n emitted.add(id);\n dot +=\n caller === undefined\n ? model.node(\n id,\n ` \"${id}\" [shape=box, style=\"dotted,filled\", fillcolor=\"${EXTERNAL_FILL}\", ` +\n `color=\"${EXTERNAL_BORDER}\", label=\"${dotValue(trigger.api)}\\\\n? unknown caller\"];\\n`,\n )\n : externalSystemNodeDot(\n caller.label,\n caller.kind,\n caller.label,\n 'external caller',\n model,\n );\n }\n dot += model.edge(\n id,\n trigger.service,\n ` \"${id}\" -> \"${service}\" [label=\"${label}\", style=dashed, color=\"${EXTERNAL_BORDER}\"];\\n`,\n );\n }\n return dot;\n}\n\n/**\n * The DECLARED external systems — the ones that said what they are, so they get a shape that says\n * it: a cylinder for a database, a folder for a bucket. Everything undeclared falls through to\n * {@link externalDot}'s generic grey box, which is why adding this broke nothing existing.\n *\n * The arrows are SOLID. A call to firestore or postgres is synchronous — it returns a value — and\n * the graph's rule is that solid means \"response comes back\". Being outside the repo is carried by\n * the node's shape, never by the line style; conflating the two is what made a blocking database\n * read look like an event.\n */\n// webpieces-disable no-function-outside-class -- DOT string builder, matching getShortName in this file\nfunction externalSystemsDot(\n graph: RuntimeGraph,\n hidden: Set<string>,\n model: GraphRenderModel,\n): string {\n const systems = graph.externalSystems ?? {};\n const ids = Object.keys(systems).sort();\n if (ids.length === 0) return '';\n\n let dot =\n '\\n // Declared external systems — drawn with the shape of what they actually are.\\n';\n for (const id of ids) {\n const system = systems[id];\n dot += externalSystemNodeDot(\n id,\n system.kind,\n system.label,\n `external ${dotValue(system.kind)}`,\n model,\n );\n }\n for (const id of ids) {\n const system = systems[id];\n const via =\n system.apis.length === 0\n ? ''\n : ` [label=\"${system.apis.length > 2 ? `Uses (${system.apis.length}) ▾` : labelList([...system.apis].sort())}\"]`;\n for (const service of [...system.usedBy].sort()) {\n if (hidden.has(service)) continue;\n dot += model.edge(\n service,\n `system__${dotId(id)}`,\n ` \"${serviceNodeId(service)}\" -> \"system__${dotId(id)}\"${via};\\n`,\n );\n }\n }\n return dot;\n}\n\n/**\n * The dashed terminal nodes + edges for calls that LEAVE the repo. Built from `unresolvedUses` —\n * a contract used by a node and implemented by nobody in-repo — which the derivation already\n * computes and which was, until now, only ever printed as a warning.\n *\n * Grouped by the api-lib that owns the contracts, so a service reaching three firestore contracts\n * draws ONE `lib-firestore (external)` box rather than three. These are drawn, never derived: they\n * are absent from levels, cycle detection and the transitive implements attribution.\n *\n * A contract carrying an `@externalSystem` declaration is skipped here — {@link externalSystemsDot}\n * has already drawn it with a real shape, and rendering it in both places would double the node.\n */\n// webpieces-disable no-function-outside-class -- DOT string builder, matching getShortName in this file\nfunction externalDot(graph: RuntimeGraph, hidden: Set<string>, model: GraphRenderModel): string {\n // \"service|externalName\" -> the apis flowing over it.\n const apisByPair = new Map<string, string[]>();\n for (const use of graph.unresolvedUses) {\n if (hidden.has(use.service)) continue;\n if (graph.apis[use.api]?.externalSystem !== undefined) continue;\n // Keyed on the OWNER's full name, never its short name: two scoped api-libs whose short\n // names match are two external systems, and fusing them draws one box for both.\n const external = graph.apis[use.api]?.owner ?? use.api;\n const key = `${use.service}${PAIR_SEP}${external}`;\n if (!apisByPair.has(key)) apisByPair.set(key, []);\n apisByPair.get(key)!.push(use.api);\n }\n if (apisByPair.size === 0) return '';\n\n let dot = '\\n // Systems outside this repo — no in-repo service implements these contracts.\\n';\n // The node ID is prefixed so an external library can never collide with a service, and built\n // from the owner's FULL name so two same-short-named owners stay two boxes; only the label\n // carries the bare name.\n const externals = new Set([...apisByPair.keys()].map((key: string) => key.split(PAIR_SEP)[1]));\n for (const external of [...externals].sort()) {\n dot += model.node(\n `external__${external}`,\n ` \"external__${dotValue(external)}\" [shape=box, style=\"dashed,filled\", fillcolor=\"${EXTERNAL_FILL}\", ` +\n `color=\"${EXTERNAL_BORDER}\", label=\"${dotValue(getShortName(external))}\\\\n(external)\"];\\n`,\n );\n }\n for (const key of [...apisByPair.keys()].sort()) {\n const parts = key.split(PAIR_SEP);\n const service = parts[0];\n const external = parts[1];\n const apis = apisByPair.get(key)!.sort();\n const via = apis.length > 2 ? `Uses (${apis.length}) ▾` : labelList(apis);\n // SOLID: this is a synchronous call that returns a value. Dashed is reserved for events, and\n // \"outside the repo\" is already said by the node's dashed border.\n dot += model.edge(\n service,\n `external__${external}`,\n ` \"${serviceNodeId(service)}\" -> \"external__${dotValue(external)}\" ` +\n `[label=\"${via}\", color=\"${EXTERNAL_BORDER}\"];\\n`,\n );\n }\n return dot;\n}\n\n/** Build the Graphviz DOT for the runtime service graph. */\n// webpieces-disable no-function-outside-class -- module entry point, matching the sibling builders here\nexport function generateRuntimeDot(\n graph: RuntimeGraph,\n title: string = 'WebPieces Runtime Architecture',\n options: RuntimeVizOptions = new RuntimeVizOptions(),\n): string {\n return generateRuntimeRenderModel(graph, title, options).fullDot;\n}\n\n/** Capture synthetic drawable nodes and edges at their original emission sites. */\n// webpieces-disable no-function-outside-class -- renderer entry point matching generateRuntimeDot\nexport function generateRuntimeRenderModel(\n graph: RuntimeGraph,\n title: string = 'WebPieces Runtime Architecture',\n options: RuntimeVizOptions = new RuntimeVizOptions(),\n): GraphRenderModel {\n const model = new GraphRenderModel();\n const details = new RuntimeDetails(graph, options.showExternalNodes);\n let dot = 'digraph RuntimeArchitecture {\\n';\n dot += ' rankdir=TB;\\n';\n dot += ' node [shape=box, style=\"filled,rounded\", fontname=\"Arial\"];\\n';\n dot += ' edge [fontname=\"Arial\", fontsize=12];\\n\\n';\n\n model.header = dot;\n\n // Services tagged drawOnGraph:false stay in the JSON but are omitted here —\n // both their node and any edge touching them are dropped from the render.\n const hidden = new Set(\n Object.keys(graph.services).filter(\n (name: string) => graph.services[name].drawOnGraph === false,\n ),\n );\n\n for (const name of Object.keys(graph.services)) {\n if (hidden.has(name)) continue;\n const svc = graph.services[name];\n const color = LEVEL_COLORS[svc.level] || '#F5F5F5';\n dot += model.node(\n name,\n ` \"${serviceNodeId(name)}\" [fillcolor=\"${color}\", label=\"${nodeLabel(name, svc, details.nodes[name].used.length)}\"];\\n`,\n );\n }\n\n dot += '\\n';\n\n for (const edge of graph.runtimeEdges) {\n if (hidden.has(edge.from) || hidden.has(edge.to)) continue;\n // Queued hops are merged across methods, so they cannot be emitted one edge at a time.\n if (edge.type === 'pubsub') continue;\n dot += model.edge(edge.from, edge.to, edgeDot(edge));\n }\n\n dot += queuesDot(graph, hidden, model);\n dot += triggerDot(graph, hidden, options, model);\n\n if (options.showExternalNodes) {\n dot += externalSystemsDot(graph, hidden, model);\n dot += externalDot(graph, hidden, model);\n }\n\n const footerStart = dot.length;\n dot += '\\n labelloc=\"t\";\\n';\n dot += ` label=\"${dotValue(title)}\\\\n(from architecture/runtime-dependencies.json)\";\\n`;\n dot += ' fontsize=20;\\n';\n dot += '}\\n';\n // Nothing downstream parses this DOT until a human opens the page, so parse-shape is checked\n // HERE — a graph that cannot render is a generation failure, not a blank page to discover later.\n assertValidDot(dot, 'runtime-architecture.dot');\n model.footer = dot.slice(footerStart);\n model.completeEndpoints();\n model.fullDot = dot;\n return model;\n}\n\nexport interface RuntimeVisualizationPaths {\n dotPath: string;\n htmlPath: string;\n}\n\n/** Write the DOT + HTML renderings to tmp/webpieces/. */\nexport function writeRuntimeVisualization(\n graph: RuntimeGraph,\n workspaceRoot: string,\n title: string = 'WebPieces Runtime Architecture',\n options: RuntimeVizOptions = new RuntimeVizOptions(),\n): RuntimeVisualizationPaths {\n const outputDir = path.join(workspaceRoot, 'tmp', 'webpieces');\n if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });\n\n const model = generateRuntimeRenderModel(graph, title, options);\n const dot = model.fullDot;\n const dotPath = path.join(outputDir, 'runtime-architecture.dot');\n fs.writeFileSync(dotPath, dot, 'utf-8');\n\n const htmlPath = path.join(outputDir, 'runtime-architecture.html');\n fs.writeFileSync(\n htmlPath,\n new RuntimeHtmlPage().render(model, title, graph, options.showExternalNodes),\n 'utf-8',\n );\n\n return { dotPath, htmlPath };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"runtime-visualizer.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-visualizer.ts"],"names":[],"mappings":"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","sourcesContent":["import { RuntimeDetails } from './runtime-details';\n/**\n * Runtime Visualizer\n *\n * Renders the runtime microservice graph (services + inferred Z -> X edges,\n * each labeled with the api(s) they flow over) to DOT + interactive HTML in\n * tmp/webpieces/runtime-architecture.{dot,html}.\n *\n * Each service node names the contracts it IMPLEMENTS. That list used to be\n * collapsed into a server/client boolean and thrown away — leaving an api that a\n * server serves but nothing in-repo calls completely invisible, and making a\n * correct api design look like a detection failure. What a service USES is NOT\n * listed: every use already draws an outgoing arrow labeled with the same\n * contract, so repeating it in the box only made every box wider.\n *\n * Shape says what a node IS and line style says what a call IS. Solid = rpc: the\n * request follows the arrow and the response flows back. Dashed = event: it flows\n * in the arrow's direction and returns once it is queued. A queue is a sideways\n * cylinder, a datastore an upright one. A queue box lists ONE LINE PER QUEUE: the\n * unit underneath is still the METHOD (what Terraform creates, and what\n * runtime-dependencies.json records), but queues of one contract sharing the same\n * producers and consumers are drawn together instead of as adjacent near-identical\n * boxes — every queue is still named, only the node count drops.\n *\n * Calls that leave the repo (a contract NOTHING in-repo implements — firestore,\n * gmail, ...) are drawn as terminal nodes, so the vendor systems that actually\n * page you at 3am stop being missing from the picture. One that DECLARES what it\n * is, via an `@externalSystem` JSDoc tag or an `external:` nx tag, gets the shape\n * of the thing it is; the rest stay generic dashed boxes. They are RENDER-ONLY:\n * derivation, levels and cycle detection never see them.\n *\n * The same is true in the other direction for endpoints nothing in-repo CALLS: a\n * `cron` method hangs off a clock and an `external` method off a box naming the\n * CALLER that posts to it (`twilio`), in the same id space as the outbound\n * systems, so a vendor we both call and are called by is ONE box. Those are the\n * entry points that wake a service up at 3am, and a graph built only from in-repo\n * callers cannot show them at all.\n *\n * EVERY node on the rendered page is clickable and opens the SHARED floating menu\n * (graph-node-menu.ts — one implementation, also used by architecture/dependencies.html\n * and every project's design.html). Lock/Unlock dims every\n * other node and every edge and lights the locked box alone. There is no \"View Design\"\n * item here — a node is a running service, queue, datastore or third-party system, not\n * an nx project, so no design.html exists to point at and the item is absent rather than\n * dead. Filter Unconnected follows separate incoming/outgoing chains and compacts the rendered\n * topology; Turn off Filter restores the full picture. Lock remains independent of Filter.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type {\n RuntimeGraph,\n RuntimeEdge,\n RuntimeQueue,\n RuntimeService,\n RuntimeTrigger,\n} from './runtime-graph';\nimport { dotValue, recordValue, assertValidDot } from './dot-syntax';\nimport { GraphRenderModel } from './graph-render-model';\nimport { RuntimeHtmlPage } from './runtime-html-page';\nimport {\n LEVEL_COLORS,\n QUEUE_FILL,\n QUEUE_SHAPE,\n QUEUE_LABEL_PREFIX,\n QUEUE_CLASS,\n EXTERNAL_SHAPES,\n EXTERNAL_FILLS,\n EXTERNAL_FILL,\n EXTERNAL_BORDER,\n CRON_FILL,\n CRON_BORDER,\n CUT_CYCLE_EDGE_ATTRS,\n} from './runtime-viz-theme';\n\n/** Apis per line inside a node label — beyond this the box grows wider than it is readable. */\nconst APIS_PER_LABEL_LINE = 3;\n\n/** Separator for the (service, external-library) grouping key; illegal in both project names. */\nconst PAIR_SEP = '|';\n\n/** Render options for the runtime graph. */\nexport class RuntimeVizOptions {\n constructor(\n /**\n * Draw the dashed terminal nodes for contracts nothing in-repo implements. On by default;\n * a repo whose external surface is noisy can turn them off in the declared Nx owner file's\n * direct policy-ID map (runtime-architecture.showExternalNodes).\n */\n public readonly showExternalNodes: boolean = true,\n ) {}\n}\n\n/**\n * The DISPLAY name of a project/service: scope stripped. LABELS ONLY.\n *\n * Never an identity. `@scope/public-api` and `public-api` are two distinct projects that strip to\n * the same string, and keying nodes or edges on that fuses them into one box (and draws the edge\n * between them as a self-loop). See {@link serviceNodeId} and lib/graph-names.ts.\n */\nfunction getShortName(name: string): string {\n return name.includes('/') ? name.split('/').pop()! : name;\n}\n\n/** The DOT identity of a runtime service: its project key, escaped — never the display name. */\n// webpieces-disable no-function-outside-class -- DOT id builder, matching getShortName in this file\nfunction serviceNodeId(name: string): string {\n return dotValue(name);\n}\n\n/** Chunk a list into `\\n`-separated label lines of at most APIS_PER_LABEL_LINE entries. */\n// webpieces-disable no-function-outside-class -- DOT label builder, matching getShortName in this file\nfunction labelList(entries: string[]): string {\n const lines: string[] = [];\n const safe = entries.map((entry: string) => dotValue(entry));\n for (let i = 0; i < safe.length; i += APIS_PER_LABEL_LINE) {\n lines.push(safe.slice(i, i + APIS_PER_LABEL_LINE).join(', '));\n }\n return lines.join('\\\\n');\n}\n\n/** Compact affordances carry full details in the HTML, including hidden external uses. */\n// webpieces-disable no-function-outside-class -- DOT label builder, matching getShortName in this file\nfunction nodeLabel(name: string, svc: RuntimeService, usesCount: number): string {\n // The DECLARED role when the graph carries one; the old inference only as a fallback for a\n // runtime-dependencies.json committed before `role` existed. Inferring it labelled every server\n // with no implements as a \"client\", which is exactly wrong for a queue-driven service.\n const role = svc.role ?? (svc.implements.length > 0 ? 'server' : 'client');\n // The declared name is quoted for the reader — those quotes MUST be DOT-escaped, or they end\n // the label string and the whole graph stops parsing.\n const declared = svc.serviceName === undefined ? '' : `, \\\\\"${dotValue(svc.serviceName)}\\\\\"`;\n let label = `${dotValue(getShortName(name))}\\\\n(${role}, L${svc.level}${declared})`;\n label += `\\\\nImplements (${svc.implements.length})\\\\nUses (${usesCount})`;\n return label;\n}\n\n/**\n * DOT for ONE NON-QUEUED runtime edge: a direct labeled SOLID arrow (producer calls consumer, the\n * response comes back). Queued (pubsub) hops are drawn by {@link queuesDot} instead, because they\n * are merged across methods and therefore cannot be emitted one edge at a time.\n *\n * Dashed is the graph's one line-level distinction, and it now says one of two things: an event that\n * returns as soon as it is queued, or (in red, labelled \"legacy cycle\") an edge a `cutLegacyCycle:`\n * tag holds out of leveling. A cut edge is still DRAWN — erasing the arrow would erase exactly the\n * debt the tag records — it is only drawn as the debt it is. See lib/cut-legacy-cycle-resolver.ts.\n */\n// webpieces-disable no-function-outside-class -- DOT string builder, matching getShortName in this file\nfunction edgeDot(edge: RuntimeEdge): string {\n const from = serviceNodeId(edge.from);\n const to = serviceNodeId(edge.to);\n const viaRaw =\n edge.via.length > 2\n ? `Uses (${edge.via.length}) ▾`\n : edge.via.map((v: string) => getShortName(v)).join(', ');\n const cut = edge.cutLegacyCycle === true;\n const label = cut ? `${dotValue(viaRaw)}\\\\nlegacy cycle` : dotValue(viaRaw);\n return ` \"${from}\" -> \"${to}\" [label=\"${label}\"${cut ? CUT_CYCLE_EDGE_ATTRS : ''}];\\n`;\n}\n\n/** The VISIBLE producers and consumers of one queue, gathered from the edges that survived hiding. */\nclass QueueEndpoints {\n readonly producers = new Set<string>();\n readonly consumers = new Set<string>();\n}\n\n/**\n * One drawn queue BOX: the queue keys (`Api.method`) it shows, and the endpoints they all share.\n * Several keys land in one box only when they agree on all three of contract, producers and\n * consumers — see {@link queuesDot}.\n */\nclass QueueGroup {\n constructor(\n public readonly members: string[],\n public readonly producers: string[],\n public readonly consumers: string[],\n ) {}\n}\n\n/**\n * The label line for ONE queue inside a box: `Api.method`, and the Terraform queue name ONLY when it\n * is not the derived `${Api}-${method}`.\n *\n * Printing a derived name was pure restatement — the line above it already said `Api.method`, so\n * every queue box carried a second line that added nothing and doubled its height. A `@Queue(...)`\n * OVERRIDE is the opposite: that string appears nowhere else on the graph, and it is the one string\n * Terraform must match, so it stays.\n */\n// webpieces-disable no-function-outside-class -- DOT label builder, matching getShortName in this file\nfunction queueLine(key: string, queue: RuntimeQueue | undefined): string {\n const line = recordValue(key);\n if (queue === undefined) return line;\n if (queue.queueName === `${queue.api}-${queue.method}`) return line;\n return `${line}\\\\nqueue: ${recordValue(queue.queueName)}`;\n}\n\n/** The node statement + enqueue/deliver arrows for one queue box. Service identities remain raw; only emitted DOT is escaped. */\n// webpieces-disable no-function-outside-class -- DOT string builder, matching getShortName in this file\nfunction queueBoxDot(\n id: string,\n body: string,\n producers: string[],\n consumers: string[],\n model: GraphRenderModel,\n): string {\n // Record-mode label: the text must clear recordValue(), and QUEUE_LABEL_PREFIX supplies the\n // empty leading field that draws the cylinder's end cap. Drop it and the node silently\n // degrades to a plain box.\n let dot = model.node(\n id,\n ` \"${id}\" [shape=${QUEUE_SHAPE}, style=\"filled\", fillcolor=\"${QUEUE_FILL}\", ` +\n `class=\"${QUEUE_CLASS}\", label=\"${QUEUE_LABEL_PREFIX}${body}\"];\\n`,\n );\n for (const producer of producers)\n dot += model.edge(\n producer,\n id,\n ` \"${serviceNodeId(producer)}\" -> \"${id}\" [label=\"enqueue\", style=dashed];\\n`,\n );\n for (const consumer of consumers)\n dot += model.edge(\n id,\n consumer,\n ` \"${id}\" -> \"${serviceNodeId(consumer)}\" [label=\"deliver\", style=dashed];\\n`,\n );\n return dot;\n}\n\n/**\n * Group the queued hops into boxes. The key is (contract, producer SET, consumer SET) — NOT the\n * (from,to) pair of some edge.\n *\n * A queue node converges EVERY producer and consumer of its method onto one box, so keying by a\n * service PAIR would re-split a two-producer queue into two boxes and invent topology nobody wrote.\n * Only queues of the same contract whose producer and consumer sets are IDENTICAL may share a box;\n * anything else stays separate, because merging it would assert a routing that does not exist.\n */\n// webpieces-disable no-function-outside-class -- DOT string builder, matching getShortName in this file\nfunction groupQueues(byQueue: Map<string, QueueEndpoints>, graph: RuntimeGraph): QueueGroup[] {\n const groups = new Map<string, QueueGroup>();\n // Sorted keys → the first member of every group, and therefore its node id, is deterministic.\n for (const key of [...byQueue.keys()].sort()) {\n const ends = byQueue.get(key)!;\n const producers = [...ends.producers].sort();\n const consumers = [...ends.consumers].sort();\n const api = graph.queues[key]?.api ?? key.split('.')[0];\n const mergeKey = [api, producers.join(','), consumers.join(',')].join(PAIR_SEP);\n const existing = groups.get(mergeKey);\n if (existing === undefined)\n groups.set(mergeKey, new QueueGroup([key], producers, consumers));\n else existing.members.push(key);\n }\n return [...groups.values()];\n}\n\n/**\n * The queued half of the graph: producer → QUEUE → consumer, drawn with a sideways-cylinder node and\n * DASHED arrows (an event returns as soon as it is queued).\n *\n * One BOX may list several queues, one per line. The unit Cloud Tasks and Terraform create is still\n * the METHOD — that is what runtime-dependencies.json records and this render never changes it — but\n * two methods of one contract flowing between exactly the same producers and consumers drew two\n * adjacent boxes whose only difference was the method name, and each carried a `queue:` line that\n * merely restated it. Listing them inside ONE box keeps every queue named (you can still see which\n * one is stuck) at a fraction of the node count.\n */\n// webpieces-disable no-function-outside-class -- DOT string builder, matching getShortName in this file\nfunction queuesDot(graph: RuntimeGraph, hidden: Set<string>, model: GraphRenderModel): string {\n const queued = graph.runtimeEdges.filter(\n (e: RuntimeEdge) => e.type === 'pubsub' && !hidden.has(e.from) && !hidden.has(e.to),\n );\n if (queued.length === 0) return '';\n\n let dot =\n '\\n // Queued hops. Each LINE in a box is one Cloud Tasks queue; queues of one contract\\n' +\n ' // sharing the same producers AND consumers are drawn in a single box.\\n';\n\n // A contract with no method table (no architecture/apis/<Api>.json for it) has no\n // per-method queue at all, so it keeps the historical unnamed per-pair box.\n const byQueue = new Map<string, QueueEndpoints>();\n for (const edge of queued) {\n const from = edge.from;\n const to = edge.to;\n if (edge.queue === undefined) {\n // Kept RAW: recordValue already applies dotValue, so escaping here would double it.\n const viaRaw =\n edge.via.length > 2\n ? `Uses (${edge.via.length}) ▾`\n : edge.via.map((v: string) => getShortName(v)).join(', ');\n dot += queueBoxDot(\n `queue__${dotId(edge.from)}__${dotId(edge.to)}`,\n `${recordValue(viaRaw)}\\\\nqueue`,\n [from],\n [to],\n model,\n );\n continue;\n }\n if (!byQueue.has(edge.queue)) byQueue.set(edge.queue, new QueueEndpoints());\n byQueue.get(edge.queue)!.producers.add(from);\n byQueue.get(edge.queue)!.consumers.add(to);\n }\n\n for (const group of groupQueues(byQueue, graph)) {\n const body = group.members\n .map((key: string) => queueLine(key, graph.queues[key]))\n .join('\\\\n');\n dot += queueBoxDot(\n `queue__${dotId(group.members[0])}`,\n body,\n group.producers,\n group.consumers,\n model,\n );\n }\n return dot;\n}\n\n/** A DOT-safe node-id fragment: anything but letters, digits and `_` becomes `_`. */\n// webpieces-disable no-function-outside-class -- DOT id builder, matching getShortName in this file\nfunction dotId(raw: string): string {\n return raw.replace(/[^A-Za-z0-9_]/g, '_');\n}\n\n/**\n * ONE external-system node statement: the shape/fill of what the system IS, its label, and a\n * parenthesised subtitle saying which way it faces.\n *\n * Shared by the OUTBOUND systems ({@link externalSystemsDot}) and the INBOUND callers\n * ({@link triggerDot}) on purpose. They emit into the SAME `system__<identity>` id space, so a\n * vendor this repo both calls and is called by is one box with arrows in both directions — and a\n * copy-pasted attribute string would have let the two drift until they stopped being one box.\n */\n// webpieces-disable no-function-outside-class -- DOT string builder, matching getShortName in this file\nfunction externalSystemNodeDot(\n identity: string,\n kind: string,\n label: string,\n subtitle: string,\n model: GraphRenderModel,\n): string {\n const shape = EXTERNAL_SHAPES[kind] ?? 'box';\n const fill = EXTERNAL_FILLS[kind] ?? EXTERNAL_FILL;\n // An Mrecord-shaped system needs the same empty leading field as a queue node, or it renders as\n // a plain box and silently loses the sideways-cylinder read.\n const isQueue = shape === QUEUE_SHAPE;\n const prefix = isQueue ? QUEUE_LABEL_PREFIX : '';\n const text = isQueue ? recordValue(label) : dotValue(label);\n // Only a queue-kind system is marked: a database here is an UPRIGHT cylinder and must not be\n // caught by the browser-side reshaping that lays queues on their side.\n const marker = isQueue ? `class=\"${QUEUE_CLASS}\", ` : '';\n return model.node(\n `system__${dotId(identity)}`,\n ` \"system__${dotId(identity)}\" [shape=${shape}, style=\"filled\", fillcolor=\"${fill}\", ` +\n `${marker}label=\"${prefix}${text}\\\\n(${subtitle})\"];\\n`,\n );\n}\n\n/**\n * The clock and outside-system nodes for endpoints NOTHING in-repo calls.\n *\n * A cron sweep and a GCP push subscription are real runtime entry points with real Terraform behind\n * them, but they produce no runtime EDGE (there is no in-repo caller), so until now they were simply\n * absent — a server's most operationally interesting endpoint could be invisible on its own graph.\n * Both are drawn pointing INTO the service that serves them, the opposite direction from\n * {@link externalDot}'s outbound vendor calls.\n *\n * The inbound box names the CALLER (`twilio`), never the contract. Naming the contract was the whole\n * bug: it restated what the service box directly below already says, while the one fact the box\n * exists to convey — which vendor is posting to us — appeared nowhere. The contract keeps its place\n * on the EDGE label, where `WhatsAppApi.inbound` reads as \"…posts to this method\".\n *\n * Identity is the caller's label in the SAME `system__` space {@link externalSystemsDot} uses, which\n * buys three things at once: two vendors hitting one contract are two boxes, one vendor hitting three\n * methods is one box with three arrows, and a vendor this repo also CALLS is that same single box.\n * `options` is passed in for the last of those — when that outbound half will be drawn, this half\n * must not restate the node statement.\n */\n// webpieces-disable no-function-outside-class -- DOT string builder, matching getShortName in this file\nfunction triggerDot(\n graph: RuntimeGraph,\n hidden: Set<string>,\n options: RuntimeVizOptions,\n model: GraphRenderModel,\n): string {\n const triggers = graph.triggers.filter((t: RuntimeTrigger) => !hidden.has(t.service));\n if (triggers.length === 0) return '';\n\n // Identities externalSystemsDot is about to draw; skipped here so the node is stated ONCE.\n const drawnOutbound = new Set(\n options.showExternalNodes ? Object.keys(graph.externalSystems ?? {}) : [],\n );\n const emitted = new Set<string>();\n let dot =\n '\\n // Entry points nothing in this repo calls: a clock, or a system outside the repo.\\n';\n for (const trigger of triggers) {\n const service = serviceNodeId(trigger.service);\n const label = dotValue(`${trigger.api}.${trigger.method}`);\n if (trigger.kind === 'cron') {\n const id = `cron__${dotId(`${trigger.api}_${trigger.method}`)}`;\n const schedule = dotValue(trigger.queueName ?? `${trigger.api}-${trigger.method}`);\n dot +=\n model.node(\n id,\n ` \"${id}\" [shape=circle, style=\"filled\", fillcolor=\"${CRON_FILL}\", ` +\n `color=\"${CRON_BORDER}\", label=\"⏰\\\\ncron\"];\\n`,\n ) +\n model.edge(\n id,\n trigger.service,\n ` \"${id}\" -> \"${service}\" [label=\"${label}\\\\n${schedule}\", color=\"${CRON_BORDER}\"];\\n`,\n );\n continue;\n }\n const caller = trigger.caller;\n // Undeclared: only possible for a graph generated BEFORE the caller was required (generation\n // now fails instead). DOTTED and question-marked, so \"we were never told who this is\" cannot\n // be mistaken for a named vendor.\n const id =\n caller === undefined\n ? `inbound__${dotId(trigger.api)}`\n : `system__${dotId(caller.label)}`;\n if (!emitted.has(id) && !(caller !== undefined && drawnOutbound.has(caller.label))) {\n emitted.add(id);\n dot +=\n caller === undefined\n ? model.node(\n id,\n ` \"${id}\" [shape=box, style=\"dotted,filled\", fillcolor=\"${EXTERNAL_FILL}\", ` +\n `color=\"${EXTERNAL_BORDER}\", label=\"${dotValue(trigger.api)}\\\\n? unknown caller\"];\\n`,\n )\n : externalSystemNodeDot(\n caller.label,\n caller.kind,\n caller.label,\n 'external caller',\n model,\n );\n }\n dot += model.edge(\n id,\n trigger.service,\n ` \"${id}\" -> \"${service}\" [label=\"${label}\", style=dashed, color=\"${EXTERNAL_BORDER}\"];\\n`,\n );\n }\n return dot;\n}\n\n/**\n * The DECLARED external systems — the ones that said what they are, so they get a shape that says\n * it: a cylinder for a database, a folder for a bucket. Everything undeclared falls through to\n * {@link externalDot}'s generic grey box, which is why adding this broke nothing existing.\n *\n * The arrows are SOLID. A call to firestore or postgres is synchronous — it returns a value — and\n * the graph's rule is that solid means \"response comes back\". Being outside the repo is carried by\n * the node's shape, never by the line style; conflating the two is what made a blocking database\n * read look like an event.\n */\n// webpieces-disable no-function-outside-class -- DOT string builder, matching getShortName in this file\nfunction externalSystemsDot(\n graph: RuntimeGraph,\n hidden: Set<string>,\n model: GraphRenderModel,\n): string {\n const systems = graph.externalSystems ?? {};\n const ids = Object.keys(systems).sort();\n if (ids.length === 0) return '';\n\n let dot =\n '\\n // Declared external systems — drawn with the shape of what they actually are.\\n';\n for (const id of ids) {\n const system = systems[id];\n dot += externalSystemNodeDot(\n id,\n system.kind,\n system.label,\n `external ${dotValue(system.kind)}`,\n model,\n );\n }\n for (const id of ids) {\n const system = systems[id];\n const via =\n system.apis.length === 0\n ? ''\n : ` [label=\"${system.apis.length > 2 ? `Uses (${system.apis.length}) ▾` : labelList([...system.apis].sort())}\"]`;\n for (const service of [...system.usedBy].sort()) {\n if (hidden.has(service)) continue;\n dot += model.edge(\n service,\n `system__${dotId(id)}`,\n ` \"${serviceNodeId(service)}\" -> \"system__${dotId(id)}\"${via};\\n`,\n );\n }\n }\n return dot;\n}\n\n/**\n * The dashed terminal nodes + edges for calls that LEAVE the repo. Built from `unresolvedUses` —\n * a contract used by a node and implemented by nobody in-repo — which the derivation already\n * computes and which was, until now, only ever printed as a warning.\n *\n * Grouped by the api-lib that owns the contracts, so a service reaching three firestore contracts\n * draws ONE `lib-firestore (external)` box rather than three. These are drawn, never derived: they\n * are absent from levels, cycle detection and the transitive implements attribution.\n *\n * A contract carrying an `@externalSystem` declaration is skipped here — {@link externalSystemsDot}\n * has already drawn it with a real shape, and rendering it in both places would double the node.\n */\n// webpieces-disable no-function-outside-class -- DOT string builder, matching getShortName in this file\nfunction externalDot(graph: RuntimeGraph, hidden: Set<string>, model: GraphRenderModel): string {\n // \"service|externalName\" -> the apis flowing over it.\n const apisByPair = new Map<string, string[]>();\n for (const use of graph.unresolvedUses) {\n if (hidden.has(use.service)) continue;\n if (graph.apis[use.api]?.externalSystem !== undefined) continue;\n // Keyed on the OWNER's full name, never its short name: two scoped api-libs whose short\n // names match are two external systems, and fusing them draws one box for both.\n const external = graph.apis[use.api]?.owner ?? use.api;\n const key = `${use.service}${PAIR_SEP}${external}`;\n if (!apisByPair.has(key)) apisByPair.set(key, []);\n apisByPair.get(key)!.push(use.api);\n }\n if (apisByPair.size === 0) return '';\n\n let dot = '\\n // Systems outside this repo — no in-repo service implements these contracts.\\n';\n // The node ID is prefixed so an external library can never collide with a service, and built\n // from the owner's FULL name so two same-short-named owners stay two boxes; only the label\n // carries the bare name.\n const externals = new Set([...apisByPair.keys()].map((key: string) => key.split(PAIR_SEP)[1]));\n for (const external of [...externals].sort()) {\n dot += model.node(\n `external__${external}`,\n ` \"external__${dotValue(external)}\" [shape=box, style=\"dashed,filled\", fillcolor=\"${EXTERNAL_FILL}\", ` +\n `color=\"${EXTERNAL_BORDER}\", label=\"${dotValue(getShortName(external))}\\\\n(external)\"];\\n`,\n );\n }\n for (const key of [...apisByPair.keys()].sort()) {\n const parts = key.split(PAIR_SEP);\n const service = parts[0];\n const external = parts[1];\n const apis = apisByPair.get(key)!.sort();\n const via = apis.length > 2 ? `Uses (${apis.length}) ▾` : labelList(apis);\n // SOLID: this is a synchronous call that returns a value. Dashed is reserved for events, and\n // \"outside the repo\" is already said by the node's dashed border.\n dot += model.edge(\n service,\n `external__${external}`,\n ` \"${serviceNodeId(service)}\" -> \"external__${dotValue(external)}\" ` +\n `[label=\"${via}\", color=\"${EXTERNAL_BORDER}\"];\\n`,\n );\n }\n return dot;\n}\n\n/** Build the Graphviz DOT for the runtime service graph. */\n// webpieces-disable no-function-outside-class -- module entry point, matching the sibling builders here\nexport function generateRuntimeDot(\n graph: RuntimeGraph,\n title: string = 'WebPieces Runtime Architecture',\n options: RuntimeVizOptions = new RuntimeVizOptions(),\n): string {\n return generateRuntimeRenderModel(graph, title, options).fullDot;\n}\n\n/** Capture synthetic drawable nodes and edges at their original emission sites. */\n// webpieces-disable no-function-outside-class -- renderer entry point matching generateRuntimeDot\nexport function generateRuntimeRenderModel(\n graph: RuntimeGraph,\n title: string = 'WebPieces Runtime Architecture',\n options: RuntimeVizOptions = new RuntimeVizOptions(),\n): GraphRenderModel {\n const model = new GraphRenderModel();\n const details = new RuntimeDetails(graph, options.showExternalNodes);\n let dot = 'digraph RuntimeArchitecture {\\n';\n dot += ' rankdir=TB;\\n';\n dot += ' node [shape=box, style=\"filled,rounded\", fontname=\"Arial\"];\\n';\n dot += ' edge [fontname=\"Arial\", fontsize=12];\\n\\n';\n\n model.header = dot;\n\n // Services tagged drawOnGraph:false stay in the JSON but are omitted here —\n // both their node and any edge touching them are dropped from the render.\n const hidden = new Set(\n Object.keys(graph.services).filter(\n (name: string) => graph.services[name].drawOnGraph === false,\n ),\n );\n\n for (const name of Object.keys(graph.services)) {\n if (hidden.has(name)) continue;\n const svc = graph.services[name];\n const color = LEVEL_COLORS[svc.level] || '#F5F5F5';\n dot += model.node(\n name,\n ` \"${serviceNodeId(name)}\" [fillcolor=\"${color}\", label=\"${nodeLabel(name, svc, details.nodes[name].used.length)}\"];\\n`,\n svc.products ?? [],\n );\n }\n\n dot += '\\n';\n\n for (const edge of graph.runtimeEdges) {\n if (hidden.has(edge.from) || hidden.has(edge.to)) continue;\n // Queued hops are merged across methods, so they cannot be emitted one edge at a time.\n if (edge.type === 'pubsub') continue;\n dot += model.edge(edge.from, edge.to, edgeDot(edge));\n }\n\n dot += queuesDot(graph, hidden, model);\n dot += triggerDot(graph, hidden, options, model);\n\n if (options.showExternalNodes) {\n dot += externalSystemsDot(graph, hidden, model);\n dot += externalDot(graph, hidden, model);\n }\n\n const footerStart = dot.length;\n dot += '\\n labelloc=\"t\";\\n';\n dot += ` label=\"${dotValue(title)}\\\\n(from architecture/runtime-dependencies.json)\";\\n`;\n dot += ' fontsize=20;\\n';\n dot += '}\\n';\n // Nothing downstream parses this DOT until a human opens the page, so parse-shape is checked\n // HERE — a graph that cannot render is a generation failure, not a blank page to discover later.\n assertValidDot(dot, 'runtime-architecture.dot');\n model.footer = dot.slice(footerStart);\n model.completeEndpoints();\n // #1179: a queue, trigger or external system belongs to the products of the services it touches.\n model.attachProducts((id: string): boolean => graph.services[id] !== undefined);\n model.fullDot = dot;\n return model;\n}\n\nexport interface RuntimeVisualizationPaths {\n dotPath: string;\n htmlPath: string;\n}\n\n/** Write the DOT + HTML renderings to tmp/webpieces/. */\nexport function writeRuntimeVisualization(\n graph: RuntimeGraph,\n workspaceRoot: string,\n title: string = 'WebPieces Runtime Architecture',\n options: RuntimeVizOptions = new RuntimeVizOptions(),\n): RuntimeVisualizationPaths {\n const outputDir = path.join(workspaceRoot, 'tmp', 'webpieces');\n if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });\n\n const model = generateRuntimeRenderModel(graph, title, options);\n const dot = model.fullDot;\n const dotPath = path.join(outputDir, 'runtime-architecture.dot');\n fs.writeFileSync(dotPath, dot, 'utf-8');\n\n const htmlPath = path.join(outputDir, 'runtime-architecture.html');\n fs.writeFileSync(\n htmlPath,\n new RuntimeHtmlPage().render(model, title, graph, options.showExternalNodes),\n 'utf-8',\n );\n\n return { dotPath, htmlPath };\n}\n"]}
|
|
@@ -59,8 +59,12 @@ class SavedSnapshot {
|
|
|
59
59
|
Object.values(graph).some((entry) => !this.isMap(entry) ||
|
|
60
60
|
!Number.isFinite(entry.level) ||
|
|
61
61
|
!Array.isArray(entry.dependsOn) ||
|
|
62
|
-
entry.dependsOn.some((dep) => typeof dep !== 'string')
|
|
63
|
-
|
|
62
|
+
entry.dependsOn.some((dep) => typeof dep !== 'string') ||
|
|
63
|
+
// Optional (#1179): absent in a file written before products existed.
|
|
64
|
+
(entry.products !== undefined &&
|
|
65
|
+
(!Array.isArray(entry.products) ||
|
|
66
|
+
entry.products.some((product) => typeof product !== 'string'))))) {
|
|
67
|
+
throw this.failure(`Unusable saved project graph at ${graphPath}: expected projects with numeric level, string dependsOn arrays and (when present) string products arrays`);
|
|
64
68
|
}
|
|
65
69
|
}
|
|
66
70
|
validateRuntime(graph) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"saved-snapshot.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/saved-snapshot.ts"],"names":[],"mappings":";;;AAAA,0DAAgE;AAGhE,iDAAoE;AACpE,yDAI4B;AAC5B,wCAAqC;AAErC,+FAA+F;AAC/F,MAAa,aAAa;IACtB,OAAO;QACH,OAAO,uDAAuD,CAAC;IACnE,CAAC;IAED,OAAO;QACH,OAAO,CACH,yEAAyE;YACzE,yGAAyG;YACzG,IAAI,CAAC,OAAO,EAAE,CACjB,CAAC;IACN,CAAC;IAED,IAAI;QACA,OAAO,CACH,0GAA0G;YAC1G,0DAA0D;YAC1D,yEAAyE,CAC5E,CAAC;IACN,CAAC;IAED,YAAY,CAAC,aAAqB,EAAE,SAAiB;QACjD,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,IAAA,+BAAgB,EAAC,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC;QAC5F,IAAI,IAAI,KAAK,IAAI;YAAE,MAAM,IAAI,CAAC,OAAO,CAAC,2BAA2B,SAAS,EAAE,CAAC,CAAC;QAC9E,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QAChD,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,WAAW,CAAC,aAAqB;QAC7B,IAAI,CAAC,IAAA,yCAAsB,EAAC,aAAa,CAAC,EAAE,CAAC;YACzC,MAAM,IAAI,CAAC,OAAO,CAAC,2BAA2B,6CAA0B,EAAE,CAAC,CAAC;QAChF,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,6CAA0B,EAAE,GAAG,EAAE,CAC7D,IAAA,mCAAgB,EAAC,aAAa,CAAC,CAClC,CAAC;QACF,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;QAC5B,OAAO,KAAK,CAAC;IACjB,CAAC;IAEO,YAAY,CAAI,SAAiB,EAAE,IAAa;QACpD,2JAA2J;QAC3J,IAAI,CAAC;YACD,OAAO,IAAI,EAAE,CAAC;QAClB,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,IAAI,KAAK,YAAY,4BAAa;gBAAE,MAAM,KAAK,CAAC;YAChD,MAAM,IAAI,CAAC,OAAO,CACd,iCAAiC,SAAS,KAAK,KAAK,CAAC,OAAO,EAAE,EAC9D,KAAK,CACR,CAAC;QACN,CAAC;IACL,CAAC;IAEO,OAAO,CAAC,OAAe,EAAE,KAAa;QAC1C,OAAO,IAAI,4BAAa,CACpB,6BAA6B,EAC7B,OAAO,EACP,SAAS,EACT,SAAS,EACT,CAAC,IAAI,qBAAM,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,IAAI,CAAC,CAAC,EAClC,SAAS,EACT,KAAK,CACR,CAAC;IACN,CAAC;IAED,+FAA+F;IACvF,KAAK,CAAC,KAAc;QACxB,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAChF,CAAC;IAED,gBAAgB,CAAC,KAAoB,EAAE,SAAiB;QACpD,IACI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;YAClB,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CACrB,CAAC,KAAK,EAAE,EAAE,CACN,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;gBAClB,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC;gBAC7B,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC;gBAC/B,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"saved-snapshot.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/saved-snapshot.ts"],"names":[],"mappings":"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","sourcesContent":["import { RuleFailError, Option } from '@webpieces/rules-config';\nimport type { EnhancedGraph } from './graph-sorter';\nimport type { RuntimeGraph } from './runtime-graph-model';\nimport { DependenciesFile, loadBlessedGraph } from './graph-loader';\nimport {\n DEFAULT_RUNTIME_GRAPH_PATH,\n loadRuntimeGraph,\n runtimeGraphFileExists,\n} from './runtime-graph-io';\nimport { toError } from '../toError';\n\n/** Saved graphs have no reliable generation timestamp; file mtimes also change on checkout. */\nexport class SavedSnapshot {\n refresh(): string {\n return 'Refresh explicitly: pnpm nx run architecture:generate';\n }\n\n message(): string {\n return (\n 'Saved generated snapshot. Viewing does not refresh architecture facts. ' +\n 'Freshness unknown: no reliable generation provenance is recorded; current source has not been checked. ' +\n this.refresh()\n );\n }\n\n html(): string {\n return (\n `<p class=\"hint\"><strong>Saved generated snapshot.</strong> Viewing does not refresh architecture facts. ` +\n `Freshness unknown; current source has not been checked. ` +\n `Refresh explicitly: <code>pnpm nx run architecture:generate</code>.</p>`\n );\n }\n\n loadProjects(workspaceRoot: string, graphPath: string): DependenciesFile {\n const file = this.readArtifact(graphPath, () => loadBlessedGraph(workspaceRoot, graphPath));\n if (file === null) throw this.failure(`No saved graph found at ${graphPath}`);\n this.validateProjects(file.projects, graphPath);\n return file;\n }\n\n loadRuntime(workspaceRoot: string): RuntimeGraph {\n if (!runtimeGraphFileExists(workspaceRoot)) {\n throw this.failure(`No saved graph found at ${DEFAULT_RUNTIME_GRAPH_PATH}`);\n }\n const graph = this.readArtifact(DEFAULT_RUNTIME_GRAPH_PATH, () =>\n loadRuntimeGraph(workspaceRoot),\n );\n this.validateRuntime(graph);\n return graph;\n }\n\n private readArtifact<T>(graphPath: string, load: () => T): T {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- saved JSON decoding boundary adds the artifact path and repair while preserving the cause\n try {\n return load();\n } catch (err: unknown) {\n const error = toError(err);\n if (error instanceof RuleFailError) throw error;\n throw this.failure(\n `Failed to read saved graph at ${graphPath}: ${error.message}`,\n error,\n );\n }\n }\n\n private failure(message: string, cause?: Error): RuleFailError {\n return new RuleFailError(\n 'saved-architecture-snapshot',\n message,\n undefined,\n undefined,\n [new Option(this.refresh(), true)],\n undefined,\n cause,\n );\n }\n\n // webpieces-disable no-any-unknown -- saved JSON is untrusted until its shape has been checked\n private isMap(value: unknown): boolean {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n }\n\n validateProjects(graph: EnhancedGraph, graphPath: string): void {\n if (\n !this.isMap(graph) ||\n Object.values(graph).some(\n (entry) =>\n !this.isMap(entry) ||\n !Number.isFinite(entry.level) ||\n !Array.isArray(entry.dependsOn) ||\n entry.dependsOn.some((dep: string) => typeof dep !== 'string') ||\n // Optional (#1179): absent in a file written before products existed.\n (entry.products !== undefined &&\n (!Array.isArray(entry.products) ||\n entry.products.some((product: string) => typeof product !== 'string'))),\n )\n ) {\n throw this.failure(\n `Unusable saved project graph at ${graphPath}: expected projects with numeric level, string dependsOn arrays and (when present) string products arrays`,\n );\n }\n }\n\n validateRuntime(graph: RuntimeGraph | null): asserts graph is RuntimeGraph {\n if (\n graph === null ||\n !this.isMap(graph) ||\n !this.isMap(graph.services) ||\n !this.isMap(graph.apis) ||\n !this.isMap(graph.queues) ||\n !Array.isArray(graph.runtimeEdges) ||\n !Array.isArray(graph.unresolvedUses) ||\n !Array.isArray(graph.triggers)\n ) {\n throw this.failure(\n `Unusable saved runtime graph at ${DEFAULT_RUNTIME_GRAPH_PATH}: expected services, apis, queues and runtimeEdges, unresolvedUses, triggers arrays`,\n );\n }\n }\n}\n"]}
|
package/src/lib/tag-truth.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tag truth — the
|
|
2
|
+
* Tag truth — the four GRAPH rules that make an nx `role:*` / `framework:*` / `product:*` tag true
|
|
3
|
+
* (#1064, #1179):
|
|
3
4
|
*
|
|
4
5
|
* api-lib-dependencies (D2) — a `role:api-lib` depends only on other `role:api-lib` projects plus a
|
|
5
6
|
* stated list of outside packages; a `role:api-client` may also import
|
|
@@ -8,6 +9,9 @@
|
|
|
8
9
|
* everything under them carries one of those two roles.
|
|
9
10
|
* framework-folder (D9) — the folder a library lives in and its framework set + role agree,
|
|
10
11
|
* both directions.
|
|
12
|
+
* product-tags (#1179) — every project whose role is listed (seed: server, client, app)
|
|
13
|
+
* carries a well-formed `product:<name>` tag, the seed the product
|
|
14
|
+
* filter and Product color mode derive membership from.
|
|
11
15
|
*
|
|
12
16
|
* They run beside `library-types-match-client` and `role-dependency` (graph-metadata.ts), in
|
|
13
17
|
* `architecture:generate` and `validate-architecture-unchanged`, AFTER enrichGraph has resolved every
|
|
@@ -55,15 +59,21 @@ export declare class FrameworkFolderRule {
|
|
|
55
59
|
readonly folders: readonly FrameworkFolder[];
|
|
56
60
|
constructor(folders: readonly FrameworkFolder[]);
|
|
57
61
|
}
|
|
62
|
+
/** `product-tags`, resolved: the roles whose every project must declare a `product:<name>` tag. */
|
|
63
|
+
export declare class ProductTagsRule {
|
|
64
|
+
readonly roles: readonly string[];
|
|
65
|
+
constructor(roles: readonly string[]);
|
|
66
|
+
}
|
|
58
67
|
/**
|
|
59
|
-
* The
|
|
68
|
+
* The four rules' switches, read from webpieces.config.json once. A rule that is OFF (or time-boxed /
|
|
60
69
|
* branch-scoped off) is `null`.
|
|
61
70
|
*/
|
|
62
71
|
export declare class TagTruthRules {
|
|
63
72
|
readonly apiLibDependencies: ApiLibDependenciesRule | null;
|
|
64
73
|
readonly apiLibPath: ApiLibPathRule | null;
|
|
65
74
|
readonly frameworkFolder: FrameworkFolderRule | null;
|
|
66
|
-
|
|
75
|
+
readonly productTags: ProductTagsRule | null;
|
|
76
|
+
constructor(apiLibDependencies: ApiLibDependenciesRule | null, apiLibPath: ApiLibPathRule | null, frameworkFolder: FrameworkFolderRule | null, productTags: ProductTagsRule | null);
|
|
67
77
|
/** Every rule off — what a caller that configured nothing asked for. */
|
|
68
78
|
static none(): TagTruthRules;
|
|
69
79
|
anyEnabled(): boolean;
|
|
@@ -88,6 +98,13 @@ export declare class TagTruthValidator {
|
|
|
88
98
|
private readonly rules;
|
|
89
99
|
constructor(rules: TagTruthRules);
|
|
90
100
|
problems(graph: EnhancedGraph, infos: Map<string, ProjectInfo>, imports: ReadonlyMap<string, ProjectImports>): string[];
|
|
101
|
+
/**
|
|
102
|
+
* #1179 — a project whose role the rule lists declares at least one well-formed `product:<name>`.
|
|
103
|
+
* The DECLARED tags are read, never the derived `products`: a server inheriting membership from an
|
|
104
|
+
* orchestrator that depends on it still states its own product. A malformed tag is not counted here
|
|
105
|
+
* because enrichGraph already fails on it, naming the tag.
|
|
106
|
+
*/
|
|
107
|
+
private productTagProblems;
|
|
91
108
|
/** D2 — the workspace edges and the outside packages of ONE api-lib / api-client. */
|
|
92
109
|
private dependencyProblems;
|
|
93
110
|
/** A package some workspace project publishes — its edge is judged as a workspace dependency above. */
|
package/src/lib/tag-truth.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Tag truth — the
|
|
3
|
+
* Tag truth — the four GRAPH rules that make an nx `role:*` / `framework:*` / `product:*` tag true
|
|
4
|
+
* (#1064, #1179):
|
|
4
5
|
*
|
|
5
6
|
* api-lib-dependencies (D2) — a `role:api-lib` depends only on other `role:api-lib` projects plus a
|
|
6
7
|
* stated list of outside packages; a `role:api-client` may also import
|
|
@@ -9,6 +10,9 @@
|
|
|
9
10
|
* everything under them carries one of those two roles.
|
|
10
11
|
* framework-folder (D9) — the folder a library lives in and its framework set + role agree,
|
|
11
12
|
* both directions.
|
|
13
|
+
* product-tags (#1179) — every project whose role is listed (seed: server, client, app)
|
|
14
|
+
* carries a well-formed `product:<name>` tag, the seed the product
|
|
15
|
+
* filter and Product color mode derive membership from.
|
|
12
16
|
*
|
|
13
17
|
* They run beside `library-types-match-client` and `role-dependency` (graph-metadata.ts), in
|
|
14
18
|
* `architecture:generate` and `validate-architecture-unchanged`, AFTER enrichGraph has resolved every
|
|
@@ -20,12 +24,13 @@
|
|
|
20
24
|
* test or a caller that configured nothing asked for — not a default.
|
|
21
25
|
*/
|
|
22
26
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
-
exports.TagTruthCheck = exports.TagTruthValidator = exports.ProjectImports = exports.TagTruthRules = exports.FrameworkFolderRule = exports.FrameworkFolder = exports.ApiLibPathRule = exports.ApiLibDependenciesRule = exports.API_CLIENT_ROLE = exports.API_LIB_ROLE = void 0;
|
|
27
|
+
exports.TagTruthCheck = exports.TagTruthValidator = exports.ProjectImports = exports.TagTruthRules = exports.ProductTagsRule = exports.FrameworkFolderRule = exports.FrameworkFolder = exports.ApiLibPathRule = exports.ApiLibDependenciesRule = exports.API_CLIENT_ROLE = exports.API_LIB_ROLE = void 0;
|
|
24
28
|
const tslib_1 = require("tslib");
|
|
25
29
|
const fs = tslib_1.__importStar(require("fs"));
|
|
26
30
|
const path = tslib_1.__importStar(require("path"));
|
|
27
31
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
28
32
|
const graph_metadata_1 = require("./graph-metadata");
|
|
33
|
+
const product_resolver_1 = require("./product-resolver");
|
|
29
34
|
const rule_gate_1 = require("./rule-gate");
|
|
30
35
|
const dep_usage_scanner_1 = require("./dep-usage-scanner");
|
|
31
36
|
const toError_1 = require("../toError");
|
|
@@ -85,26 +90,39 @@ class FrameworkFolderRule {
|
|
|
85
90
|
}
|
|
86
91
|
}
|
|
87
92
|
exports.FrameworkFolderRule = FrameworkFolderRule;
|
|
93
|
+
/** `product-tags`, resolved: the roles whose every project must declare a `product:<name>` tag. */
|
|
94
|
+
class ProductTagsRule {
|
|
95
|
+
roles;
|
|
96
|
+
constructor(roles) {
|
|
97
|
+
this.roles = roles;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
exports.ProductTagsRule = ProductTagsRule;
|
|
88
101
|
/**
|
|
89
|
-
* The
|
|
102
|
+
* The four rules' switches, read from webpieces.config.json once. A rule that is OFF (or time-boxed /
|
|
90
103
|
* branch-scoped off) is `null`.
|
|
91
104
|
*/
|
|
92
105
|
class TagTruthRules {
|
|
93
106
|
apiLibDependencies;
|
|
94
107
|
apiLibPath;
|
|
95
108
|
frameworkFolder;
|
|
96
|
-
|
|
109
|
+
productTags;
|
|
110
|
+
constructor(apiLibDependencies, apiLibPath, frameworkFolder, productTags) {
|
|
97
111
|
this.apiLibDependencies = apiLibDependencies;
|
|
98
112
|
this.apiLibPath = apiLibPath;
|
|
99
113
|
this.frameworkFolder = frameworkFolder;
|
|
114
|
+
this.productTags = productTags;
|
|
100
115
|
}
|
|
101
116
|
/** Every rule off — what a caller that configured nothing asked for. */
|
|
102
117
|
// webpieces-disable no-function-outside-class -- static factory of this class
|
|
103
118
|
static none() {
|
|
104
|
-
return new TagTruthRules(null, null, null);
|
|
119
|
+
return new TagTruthRules(null, null, null, null);
|
|
105
120
|
}
|
|
106
121
|
anyEnabled() {
|
|
107
|
-
return this.apiLibDependencies !== null ||
|
|
122
|
+
return (this.apiLibDependencies !== null ||
|
|
123
|
+
this.apiLibPath !== null ||
|
|
124
|
+
this.frameworkFolder !== null ||
|
|
125
|
+
this.productTags !== null);
|
|
108
126
|
}
|
|
109
127
|
/** `mode: OFF` and both escape hatches come from RuleGate, so there is ONE reading of them. */
|
|
110
128
|
// webpieces-disable no-function-outside-class -- static factory of this class
|
|
@@ -116,9 +134,10 @@ class TagTruthRules {
|
|
|
116
134
|
const deps = optionsOf("api-lib-dependencies");
|
|
117
135
|
const where = optionsOf("api-lib-path");
|
|
118
136
|
const folder = optionsOf("framework-folder");
|
|
137
|
+
const products = optionsOf("product-tags");
|
|
119
138
|
return new TagTruthRules(deps === null ? null : TagTruthRules.dependenciesRule(deps), where === null ? null : new ApiLibPathRule(where['paths']), folder === null
|
|
120
139
|
? null
|
|
121
|
-
: new FrameworkFolderRule(folder['entries'].map(FrameworkFolder.fromEntry)));
|
|
140
|
+
: new FrameworkFolderRule(folder['entries'].map(FrameworkFolder.fromEntry)), products === null ? null : new ProductTagsRule(products['roles']));
|
|
122
141
|
}
|
|
123
142
|
// webpieces-disable no-function-outside-class -- private static reader of this class
|
|
124
143
|
static dependenciesRule(options) {
|
|
@@ -166,9 +185,31 @@ class TagTruthValidator {
|
|
|
166
185
|
if (this.rules.frameworkFolder !== null) {
|
|
167
186
|
problems.push(...this.folderProblems(name, root, entry, this.rules.frameworkFolder));
|
|
168
187
|
}
|
|
188
|
+
if (this.rules.productTags !== null) {
|
|
189
|
+
problems.push(...this.productTagProblems(info, root, entry, this.rules.productTags));
|
|
190
|
+
}
|
|
169
191
|
}
|
|
170
192
|
return problems;
|
|
171
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* #1179 — a project whose role the rule lists declares at least one well-formed `product:<name>`.
|
|
196
|
+
* The DECLARED tags are read, never the derived `products`: a server inheriting membership from an
|
|
197
|
+
* orchestrator that depends on it still states its own product. A malformed tag is not counted here
|
|
198
|
+
* because enrichGraph already fails on it, naming the tag.
|
|
199
|
+
*/
|
|
200
|
+
productTagProblems(info, root, entry, rule) {
|
|
201
|
+
const role = entry.role;
|
|
202
|
+
if (role === undefined || !rule.roles.includes(role))
|
|
203
|
+
return [];
|
|
204
|
+
if (new product_resolver_1.ProductResolver().resolve(info).products.length > 0)
|
|
205
|
+
return [];
|
|
206
|
+
return [
|
|
207
|
+
`${"product-tags"}: '${info.name}' is role:${role} but carries no product tag — every ` +
|
|
208
|
+
`${rule.roles.map((each) => `role:${each}`).join(' / ')} must name the product(s) it belongs ` +
|
|
209
|
+
`to, so the architecture graphs can show one product. Add "product:<name>" (lowercase kebab, ` +
|
|
210
|
+
`several allowed) to the "tags" in ${root}/project.json, e.g. "tags": [..., "product:<name>"].`,
|
|
211
|
+
];
|
|
212
|
+
}
|
|
172
213
|
/** D2 — the workspace edges and the outside packages of ONE api-lib / api-client. */
|
|
173
214
|
dependencyProblems(name, entry, graph, imports, rule) {
|
|
174
215
|
const role = entry.role;
|