@likec4/generators 1.31.0 → 1.32.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/d2/generate-d2.d.ts +3 -2
  2. package/dist/d2/generate-d2.js +2 -1
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +1 -0
  5. package/dist/mmd/generate-mmd.d.ts +3 -2
  6. package/dist/mmd/generate-mmd.js +2 -1
  7. package/dist/model/generate-aux.d.ts +2 -0
  8. package/dist/model/generate-aux.js +66 -0
  9. package/dist/model/generate-likec4-model.d.ts +2 -2
  10. package/dist/model/generate-likec4-model.js +8 -10
  11. package/dist/puml/generate-puml.d.ts +2 -0
  12. package/dist/puml/generate-puml.js +176 -0
  13. package/dist/puml/index.d.ts +1 -0
  14. package/dist/puml/index.js +1 -0
  15. package/dist/react/generate-react-types.d.ts +2 -2
  16. package/dist/react/generate-react-types.js +14 -73
  17. package/dist/views-data-ts/generate-views-data.d.ts +9 -0
  18. package/package.json +7 -7
  19. package/src/__mocks__/data.ts +83 -83
  20. package/src/d2/__snapshots__/generate-d2.spec.ts.snap +78 -0
  21. package/src/d2/generate-d2.spec.ts +91 -4
  22. package/src/d2/generate-d2.ts +20 -15
  23. package/src/index.ts +1 -0
  24. package/src/mmd/generate-mmd.spec.ts +12 -4
  25. package/src/mmd/generate-mmd.ts +25 -20
  26. package/src/model/__snapshots__/aux.generate-valid-code.snap +56 -0
  27. package/src/model/__snapshots__/{likec4-model.snap → likec4.computed-model.snap} +342 -165
  28. package/src/model/__snapshots__/likec4.parsed-model.snap +671 -0
  29. package/src/model/generate-aux.spec.ts +65 -0
  30. package/src/model/generate-aux.ts +72 -0
  31. package/src/model/generate-likec4-model.spec.ts +34 -8
  32. package/src/model/generate-likec4-model.ts +13 -14
  33. package/src/puml/__snapshots__/generate-puml.spec.ts.snap +184 -0
  34. package/src/puml/generate-puml.spec.ts +26 -0
  35. package/src/puml/generate-puml.ts +264 -0
  36. package/src/puml/index.ts +1 -0
  37. package/src/react/__snapshots__/valid-code.snap +111 -0
  38. package/src/react/generate-react-types.spec.ts +67 -0
  39. package/src/react/generate-react-types.ts +16 -77
  40. package/src/views-data-ts/generate-views-data.ts +18 -9
