@webpieces/nx-webpieces-rules 0.4.900 → 0.4.902
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/package.json +7 -7
- package/src/lib/graph-legend.js +2 -2
- package/src/lib/graph-legend.js.map +1 -1
- package/src/lib/graph-loader.d.ts +2 -2
- package/src/lib/graph-loader.js +2 -2
- package/src/lib/graph-loader.js.map +1 -1
- package/src/lib/graph-metadata.d.ts +1 -1
- package/src/lib/graph-metadata.js +3 -4
- package/src/lib/graph-metadata.js.map +1 -1
- package/src/lib/graph-mode-state.client.js +1 -1
- package/src/lib/graph-mode-state.client.js.map +1 -1
- package/src/lib/graph-page-shell.d.ts +1 -1
- package/src/lib/graph-page-shell.js +2 -2
- package/src/lib/graph-page-shell.js.map +1 -1
- package/src/lib/graph-products.d.ts +24 -42
- package/src/lib/graph-products.js +31 -127
- package/src/lib/graph-products.js.map +1 -1
- package/src/lib/graph-sorter.d.ts +3 -6
- package/src/lib/graph-sorter.js.map +1 -1
- package/src/lib/product-dependencies.d.ts +5 -3
- package/src/lib/product-dependencies.js +6 -5
- package/src/lib/product-dependencies.js.map +1 -1
- package/src/lib/product-resolver.d.ts +5 -6
- package/src/lib/product-resolver.js +5 -6
- package/src/lib/product-resolver.js.map +1 -1
- package/src/lib/runtime-graph-model.d.ts +4 -6
- package/src/lib/runtime-graph-model.js.map +1 -1
- package/src/lib/runtime-graph.js +1 -1
- package/src/lib/runtime-graph.js.map +1 -1
- package/src/lib/tag-truth.d.ts +2 -2
- package/src/lib/tag-truth.js +2 -2
- package/src/lib/tag-truth.js.map +1 -1
|
@@ -78,7 +78,7 @@ export declare class GraphPageShell {
|
|
|
78
78
|
private filterPopover;
|
|
79
79
|
/**
|
|
80
80
|
* The Product group (#1179): one chip per declared product, each with its palette swatch. Several
|
|
81
|
-
* chips select the UNION of their
|
|
81
|
+
* chips select the UNION of their members; the group intersects with the others. Omitted when the
|
|
82
82
|
* workspace declares no product.
|
|
83
83
|
*/
|
|
84
84
|
private productGroup;
|
|
@@ -176,7 +176,7 @@ class GraphPageShell {
|
|
|
176
176
|
}
|
|
177
177
|
/**
|
|
178
178
|
* The Product group (#1179): one chip per declared product, each with its palette swatch. Several
|
|
179
|
-
* chips select the UNION of their
|
|
179
|
+
* chips select the UNION of their members; the group intersects with the others. Omitted when the
|
|
180
180
|
* workspace declares no product.
|
|
181
181
|
*/
|
|
182
182
|
productGroup(products) {
|
|
@@ -231,7 +231,7 @@ class GraphPageShell {
|
|
|
231
231
|
<p>Click a box for Lock, Filter Unconnected, or Implements. Implements in the box is a plain summary. Click Uses on a relationship line for its complete details. Hover traces chains without opening details.</p>
|
|
232
232
|
<p>Lock pins separate incoming and outgoing chains; hover adds another chain temporarily. Hide unconnected removes unrelated boxes independently. Clear all clears facets, leaving Lock and chain filtering intact.</p>
|
|
233
233
|
<p>Filters intersect declared runtime, role, product closure and Nx build scope. Counts describe services; attached contextual nodes are counted separately. Impact uses compile-project sets, never runtime reachability. Queues and vendors are context, never source owners or CI projects.</p>
|
|
234
|
-
<p>Runtime uses declared framework colors, Architecture declared roles; contextual nodes keep their kind colors. Product colors
|
|
234
|
+
<p>Runtime uses declared framework colors, Architecture declared roles; contextual nodes keep their kind colors. Product colors come from each project's declared product: tags; contextual nodes inherit adjacent memberships. Unknown metadata is neutral. Missing saved metadata requires explicit architecture:generate.</p>
|
|
235
235
|
<p><kbd>/</kbd> Focus search · <kbd>1</kbd>–<kbd>4</kbd> modes · <kbd>Esc</kbd> dismiss or unlock</p>
|
|
236
236
|
</div>`;
|
|
237
237
|
return `<div class="wp-pop wp-help" id="wp-help" role="dialog" aria-label="How to read this graph" hidden>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-page-shell.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-page-shell.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The architecture page's shell (#1155): a collapsible dark drawer on the left holding every control,\n * and the graph filling the rest of the viewport.\n *\n * Before this, two hint paragraphs, a ~650px three-column legend, a lock dropdown and a zoom bar all\n * sat ABOVE the graph, so a laptop's first screen showed no graph at all. Now the hints are a \"?\"\n * popover, the legend lives in the drawer (and can pop out over the graph), zoom floats bottom-right\n * (graph-navigation.ts) and the responsibilities open in a right-hand panel.\n *\n * Markup only. Behaviour is graph-visualizer.client.ts; styling is graph-page-styles.ts. No element\n * carries a `style=` attribute — every look is a class.\n */\n\nimport { FRAMEWORK_ORDER, GRAPH_MODES, GraphMode, ModeInfo, ROLE_STYLES, RoleStyle } from './graph-color-modes';\nimport { ProductColor, ProductPalette } from './graph-products';\n\n/**\n * The Filter popover's \"Changes on this branch\" choices. The page computes each set from the Impact\n * sidecar (graph-impact.ts): CHANGED = touched; DEPENDENTS = touched + affected (nx affected: their\n * tests re-run); DEPENDENCIES = touched + every transitive dependency of it; BUILD = touched +\n * affected + every build input — the dependencies of the DEPENDENTS too, which is why it is more than\n * the union of the two rows above it.\n *\n * \"Dependents\" are what USE a project and \"dependencies\" what it uses (#1179). \"Dependee\" would mean\n * the thing depended ON — the opposite of the dependents row — so it is never used.\n */\nexport enum ChangeScope {\n EVERYTHING = 'everything',\n CHANGED = 'changed',\n DEPENDENTS = 'dependents',\n DEPENDENCIES = 'dependencies',\n BUILD = 'build',\n}\n\n/**\n * One change scope: its name and the grey second line under it, like the Color-by menu. These are the\n * ONE copy of the labels — the page script reads each name back from its `data-wp-scope-label` (the\n * top-bar pill), so the two cannot drift apart.\n */\nexport class ScopeOption {\n constructor(\n public readonly scope: ChangeScope,\n public readonly label: string,\n public readonly subtitle: string,\n ) {}\n}\n\nexport const SCOPE_OPTIONS: readonly ScopeOption[] = [\n new ScopeOption(ChangeScope.EVERYTHING, 'All', 'every project'),\n new ScopeOption(ChangeScope.CHANGED, 'Changed', 'owns a changed file'),\n new ScopeOption(ChangeScope.DEPENDENTS, 'Changed + dependents', '+ everything that uses them (their tests re-run)'),\n new ScopeOption(ChangeScope.DEPENDENCIES, 'Changed + dependencies', '+ everything they use'),\n new ScopeOption(ChangeScope.BUILD, 'Changed + dependents + dependencies', 'everything CI builds for this branch'),\n];\n\n/** Everything the shell frames, already rendered by its owner. */\nexport class ShellParts {\n constructor(\n public readonly title: string,\n public readonly lockControl: string,\n public readonly legend: string,\n public readonly edgeKey: string,\n public readonly filterStatus: string,\n public readonly snapshot: string,\n public readonly responsibilities: string,\n /** Every declared product, sorted (#1179); empty hides the Filter's Product group. */\n public readonly products: string[],\n public readonly runtime: boolean = false,\n public readonly frameworks: readonly string[] = FRAMEWORK_ORDER,\n public readonly roles: readonly string[] = ROLE_STYLES.map((style: RoleStyle): string => style.role),\n public readonly emptyState: string = '',\n ) {}\n}\n\nexport class GraphPageShell {\n body(parts: ShellParts): string {\n return `<div class=\"wp-shell\" id=\"wp-shell\">\n ${this.drawer(parts)}\n <main class=\"wp-main\">\n ${this.topbar(parts)}\n <div id=\"graph\"></div>\n ${this.overlays(parts)}\n </main>\n</div>`;\n }\n\n private drawer(parts: ShellParts): string {\n return `<aside class=\"wp-side\" id=\"wp-side\" aria-label=\"Graph controls\">\n <div class=\"wp-brand\">${this.brandIcon()}<span>${parts.title}</span></div>\n <div class=\"wp-group\" role=\"group\" aria-label=\"Color mode\">\n <div class=\"wp-label\">Color by</div>\n ${this.modePulldown()}\n <p class=\"wp-mode-reason\" id=\"wp-impact-reason\" hidden></p>\n ${this.impactKinds('drawer')}\n </div>\n <div class=\"wp-group\">\n <button type=\"button\" class=\"wp-filter-btn\" id=\"wp-filter-open\" aria-controls=\"wp-filter-pop\" aria-expanded=\"false\"><span>Filter</span><span class=\"wp-badge\" id=\"wp-filter-badge\" hidden>0</span></button>\n </div>\n <div class=\"wp-group\">\n <label class=\"wp-label\" for=\"wp-lock\">Focus</label>\n ${parts.lockControl}\n <button type=\"button\" class=\"wp-toggle\" id=\"wp-lock-toggle\" aria-pressed=\"false\">Lock <span class=\"wp-sw\" aria-hidden=\"true\"></span></button>\n <button type=\"button\" class=\"wp-toggle\" id=\"wp-filter-toggle\" aria-pressed=\"false\">Hide unconnected <span class=\"wp-sw\" aria-hidden=\"true\"></span></button>\n </div>\n <div class=\"wp-group\">\n <div class=\"wp-label wp-label-row\">Legend <button type=\"button\" class=\"wp-icon wp-icon-mini wp-icon-dark\" id=\"wp-legend-pop\" title=\"Pop the legend out over the graph\" aria-label=\"Pop the legend out over the graph\">⇱</button></div>\n ${parts.legend}\n </div>\n <div class=\"wp-group\">\n <div class=\"wp-label\">Edges</div>\n ${parts.edgeKey}\n </div>\n <div class=\"wp-foot\">\n <button type=\"button\" class=\"wp-link\" id=\"wp-resp-open\" aria-expanded=\"false\">Responsibilities</button>\n <button type=\"button\" class=\"wp-link\" id=\"wp-snapshot-open\" aria-expanded=\"false\">Saved snapshot</button>\n </div>\n </aside>`;\n }\n\n /**\n * \"Color by\": ONE pill-shaped trigger showing the current mode (name + subtitle) that opens a\n * menu of the four. Keys 1/2/3/4 and the #runtime / #architecture / #impact / #product links still\n * switch.\n */\n private modePulldown(): string {\n const first = GRAPH_MODES[0];\n return `<div class=\"wp-pulldown\">\n <button type=\"button\" class=\"wp-pill-trigger\" id=\"wp-mode-trigger\" aria-haspopup=\"menu\" aria-controls=\"wp-mode-menu\" aria-expanded=\"false\">\n <span class=\"wp-pill-text\"><span class=\"wp-mode-name\" id=\"wp-mode-current\">${first.name}</span><span class=\"wp-mode-sub\" id=\"wp-mode-current-sub\">${first.subtitle}</span></span><span class=\"wp-caret\" aria-hidden=\"true\">▾</span>\n </button>\n <div class=\"wp-menu\" id=\"wp-mode-menu\" role=\"menu\" aria-label=\"Color by\" hidden>${GRAPH_MODES.map((info: ModeInfo, index: number): string => this.modeItem(info, index)).join('')}</div>\n </div>`;\n }\n\n /** One menu option, two lines: the mode's name, then its grey subtitle. */\n private modeItem(info: ModeInfo, index: number): string {\n const checked = info.mode === GraphMode.RUNTIME ? 'true' : 'false';\n return (\n `<button type=\"button\" class=\"wp-mode\" role=\"menuitemradio\" data-wp-mode=\"${info.mode}\" aria-checked=\"${checked}\" title=\"Key ${index + 1}\">` +\n `<span class=\"wp-mode-name\">${info.name}</span><span class=\"wp-mode-sub\">${info.subtitle}</span></button>`\n );\n }\n\n /**\n * The Filter popover. Its four groups INTERSECT: a box is shown when it is in the chosen change\n * scope AND carries any selected runtime chip (when any is selected) AND any selected role chip\n * (when any is selected) AND belongs to any selected product (when any is selected). Nothing\n * applies until \"Show N projects\".\n */\n private filterPopover(parts: ShellParts): string {\n const scopes = SCOPE_OPTIONS.map(\n (option: ScopeOption): string =>\n `<label class=\"wp-scope\"><input type=\"radio\" name=\"wp-scope\" value=\"${option.scope}\"${option.scope === ChangeScope.EVERYTHING ? ' checked' : ''}>` +\n `<span class=\"wp-scope-text\"><span class=\"wp-scope-name\" data-wp-scope-label=\"${option.scope}\">${option.label}</span>` +\n `<span class=\"wp-scope-sub\">${parts.runtime && option.scope === ChangeScope.EVERYTHING ? 'every service' : option.subtitle}</span></span>` +\n `<span class=\"wp-count\" data-wp-scope-count=\"${option.scope}\"></span></label>`,\n ).join('');\n const chips = (group: string, values: readonly string[]): string =>\n values\n .map(\n (value: string): string =>\n `<button type=\"button\" class=\"wp-chip\" data-wp-chip-group=\"${group}\" data-wp-chip=\"${value}\" aria-pressed=\"false\">${value}</button>`,\n )\n .join('');\n return `<div class=\"wp-pop wp-filter-pop\" id=\"wp-filter-pop\" role=\"dialog\" aria-label=\"Filter ${parts.runtime ? 'services' : 'projects'}\" hidden>\n <header><span>Filter</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-filter-close\" aria-label=\"Close the filter\">✕</button></header>\n <fieldset class=\"wp-fgroup\" id=\"wp-scope-group\"><legend id=\"wp-scope-legend\">Changes on this branch</legend>${this.impactKinds('filter')}${scopes}<p class=\"wp-scope-reason\" id=\"wp-scope-reason\" hidden></p></fieldset>\n <fieldset class=\"wp-fgroup\"><legend>Runtime <small>any match</small></legend><div class=\"wp-chips\">${chips('runtime', parts.frameworks)}</div>${parts.runtime && parts.frameworks.length === 0 ? '<p>No declared framework metadata in the saved project graph. Refresh explicitly with architecture:generate.</p>' : ''}</fieldset>\n <fieldset class=\"wp-fgroup\"><legend>Role <small>any match</small></legend><div class=\"wp-chips\">${chips('role', parts.roles)}</div></fieldset>\n ${this.productGroup(parts.products)}\n <footer><button type=\"button\" class=\"wp-text-btn\" id=\"wp-filter-clear\">Clear all</button><button type=\"button\" class=\"wp-primary\" id=\"wp-filter-apply\">Show all ${parts.runtime ? 'services' : 'projects'}</button></footer>\n </div>`;\n }\n\n /**\n * The Product group (#1179): one chip per declared product, each with its palette swatch. Several\n * chips select the UNION of their closures; the group intersects with the others. Omitted when the\n * workspace declares no product.\n */\n private productGroup(products: string[]): string {\n if (products.length === 0) return '';\n const palette = new ProductPalette(products);\n const chips = palette.colors\n .map(\n (color: ProductColor): string =>\n `<button type=\"button\" class=\"wp-chip\" data-wp-chip-group=\"product\" data-wp-chip=\"${color.product}\" aria-pressed=\"false\">` +\n `<svg class=\"wp-chip-swatch\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"5\" r=\"4.5\" fill=\"${color.color}\"/></svg>` +\n `${color.product}</button>`,\n )\n .join('');\n return `<fieldset class=\"wp-fgroup\" id=\"wp-product-group\"><legend>Product <small>any match</small></legend><div class=\"wp-chips\">${chips}</div></fieldset>`;\n }\n\n /**\n * Which Impact scan the page shows (#1163): \"Changed on this branch\" (fork point → working tree)\n * or \"Last commit\" (HEAD^ → working tree). Hidden until the page finds TWO scans in the sidecar;\n * `where` tells the drawer's copy (shown only in Impact mode) from the Filter popover's.\n */\n private impactKinds(where: string): string {\n return (\n `<div class=\"wp-chips wp-impact-kinds\" data-wp-impact-kinds=\"${where}\" role=\"group\" aria-label=\"Compare\" hidden>` +\n '<button type=\"button\" class=\"wp-chip\" data-wp-impact-kind=\"branch\" aria-pressed=\"false\">Changed on this branch</button>' +\n '<button type=\"button\" class=\"wp-chip\" data-wp-impact-kind=\"commit\" aria-pressed=\"false\">Last commit</button></div>'\n );\n }\n\n private topbar(parts: ShellParts): string {\n return `<div class=\"wp-topbar\">\n <button type=\"button\" class=\"wp-icon\" id=\"wp-collapse\" aria-label=\"Toggle the controls drawer\" aria-controls=\"wp-side\" aria-expanded=\"true\">☰</button>\n <div class=\"wp-crumb\" id=\"wp-crumb\">Mode: <b>Runtime</b></div>\n <div class=\"wp-pills\" id=\"wp-filter-pills\" aria-label=\"Active filters\"></div>\n ${parts.filterStatus}\n <button type=\"button\" class=\"wp-icon\" id=\"wp-help-btn\" aria-label=\"How to read this graph\" aria-controls=\"wp-help\" aria-expanded=\"false\">?</button>\n </div>`;\n }\n\n private overlays(parts: ShellParts): string {\n return `${parts.emptyState}<div class=\"wp-pop wp-legend-popout\" id=\"wp-legend-popout\" hidden>\n <header><span id=\"wp-legend-pop-title\">Legend · Runtime</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-legend-close\" aria-label=\"Close the legend\">✕</button></header>\n ${parts.legend}\n </div>\n ${this.help(parts.runtime)}\n ${this.filterPopover(parts)}\n <div class=\"wp-pop wp-snapshot\" id=\"wp-snapshot\" hidden>${parts.snapshot}</div>\n <aside class=\"wp-panel\" id=\"wp-resp-panel\" aria-label=\"Responsibilities\" hidden>\n <header><span>Responsibilities</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-resp-close\" aria-label=\"Close responsibilities\">✕</button></header>\n ${parts.responsibilities}\n </aside>`;\n }\n\n /** The two hint paragraphs that used to sit above the graph, plus the keyboard shortcuts. */\n private help(runtime: boolean): string {\n if (runtime) return `<div class=\"wp-pop wp-help\" id=\"wp-help\" role=\"dialog\" aria-label=\"How to read this graph\" hidden>\n <header><span>Reading the runtime graph</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-help-close\" aria-label=\"Close help\">✕</button></header>\n <p>Rows preserve saved runtime call-depth levels. Boxes are services; horizontal cylinders are queues, upright cylinders datastores, and clocks scheduled triggers. Solid arrows are RPC calls, dashed arrows queued events; red arrows admit legacy cycles.</p>\n <p>Click a box for Lock, Filter Unconnected, or Implements. Implements in the box is a plain summary. Click Uses on a relationship line for its complete details. Hover traces chains without opening details.</p>\n <p>Lock pins separate incoming and outgoing chains; hover adds another chain temporarily. Hide unconnected removes unrelated boxes independently. Clear all clears facets, leaving Lock and chain filtering intact.</p>\n <p>Filters intersect declared runtime, role, product closure and Nx build scope. Counts describe services; attached contextual nodes are counted separately. Impact uses compile-project sets, never runtime reachability. Queues and vendors are context, never source owners or CI projects.</p>\n <p>Runtime uses declared framework colors, Architecture declared roles; contextual nodes keep their kind colors. Product colors include inherited membership. Unknown metadata is neutral. Missing saved metadata requires explicit architecture:generate.</p>\n <p><kbd>/</kbd> Focus search · <kbd>1</kbd>–<kbd>4</kbd> modes · <kbd>Esc</kbd> dismiss or unlock</p>\n </div>`;\n return `<div class=\"wp-pop wp-help\" id=\"wp-help\" role=\"dialog\" aria-label=\"How to read this graph\" hidden>\n <header><span>Reading the graph</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-help-close\" aria-label=\"Close help\">✕</button></header>\n <p>Every row is one dependency level: the HIGHEST level is the top row and L0, the foundation libraries, is always the bottom row. Servers, clients and apps always form the top row (a bundle sits above the apps it aggregates). Transitive dependencies are allowed but not drawn.</p>\n <p>💡 <strong>Click any box</strong> for its menu: <strong>View Design</strong> (only where the project has a generated <strong>design.html</strong>, i.e. what the AI sees inside it), <strong>Lock/Unlock</strong> (the same lock as the Focus field), <strong>Filter Unconnected</strong> (removes unrelated boxes, keeping each L-number row).</p>\n <p><strong>Filter</strong> narrows the graph by what changed on this branch or in the last commit, by runtime, by role and by product; the groups combine. A product keeps its servers, clients and apps plus everything they depend on, so <strong>Product</strong> + <strong>Changed + dependents + dependencies</strong> is exactly what this branch builds that can affect that product. Every box keeps its own L-row, and a level left empty shows as a thin labeled band.</p>\n <p>🔦 <strong>Hover any box</strong> to trace its <em>entire</em> dependency chain — every ancestor above it and every dependency below it, with the lines between — while the rest of the graph dims.</p>\n <p><strong>Runtime</strong>: a box may depend on a box carrying every color it has. <strong>Architecture</strong>: what each box is in the system. <strong>Impact</strong>: what changed on this branch or in the last commit (amber) and its dependents (amber outline); with both available, a toggle picks which. <strong>Product</strong>: which products each box belongs to — one color per product, stripes for several, grey for every product, a dashed white box for none.</p>\n <p><kbd>/</kbd> focus the lock search · <kbd>1</kbd> <kbd>2</kbd> <kbd>3</kbd> <kbd>4</kbd> switch mode · <kbd>Esc</kbd> unlock</p>\n </div>`;\n }\n\n private brandIcon(): string {\n return (\n '<svg width=\"22\" height=\"22\" viewBox=\"0 0 22 22\" aria-hidden=\"true\">' +\n '<rect x=\"1\" y=\"1\" width=\"8\" height=\"6\" rx=\"1.5\" fill=\"none\" stroke=\"#a46bf7\" stroke-width=\"1.6\"/>' +\n '<rect x=\"13\" y=\"1\" width=\"8\" height=\"6\" rx=\"1.5\" fill=\"none\" stroke=\"#e9eaf0\" stroke-width=\"1.6\"/>' +\n '<rect x=\"7\" y=\"15\" width=\"8\" height=\"6\" rx=\"1.5\" fill=\"none\" stroke=\"#e9eaf0\" stroke-width=\"1.6\"/>' +\n '<path d=\"M5 7v4h12V7M11 11v4\" fill=\"none\" stroke=\"#e9eaf0\" stroke-width=\"1.6\"/></svg>'\n );\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"graph-page-shell.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-page-shell.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The architecture page's shell (#1155): a collapsible dark drawer on the left holding every control,\n * and the graph filling the rest of the viewport.\n *\n * Before this, two hint paragraphs, a ~650px three-column legend, a lock dropdown and a zoom bar all\n * sat ABOVE the graph, so a laptop's first screen showed no graph at all. Now the hints are a \"?\"\n * popover, the legend lives in the drawer (and can pop out over the graph), zoom floats bottom-right\n * (graph-navigation.ts) and the responsibilities open in a right-hand panel.\n *\n * Markup only. Behaviour is graph-visualizer.client.ts; styling is graph-page-styles.ts. No element\n * carries a `style=` attribute — every look is a class.\n */\n\nimport { FRAMEWORK_ORDER, GRAPH_MODES, GraphMode, ModeInfo, ROLE_STYLES, RoleStyle } from './graph-color-modes';\nimport { ProductColor, ProductPalette } from './graph-products';\n\n/**\n * The Filter popover's \"Changes on this branch\" choices. The page computes each set from the Impact\n * sidecar (graph-impact.ts): CHANGED = touched; DEPENDENTS = touched + affected (nx affected: their\n * tests re-run); DEPENDENCIES = touched + every transitive dependency of it; BUILD = touched +\n * affected + every build input — the dependencies of the DEPENDENTS too, which is why it is more than\n * the union of the two rows above it.\n *\n * \"Dependents\" are what USE a project and \"dependencies\" what it uses (#1179). \"Dependee\" would mean\n * the thing depended ON — the opposite of the dependents row — so it is never used.\n */\nexport enum ChangeScope {\n EVERYTHING = 'everything',\n CHANGED = 'changed',\n DEPENDENTS = 'dependents',\n DEPENDENCIES = 'dependencies',\n BUILD = 'build',\n}\n\n/**\n * One change scope: its name and the grey second line under it, like the Color-by menu. These are the\n * ONE copy of the labels — the page script reads each name back from its `data-wp-scope-label` (the\n * top-bar pill), so the two cannot drift apart.\n */\nexport class ScopeOption {\n constructor(\n public readonly scope: ChangeScope,\n public readonly label: string,\n public readonly subtitle: string,\n ) {}\n}\n\nexport const SCOPE_OPTIONS: readonly ScopeOption[] = [\n new ScopeOption(ChangeScope.EVERYTHING, 'All', 'every project'),\n new ScopeOption(ChangeScope.CHANGED, 'Changed', 'owns a changed file'),\n new ScopeOption(ChangeScope.DEPENDENTS, 'Changed + dependents', '+ everything that uses them (their tests re-run)'),\n new ScopeOption(ChangeScope.DEPENDENCIES, 'Changed + dependencies', '+ everything they use'),\n new ScopeOption(ChangeScope.BUILD, 'Changed + dependents + dependencies', 'everything CI builds for this branch'),\n];\n\n/** Everything the shell frames, already rendered by its owner. */\nexport class ShellParts {\n constructor(\n public readonly title: string,\n public readonly lockControl: string,\n public readonly legend: string,\n public readonly edgeKey: string,\n public readonly filterStatus: string,\n public readonly snapshot: string,\n public readonly responsibilities: string,\n /** Every declared product, sorted (#1179); empty hides the Filter's Product group. */\n public readonly products: string[],\n public readonly runtime: boolean = false,\n public readonly frameworks: readonly string[] = FRAMEWORK_ORDER,\n public readonly roles: readonly string[] = ROLE_STYLES.map((style: RoleStyle): string => style.role),\n public readonly emptyState: string = '',\n ) {}\n}\n\nexport class GraphPageShell {\n body(parts: ShellParts): string {\n return `<div class=\"wp-shell\" id=\"wp-shell\">\n ${this.drawer(parts)}\n <main class=\"wp-main\">\n ${this.topbar(parts)}\n <div id=\"graph\"></div>\n ${this.overlays(parts)}\n </main>\n</div>`;\n }\n\n private drawer(parts: ShellParts): string {\n return `<aside class=\"wp-side\" id=\"wp-side\" aria-label=\"Graph controls\">\n <div class=\"wp-brand\">${this.brandIcon()}<span>${parts.title}</span></div>\n <div class=\"wp-group\" role=\"group\" aria-label=\"Color mode\">\n <div class=\"wp-label\">Color by</div>\n ${this.modePulldown()}\n <p class=\"wp-mode-reason\" id=\"wp-impact-reason\" hidden></p>\n ${this.impactKinds('drawer')}\n </div>\n <div class=\"wp-group\">\n <button type=\"button\" class=\"wp-filter-btn\" id=\"wp-filter-open\" aria-controls=\"wp-filter-pop\" aria-expanded=\"false\"><span>Filter</span><span class=\"wp-badge\" id=\"wp-filter-badge\" hidden>0</span></button>\n </div>\n <div class=\"wp-group\">\n <label class=\"wp-label\" for=\"wp-lock\">Focus</label>\n ${parts.lockControl}\n <button type=\"button\" class=\"wp-toggle\" id=\"wp-lock-toggle\" aria-pressed=\"false\">Lock <span class=\"wp-sw\" aria-hidden=\"true\"></span></button>\n <button type=\"button\" class=\"wp-toggle\" id=\"wp-filter-toggle\" aria-pressed=\"false\">Hide unconnected <span class=\"wp-sw\" aria-hidden=\"true\"></span></button>\n </div>\n <div class=\"wp-group\">\n <div class=\"wp-label wp-label-row\">Legend <button type=\"button\" class=\"wp-icon wp-icon-mini wp-icon-dark\" id=\"wp-legend-pop\" title=\"Pop the legend out over the graph\" aria-label=\"Pop the legend out over the graph\">⇱</button></div>\n ${parts.legend}\n </div>\n <div class=\"wp-group\">\n <div class=\"wp-label\">Edges</div>\n ${parts.edgeKey}\n </div>\n <div class=\"wp-foot\">\n <button type=\"button\" class=\"wp-link\" id=\"wp-resp-open\" aria-expanded=\"false\">Responsibilities</button>\n <button type=\"button\" class=\"wp-link\" id=\"wp-snapshot-open\" aria-expanded=\"false\">Saved snapshot</button>\n </div>\n </aside>`;\n }\n\n /**\n * \"Color by\": ONE pill-shaped trigger showing the current mode (name + subtitle) that opens a\n * menu of the four. Keys 1/2/3/4 and the #runtime / #architecture / #impact / #product links still\n * switch.\n */\n private modePulldown(): string {\n const first = GRAPH_MODES[0];\n return `<div class=\"wp-pulldown\">\n <button type=\"button\" class=\"wp-pill-trigger\" id=\"wp-mode-trigger\" aria-haspopup=\"menu\" aria-controls=\"wp-mode-menu\" aria-expanded=\"false\">\n <span class=\"wp-pill-text\"><span class=\"wp-mode-name\" id=\"wp-mode-current\">${first.name}</span><span class=\"wp-mode-sub\" id=\"wp-mode-current-sub\">${first.subtitle}</span></span><span class=\"wp-caret\" aria-hidden=\"true\">▾</span>\n </button>\n <div class=\"wp-menu\" id=\"wp-mode-menu\" role=\"menu\" aria-label=\"Color by\" hidden>${GRAPH_MODES.map((info: ModeInfo, index: number): string => this.modeItem(info, index)).join('')}</div>\n </div>`;\n }\n\n /** One menu option, two lines: the mode's name, then its grey subtitle. */\n private modeItem(info: ModeInfo, index: number): string {\n const checked = info.mode === GraphMode.RUNTIME ? 'true' : 'false';\n return (\n `<button type=\"button\" class=\"wp-mode\" role=\"menuitemradio\" data-wp-mode=\"${info.mode}\" aria-checked=\"${checked}\" title=\"Key ${index + 1}\">` +\n `<span class=\"wp-mode-name\">${info.name}</span><span class=\"wp-mode-sub\">${info.subtitle}</span></button>`\n );\n }\n\n /**\n * The Filter popover. Its four groups INTERSECT: a box is shown when it is in the chosen change\n * scope AND carries any selected runtime chip (when any is selected) AND any selected role chip\n * (when any is selected) AND belongs to any selected product (when any is selected). Nothing\n * applies until \"Show N projects\".\n */\n private filterPopover(parts: ShellParts): string {\n const scopes = SCOPE_OPTIONS.map(\n (option: ScopeOption): string =>\n `<label class=\"wp-scope\"><input type=\"radio\" name=\"wp-scope\" value=\"${option.scope}\"${option.scope === ChangeScope.EVERYTHING ? ' checked' : ''}>` +\n `<span class=\"wp-scope-text\"><span class=\"wp-scope-name\" data-wp-scope-label=\"${option.scope}\">${option.label}</span>` +\n `<span class=\"wp-scope-sub\">${parts.runtime && option.scope === ChangeScope.EVERYTHING ? 'every service' : option.subtitle}</span></span>` +\n `<span class=\"wp-count\" data-wp-scope-count=\"${option.scope}\"></span></label>`,\n ).join('');\n const chips = (group: string, values: readonly string[]): string =>\n values\n .map(\n (value: string): string =>\n `<button type=\"button\" class=\"wp-chip\" data-wp-chip-group=\"${group}\" data-wp-chip=\"${value}\" aria-pressed=\"false\">${value}</button>`,\n )\n .join('');\n return `<div class=\"wp-pop wp-filter-pop\" id=\"wp-filter-pop\" role=\"dialog\" aria-label=\"Filter ${parts.runtime ? 'services' : 'projects'}\" hidden>\n <header><span>Filter</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-filter-close\" aria-label=\"Close the filter\">✕</button></header>\n <fieldset class=\"wp-fgroup\" id=\"wp-scope-group\"><legend id=\"wp-scope-legend\">Changes on this branch</legend>${this.impactKinds('filter')}${scopes}<p class=\"wp-scope-reason\" id=\"wp-scope-reason\" hidden></p></fieldset>\n <fieldset class=\"wp-fgroup\"><legend>Runtime <small>any match</small></legend><div class=\"wp-chips\">${chips('runtime', parts.frameworks)}</div>${parts.runtime && parts.frameworks.length === 0 ? '<p>No declared framework metadata in the saved project graph. Refresh explicitly with architecture:generate.</p>' : ''}</fieldset>\n <fieldset class=\"wp-fgroup\"><legend>Role <small>any match</small></legend><div class=\"wp-chips\">${chips('role', parts.roles)}</div></fieldset>\n ${this.productGroup(parts.products)}\n <footer><button type=\"button\" class=\"wp-text-btn\" id=\"wp-filter-clear\">Clear all</button><button type=\"button\" class=\"wp-primary\" id=\"wp-filter-apply\">Show all ${parts.runtime ? 'services' : 'projects'}</button></footer>\n </div>`;\n }\n\n /**\n * The Product group (#1179): one chip per declared product, each with its palette swatch. Several\n * chips select the UNION of their members; the group intersects with the others. Omitted when the\n * workspace declares no product.\n */\n private productGroup(products: string[]): string {\n if (products.length === 0) return '';\n const palette = new ProductPalette(products);\n const chips = palette.colors\n .map(\n (color: ProductColor): string =>\n `<button type=\"button\" class=\"wp-chip\" data-wp-chip-group=\"product\" data-wp-chip=\"${color.product}\" aria-pressed=\"false\">` +\n `<svg class=\"wp-chip-swatch\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"5\" r=\"4.5\" fill=\"${color.color}\"/></svg>` +\n `${color.product}</button>`,\n )\n .join('');\n return `<fieldset class=\"wp-fgroup\" id=\"wp-product-group\"><legend>Product <small>any match</small></legend><div class=\"wp-chips\">${chips}</div></fieldset>`;\n }\n\n /**\n * Which Impact scan the page shows (#1163): \"Changed on this branch\" (fork point → working tree)\n * or \"Last commit\" (HEAD^ → working tree). Hidden until the page finds TWO scans in the sidecar;\n * `where` tells the drawer's copy (shown only in Impact mode) from the Filter popover's.\n */\n private impactKinds(where: string): string {\n return (\n `<div class=\"wp-chips wp-impact-kinds\" data-wp-impact-kinds=\"${where}\" role=\"group\" aria-label=\"Compare\" hidden>` +\n '<button type=\"button\" class=\"wp-chip\" data-wp-impact-kind=\"branch\" aria-pressed=\"false\">Changed on this branch</button>' +\n '<button type=\"button\" class=\"wp-chip\" data-wp-impact-kind=\"commit\" aria-pressed=\"false\">Last commit</button></div>'\n );\n }\n\n private topbar(parts: ShellParts): string {\n return `<div class=\"wp-topbar\">\n <button type=\"button\" class=\"wp-icon\" id=\"wp-collapse\" aria-label=\"Toggle the controls drawer\" aria-controls=\"wp-side\" aria-expanded=\"true\">☰</button>\n <div class=\"wp-crumb\" id=\"wp-crumb\">Mode: <b>Runtime</b></div>\n <div class=\"wp-pills\" id=\"wp-filter-pills\" aria-label=\"Active filters\"></div>\n ${parts.filterStatus}\n <button type=\"button\" class=\"wp-icon\" id=\"wp-help-btn\" aria-label=\"How to read this graph\" aria-controls=\"wp-help\" aria-expanded=\"false\">?</button>\n </div>`;\n }\n\n private overlays(parts: ShellParts): string {\n return `${parts.emptyState}<div class=\"wp-pop wp-legend-popout\" id=\"wp-legend-popout\" hidden>\n <header><span id=\"wp-legend-pop-title\">Legend · Runtime</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-legend-close\" aria-label=\"Close the legend\">✕</button></header>\n ${parts.legend}\n </div>\n ${this.help(parts.runtime)}\n ${this.filterPopover(parts)}\n <div class=\"wp-pop wp-snapshot\" id=\"wp-snapshot\" hidden>${parts.snapshot}</div>\n <aside class=\"wp-panel\" id=\"wp-resp-panel\" aria-label=\"Responsibilities\" hidden>\n <header><span>Responsibilities</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-resp-close\" aria-label=\"Close responsibilities\">✕</button></header>\n ${parts.responsibilities}\n </aside>`;\n }\n\n /** The two hint paragraphs that used to sit above the graph, plus the keyboard shortcuts. */\n private help(runtime: boolean): string {\n if (runtime) return `<div class=\"wp-pop wp-help\" id=\"wp-help\" role=\"dialog\" aria-label=\"How to read this graph\" hidden>\n <header><span>Reading the runtime graph</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-help-close\" aria-label=\"Close help\">✕</button></header>\n <p>Rows preserve saved runtime call-depth levels. Boxes are services; horizontal cylinders are queues, upright cylinders datastores, and clocks scheduled triggers. Solid arrows are RPC calls, dashed arrows queued events; red arrows admit legacy cycles.</p>\n <p>Click a box for Lock, Filter Unconnected, or Implements. Implements in the box is a plain summary. Click Uses on a relationship line for its complete details. Hover traces chains without opening details.</p>\n <p>Lock pins separate incoming and outgoing chains; hover adds another chain temporarily. Hide unconnected removes unrelated boxes independently. Clear all clears facets, leaving Lock and chain filtering intact.</p>\n <p>Filters intersect declared runtime, role, product closure and Nx build scope. Counts describe services; attached contextual nodes are counted separately. Impact uses compile-project sets, never runtime reachability. Queues and vendors are context, never source owners or CI projects.</p>\n <p>Runtime uses declared framework colors, Architecture declared roles; contextual nodes keep their kind colors. Product colors come from each project's declared product: tags; contextual nodes inherit adjacent memberships. Unknown metadata is neutral. Missing saved metadata requires explicit architecture:generate.</p>\n <p><kbd>/</kbd> Focus search · <kbd>1</kbd>–<kbd>4</kbd> modes · <kbd>Esc</kbd> dismiss or unlock</p>\n </div>`;\n return `<div class=\"wp-pop wp-help\" id=\"wp-help\" role=\"dialog\" aria-label=\"How to read this graph\" hidden>\n <header><span>Reading the graph</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-help-close\" aria-label=\"Close help\">✕</button></header>\n <p>Every row is one dependency level: the HIGHEST level is the top row and L0, the foundation libraries, is always the bottom row. Servers, clients and apps always form the top row (a bundle sits above the apps it aggregates). Transitive dependencies are allowed but not drawn.</p>\n <p>💡 <strong>Click any box</strong> for its menu: <strong>View Design</strong> (only where the project has a generated <strong>design.html</strong>, i.e. what the AI sees inside it), <strong>Lock/Unlock</strong> (the same lock as the Focus field), <strong>Filter Unconnected</strong> (removes unrelated boxes, keeping each L-number row).</p>\n <p><strong>Filter</strong> narrows the graph by what changed on this branch or in the last commit, by runtime, by role and by product; the groups combine. A product keeps its servers, clients and apps plus everything they depend on, so <strong>Product</strong> + <strong>Changed + dependents + dependencies</strong> is exactly what this branch builds that can affect that product. Every box keeps its own L-row, and a level left empty shows as a thin labeled band.</p>\n <p>🔦 <strong>Hover any box</strong> to trace its <em>entire</em> dependency chain — every ancestor above it and every dependency below it, with the lines between — while the rest of the graph dims.</p>\n <p><strong>Runtime</strong>: a box may depend on a box carrying every color it has. <strong>Architecture</strong>: what each box is in the system. <strong>Impact</strong>: what changed on this branch or in the last commit (amber) and its dependents (amber outline); with both available, a toggle picks which. <strong>Product</strong>: which products each box belongs to — one color per product, stripes for several, grey for every product, a dashed white box for none.</p>\n <p><kbd>/</kbd> focus the lock search · <kbd>1</kbd> <kbd>2</kbd> <kbd>3</kbd> <kbd>4</kbd> switch mode · <kbd>Esc</kbd> unlock</p>\n </div>`;\n }\n\n private brandIcon(): string {\n return (\n '<svg width=\"22\" height=\"22\" viewBox=\"0 0 22 22\" aria-hidden=\"true\">' +\n '<rect x=\"1\" y=\"1\" width=\"8\" height=\"6\" rx=\"1.5\" fill=\"none\" stroke=\"#a46bf7\" stroke-width=\"1.6\"/>' +\n '<rect x=\"13\" y=\"1\" width=\"8\" height=\"6\" rx=\"1.5\" fill=\"none\" stroke=\"#e9eaf0\" stroke-width=\"1.6\"/>' +\n '<rect x=\"7\" y=\"15\" width=\"8\" height=\"6\" rx=\"1.5\" fill=\"none\" stroke=\"#e9eaf0\" stroke-width=\"1.6\"/>' +\n '<path d=\"M5 7v4h12V7M11 11v4\" fill=\"none\" stroke=\"#e9eaf0\" stroke-width=\"1.6\"/></svg>'\n );\n }\n}\n"]}
|
|
@@ -1,34 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Products on the architecture graphs (#1179).
|
|
3
3
|
*
|
|
4
|
-
* A monorepo can hold several PRODUCTS, each with its own clients and servers. A
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* A monorepo can hold several PRODUCTS, each with its own clients and servers. A project declares the
|
|
5
|
+
* products it belongs to with the nx tag `product:<name>` (product-resolver.ts). Membership is TAGS
|
|
6
|
+
* ONLY (#1216) on both graphs — nothing is inherited over an edge:
|
|
7
7
|
*
|
|
8
|
-
* - ProductMembership — the dependency graph. A project's
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* repos carries its own tag for exactly that reason: no in-repo walk can ever reach it correctly.
|
|
19
|
-
* - RuntimeProductMembership — the runtime graph, under the same rule. A service that already carries
|
|
20
|
-
* products (declared, or derived on the dependency graph above) keeps exactly them; a service with
|
|
21
|
-
* none belongs to every product whose service reaches it over RUNTIME `dependsOn`, including through
|
|
22
|
-
* a queue to its consumers, and that walk stops at a service with products of its own. A call from
|
|
23
|
-
* one product into another product's service still shows — as the runtime EDGE between them.
|
|
8
|
+
* - ProductMembership — the dependency graph. A project's products are exactly its own declared
|
|
9
|
+
* `product:` tags. An UNTAGGED project is in no product, however many tagged projects depend on it,
|
|
10
|
+
* and renders as "no product" — a box that still needs a tag. A consumer that wants every project
|
|
11
|
+
* assigned lists the library roles in `product-tags.roles`, so an untagged library fails generation
|
|
12
|
+
* instead of quietly sitting outside every product (and outside the product dependency matrix,
|
|
13
|
+
* which skips any edge with an untagged end — product-dependencies.ts).
|
|
14
|
+
* - RuntimeProductMembership — the runtime graph. A service's products are exactly its project's
|
|
15
|
+
* declared `product:` tags. A service is never put in a product because another service calls it or
|
|
16
|
+
* feeds its queue; a call from one product into another product's service shows as the runtime EDGE
|
|
17
|
+
* between them. A service whose project carries no tag is in no product.
|
|
24
18
|
* - ProductPalette — the Product color mode's colors: a fixed palette of eight assigned in sorted
|
|
25
19
|
* product-name order, a neutral fill for a box shared by EVERY product, and a dashed white box for
|
|
26
20
|
* one in no product.
|
|
27
21
|
* - ProductBoundary — which dependency edges cross from one product into another (#1204), drawn
|
|
28
22
|
* dashed in the target product's color in the Product color mode.
|
|
29
23
|
*
|
|
30
|
-
* Membership is stable per checkout (it depends on tags
|
|
31
|
-
*
|
|
24
|
+
* Membership is stable per checkout (it depends on tags only, never on a diff), so it is written into
|
|
25
|
+
* the committed dependencies.json / runtime-dependencies.json rather than into a sidecar.
|
|
32
26
|
*/
|
|
33
27
|
import type { EnhancedGraph } from './graph-sorter';
|
|
34
28
|
import type { RuntimeGraph } from './runtime-graph-model';
|
|
@@ -76,38 +70,26 @@ export declare class ProductBoundary {
|
|
|
76
70
|
crossedInto(from: readonly string[], to: readonly string[]): string | null;
|
|
77
71
|
}
|
|
78
72
|
/**
|
|
79
|
-
* The architecture graph's product membership:
|
|
80
|
-
* project's own declared products are final.
|
|
73
|
+
* The architecture graph's product membership: exactly each project's declared `product:` tags (#1216).
|
|
81
74
|
*/
|
|
82
75
|
export declare class ProductMembership {
|
|
83
76
|
/**
|
|
84
77
|
* @param seeds project → the products it DECLARES (its `product:` tags)
|
|
85
|
-
* @returns project → its sorted products
|
|
86
|
-
*
|
|
87
|
-
*
|
|
78
|
+
* @returns project → its sorted declared products. An untagged project is absent — it is in no
|
|
79
|
+
* product, however many tagged projects depend on it — so with no tag anywhere the map is
|
|
80
|
+
* empty.
|
|
88
81
|
*/
|
|
89
82
|
compute(seeds: ReadonlyMap<string, readonly string[]>, graph: EnhancedGraph): Map<string, string[]>;
|
|
90
|
-
/**
|
|
91
|
-
* Every UNTAGGED project `seed` reaches through `dependsOn`. A tagged project is neither collected
|
|
92
|
-
* nor walked through: its products are final, and its own seed walk covers its dependencies.
|
|
93
|
-
*/
|
|
94
|
-
private closure;
|
|
95
83
|
}
|
|
96
84
|
/**
|
|
97
|
-
* The runtime graph's product membership:
|
|
98
|
-
*
|
|
85
|
+
* The runtime graph's product membership: exactly each service's project's declared `product:` tags
|
|
86
|
+
* (#1216). Never derived from who calls a service or feeds its queue.
|
|
99
87
|
*/
|
|
100
88
|
export declare class RuntimeProductMembership {
|
|
101
89
|
/**
|
|
102
|
-
* Stamp `products` onto every service of `graph`
|
|
103
|
-
*
|
|
104
|
-
* service
|
|
105
|
-
* seed. A `queue:<Api.method>` dependency is followed to that queue's consumers. A service in no
|
|
106
|
-
* product gets no field.
|
|
90
|
+
* Stamp `products` onto every service of `graph` whose project declares any. `projects` is the
|
|
91
|
+
* dependency graph, whose `products` are exactly each project's declared tags (ProductMembership);
|
|
92
|
+
* a service whose project declares none gets no field.
|
|
107
93
|
*/
|
|
108
94
|
apply(graph: RuntimeGraph, projects: EnhancedGraph): void;
|
|
109
|
-
/** Every service WITHOUT products of its own that `seed` reaches; a seed is neither collected nor walked through. */
|
|
110
|
-
private closure;
|
|
111
|
-
/** The services one service calls: direct ones, and the consumers behind every queue it feeds. */
|
|
112
|
-
private callees;
|
|
113
95
|
}
|
|
@@ -2,34 +2,28 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Products on the architecture graphs (#1179).
|
|
4
4
|
*
|
|
5
|
-
* A monorepo can hold several PRODUCTS, each with its own clients and servers. A
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* A monorepo can hold several PRODUCTS, each with its own clients and servers. A project declares the
|
|
6
|
+
* products it belongs to with the nx tag `product:<name>` (product-resolver.ts). Membership is TAGS
|
|
7
|
+
* ONLY (#1216) on both graphs — nothing is inherited over an edge:
|
|
8
8
|
*
|
|
9
|
-
* - ProductMembership — the dependency graph. A project's
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* repos carries its own tag for exactly that reason: no in-repo walk can ever reach it correctly.
|
|
20
|
-
* - RuntimeProductMembership — the runtime graph, under the same rule. A service that already carries
|
|
21
|
-
* products (declared, or derived on the dependency graph above) keeps exactly them; a service with
|
|
22
|
-
* none belongs to every product whose service reaches it over RUNTIME `dependsOn`, including through
|
|
23
|
-
* a queue to its consumers, and that walk stops at a service with products of its own. A call from
|
|
24
|
-
* one product into another product's service still shows — as the runtime EDGE between them.
|
|
9
|
+
* - ProductMembership — the dependency graph. A project's products are exactly its own declared
|
|
10
|
+
* `product:` tags. An UNTAGGED project is in no product, however many tagged projects depend on it,
|
|
11
|
+
* and renders as "no product" — a box that still needs a tag. A consumer that wants every project
|
|
12
|
+
* assigned lists the library roles in `product-tags.roles`, so an untagged library fails generation
|
|
13
|
+
* instead of quietly sitting outside every product (and outside the product dependency matrix,
|
|
14
|
+
* which skips any edge with an untagged end — product-dependencies.ts).
|
|
15
|
+
* - RuntimeProductMembership — the runtime graph. A service's products are exactly its project's
|
|
16
|
+
* declared `product:` tags. A service is never put in a product because another service calls it or
|
|
17
|
+
* feeds its queue; a call from one product into another product's service shows as the runtime EDGE
|
|
18
|
+
* between them. A service whose project carries no tag is in no product.
|
|
25
19
|
* - ProductPalette — the Product color mode's colors: a fixed palette of eight assigned in sorted
|
|
26
20
|
* product-name order, a neutral fill for a box shared by EVERY product, and a dashed white box for
|
|
27
21
|
* one in no product.
|
|
28
22
|
* - ProductBoundary — which dependency edges cross from one product into another (#1204), drawn
|
|
29
23
|
* dashed in the target product's color in the Product color mode.
|
|
30
24
|
*
|
|
31
|
-
* Membership is stable per checkout (it depends on tags
|
|
32
|
-
*
|
|
25
|
+
* Membership is stable per checkout (it depends on tags only, never on a diff), so it is written into
|
|
26
|
+
* the committed dependencies.json / runtime-dependencies.json rather than into a sidecar.
|
|
33
27
|
*/
|
|
34
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
29
|
exports.RuntimeProductMembership = exports.ProductMembership = exports.ProductBoundary = exports.ProductPalette = exports.PRODUCT_NONE_BORDER = exports.PRODUCT_NONE_FILL = exports.PRODUCT_SHARED_FILL = exports.PRODUCT_PALETTE_SIZE = exports.ProductColor = void 0;
|
|
@@ -129,131 +123,41 @@ class ProductBoundary {
|
|
|
129
123
|
}
|
|
130
124
|
exports.ProductBoundary = ProductBoundary;
|
|
131
125
|
/**
|
|
132
|
-
* The architecture graph's product membership:
|
|
133
|
-
* project's own declared products are final.
|
|
126
|
+
* The architecture graph's product membership: exactly each project's declared `product:` tags (#1216).
|
|
134
127
|
*/
|
|
135
128
|
class ProductMembership {
|
|
136
129
|
/**
|
|
137
130
|
* @param seeds project → the products it DECLARES (its `product:` tags)
|
|
138
|
-
* @returns project → its sorted products
|
|
139
|
-
*
|
|
140
|
-
*
|
|
131
|
+
* @returns project → its sorted declared products. An untagged project is absent — it is in no
|
|
132
|
+
* product, however many tagged projects depend on it — so with no tag anywhere the map is
|
|
133
|
+
* empty.
|
|
141
134
|
*/
|
|
142
135
|
compute(seeds, graph) {
|
|
143
|
-
const declared = new Map();
|
|
144
|
-
seeds.forEach((products, seed) => {
|
|
145
|
-
if (products.length > 0 && graph[seed] !== undefined)
|
|
146
|
-
declared.set(seed, products);
|
|
147
|
-
});
|
|
148
|
-
const members = new Map();
|
|
149
|
-
declared.forEach((products, seed) => {
|
|
150
|
-
members.set(seed, new Set(products));
|
|
151
|
-
});
|
|
152
|
-
declared.forEach((products, seed) => {
|
|
153
|
-
for (const project of this.closure(seed, graph, declared)) {
|
|
154
|
-
const set = members.get(project) ?? new Set();
|
|
155
|
-
for (const product of products)
|
|
156
|
-
set.add(product);
|
|
157
|
-
members.set(project, set);
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
136
|
const result = new Map();
|
|
161
|
-
|
|
162
|
-
|
|
137
|
+
seeds.forEach((products, project) => {
|
|
138
|
+
if (products.length > 0 && graph[project] !== undefined)
|
|
139
|
+
result.set(project, [...new Set(products)].sort());
|
|
163
140
|
});
|
|
164
141
|
return result;
|
|
165
142
|
}
|
|
166
|
-
/**
|
|
167
|
-
* Every UNTAGGED project `seed` reaches through `dependsOn`. A tagged project is neither collected
|
|
168
|
-
* nor walked through: its products are final, and its own seed walk covers its dependencies.
|
|
169
|
-
*/
|
|
170
|
-
closure(seed, graph, declared) {
|
|
171
|
-
const seen = new Set([seed]);
|
|
172
|
-
const reached = new Set();
|
|
173
|
-
const stack = [seed];
|
|
174
|
-
while (stack.length > 0) {
|
|
175
|
-
const project = stack.pop();
|
|
176
|
-
for (const dep of graph[project]?.dependsOn ?? []) {
|
|
177
|
-
if (seen.has(dep))
|
|
178
|
-
continue;
|
|
179
|
-
seen.add(dep);
|
|
180
|
-
if (declared.has(dep))
|
|
181
|
-
continue;
|
|
182
|
-
reached.add(dep);
|
|
183
|
-
stack.push(dep);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
return reached;
|
|
187
|
-
}
|
|
188
143
|
}
|
|
189
144
|
exports.ProductMembership = ProductMembership;
|
|
190
145
|
/**
|
|
191
|
-
* The runtime graph's product membership:
|
|
192
|
-
*
|
|
146
|
+
* The runtime graph's product membership: exactly each service's project's declared `product:` tags
|
|
147
|
+
* (#1216). Never derived from who calls a service or feeds its queue.
|
|
193
148
|
*/
|
|
194
149
|
class RuntimeProductMembership {
|
|
195
150
|
/**
|
|
196
|
-
* Stamp `products` onto every service of `graph`
|
|
197
|
-
*
|
|
198
|
-
* service
|
|
199
|
-
* seed. A `queue:<Api.method>` dependency is followed to that queue's consumers. A service in no
|
|
200
|
-
* product gets no field.
|
|
151
|
+
* Stamp `products` onto every service of `graph` whose project declares any. `projects` is the
|
|
152
|
+
* dependency graph, whose `products` are exactly each project's declared tags (ProductMembership);
|
|
153
|
+
* a service whose project declares none gets no field.
|
|
201
154
|
*/
|
|
202
155
|
apply(graph, projects) {
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
seeds.set(name, own);
|
|
208
|
-
}
|
|
209
|
-
const members = new Map();
|
|
210
|
-
seeds.forEach((own, name) => {
|
|
211
|
-
members.set(name, new Set(own));
|
|
212
|
-
});
|
|
213
|
-
seeds.forEach((own, name) => {
|
|
214
|
-
for (const reached of this.closure(name, graph, seeds)) {
|
|
215
|
-
const set = members.get(reached) ?? new Set();
|
|
216
|
-
for (const product of own)
|
|
217
|
-
set.add(product);
|
|
218
|
-
members.set(reached, set);
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
members.forEach((set, name) => {
|
|
222
|
-
const service = graph.services[name];
|
|
223
|
-
if (service !== undefined)
|
|
224
|
-
service.products = [...set].sort();
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
/** Every service WITHOUT products of its own that `seed` reaches; a seed is neither collected nor walked through. */
|
|
228
|
-
closure(seed, graph, seeds) {
|
|
229
|
-
const seen = new Set([seed]);
|
|
230
|
-
const reached = new Set();
|
|
231
|
-
const stack = [seed];
|
|
232
|
-
while (stack.length > 0) {
|
|
233
|
-
const name = stack.pop();
|
|
234
|
-
for (const next of this.callees(name, graph)) {
|
|
235
|
-
if (seen.has(next) || graph.services[next] === undefined)
|
|
236
|
-
continue;
|
|
237
|
-
seen.add(next);
|
|
238
|
-
if (seeds.has(next))
|
|
239
|
-
continue;
|
|
240
|
-
reached.add(next);
|
|
241
|
-
stack.push(next);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
return reached;
|
|
245
|
-
}
|
|
246
|
-
/** The services one service calls: direct ones, and the consumers behind every queue it feeds. */
|
|
247
|
-
callees(name, graph) {
|
|
248
|
-
const callees = [];
|
|
249
|
-
for (const dep of graph.services[name]?.dependsOn ?? []) {
|
|
250
|
-
if (!dep.startsWith('queue:')) {
|
|
251
|
-
callees.push(dep);
|
|
252
|
-
continue;
|
|
253
|
-
}
|
|
254
|
-
callees.push(...(graph.queues[dep.slice('queue:'.length)]?.consumedBy ?? []));
|
|
156
|
+
for (const [name, service] of Object.entries(graph.services)) {
|
|
157
|
+
const declared = projects[name]?.products ?? [];
|
|
158
|
+
if (declared.length > 0)
|
|
159
|
+
service.products = [...declared].sort();
|
|
255
160
|
}
|
|
256
|
-
return callees;
|
|
257
161
|
}
|
|
258
162
|
}
|
|
259
163
|
exports.RuntimeProductMembership = RuntimeProductMembership;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-products.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-products.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Products on the architecture graphs (#1179).\n *\n * A monorepo can hold several PRODUCTS, each with its own clients and servers. A product is declared\n * on its entry points with the nx tag `product:<name>` (product-resolver.ts); everything else a\n * product consists of is DERIVED here, never declared:\n *\n * - ProductMembership — the dependency graph. A project's OWN `product:` tags are FINAL (#1201): a\n * project that declares any keeps exactly its declared set. An UNTAGGED project belongs to every\n * product whose tagged project reaches it by a downward walk over `dependsOn` — and that walk stops\n * at a tagged project, whose own dependencies are reached by its own seed walk. That closure is\n * everything that can change the product's behaviour, so it is what a reviewer of that product\n * reviews. It must start from EVERY entry point, clients and servers both: a client reaches its\n * server only over HTTP, never through an nx edge, so a server is in its product only because it is\n * tagged. Why final: a project is one product's, never two by accident — the example apps depend on\n * the platform libraries, and a platform library tagged `product:platform` must not be painted\n * `example` because an example reaches it. A published library whose entry points live in OTHER\n * repos carries its own tag for exactly that reason: no in-repo walk can ever reach it correctly.\n * - RuntimeProductMembership — the runtime graph, under the same rule. A service that already carries\n * products (declared, or derived on the dependency graph above) keeps exactly them; a service with\n * none belongs to every product whose service reaches it over RUNTIME `dependsOn`, including through\n * a queue to its consumers, and that walk stops at a service with products of its own. A call from\n * one product into another product's service still shows — as the runtime EDGE between them.\n * - ProductPalette — the Product color mode's colors: a fixed palette of eight assigned in sorted\n * product-name order, a neutral fill for a box shared by EVERY product, and a dashed white box for\n * one in no product.\n * - ProductBoundary — which dependency edges cross from one product into another (#1204), drawn\n * dashed in the target product's color in the Product color mode.\n *\n * Membership is stable per checkout (it depends on tags and edges, never on a diff), so it is written\n * into the committed dependencies.json / runtime-dependencies.json rather than into a sidecar.\n */\n\nimport type { EnhancedGraph } from './graph-sorter';\nimport type { RuntimeGraph } from './runtime-graph-model';\n\n// The same two text colors graph-color-modes.ts writes on every fill. Spelled here rather than\n// imported because graph-color-modes.ts imports THIS file (for the palette), and a file-import cycle\n// is refused by `no-file-import-cycles`.\nconst DARK_TEXT = '#1a1c22';\nconst LIGHT_TEXT = '#ffffff';\n\n/** One product's color, and the text color that reads on it. Data-only. */\nexport class ProductColor {\n constructor(\n public readonly product: string,\n public readonly color: string,\n public readonly fontColor: string,\n /** False for a product past the palette's eight, which is painted with the neutral fill. */\n public readonly inPalette: boolean,\n ) {}\n}\n\n/** The eight product colors, in assignment order. Distinct from every runtime and role color. */\nconst PRODUCT_COLORS: readonly (readonly [string, string])[] = [\n ['#3f6fb5', LIGHT_TEXT],\n ['#f28e2b', DARK_TEXT],\n ['#c0392b', LIGHT_TEXT],\n ['#5cb8b2', DARK_TEXT],\n ['#3d8b3d', LIGHT_TEXT],\n ['#edc948', DARK_TEXT],\n ['#8e5ea2', LIGHT_TEXT],\n ['#ff9da7', DARK_TEXT],\n];\n\n/** How many products get a color of their own; any further product uses the neutral fill. */\nexport const PRODUCT_PALETTE_SIZE = PRODUCT_COLORS.length;\n\n/** A box shared by EVERY product, and every product past the palette's eight. */\nexport const PRODUCT_SHARED_FILL = '#c9ced8';\n/** A box in no product: white, with a dashed border. */\nexport const PRODUCT_NONE_FILL = '#ffffff';\nexport const PRODUCT_NONE_BORDER = '#8a90a0';\n\nexport class ProductPalette {\n /** Every declared product, sorted, each with its color. */\n readonly colors: ProductColor[];\n\n constructor(products: readonly string[]) {\n const sorted = [...new Set(products)].sort();\n this.colors = sorted.map((product: string, index: number): ProductColor => {\n const entry = PRODUCT_COLORS[index];\n if (entry === undefined) return new ProductColor(product, PRODUCT_SHARED_FILL, DARK_TEXT, false);\n return new ProductColor(product, entry[0], entry[1], true);\n });\n }\n\n /** Every product carried by a project of the graph, as one palette. */\n // webpieces-disable no-function-outside-class -- static factory of this class\n static fromGraph(graph: EnhancedGraph): ProductPalette {\n const all: string[] = [];\n for (const entry of Object.values(graph)) all.push(...(entry.products ?? []));\n return new ProductPalette(all);\n }\n\n names(): string[] {\n return this.colors.map((color: ProductColor): string => color.product);\n }\n\n colorOf(product: string): ProductColor {\n return (\n this.colors.find((color: ProductColor): boolean => color.product === product) ??\n new ProductColor(product, PRODUCT_SHARED_FILL, DARK_TEXT, false)\n );\n }\n\n /** True for a box carrying every product of a workspace that declares two or more. */\n sharedByAll(products: readonly string[]): boolean {\n return this.colors.length > 1 && this.colors.every((color: ProductColor): boolean => products.includes(color.product));\n }\n\n /** True when some product is past the palette's eight, so the legend must say it is neutral. */\n overflows(): boolean {\n return this.colors.some((color: ProductColor): boolean => !color.inPalette);\n }\n}\n\n/**\n * Where a dependency edge crosses a product boundary (#1204), for the Product color mode: an edge\n * A -> B crosses when B does not carry every product of A, and it is drawn dashed in the color of the\n * product it crosses INTO, so the boundaries read at a glance while same-product edges stay quiet.\n * Whether a crossing is ALLOWED is the product dependency matrix's call (product-dependencies.ts):\n * with `product-tags` ON, generation already refused every crossing the matrix does not allow, so\n * every crossing drawn is an allowed one.\n */\nexport class ProductBoundary {\n /** The product the edge crosses into, or null when the target carries every product of the source. */\n crossedInto(from: readonly string[], to: readonly string[]): string | null {\n if (from.length === 0 || to.length === 0) return null;\n if (from.every((product: string): boolean => to.includes(product))) return null;\n return to.find((product: string): boolean => !from.includes(product)) ?? to[0];\n }\n}\n\n/**\n * The architecture graph's product membership: declared seeds walked DOWN over `dependsOn`, where a\n * project's own declared products are final.\n */\nexport class ProductMembership {\n /**\n * @param seeds project → the products it DECLARES (its `product:` tags)\n * @returns project → its sorted products: exactly its declared set when it declares any; otherwise\n * every product whose tagged project reaches it without passing through another tagged\n * project. A project in no product is absent, so with no tag anywhere the map is empty.\n */\n compute(seeds: ReadonlyMap<string, readonly string[]>, graph: EnhancedGraph): Map<string, string[]> {\n const declared = new Map<string, readonly string[]>();\n seeds.forEach((products: readonly string[], seed: string): void => {\n if (products.length > 0 && graph[seed] !== undefined) declared.set(seed, products);\n });\n const members = new Map<string, Set<string>>();\n declared.forEach((products: readonly string[], seed: string): void => {\n members.set(seed, new Set(products));\n });\n declared.forEach((products: readonly string[], seed: string): void => {\n for (const project of this.closure(seed, graph, declared)) {\n const set = members.get(project) ?? new Set<string>();\n for (const product of products) set.add(product);\n members.set(project, set);\n }\n });\n const result = new Map<string, string[]>();\n members.forEach((set: Set<string>, project: string): void => {\n result.set(project, [...set].sort());\n });\n return result;\n }\n\n /**\n * Every UNTAGGED project `seed` reaches through `dependsOn`. A tagged project is neither collected\n * nor walked through: its products are final, and its own seed walk covers its dependencies.\n */\n private closure(seed: string, graph: EnhancedGraph, declared: ReadonlyMap<string, readonly string[]>): Set<string> {\n const seen = new Set<string>([seed]);\n const reached = new Set<string>();\n const stack = [seed];\n while (stack.length > 0) {\n const project = stack.pop() as string;\n for (const dep of graph[project]?.dependsOn ?? []) {\n if (seen.has(dep)) continue;\n seen.add(dep);\n if (declared.has(dep)) continue;\n reached.add(dep);\n stack.push(dep);\n }\n }\n return reached;\n }\n}\n\n/**\n * The runtime graph's product membership: services that carry products walked over RUNTIME\n * `dependsOn`, where a service's own products are final.\n */\nexport class RuntimeProductMembership {\n /**\n * Stamp `products` onto every service of `graph` that some product reaches. Seeds are the\n * services whose dependencies.json entry carries `products`, and each keeps exactly those; a\n * service with none gets every product whose seed reaches it without passing through another\n * seed. A `queue:<Api.method>` dependency is followed to that queue's consumers. A service in no\n * product gets no field.\n */\n apply(graph: RuntimeGraph, projects: EnhancedGraph): void {\n const seeds = new Map<string, readonly string[]>();\n for (const name of Object.keys(graph.services)) {\n const own = projects[name]?.products ?? [];\n if (own.length > 0) seeds.set(name, own);\n }\n const members = new Map<string, Set<string>>();\n seeds.forEach((own: readonly string[], name: string): void => {\n members.set(name, new Set(own));\n });\n seeds.forEach((own: readonly string[], name: string): void => {\n for (const reached of this.closure(name, graph, seeds)) {\n const set = members.get(reached) ?? new Set<string>();\n for (const product of own) set.add(product);\n members.set(reached, set);\n }\n });\n members.forEach((set: Set<string>, name: string): void => {\n const service = graph.services[name];\n if (service !== undefined) service.products = [...set].sort();\n });\n }\n\n /** Every service WITHOUT products of its own that `seed` reaches; a seed is neither collected nor walked through. */\n private closure(seed: string, graph: RuntimeGraph, seeds: ReadonlyMap<string, readonly string[]>): Set<string> {\n const seen = new Set<string>([seed]);\n const reached = new Set<string>();\n const stack = [seed];\n while (stack.length > 0) {\n const name = stack.pop() as string;\n for (const next of this.callees(name, graph)) {\n if (seen.has(next) || graph.services[next] === undefined) continue;\n seen.add(next);\n if (seeds.has(next)) continue;\n reached.add(next);\n stack.push(next);\n }\n }\n return reached;\n }\n\n /** The services one service calls: direct ones, and the consumers behind every queue it feeds. */\n private callees(name: string, graph: RuntimeGraph): string[] {\n const callees: string[] = [];\n for (const dep of graph.services[name]?.dependsOn ?? []) {\n if (!dep.startsWith('queue:')) {\n callees.push(dep);\n continue;\n }\n callees.push(...(graph.queues[dep.slice('queue:'.length)]?.consumedBy ?? []));\n }\n return callees;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"graph-products.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-products.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Products on the architecture graphs (#1179).\n *\n * A monorepo can hold several PRODUCTS, each with its own clients and servers. A project declares the\n * products it belongs to with the nx tag `product:<name>` (product-resolver.ts). Membership is TAGS\n * ONLY (#1216) on both graphs — nothing is inherited over an edge:\n *\n * - ProductMembership — the dependency graph. A project's products are exactly its own declared\n * `product:` tags. An UNTAGGED project is in no product, however many tagged projects depend on it,\n * and renders as \"no product\" — a box that still needs a tag. A consumer that wants every project\n * assigned lists the library roles in `product-tags.roles`, so an untagged library fails generation\n * instead of quietly sitting outside every product (and outside the product dependency matrix,\n * which skips any edge with an untagged end — product-dependencies.ts).\n * - RuntimeProductMembership — the runtime graph. A service's products are exactly its project's\n * declared `product:` tags. A service is never put in a product because another service calls it or\n * feeds its queue; a call from one product into another product's service shows as the runtime EDGE\n * between them. A service whose project carries no tag is in no product.\n * - ProductPalette — the Product color mode's colors: a fixed palette of eight assigned in sorted\n * product-name order, a neutral fill for a box shared by EVERY product, and a dashed white box for\n * one in no product.\n * - ProductBoundary — which dependency edges cross from one product into another (#1204), drawn\n * dashed in the target product's color in the Product color mode.\n *\n * Membership is stable per checkout (it depends on tags only, never on a diff), so it is written into\n * the committed dependencies.json / runtime-dependencies.json rather than into a sidecar.\n */\n\nimport type { EnhancedGraph } from './graph-sorter';\nimport type { RuntimeGraph } from './runtime-graph-model';\n\n// The same two text colors graph-color-modes.ts writes on every fill. Spelled here rather than\n// imported because graph-color-modes.ts imports THIS file (for the palette), and a file-import cycle\n// is refused by `no-file-import-cycles`.\nconst DARK_TEXT = '#1a1c22';\nconst LIGHT_TEXT = '#ffffff';\n\n/** One product's color, and the text color that reads on it. Data-only. */\nexport class ProductColor {\n constructor(\n public readonly product: string,\n public readonly color: string,\n public readonly fontColor: string,\n /** False for a product past the palette's eight, which is painted with the neutral fill. */\n public readonly inPalette: boolean,\n ) {}\n}\n\n/** The eight product colors, in assignment order. Distinct from every runtime and role color. */\nconst PRODUCT_COLORS: readonly (readonly [string, string])[] = [\n ['#3f6fb5', LIGHT_TEXT],\n ['#f28e2b', DARK_TEXT],\n ['#c0392b', LIGHT_TEXT],\n ['#5cb8b2', DARK_TEXT],\n ['#3d8b3d', LIGHT_TEXT],\n ['#edc948', DARK_TEXT],\n ['#8e5ea2', LIGHT_TEXT],\n ['#ff9da7', DARK_TEXT],\n];\n\n/** How many products get a color of their own; any further product uses the neutral fill. */\nexport const PRODUCT_PALETTE_SIZE = PRODUCT_COLORS.length;\n\n/** A box shared by EVERY product, and every product past the palette's eight. */\nexport const PRODUCT_SHARED_FILL = '#c9ced8';\n/** A box in no product: white, with a dashed border. */\nexport const PRODUCT_NONE_FILL = '#ffffff';\nexport const PRODUCT_NONE_BORDER = '#8a90a0';\n\nexport class ProductPalette {\n /** Every declared product, sorted, each with its color. */\n readonly colors: ProductColor[];\n\n constructor(products: readonly string[]) {\n const sorted = [...new Set(products)].sort();\n this.colors = sorted.map((product: string, index: number): ProductColor => {\n const entry = PRODUCT_COLORS[index];\n if (entry === undefined) return new ProductColor(product, PRODUCT_SHARED_FILL, DARK_TEXT, false);\n return new ProductColor(product, entry[0], entry[1], true);\n });\n }\n\n /** Every product carried by a project of the graph, as one palette. */\n // webpieces-disable no-function-outside-class -- static factory of this class\n static fromGraph(graph: EnhancedGraph): ProductPalette {\n const all: string[] = [];\n for (const entry of Object.values(graph)) all.push(...(entry.products ?? []));\n return new ProductPalette(all);\n }\n\n names(): string[] {\n return this.colors.map((color: ProductColor): string => color.product);\n }\n\n colorOf(product: string): ProductColor {\n return (\n this.colors.find((color: ProductColor): boolean => color.product === product) ??\n new ProductColor(product, PRODUCT_SHARED_FILL, DARK_TEXT, false)\n );\n }\n\n /** True for a box carrying every product of a workspace that declares two or more. */\n sharedByAll(products: readonly string[]): boolean {\n return this.colors.length > 1 && this.colors.every((color: ProductColor): boolean => products.includes(color.product));\n }\n\n /** True when some product is past the palette's eight, so the legend must say it is neutral. */\n overflows(): boolean {\n return this.colors.some((color: ProductColor): boolean => !color.inPalette);\n }\n}\n\n/**\n * Where a dependency edge crosses a product boundary (#1204), for the Product color mode: an edge\n * A -> B crosses when B does not carry every product of A, and it is drawn dashed in the color of the\n * product it crosses INTO, so the boundaries read at a glance while same-product edges stay quiet.\n * Whether a crossing is ALLOWED is the product dependency matrix's call (product-dependencies.ts):\n * with `product-tags` ON, generation already refused every crossing the matrix does not allow, so\n * every crossing drawn is an allowed one.\n */\nexport class ProductBoundary {\n /** The product the edge crosses into, or null when the target carries every product of the source. */\n crossedInto(from: readonly string[], to: readonly string[]): string | null {\n if (from.length === 0 || to.length === 0) return null;\n if (from.every((product: string): boolean => to.includes(product))) return null;\n return to.find((product: string): boolean => !from.includes(product)) ?? to[0];\n }\n}\n\n/**\n * The architecture graph's product membership: exactly each project's declared `product:` tags (#1216).\n */\nexport class ProductMembership {\n /**\n * @param seeds project → the products it DECLARES (its `product:` tags)\n * @returns project → its sorted declared products. An untagged project is absent — it is in no\n * product, however many tagged projects depend on it — so with no tag anywhere the map is\n * empty.\n */\n compute(seeds: ReadonlyMap<string, readonly string[]>, graph: EnhancedGraph): Map<string, string[]> {\n const result = new Map<string, string[]>();\n seeds.forEach((products: readonly string[], project: string): void => {\n if (products.length > 0 && graph[project] !== undefined) result.set(project, [...new Set(products)].sort());\n });\n return result;\n }\n}\n\n/**\n * The runtime graph's product membership: exactly each service's project's declared `product:` tags\n * (#1216). Never derived from who calls a service or feeds its queue.\n */\nexport class RuntimeProductMembership {\n /**\n * Stamp `products` onto every service of `graph` whose project declares any. `projects` is the\n * dependency graph, whose `products` are exactly each project's declared tags (ProductMembership);\n * a service whose project declares none gets no field.\n */\n apply(graph: RuntimeGraph, projects: EnhancedGraph): void {\n for (const [name, service] of Object.entries(graph.services)) {\n const declared = projects[name]?.products ?? [];\n if (declared.length > 0) service.products = [...declared].sort();\n }\n }\n}\n"]}
|
|
@@ -62,12 +62,9 @@ export interface GraphEntry {
|
|
|
62
62
|
*/
|
|
63
63
|
cutLegacyCycle?: string[];
|
|
64
64
|
/**
|
|
65
|
-
* The products this project belongs to (#1179), sorted
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* another tagged project. It is the project's blast radius — a change here is a change to each of
|
|
69
|
-
* these products. Absent when no product reaches it (and everywhere when no project carries a
|
|
70
|
-
* `product:` tag). See graph-products.ts.
|
|
65
|
+
* The products this project belongs to (#1179), sorted: exactly its declared `product:<name>` tags
|
|
66
|
+
* (#1216). Absent for an untagged project — it is in no product, however many tagged projects
|
|
67
|
+
* depend on it. See graph-products.ts.
|
|
71
68
|
*/
|
|
72
69
|
products?: string[];
|
|
73
70
|
shortDescription?: string;
|