@webpieces/nx-webpieces-rules 0.3.239 → 0.3.241
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 +6 -6
- package/src/executors/di-graph-generate/executor.js +6 -3
- package/src/executors/di-graph-generate/executor.js.map +1 -1
- package/src/executors/generate/executor.js +15 -2
- package/src/executors/generate/executor.js.map +1 -1
- package/src/executors/validate-architecture-unchanged/executor.js +32 -18
- package/src/executors/validate-architecture-unchanged/executor.js.map +1 -1
- package/src/executors/visualize/executor.js +3 -2
- package/src/executors/visualize/executor.js.map +1 -1
- package/src/index.d.ts +4 -0
- package/src/index.js +4 -0
- package/src/index.js.map +1 -1
- package/src/lib/di-graph/analyzer.d.ts +3 -1
- package/src/lib/di-graph/analyzer.js +0 -0
- package/src/lib/di-graph/analyzer.js.map +1 -1
- package/src/lib/di-graph/mermaid.d.ts +3 -1
- package/src/lib/di-graph/mermaid.js +23 -7
- package/src/lib/di-graph/mermaid.js.map +1 -1
- package/src/lib/di-graph/model.d.ts +31 -6
- package/src/lib/di-graph/model.js +43 -11
- package/src/lib/di-graph/model.js.map +1 -1
- package/src/lib/di-graph/serializer.js +0 -0
- package/src/lib/di-graph/serializer.js.map +1 -1
- package/src/lib/framework-resolver.d.ts +26 -0
- package/src/lib/framework-resolver.js +97 -0
- package/src/lib/framework-resolver.js.map +1 -0
- package/src/lib/graph-comparator.d.ts +9 -0
- package/src/lib/graph-comparator.js +34 -1
- package/src/lib/graph-comparator.js.map +1 -1
- package/src/lib/graph-loader.d.ts +34 -7
- package/src/lib/graph-loader.js +90 -27
- package/src/lib/graph-loader.js.map +1 -1
- package/src/lib/graph-metadata.d.ts +36 -0
- package/src/lib/graph-metadata.js +108 -0
- package/src/lib/graph-metadata.js.map +1 -0
- package/src/lib/graph-sorter.d.ts +11 -1
- package/src/lib/graph-sorter.js.map +1 -1
- package/src/lib/project-info.d.ts +15 -0
- package/src/lib/project-info.js +23 -0
- package/src/lib/project-info.js.map +1 -0
- package/src/lib/responsibilities.d.ts +27 -0
- package/src/lib/responsibilities.js +60 -0
- package/src/lib/responsibilities.js.map +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mermaid.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/mermaid.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;
|
|
1
|
+
{"version":3,"file":"mermaid.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/mermaid.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Mermaid Emitter\n *\n * Renders the DI graph as design.md — a Mermaid `graph TD` diagram that GitHub\n * and IDEs render inline, so the DI design is reviewable in every PR. Emitted\n * in the same sorted order as design.json so diffs are equally stable.\n */\n\nimport { DiDesign, DiGraph, DiNode } from './model';\nimport { sortGraph } from './serializer';\n\n/** Mermaid node ids must be simple identifiers — map graph ids to safe ids. */\nfunction mermaidId(id: string): string {\n return id.replace(/[^A-Za-z0-9_]/g, '_');\n}\n\n/** Escape text destined for a quoted mermaid label. */\nfunction label(text: string): string {\n return text.replace(/\"/g, '#quot;');\n}\n\n/** Escape an edge label (rendered between pipes). */\nfunction edgeLabel(text: string): string {\n return text.replace(/\\|/g, '/').replace(/\"/g, \"'\");\n}\n\nfunction nodeStatement(node: DiNode): string {\n const id = mermaidId(node.id);\n const text = label(node.className);\n if (node.kind === 'controller') return ` ${id}[\"${text}\"]:::controller`;\n if (node.kind === 'constant' || node.kind === 'dynamic') return ` ${id}([\"${text}\"])`;\n if (node.kind === 'unresolved') return ` ${id}{{\"${text} ?\"}}:::unresolved`;\n return ` ${id}[\"${text}\"]`;\n}\n\nfunction graphBody(design: DiDesign): string[] {\n const lines: string[] = ['graph TD'];\n for (const node of design.nodes) {\n lines.push(nodeStatement(node));\n }\n for (const edge of design.edges) {\n const from = mermaidId(edge.from);\n const to = mermaidId(edge.to);\n const token = edge.injection === 'multiInject' ? `multiInject ${edge.token}` : edge.token;\n if (token !== '') {\n lines.push(` ${from} -->|${edgeLabel(token)}| ${to}`);\n } else {\n lines.push(` ${from} --> ${to}`);\n }\n }\n lines.push(' classDef controller fill:#1f6feb,color:#ffffff,stroke:#0d419d');\n lines.push(' classDef unresolved fill:#f0ad4e,color:#000000,stroke:#b8860b,stroke-dasharray: 5 5');\n return lines;\n}\n\n/** One `## Root` section with the design's Mermaid diagram. */\nfunction designSection(design: DiDesign): string[] {\n const kind = design.rootKind === 'controller' ? 'controller' : 'root';\n const heading = `## ${design.root} — ${kind}, Level 0…${design.maxLevel}`;\n return [heading, '', '```mermaid', ...graphBody(design), '```', ''];\n}\n\n/**\n * Render the design.md content for a project's DI graph — one Mermaid diagram\n * per design (per controller, or per library top-of-DAG root), each rooted at\n * Level 0 with its injections fanning downward.\n */\nexport function toDesignMarkdown(graph: DiGraph): string {\n sortGraph(graph);\n\n const header = [\n `# DI Design Graph — ${graph.project}`,\n '',\n `> GENERATED by \\`nx run ${graph.project}:di-graph-generate\\` — do not edit by hand.`,\n `> Machine-readable version: [design.json](./design.json)`,\n '',\n `Each section below is one root's dependency tree: Level 0 is the root`,\n `(a \\`@Controller\\` or top-of-DAG class), and constructor injections fan`,\n `downward through Levels 1, 2, … A dependency shared by multiple roots`,\n `appears in each root's tree.`,\n '',\n ];\n\n if (graph.designs.length === 0) {\n return header.concat(['No DI-registered classes found in this project.', '']).join('\\n');\n }\n\n const sections: string[] = [];\n for (const design of graph.designs) {\n sections.push(...designSection(design));\n }\n\n const legend = [\n '',\n 'Edges are constructor injections: `-->|TOKEN|` for `@inject`/`@multiInject`,',\n 'unlabeled arrows for inject-by-type. Rounded nodes are `toConstantValue`/',\n '`toDynamicValue` leaves; dashed nodes are tokens the analyzer could not resolve.',\n '',\n ];\n\n return header.concat(sections).concat(legend).join('\\n');\n}\n"]}
|
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
* Data classes for the per-project Inversify dependency DAG that is generated
|
|
5
5
|
* into <projectRoot>/design.json and <projectRoot>/design.md on every build.
|
|
6
6
|
*
|
|
7
|
+
* A project's design.json is a container (`DiGraph`) holding an ARRAY of
|
|
8
|
+
* per-root designs (`DiDesign`) — one self-contained downward tree per
|
|
9
|
+
* @Controller class (or, for controller-less library projects, per top-of-DAG
|
|
10
|
+
* class). Each node in a design carries its `level` = BFS depth from that
|
|
11
|
+
* design's root (root = level 0, its direct injections = level 1, and so on).
|
|
12
|
+
*
|
|
7
13
|
* All structures are classes (not interfaces) per the repo convention for
|
|
8
14
|
* data-only structures.
|
|
9
15
|
*/
|
|
@@ -22,7 +28,9 @@ export declare class DiNode {
|
|
|
22
28
|
kind: DiNodeKind;
|
|
23
29
|
scope: DiScope;
|
|
24
30
|
file: string;
|
|
25
|
-
|
|
31
|
+
/** BFS depth from the design's root (root = 0). Filled in after the walk. */
|
|
32
|
+
level: number;
|
|
33
|
+
constructor(id: string, className: string, kind: DiNodeKind, scope: DiScope, file: string, level?: number);
|
|
26
34
|
}
|
|
27
35
|
/**
|
|
28
36
|
* A constructor-injection edge: `from` class injects `to` node.
|
|
@@ -38,15 +46,32 @@ export declare class DiEdge {
|
|
|
38
46
|
constructor(from: string, to: string, injection: DiInjectionKind, token: string, tokenKey: string, paramName: string, paramType: string);
|
|
39
47
|
}
|
|
40
48
|
/**
|
|
41
|
-
*
|
|
49
|
+
* One self-contained DI design tree: a single root (a @Controller, or a
|
|
50
|
+
* library top-of-DAG class) and everything it injects "on down". Shared
|
|
51
|
+
* dependencies are duplicated across designs — each design is its own tree so
|
|
52
|
+
* it can be reviewed in isolation.
|
|
42
53
|
*/
|
|
43
|
-
export declare class
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
54
|
+
export declare class DiDesign {
|
|
55
|
+
/** Node id of this design's root (level 0). */
|
|
56
|
+
root: string;
|
|
57
|
+
rootKind: DiNodeKind;
|
|
58
|
+
/** Workspace-relative posix path of the root class's file. */
|
|
59
|
+
file: string;
|
|
60
|
+
/** Deepest level reached from the root (0 when the root has no injections). */
|
|
61
|
+
maxLevel: number;
|
|
47
62
|
nodes: DiNode[];
|
|
48
63
|
edges: DiEdge[];
|
|
49
64
|
unresolved: string[];
|
|
65
|
+
constructor(root: string, rootKind: DiNodeKind, file: string);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The full per-project DI graph, serialized to design.json — a container over
|
|
69
|
+
* one `DiDesign` per root (controller or library top-of-DAG class).
|
|
70
|
+
*/
|
|
71
|
+
export declare class DiGraph {
|
|
72
|
+
schemaVersion: number;
|
|
73
|
+
project: string;
|
|
74
|
+
designs: DiDesign[];
|
|
50
75
|
constructor(project: string);
|
|
51
76
|
}
|
|
52
77
|
/**
|
|
@@ -5,11 +5,17 @@
|
|
|
5
5
|
* Data classes for the per-project Inversify dependency DAG that is generated
|
|
6
6
|
* into <projectRoot>/design.json and <projectRoot>/design.md on every build.
|
|
7
7
|
*
|
|
8
|
+
* A project's design.json is a container (`DiGraph`) holding an ARRAY of
|
|
9
|
+
* per-root designs (`DiDesign`) — one self-contained downward tree per
|
|
10
|
+
* @Controller class (or, for controller-less library projects, per top-of-DAG
|
|
11
|
+
* class). Each node in a design carries its `level` = BFS depth from that
|
|
12
|
+
* design's root (root = level 0, its direct injections = level 1, and so on).
|
|
13
|
+
*
|
|
8
14
|
* All structures are classes (not interfaces) per the repo convention for
|
|
9
15
|
* data-only structures.
|
|
10
16
|
*/
|
|
11
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.Binding = exports.TokenRef = exports.DiGraph = exports.DiEdge = exports.DiNode = void 0;
|
|
18
|
+
exports.Binding = exports.TokenRef = exports.DiGraph = exports.DiDesign = exports.DiEdge = exports.DiNode = void 0;
|
|
13
19
|
/**
|
|
14
20
|
* A node in the DI graph — a class, a constant/dynamic binding leaf, or an
|
|
15
21
|
* unresolved token placeholder.
|
|
@@ -20,12 +26,15 @@ class DiNode {
|
|
|
20
26
|
kind;
|
|
21
27
|
scope;
|
|
22
28
|
file;
|
|
23
|
-
|
|
29
|
+
/** BFS depth from the design's root (root = 0). Filled in after the walk. */
|
|
30
|
+
level;
|
|
31
|
+
constructor(id, className, kind, scope, file, level = 0) {
|
|
24
32
|
this.id = id;
|
|
25
33
|
this.className = className;
|
|
26
34
|
this.kind = kind;
|
|
27
35
|
this.scope = scope;
|
|
28
36
|
this.file = file;
|
|
37
|
+
this.level = level;
|
|
29
38
|
}
|
|
30
39
|
}
|
|
31
40
|
exports.DiNode = DiNode;
|
|
@@ -52,24 +61,47 @@ class DiEdge {
|
|
|
52
61
|
}
|
|
53
62
|
exports.DiEdge = DiEdge;
|
|
54
63
|
/**
|
|
55
|
-
*
|
|
64
|
+
* One self-contained DI design tree: a single root (a @Controller, or a
|
|
65
|
+
* library top-of-DAG class) and everything it injects "on down". Shared
|
|
66
|
+
* dependencies are duplicated across designs — each design is its own tree so
|
|
67
|
+
* it can be reviewed in isolation.
|
|
56
68
|
*/
|
|
57
|
-
class
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
69
|
+
class DiDesign {
|
|
70
|
+
/** Node id of this design's root (level 0). */
|
|
71
|
+
root;
|
|
72
|
+
rootKind;
|
|
73
|
+
/** Workspace-relative posix path of the root class's file. */
|
|
74
|
+
file;
|
|
75
|
+
/** Deepest level reached from the root (0 when the root has no injections). */
|
|
76
|
+
maxLevel;
|
|
61
77
|
nodes;
|
|
62
78
|
edges;
|
|
63
79
|
unresolved;
|
|
64
|
-
constructor(
|
|
65
|
-
this.
|
|
66
|
-
this.
|
|
67
|
-
this.
|
|
80
|
+
constructor(root, rootKind, file) {
|
|
81
|
+
this.root = root;
|
|
82
|
+
this.rootKind = rootKind;
|
|
83
|
+
this.file = file;
|
|
84
|
+
this.maxLevel = 0;
|
|
68
85
|
this.nodes = [];
|
|
69
86
|
this.edges = [];
|
|
70
87
|
this.unresolved = [];
|
|
71
88
|
}
|
|
72
89
|
}
|
|
90
|
+
exports.DiDesign = DiDesign;
|
|
91
|
+
/**
|
|
92
|
+
* The full per-project DI graph, serialized to design.json — a container over
|
|
93
|
+
* one `DiDesign` per root (controller or library top-of-DAG class).
|
|
94
|
+
*/
|
|
95
|
+
class DiGraph {
|
|
96
|
+
schemaVersion;
|
|
97
|
+
project;
|
|
98
|
+
designs;
|
|
99
|
+
constructor(project) {
|
|
100
|
+
this.schemaVersion = 2;
|
|
101
|
+
this.project = project;
|
|
102
|
+
this.designs = [];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
73
105
|
exports.DiGraph = DiGraph;
|
|
74
106
|
/**
|
|
75
107
|
* Canonical identity of a DI token — the key links `Symbol.for('X')` token
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/model.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/model.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DI Graph Model\n *\n * Data classes for the per-project Inversify dependency DAG that is generated\n * into <projectRoot>/design.json and <projectRoot>/design.md on every build.\n *\n * A project's design.json is a container (`DiGraph`) holding an ARRAY of\n * per-root designs (`DiDesign`) — one self-contained downward tree per\n * @Controller class (or, for controller-less library projects, per top-of-DAG\n * class). Each node in a design carries its `level` = BFS depth from that\n * design's root (root = level 0, its direct injections = level 1, and so on).\n *\n * All structures are classes (not interfaces) per the repo convention for\n * data-only structures.\n */\n\nimport type * as ts from 'typescript';\n\nexport type DiNodeKind = 'controller' | 'class' | 'constant' | 'dynamic' | 'unresolved';\n\nexport type DiInjectionKind = 'token' | 'type' | 'multiInject';\n\nexport type DiScope = 'singleton' | 'transient' | 'unknown';\n\nexport type BindingKind = 'to' | 'toSelf' | 'toConstantValue' | 'toDynamicValue' | 'decorator';\n\n/**\n * A node in the DI graph — a class, a constant/dynamic binding leaf, or an\n * unresolved token placeholder.\n */\nexport class DiNode {\n id: string;\n className: string;\n kind: DiNodeKind;\n scope: DiScope;\n file: string;\n /** BFS depth from the design's root (root = 0). Filled in after the walk. */\n level: number;\n\n constructor(id: string, className: string, kind: DiNodeKind, scope: DiScope, file: string, level = 0) {\n this.id = id;\n this.className = className;\n this.kind = kind;\n this.scope = scope;\n this.file = file;\n this.level = level;\n }\n}\n\n/**\n * A constructor-injection edge: `from` class injects `to` node.\n */\nexport class DiEdge {\n from: string;\n to: string;\n injection: DiInjectionKind;\n token: string;\n tokenKey: string;\n paramName: string;\n paramType: string;\n\n constructor(\n from: string,\n to: string,\n injection: DiInjectionKind,\n token: string,\n tokenKey: string,\n paramName: string,\n paramType: string,\n ) {\n this.from = from;\n this.to = to;\n this.injection = injection;\n this.token = token;\n this.tokenKey = tokenKey;\n this.paramName = paramName;\n this.paramType = paramType;\n }\n}\n\n/**\n * One self-contained DI design tree: a single root (a @Controller, or a\n * library top-of-DAG class) and everything it injects \"on down\". Shared\n * dependencies are duplicated across designs — each design is its own tree so\n * it can be reviewed in isolation.\n */\nexport class DiDesign {\n /** Node id of this design's root (level 0). */\n root: string;\n rootKind: DiNodeKind;\n /** Workspace-relative posix path of the root class's file. */\n file: string;\n /** Deepest level reached from the root (0 when the root has no injections). */\n maxLevel: number;\n nodes: DiNode[];\n edges: DiEdge[];\n unresolved: string[];\n\n constructor(root: string, rootKind: DiNodeKind, file: string) {\n this.root = root;\n this.rootKind = rootKind;\n this.file = file;\n this.maxLevel = 0;\n this.nodes = [];\n this.edges = [];\n this.unresolved = [];\n }\n}\n\n/**\n * The full per-project DI graph, serialized to design.json — a container over\n * one `DiDesign` per root (controller or library top-of-DAG class).\n */\nexport class DiGraph {\n schemaVersion: number;\n project: string;\n designs: DiDesign[];\n\n constructor(project: string) {\n this.schemaVersion = 2;\n this.project = project;\n this.designs = [];\n }\n}\n\n/**\n * Canonical identity of a DI token — the key links `Symbol.for('X')` token\n * definitions to their bind() sites even across packages; display is what a\n * human reads in design.md edge labels (e.g. \"TYPES.Counter\").\n */\nexport class TokenRef {\n key: string;\n display: string;\n\n constructor(key: string, display: string) {\n this.key = key;\n this.display = display;\n }\n}\n\n/**\n * One binding discovered in pass 1 — either a ContainerModule bind() call or a\n * @provideSingleton/@provideSingletonAs/@provideTransient decorator.\n */\nexport class Binding {\n tokenKey: string;\n tokenDisplay: string;\n kind: BindingKind;\n scope: DiScope;\n /** Implementation class for to/toSelf/decorator bindings; null for constant/dynamic. */\n implClass: ts.ClassDeclaration | null;\n /** Source text of the bound expression for constant/dynamic leaves; '' otherwise. */\n valueText: string;\n /** Workspace-relative posix path of the file the binding appears in. */\n file: string;\n\n constructor(\n tokenKey: string,\n tokenDisplay: string,\n kind: BindingKind,\n scope: DiScope,\n implClass: ts.ClassDeclaration | null,\n valueText: string,\n file: string,\n ) {\n this.tokenKey = tokenKey;\n this.tokenDisplay = tokenDisplay;\n this.kind = kind;\n this.scope = scope;\n this.implClass = implClass;\n this.valueText = valueText;\n this.file = file;\n }\n}\n"]}
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serializer.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/serializer.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;
|
|
1
|
+
{"version":3,"file":"serializer.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/serializer.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AA0BH,8BAMC;AAGD,oCAGC;AAlCD,SAAS,YAAY,CAAC,CAAS,EAAE,CAAS;IACtC,OAAO,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,OAAO,CAAC,IAAY;IACzB,OAAO,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;AACxE,CAAC;AAED,SAAS,YAAY,CAAC,CAAS,EAAE,CAAS;IACtC,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACxB,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACxB,OAAO,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClD,CAAC;AAED,8CAA8C;AAC9C,SAAS,UAAU,CAAC,MAAgB;IAChC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAChC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAChC,MAAM,CAAC,UAAU,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAC/D,CAAC;AAED,iEAAiE;AACjE,SAAgB,SAAS,CAAC,KAAc;IACpC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAW,EAAE,CAAW,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACnG,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QACjC,UAAU,CAAC,MAAM,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,kEAAkE;AAClE,SAAgB,YAAY,CAAC,KAAc;IACvC,SAAS,CAAC,KAAK,CAAC,CAAC;IACjB,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;AACjD,CAAC","sourcesContent":["/**\n * DI Graph Serializer\n *\n * Deterministic serialization for design.json: every collection sorted, no\n * timestamps, workspace-relative posix paths only — so the committed file is\n * byte-identical across regenerations of an unchanged graph.\n */\n\nimport { DiDesign, DiEdge, DiGraph, DiNode } from './model';\n\nfunction compareNodes(a: DiNode, b: DiNode): number {\n return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;\n}\n\nfunction edgeKey(edge: DiEdge): string {\n return `${edge.from} ${edge.to} ${edge.tokenKey} ${edge.paramName}`;\n}\n\nfunction compareEdges(a: DiEdge, b: DiEdge): number {\n const keyA = edgeKey(a);\n const keyB = edgeKey(b);\n return keyA < keyB ? -1 : keyA > keyB ? 1 : 0;\n}\n\n/** Sort one design's collections in place. */\nfunction sortDesign(design: DiDesign): void {\n design.nodes.sort(compareNodes);\n design.edges.sort(compareEdges);\n design.unresolved = [...new Set(design.unresolved)].sort();\n}\n\n/** Sort all graph collections in place (stable output order). */\nexport function sortGraph(graph: DiGraph): DiGraph {\n graph.designs.sort((a: DiDesign, b: DiDesign) => (a.root < b.root ? -1 : a.root > b.root ? 1 : 0));\n for (const design of graph.designs) {\n sortDesign(design);\n }\n return graph;\n}\n\n/** Serialize a (sorted) graph to the design.json file content. */\nexport function toDesignJson(graph: DiGraph): string {\n sortGraph(graph);\n return JSON.stringify(graph, null, 4) + '\\n';\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework Resolver
|
|
3
|
+
*
|
|
4
|
+
* Determines the `framework` field written per project into
|
|
5
|
+
* architecture/dependencies.json (angular, react, express, all-ts, ...).
|
|
6
|
+
*
|
|
7
|
+
* Resolution order:
|
|
8
|
+
* 1. Explicit nx tag `framework:<value>` on the project (project.json tags) —
|
|
9
|
+
* any value is allowed so new frameworks need no code change here.
|
|
10
|
+
* 2. Inference from the project's package.json dependencies.
|
|
11
|
+
* 3. Fallback: 'all-ts' (plain TypeScript library).
|
|
12
|
+
*/
|
|
13
|
+
import { ProjectInfo } from './project-info';
|
|
14
|
+
export declare const FRAMEWORK_TAG_PREFIX = "framework:";
|
|
15
|
+
export declare class FrameworkResolution {
|
|
16
|
+
/** Resolved framework name, or null when resolution failed */
|
|
17
|
+
readonly framework: string | null;
|
|
18
|
+
/** Problem description when resolution failed, otherwise null */
|
|
19
|
+
readonly problem: string | null;
|
|
20
|
+
constructor(
|
|
21
|
+
/** Resolved framework name, or null when resolution failed */
|
|
22
|
+
framework: string | null,
|
|
23
|
+
/** Problem description when resolution failed, otherwise null */
|
|
24
|
+
problem: string | null);
|
|
25
|
+
}
|
|
26
|
+
export declare function resolveFramework(info: ProjectInfo, workspaceRoot: string): FrameworkResolution;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Framework Resolver
|
|
4
|
+
*
|
|
5
|
+
* Determines the `framework` field written per project into
|
|
6
|
+
* architecture/dependencies.json (angular, react, express, all-ts, ...).
|
|
7
|
+
*
|
|
8
|
+
* Resolution order:
|
|
9
|
+
* 1. Explicit nx tag `framework:<value>` on the project (project.json tags) —
|
|
10
|
+
* any value is allowed so new frameworks need no code change here.
|
|
11
|
+
* 2. Inference from the project's package.json dependencies.
|
|
12
|
+
* 3. Fallback: 'all-ts' (plain TypeScript library).
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.FrameworkResolution = exports.FRAMEWORK_TAG_PREFIX = void 0;
|
|
16
|
+
exports.resolveFramework = resolveFramework;
|
|
17
|
+
const tslib_1 = require("tslib");
|
|
18
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
19
|
+
const path = tslib_1.__importStar(require("path"));
|
|
20
|
+
const toError_1 = require("../toError");
|
|
21
|
+
exports.FRAMEWORK_TAG_PREFIX = 'framework:';
|
|
22
|
+
/**
|
|
23
|
+
* A package.json dependency name that identifies a framework.
|
|
24
|
+
*/
|
|
25
|
+
class FrameworkMarker {
|
|
26
|
+
dependency;
|
|
27
|
+
framework;
|
|
28
|
+
constructor(dependency, framework) {
|
|
29
|
+
this.dependency = dependency;
|
|
30
|
+
this.framework = framework;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Dependency name → framework, checked in order (first match wins).
|
|
35
|
+
*/
|
|
36
|
+
const FRAMEWORK_DEPENDENCY_MARKERS = [
|
|
37
|
+
new FrameworkMarker('@angular/core', 'angular'),
|
|
38
|
+
new FrameworkMarker('react', 'react'),
|
|
39
|
+
new FrameworkMarker('express', 'express'),
|
|
40
|
+
];
|
|
41
|
+
class FrameworkResolution {
|
|
42
|
+
framework;
|
|
43
|
+
problem;
|
|
44
|
+
constructor(
|
|
45
|
+
/** Resolved framework name, or null when resolution failed */
|
|
46
|
+
framework,
|
|
47
|
+
/** Problem description when resolution failed, otherwise null */
|
|
48
|
+
problem) {
|
|
49
|
+
this.framework = framework;
|
|
50
|
+
this.problem = problem;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.FrameworkResolution = FrameworkResolution;
|
|
54
|
+
function resolveFramework(info, workspaceRoot) {
|
|
55
|
+
const tagValues = info.tags
|
|
56
|
+
.filter((tag) => tag.startsWith(exports.FRAMEWORK_TAG_PREFIX))
|
|
57
|
+
.map((tag) => tag.slice(exports.FRAMEWORK_TAG_PREFIX.length).trim());
|
|
58
|
+
if (tagValues.length > 1) {
|
|
59
|
+
return new FrameworkResolution(null, `${info.name}: has ${tagValues.length} 'framework:' tags (${tagValues.join(', ')}) — a project must have at most one`);
|
|
60
|
+
}
|
|
61
|
+
if (tagValues.length === 1) {
|
|
62
|
+
if (tagValues[0].length === 0) {
|
|
63
|
+
return new FrameworkResolution(null, `${info.name}: 'framework:' tag has an empty value`);
|
|
64
|
+
}
|
|
65
|
+
return new FrameworkResolution(tagValues[0], null);
|
|
66
|
+
}
|
|
67
|
+
return new FrameworkResolution(inferFromPackageJson(info, workspaceRoot), null);
|
|
68
|
+
}
|
|
69
|
+
function inferFromPackageJson(info, workspaceRoot) {
|
|
70
|
+
const pkgJsonPath = path.join(workspaceRoot, info.root, 'package.json');
|
|
71
|
+
if (!fs.existsSync(pkgJsonPath)) {
|
|
72
|
+
return 'all-ts';
|
|
73
|
+
}
|
|
74
|
+
let allDeps;
|
|
75
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
76
|
+
try {
|
|
77
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
|
|
78
|
+
allDeps = {
|
|
79
|
+
...(pkgJson.dependencies ?? {}),
|
|
80
|
+
...(pkgJson.devDependencies ?? {}),
|
|
81
|
+
...(pkgJson.peerDependencies ?? {}),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
const error = (0, toError_1.toError)(err);
|
|
86
|
+
throw new Error(`Failed to parse ${pkgJsonPath} while inferring framework for ${info.name}`, {
|
|
87
|
+
cause: error,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
for (const marker of FRAMEWORK_DEPENDENCY_MARKERS) {
|
|
91
|
+
if (marker.dependency in allDeps) {
|
|
92
|
+
return marker.framework;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return 'all-ts';
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=framework-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"framework-resolver.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/framework-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Framework Resolver\n *\n * Determines the `framework` field written per project into\n * architecture/dependencies.json (angular, react, express, all-ts, ...).\n *\n * Resolution order:\n * 1. Explicit nx tag `framework:<value>` on the project (project.json tags) —\n * any value is allowed so new frameworks need no code change here.\n * 2. Inference from the project's package.json dependencies.\n * 3. Fallback: 'all-ts' (plain TypeScript library).\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { ProjectInfo } from './project-info';\nimport { toError } from '../toError';\n\nexport const FRAMEWORK_TAG_PREFIX = 'framework:';\n\n/**\n * A package.json dependency name that identifies a framework.\n */\nclass FrameworkMarker {\n constructor(\n public readonly dependency: string,\n public readonly framework: string\n ) {}\n}\n\n/**\n * Dependency name → framework, checked in order (first match wins).\n */\nconst FRAMEWORK_DEPENDENCY_MARKERS: ReadonlyArray<FrameworkMarker> = [\n new FrameworkMarker('@angular/core', 'angular'),\n new FrameworkMarker('react', 'react'),\n new FrameworkMarker('express', 'express'),\n];\n\nexport class FrameworkResolution {\n constructor(\n /** Resolved framework name, or null when resolution failed */\n public readonly framework: string | null,\n /** Problem description when resolution failed, otherwise null */\n public readonly problem: string | null\n ) {}\n}\n\nexport function resolveFramework(info: ProjectInfo, workspaceRoot: string): FrameworkResolution {\n const tagValues = info.tags\n .filter((tag: string) => tag.startsWith(FRAMEWORK_TAG_PREFIX))\n .map((tag: string) => tag.slice(FRAMEWORK_TAG_PREFIX.length).trim());\n\n if (tagValues.length > 1) {\n return new FrameworkResolution(\n null,\n `${info.name}: has ${tagValues.length} 'framework:' tags (${tagValues.join(', ')}) — a project must have at most one`\n );\n }\n if (tagValues.length === 1) {\n if (tagValues[0].length === 0) {\n return new FrameworkResolution(null, `${info.name}: 'framework:' tag has an empty value`);\n }\n return new FrameworkResolution(tagValues[0], null);\n }\n\n return new FrameworkResolution(inferFromPackageJson(info, workspaceRoot), null);\n}\n\nfunction inferFromPackageJson(info: ProjectInfo, workspaceRoot: string): string {\n const pkgJsonPath = path.join(workspaceRoot, info.root, 'package.json');\n if (!fs.existsSync(pkgJsonPath)) {\n return 'all-ts';\n }\n\n let allDeps: Record<string, string>;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));\n allDeps = {\n ...(pkgJson.dependencies ?? {}),\n ...(pkgJson.devDependencies ?? {}),\n ...(pkgJson.peerDependencies ?? {}),\n };\n } catch (err: unknown) {\n const error = toError(err);\n throw new Error(`Failed to parse ${pkgJsonPath} while inferring framework for ${info.name}`, {\n cause: error,\n });\n }\n\n for (const marker of FRAMEWORK_DEPENDENCY_MARKERS) {\n if (marker.dependency in allDeps) {\n return marker.framework;\n }\n }\n return 'all-ts';\n}\n"]}
|
|
@@ -5,6 +5,14 @@
|
|
|
5
5
|
* Used in validate mode to ensure developers have updated the graph file.
|
|
6
6
|
*/
|
|
7
7
|
import type { EnhancedGraph } from './graph-sorter';
|
|
8
|
+
/**
|
|
9
|
+
* A changed metadata field on a project (framework, shortDescription, ...)
|
|
10
|
+
*/
|
|
11
|
+
export interface FieldChange {
|
|
12
|
+
field: string;
|
|
13
|
+
from: string | undefined;
|
|
14
|
+
to: string | undefined;
|
|
15
|
+
}
|
|
8
16
|
/**
|
|
9
17
|
* Difference between two graphs
|
|
10
18
|
*/
|
|
@@ -19,6 +27,7 @@ export interface GraphDiff {
|
|
|
19
27
|
from: number;
|
|
20
28
|
to: number;
|
|
21
29
|
} | null;
|
|
30
|
+
changedFields: FieldChange[];
|
|
22
31
|
}[];
|
|
23
32
|
}
|
|
24
33
|
/**
|
|
@@ -7,6 +7,15 @@
|
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.compareGraphs = compareGraphs;
|
|
10
|
+
/**
|
|
11
|
+
* Metadata fields compared per project (beyond level + dependsOn)
|
|
12
|
+
*/
|
|
13
|
+
const METADATA_FIELDS = [
|
|
14
|
+
'framework',
|
|
15
|
+
'shortDescription',
|
|
16
|
+
'responsibilitiesFile',
|
|
17
|
+
'designFile',
|
|
18
|
+
];
|
|
10
19
|
/**
|
|
11
20
|
* Compare two graphs and return the differences
|
|
12
21
|
*
|
|
@@ -67,16 +76,37 @@ function findModifiedProjects(current, saved, currentProjects, savedProjects, di
|
|
|
67
76
|
const levelChanged = currentEntry.level !== savedEntry.level
|
|
68
77
|
? { from: savedEntry.level, to: currentEntry.level }
|
|
69
78
|
: null;
|
|
70
|
-
|
|
79
|
+
const changedFields = findChangedFields(currentEntry, savedEntry);
|
|
80
|
+
if (addedDeps.length > 0 || removedDeps.length > 0 || levelChanged || changedFields.length > 0) {
|
|
71
81
|
diff.modified.push({
|
|
72
82
|
project,
|
|
73
83
|
addedDeps,
|
|
74
84
|
removedDeps,
|
|
75
85
|
levelChanged,
|
|
86
|
+
changedFields,
|
|
76
87
|
});
|
|
77
88
|
}
|
|
78
89
|
}
|
|
79
90
|
}
|
|
91
|
+
function findChangedFields(currentEntry, savedEntry) {
|
|
92
|
+
const changes = [];
|
|
93
|
+
for (const field of METADATA_FIELDS) {
|
|
94
|
+
const from = savedEntry[field];
|
|
95
|
+
const to = currentEntry[field];
|
|
96
|
+
if (from !== to) {
|
|
97
|
+
changes.push({ field, from, to });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return changes;
|
|
101
|
+
}
|
|
102
|
+
function formatFieldValue(value) {
|
|
103
|
+
if (value === undefined)
|
|
104
|
+
return '(none)';
|
|
105
|
+
const MAX_SUMMARY_VALUE_CHARS = 60;
|
|
106
|
+
return value.length > MAX_SUMMARY_VALUE_CHARS
|
|
107
|
+
? `"${value.slice(0, MAX_SUMMARY_VALUE_CHARS)}..."`
|
|
108
|
+
: `"${value}"`;
|
|
109
|
+
}
|
|
80
110
|
function buildSummary(diff) {
|
|
81
111
|
const summaryParts = [];
|
|
82
112
|
if (diff.added.length > 0) {
|
|
@@ -96,6 +126,9 @@ function buildSummary(diff) {
|
|
|
96
126
|
if (mod.levelChanged) {
|
|
97
127
|
parts.push(`level: ${mod.levelChanged.from} -> ${mod.levelChanged.to}`);
|
|
98
128
|
}
|
|
129
|
+
for (const change of mod.changedFields) {
|
|
130
|
+
parts.push(`${change.field}: ${formatFieldValue(change.from)} -> ${formatFieldValue(change.to)}`);
|
|
131
|
+
}
|
|
99
132
|
if (parts.length > 0) {
|
|
100
133
|
summaryParts.push(`${mod.project}: ${parts.join('; ')}`);
|
|
101
134
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-comparator.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-comparator.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Graph Comparator\n *\n * Compares the current generated graph with the saved (blessed) graph.\n * Used in validate mode to ensure developers have updated the graph file.\n */\n\nimport type { EnhancedGraph } from './graph-sorter';\n\n/**\n * Difference between two graphs\n */\nexport interface GraphDiff {\n added: string[];\n removed: string[];\n modified: {\n project: string;\n addedDeps: string[];\n removedDeps: string[];\n levelChanged: { from: number; to: number } | null;\n }[];\n}\n\n/**\n * Comparison result\n */\nexport interface ComparisonResult {\n identical: boolean;\n diff: GraphDiff;\n summary: string;\n}\n\n/**\n * Compare two graphs and return the differences\n *\n * @param current - Currently generated graph\n * @param saved - Previously saved (blessed) graph\n * @returns Comparison result with detailed diff\n */\nexport function compareGraphs(current: EnhancedGraph, saved: EnhancedGraph): ComparisonResult {\n const currentProjects = new Set(Object.keys(current));\n const savedProjects = new Set(Object.keys(saved));\n\n const diff: GraphDiff = {\n added: [],\n removed: [],\n modified: [],\n };\n\n // Find added projects\n for (const project of currentProjects) {\n if (!savedProjects.has(project)) {\n diff.added.push(project);\n }\n }\n\n // Find removed projects\n for (const project of savedProjects) {\n if (!currentProjects.has(project)) {\n diff.removed.push(project);\n }\n }\n\n // Find modified projects\n findModifiedProjects(current, saved, currentProjects, savedProjects, diff);\n\n const identical =\n diff.added.length === 0 && diff.removed.length === 0 && diff.modified.length === 0;\n\n const summary = identical ? 'Graphs are identical' : buildSummary(diff);\n\n return {\n identical,\n diff,\n summary,\n };\n}\n\nfunction findModifiedProjects(\n current: EnhancedGraph,\n saved: EnhancedGraph,\n currentProjects: Set<string>,\n savedProjects: Set<string>,\n diff: GraphDiff\n): void {\n for (const project of currentProjects) {\n if (!savedProjects.has(project)) continue;\n\n const currentEntry = current[project];\n const savedEntry = saved[project];\n\n const currentDeps = new Set(currentEntry.dependsOn);\n const savedDeps = new Set(savedEntry.dependsOn);\n\n const addedDeps: string[] = [];\n const removedDeps: string[] = [];\n\n for (const dep of currentDeps) {\n if (!savedDeps.has(dep)) {\n addedDeps.push(dep);\n }\n }\n\n for (const dep of savedDeps) {\n if (!currentDeps.has(dep)) {\n removedDeps.push(dep);\n }\n }\n\n const levelChanged =\n currentEntry.level !== savedEntry.level\n ? { from: savedEntry.level, to: currentEntry.level }\n : null;\n\n if (addedDeps.length > 0 || removedDeps.length > 0 || levelChanged) {\n diff.modified.push({\n project,\n addedDeps,\n removedDeps,\n levelChanged,\n });\n }\n }\n}\n\nfunction buildSummary(diff: GraphDiff): string {\n const summaryParts: string[] = [];\n\n if (diff.added.length > 0) {\n summaryParts.push(`Added projects: ${diff.added.join(', ')}`);\n }\n\n if (diff.removed.length > 0) {\n summaryParts.push(`Removed projects: ${diff.removed.join(', ')}`);\n }\n\n for (const mod of diff.modified) {\n const parts: string[] = [];\n if (mod.addedDeps.length > 0) {\n parts.push(`+deps: ${mod.addedDeps.join(', ')}`);\n }\n if (mod.removedDeps.length > 0) {\n parts.push(`-deps: ${mod.removedDeps.join(', ')}`);\n }\n if (mod.levelChanged) {\n parts.push(`level: ${mod.levelChanged.from} -> ${mod.levelChanged.to}`);\n }\n if (parts.length > 0) {\n summaryParts.push(`${mod.project}: ${parts.join('; ')}`);\n }\n }\n\n return summaryParts.join('\\n');\n}\n"]}
|
|
1
|
+
{"version":3,"file":"graph-comparator.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-comparator.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Graph Comparator\n *\n * Compares the current generated graph with the saved (blessed) graph.\n * Used in validate mode to ensure developers have updated the graph file.\n */\n\nimport type { EnhancedGraph, GraphEntry } from './graph-sorter';\n\n/**\n * A changed metadata field on a project (framework, shortDescription, ...)\n */\nexport interface FieldChange {\n field: string;\n from: string | undefined;\n to: string | undefined;\n}\n\n/**\n * Difference between two graphs\n */\nexport interface GraphDiff {\n added: string[];\n removed: string[];\n modified: {\n project: string;\n addedDeps: string[];\n removedDeps: string[];\n levelChanged: { from: number; to: number } | null;\n changedFields: FieldChange[];\n }[];\n}\n\n/**\n * Metadata fields compared per project (beyond level + dependsOn)\n */\nconst METADATA_FIELDS: ReadonlyArray<keyof GraphEntry & string> = [\n 'framework',\n 'shortDescription',\n 'responsibilitiesFile',\n 'designFile',\n];\n\n/**\n * Comparison result\n */\nexport interface ComparisonResult {\n identical: boolean;\n diff: GraphDiff;\n summary: string;\n}\n\n/**\n * Compare two graphs and return the differences\n *\n * @param current - Currently generated graph\n * @param saved - Previously saved (blessed) graph\n * @returns Comparison result with detailed diff\n */\nexport function compareGraphs(current: EnhancedGraph, saved: EnhancedGraph): ComparisonResult {\n const currentProjects = new Set(Object.keys(current));\n const savedProjects = new Set(Object.keys(saved));\n\n const diff: GraphDiff = {\n added: [],\n removed: [],\n modified: [],\n };\n\n // Find added projects\n for (const project of currentProjects) {\n if (!savedProjects.has(project)) {\n diff.added.push(project);\n }\n }\n\n // Find removed projects\n for (const project of savedProjects) {\n if (!currentProjects.has(project)) {\n diff.removed.push(project);\n }\n }\n\n // Find modified projects\n findModifiedProjects(current, saved, currentProjects, savedProjects, diff);\n\n const identical =\n diff.added.length === 0 && diff.removed.length === 0 && diff.modified.length === 0;\n\n const summary = identical ? 'Graphs are identical' : buildSummary(diff);\n\n return {\n identical,\n diff,\n summary,\n };\n}\n\nfunction findModifiedProjects(\n current: EnhancedGraph,\n saved: EnhancedGraph,\n currentProjects: Set<string>,\n savedProjects: Set<string>,\n diff: GraphDiff\n): void {\n for (const project of currentProjects) {\n if (!savedProjects.has(project)) continue;\n\n const currentEntry = current[project];\n const savedEntry = saved[project];\n\n const currentDeps = new Set(currentEntry.dependsOn);\n const savedDeps = new Set(savedEntry.dependsOn);\n\n const addedDeps: string[] = [];\n const removedDeps: string[] = [];\n\n for (const dep of currentDeps) {\n if (!savedDeps.has(dep)) {\n addedDeps.push(dep);\n }\n }\n\n for (const dep of savedDeps) {\n if (!currentDeps.has(dep)) {\n removedDeps.push(dep);\n }\n }\n\n const levelChanged =\n currentEntry.level !== savedEntry.level\n ? { from: savedEntry.level, to: currentEntry.level }\n : null;\n\n const changedFields = findChangedFields(currentEntry, savedEntry);\n\n if (addedDeps.length > 0 || removedDeps.length > 0 || levelChanged || changedFields.length > 0) {\n diff.modified.push({\n project,\n addedDeps,\n removedDeps,\n levelChanged,\n changedFields,\n });\n }\n }\n}\n\nfunction findChangedFields(currentEntry: GraphEntry, savedEntry: GraphEntry): FieldChange[] {\n const changes: FieldChange[] = [];\n for (const field of METADATA_FIELDS) {\n const from = savedEntry[field] as string | undefined;\n const to = currentEntry[field] as string | undefined;\n if (from !== to) {\n changes.push({ field, from, to });\n }\n }\n return changes;\n}\n\nfunction formatFieldValue(value: string | undefined): string {\n if (value === undefined) return '(none)';\n const MAX_SUMMARY_VALUE_CHARS = 60;\n return value.length > MAX_SUMMARY_VALUE_CHARS\n ? `\"${value.slice(0, MAX_SUMMARY_VALUE_CHARS)}...\"`\n : `\"${value}\"`;\n}\n\nfunction buildSummary(diff: GraphDiff): string {\n const summaryParts: string[] = [];\n\n if (diff.added.length > 0) {\n summaryParts.push(`Added projects: ${diff.added.join(', ')}`);\n }\n\n if (diff.removed.length > 0) {\n summaryParts.push(`Removed projects: ${diff.removed.join(', ')}`);\n }\n\n for (const mod of diff.modified) {\n const parts: string[] = [];\n if (mod.addedDeps.length > 0) {\n parts.push(`+deps: ${mod.addedDeps.join(', ')}`);\n }\n if (mod.removedDeps.length > 0) {\n parts.push(`-deps: ${mod.removedDeps.join(', ')}`);\n }\n if (mod.levelChanged) {\n parts.push(`level: ${mod.levelChanged.from} -> ${mod.levelChanged.to}`);\n }\n for (const change of mod.changedFields) {\n parts.push(\n `${change.field}: ${formatFieldValue(change.from)} -> ${formatFieldValue(change.to)}`\n );\n }\n if (parts.length > 0) {\n summaryParts.push(`${mod.project}: ${parts.join('; ')}`);\n }\n }\n\n return summaryParts.join('\\n');\n}\n"]}
|
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Handles loading and saving the blessed dependency graph file.
|
|
5
5
|
* The graph is stored at architecture/dependencies.json in the workspace root.
|
|
6
|
+
*
|
|
7
|
+
* File format (schema aimed at AI consumers):
|
|
8
|
+
* {
|
|
9
|
+
* "aiInstructions": "...how AI should use the per-project fields...",
|
|
10
|
+
* "projects": {
|
|
11
|
+
* "<project>": { level, framework, shortDescription,
|
|
12
|
+
* responsibilitiesFile, designFile, dependsOn }
|
|
13
|
+
* }
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* The legacy format (flat { "<project>": { level, dependsOn } } map) is still
|
|
17
|
+
* readable so validation against a pre-upgrade file produces a clean
|
|
18
|
+
* "re-run architecture:generate" diff instead of a parse failure.
|
|
6
19
|
*/
|
|
7
20
|
import type { EnhancedGraph } from './graph-sorter';
|
|
8
21
|
/**
|
|
@@ -10,19 +23,33 @@ import type { EnhancedGraph } from './graph-sorter';
|
|
|
10
23
|
*/
|
|
11
24
|
export declare const DEFAULT_GRAPH_PATH = "architecture/dependencies.json";
|
|
12
25
|
/**
|
|
13
|
-
*
|
|
26
|
+
* Top-level instructions embedded in dependencies.json telling AI how to use
|
|
27
|
+
* the per-project metadata fields.
|
|
28
|
+
*/
|
|
29
|
+
export declare const AI_INSTRUCTIONS: string;
|
|
30
|
+
/**
|
|
31
|
+
* The full contents of architecture/dependencies.json.
|
|
32
|
+
*/
|
|
33
|
+
export declare class DependenciesFile {
|
|
34
|
+
readonly aiInstructions: string;
|
|
35
|
+
readonly projects: EnhancedGraph;
|
|
36
|
+
constructor(aiInstructions: string, projects: EnhancedGraph);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Load the blessed graph from disk. Understands both the current wrapper
|
|
40
|
+
* format and the legacy flat map (which loads with empty aiInstructions).
|
|
14
41
|
*
|
|
15
42
|
* @param workspaceRoot - Absolute path to workspace root
|
|
16
|
-
* @param graphPath - Relative path to graph file (default:
|
|
17
|
-
* @returns The blessed graph, or null if
|
|
43
|
+
* @param graphPath - Relative path to graph file (default: architecture/dependencies.json)
|
|
44
|
+
* @returns The blessed graph file, or null if it doesn't exist
|
|
18
45
|
*/
|
|
19
|
-
export declare function loadBlessedGraph(workspaceRoot: string, graphPath?: string):
|
|
46
|
+
export declare function loadBlessedGraph(workspaceRoot: string, graphPath?: string): DependenciesFile | null;
|
|
20
47
|
/**
|
|
21
|
-
* Save the graph to disk
|
|
48
|
+
* Save the graph to disk in the wrapper format with the standard aiInstructions.
|
|
22
49
|
*
|
|
23
|
-
* @param graph - The graph to save
|
|
50
|
+
* @param graph - The enriched project graph to save
|
|
24
51
|
* @param workspaceRoot - Absolute path to workspace root
|
|
25
|
-
* @param graphPath - Relative path to graph file (default:
|
|
52
|
+
* @param graphPath - Relative path to graph file (default: architecture/dependencies.json)
|
|
26
53
|
*/
|
|
27
54
|
export declare function saveGraph(graph: EnhancedGraph, workspaceRoot: string, graphPath?: string): void;
|
|
28
55
|
/**
|