@@ -0,0 +1,72 @@
1
+ import {
2
+ compareNatural,
3
+ sortNaturalByFqn,
4
+ } from '@likec4/core'
5
+ import { type AnyLikeC4Model } from '@likec4/core/model'
6
+ import { keys, map, pipe, values } from 'remeda'
7
+
8
+ function toUnion(elements: string[]) {
9
+ if (elements.length === 0) {
10
+ return 'never'
11
+ }
12
+ let union = elements
13
+ .sort(compareNatural)
14
+ .map(v => ` | ${JSON.stringify(v)}`)
15
+ return union.join('\n').trimStart()
16
+ }
17
+
18
+ function elementIdToUnion(_elements: Record<string, { id: string }>) {
19
+ const elements = values(_elements)
20
+ if (elements.length === 0) {
21
+ return 'never'
22
+ }
23
+ let union = pipe(
24
+ elements,
25
+ sortNaturalByFqn,
26
+ map(v => ` | ${JSON.stringify(v.id)}`),
27
+ )
28
+ return union.join('\n').trimStart()
29
+ }
30
+
31
+ export function generateAux(model: AnyLikeC4Model) {
32
+ return `
33
+ import type { Aux, SpecAux } from '@likec4/core/types';
34
+
35
+ export type $Specs = SpecAux<
36
+ // Element kinds
37
+ ${toUnion(keys(model.specification.elements))},
38
+ // Deployment kinds
39
+ ${toUnion(keys(model.specification.deployments ?? {}))},
40
+ // Relationship kinds
41
+ ${toUnion(keys(model.specification.relationships ?? {}))},
42
+ // Tags
43
+ ${toUnion(keys(model.specification.tags ?? {}))},
44
+ // Metadata keys
45
+ ${toUnion(model.specification.metadataKeys ?? [])}
46
+ >
47
+
48
+ export type $Aux = Aux<
49
+ ${JSON.stringify(model.stage)},
50
+ // Elements
51
+ ${elementIdToUnion(model.$data.elements)},
52
+ // Deployments
53
+ ${elementIdToUnion(model.$data.deployments.elements)},
54
+ // Views
55
+ ${toUnion(keys(model.$data.views))},
56
+ // Project ID
57
+ ${JSON.stringify(model.projectId)},
58
+ $Specs
59
+ >
60
+
61
+ export type $ElementId = $Aux['ElementId']
62
+ export type $DeploymentId = $Aux['DeploymentId']
63
+ export type $ViewId = $Aux['ViewId']
64
+
65
+ export type $ElementKind = $Aux['ElementKind']
66
+ export type $RelationKind = $Aux['RelationKind']
67
+ export type $DeploymentKind = $Aux['DeploymentKind']
68
+ export type $Tag = $Aux['Tag']
69
+ export type $Tags = readonly $Aux['Tag'][]
70
+ export type $MetadataKey = $Aux['MetadataKey']
71
+ `.trimStart()
72
+ }
@@ -1,6 +1,6 @@
1
- import { LikeC4Model } from '@likec4/core'
2
1
  import { Builder } from '@likec4/core/builder'
3
- import { computeViews, viewsWithReadableEdges } from '@likec4/core/compute-view'
2
+ import { computeParsedModelData, viewsWithReadableEdges } from '@likec4/core/compute-view'
3
+ import { LikeC4Model } from '@likec4/core/model'
4
4
  import { describe, it } from 'vitest'
5
5
  import { generateLikeC4Model } from './generate-likec4-model'
6
6
 
@@ -53,6 +53,11 @@ const {
53
53
  },
54
54
  },
55
55
  },
56
+ tags: {
57
+ internal: {},
58
+ external: {},
59
+ },
60
+ metadataKeys: ['key1'],
56
61
  deployments: {
57
62
  env: {},
58
63
  zone: {},
@@ -70,11 +75,17 @@ const builder = b
70
75
  mobile('mobile'),
71
76
  ),
72
77
  component('auth'),
73
- component('backend').with(
78
+ component('backend', {
79
+ metadata: {
80
+ key1: 'value1',
81
+ },
82
+ tags: ['external'],
83
+ }).with(
74
84
  component('api'),
75
85
  component('graphql'),
76
86
  ),
77
87
  component('media', {
88
+ tags: ['internal'],
78
89
  shape: 'storage',
79
90
  }),
80
91
  ),
@@ -83,6 +94,9 @@ const builder = b
83
94
  shape: 'storage',
84
95
  }),
85
96
  component('s3', {
97
+ metadata: {
98
+ key1: 'value2',
99
+ },
86
100
  shape: 'storage',
87
101
  }),
88
102
  ),
@@ -103,7 +117,13 @@ const builder = b
103
117
  $m.rel('cloud.backend.api', 'aws.rds', 'reads/writes'),
104
118
  $m.rel('cloud.backend.api', 'email', 'sends emails'),
105
119
  $m.rel('cloud.media', 'aws.s3', 'uploads'),
106
- $m.rel('email', 'customer', 'sends emails'),
120
+ $m.rel('email', 'customer', {
121
+ tags: ['external'],
122
+ title: 'sends emails',
123
+ metadata: {
124
+ key1: 'value3',
125
+ },
126
+ }),
107
127
  ),
