yarramate 0.1.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/LICENSE +21 -0
- package/README.md +174 -0
- package/dist/adapter-mapping.d.ts +40 -0
- package/dist/adapter-mapping.js +200 -0
- package/dist/adapters/graphify-cli.d.ts +3 -0
- package/dist/adapters/graphify-cli.js +127 -0
- package/dist/adapters/graphify-entry.d.ts +1 -0
- package/dist/adapters/graphify-entry.js +1 -0
- package/dist/adapters/graphify.d.ts +23 -0
- package/dist/adapters/graphify.js +47 -0
- package/dist/adapters/likec4-cli.d.ts +3 -0
- package/dist/adapters/likec4-cli.js +662 -0
- package/dist/adapters/likec4-export.d.ts +25 -0
- package/dist/adapters/likec4-export.js +204 -0
- package/dist/adapters/likec4-kind-mapping.d.ts +22 -0
- package/dist/adapters/likec4-kind-mapping.js +60 -0
- package/dist/adapters/likec4-prepare.d.ts +28 -0
- package/dist/adapters/likec4-prepare.js +154 -0
- package/dist/adapters/likec4-project.d.ts +58 -0
- package/dist/adapters/likec4-project.js +176 -0
- package/dist/adapters/likec4.d.ts +4 -0
- package/dist/adapters/likec4.js +4 -0
- package/dist/architecture-state.d.ts +22 -0
- package/dist/architecture-state.js +63 -0
- package/dist/check-command.d.ts +2 -0
- package/dist/check-command.js +232 -0
- package/dist/cli-support.d.ts +24 -0
- package/dist/cli-support.js +81 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +575 -0
- package/dist/compiler.d.ts +66 -0
- package/dist/compiler.js +940 -0
- package/dist/core-contract.d.ts +43 -0
- package/dist/core-contract.js +162 -0
- package/dist/evidence.d.ts +58 -0
- package/dist/evidence.js +161 -0
- package/dist/graph.d.ts +2 -0
- package/dist/graph.js +37 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +9 -0
- package/dist/profile.d.ts +30 -0
- package/dist/profile.js +162 -0
- package/dist/projection.d.ts +42 -0
- package/dist/projection.js +183 -0
- package/dist/reconciliation.d.ts +26 -0
- package/dist/reconciliation.js +47 -0
- package/dist/source-document.d.ts +24 -0
- package/dist/source-document.js +80 -0
- package/dist/workspace.d.ts +29 -0
- package/dist/workspace.js +114 -0
- package/docs/CONSUMING-YARRAMATE.md +145 -0
- package/package.json +110 -0
- package/schema/yarramate-adapter-mapping.schema.json +59 -0
- package/schema/yarramate-check-result.schema.json +92 -0
- package/schema/yarramate-core-contract.schema.json +132 -0
- package/schema/yarramate-diagnostic-result.schema.json +64 -0
- package/schema/yarramate-document.schema.json +168 -0
- package/schema/yarramate-evidence-report.schema.json +73 -0
- package/schema/yarramate-evidence.schema.json +92 -0
- package/schema/yarramate-graph-v2.schema.json +170 -0
- package/schema/yarramate-likec4-check-result.schema.json +50 -0
- package/schema/yarramate-likec4-diagnostic-result.schema.json +69 -0
- package/schema/yarramate-likec4-generated-project-v2.schema.json +100 -0
- package/schema/yarramate-likec4-generated-project.schema.json +76 -0
- package/schema/yarramate-likec4-kind-mapping.schema.json +65 -0
- package/schema/yarramate-likec4-project.schema.json +160 -0
- package/schema/yarramate-profile.schema.json +113 -0
- package/schema/yarramate-projection-result.schema.json +177 -0
- package/schema/yarramate-projection.schema.json +126 -0
- package/schema/yarramate-reconciliation-report.schema.json +90 -0
- package/schema/yarramate-state-comparison.schema.json +61 -0
- package/schema/yarramate-workspace.schema.json +55 -0
- package/skills/yarramate-architecture/SKILL.md +135 -0
- package/skills/yarramate-architecture/agents/openai.yaml +4 -0
- package/skills/yarramate-architecture/references/journey-checklists.md +57 -0
- package/skills/yarramate-architecture/references/native-authoring.md +167 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { adapterMappingEntryLocation, adapterMappingLocation, } from '../adapter-mapping.js';
|
|
2
|
+
import { diagnosticOrder } from '../source-document.js';
|
|
3
|
+
const identifier = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
4
|
+
const valueFor = (claims, subject, predicate) => {
|
|
5
|
+
const object = claims.find((claim) => claim.subject === subject && claim.predicate === predicate)?.object;
|
|
6
|
+
return object !== undefined && 'value' in object
|
|
7
|
+
? object.value
|
|
8
|
+
: undefined;
|
|
9
|
+
};
|
|
10
|
+
const referencesFor = (claims, subject, predicate) => claims
|
|
11
|
+
.flatMap((claim) => claim.subject === subject &&
|
|
12
|
+
claim.predicate === predicate &&
|
|
13
|
+
'ref' in claim.object
|
|
14
|
+
? [claim.object.ref]
|
|
15
|
+
: [])
|
|
16
|
+
.sort();
|
|
17
|
+
const sourceForConcept = (claims, subject) => claims.find((claim) => claim.subject === subject &&
|
|
18
|
+
claim.predicate === 'yarramate/concept/kind')?.source;
|
|
19
|
+
const metadataLines = (entries, indentation) => {
|
|
20
|
+
const present = entries.filter((entry) => entry[1] !== undefined &&
|
|
21
|
+
(typeof entry[1] === 'string' || entry[1].length > 0));
|
|
22
|
+
if (present.length === 0)
|
|
23
|
+
return [];
|
|
24
|
+
return [
|
|
25
|
+
`${indentation}metadata {`,
|
|
26
|
+
...present.map(([key, value]) => Array.isArray(value)
|
|
27
|
+
? `${indentation} ${key} [${value.map(quote).join(', ')}]`
|
|
28
|
+
: `${indentation} ${key} ${quote(value)}`),
|
|
29
|
+
`${indentation}}`,
|
|
30
|
+
];
|
|
31
|
+
};
|
|
32
|
+
const kindId = (identity) => {
|
|
33
|
+
const separator = identity.lastIndexOf('#');
|
|
34
|
+
return separator === -1 ? identity : identity.slice(separator + 1);
|
|
35
|
+
};
|
|
36
|
+
const quote = (value) => `'${value.replaceAll('\\', '\\\\').replaceAll("'", "\\'").replaceAll('\n', '\\n')}'`;
|
|
37
|
+
export function exportLikeC4(projection, mapping, kindMapping, options = {}) {
|
|
38
|
+
const diagnostics = [];
|
|
39
|
+
if (mapping.adapter !== 'likec4') {
|
|
40
|
+
const source = adapterMappingLocation(mapping, 'adapter');
|
|
41
|
+
diagnostics.push({
|
|
42
|
+
severity: 'error',
|
|
43
|
+
code: 'YMLC101',
|
|
44
|
+
message: `Adapter mapping "${mapping.id}@${mapping.version}" targets "${mapping.adapter}", not "likec4"`,
|
|
45
|
+
path: source.path,
|
|
46
|
+
pointer: source.pointer,
|
|
47
|
+
line: source.line,
|
|
48
|
+
column: source.column,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const externalByNative = new Map(mapping.mappings
|
|
52
|
+
.filter(({ type }) => type === 'concept')
|
|
53
|
+
.map(({ native, external }) => [native, external]));
|
|
54
|
+
const externalConceptKind = new Map(kindMapping?.conceptKinds.map(({ native, external }) => [
|
|
55
|
+
native,
|
|
56
|
+
external,
|
|
57
|
+
]) ?? []);
|
|
58
|
+
const externalRelationshipKind = new Map(kindMapping?.relationshipKinds.map(({ native, external }) => [
|
|
59
|
+
native,
|
|
60
|
+
external,
|
|
61
|
+
]) ?? []);
|
|
62
|
+
const concepts = projection.subjects
|
|
63
|
+
.filter(({ type }) => type === 'concept')
|
|
64
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
65
|
+
const comparisonChange = new Map(options.comparison === undefined
|
|
66
|
+
? []
|
|
67
|
+
: [
|
|
68
|
+
...options.comparison.added.map(({ id }) => [id, 'added']),
|
|
69
|
+
...options.comparison.removed.map(({ id }) => [id, 'removed']),
|
|
70
|
+
...options.comparison.retained.map(({ id }) => [id, 'retained']),
|
|
71
|
+
]);
|
|
72
|
+
for (const concept of concepts) {
|
|
73
|
+
const external = externalByNative.get(concept.id);
|
|
74
|
+
if (external === undefined) {
|
|
75
|
+
const source = sourceForConcept(projection.claims, concept.id);
|
|
76
|
+
if (source === undefined)
|
|
77
|
+
continue;
|
|
78
|
+
diagnostics.push({
|
|
79
|
+
severity: 'error',
|
|
80
|
+
code: 'YMLC102',
|
|
81
|
+
message: `Projected concept "${concept.id}" has no LikeC4 mapping`,
|
|
82
|
+
subject: concept.id,
|
|
83
|
+
path: source.path,
|
|
84
|
+
pointer: source.pointer,
|
|
85
|
+
line: source.line,
|
|
86
|
+
column: source.column,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
else if (!identifier.test(external)) {
|
|
90
|
+
const entry = mapping.mappings.find(({ native, type }) => native === concept.id && type === 'concept');
|
|
91
|
+
const source = adapterMappingEntryLocation(mapping, entry, 'external');
|
|
92
|
+
diagnostics.push({
|
|
93
|
+
severity: 'error',
|
|
94
|
+
code: 'YMLC103',
|
|
95
|
+
message: `LikeC4 identity "${external}" is not a valid identifier`,
|
|
96
|
+
subject: concept.id,
|
|
97
|
+
path: source.path,
|
|
98
|
+
pointer: source.pointer,
|
|
99
|
+
line: source.line,
|
|
100
|
+
column: source.column,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (diagnostics.length > 0) {
|
|
105
|
+
return {
|
|
106
|
+
ok: false,
|
|
107
|
+
diagnostics: diagnostics.sort(diagnosticOrder),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const lines = [
|
|
111
|
+
'// Generated by YarraMate. Edit the native documents, not this file.',
|
|
112
|
+
'model {',
|
|
113
|
+
];
|
|
114
|
+
for (const concept of concepts) {
|
|
115
|
+
const external = externalByNative.get(concept.id);
|
|
116
|
+
const semanticKind = valueFor(projection.claims, concept.id, 'yarramate/concept/kind') ?? 'element';
|
|
117
|
+
const kind = externalConceptKind.get(semanticKind) ?? kindId(semanticKind);
|
|
118
|
+
const name = valueFor(projection.claims, concept.id, 'yarramate/concept/name') ?? concept.id;
|
|
119
|
+
const description = valueFor(projection.claims, concept.id, 'yarramate/concept/description');
|
|
120
|
+
const metadata = metadataLines([
|
|
121
|
+
['yarramateId', concept.id],
|
|
122
|
+
['yarramateKind', semanticKind],
|
|
123
|
+
['yarramateChange', comparisonChange.get(concept.id)],
|
|
124
|
+
[
|
|
125
|
+
'status',
|
|
126
|
+
valueFor(projection.claims, concept.id, 'yarramate/lifecycle/status'),
|
|
127
|
+
],
|
|
128
|
+
[
|
|
129
|
+
'owner',
|
|
130
|
+
referencesFor(projection.claims, concept.id, 'yarramate/ownership/owner')[0],
|
|
131
|
+
],
|
|
132
|
+
[
|
|
133
|
+
'constraints',
|
|
134
|
+
referencesFor(projection.claims, concept.id, 'yarramate/constraint/requires'),
|
|
135
|
+
],
|
|
136
|
+
], ' ');
|
|
137
|
+
if (description === undefined && metadata.length === 0) {
|
|
138
|
+
lines.push(` ${external} = ${kind} ${quote(name)}`);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
lines.push(` ${external} = ${kind} ${quote(name)} {`, ...(description === undefined
|
|
142
|
+
? []
|
|
143
|
+
: [` description ${quote(description)}`]), ...metadata, ' }');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const relationships = projection.subjects
|
|
147
|
+
.filter(({ type }) => type === 'relationship')
|
|
148
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
149
|
+
if (relationships.length > 0)
|
|
150
|
+
lines.push('');
|
|
151
|
+
for (const relationship of relationships) {
|
|
152
|
+
const structural = projection.claims.find((claim) => claim.id === relationship.id);
|
|
153
|
+
if (structural === undefined ||
|
|
154
|
+
!('ref' in structural.object)) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const source = externalByNative.get(structural.subject);
|
|
158
|
+
const target = externalByNative.get(structural.object.ref);
|
|
159
|
+
if (source === undefined || target === undefined)
|
|
160
|
+
continue;
|
|
161
|
+
const name = valueFor(projection.claims, relationship.id, 'yarramate/relationship/name');
|
|
162
|
+
const metadata = metadataLines([
|
|
163
|
+
['yarramateId', relationship.id],
|
|
164
|
+
['yarramateKind', structural.predicate],
|
|
165
|
+
['yarramateChange', comparisonChange.get(relationship.id)],
|
|
166
|
+
[
|
|
167
|
+
'status',
|
|
168
|
+
valueFor(projection.claims, relationship.id, 'yarramate/lifecycle/status'),
|
|
169
|
+
],
|
|
170
|
+
[
|
|
171
|
+
'mode',
|
|
172
|
+
valueFor(projection.claims, relationship.id, 'yarramate/access/mode'),
|
|
173
|
+
],
|
|
174
|
+
[
|
|
175
|
+
'content',
|
|
176
|
+
valueFor(projection.claims, relationship.id, 'yarramate/flow/content'),
|
|
177
|
+
],
|
|
178
|
+
], ' ');
|
|
179
|
+
lines.push(` ${source} -[${externalRelationshipKind.get(structural.predicate) ?? kindId(structural.predicate)}]-> ${target}${name === undefined ? '' : ` ${quote(name)}`}${metadata.length === 0 ? '' : ' {'}`, ...metadata, ...(metadata.length === 0 ? [] : [' }']));
|
|
180
|
+
}
|
|
181
|
+
const viewId = identifier.test(projection.projection.split('@')[0] ?? '')
|
|
182
|
+
? projection.projection.split('@')[0]
|
|
183
|
+
: 'index';
|
|
184
|
+
lines.push('}', '', 'views {', ` view ${viewId} {`, ...(projection.presentation?.title === undefined
|
|
185
|
+
? []
|
|
186
|
+
: [` title ${quote(projection.presentation.title)}`]), ...(projection.presentation?.description === undefined
|
|
187
|
+
? []
|
|
188
|
+
: [
|
|
189
|
+
` description ${quote(projection.presentation.description)}`,
|
|
190
|
+
]), ' include *', ...(options.comparison === undefined
|
|
191
|
+
? []
|
|
192
|
+
: concepts.map((concept) => {
|
|
193
|
+
const external = externalByNative.get(concept.id);
|
|
194
|
+
const change = comparisonChange.get(concept.id);
|
|
195
|
+
if (change === 'added') {
|
|
196
|
+
return ` style ${external} { color green }`;
|
|
197
|
+
}
|
|
198
|
+
if (change === 'removed') {
|
|
199
|
+
return ` style ${external} { color red; border dashed }`;
|
|
200
|
+
}
|
|
201
|
+
return ` style ${external} { color gray }`;
|
|
202
|
+
})), ' autoLayout LeftRight', ' }', '}', '');
|
|
203
|
+
return { ok: true, source: lines.join('\n') };
|
|
204
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Diagnostic, WorkspaceSource } from '../compiler.js';
|
|
2
|
+
import { type SourceLocation } from '../source-document.js';
|
|
3
|
+
export interface LikeC4KindEntry {
|
|
4
|
+
readonly native: string;
|
|
5
|
+
readonly external: string;
|
|
6
|
+
}
|
|
7
|
+
export interface LikeC4KindMapping {
|
|
8
|
+
readonly format: 'yarramate/likec4-kind-mapping/v1';
|
|
9
|
+
readonly id: string;
|
|
10
|
+
readonly version: string;
|
|
11
|
+
readonly conceptKinds: readonly LikeC4KindEntry[];
|
|
12
|
+
readonly relationshipKinds: readonly LikeC4KindEntry[];
|
|
13
|
+
}
|
|
14
|
+
export type LikeC4KindMappingLoadResult = {
|
|
15
|
+
readonly ok: true;
|
|
16
|
+
readonly mapping: LikeC4KindMapping;
|
|
17
|
+
} | {
|
|
18
|
+
readonly ok: false;
|
|
19
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
20
|
+
};
|
|
21
|
+
export declare function loadLikeC4KindMapping(source: WorkspaceSource): LikeC4KindMappingLoadResult;
|
|
22
|
+
export declare function likeC4KindMappingExternalLocation(mapping: LikeC4KindMapping, category: 'conceptKinds' | 'relationshipKinds', native: string): SourceLocation | undefined;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import Ajv2020Module from 'ajv/dist/2020.js';
|
|
2
|
+
import { diagnosticOrder, loadSourceDocument, locateSourcePath, } from '../source-document.js';
|
|
3
|
+
import kindMappingSchema from '../../schema/yarramate-likec4-kind-mapping.schema.json' with {
|
|
4
|
+
type: 'json'
|
|
5
|
+
};
|
|
6
|
+
const Ajv2020 = Ajv2020Module.default;
|
|
7
|
+
const validateSchema = new Ajv2020({ allErrors: true }).compile(kindMappingSchema);
|
|
8
|
+
const kindMappingLocations = new WeakMap();
|
|
9
|
+
const entryOrder = (left, right) => left.native.localeCompare(right.native) ||
|
|
10
|
+
left.external.localeCompare(right.external);
|
|
11
|
+
export function loadLikeC4KindMapping(source) {
|
|
12
|
+
const loaded = loadSourceDocument(source, validateSchema, 'LikeC4 kind mapping');
|
|
13
|
+
if (!loaded.ok)
|
|
14
|
+
return loaded;
|
|
15
|
+
const { value, yaml, lineCounter } = loaded.document;
|
|
16
|
+
const diagnostics = [];
|
|
17
|
+
const checkDuplicates = (field, label) => {
|
|
18
|
+
const seen = new Set();
|
|
19
|
+
for (const [index, entry] of value[field].entries()) {
|
|
20
|
+
if (seen.has(entry.native)) {
|
|
21
|
+
const location = locateSourcePath(source.path, yaml, lineCounter, [field, index, 'native'], `/${field}/${index}/native`);
|
|
22
|
+
diagnostics.push({
|
|
23
|
+
severity: 'error',
|
|
24
|
+
code: 'YMLC201',
|
|
25
|
+
message: `Native ${label} kind "${entry.native}" is mapped more than once`,
|
|
26
|
+
...location,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
seen.add(entry.native);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
checkDuplicates('conceptKinds', 'concept');
|
|
33
|
+
checkDuplicates('relationshipKinds', 'relationship');
|
|
34
|
+
if (diagnostics.length > 0) {
|
|
35
|
+
return {
|
|
36
|
+
ok: false,
|
|
37
|
+
diagnostics: diagnostics.sort(diagnosticOrder),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const mapping = {
|
|
41
|
+
...value,
|
|
42
|
+
conceptKinds: [...value.conceptKinds].sort(entryOrder),
|
|
43
|
+
relationshipKinds: [...value.relationshipKinds].sort(entryOrder),
|
|
44
|
+
};
|
|
45
|
+
const locateExternal = (field, entry) => {
|
|
46
|
+
const authoredIndex = value[field].indexOf(entry);
|
|
47
|
+
return locateSourcePath(source.path, yaml, lineCounter, [field, authoredIndex, 'external'], `/${field}/${authoredIndex}/external`);
|
|
48
|
+
};
|
|
49
|
+
kindMappingLocations.set(mapping, {
|
|
50
|
+
conceptKinds: mapping.conceptKinds.map((entry) => locateExternal('conceptKinds', entry)),
|
|
51
|
+
relationshipKinds: mapping.relationshipKinds.map((entry) => locateExternal('relationshipKinds', entry)),
|
|
52
|
+
});
|
|
53
|
+
return { ok: true, mapping };
|
|
54
|
+
}
|
|
55
|
+
export function likeC4KindMappingExternalLocation(mapping, category, native) {
|
|
56
|
+
const index = mapping[category].findIndex((entry) => entry.native === native);
|
|
57
|
+
return index === -1
|
|
58
|
+
? undefined
|
|
59
|
+
: kindMappingLocations.get(mapping)?.[category][index];
|
|
60
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type AdapterMapping } from '../adapter-mapping.js';
|
|
2
|
+
import { type Diagnostic, type WorkspaceSource } from '../compiler.js';
|
|
3
|
+
import { type ProjectionResult } from '../projection.js';
|
|
4
|
+
import { type LikeC4ExportDiagnostic } from './likec4-export.js';
|
|
5
|
+
import { type LikeC4KindMapping } from './likec4-kind-mapping.js';
|
|
6
|
+
export interface LikeC4PreparationInput {
|
|
7
|
+
readonly sources: readonly WorkspaceSource[];
|
|
8
|
+
readonly projection: WorkspaceSource;
|
|
9
|
+
readonly subjectMapping: WorkspaceSource;
|
|
10
|
+
readonly kindMapping?: WorkspaceSource;
|
|
11
|
+
readonly comparison?: {
|
|
12
|
+
readonly from: string;
|
|
13
|
+
readonly to: string;
|
|
14
|
+
};
|
|
15
|
+
readonly vocabulary: 'bundled' | 'consumer';
|
|
16
|
+
}
|
|
17
|
+
export type LikeC4PreparationDiagnostic = Diagnostic | LikeC4ExportDiagnostic;
|
|
18
|
+
export type LikeC4PreparationResult = {
|
|
19
|
+
readonly ok: true;
|
|
20
|
+
readonly source: string;
|
|
21
|
+
readonly projection: ProjectionResult;
|
|
22
|
+
readonly subjectMapping: AdapterMapping;
|
|
23
|
+
readonly kindMapping?: LikeC4KindMapping;
|
|
24
|
+
} | {
|
|
25
|
+
readonly ok: false;
|
|
26
|
+
readonly diagnostics: readonly LikeC4PreparationDiagnostic[];
|
|
27
|
+
};
|
|
28
|
+
export declare function prepareLikeC4Export(input: LikeC4PreparationInput): LikeC4PreparationResult;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { LineCounter, parseDocument } from 'yaml';
|
|
2
|
+
import { loadAdapterMapping, validateAdapterMapping, } from '../adapter-mapping.js';
|
|
3
|
+
import { compileWorkspaceWithProfileContext, } from '../compiler.js';
|
|
4
|
+
import { compareArchitectureStates } from '../architecture-state.js';
|
|
5
|
+
import { conceptKinds, relationshipPolicies } from '../profile.js';
|
|
6
|
+
import { evaluateProjection, loadProjection, } from '../projection.js';
|
|
7
|
+
import { diagnosticOrder, locateSourcePath, } from '../source-document.js';
|
|
8
|
+
import { exportLikeC4, } from './likec4-export.js';
|
|
9
|
+
import { likeC4KindMappingExternalLocation, loadLikeC4KindMapping, } from './likec4-kind-mapping.js';
|
|
10
|
+
const terminalKind = (identity) => {
|
|
11
|
+
const separator = identity.lastIndexOf('#');
|
|
12
|
+
return separator === -1 ? identity : identity.slice(separator + 1);
|
|
13
|
+
};
|
|
14
|
+
const unsupportedBundledKinds = (projection, kindMapping) => {
|
|
15
|
+
const supportedConceptKinds = new Set(conceptKinds.map(({ id }) => id));
|
|
16
|
+
const supportedRelationshipKinds = new Set(relationshipPolicies.map(({ id }) => id));
|
|
17
|
+
const mappedConceptKinds = new Map(kindMapping?.conceptKinds.map(({ native, external }) => [
|
|
18
|
+
native,
|
|
19
|
+
external,
|
|
20
|
+
]) ?? []);
|
|
21
|
+
const mappedRelationshipKinds = new Map(kindMapping?.relationshipKinds.map(({ native, external }) => [
|
|
22
|
+
native,
|
|
23
|
+
external,
|
|
24
|
+
]) ?? []);
|
|
25
|
+
const diagnostics = [];
|
|
26
|
+
for (const subject of projection.subjects) {
|
|
27
|
+
const semanticKind = subject.type === 'concept'
|
|
28
|
+
? projection.claims.find((claim) => claim.subject === subject.id &&
|
|
29
|
+
claim.predicate === 'yarramate/concept/kind' &&
|
|
30
|
+
'value' in claim.object)?.object
|
|
31
|
+
: projection.claims.find((claim) => claim.id === subject.id)
|
|
32
|
+
?.predicate;
|
|
33
|
+
const identity = typeof semanticKind === 'string'
|
|
34
|
+
? semanticKind
|
|
35
|
+
: semanticKind !== undefined && 'value' in semanticKind
|
|
36
|
+
? semanticKind.value
|
|
37
|
+
: undefined;
|
|
38
|
+
if (identity === undefined)
|
|
39
|
+
continue;
|
|
40
|
+
const external = subject.type === 'concept'
|
|
41
|
+
? (mappedConceptKinds.get(identity) ?? terminalKind(identity))
|
|
42
|
+
: (mappedRelationshipKinds.get(identity) ?? terminalKind(identity));
|
|
43
|
+
const supported = subject.type === 'concept'
|
|
44
|
+
? supportedConceptKinds.has(external)
|
|
45
|
+
: supportedRelationshipKinds.has(external);
|
|
46
|
+
if (!supported) {
|
|
47
|
+
const mappedSource = kindMapping === undefined
|
|
48
|
+
? undefined
|
|
49
|
+
: likeC4KindMappingExternalLocation(kindMapping, subject.type === 'concept'
|
|
50
|
+
? 'conceptKinds'
|
|
51
|
+
: 'relationshipKinds', identity);
|
|
52
|
+
const sourceClaim = subject.type === 'concept'
|
|
53
|
+
? projection.claims.find((claim) => claim.subject === subject.id &&
|
|
54
|
+
claim.predicate === 'yarramate/concept/kind')
|
|
55
|
+
: projection.claims.find((claim) => claim.id === subject.id);
|
|
56
|
+
const source = mappedSource ?? sourceClaim?.source;
|
|
57
|
+
if (source === undefined)
|
|
58
|
+
continue;
|
|
59
|
+
diagnostics.push({
|
|
60
|
+
severity: 'error',
|
|
61
|
+
code: 'YMLC104',
|
|
62
|
+
message: `Semantic ${subject.type} kind "${identity}" resolves to unsupported bundled LikeC4 kind "${external}"`,
|
|
63
|
+
subject: subject.id,
|
|
64
|
+
path: source.path,
|
|
65
|
+
pointer: source.pointer,
|
|
66
|
+
line: source.line,
|
|
67
|
+
column: source.column,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return diagnostics.sort(diagnosticOrder);
|
|
72
|
+
};
|
|
73
|
+
export function prepareLikeC4Export(input) {
|
|
74
|
+
const compilation = compileWorkspaceWithProfileContext(input.sources);
|
|
75
|
+
if (!compilation.ok)
|
|
76
|
+
return compilation;
|
|
77
|
+
const projection = loadProjection(input.projection);
|
|
78
|
+
if (!projection.ok)
|
|
79
|
+
return projection;
|
|
80
|
+
const projectionStateLocation = (state) => {
|
|
81
|
+
const states = projection.projection.query.states ?? [];
|
|
82
|
+
const index = states.indexOf(state);
|
|
83
|
+
const pointer = index === -1 ? '/query/states' : `/query/states/${index}`;
|
|
84
|
+
const lineCounter = new LineCounter();
|
|
85
|
+
const yaml = parseDocument(input.projection.source, {
|
|
86
|
+
lineCounter,
|
|
87
|
+
});
|
|
88
|
+
return locateSourcePath(input.projection.path, yaml, lineCounter, index === -1 ? ['query', 'states'] : ['query', 'states', index], pointer);
|
|
89
|
+
};
|
|
90
|
+
const subjectMapping = loadAdapterMapping(input.subjectMapping);
|
|
91
|
+
if (!subjectMapping.ok)
|
|
92
|
+
return subjectMapping;
|
|
93
|
+
const subjectValidation = validateAdapterMapping(compilation.graph, subjectMapping.mapping);
|
|
94
|
+
if (!subjectValidation.ok)
|
|
95
|
+
return subjectValidation;
|
|
96
|
+
const kindMapping = input.kindMapping === undefined
|
|
97
|
+
? undefined
|
|
98
|
+
: loadLikeC4KindMapping(input.kindMapping);
|
|
99
|
+
if (kindMapping !== undefined && !kindMapping.ok)
|
|
100
|
+
return kindMapping;
|
|
101
|
+
if (input.comparison !== undefined) {
|
|
102
|
+
const selectedStates = projection.projection.query.states ?? [];
|
|
103
|
+
const omittedStates = [
|
|
104
|
+
...new Set([input.comparison.from, input.comparison.to]),
|
|
105
|
+
].filter((state) => !selectedStates.includes(state));
|
|
106
|
+
if (omittedStates.length > 0) {
|
|
107
|
+
return {
|
|
108
|
+
ok: false,
|
|
109
|
+
diagnostics: omittedStates.map((state) => ({
|
|
110
|
+
severity: 'error',
|
|
111
|
+
code: 'YMLC106',
|
|
112
|
+
message: `Comparison state "${state}" is not selected by projection "${projection.projection.id}@${projection.projection.version}"`,
|
|
113
|
+
subject: state,
|
|
114
|
+
...projectionStateLocation(state),
|
|
115
|
+
})),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const projectionResult = evaluateProjection(compilation.graph, projection.projection, compilation.profileContext);
|
|
120
|
+
const comparison = input.comparison === undefined
|
|
121
|
+
? undefined
|
|
122
|
+
: compareArchitectureStates(compilation.graph, input.comparison.from, input.comparison.to);
|
|
123
|
+
if (comparison !== undefined && !comparison.ok) {
|
|
124
|
+
return {
|
|
125
|
+
ok: false,
|
|
126
|
+
diagnostics: comparison.issues.map((issue) => ({
|
|
127
|
+
severity: 'error',
|
|
128
|
+
code: 'YMLC105',
|
|
129
|
+
message: issue.message,
|
|
130
|
+
subject: issue.state,
|
|
131
|
+
...projectionStateLocation(issue.state),
|
|
132
|
+
})),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (input.vocabulary === 'bundled') {
|
|
136
|
+
const diagnostics = unsupportedBundledKinds(projectionResult, kindMapping?.mapping);
|
|
137
|
+
if (diagnostics.length > 0)
|
|
138
|
+
return { ok: false, diagnostics };
|
|
139
|
+
}
|
|
140
|
+
const exported = exportLikeC4(projectionResult, subjectMapping.mapping, kindMapping?.mapping, comparison === undefined
|
|
141
|
+
? undefined
|
|
142
|
+
: { comparison: comparison.comparison });
|
|
143
|
+
if (!exported.ok)
|
|
144
|
+
return exported;
|
|
145
|
+
return {
|
|
146
|
+
ok: true,
|
|
147
|
+
source: exported.source,
|
|
148
|
+
projection: projectionResult,
|
|
149
|
+
subjectMapping: subjectMapping.mapping,
|
|
150
|
+
...(kindMapping === undefined
|
|
151
|
+
? {}
|
|
152
|
+
: { kindMapping: kindMapping.mapping }),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { WorkspaceSource } from '../compiler.js';
|
|
2
|
+
import type { LikeC4PreparationResult } from './likec4-prepare.js';
|
|
3
|
+
import { type LikeC4ExportResult } from './likec4-export.js';
|
|
4
|
+
export interface LikeC4ProjectDefinition {
|
|
5
|
+
readonly format: 'yarramate/likec4-project/v1';
|
|
6
|
+
readonly id: string;
|
|
7
|
+
readonly version: string;
|
|
8
|
+
readonly title: string;
|
|
9
|
+
readonly mapping: string;
|
|
10
|
+
readonly kindMapping?: string;
|
|
11
|
+
readonly views: ReadonlyArray<{
|
|
12
|
+
readonly id?: string;
|
|
13
|
+
readonly projection: string;
|
|
14
|
+
readonly compare?: {
|
|
15
|
+
readonly from: string;
|
|
16
|
+
readonly to: string;
|
|
17
|
+
};
|
|
18
|
+
readonly dynamic?: {
|
|
19
|
+
readonly steps: ReadonlyArray<{
|
|
20
|
+
readonly relationship: string;
|
|
21
|
+
readonly title?: string;
|
|
22
|
+
}>;
|
|
23
|
+
};
|
|
24
|
+
readonly deployment?: LikeC4Deployment;
|
|
25
|
+
}>;
|
|
26
|
+
}
|
|
27
|
+
export interface LikeC4Deployment {
|
|
28
|
+
readonly nodes: ReadonlyArray<{
|
|
29
|
+
readonly id: string;
|
|
30
|
+
readonly kind: 'environment' | 'zone' | 'host' | 'runtime';
|
|
31
|
+
readonly name: string;
|
|
32
|
+
readonly parent?: string;
|
|
33
|
+
}>;
|
|
34
|
+
readonly instances: ReadonlyArray<{
|
|
35
|
+
readonly id: string;
|
|
36
|
+
readonly subject: string;
|
|
37
|
+
readonly node: string;
|
|
38
|
+
}>;
|
|
39
|
+
}
|
|
40
|
+
export declare const loadLikeC4ProjectDefinition: (source: WorkspaceSource) => import("../source-document.js").SourceDocumentResult<LikeC4ProjectDefinition>;
|
|
41
|
+
export interface PreparedLikeC4ProjectView {
|
|
42
|
+
readonly id?: string;
|
|
43
|
+
readonly prepared: Extract<LikeC4PreparationResult, {
|
|
44
|
+
readonly ok: true;
|
|
45
|
+
}>;
|
|
46
|
+
readonly comparison?: {
|
|
47
|
+
readonly from: string;
|
|
48
|
+
readonly to: string;
|
|
49
|
+
};
|
|
50
|
+
readonly dynamic?: {
|
|
51
|
+
readonly steps: ReadonlyArray<{
|
|
52
|
+
readonly relationship: string;
|
|
53
|
+
readonly title?: string;
|
|
54
|
+
}>;
|
|
55
|
+
};
|
|
56
|
+
readonly deployment?: LikeC4Deployment;
|
|
57
|
+
}
|
|
58
|
+
export declare function exportLikeC4Project(project: LikeC4ProjectDefinition, views: readonly PreparedLikeC4ProjectView[]): LikeC4ExportResult;
|