@getmikk/diagram-generator 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +245 -0
- package/package.json +30 -26
- package/src/generators/capsule-diagram.ts +79 -79
- package/src/generators/dependency-matrix.ts +97 -97
- package/src/generators/flow-diagram.ts +108 -108
- package/src/generators/health-diagram.ts +88 -88
- package/src/generators/impact-diagram.ts +65 -65
- package/src/generators/main-diagram.ts +63 -63
- package/src/generators/module-diagram.ts +75 -75
- package/src/index.ts +8 -8
- package/src/orchestrator.ts +76 -76
- package/tests/smoke.test.ts +5 -5
- package/tsconfig.json +15 -15
|
@@ -1,75 +1,75 @@
|
|
|
1
|
-
import * as path from 'node:path'
|
|
2
|
-
import type { MikkContract, MikkLock } from '@getmikk/core'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* ModuleDiagramGenerator — generates a detailed Mermaid diagram for a
|
|
6
|
-
* single module, showing its internal files and function call graph.
|
|
7
|
-
* Outputs: .mikk/diagrams/modules/{moduleId}.mmd
|
|
8
|
-
*/
|
|
9
|
-
export class ModuleDiagramGenerator {
|
|
10
|
-
constructor(
|
|
11
|
-
private contract: MikkContract,
|
|
12
|
-
private lock: MikkLock
|
|
13
|
-
) { }
|
|
14
|
-
|
|
15
|
-
generate(moduleId: string): string {
|
|
16
|
-
const module = this.contract.declared.modules.find(m => m.id === moduleId)
|
|
17
|
-
if (!module) return `%% Module "${moduleId}" not found`
|
|
18
|
-
|
|
19
|
-
const lines: string[] = []
|
|
20
|
-
lines.push(`graph TD`)
|
|
21
|
-
lines.push(` subgraph mod_${this.sanitizeId(moduleId)}["📦 Module: ${module.name}"]`)
|
|
22
|
-
|
|
23
|
-
const moduleFunctions = Object.values(this.lock.functions).filter(f => f.moduleId === moduleId)
|
|
24
|
-
|
|
25
|
-
// Group functions by file
|
|
26
|
-
const functionsByFile = new Map<string, typeof moduleFunctions>()
|
|
27
|
-
for (const fn of moduleFunctions) {
|
|
28
|
-
if (!functionsByFile.has(fn.file)) {
|
|
29
|
-
functionsByFile.set(fn.file, [])
|
|
30
|
-
}
|
|
31
|
-
functionsByFile.get(fn.file)!.push(fn)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Add file subgraphs
|
|
35
|
-
for (const [filePath, fns] of functionsByFile) {
|
|
36
|
-
const fileName = path.basename(filePath)
|
|
37
|
-
lines.push(` subgraph file_${this.sanitizeId(filePath)}["📄 ${fileName}"]`)
|
|
38
|
-
for (const fn of fns) {
|
|
39
|
-
const icon = fn.calledBy.length === 0 ? '⚡' : 'λ' // ⚡ for entry points (called by nothing internal)
|
|
40
|
-
lines.push(` ${this.sanitizeId(fn.id)}["${icon} ${fn.name}"]`)
|
|
41
|
-
}
|
|
42
|
-
lines.push(' end')
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
lines.push(' end')
|
|
46
|
-
lines.push('')
|
|
47
|
-
|
|
48
|
-
// Add call edges
|
|
49
|
-
for (const fn of moduleFunctions) {
|
|
50
|
-
for (const callTarget of fn.calls) {
|
|
51
|
-
const targetFn = this.lock.functions[callTarget]
|
|
52
|
-
if (targetFn) {
|
|
53
|
-
if (targetFn.moduleId === moduleId) {
|
|
54
|
-
// Internal call
|
|
55
|
-
lines.push(` ${this.sanitizeId(fn.id)} --> ${this.sanitizeId(callTarget)}`)
|
|
56
|
-
} else {
|
|
57
|
-
// External call
|
|
58
|
-
const targetMod = targetFn.moduleId
|
|
59
|
-
lines.push(` ${this.sanitizeId(fn.id)} -.->|"calls"| ext_${this.sanitizeId(callTarget)}["🔗 ${targetFn.name}<br/>(${targetMod})"]`)
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
lines.push('')
|
|
66
|
-
lines.push(' classDef default fill:#f9f9f9,stroke:#333,stroke-width:1px')
|
|
67
|
-
lines.push(' classDef external fill:#ecf0f1,stroke:#bdc3c7,stroke-dasharray: 5 5')
|
|
68
|
-
|
|
69
|
-
return lines.join('\n')
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
private sanitizeId(id: string): string {
|
|
73
|
-
return id.replace(/[^a-zA-Z0-9_]/g, '_')
|
|
74
|
-
}
|
|
75
|
-
}
|
|
1
|
+
import * as path from 'node:path'
|
|
2
|
+
import type { MikkContract, MikkLock } from '@getmikk/core'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ModuleDiagramGenerator — generates a detailed Mermaid diagram for a
|
|
6
|
+
* single module, showing its internal files and function call graph.
|
|
7
|
+
* Outputs: .mikk/diagrams/modules/{moduleId}.mmd
|
|
8
|
+
*/
|
|
9
|
+
export class ModuleDiagramGenerator {
|
|
10
|
+
constructor(
|
|
11
|
+
private contract: MikkContract,
|
|
12
|
+
private lock: MikkLock
|
|
13
|
+
) { }
|
|
14
|
+
|
|
15
|
+
generate(moduleId: string): string {
|
|
16
|
+
const module = this.contract.declared.modules.find(m => m.id === moduleId)
|
|
17
|
+
if (!module) return `%% Module "${moduleId}" not found`
|
|
18
|
+
|
|
19
|
+
const lines: string[] = []
|
|
20
|
+
lines.push(`graph TD`)
|
|
21
|
+
lines.push(` subgraph mod_${this.sanitizeId(moduleId)}["📦 Module: ${module.name}"]`)
|
|
22
|
+
|
|
23
|
+
const moduleFunctions = Object.values(this.lock.functions).filter(f => f.moduleId === moduleId)
|
|
24
|
+
|
|
25
|
+
// Group functions by file
|
|
26
|
+
const functionsByFile = new Map<string, typeof moduleFunctions>()
|
|
27
|
+
for (const fn of moduleFunctions) {
|
|
28
|
+
if (!functionsByFile.has(fn.file)) {
|
|
29
|
+
functionsByFile.set(fn.file, [])
|
|
30
|
+
}
|
|
31
|
+
functionsByFile.get(fn.file)!.push(fn)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Add file subgraphs
|
|
35
|
+
for (const [filePath, fns] of functionsByFile) {
|
|
36
|
+
const fileName = path.basename(filePath)
|
|
37
|
+
lines.push(` subgraph file_${this.sanitizeId(filePath)}["📄 ${fileName}"]`)
|
|
38
|
+
for (const fn of fns) {
|
|
39
|
+
const icon = fn.calledBy.length === 0 ? '⚡' : 'λ' // ⚡ for entry points (called by nothing internal)
|
|
40
|
+
lines.push(` ${this.sanitizeId(fn.id)}["${icon} ${fn.name}"]`)
|
|
41
|
+
}
|
|
42
|
+
lines.push(' end')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
lines.push(' end')
|
|
46
|
+
lines.push('')
|
|
47
|
+
|
|
48
|
+
// Add call edges
|
|
49
|
+
for (const fn of moduleFunctions) {
|
|
50
|
+
for (const callTarget of fn.calls) {
|
|
51
|
+
const targetFn = this.lock.functions[callTarget]
|
|
52
|
+
if (targetFn) {
|
|
53
|
+
if (targetFn.moduleId === moduleId) {
|
|
54
|
+
// Internal call
|
|
55
|
+
lines.push(` ${this.sanitizeId(fn.id)} --> ${this.sanitizeId(callTarget)}`)
|
|
56
|
+
} else {
|
|
57
|
+
// External call
|
|
58
|
+
const targetMod = targetFn.moduleId
|
|
59
|
+
lines.push(` ${this.sanitizeId(fn.id)} -.->|"calls"| ext_${this.sanitizeId(callTarget)}["🔗 ${targetFn.name}<br/>(${targetMod})"]`)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
lines.push('')
|
|
66
|
+
lines.push(' classDef default fill:#f9f9f9,stroke:#333,stroke-width:1px')
|
|
67
|
+
lines.push(' classDef external fill:#ecf0f1,stroke:#bdc3c7,stroke-dasharray: 5 5')
|
|
68
|
+
|
|
69
|
+
return lines.join('\n')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private sanitizeId(id: string): string {
|
|
73
|
+
return id.replace(/[^a-zA-Z0-9_]/g, '_')
|
|
74
|
+
}
|
|
75
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export { DiagramOrchestrator } from './orchestrator.js'
|
|
2
|
-
export { MainDiagramGenerator } from './generators/main-diagram.js'
|
|
3
|
-
export { ModuleDiagramGenerator } from './generators/module-diagram.js'
|
|
4
|
-
export { ImpactDiagramGenerator } from './generators/impact-diagram.js'
|
|
5
|
-
export { HealthDiagramGenerator } from './generators/health-diagram.js'
|
|
6
|
-
export { FlowDiagramGenerator } from './generators/flow-diagram.js'
|
|
7
|
-
export { CapsuleDiagramGenerator } from './generators/capsule-diagram.js'
|
|
8
|
-
export { DependencyMatrixGenerator } from './generators/dependency-matrix.js'
|
|
1
|
+
export { DiagramOrchestrator } from './orchestrator.js'
|
|
2
|
+
export { MainDiagramGenerator } from './generators/main-diagram.js'
|
|
3
|
+
export { ModuleDiagramGenerator } from './generators/module-diagram.js'
|
|
4
|
+
export { ImpactDiagramGenerator } from './generators/impact-diagram.js'
|
|
5
|
+
export { HealthDiagramGenerator } from './generators/health-diagram.js'
|
|
6
|
+
export { FlowDiagramGenerator } from './generators/flow-diagram.js'
|
|
7
|
+
export { CapsuleDiagramGenerator } from './generators/capsule-diagram.js'
|
|
8
|
+
export { DependencyMatrixGenerator } from './generators/dependency-matrix.js'
|
package/src/orchestrator.ts
CHANGED
|
@@ -1,76 +1,76 @@
|
|
|
1
|
-
import * as path from 'node:path'
|
|
2
|
-
import * as fs from 'node:fs/promises'
|
|
3
|
-
import type { MikkContract, MikkLock } from '@getmikk/core'
|
|
4
|
-
import { MainDiagramGenerator } from './generators/main-diagram.js'
|
|
5
|
-
import { ModuleDiagramGenerator } from './generators/module-diagram.js'
|
|
6
|
-
import { ImpactDiagramGenerator } from './generators/impact-diagram.js'
|
|
7
|
-
import { HealthDiagramGenerator } from './generators/health-diagram.js'
|
|
8
|
-
import { FlowDiagramGenerator } from './generators/flow-diagram.js'
|
|
9
|
-
import { CapsuleDiagramGenerator } from './generators/capsule-diagram.js'
|
|
10
|
-
import { DependencyMatrixGenerator } from './generators/dependency-matrix.js'
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* DiagramOrchestrator — generates all diagram types and writes them to
|
|
14
|
-
* the .mikk/diagrams/ directory structure.
|
|
15
|
-
*/
|
|
16
|
-
export class DiagramOrchestrator {
|
|
17
|
-
constructor(
|
|
18
|
-
private contract: MikkContract,
|
|
19
|
-
private lock: MikkLock,
|
|
20
|
-
private projectRoot: string
|
|
21
|
-
) { }
|
|
22
|
-
|
|
23
|
-
/** Generate all diagrams */
|
|
24
|
-
async generateAll(): Promise<{ generated: string[] }> {
|
|
25
|
-
const generated: string[] = []
|
|
26
|
-
|
|
27
|
-
// Main diagram
|
|
28
|
-
const mainGen = new MainDiagramGenerator(this.contract, this.lock)
|
|
29
|
-
await this.writeDiagram('diagrams/main.mmd', mainGen.generate())
|
|
30
|
-
generated.push('diagrams/main.mmd')
|
|
31
|
-
|
|
32
|
-
// Health diagram
|
|
33
|
-
const healthGen = new HealthDiagramGenerator(this.contract, this.lock)
|
|
34
|
-
await this.writeDiagram('diagrams/health.mmd', healthGen.generate())
|
|
35
|
-
generated.push('diagrams/health.mmd')
|
|
36
|
-
|
|
37
|
-
// Flow diagram (entry points)
|
|
38
|
-
const flowGen = new FlowDiagramGenerator(this.lock)
|
|
39
|
-
await this.writeDiagram('diagrams/flows/entry-points.mmd', flowGen.generateEntryPoints())
|
|
40
|
-
generated.push('diagrams/flows/entry-points.mmd')
|
|
41
|
-
|
|
42
|
-
// Dependency matrix
|
|
43
|
-
const matrixGen = new DependencyMatrixGenerator(this.contract, this.lock)
|
|
44
|
-
await this.writeDiagram('diagrams/dependency-matrix.mmd', matrixGen.generate())
|
|
45
|
-
generated.push('diagrams/dependency-matrix.mmd')
|
|
46
|
-
|
|
47
|
-
// Per-module diagrams
|
|
48
|
-
for (const module of this.contract.declared.modules) {
|
|
49
|
-
const moduleGen = new ModuleDiagramGenerator(this.contract, this.lock)
|
|
50
|
-
await this.writeDiagram(`diagrams/modules/${module.id}.mmd`, moduleGen.generate(module.id))
|
|
51
|
-
generated.push(`diagrams/modules/${module.id}.mmd`)
|
|
52
|
-
|
|
53
|
-
const capsuleGen = new CapsuleDiagramGenerator(this.contract, this.lock)
|
|
54
|
-
await this.writeDiagram(`diagrams/capsules/${module.id}.mmd`, capsuleGen.generate(module.id))
|
|
55
|
-
generated.push(`diagrams/capsules/${module.id}.mmd`)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return { generated }
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** Generate impact diagram for specific changes */
|
|
62
|
-
async generateImpact(changedIds: string[], impactedIds: string[]): Promise<string> {
|
|
63
|
-
const impactGen = new ImpactDiagramGenerator(this.lock)
|
|
64
|
-
const diagram = impactGen.generate(changedIds, impactedIds)
|
|
65
|
-
const timestamp = Date.now()
|
|
66
|
-
const filename = `diagrams/impact/impact-${timestamp}.mmd`
|
|
67
|
-
await this.writeDiagram(filename, diagram)
|
|
68
|
-
return filename
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
private async writeDiagram(relativePath: string, content: string): Promise<void> {
|
|
72
|
-
const fullPath = path.join(this.projectRoot, '.mikk', relativePath)
|
|
73
|
-
await fs.mkdir(path.dirname(fullPath), { recursive: true })
|
|
74
|
-
await fs.writeFile(fullPath, content, 'utf-8')
|
|
75
|
-
}
|
|
76
|
-
}
|
|
1
|
+
import * as path from 'node:path'
|
|
2
|
+
import * as fs from 'node:fs/promises'
|
|
3
|
+
import type { MikkContract, MikkLock } from '@getmikk/core'
|
|
4
|
+
import { MainDiagramGenerator } from './generators/main-diagram.js'
|
|
5
|
+
import { ModuleDiagramGenerator } from './generators/module-diagram.js'
|
|
6
|
+
import { ImpactDiagramGenerator } from './generators/impact-diagram.js'
|
|
7
|
+
import { HealthDiagramGenerator } from './generators/health-diagram.js'
|
|
8
|
+
import { FlowDiagramGenerator } from './generators/flow-diagram.js'
|
|
9
|
+
import { CapsuleDiagramGenerator } from './generators/capsule-diagram.js'
|
|
10
|
+
import { DependencyMatrixGenerator } from './generators/dependency-matrix.js'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* DiagramOrchestrator — generates all diagram types and writes them to
|
|
14
|
+
* the .mikk/diagrams/ directory structure.
|
|
15
|
+
*/
|
|
16
|
+
export class DiagramOrchestrator {
|
|
17
|
+
constructor(
|
|
18
|
+
private contract: MikkContract,
|
|
19
|
+
private lock: MikkLock,
|
|
20
|
+
private projectRoot: string
|
|
21
|
+
) { }
|
|
22
|
+
|
|
23
|
+
/** Generate all diagrams */
|
|
24
|
+
async generateAll(): Promise<{ generated: string[] }> {
|
|
25
|
+
const generated: string[] = []
|
|
26
|
+
|
|
27
|
+
// Main diagram
|
|
28
|
+
const mainGen = new MainDiagramGenerator(this.contract, this.lock)
|
|
29
|
+
await this.writeDiagram('diagrams/main.mmd', mainGen.generate())
|
|
30
|
+
generated.push('diagrams/main.mmd')
|
|
31
|
+
|
|
32
|
+
// Health diagram
|
|
33
|
+
const healthGen = new HealthDiagramGenerator(this.contract, this.lock)
|
|
34
|
+
await this.writeDiagram('diagrams/health.mmd', healthGen.generate())
|
|
35
|
+
generated.push('diagrams/health.mmd')
|
|
36
|
+
|
|
37
|
+
// Flow diagram (entry points)
|
|
38
|
+
const flowGen = new FlowDiagramGenerator(this.lock)
|
|
39
|
+
await this.writeDiagram('diagrams/flows/entry-points.mmd', flowGen.generateEntryPoints())
|
|
40
|
+
generated.push('diagrams/flows/entry-points.mmd')
|
|
41
|
+
|
|
42
|
+
// Dependency matrix
|
|
43
|
+
const matrixGen = new DependencyMatrixGenerator(this.contract, this.lock)
|
|
44
|
+
await this.writeDiagram('diagrams/dependency-matrix.mmd', matrixGen.generate())
|
|
45
|
+
generated.push('diagrams/dependency-matrix.mmd')
|
|
46
|
+
|
|
47
|
+
// Per-module diagrams
|
|
48
|
+
for (const module of this.contract.declared.modules) {
|
|
49
|
+
const moduleGen = new ModuleDiagramGenerator(this.contract, this.lock)
|
|
50
|
+
await this.writeDiagram(`diagrams/modules/${module.id}.mmd`, moduleGen.generate(module.id))
|
|
51
|
+
generated.push(`diagrams/modules/${module.id}.mmd`)
|
|
52
|
+
|
|
53
|
+
const capsuleGen = new CapsuleDiagramGenerator(this.contract, this.lock)
|
|
54
|
+
await this.writeDiagram(`diagrams/capsules/${module.id}.mmd`, capsuleGen.generate(module.id))
|
|
55
|
+
generated.push(`diagrams/capsules/${module.id}.mmd`)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { generated }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Generate impact diagram for specific changes */
|
|
62
|
+
async generateImpact(changedIds: string[], impactedIds: string[]): Promise<string> {
|
|
63
|
+
const impactGen = new ImpactDiagramGenerator(this.lock)
|
|
64
|
+
const diagram = impactGen.generate(changedIds, impactedIds)
|
|
65
|
+
const timestamp = Date.now()
|
|
66
|
+
const filename = `diagrams/impact/impact-${timestamp}.mmd`
|
|
67
|
+
await this.writeDiagram(filename, diagram)
|
|
68
|
+
return filename
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private async writeDiagram(relativePath: string, content: string): Promise<void> {
|
|
72
|
+
const fullPath = path.join(this.projectRoot, '.mikk', relativePath)
|
|
73
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true })
|
|
74
|
+
await fs.writeFile(fullPath, content, 'utf-8')
|
|
75
|
+
}
|
|
76
|
+
}
|
package/tests/smoke.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { expect, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
test("smoke test - diagram-generator", () => {
|
|
4
|
-
expect(true).toBe(true);
|
|
5
|
-
});
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
test("smoke test - diagram-generator", () => {
|
|
4
|
+
expect(true).toBe(true);
|
|
5
|
+
});
|
package/tsconfig.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.base.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"outDir": "dist",
|
|
5
|
-
"rootDir": "src"
|
|
6
|
-
},
|
|
7
|
-
"include": [
|
|
8
|
-
"src/**/*"
|
|
9
|
-
],
|
|
10
|
-
"exclude": [
|
|
11
|
-
"node_modules",
|
|
12
|
-
"dist",
|
|
13
|
-
"tests"
|
|
14
|
-
]
|
|
15
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "dist",
|
|
5
|
+
"rootDir": "src"
|
|
6
|
+
},
|
|
7
|
+
"include": [
|
|
8
|
+
"src/**/*"
|
|
9
|
+
],
|
|
10
|
+
"exclude": [
|
|
11
|
+
"node_modules",
|
|
12
|
+
"dist",
|
|
13
|
+
"tests"
|
|
14
|
+
]
|
|
15
|
+
}
|