108
128
  deployment(
109
129
  node('customer').with(
@@ -148,11 +168,17 @@ const builder = b
148
168
  ),
149
169
  ),
150
170
  )
151
- const computed = viewsWithReadableEdges(computeViews(builder.build()))
152
- const m = LikeC4Model.create(computed)
153
171
 
154
172
  describe('generateLikeC4Model', () => {
155
- it('should generate', async ({ expect }) => {
156
- await expect(generateLikeC4Model(m)).toMatchFileSnapshot('__snapshots__/likec4-model.snap')
173
+ it('parsed-model', async ({ expect }) => {
174
+ const parsed = builder.build()
175
+ const m = LikeC4Model.create(parsed)
176
+ await expect(generateLikeC4Model(m)).toMatchFileSnapshot('__snapshots__/likec4.parsed-model.snap')
177
+ })
178
+
179
+ it('computed-model', async ({ expect }) => {
180
+ const computed = viewsWithReadableEdges(computeParsedModelData(builder.build()))
181
+ const m = LikeC4Model.create(computed)
182
+ await expect(generateLikeC4Model(m)).toMatchFileSnapshot('__snapshots__/likec4.computed-model.snap')
157
183
  })
158
184
  })
@@ -1,9 +1,14 @@
1
- import { type LikeC4Model } from '@likec4/core'
1
+ import type { LikeC4Model } from '@likec4/core/model'
2
2
  import JSON5 from 'json5'
3
3
  import { CompositeGeneratorNode, toString } from 'langium/generate'
4
+ import { capitalize } from 'remeda'
5
+ import { generateAux } from './generate-aux'
4
6
 
5
- export function generateLikeC4Model(model: LikeC4Model) {
7
+ export function generateLikeC4Model(model: LikeC4Model<any>) {
6
8
  const out = new CompositeGeneratorNode()
9
+ const aux = generateAux(model)
10
+ const ModelData = capitalize(model.stage) + 'LikeC4ModelData'
11
+
7
12
  out.appendTemplate`
8
13
  /* prettier-ignore-start */
9
14
  /* eslint-disable */
@@ -13,19 +18,13 @@ export function generateLikeC4Model(model: LikeC4Model) {
13
18
  * DO NOT EDIT MANUALLY!
14
19
  ******************************************************************************/
15
20
 
16
- import { LikeC4Model } from 'likec4/model'
17
-
18
- export const likeC4Model = LikeC4Model.fromDump(${
19
- JSON5.stringify(model.$model, {
20
- space: 2,
21
- quote: '\'',
22
- })
23
- })
21
+ import { LikeC4Model } from '@likec4/core/model'
22
+ import type { ${ModelData} } from '@likec4/core/types'
23
+ ${aux}
24
24
 
25
- export type LikeC4ModelTypes = typeof likeC4Model.Aux
26
- export type LikeC4ElementId = LikeC4ModelTypes['Fqn']
27
- export type LikeC4DeploymentId = LikeC4ModelTypes['Deployment']
28
- export type LikeC4ViewId = LikeC4ModelTypes['ViewId']
25
+ export const likec4model: LikeC4Model<$Aux> = new LikeC4Model(<${ModelData}<$Aux>>(${
26
+ JSON5.stringify(model.$data, { space: 2, quote: '\'' })
27
+ } as unknown))
29
28
 
30
29
  /* prettier-ignore-end */
31
30
  `
@@ -0,0 +1,184 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`generate puml - fakeComputedView 3 Levels 1`] = `
4
+ "@startuml
5
+ title "Context: Cloud"
6
+ top to bottom direction
7
+
8
+ hide stereotype
9
+ skinparam ranksep 60
10
+ skinparam nodesep 30
11
+ skinparam {
12
+ arrowFontSize 10
13
+ defaultTextAlignment center
14
+ wrapWidth 200
15
+ maxMessageSize 100
16
+ shadowing false
17
+ }
18
+
19
+ skinparam rectangle<<Amazon>>{
20
+ BackgroundColor #3b82f6
21
+ FontColor #FFFFFF
22
+ BorderColor #3b82f6
23
+ }
24
+ skinparam rectangle<<Customer>>{
25
+ BackgroundColor #3b82f6
26
+ FontColor #FFFFFF
27
+ BorderColor #3b82f6
28
+ }
29
+ skinparam rectangle<<Support>>{
30
+ BackgroundColor #3b82f6
31
+ FontColor #FFFFFF
32
+ BorderColor #3b82f6
33
+ }
34
+ skinparam rectangle<<CloudBackendGraphql>>{
35
+ BackgroundColor #3b82f6
36
+ FontColor #FFFFFF
37
+ BorderColor #3b82f6
38
+ }
39
+ skinparam rectangle<<CloudBackendStorage>>{
40
+ BackgroundColor #3b82f6
41
+ FontColor #FFFFFF
42
+ BorderColor #3b82f6
43
+ }
44
+ skinparam rectangle<<CloudFrontendAdminPanel>>{
45
+ BackgroundColor #3b82f6
46
+ FontColor #FFFFFF
47
+ BorderColor #3b82f6
48
+ }
49
+ skinparam rectangle<<CloudFrontendDashboard>>{
50
+ BackgroundColor #3b82f6
51
+ FontColor #FFFFFF
52
+ BorderColor #3b82f6
53
+ }
54
+ rectangle "==amazon" <<Amazon>> as Amazon
55
+ rectangle "cloud" <<Cloud>> as Cloud {
56
+ skinparam RectangleBorderColor<<Cloud>> #3b82f6
57
+ skinparam RectangleFontColor<<Cloud>> #3b82f6
58
+ skinparam RectangleBorderStyle<<Cloud>> dashed
59
+
60
+ rectangle "backend" <<CloudBackend>> as CloudBackend {
61
+ skinparam RectangleBorderColor<<CloudBackend>> #3b82f6
62
+ skinparam RectangleFontColor<<CloudBackend>> #3b82f6
63
+ skinparam RectangleBorderStyle<<CloudBackend>> dashed
64
+
65
+ rectangle "==graphql" <<CloudBackendGraphql>> as CloudBackendGraphql
66
+ rectangle "==storage" <<CloudBackendStorage>> as CloudBackendStorage
67
+ }
68
+ rectangle "==adminPanel" <<CloudFrontendAdminPanel>> as CloudFrontendAdminPanel
69
+ rectangle "==dashboard" <<CloudFrontendDashboard>> as CloudFrontendDashboard
70
+ }
71
+ rectangle "==customer" <<Customer>> as Customer
72
+ rectangle "==support" <<Support>> as Support
73
+
74
+ CloudFrontendDashboard .[#777777,thickness=2].> CloudBackendGraphql
75
+ CloudFrontendAdminPanel .[#777777,thickness=2].> CloudBackendGraphql
76
+ CloudBackendStorage .[#777777,thickness=2].> Amazon
77
+ CloudBackendGraphql .[#777777,thickness=2].> CloudBackendStorage
78
+ Support .[#777777,thickness=2].> CloudFrontendAdminPanel
79
+ Customer .[#777777,thickness=2].> CloudFrontendDashboard
80
+ @enduml
81
+ "
82
+ `;
83
+
84
+ exports[`generate puml - fakeDiagram 1`] = `
85
+ "@startuml
86
+ title "fakeView"
87
+ top to bottom direction
88
+
89
+ hide stereotype
90
+ skinparam ranksep 60
91
+ skinparam nodesep 30
92
+ skinparam {
93
+ arrowFontSize 10
94
+ defaultTextAlignment center
95
+ wrapWidth 200
96
+ maxMessageSize 100
97
+ shadowing false
98
+ }
99
+
100
+ skinparam rectangle<<Amazon>>{
101
+ BackgroundColor #3b82f6
102
+ FontColor #FFFFFF
103
+ BorderColor #3b82f6
104
+ }
105
+ skinparam rectangle<<Customer>>{
106
+ BackgroundColor #3b82f6
107
+ FontColor #FFFFFF
108
+ BorderColor #3b82f6
109
+ }
110
+ skinparam rectangle<<Support>>{
111
+ BackgroundColor #3b82f6
112
+ FontColor #FFFFFF
113
+ BorderColor #3b82f6
114
+ }
115
+ skinparam rectangle<<CloudBackend>>{
116
+ BackgroundColor #3b82f6
117
+ FontColor #FFFFFF
118
+ BorderColor #3b82f6
119
+ }
120
+ skinparam rectangle<<CloudFrontend>>{
121
+ BackgroundColor #3b82f6
122
+ FontColor #FFFFFF
123
+ BorderColor #3b82f6
124
+ }
125
+ rectangle "==amazon" <<Amazon>> as Amazon
126
+ rectangle "cloud" <<Cloud>> as Cloud {
127
+ skinparam RectangleBorderColor<<Cloud>> #3b82f6
128
+ skinparam RectangleFontColor<<Cloud>> #3b82f6
129
+ skinparam RectangleBorderStyle<<Cloud>> dashed
130
+
131
+ rectangle "==backend" <<CloudBackend>> as CloudBackend
132
+ rectangle "==frontend" <<CloudFrontend>> as CloudFrontend
133
+ }
134
+ rectangle "==customer" <<Customer>> as Customer
135
+ rectangle "==support" <<Support>> as Support
136
+
137
+ CloudFrontend .[#777777,thickness=2].> CloudBackend : "<color:#777777>requests<color:#777777>"
138
+ CloudBackend .[#777777,thickness=2].> Amazon
139
+ Support .[#777777,thickness=2].> CloudFrontend
140
+ Customer .[#777777,thickness=2].> CloudFrontend : "<color:#777777>opens<color:#777777>"
141
+ @enduml
142
+ "
143
+ `;
144
+
145
+ exports[`generate puml - fakeDiagram2 1`] = `
146
+ "@startuml
147
+ title "frontend"
148
+ top to bottom direction
149
+
150
+ hide stereotype
151
+ skinparam ranksep 60
152
+ skinparam nodesep 30
153
+ skinparam {
154
+ arrowFontSize 10
155
+ defaultTextAlignment center
156
+ wrapWidth 200
157
+ maxMessageSize 100
158
+ shadowing false
159
+ }
160
+
161
+ skinparam rectangle<<Client>>{
162
+ BackgroundColor #3b82f6
163
+ FontColor #FFFFFF
164
+ BorderColor #3b82f6
165
+ }
166
+ skinparam rectangle<<SystemBackend>>{
167
+ BackgroundColor #3b82f6
168
+ FontColor #FFFFFF
169
+ BorderColor #3b82f6
170
+ }
171
+ skinparam rectangle<<SystemFrontend>>{
172
+ BackgroundColor #3b82f6
173
+ FontColor #FFFFFF
174
+ BorderColor #3b82f6
175
+ }
176
+ rectangle "==client" <<Client>> as Client
177
+ rectangle "==backend" <<SystemBackend>> as SystemBackend
178
+ rectangle "==frontend" <<SystemFrontend>> as SystemFrontend
179
+
180
+ SystemFrontend .[#777777,thickness=2].> SystemBackend : "<color:#777777>requests<color:#777777>"
181
+ Client .[#777777,thickness=2].> SystemFrontend : "<color:#777777>opens<color:#777777>"
182
+ @enduml
183
+ "
184
+ `;
@@ -0,0 +1,26 @@
1
+ import type { LikeC4ViewModel } from '@likec4/core/model'
2
+ import type { aux, ProcessedView } from '@likec4/core/types'
3
+ import { expect, test, vi } from 'vitest'
4
+ import { fakeComputedView3Levels, fakeDiagram, fakeDiagram2 } from '../__mocks__/data'
5
+ import { generatePuml } from './generate-puml'
6
+
7
+ const mockViewModel = vi.fn(function($view: ProcessedView) {
8
+ return {
9
+ $view,
10
+ $model: {
11
+ specification: {},
12
+ },
13
+ } as unknown as LikeC4ViewModel<aux.Unknown>
14
+ })
15
+
16
+ test('generate puml - fakeDiagram', () => {
17
+ expect(generatePuml(mockViewModel(fakeDiagram))).toMatchSnapshot()
18
+ })
19
+
20
+ test('generate puml - fakeDiagram2', () => {
21
+ expect(generatePuml(mockViewModel(fakeDiagram2))).toMatchSnapshot()
22
+ })
23
+
24
+ test('generate puml - fakeComputedView 3 Levels', () => {
25
+ expect(generatePuml(mockViewModel(fakeComputedView3Levels))).toMatchSnapshot()
26
+ })
@@ -0,0 +1,264 @@
1
+ import type { aux, LikeC4ViewModel } from '@likec4/core/model'
2
+ import type {
3
+ ComputedEdge,
4
+ ComputedNode,
5
+ ElementThemeColorValues,
6
+ KeysOf,
7
+ NodeId,
8
+ ProcessedView,
9
+ RelationshipThemeColorValues,
10
+ ThemeColorValues,
11
+ } from '@likec4/core/types'
12
+ import { CompositeGeneratorNode, joinToNode, NL, toString } from 'langium/generate'
13
+ import { isNullish as isNil } from 'remeda'
14
+
15
+ const capitalizeFirstLetter = (value: string) => value.charAt(0).toLocaleUpperCase() + value.slice(1)
16
+
17
+ const fqnName = (nodeId: string): string => nodeId.split('.').map(capitalizeFirstLetter).join('')
18
+
19
+ const nodeName = (node: ComputedNode): string => {
20
+ return fqnName(node.parent ? node.id.slice(node.parent.length + 1) : node.id)
21
+ }
22
+
23
+ const pumlColor = (
24
+ color: string | undefined,
25
+ customColorProvider: (colorKey: string) => string | undefined,
26
+ defaultColor: string = '#3b82f6',
27
+ ) => {
28
+ switch (color) {
29
+ case 'blue':
30
+ case 'primary': {
31
+ return '#3b82f6'
32
+ }
33
+ case 'amber': {
34
+ return '#a35829'
35
+ }
36
+ case 'gray': {
37
+ return '#737373'
38
+ }
39
+ case 'green': {
40
+ return '#428a4f'
41
+ }
42
+ case 'indigo': {
43
+ return '#6366f1'
44
+ }
45
+ case 'slate':
46
+ case 'muted': {
47
+ return '#64748b'
48
+ }
49
+ case 'red': {
50
+ return '#ac4d39'
51
+ }
52
+ case 'sky':
53
+ case 'secondary': {
54
+ return '#0284c7'
55
+ }
56
+ case null:
57
+ case undefined: {
58
+ return defaultColor
59
+ }
60
+ default:
61
+ return customColorProvider(color) || color
62
+ }
63
+ }
64
+
65
+ const pumlDirection = ({ autoLayout }: ProcessedView) => {
66
+ switch (autoLayout.direction) {
67
+ case 'TB': {
68
+ return 'top to bottom'
69
+ }
70
+ case 'BT': {
71
+ console.warn('Bottom to top direction is not supported. Defaulting to top to bottom.')
72
+ return 'top to bottom'
73
+ }
74
+ case 'LR': {
75
+ return 'left to right'
76
+ }
77
+ case 'RL': {
78
+ console.warn('Right to left direction is not supported. Defaulting to left to right.')
79
+ return 'left to right'
80
+ }
81
+ }
82
+ }
83
+
84
+ const pumlShape = ({ shape }: ComputedNode) => {
85
+ switch (shape) {
86
+ case 'queue':
87
+ case 'rectangle':
88
+ case 'person': {
89
+ return shape
90
+ }
91
+ case 'storage':
92
+ case 'cylinder': {
93
+ return 'database' as const
94
+ }
95
+ case 'mobile':
96
+ case 'browser': {
97
+ return 'rectangle' as const
98
+ }
99
+ }
100
+ }
101
+
102
+ const escapeLabel = (label: string | null | undefined) => isNil(label) ? null : JSON.stringify(label).slice(1, -1)
103
+
104
+ export function generatePuml(viewmodel: LikeC4ViewModel<aux.Unknown>) {
105
+ const view = viewmodel.$view
106
+ const customColorDefinitions = viewmodel.$model.specification.customColors ?? {}
107
+ const { nodes, edges } = view
108
+ const customColors = new Map<string, ThemeColorValues>(Object.entries(customColorDefinitions))
109
+ const elemntColorProvider = (key: KeysOf<ElementThemeColorValues>) => (colorKey: string) =>
110
+ customColors.get(colorKey)?.elements[key]
111
+ const relationshipsColorProvider = (key: KeysOf<RelationshipThemeColorValues>) => (colorKey: string) =>
112
+ customColors.get(colorKey)?.relationships[key]
113
+ const names = new Map<NodeId, string>()
114
+
115
+ const printHeader = () => {
116
+ return new CompositeGeneratorNode()
117
+ .append('title "', view.title || view.id, '"', NL)
118
+ .append(pumlDirection(view), ' direction', NL)
119
+ }
120
+
121
+ const printTheme = () => {
122
+ return new CompositeGeneratorNode()
123
+ .append('hide stereotype', NL)
124
+ .append('skinparam ranksep ', '60', NL)
125
+ .append('skinparam nodesep ', '30', NL)
126
+ .append('skinparam {', NL)
127
+ .indent({
128
+ indentedChildren: indent =>
129
+ indent
130
+ .append('arrowFontSize ', '10', NL)
131
+ .append('defaultTextAlignment ', 'center', NL)
132
+ .append('wrapWidth ', '200', NL)
133
+ .append('maxMessageSize ', '100', NL)
134
+ .append('shadowing ', 'false', NL),
135
+ indentation: 2,
136
+ })
137
+ .append('}', NL)
138
+ }
139
+
140
+ const printStereotypes = (node: ComputedNode): CompositeGeneratorNode => {
141
+ const shape = pumlShape(node)
142
+ const fqn = fqnName(node.id)
143
+
144
+ return new CompositeGeneratorNode()
145
+ .append('skinparam ', shape, '<<', fqn, '>>', '{', NL)
146
+ .indent({
147
+ indentedChildren: indent =>
148
+ indent
149
+ .append('BackgroundColor ', pumlColor(node.color, elemntColorProvider('fill')), NL)
150
+ .append(
151
+ 'FontColor ',
152
+ customColors.has(node.color)
153
+ ? pumlColor(node.color, elemntColorProvider('hiContrast'))
154
+ : '#FFFFFF',
155
+ NL,
156
+ )
157
+ .append('BorderColor ', pumlColor(node.color, elemntColorProvider('stroke')), NL),
158
+ indentation: 2,
159
+ })
160
+ .append('}', NL)
161
+ }
162
+
163
+ const printNode = (node: ComputedNode): CompositeGeneratorNode => {
164
+ const shape = pumlShape(node)
165
+ const fqn = fqnName(node.id)
166
+ const label = escapeLabel(node.title) || nodeName(node)
167
+ const tech = escapeLabel(node.technology)
168
+ names.set(node.id, fqn)
169
+
170
+ return new CompositeGeneratorNode()
171
+ .append(shape, ' ')
172
+ .append('"')
173
+ .append('==', label)
174
+ .appendIf(!!tech, '\\n', '<size:10>[', tech!, ']</size>')
175
+ .appendIf(!!node.description, '\\n\\n', escapeLabel(node.description)!)
176
+ .append('"', ' <<', fqn, '>> ', 'as ', fqn, NL)
177
+ }
178
+
179
+ const printBoundary = (node: ComputedNode): CompositeGeneratorNode => {
180
+ const label = escapeLabel(node.title) || nodeName(node)
181
+ const fqn = fqnName(node.id)
182
+ names.set(node.id, fqn)
183
+
184
+ return new CompositeGeneratorNode()
185
+ .append('rectangle "', label, '" <<', fqn, '>> as ', fqn, ' {', NL)
186
+ .indent({
187
+ indentedChildren: indent =>
188
+ indent
189
+ .append(
190
+ 'skinparam ',
191
+ 'RectangleBorderColor<<',
192
+ fqn,
193
+ '>> ',
194
+ pumlColor(node.color, elemntColorProvider('fill')),
195
+ NL,
196
+ )
197
+ .append(
198
+ 'skinparam ',
199
+ 'RectangleFontColor<<',
200
+ fqn,
201
+ '>> ',
202
+ pumlColor(node.color, elemntColorProvider('fill')),
203
+ NL,
204
+ )
205
+ .append('skinparam ', 'RectangleBorderStyle<<', fqn, '>> ', 'dashed', NL, NL)
206
+ .append(joinToNode(
207
+ nodes.filter(n => n.parent === node.id),
208
+ c => c.children.length > 0 ? printBoundary(c) : printNode(c),
209
+ )),
210
+ indentation: 2,
211
+ })
212
+ .append('}', NL)
213
+ }
214
+
215
+ const printEdge = (edge: ComputedEdge): CompositeGeneratorNode => {
216
+ const tech = escapeLabel(edge.technology) || ''
217
+ const label = escapeLabel(edge.label) || ''
218
+ const color = pumlColor(edge.color, relationshipsColorProvider('lineColor'), '#777777')
219
+
220
+ const colorTag = (color: string) => `<color:${color}>`
221
+
222
+ return new CompositeGeneratorNode()
223
+ .append(names.get(edge.source), ' .[', color, ',thickness=2].> ', names.get(edge.target))
224
+ .appendIf(!!(label || tech), ' : "', colorTag(color))
225
+ .appendIf(!!label, label, colorTag(color))
226
+ .appendIf(!!(label && tech), '\\n')
227
+ .appendIf(!!tech, colorTag(color), '<size:8>[', tech, ']</size>')
228
+ .appendIf(!!(label || tech), '"')
229
+ .append(NL)
230
+ }
231
+
232
+ return toString(
233
+ new CompositeGeneratorNode()
234
+ .append('@startuml', NL)
235
+ .append(printHeader(), NL)
236
+ .append(printTheme(), NL)
237
+ .append(
238
+ joinToNode(
239
+ nodes.filter(n => n.children.length == 0),
240
+ n => printStereotypes(n),
241
+ {
242
+ appendNewLineIfNotEmpty: true,
243
+ },
244
+ ),
245
+ )
246
+ .append(
247
+ joinToNode(
248
+ nodes.filter(n => isNil(n.parent)),
249
+ n => n.children.length > 0 ? printBoundary(n) : printNode(n),
250
+ {
251
+ appendNewLineIfNotEmpty: true,
252
+ },
253
+ ),
254
+ )
255
+ .appendIf(
256
+ edges.length > 0,
257
+ NL,
258
+ joinToNode(edges, e => printEdge(e), {
259
+ appendNewLineIfNotEmpty: true,
260
+ }),
261
+ )
262
+ .append(`@enduml`, NL),
263
+ )
264
+ }
@@ -0,0 +1 @@
1
+ export * from './generate-puml'